Line data Source code
1 : #include "audio/effects/modulation/chorus.h"
2 :
3 : #include "audio/effects/core/effect_factory.h"
4 :
5 : namespace Amplitron {
6 :
7 2 : static EffectRegistrar<Chorus> reg("Chorus");
8 :
9 68 : Chorus::Chorus() {
10 153 : params_ = {
11 51 : {"Rate", 1.5f, 0.1f, 10.0f, 1.5f, "Hz",
12 17 : "Speed of the modulation sweep. Higher values create faster wobbling effects."},
13 17 : {"Depth", 5.0f, 0.5f, 20.0f, 5.0f, "ms",
14 17 : "Intensity of the modulation. Higher values create a deeper, more pronounced pitch "
15 : "shift."},
16 17 : {"Level", 0.5f, 0.0f, 1.0f, 0.5f, "",
17 17 : "Mix volume of the chorus effect. 0 is dry, 1 is fully wet."},
18 153 : };
19 51 : set_sample_rate(DEFAULT_SAMPLE_RATE);
20 102 : }
21 :
22 102 : void Chorus::set_sample_rate(int sample_rate) {
23 102 : Effect::set_sample_rate(sample_rate);
24 102 : max_delay_samples_ = static_cast<int>(sample_rate * 0.05f); // 50ms max
25 102 : delay_buffer_.assign(max_delay_samples_, 0.0f);
26 102 : write_pos_ = 0;
27 102 : }
28 :
29 24 : void Chorus::process(float* buffer, int num_samples) {
30 24 : if (!enabled_) return;
31 :
32 24 : const float alpha = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.020f));
33 24 : smoothed_rate_ += alpha * (params_[0].value - smoothed_rate_);
34 24 : float rate = smoothed_rate_;
35 24 : float depth_ms = params_[1].value;
36 24 : float level = params_[2].value;
37 :
38 24 : float depth_samples = depth_ms * 0.001f * sample_rate_;
39 24 : float lfo_inc = rate / sample_rate_;
40 :
41 6168 : for (int i = 0; i < num_samples; ++i) {
42 6144 : float dry = buffer[i];
43 :
44 : // Write current sample to delay buffer
45 6144 : delay_buffer_[write_pos_] = buffer[i];
46 :
47 : // LFO modulated delay
48 6144 : float lfo = 0.5f * (1.0f + std::sin(TWO_PI * lfo_phase_));
49 6144 : float delay = 1.0f + lfo * depth_samples;
50 :
51 : // Linear interpolation for fractional delay
52 6144 : float read_pos_f = static_cast<float>(write_pos_) - delay;
53 6144 : if (read_pos_f < 0.0f) read_pos_f += max_delay_samples_;
54 :
55 6144 : int read_pos_i = static_cast<int>(read_pos_f);
56 6144 : float frac = read_pos_f - read_pos_i;
57 :
58 6144 : int pos0 = read_pos_i % max_delay_samples_;
59 6144 : int pos1 = (read_pos_i + 1) % max_delay_samples_;
60 :
61 6144 : float delayed = delay_buffer_[pos0] * (1.0f - frac) + delay_buffer_[pos1] * frac;
62 :
63 6144 : buffer[i] = dry * (1.0f - level * 0.5f) + delayed * level;
64 :
65 6144 : write_pos_ = (write_pos_ + 1) % max_delay_samples_;
66 6144 : lfo_phase_ += lfo_inc;
67 6144 : if (lfo_phase_ >= 1.0f) lfo_phase_ -= 1.0f;
68 2048 : }
69 8 : }
70 :
71 0 : void Chorus::process_stereo(float* left, float* right, int num_samples) {
72 0 : if (!enabled_) {
73 0 : return;
74 : }
75 0 : const float alpha = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.020f));
76 0 : smoothed_rate_ += alpha * (params_[0].value - smoothed_rate_);
77 0 : float rate = smoothed_rate_;
78 0 : float depth_ms = params_[1].value;
79 0 : float level = params_[2].value;
80 :
81 0 : const float depth_samp = depth_ms * 0.001f * sample_rate_;
82 0 : const float lfo_inc = rate / sample_rate_;
83 :
84 0 : for (int i = 0; i < num_samples; ++i) {
85 0 : const float dry = left[i];
86 :
87 0 : delay_buffer_[write_pos_] = dry;
88 :
89 : // L: LFO at current phase
90 0 : const float lfo_l = 0.5f * (1.0f + std::sin(TWO_PI * lfo_phase_));
91 0 : const float dly_l = 1.0f + lfo_l * depth_samp;
92 : // R: LFO at +90° (0.25 of normalised cycle) for quadrature width
93 0 : const float lfo_r = 0.5f * (1.0f + std::sin(TWO_PI * (lfo_phase_ + 0.25f)));
94 0 : const float dly_r = 1.0f + lfo_r * depth_samp;
95 :
96 : // Fractional read helper (capture by reference into the loop)
97 0 : auto read_tap = [&](float delay) -> float {
98 0 : float rp = static_cast<float>(write_pos_) - delay;
99 0 : if (rp < 0.0f) rp += static_cast<float>(max_delay_samples_);
100 0 : const int ri = static_cast<int>(rp);
101 0 : const float f = rp - static_cast<float>(ri);
102 0 : const int p0 = ri % max_delay_samples_;
103 0 : const int p1 = (ri + 1) % max_delay_samples_;
104 0 : return delay_buffer_[p0] * (1.0f - f) + delay_buffer_[p1] * f;
105 0 : };
106 :
107 0 : const float dry_gain = 1.0f - level * 0.5f;
108 0 : left[i] = dry * dry_gain + read_tap(dly_l) * level;
109 0 : right[i] = dry * dry_gain + read_tap(dly_r) * level;
110 :
111 0 : write_pos_ = (write_pos_ + 1) % max_delay_samples_;
112 0 : lfo_phase_ += lfo_inc;
113 0 : if (lfo_phase_ >= 1.0f) lfo_phase_ -= 1.0f;
114 0 : }
115 0 : }
116 :
117 3 : void Chorus::set_transport_state(float bpm) {
118 3 : if (!std::isfinite(bpm) || bpm <= 0.0f) return;
119 3 : if (bpm == last_bpm_) return;
120 3 : last_bpm_ = bpm;
121 : // BPM to Hz
122 3 : float target_rate_hz = bpm / 60.0f;
123 : // set knob
124 3 : params_[0].value = clamp(target_rate_hz, params_[0].min_val, params_[0].max_val);
125 1 : }
126 :
127 51 : void Chorus::reset() {
128 51 : std::fill(delay_buffer_.begin(), delay_buffer_.end(), 0.0f);
129 51 : write_pos_ = 0;
130 51 : lfo_phase_ = 0.0f;
131 51 : }
132 :
133 : } // namespace Amplitron
|