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 154 : MultiBandCompressor::MultiBandCompressor() {
9 858 : params_ = {
10 : // Crossovers
11 44 : {"Low XOver", 200.0f, 50.0f, 1000.0f, 200.0f, "Hz", "Crossover frequency separating the Low and Mid frequency bands."},
12 22 : {"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 22 : {"Low Thresh", -20.0f, -60.0f, 0.0f, -20.0f, "dB", "Low band threshold: volume level above which compression occurs."},
16 22 : {"Low Ratio", 4.0f, 1.0f, 20.0f, 4.0f, ":1", "Low band ratio: compression strength for the Low band."},
17 22 : {"Low Attack", 5.0f, 0.1f, 50.0f, 5.0f, "ms", "Low band attack: how quickly the compressor acts on Low transients."},
18 22 : {"Low Release", 100.0f, 10.0f, 500.0f, 100.0f, "ms", "Low band release: recovery speed of the Low band compressor."},
19 22 : {"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 22 : {"Mid Thresh", -20.0f, -60.0f, 0.0f, -20.0f, "dB", "Mid band threshold: volume level above which compression occurs."},
23 22 : {"Mid Ratio", 4.0f, 1.0f, 20.0f, 4.0f, ":1", "Mid band ratio: compression strength for the Mid band."},
24 22 : {"Mid Attack", 5.0f, 0.1f, 50.0f, 5.0f, "ms", "Mid band attack: how quickly the compressor acts on Mid transients."},
25 22 : {"Mid Release", 100.0f, 10.0f, 500.0f, 100.0f, "ms", "Mid band release: recovery speed of the Mid band compressor."},
26 22 : {"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 22 : {"High Thresh", -20.0f, -60.0f, 0.0f, -20.0f, "dB", "High band threshold: volume level above which compression occurs."},
30 22 : {"High Ratio", 4.0f, 1.0f, 20.0f, 4.0f, ":1", "High band ratio: compression strength for the High band."},
31 22 : {"High Attack", 5.0f, 0.1f, 50.0f, 5.0f, "ms", "High band attack: how quickly the compressor acts on High transients."},
32 22 : {"High Release", 100.0f, 10.0f, 500.0f, 100.0f, "ms", "High band release: recovery speed of the High band compressor."},
33 22 : {"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 22 : {"Out Gain", 0.0f, -20.0f, 20.0f, 0.0f, "dB", "Global output gain applied to the final summed signal."}
37 858 : };
38 :
39 66 : gain_reduction_db_[0].store(0.0f);
40 66 : gain_reduction_db_[1].store(0.0f);
41 66 : gain_reduction_db_[2].store(0.0f);
42 :
43 88 : set_sample_rate(DEFAULT_SAMPLE_RATE);
44 154 : }
45 :
46 90 : void MultiBandCompressor::set_sample_rate(int sample_rate) {
47 90 : Effect::set_sample_rate(sample_rate);
48 90 : recompute_coefficients();
49 68 : }
50 :
51 114 : void MultiBandCompressor::recompute_coefficients() {
52 114 : float f_low = params_[0].value;
53 114 : float f_high = params_[1].value;
54 :
55 : // Enforce that f_low < f_high logically
56 114 : 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 114 : low_pass_filter_.calculate_coefficients(f_low, sample_rate_);
65 114 : low_high_pass_filter_.calculate_coefficients(f_low, sample_rate_);
66 :
67 114 : mid_pass_filter_.calculate_coefficients(f_high, sample_rate_);
68 114 : mid_high_pass_filter_.calculate_coefficients(f_high, sample_rate_);
69 114 : }
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
|