LCOV - code coverage report
Current view: top level - src/audio/effects/modulation - chorus.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 62.6 % 91 57
Test Date: 2026-06-07 15:51:50 Functions: 71.4 % 7 5

            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
        

Generated by: LCOV version 2.0-1