LCOV - code coverage report
Current view: top level - src/audio/effects/dynamics - multiband_compressor.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 95.5 % 133 127
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 5 5

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

Generated by: LCOV version 2.0-1