Line data Source code
1 : #include "audio/effects/flanger.h"
2 : #include "audio/effects/effect_factory.h"
3 : #include <cmath>
4 :
5 : namespace Amplitron {
6 :
7 2 : static EffectRegistrar<Flanger> reg("Flanger");
8 :
9 : // Param indices
10 : static constexpr int P_RATE = 0;
11 : static constexpr int P_DEPTH = 1;
12 : static constexpr int P_DELAY = 2;
13 : static constexpr int P_FEEDBACK = 3;
14 : static constexpr int P_MIX = 4;
15 :
16 : // Max total delay: 15ms base + 7ms depth = 22ms. Size to 30ms for safety.
17 : static constexpr float MAX_DELAY_MS = 30.0f;
18 :
19 96 : Flanger::Flanger() {
20 312 : params_ = {
21 72 : {"Rate", 0.3f, 0.05f, 5.0f, 0.3f, "Hz",
22 24 : "LFO speed. Slow rates create a dreamy sweep; fast rates push toward vibrato territory."},
23 24 : {"Depth", 2.0f, 0.1f, 7.0f, 2.0f, "ms",
24 24 : "Modulation depth in milliseconds. Controls the width of the delay time sweep around the base delay."},
25 24 : {"Delay", 1.0f, 0.1f, 10.0f, 1.0f, "ms",
26 24 : "Base delay time. Short values (< 2ms) create tight comb filtering; longer values soften the effect."},
27 24 : {"Feedback", 0.7f, -0.95f, 0.95f, 0.7f, "",
28 24 : "Feedback amount. Positive values add metallic resonance; negative values create a hollow, phasey character."},
29 24 : {"Mix", 0.5f, 0.0f, 1.0f, 0.5f, "",
30 24 : "Dry/wet blend. At 0.5 the comb filter notches are deepest for the classic flanger sound."},
31 312 : };
32 72 : set_sample_rate(DEFAULT_SAMPLE_RATE);
33 144 : }
34 :
35 141 : void Flanger::set_sample_rate(int sample_rate) {
36 141 : Effect::set_sample_rate(sample_rate);
37 141 : max_delay_samples_ = static_cast<int>(sample_rate * MAX_DELAY_MS * 0.001f) + 2;
38 141 : delay_buffer_.assign(max_delay_samples_, 0.0f);
39 141 : delay_buffer_r_.assign(max_delay_samples_, 0.0f);
40 141 : reset();
41 141 : }
42 :
43 102 : void Flanger::process(float* buffer, int num_samples) {
44 102 : if (!enabled_) return;
45 :
46 96 : const float rate = params_[P_RATE].value;
47 96 : const float depth_ms = params_[P_DEPTH].value;
48 96 : const float delay_ms = params_[P_DELAY].value;
49 96 : const float feedback = params_[P_FEEDBACK].value;
50 96 : const float mix = params_[P_MIX].value;
51 :
52 96 : const float lfo_inc = rate / static_cast<float>(sample_rate_);
53 :
54 53856 : for (int i = 0; i < num_samples; ++i) {
55 53760 : const float dry = buffer[i];
56 :
57 : // LFO in [0, 1]
58 53760 : const float lfo = 0.5f * (1.0f + std::sin(TWO_PI * lfo_phase_));
59 :
60 : // Modulated delay in samples
61 71680 : const float delay_samples = clamp(
62 53760 : (delay_ms + lfo * depth_ms) * 0.001f * static_cast<float>(sample_rate_),
63 53760 : 1.0f, static_cast<float>(max_delay_samples_ - 2));
64 :
65 : // Fractional read position
66 53760 : float read_pos_f = static_cast<float>(write_pos_) - delay_samples;
67 53760 : if (read_pos_f < 0.0f) read_pos_f += static_cast<float>(max_delay_samples_);
68 :
69 53760 : const int ipos = static_cast<int>(read_pos_f);
70 53760 : const float frac = read_pos_f - static_cast<float>(ipos);
71 53760 : const int pos0 = ipos % max_delay_samples_;
72 53760 : const int pos1 = (ipos + 1) % max_delay_samples_;
73 :
74 53760 : const float delayed = delay_buffer_[pos0] * (1.0f - frac) + delay_buffer_[pos1] * frac;
75 :
76 : // Write dry + feedback into the delay line; clamp to prevent runaway
77 53760 : delay_buffer_[write_pos_] = clamp(dry + feedback * delayed, -2.0f, 2.0f);
78 :
79 53760 : buffer[i] = dry * (1.0f - mix) + delayed * mix;
80 :
81 53760 : write_pos_ = (write_pos_ + 1) % max_delay_samples_;
82 53760 : lfo_phase_ += lfo_inc;
83 53760 : if (lfo_phase_ >= 1.0f) lfo_phase_ -= 1.0f;
84 17920 : }
85 34 : }
86 :
87 0 : void Flanger::process_stereo(float* left, float* right, int num_samples) {
88 0 : if (!enabled_) {
89 0 : return;
90 : }
91 :
92 0 : const float rate = params_[P_RATE].value;
93 0 : const float depth_ms = params_[P_DEPTH].value;
94 0 : const float delay_ms = params_[P_DELAY].value;
95 0 : const float feedback = params_[P_FEEDBACK].value;
96 0 : const float mix = params_[P_MIX].value;
97 0 : const float lfo_inc = rate / static_cast<float>(sample_rate_);
98 :
99 0 : for (int i = 0; i < num_samples; ++i) {
100 0 : const float dry_l = left[i];
101 0 : const float dry_r = right[i];
102 :
103 : // Left LFO
104 0 : const float lfo_l = 0.5f * (1.0f + std::sin(TWO_PI * lfo_phase_));
105 0 : const float delay_samp_l = clamp(
106 0 : (delay_ms + lfo_l * depth_ms) * 0.001f * static_cast<float>(sample_rate_),
107 0 : 1.0f, static_cast<float>(max_delay_samples_ - 2));
108 :
109 0 : float rp_l = static_cast<float>(write_pos_) - delay_samp_l;
110 0 : if (rp_l < 0.0f) rp_l += static_cast<float>(max_delay_samples_);
111 0 : const int ip_l = static_cast<int>(rp_l);
112 0 : const float f_l = rp_l - static_cast<float>(ip_l);
113 0 : const float delayed_l = delay_buffer_[ip_l % max_delay_samples_] * (1.0f - f_l) +
114 0 : delay_buffer_[(ip_l + 1) % max_delay_samples_] * f_l;
115 :
116 0 : delay_buffer_[write_pos_] = clamp(dry_l + feedback * delayed_l, -2.0f, 2.0f);
117 0 : left[i] = dry_l * (1.0f - mix) + delayed_l * mix;
118 :
119 : // Right LFO — 180° offset (0.5 of normalised cycle)
120 0 : const float lfo_r = 0.5f * (1.0f + std::sin(TWO_PI * (lfo_phase_ + 0.5f)));
121 0 : const float delay_samp_r = clamp(
122 0 : (delay_ms + lfo_r * depth_ms) * 0.001f * static_cast<float>(sample_rate_),
123 0 : 1.0f, static_cast<float>(max_delay_samples_ - 2));
124 :
125 0 : float rp_r = static_cast<float>(write_pos_r_) - delay_samp_r;
126 0 : if (rp_r < 0.0f) rp_r += static_cast<float>(max_delay_samples_);
127 0 : const int ip_r = static_cast<int>(rp_r);
128 0 : const float f_r = rp_r - static_cast<float>(ip_r);
129 0 : const float delayed_r = delay_buffer_r_[ip_r % max_delay_samples_] * (1.0f - f_r) +
130 0 : delay_buffer_r_[(ip_r + 1) % max_delay_samples_] * f_r;
131 :
132 0 : delay_buffer_r_[write_pos_r_] = clamp(dry_r + feedback * delayed_r, -2.0f, 2.0f);
133 0 : right[i] = dry_r * (1.0f - mix) + delayed_r * mix;
134 :
135 0 : write_pos_ = (write_pos_ + 1) % max_delay_samples_;
136 0 : write_pos_r_ = (write_pos_r_ + 1) % max_delay_samples_;
137 0 : lfo_phase_ += lfo_inc;
138 0 : if (lfo_phase_ >= 1.0f) lfo_phase_ -= 1.0f;
139 0 : }
140 0 : }
141 :
142 255 : void Flanger::reset() {
143 255 : std::fill(delay_buffer_.begin(), delay_buffer_.end(), 0.0f);
144 255 : std::fill(delay_buffer_r_.begin(), delay_buffer_r_.end(), 0.0f);
145 255 : write_pos_ = 0;
146 255 : write_pos_r_ = 0;
147 255 : lfo_phase_ = 0.0f;
148 255 : }
149 :
150 : } // namespace Amplitron
|