Line data Source code
1 : #include "audio/effects/modulation/phaser.h"
2 :
3 : #include <cmath>
4 :
5 : #include "audio/effects/core/effect_factory.h"
6 :
7 : namespace Amplitron {
8 :
9 2 : static EffectRegistrar<Phaser> reg("Phaser");
10 :
11 : // Param indices
12 : static constexpr int P_RATE = 0;
13 : static constexpr int P_DEPTH = 1;
14 : static constexpr int P_STAGES = 2;
15 : static constexpr int P_FEEDBACK = 3;
16 : static constexpr int P_MIX = 4;
17 :
18 : // Stage count table: Stages param 0..3 -> 4/6/8/12 stages
19 : static constexpr int STAGE_COUNTS[4] = {4, 6, 8, 12};
20 :
21 76 : Phaser::Phaser() {
22 247 : params_ = {
23 57 : {"Rate", 0.5f, 0.05f, 10.0f, 0.5f, "Hz",
24 19 : "LFO speed. Low values create a slow, hypnotic sweep; high values create a fast "
25 : "vibrato-like effect."},
26 19 : {"Depth", 0.7f, 0.0f, 1.0f, 0.7f, "",
27 19 : "Modulation depth. Controls how wide the all-pass frequency sweeps across the spectrum."},
28 19 : {"Stages", 0.0f, 0.0f, 3.0f, 0.0f, "",
29 19 : "Number of all-pass stages: 0=4 (Phase 90), 1=6, 2=8, 3=12. More stages add more "
30 : "notches."},
31 19 : {"Feedback", 0.5f, 0.0f, 0.95f, 0.5f, "",
32 19 : "Feeds the chain output back to the input, adding resonance and intensity to the phasing "
33 : "notches."},
34 19 : {"Mix", 0.5f, 0.0f, 1.0f, 0.5f, "",
35 19 : "Dry/wet blend. At 0.5 the notch effect is most pronounced (classic phaser mix point)."},
36 247 : };
37 76 : set_sample_rate(DEFAULT_SAMPLE_RATE);
38 114 : }
39 :
40 108 : void Phaser::set_sample_rate(int sample_rate) {
41 108 : Effect::set_sample_rate(sample_rate);
42 108 : reset();
43 89 : }
44 :
45 45 : void Phaser::process(float* buffer, int num_samples) {
46 45 : if (!enabled_) return;
47 :
48 42 : const float rate = params_[P_RATE].value;
49 42 : const float depth = params_[P_DEPTH].value;
50 42 : const int nstages = STAGE_COUNTS[(int)clamp(params_[P_STAGES].value + 0.5f, 0.0f, 3.0f)];
51 42 : const float feedback = params_[P_FEEDBACK].value;
52 42 : const float mix = params_[P_MIX].value;
53 :
54 42 : const float lfo_inc = rate / static_cast<float>(sample_rate_);
55 : // Logarithmic sweep: base_freq * exp(lfo * depth * ln(ratio))
56 : // fc range: ~200 Hz (lfo=0) to ~4000 Hz (lfo=1, depth=1)
57 42 : const float log_ratio = std::log(20.0f); // ln(4000/200)
58 :
59 15402 : for (int i = 0; i < num_samples; ++i) {
60 15360 : const float dry = buffer[i];
61 :
62 : // LFO in [0, 1]
63 15360 : const float lfo = 0.5f * (1.0f + std::sin(TWO_PI * lfo_phase_));
64 :
65 : // Modulated all-pass corner frequency (log sweep)
66 20480 : const float fc = clamp(200.0f * std::exp(lfo * depth * log_ratio), 80.0f,
67 10240 : static_cast<float>(sample_rate_) * 0.40f);
68 :
69 : // 1st-order all-pass coefficient: c = (tan(π*fc/fs) - 1) / (tan(π*fc/fs) + 1)
70 15360 : const float t = std::tan(3.14159265f * fc / static_cast<float>(sample_rate_));
71 15360 : const float apc = (t - 1.0f) / (t + 1.0f);
72 :
73 : // Feed input + feedback into the all-pass cascade
74 15360 : float x = dry + feedback * feedback_state_;
75 :
76 125952 : for (int s = 0; s < nstages; ++s) {
77 : // y[n] = c * (x[n] - y[n-1]) + x[n-1]
78 110592 : const float y = apc * (x - apf_yprev_[s]) + apf_xprev_[s];
79 110592 : apf_xprev_[s] = x;
80 110592 : apf_yprev_[s] = y;
81 110592 : x = y;
82 36864 : }
83 :
84 15360 : feedback_state_ = x;
85 :
86 15360 : buffer[i] = dry * (1.0f - mix) + x * mix;
87 :
88 15360 : lfo_phase_ += lfo_inc;
89 15360 : if (lfo_phase_ >= 1.0f) lfo_phase_ -= 1.0f;
90 5120 : }
91 15 : }
92 :
93 0 : void Phaser::process_stereo(float* left, float* right, int num_samples) {
94 0 : if (!enabled_) {
95 0 : return;
96 : }
97 :
98 0 : const float rate = params_[P_RATE].value;
99 0 : const float depth = params_[P_DEPTH].value;
100 0 : const int nstages = STAGE_COUNTS[(int)clamp(params_[P_STAGES].value + 0.5f, 0.0f, 3.0f)];
101 0 : const float feedback = params_[P_FEEDBACK].value;
102 0 : const float mix = params_[P_MIX].value;
103 :
104 0 : const float lfo_inc = rate / static_cast<float>(sample_rate_);
105 0 : const float log_ratio = std::log(20.0f);
106 :
107 0 : for (int i = 0; i < num_samples; ++i) {
108 0 : const float dry_l = left[i];
109 0 : const float dry_r = right[i];
110 :
111 : // Left LFO
112 0 : const float lfo_l = 0.5f * (1.0f + std::sin(TWO_PI * lfo_phase_));
113 0 : const float fc_l = clamp(200.0f * std::exp(lfo_l * depth * log_ratio), 80.0f,
114 0 : static_cast<float>(sample_rate_) * 0.40f);
115 0 : const float t_l = std::tan(3.14159265f * fc_l / static_cast<float>(sample_rate_));
116 0 : const float apc_l = (t_l - 1.0f) / (t_l + 1.0f);
117 :
118 : // Right LFO — 180° offset (0.5 of normalised cycle)
119 0 : const float lfo_r = 0.5f * (1.0f + std::sin(TWO_PI * (lfo_phase_ + 0.5f)));
120 0 : const float fc_r = clamp(200.0f * std::exp(lfo_r * depth * log_ratio), 80.0f,
121 0 : static_cast<float>(sample_rate_) * 0.40f);
122 0 : const float t_r = std::tan(3.14159265f * fc_r / static_cast<float>(sample_rate_));
123 0 : const float apc_r = (t_r - 1.0f) / (t_r + 1.0f);
124 :
125 : // Left APF cascade
126 0 : float x_l = dry_l + feedback * feedback_state_;
127 0 : for (int s = 0; s < nstages; ++s) {
128 0 : const float y = apc_l * (x_l - apf_yprev_[s]) + apf_xprev_[s];
129 0 : apf_xprev_[s] = x_l;
130 0 : apf_yprev_[s] = y;
131 0 : x_l = y;
132 0 : }
133 0 : feedback_state_ = x_l;
134 :
135 : // Right APF cascade
136 0 : float x_r = dry_r + feedback * feedback_state_r_;
137 0 : for (int s = 0; s < nstages; ++s) {
138 0 : const float y = apc_r * (x_r - apf_yprev_r_[s]) + apf_xprev_r_[s];
139 0 : apf_xprev_r_[s] = x_r;
140 0 : apf_yprev_r_[s] = y;
141 0 : x_r = y;
142 0 : }
143 0 : feedback_state_r_ = x_r;
144 :
145 0 : left[i] = dry_l * (1.0f - mix) + x_l * mix;
146 0 : right[i] = dry_r * (1.0f - mix) + x_r * mix;
147 :
148 0 : lfo_phase_ += lfo_inc;
149 0 : if (lfo_phase_ >= 1.0f) lfo_phase_ -= 1.0f;
150 0 : }
151 0 : }
152 :
153 168 : void Phaser::reset() {
154 168 : lfo_phase_ = 0.0f;
155 168 : feedback_state_ = 0.0f;
156 168 : feedback_state_r_ = 0.0f;
157 168 : apf_xprev_.fill(0.0f);
158 168 : apf_yprev_.fill(0.0f);
159 168 : apf_xprev_r_.fill(0.0f);
160 168 : apf_yprev_r_.fill(0.0f);
161 168 : }
162 :
163 : } // namespace Amplitron
|