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
|