]> git.sesse.net Git - nageru/blobdiff - stereocompressor.cpp
Add a compressor (kindly relicensed by Rune Holm). Fixed for now, and only to get...
[nageru] / stereocompressor.cpp
diff --git a/stereocompressor.cpp b/stereocompressor.cpp
new file mode 100644 (file)
index 0000000..0d87aab
--- /dev/null
@@ -0,0 +1,67 @@
+#include <math.h>
+#include <assert.h>
+#include <algorithm>
+
+#include "stereocompressor.h"
+
+namespace {
+
+inline float compressor_knee(float x, float threshold, float inv_threshold, float inv_ratio_minus_one, float postgain)
+{
+       assert(inv_ratio_minus_one <= 0.0f);
+       if (x > threshold && inv_ratio_minus_one < 0.0f) {
+               return postgain * pow(x * inv_threshold, inv_ratio_minus_one);
+       } else {
+               return postgain;
+       }
+}
+
+}  // namespace
+
+void StereoCompressor::process(float *buf, size_t num_samples, float threshold, float ratio,
+           float attack_time, float release_time, float makeup_gain)
+{
+       float attack_increment = float(pow(2.0f, 1.0f / (attack_time * sample_rate + 1)));
+       if (attack_time == 0.0f) attack_increment = 100000;  // For instant attack reaction.
+
+       const float release_increment = float(pow(2.0f, -1.0f / (release_time * sample_rate + 1)));
+       const float peak_increment = float(pow(2.0f, -1.0f / (0.003f * sample_rate + 1)));
+
+       float inv_ratio_minus_one = 1.0f / ratio - 1.0f;
+       if (ratio > 63) inv_ratio_minus_one = -1.0f;  // Infinite ratio.
+       float inv_threshold = 1.0f / threshold;
+
+       float *left_ptr = buf;
+       float *right_ptr = buf + 1;
+
+       float peak_level = this->peak_level;
+       float compr_level = this->compr_level;
+
+       for (size_t i = 0; i < num_samples; ++i) {
+               if (fabs(*left_ptr) > peak_level) peak_level = float(fabs(*left_ptr));
+               if (fabs(*right_ptr) > peak_level) peak_level = float(fabs(*right_ptr));
+
+               if (peak_level > compr_level) {
+                       compr_level = std::min(compr_level * attack_increment, peak_level);
+               } else {
+                       compr_level = std::max(compr_level * release_increment, 0.0001f);
+               }
+
+               float scalefactor_with_gain = compressor_knee(compr_level, threshold, inv_threshold, inv_ratio_minus_one, makeup_gain);
+
+               *left_ptr *= scalefactor_with_gain;
+               left_ptr += 2;
+
+               *right_ptr *= scalefactor_with_gain;
+               right_ptr += 2;
+
+               peak_level = std::max(peak_level * peak_increment, 0.0001f);
+       }
+
+       // Store attenuation level for debug/visualization.
+       scalefactor = compressor_knee(compr_level, threshold, inv_threshold, inv_ratio_minus_one, 1.0f);
+
+       this->peak_level = peak_level;
+       this->compr_level = compr_level;
+}
+