LCOV - code coverage report
Current view: top level - src/audio/effects - amp_simulator.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 99.1 % 106 105
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 6 6

            Line data    Source code
       1              : #include "audio/effects/amp_simulator.h"
       2              : #include "audio/effects/effect_factory.h"
       3              : 
       4              : namespace Amplitron {
       5              : 
       6            2 : static EffectRegistrar<AmpSimulator> reg("Amp Sim");
       7              : 
       8              : // ============================================================
       9              : // Factory amp model library
      10              : // ============================================================
      11              : 
      12          543 : const std::vector<AmpModel>& get_amp_models() {
      13          362 :     static const std::vector<AmpModel> models = {
      14              :         // ---------------------------------------------------------
      15              :         // Clean American -- Fender Twin/Deluxe
      16              :         // ---------------------------------------------------------
      17              :         {
      18              :             "Clean American",
      19              :             "Fender Twin / Deluxe",
      20              :             "Sparkling clean, scooped mids, glassy highs",
      21              :             200.0f, 3.0f, 0.7f,
      22              :             800.0f, -2.0f, 0.8f,
      23              :             3500.0f, 2.5f, 0.7f,
      24              :             1.2f, 0.0f, 1.0f, 0.85f,
      25              :             0.01f, 0.005f, 0.0f,
      26              :         },
      27              :         // ---------------------------------------------------------
      28              :         // British Crunch -- Marshall JCM800
      29              :         // ---------------------------------------------------------
      30              :         {
      31              :             "British Crunch",
      32              :             "Marshall JCM800",
      33              :             "Warm breakup, mid-forward, classic rock crunch",
      34              :             180.0f, -1.0f, 0.8f,
      35              :             650.0f, 4.0f, 1.2f,
      36              :             3000.0f, 1.5f, 0.7f,
      37              :             3.5f, 0.15f, 0.8f, 0.7f,
      38              :             0.05f, 0.008f, 0.15f,
      39              :         },
      40              :         // ---------------------------------------------------------
      41              :         // High Gain Modern -- Mesa Boogie Rectifier
      42              :         // ---------------------------------------------------------
      43              :         {
      44              :             "High Gain Modern",
      45              :             "Mesa Boogie Rectifier",
      46              :             "Tight low-end, scooped mids, aggressive distortion",
      47              :             150.0f, 1.5f, 1.0f,
      48              :             500.0f, -4.5f, 0.9f,
      49              :             4000.0f, 2.0f, 0.8f,
      50              :             8.0f, 0.7f, 0.9f, 0.55f,
      51              :             0.15f, 0.003f, 0.25f,
      52              :         },
      53              :         // ---------------------------------------------------------
      54              :         // Jazz Warm -- Roland JC-120
      55              :         // ---------------------------------------------------------
      56              :         {
      57              :             "Jazz Warm",
      58              :             "Roland JC-120",
      59              :             "Flat clean, warm highs rolloff, round tone",
      60              :             250.0f, 2.0f, 0.6f,
      61              :             700.0f, 0.5f, 0.7f,
      62              :             2800.0f, -3.5f, 0.6f,
      63              :             1.0f, 0.0f, 1.0f, 0.9f,
      64              :             0.005f, 0.003f, 0.0f,
      65              :         },
      66          365 :     };
      67          543 :     return models;
      68            0 : }
      69              : 
      70              : // ============================================================
      71              : // AmpSimulator implementation
      72              : // ============================================================
      73              : 
      74          108 : AmpSimulator::AmpSimulator() {
      75           81 :     float max_model = static_cast<float>(get_amp_models().size() - 1);
      76          405 :     params_ = {
      77           54 :         {"Model", 0.0f, 0.0f, max_model, 0.0f, "", "Selects the amplifier model. Each model has unique EQ curves, gain staging, and clipping characteristics."},
      78           27 :         {"Gain",  0.5f, 0.0f, 1.0f, 0.5f, "", "Preamp gain control. Drives the virtual tubes harder for more compression and distortion."},
      79           27 :         {"Bass",  0.0f, -6.0f, 6.0f, 0.0f, "dB", "Pre-distortion low-frequency trim. Adjusts the fatness and punch of the amplifier."},
      80           27 :         {"Mid",   0.0f, -6.0f, 6.0f, 0.0f, "dB", "Pre-distortion mid-frequency trim. Controls the core voice and 'bark' of the amplifier."},
      81           27 :         {"Treble", 0.0f, -6.0f, 6.0f, 0.0f, "dB", "Pre-distortion high-frequency trim. Adjusts the brightness and bite before clipping."},
      82           27 :         {"Level", 0.7f, 0.0f, 1.0f, 0.7f, "", "Master output volume of the amplifier. Does not affect the amount of distortion."},
      83          378 :     };
      84           81 :     set_sample_rate(DEFAULT_SAMPLE_RATE);
      85          162 : }
      86              : 
      87          150 : void AmpSimulator::set_sample_rate(int sample_rate) {
      88          150 :     Effect::set_sample_rate(sample_rate);
      89              :     // Snap smoothing states to current params to avoid transient on rate change
      90          150 :     bass_trim_state_ = params_[2].value;
      91          150 :     mid_trim_state_ = params_[3].value;
      92          150 :     treble_trim_state_ = params_[4].value;
      93          150 :     gain_smoothed_   = params_[1].value;
      94          150 :     level_smoothed_  = params_[5].value;
      95          150 :     cached_model_index_ = -1; // force recompute
      96          150 :     recompute_coefficients_if_dirty();
      97          150 : }
      98              : 
      99          189 : void AmpSimulator::recompute_coefficients_if_dirty() {
     100          315 :     int model_idx = clamp(static_cast<int>(params_[0].value + 0.5f),
     101          189 :                           0, static_cast<int>(get_amp_models().size()) - 1);
     102          189 :     float bass_trim = bass_trim_state_;
     103          189 :     float mid_trim = mid_trim_state_;
     104          189 :     float treble_trim = treble_trim_state_;
     105          189 :     float gain_knob = params_[1].value;
     106              : 
     107          198 :     if (model_idx != cached_model_index_ ||
     108           27 :         bass_trim != cached_bass_ ||
     109           27 :         mid_trim != cached_mid_ ||
     110           27 :         treble_trim != cached_treble_ ||
     111           27 :         gain_knob != cached_gain_) {
     112              : 
     113          162 :         const AmpModel& model = get_amp_models()[model_idx];
     114          162 :         low_shelf_.set_low_shelf(model.bass_freq, model.bass_gain_db + bass_trim, model.bass_q, sample_rate_);
     115          162 :         mid_peak_.set_peaking(model.mid_freq, model.mid_gain_db + mid_trim, model.mid_q, sample_rate_);
     116          162 :         high_shelf_.set_high_shelf(model.treble_freq, model.treble_gain_db + treble_trim, model.treble_q, sample_rate_);
     117              : 
     118          162 :         cached_model_index_ = model_idx;
     119          162 :         cached_bass_ = bass_trim;
     120          162 :         cached_mid_ = mid_trim;
     121          162 :         cached_treble_ = treble_trim;
     122          162 :         cached_gain_ = gain_knob;
     123           54 :     }
     124          189 : }
     125              : 
     126           39 : void AmpSimulator::process(float* buffer, int num_samples) {
     127           39 :     if (!enabled_) return;
     128              : 
     129              :     // One-pole smoothing: advance trim states toward raw param targets each block
     130           39 :     const float alpha = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.010f)); // 10 ms
     131           39 :     bass_trim_state_   += alpha * (params_[2].value - bass_trim_state_);
     132           39 :     mid_trim_state_    += alpha * (params_[3].value - mid_trim_state_);
     133           39 :     treble_trim_state_ += alpha * (params_[4].value - treble_trim_state_);
     134           39 :     gain_smoothed_     += alpha * (params_[1].value - gain_smoothed_);
     135           39 :     level_smoothed_    += alpha * (params_[5].value - level_smoothed_);
     136              : 
     137           39 :     recompute_coefficients_if_dirty();
     138              : 
     139           65 :     int model_idx = clamp(static_cast<int>(params_[0].value + 0.5f),
     140           39 :                           0, static_cast<int>(get_amp_models().size()) - 1);
     141           39 :     const AmpModel& model = get_amp_models()[model_idx];
     142              : 
     143           39 :     float gain_knob = gain_smoothed_;
     144           39 :     float level = level_smoothed_;
     145              : 
     146              :     // Effective preamp gain: model base * user gain control (0-2x range)
     147           39 :     float effective_gain = model.preamp_gain * (0.2f + gain_knob * 1.8f);
     148           39 :     float sat_mix = model.saturation_mix;
     149           39 :     float asym = model.asymmetry;
     150           39 :     float model_output = model.output_level;
     151           39 :     float attack = model.attack_coeff;
     152           39 :     float release = model.release_coeff;
     153           39 :     float sag = model.sag_amount;
     154              : 
     155        20007 :     for (int i = 0; i < num_samples; ++i) {
     156        19968 :         float dry = buffer[i];
     157        19968 :         float x = buffer[i];
     158              : 
     159              :         // --- Envelope follower for dynamic response ---
     160        19968 :         float abs_x = std::fabs(x);
     161        19968 :         if (abs_x > envelope_) {
     162         9642 :             envelope_ += attack * (abs_x - envelope_);
     163         3214 :         } else {
     164        10326 :             envelope_ += release * (abs_x - envelope_);
     165              :         }
     166              : 
     167              :         // Power sag: reduce gain when envelope is high (tube amp compression)
     168        19968 :         float sag_factor = 1.0f - sag * clamp(envelope_, 0.0f, 1.0f);
     169              : 
     170              :         // --- Input gain with sag ---
     171        19968 :         x *= effective_gain * sag_factor;
     172              : 
     173              :         // --- Tone stack (pre-saturation EQ) ---
     174        19968 :         x = low_shelf_.process(x);
     175        19968 :         x = mid_peak_.process(x);
     176        19968 :         x = high_shelf_.process(x);
     177              : 
     178              :         // --- Waveshaping saturation ---
     179              :         // Soft clipping path (tube-like)
     180         6656 :         float soft;
     181        19968 :         if (x > 0.0f) {
     182        10245 :             soft = 1.0f - std::exp(-x);
     183         3415 :         } else {
     184         9723 :             soft = -1.0f + std::exp(x);
     185         9723 :             soft *= asym;
     186              :         }
     187              : 
     188              :         // Hard clipping path
     189        19968 :         float hard = hard_clip(x, 1.0f);
     190              : 
     191              :         // Blend soft and hard clipping
     192        19968 :         x = soft * (1.0f - sat_mix) + hard * sat_mix;
     193              : 
     194              :         // --- DC blocking high-pass filter ---
     195        19968 :         x = dc_block_.hp(x, 0.005f);
     196              : 
     197              :         // --- Output level ---
     198        19968 :         x *= model_output * level;
     199              : 
     200              :         // Safety clamp
     201        19968 :         x = clamp(x, -1.0f, 1.0f);
     202              : 
     203              :         // Wet/dry mix
     204        19968 :         buffer[i] = dry * (1.0f - mix_) + x * mix_;
     205         6656 :     }
     206           13 : }
     207              : 
     208           69 : void AmpSimulator::reset() {
     209           69 :     low_shelf_.reset();
     210           69 :     mid_peak_.reset();
     211           69 :     high_shelf_.reset();
     212           69 :     envelope_ = 0.0f;
     213           69 :     dc_block_.reset();
     214           69 : }
     215              : 
     216              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1