LCOV - code coverage report
Current view: top level - src/audio/effects - multiband_compressor.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 94.7 % 113 107
Test Date: 2026-06-01 11:15:25 Functions: 100.0 % 5 5

            Line data    Source code
       1              : #include "audio/effects/multiband_compressor.h"
       2              : #include "audio/effects/effect_factory.h"
       3              : 
       4              : namespace Amplitron {
       5              : 
       6            2 : static EffectRegistrar<MultiBandCompressor> reg("MultiBand Compressor");
       7              : 
       8          105 : MultiBandCompressor::MultiBandCompressor() {
       9          585 :     params_ = {
      10              :         // Crossovers
      11           30 :         {"Low XOver",   200.0f,   50.0f,  1000.0f,   200.0f, "Hz", "Crossover frequency separating the Low and Mid frequency bands."},
      12           15 :         {"High XOver", 4000.0f, 1000.0f, 15000.0f,  4000.0f, "Hz", "Crossover frequency separating the Mid and High frequency bands."},
      13              : 
      14              :         // Low band compressor
      15           15 :         {"Low Thresh",  -20.0f,  -60.0f,     0.0f,   -20.0f, "dB", "Low band threshold: volume level above which compression occurs."},
      16           15 :         {"Low Ratio",     4.0f,    1.0f,    20.0f,     4.0f, ":1", "Low band ratio: compression strength for the Low band."},
      17           15 :         {"Low Attack",    5.0f,    0.1f,    50.0f,     5.0f, "ms", "Low band attack: how quickly the compressor acts on Low transients."},
      18           15 :         {"Low Release", 100.0f,   10.0f,   500.0f,   100.0f, "ms", "Low band release: recovery speed of the Low band compressor."},
      19           15 :         {"Low Makeup",    0.0f,    0.0f,    30.0f,     0.0f, "dB", "Low band makeup gain: volume boost applied to the compressed Low band."},
      20              : 
      21              :         // Mid band compressor
      22           15 :         {"Mid Thresh",  -20.0f,  -60.0f,     0.0f,   -20.0f, "dB", "Mid band threshold: volume level above which compression occurs."},
      23           15 :         {"Mid Ratio",     4.0f,    1.0f,    20.0f,     4.0f, ":1", "Mid band ratio: compression strength for the Mid band."},
      24           15 :         {"Mid Attack",    5.0f,    0.1f,    50.0f,     5.0f, "ms", "Mid band attack: how quickly the compressor acts on Mid transients."},
      25           15 :         {"Mid Release", 100.0f,   10.0f,   500.0f,   100.0f, "ms", "Mid band release: recovery speed of the Mid band compressor."},
      26           15 :         {"Mid Makeup",    0.0f,    0.0f,    30.0f,     0.0f, "dB", "Mid band makeup gain: volume boost applied to the compressed Mid band."},
      27              : 
      28              :         // High band compressor
      29           15 :         {"High Thresh", -20.0f,  -60.0f,     0.0f,   -20.0f, "dB", "High band threshold: volume level above which compression occurs."},
      30           15 :         {"High Ratio",     4.0f,    1.0f,    20.0f,     4.0f, ":1", "High band ratio: compression strength for the High band."},
      31           15 :         {"High Attack",    5.0f,    0.1f,    50.0f,     5.0f, "ms", "High band attack: how quickly the compressor acts on High transients."},
      32           15 :         {"High Release", 100.0f,   10.0f,   500.0f,   100.0f, "ms", "High band release: recovery speed of the High band compressor."},
      33           15 :         {"High Makeup",   0.0f,    0.0f,    30.0f,     0.0f, "dB", "High band makeup gain: volume boost applied to the compressed High band."},
      34              : 
      35              :         // Global Output
      36           15 :         {"Out Gain",      0.0f,  -20.0f,    20.0f,     0.0f, "dB", "Global output gain applied to the final summed signal."}
      37          585 :     };
      38              : 
      39           45 :     gain_reduction_db_[0].store(0.0f);
      40           45 :     gain_reduction_db_[1].store(0.0f);
      41           45 :     gain_reduction_db_[2].store(0.0f);
      42              : 
      43           60 :     set_sample_rate(DEFAULT_SAMPLE_RATE);
      44          105 : }
      45              : 
      46           69 : void MultiBandCompressor::set_sample_rate(int sample_rate) {
      47           69 :     Effect::set_sample_rate(sample_rate);
      48           69 :     recompute_coefficients();
      49           54 : }
      50              : 
      51           93 : void MultiBandCompressor::recompute_coefficients() {
      52           93 :     float f_low = params_[0].value;
      53           93 :     float f_high = params_[1].value;
      54              : 
      55              :     // Enforce that f_low < f_high logically
      56           93 :     if (f_low >= f_high) {
      57            0 :         f_low = f_high - 10.0f;
      58            0 :         if (f_low < params_[0].min_val) {
      59            0 :             f_low = params_[0].min_val;
      60            0 :             f_high = f_low + 10.0f;
      61            0 :         }
      62            0 :     }
      63              : 
      64           93 :     low_pass_filter_.calculate_coefficients(f_low, sample_rate_);
      65           93 :     low_high_pass_filter_.calculate_coefficients(f_low, sample_rate_);
      66              : 
      67           93 :     mid_pass_filter_.calculate_coefficients(f_high, sample_rate_);
      68           93 :     mid_high_pass_filter_.calculate_coefficients(f_high, sample_rate_);
      69           93 : }
      70              : 
      71           84 : void MultiBandCompressor::process(float* buffer, int num_samples) {
      72           84 :     if (!enabled_) return;
      73              : 
      74              :     // Crossover frequencies
      75           84 :     float f_low = params_[0].value;
      76           84 :     float f_high = params_[1].value;
      77              : 
      78              :     // Check if crossover filters need to recompute coefficients (thread-safe local recompute)
      79           84 :     if (f_low != cached_low_xover_ || f_high != cached_high_xover_ || sample_rate_ != cached_sample_rate_) {
      80           24 :         recompute_coefficients();
      81           24 :         cached_low_xover_ = f_low;
      82           24 :         cached_high_xover_ = f_high;
      83           24 :         cached_sample_rate_ = sample_rate_;
      84            8 :     }
      85              : 
      86              :     // Extract per-band compressor parameters
      87           84 :     float threshold_db[3] = { params_[2].value, params_[7].value, params_[12].value };
      88           84 :     float ratio[3]        = { params_[3].value, params_[8].value, params_[13].value };
      89           84 :     float attack_ms[3]    = { params_[4].value, params_[9].value, params_[14].value };
      90           84 :     float release_ms[3]   = { params_[5].value, params_[10].value, params_[15].value };
      91           84 :     float makeup[3]       = { db_to_linear(params_[6].value), db_to_linear(params_[11].value), db_to_linear(params_[16].value) };
      92           84 :     float output_gain     = db_to_linear(params_[17].value);
      93              : 
      94              :     // Apply attack/release parameter smoothing to avoid pops/clicks during UI drags
      95              :     // Keep smoothing short to reduce zipper noise while minimizing parameter-lag audibility.
      96           84 :     const float alpha = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.002f)); // 2 ms smoothing time constant
      97          336 :     for (int b = 0; b < 3; ++b) {
      98          252 :         smoothed_attack_ms_[b]  += alpha * (attack_ms[b]  - smoothed_attack_ms_[b]);
      99          252 :         smoothed_release_ms_[b] += alpha * (release_ms[b] - smoothed_release_ms_[b]);
     100           84 :     }
     101              : 
     102              :     // Calculate filter coefficients for envelope followers
     103              :     float attack_coeff[3], release_coeff[3];
     104          336 :     for (int b = 0; b < 3; ++b) {
     105          252 :         attack_coeff[b]  = EnvelopeFollower::time_to_coeff(smoothed_attack_ms_[b], sample_rate_);
     106          252 :         release_coeff[b] = EnvelopeFollower::time_to_coeff(smoothed_release_ms_[b], sample_rate_);
     107           84 :     }
     108              : 
     109              :     // Crossover processing and dynamic range compression
     110        67668 :     for (int i = 0; i < num_samples; ++i) {
     111        67584 :         float x = buffer[i];
     112              : 
     113              :         // 1. Split band via subtractive crossovers
     114        67584 :         float x_low = low_pass_filter_.process(x);
     115        67584 :         float x_high = mid_high_pass_filter_.process(x);
     116        67584 :         float x_mid = x - x_low - x_high;
     117              : 
     118              :         // 2. Compute envelope followers per band (using process_additive matching compressor)
     119        22528 :         float env_val[3];
     120        67584 :         env_val[0] = env_[0].process_additive(x_low, attack_coeff[0], release_coeff[0]);
     121        67584 :         env_val[1] = env_[1].process_additive(x_mid, attack_coeff[1], release_coeff[1]);
     122        67584 :         env_val[2] = env_[2].process_additive(x_high, attack_coeff[2], release_coeff[2]);
     123              : 
     124              :         // 3. Gain reduction computation
     125        67584 :         float gain_db[3] = { 0.0f, 0.0f, 0.0f };
     126       270336 :         for (int b = 0; b < 3; ++b) {
     127       202752 :             float env_db = linear_to_db(env_val[b]);
     128       202752 :             if (env_db > threshold_db[b]) {
     129       146631 :                 gain_db[b] = (threshold_db[b] - env_db) * (1.0f - 1.0f / ratio[b]);
     130        48877 :             }
     131        67584 :         }
     132              : 
     133              :         // 4. Update responsive gain reduction meter envelopes for UI
     134       270336 :         for (int b = 0; b < 3; ++b) {
     135       202752 :             float gr_instant = -gain_db[b]; // non-negative value
     136       202752 :             if (gr_instant > gr_peak_[b]) {
     137        12048 :                 gr_peak_[b] = gr_instant; // instant attack
     138         4015 :             } else {
     139              :                 // Smooth decay visual (decay time constant: ~150 ms)
     140       190704 :                 float decay_coeff = std::exp(-1.0f / (sample_rate_ * 0.150f));
     141       190704 :                 gr_peak_[b] = decay_coeff * gr_peak_[b] + (1.0f - decay_coeff) * gr_instant;
     142              :             }
     143       202752 :             gain_reduction_db_[b].store(gr_peak_[b], std::memory_order_relaxed);
     144        67584 :         }
     145              : 
     146              :         // 5. Apply compression gain and makeup gain per band
     147        67584 :         float y_low  = x_low  * db_to_linear(gain_db[0]) * makeup[0];
     148        67584 :         float y_mid  = x_mid  * db_to_linear(gain_db[1]) * makeup[1];
     149        67584 :         float y_high = x_high * db_to_linear(gain_db[2]) * makeup[2];
     150              : 
     151              :         // 6. Recombine signal and apply global output gain
     152        67584 :         buffer[i] = (y_low + y_mid + y_high) * output_gain;
     153        22528 :     }
     154           28 : }
     155              : 
     156           27 : void MultiBandCompressor::reset() {
     157           27 :     low_pass_filter_.reset();
     158           27 :     low_high_pass_filter_.reset();
     159           27 :     mid_pass_filter_.reset();
     160           27 :     mid_high_pass_filter_.reset();
     161              : 
     162          108 :     for (int b = 0; b < 3; ++b) {
     163           81 :         env_[b].reset();
     164           81 :         gr_peak_[b] = 0.0f;
     165           81 :         gain_reduction_db_[b].store(0.0f);
     166           27 :     }
     167           27 : }
     168              : 
     169              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1