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