LCOV - code coverage report
Current view: top level - src/audio/effects - reverb.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 99.3 % 135 134
Test Date: 2026-06-01 11:15:25 Functions: 100.0 % 6 6

            Line data    Source code
       1              : #include "audio/effects/reverb.h"
       2              : #include "audio/effects/effect_factory.h"
       3              : 
       4              : namespace Amplitron {
       5              : 
       6            2 : static EffectRegistrar<Reverb> reg("Reverb");
       7              : 
       8              : // Comb filter delay lengths (in samples at 44100Hz, will be scaled)
       9              : static const int COMB_LENGTHS[]   = {1116, 1188, 1277, 1356};
      10              : static const int ALLPASS_LENGTHS[] = {556, 441};
      11              : // Right-channel lengths add a prime offset for decorrelation (classic Freeverb technique)
      12              : static constexpr int STEREO_SPREAD = 23;
      13              : static const int COMB_LENGTHS_R[]   = {1116 + STEREO_SPREAD, 1188 + STEREO_SPREAD,
      14              :                                         1277 + STEREO_SPREAD, 1356 + STEREO_SPREAD};
      15              : static const int ALLPASS_LENGTHS_R[] = {556 + STEREO_SPREAD, 441 + STEREO_SPREAD};
      16              : 
      17          196 : Reverb::Reverb() {
      18          441 :     params_ = {
      19           98 :         {"Decay",  0.6f, 0.1f, 0.99f, 0.6f, "", "Length of the reverb tail. Higher values simulate larger acoustic spaces like halls or caves."},
      20           49 :         {"Damp",   0.4f, 0.0f, 1.0f,  0.4f, "", "High-frequency damping. Higher values absorb treble faster, simulating softer room materials."},
      21           49 :         {"Level",  0.3f, 0.0f, 1.0f,  0.3f, "", "Mix volume of the reverb effect. Controls how wet or distant the overall sound feels."},
      22          441 :     };
      23          147 :     init_filters();
      24          343 : }
      25              : 
      26          165 : void Reverb::set_sample_rate(int sample_rate) {
      27          165 :     Effect::set_sample_rate(sample_rate);
      28          165 :     init_filters();
      29          165 : }
      30              : 
      31          312 : void Reverb::init_filters() {
      32          312 :     const float scale = static_cast<float>(sample_rate_) / 44100.0f;
      33              : 
      34         1560 :     for (int i = 0; i < NUM_COMBS; ++i) {
      35         1248 :         int len = static_cast<int>(COMB_LENGTHS[i] * scale);
      36         1248 :         combs_[i].buffer.assign(len, 0.0f);
      37         1248 :         combs_[i].write_pos = 0;
      38         1248 :         combs_[i].lp_state = 0.0f;
      39              : 
      40         1248 :         int len_r = static_cast<int>(COMB_LENGTHS_R[i] * scale);
      41         1248 :         combs_r_[i].buffer.assign(len_r, 0.0f);
      42         1248 :         combs_r_[i].write_pos = 0;
      43         1248 :         combs_r_[i].lp_state = 0.0f;
      44          416 :     }
      45              : 
      46          936 :     for (int i = 0; i < NUM_ALLPASS; ++i) {
      47          624 :         int len = static_cast<int>(ALLPASS_LENGTHS[i] * scale);
      48          624 :         allpasses_[i].buffer.assign(len, 0.0f);
      49          624 :         allpasses_[i].write_pos = 0;
      50          624 :         allpasses_[i].feedback = 0.5f;
      51              : 
      52          624 :         int len_r = static_cast<int>(ALLPASS_LENGTHS_R[i] * scale);
      53          624 :         allpasses_r_[i].buffer.assign(len_r, 0.0f);
      54          624 :         allpasses_r_[i].write_pos = 0;
      55          624 :         allpasses_r_[i].feedback = 0.5f;
      56          208 :     }
      57          312 : }
      58              : 
      59           99 : void Reverb::process(float* buffer, int num_samples) {
      60           99 :     if (!enabled_) return;
      61              : 
      62           96 :     float decay = params_[0].value;
      63           96 :     float damp = params_[1].value;
      64           96 :     float level = params_[2].value;
      65              : 
      66       189408 :     for (int i = 0; i < num_samples; ++i) {
      67       189312 :         float dry = buffer[i];
      68       189312 :         float input = buffer[i] * 0.2f;
      69       189312 :         float out = 0.0f;
      70              : 
      71              :         // Parallel comb filters
      72       946560 :         for (int c = 0; c < NUM_COMBS; ++c) {
      73       757248 :             auto& comb = combs_[c];
      74       757248 :             int buf_len = static_cast<int>(comb.buffer.size());
      75       757248 :             int read_pos = comb.write_pos;
      76              : 
      77       757248 :             float delayed = comb.buffer[read_pos];
      78              : 
      79              :             // Damping LP filter
      80       757248 :             comb.lp_state = delayed * (1.0f - damp) + comb.lp_state * damp;
      81       757248 :             float fb_sample = comb.lp_state * decay;
      82              : 
      83       757248 :             comb.buffer[comb.write_pos] = input + fb_sample;
      84       757248 :             comb.write_pos = (comb.write_pos + 1) % buf_len;
      85              : 
      86       757248 :             out += delayed;
      87       252416 :         }
      88              : 
      89              :         // Series allpass filters
      90       567936 :         for (int a = 0; a < NUM_ALLPASS; ++a) {
      91       378624 :             auto& ap = allpasses_[a];
      92       378624 :             int buf_len = static_cast<int>(ap.buffer.size());
      93              : 
      94       378624 :             float delayed = ap.buffer[ap.write_pos];
      95       378624 :             float temp = out + delayed * ap.feedback;
      96       378624 :             ap.buffer[ap.write_pos] = temp;
      97       378624 :             out = delayed - out * ap.feedback;
      98              : 
      99       378624 :             ap.write_pos = (ap.write_pos + 1) % buf_len;
     100       126208 :         }
     101              : 
     102       189312 :         buffer[i] = dry * (1.0f - level) + out * level;
     103        63104 :     }
     104           33 : }
     105              : 
     106            3 : void Reverb::process_stereo(float* left, float* right, int num_samples) {
     107            3 :     if (!enabled_) {
     108            0 :         return;
     109              :     }
     110              : 
     111            3 :     const float decay = params_[0].value;
     112            3 :     const float damp  = params_[1].value;
     113            3 :     const float level = params_[2].value;
     114              : 
     115         6147 :     for (int i = 0; i < num_samples; ++i) {
     116         6144 :         const float input_l = left[i]  * 0.2f;
     117         6144 :         const float input_r = right[i] * 0.2f;
     118         6144 :         float out_l = 0.0f;
     119         6144 :         float out_r = 0.0f;
     120              : 
     121              :         // Parallel comb filters — left
     122        30720 :         for (int c = 0; c < NUM_COMBS; ++c) {
     123        24576 :             auto& comb = combs_[c];
     124        24576 :             const int buf_len = static_cast<int>(comb.buffer.size());
     125        24576 :             float delayed = comb.buffer[comb.write_pos];
     126        24576 :             comb.lp_state = delayed * (1.0f - damp) + comb.lp_state * damp;
     127        24576 :             comb.buffer[comb.write_pos] = input_l + comb.lp_state * decay;
     128        24576 :             comb.write_pos = (comb.write_pos + 1) % buf_len;
     129        24576 :             out_l += delayed;
     130         8192 :         }
     131              :         // Parallel comb filters — right
     132        30720 :         for (int c = 0; c < NUM_COMBS; ++c) {
     133        24576 :             auto& comb = combs_r_[c];
     134        24576 :             const int buf_len = static_cast<int>(comb.buffer.size());
     135        24576 :             float delayed = comb.buffer[comb.write_pos];
     136        24576 :             comb.lp_state = delayed * (1.0f - damp) + comb.lp_state * damp;
     137        24576 :             comb.buffer[comb.write_pos] = input_r + comb.lp_state * decay;
     138        24576 :             comb.write_pos = (comb.write_pos + 1) % buf_len;
     139        24576 :             out_r += delayed;
     140         8192 :         }
     141              : 
     142              :         // Series allpass filters — left
     143        18432 :         for (int a = 0; a < NUM_ALLPASS; ++a) {
     144        12288 :             auto& ap = allpasses_[a];
     145        12288 :             const int buf_len = static_cast<int>(ap.buffer.size());
     146        12288 :             float delayed = ap.buffer[ap.write_pos];
     147        12288 :             float temp = out_l + delayed * ap.feedback;
     148        12288 :             ap.buffer[ap.write_pos] = temp;
     149        12288 :             out_l = delayed - out_l * ap.feedback;
     150        12288 :             ap.write_pos = (ap.write_pos + 1) % buf_len;
     151         4096 :         }
     152              :         // Series allpass filters — right
     153        18432 :         for (int a = 0; a < NUM_ALLPASS; ++a) {
     154        12288 :             auto& ap = allpasses_r_[a];
     155        12288 :             const int buf_len = static_cast<int>(ap.buffer.size());
     156        12288 :             float delayed = ap.buffer[ap.write_pos];
     157        12288 :             float temp = out_r + delayed * ap.feedback;
     158        12288 :             ap.buffer[ap.write_pos] = temp;
     159        12288 :             out_r = delayed - out_r * ap.feedback;
     160        12288 :             ap.write_pos = (ap.write_pos + 1) % buf_len;
     161         4096 :         }
     162              : 
     163         6144 :         left[i]  = left[i]  * (1.0f - level) + out_l * level;
     164         6144 :         right[i] = right[i] * (1.0f - level) + out_r * level;
     165         2048 :     }
     166            1 : }
     167              : 
     168          168 : void Reverb::reset() {
     169          840 :     for (auto& c : combs_) {
     170          672 :         std::fill(c.buffer.begin(), c.buffer.end(), 0.0f);
     171          672 :         c.write_pos = 0;
     172          672 :         c.lp_state = 0.0f;
     173              :     }
     174          840 :     for (auto& c : combs_r_) {
     175          672 :         std::fill(c.buffer.begin(), c.buffer.end(), 0.0f);
     176          672 :         c.write_pos = 0;
     177          672 :         c.lp_state = 0.0f;
     178              :     }
     179          504 :     for (auto& a : allpasses_) {
     180          336 :         std::fill(a.buffer.begin(), a.buffer.end(), 0.0f);
     181          336 :         a.write_pos = 0;
     182              :     }
     183          504 :     for (auto& a : allpasses_r_) {
     184          336 :         std::fill(a.buffer.begin(), a.buffer.end(), 0.0f);
     185          336 :         a.write_pos = 0;
     186              :     }
     187          168 : }
     188              : 
     189              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1