LCOV - code coverage report
Current view: top level - src/audio/effects/modulation - flanger.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 58.0 % 100 58
Test Date: 2026-06-07 15:51:50 Functions: 80.0 % 5 4

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

Generated by: LCOV version 2.0-1