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
|