LCOV - code coverage report
Current view: top level - src/audio/effects/amp_cab - amp_simulator.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 99.1 % 113 112
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 6 6

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

Generated by: LCOV version 2.0-1