Line data Source code
1 : #pragma once
2 :
3 : #include <atomic>
4 :
5 : #include "audio/dsp/biquad.h"
6 : #include "audio/dsp/envelope_follower.h"
7 : #include "audio/effects/core/effect.h"
8 :
9 : namespace Amplitron {
10 :
11 : /**
12 : * @brief 2nd-order Butterworth Low-Pass filter stage.
13 : * Two cascaded stages form a Linkwitz-Riley 4th-order (LR4) filter.
14 : */
15 : struct ButterworthLP {
16 : Biquad b1, b2;
17 :
18 186 : void calculate_coefficients(float cutoff, float sample_rate) {
19 186 : float w0 = TWO_PI * cutoff / sample_rate;
20 186 : float cos_w0 = std::cos(w0);
21 186 : float sin_w0 = std::sin(w0);
22 186 : float alpha = sin_w0 / (2.0f * 0.70710678f); // Q = 1/sqrt(2)
23 186 : float a0 = 1.0f + alpha;
24 :
25 186 : float b0 = (1.0f - cos_w0) / 2.0f;
26 186 : float b1_val = 1.0f - cos_w0;
27 186 : float b2_val = (1.0f - cos_w0) / 2.0f;
28 186 : float a1 = -2.0f * cos_w0;
29 186 : float a2 = 1.0f - alpha;
30 :
31 186 : b1.b0 = b2.b0 = b0 / a0;
32 186 : b1.b1 = b2.b1 = b1_val / a0;
33 186 : b1.b2 = b2.b2 = b2_val / a0;
34 186 : b1.a1 = b2.a1 = a1 / a0;
35 186 : b1.a2 = b2.a2 = a2 / a0;
36 186 : }
37 :
38 67584 : float process(float x) { return b2.process(b1.process(x)); }
39 :
40 45 : void reset() {
41 45 : b1.reset();
42 45 : b2.reset();
43 36 : }
44 : };
45 :
46 : /**
47 : * @brief 2nd-order Butterworth High-Pass filter stage.
48 : * Two cascaded stages form a Linkwitz-Riley 4th-order (LR4) filter.
49 : */
50 : struct ButterworthHP {
51 : Biquad b1, b2;
52 :
53 186 : void calculate_coefficients(float cutoff, float sample_rate) {
54 186 : float w0 = TWO_PI * cutoff / sample_rate;
55 186 : float cos_w0 = std::cos(w0);
56 186 : float sin_w0 = std::sin(w0);
57 186 : float alpha = sin_w0 / (2.0f * 0.70710678f); // Q = 1/sqrt(2)
58 186 : float a0 = 1.0f + alpha;
59 :
60 186 : float b0 = (1.0f + cos_w0) / 2.0f;
61 186 : float b1_val = -(1.0f + cos_w0);
62 186 : float b2_val = (1.0f + cos_w0) / 2.0f;
63 186 : float a1 = -2.0f * cos_w0;
64 186 : float a2 = 1.0f - alpha;
65 :
66 186 : b1.b0 = b2.b0 = b0 / a0;
67 186 : b1.b1 = b2.b1 = b1_val / a0;
68 186 : b1.b2 = b2.b2 = b2_val / a0;
69 186 : b1.a1 = b2.a1 = a1 / a0;
70 186 : b1.a2 = b2.a2 = a2 / a0;
71 186 : }
72 :
73 67584 : float process(float x) { return b2.process(b1.process(x)); }
74 :
75 45 : void reset() {
76 45 : b1.reset();
77 45 : b2.reset();
78 36 : }
79 : };
80 :
81 : /**
82 : * @brief A professional 3-band dynamics compressor.
83 : * Crossover filters split the signal into Low, Mid, and High bands.
84 : * Each band is compressed independently and summed back to the output.
85 : */
86 2 : class MultiBandCompressor : public Effect {
87 : public:
88 : MultiBandCompressor();
89 : void process(float* buffer, int num_samples) override;
90 : void reset() override;
91 3477 : const char* name() const override { return "MultiBand Compressor"; }
92 3 : const char* type_id() const override { return "MultiBand Compressor"; }
93 300 : std::vector<EffectParam>& params() override { return params_; }
94 0 : const std::vector<EffectParam>& params() const override { return params_; }
95 : void set_sample_rate(int sample_rate) override;
96 :
97 : // Thread-safe gain reduction query for the GUI meters
98 558 : float get_gain_reduction_db(int band) const {
99 558 : if (band < 0 || band >= 3) return 0.0f;
100 554 : return gain_reduction_db_[band].load(std::memory_order_relaxed);
101 186 : }
102 :
103 : private:
104 : std::vector<EffectParam> params_;
105 :
106 : // Crossover filters (LR4 crossovers)
107 : ButterworthLP low_pass_filter_;
108 : ButterworthHP low_high_pass_filter_;
109 : ButterworthLP mid_pass_filter_;
110 : ButterworthHP mid_high_pass_filter_;
111 :
112 : // Per-band compressors
113 : EnvelopeFollower env_[3];
114 : float smoothed_attack_ms_[3] = {5.0f, 5.0f, 5.0f};
115 : float smoothed_release_ms_[3] = {100.0f, 100.0f, 100.0f};
116 :
117 : // Thread-safe gain reduction tracking (instantly updated)
118 : std::atomic<float> gain_reduction_db_[3];
119 :
120 : // Peak-decay envelope for smoother GUI meter visual response
121 : float gr_peak_[3] = {0.0f, 0.0f, 0.0f};
122 :
123 : // Cached states to determine when to recompute crossover coefficients
124 : float cached_low_xover_ = -1.0f;
125 : float cached_high_xover_ = -1.0f;
126 : int cached_sample_rate_ = -1;
127 :
128 : void recompute_coefficients();
129 : };
130 :
131 : } // namespace Amplitron
|