LCOV - code coverage report
Current view: top level - src/audio/effects - chorus.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 61.4 % 88 54
Test Date: 2026-06-03 09:13:19 Functions: 71.4 % 7 5

            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
        

Generated by: LCOV version 2.0-1