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
|