You've already forked torvalds-GuitarPedal
mirror of
https://github.com/torvalds/GuitarPedal.git
synced 2026-08-18 13:13:35 +00:00
'Software' was the directory everything that was not KiCad ended up in, which stopped describing anything a while ago - Validation and the web app are software too. Worse, it put the shared parts inside the firmware, where they read as the firmware's own. They are not. Effects/ has three consumers built from it: the firmware, Validation's bench, and the web app's controls, all generated from the same POT: comments by gen_effects.py. Audio/ has two - the bench compiles the same biquads, the same envelope followers and the same single_sample(), which is the whole reason a measurement on a workstation says anything about the pedal. Neither belongs under Firmware/, so neither is under it any more: Effects/ one file per effect Audio/ the DSP they are built from, and the audio loop Firmware/ the rest of what runs on the pedal, and the submodules WebMIDI/ the web app scripts/ what the build runs Validation/ unchanged Hardware/, Documentation/, Images/ CMakeLists.txt and the wrapper Makefile move to the top with them, because the build now consumes four of those directories and generates into a fifth. board.local and build/ come along; MIDI_CC_MAP.md is generated into Documentation/ rather than into the old Software/ root. scripts/ goes with the build rather than staying under the firmware, because six of the ten had nothing to do with the firmware: gen_effects.py reads Effects/ and writes to three different places, pow2/log2/quarter_sine generate Audio/'s tables, check-readme.py compares Effects/ against the README, and server.py serves the web app. Four of them are invoked from Validation, which was reaching into Firmware/ for tooling - the same burying this commit is undoing. The four that really are about the firmware are ELF checks the top-level build drives anyway, and a second scripts directory would only be a second place to look. C includes say "Audio/foo.h" and the generated map says "Effects/bar.h", with the repository root on the include path for both the firmware and the bench. Spelling the directory out rather than relying on a bare name is what keeps Audio/cycles.h shimmable: a quoted include searches the including file's own directory first. The submodules are renamed as well as moved. git mv updates their paths but leaves the section names, and 'Software/pico-sdk' surviving in .gitmodules would be the word this commit removes, still load-bearing. That meant the nested modules under pico-sdk too - six .git files pointing into .git/modules/Software - which is why 'git submodule update --init --recursive' is worth running once after pulling this. Verified rather than assumed: a clean configure and build, make check (failing only on the missing-eeprom case it already failed on), check-effects, all four analysis pages reproducing every series and drawing every chart, and a flash to the board that still measures a routed reverb where it did before. One latent bug fell out of it. bench/coeff declared only quarter_sine.h of the three generated math tables, and Audio/util.h includes pow2.h and log2.h as well - so building that target with an empty gen/ could never have worked. 'make bench' builds bench/bench first, which generates all three, so it stayed hidden until this rebuilt everything from nothing. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
461 lines
16 KiB
C
461 lines
16 KiB
C
// NAME: Frenchie Amp [FRENCHIE]
|
||
// PRIORITY: 125
|
||
// POT: "Gain" LINEAR(0 1) = 0.5
|
||
// POT: "Tone" LINEAR(0 1) = 0.5
|
||
// POT: "Input" LINEAR(0 1) = 0.5
|
||
// POT: "Sag" LINEAR(0 1) = 0.5
|
||
// POT: "Level" LINEAR(0 2.0) = 0.25
|
||
|
||
/*
|
||
* 5W Class A tube amplifier (Fender Champ topology)
|
||
*
|
||
* Signal path:
|
||
* input -> RF filter -> input stage (grid physics) -> coupling cap HP ->
|
||
* PSU sag -> 12AX7 triode (density-aware) -> tone control ->
|
||
* 6V6 SE Class A power amp -> soft gate -> output
|
||
*
|
||
* Density tracker simplified to 2-band (LO + MID) for embedded.
|
||
* Full version uses 4 bands; the extra two (HI, AIR) modulate Miller Q
|
||
* and an air shelf -- nice to have but not essential to the sound.
|
||
*/
|
||
|
||
// This is almost certainly not the right frequency to filter at
|
||
// but unlike the Klon, I don't know what the right values are
|
||
//
|
||
// Built once in frenchie_init() rather than inline: the coefficient
|
||
// costs a pow2() now (see audio/single-pole.h) and this is called twice
|
||
// a sample, from the triode and from the power amp. One coefficient
|
||
// serves both, because they share the frequency and not the state.
|
||
static struct single_pole_coeff frenchie_dc_c;
|
||
|
||
static inline float frenchie_dc_step(struct single_pole_state *state, float x)
|
||
{
|
||
return single_pole_hpf(x, state, frenchie_dc_c);
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* 2-BAND DENSITY TRACKER */
|
||
/* Simplified from 4-band for embedded. Tracks LO + MID energy to */
|
||
/* derive harmonic density and mid_density. These drive the triode's */
|
||
/* Miller LP cutoff and drive boost. */
|
||
/* ================================================================== */
|
||
|
||
struct frenchie_density {
|
||
struct biquad f_lo; /* LP at 250Hz — isolates fundamental */
|
||
struct biquad f_mid_lo; /* LP at 2kHz — \ */
|
||
struct biquad f_mid_hi; /* HP at 250Hz — / bandpass for mids */
|
||
struct envelope env_lo, env_mid, env_total;
|
||
float density; /* 1.0–2.5: how much harmonic content */
|
||
float mid_density; /* 0.0–1.0: mid-band energy ratio */
|
||
float smooth; /* smoothing coefficient */
|
||
};
|
||
|
||
static inline void __audio_func(frenchie_density_init)(struct frenchie_density *d)
|
||
{
|
||
biquad_lpf(&d->f_lo, 250.0f, 0.7f);
|
||
biquad_lpf(&d->f_mid_lo, 2000.0f, 0.7f);
|
||
biquad_hpf(&d->f_mid_hi, 250.0f, 0.7f);
|
||
envelope_init(&d->env_lo, 2.0f, 80.0f);
|
||
envelope_init(&d->env_mid, 2.0f, 80.0f);
|
||
envelope_init(&d->env_total, 2.0f, 80.0f);
|
||
d->smooth = time_constant(30.0f);
|
||
}
|
||
|
||
static inline void frenchie_density_step(struct frenchie_density *d, float x)
|
||
{
|
||
float lo = biquad_step(&d->f_lo, x);
|
||
float mid = biquad_step(&d->f_mid_hi, biquad_step(&d->f_mid_lo, x));
|
||
float e_lo = envelope_step(&d->env_lo, lo);
|
||
float e_mid = envelope_step(&d->env_mid, mid);
|
||
float e_tot = envelope_step(&d->env_total, x);
|
||
float raw = 1.0f, raw_mid = 0.0f;
|
||
|
||
if (e_lo > 0.0001f)
|
||
raw = clamp(e_tot / e_lo, 1.0f, 2.5f);
|
||
if (e_tot > 0.0001f)
|
||
raw_mid = clamp(e_mid / e_tot, 0.0f, 1.0f);
|
||
|
||
d->density = linear(d->smooth, raw, d->density);
|
||
d->mid_density = linear(d->smooth, raw_mid, d->mid_density);
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* INPUT STAGE — 12AX7 grid physics */
|
||
/* Coupling cap bias shift, grid conduction, cathode bypass shelf, */
|
||
/* input loading LP. Sets the amp's "feel" before any gain stage. */
|
||
/* ================================================================== */
|
||
|
||
struct frenchie_input_stage {
|
||
struct biquad input_lp; /* input loading */
|
||
struct envelope peak; /* peak envelope for metering */
|
||
float cap_charge;
|
||
float cap_charge_rate;
|
||
float cap_drain_rate;
|
||
};
|
||
|
||
static inline void frenchie_input_stage_init(struct frenchie_input_stage *s)
|
||
{
|
||
biquad_lpf(&s->input_lp, 4500.0f, 0.6f);
|
||
envelope_init(&s->peak, 0.2f, 50.0f);
|
||
s->cap_charge_rate = time_constant(0.5f);
|
||
s->cap_drain_rate = time_constant(30.0f);
|
||
}
|
||
|
||
static inline float frenchie_input_stage_step(struct frenchie_input_stage *s, float x, float intensity)
|
||
{
|
||
float loaded, bias_shift, biased, grid_thresh, conducted;
|
||
|
||
/* Input loading LP, blended by intensity */
|
||
loaded = biquad_step(&s->input_lp, x);
|
||
loaded = linear(intensity, x, loaded);
|
||
|
||
/* Coupling cap bias shift */
|
||
if (loaded > 0.1f)
|
||
s->cap_charge = linear(s->cap_charge_rate, loaded * 0.15f * intensity, s->cap_charge);
|
||
else
|
||
s->cap_charge = s->cap_drain_rate * s->cap_charge;
|
||
|
||
bias_shift = s->cap_charge;
|
||
biased = loaded - bias_shift;
|
||
|
||
/* Grid conduction — tube grid draws current on positive peaks */
|
||
grid_thresh = 1.0f - intensity * 0.85f;
|
||
if (biased > grid_thresh)
|
||
conducted = grid_thresh
|
||
+ tanhf((biased - grid_thresh) * (1.0f + intensity * 3.0f))
|
||
* (0.05f + intensity * 0.15f);
|
||
else
|
||
conducted = biased;
|
||
|
||
/* 5F1 Champ V1A has no bypass cap, so response is flat here. */
|
||
envelope_step(&s->peak, x);
|
||
return conducted;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* PSU SAG — 5Y3 tube rectifier power supply */
|
||
/* Slow attack, slow release, massive droop. THE Champ feel. */
|
||
/* ================================================================== */
|
||
|
||
struct frenchie_psu {
|
||
float voltage, droop;
|
||
float att, rel;
|
||
float max_droop, min_v, base_v;
|
||
};
|
||
|
||
static inline void frenchie_psu_init(struct frenchie_psu *p)
|
||
{
|
||
/* 5Y3 tube rectifier: FAST attack (0.5ms), SLOW release (200ms) */
|
||
p->att = time_constant(0.5f);
|
||
p->rel = time_constant(200.0f);
|
||
p->max_droop = 0.30f; /* 30% voltage drop under load */
|
||
p->min_v = 0.55f; /* never below 55% of nominal */
|
||
p->base_v = 1.0f;
|
||
}
|
||
|
||
static inline float frenchie_psu_step(struct frenchie_psu *p, float signal_level, float sag_depth)
|
||
{
|
||
float draw = signal_level * signal_level;
|
||
|
||
if (draw > p->droop)
|
||
p->droop = linear(p->att, draw, p->droop);
|
||
else
|
||
p->droop = linear(p->rel, draw, p->droop);
|
||
|
||
p->voltage = p->base_v - p->droop * p->max_droop * sag_depth;
|
||
p->voltage = clamp(p->voltage, p->min_v, p->base_v);
|
||
return p->voltage;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* TRIODE — 12AX7 density-aware preamp tube */
|
||
/* */
|
||
/* coupling cap HP → grid leak bias → density-boosted drive → */
|
||
/* asymmetric tanh clip (bplus modulates ceiling) → */
|
||
/* grid conduction → density-driven Miller LP → DC block */
|
||
/* */
|
||
/* Character comes from parameters, not code. */
|
||
/* ================================================================== */
|
||
|
||
struct frenchie_triode {
|
||
struct biquad hp; /* coupling cap */
|
||
struct biquad miller; /* Miller capacitance LP */
|
||
struct frenchie_density hd; /* harmonic density tracker */
|
||
struct single_pole_state dc; /* DC blocker */
|
||
struct envelope peak; /* peak envelope for metering */
|
||
float cap_charge;
|
||
float cap_charge_rate, cap_drain_rate;
|
||
float miller_base_freq;
|
||
float last_miller_freq;
|
||
};
|
||
|
||
static inline void frenchie_triode_init(struct frenchie_triode *t, float hp_freq, float lp_freq)
|
||
{
|
||
biquad_hpf(&t->hp, hp_freq, 0.7f);
|
||
biquad_lpf(&t->miller, lp_freq, 0.6f);
|
||
t->miller_base_freq = lp_freq;
|
||
t->last_miller_freq = lp_freq;
|
||
frenchie_density_init(&t->hd);
|
||
envelope_init(&t->peak, 0.2f, 50.0f);
|
||
t->cap_charge_rate = time_constant(0.5f);
|
||
t->cap_drain_rate = time_constant(30.0f);
|
||
}
|
||
|
||
static inline float frenchie_triode_step(struct frenchie_triode *t, float x,
|
||
float drive, float bplus,
|
||
float pos_hard, float neg_hard,
|
||
float pos_ceil, float neg_ceil,
|
||
float gc_thresh, float gc_amount)
|
||
{
|
||
float b, gain, bias_shift, drive_boost, lp_freq;
|
||
|
||
/* Coupling cap HP */
|
||
b = biquad_step(&t->hp, x);
|
||
|
||
/* Grid leak bias shift — coupling cap charges on positive signal,
|
||
* shifts the DC operating point down. Slow drain = long recovery. */
|
||
if (b > 0.1f)
|
||
t->cap_charge = linear(t->cap_charge_rate, b * 0.12f, t->cap_charge);
|
||
else
|
||
t->cap_charge = t->cap_drain_rate * t->cap_charge;
|
||
|
||
bias_shift = t->cap_charge;
|
||
b -= bias_shift;
|
||
|
||
/* Density-boosted drive — mid content pushes tube harder */
|
||
drive_boost = 1.0f + t->hd.mid_density * 0.4f;
|
||
gain = (1.0f + drive * 4.0f) * drive_boost;
|
||
b *= gain;
|
||
|
||
/* Density tracker (pre-clip) */
|
||
frenchie_density_step(&t->hd, b);
|
||
|
||
// Replaced piecewise tanh with a single offset tanh to maintain identical
|
||
// clipping asymptotes (+0.85, -1.0) and slope at zero (0.935) without crossover distortion.
|
||
/* Asymmetric tanh clipping — THE transfer function.
|
||
* Positive clips harder (plate saturation), negative clips softer
|
||
* (cutoff is gradual). bplus scales ceiling — PSU sag compresses. */
|
||
b = (0.925f * tanhf(1.0175f * b + 0.08126f) - 0.075f) * bplus;
|
||
|
||
/* Grid conduction — soft clamp on positive peaks */
|
||
float peak_val = envelope_step(&t->peak, b);
|
||
if (b > gc_thresh && peak_val > gc_thresh * 0.8f)
|
||
b = gc_thresh + tanhf((b - gc_thresh) * 3.0f) * gc_amount;
|
||
|
||
/* Miller LP — density drives cutoff down.
|
||
* More harmonics = more intermod = Miller capacitance increases.
|
||
* Only recalculate when freq changes meaningfully. */
|
||
lp_freq = t->miller_base_freq - (t->hd.density - 1.0f) * 500.0f;
|
||
lp_freq = clamp(lp_freq, t->miller_base_freq - 1500.0f,
|
||
t->miller_base_freq + 500.0f);
|
||
if (fabsf(lp_freq - t->last_miller_freq) > 30.0f) {
|
||
biquad_lpf(&t->miller, lp_freq, 0.6f);
|
||
t->last_miller_freq = lp_freq;
|
||
}
|
||
b = biquad_step(&t->miller, b);
|
||
|
||
/* DC block */
|
||
return frenchie_dc_step(&t->dc, b);
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* SINGLE-ENDED CLASS A POWER AMP — 6V6 */
|
||
/* */
|
||
/* One tube does everything. Asymmetric clip: positive half hits */
|
||
/* plate saturation (hard, low ceiling), negative half runs into */
|
||
/* cutoff (soft, high ceiling). This asymmetry generates even */
|
||
/* harmonics — the "warm" sound of Class A. */
|
||
/* */
|
||
/* Cathode bias shift compresses under load (the "spongy" feel). */
|
||
/* Transformer saturates on low-frequency content. */
|
||
/* Winding resonance adds upper-mid character. */
|
||
/* ================================================================== */
|
||
|
||
struct frenchie_power_amp {
|
||
struct frenchie_density hd; /* harmonic density tracker */
|
||
struct single_pole_state dc; /* DC blocker */
|
||
struct envelope cathode;
|
||
struct biquad xfmr_res; /* transformer winding resonance */
|
||
struct biquad alias_lp; /* anti-alias after clipping */
|
||
float xfmr_lp; /* 1-pole LP state for xfmr sat */
|
||
float drive;
|
||
};
|
||
|
||
static inline void frenchie_power_amp_init(struct frenchie_power_amp *pa)
|
||
{
|
||
frenchie_density_init(&pa->hd);
|
||
/* Cathode: 5ms attack, 150ms release, 20% shift */
|
||
envelope_init(&pa->cathode, 5.0f, 150.0f);
|
||
|
||
// FIX: The original lms_bq_set_peak function mathematically doubled the
|
||
// specified decibel boost because it passed `A = 10^(db/20)` into the
|
||
// equations instead of the standard `A = 10^(db/40)`.
|
||
// To match the original code's exact response curve, we double the db value here.
|
||
biquad_peaking(&pa->xfmr_res, 3500.0f, 2.0f, db_to_A(2.5f * 2.0f));
|
||
|
||
biquad_lpf(&pa->alias_lp, 10000.0f, 0.4f);
|
||
}
|
||
|
||
static inline float frenchie_power_amp_step(struct frenchie_power_amp *pa, float x, float bplus)
|
||
{
|
||
float b, out, cathode_shift, xfmr_sat;
|
||
float lo_content, hi_content, lo_saturated, lo_final;
|
||
|
||
/* Drive scaling */
|
||
b = x * (1.0f + pa->drive * 2.0f);
|
||
|
||
/* Density tracker */
|
||
frenchie_density_step(&pa->hd, b);
|
||
|
||
/* Cathode bias shift — envelope on signal shifts DC operating point */
|
||
float cathode_val = envelope_step(&pa->cathode, b);
|
||
cathode_shift = cathode_val * 0.20f;
|
||
b -= cathode_shift;
|
||
|
||
// Replaced piecewise tanh with a single offset tanh to maintain identical
|
||
// clipping asymptotes (+0.7, -1.0) and slope at zero (1.26) without crossover distortion.
|
||
/* Single-ended asymmetric clipping.
|
||
* pos: hard clip (1.8), low ceiling (0.7) — plate saturation
|
||
* neg: soft clip (0.6), high ceiling (1.0) — gradual cutoff
|
||
* Even harmonics preserved (no push-pull cancellation). */
|
||
out = (0.85f * tanhf(1.53f * b + 0.17833f) - 0.15f) * bplus;
|
||
|
||
/* Transformer saturation — iron core saturates on low-freq content.
|
||
* Split into LO (1-pole LP) and HI, saturate only LO, recombine. */
|
||
float env_lo_val = pa->hd.env_lo.value;
|
||
xfmr_sat = clamp(env_lo_val * 4.0f, 0.0f, 1.0f);
|
||
if (xfmr_sat > 0.05f) {
|
||
pa->xfmr_lp = pa->xfmr_lp * 0.988f + out * 0.012f;
|
||
lo_content = pa->xfmr_lp;
|
||
hi_content = out - lo_content;
|
||
lo_saturated = tanhf(lo_content * (1.0f + xfmr_sat * 1.5f))
|
||
/ (1.0f + xfmr_sat * 0.3f);
|
||
lo_final = lo_content + xfmr_sat * (lo_saturated - lo_content);
|
||
out = lo_final + hi_content;
|
||
}
|
||
|
||
/* Transformer winding resonance — parasitic peak ~3.5kHz */
|
||
out = biquad_step(&pa->xfmr_res, out);
|
||
/* Anti-alias LP */
|
||
out = biquad_step(&pa->alias_lp, out);
|
||
|
||
/* DC block */
|
||
return frenchie_dc_step(&pa->dc, out) * 0.85f;
|
||
}
|
||
|
||
/* ================================================================== */
|
||
/* SOFT GATE — envelope-following noise gate */
|
||
/* ================================================================== */
|
||
|
||
struct frenchie_gate {
|
||
struct envelope env;
|
||
float open_thresh, close_thresh;
|
||
};
|
||
|
||
static inline void frenchie_gate_init(struct frenchie_gate *g)
|
||
{
|
||
envelope_init(&g->env, 0.5f, 150.0f);
|
||
g->open_thresh = 0.001f;
|
||
g->close_thresh = 0.0002f;
|
||
}
|
||
|
||
static inline float frenchie_gate_step(struct frenchie_gate *g, float x)
|
||
{
|
||
float e, t, gain;
|
||
|
||
e = envelope_step(&g->env, x);
|
||
|
||
if (e > g->open_thresh)
|
||
gain = 1.0f;
|
||
else if (e < g->close_thresh)
|
||
gain = 0.0f;
|
||
else {
|
||
t = (e - g->close_thresh) / (g->open_thresh - g->close_thresh);
|
||
gain = t * t * (3.0f - 2.0f * t);
|
||
}
|
||
return x * gain;
|
||
}
|
||
|
||
static struct {
|
||
struct biquad rf_filter; /* 10kHz — grid stopper × Cgk */
|
||
struct frenchie_input_stage input_stage; /* 12AX7 grid physics */
|
||
struct frenchie_psu psu; /* 5Y3 tube rectifier */
|
||
struct frenchie_triode v1a; /* 12AX7 preamp triode */
|
||
struct single_pole_state tone_lp; /* 1-pole passive tone control */
|
||
struct single_pole_coeff tone_coeff;
|
||
struct frenchie_power_amp power_amp; /* 6V6 Class A power amp */
|
||
struct frenchie_gate gate; /* noise gate */
|
||
|
||
/* Parameters */
|
||
float gain;
|
||
float input_intensity;
|
||
float sag_depth;
|
||
float out_level;
|
||
|
||
int initialized;
|
||
} frenchie;
|
||
|
||
static inline void frenchie_init(unsigned char pot[10])
|
||
{
|
||
if (!frenchie.initialized) {
|
||
frenchie.v1a.hd.density = 1.0f;
|
||
frenchie.power_amp.hd.density = 1.0f;
|
||
frenchie.psu.voltage = 1.0f;
|
||
frenchie.initialized = 1;
|
||
}
|
||
|
||
frenchie_dc_c = single_pole_freq(20.0f);
|
||
|
||
biquad_lpf(&frenchie.rf_filter, 10000.0f, 0.5f);
|
||
frenchie_input_stage_init(&frenchie.input_stage);
|
||
frenchie_psu_init(&frenchie.psu);
|
||
/* V1a: coupling cap HP at 30Hz, Miller LP at 8kHz */
|
||
frenchie_triode_init(&frenchie.v1a, 30.0f, 8000.0f);
|
||
frenchie_power_amp_init(&frenchie.power_amp);
|
||
frenchie_gate_init(&frenchie.gate);
|
||
|
||
frenchie.gain = frenchie_gain_pot(pot);
|
||
float tone = frenchie_tone_pot(pot);
|
||
frenchie.input_intensity = frenchie_input_pot(pot);
|
||
frenchie.sag_depth = frenchie_sag_pot(pot);
|
||
frenchie.out_level = frenchie_level_pot(pot);
|
||
|
||
frenchie.tone_coeff = single_pole_freq(1500.0f + tone * 18500.0f);
|
||
}
|
||
|
||
static inline float frenchie_step(float in)
|
||
{
|
||
float mono = in;
|
||
float bplus;
|
||
|
||
mono = clamp(mono, -1.0f, 1.0f);
|
||
|
||
/* RF filter — 68kΩ grid stopper × grid capacitance */
|
||
mono = biquad_step(&frenchie.rf_filter, mono);
|
||
|
||
/* Input stage — grid physics (coupling cap bias, grid conduction) */
|
||
if (frenchie.input_intensity > 0.0f)
|
||
mono = frenchie_input_stage_step(&frenchie.input_stage, mono, frenchie.input_intensity);
|
||
|
||
/* PSU sag — 5Y3 tube rectifier droops under load */
|
||
bplus = frenchie_psu_step(&frenchie.psu,
|
||
fabsf(mono) * (1.0f + frenchie.gain * 3.0f), frenchie.sag_depth);
|
||
|
||
/* V1a — 12AX7 preamp triode (the sound of the amp) */
|
||
mono = frenchie_triode_step(&frenchie.v1a, mono, frenchie.gain * 0.65f, bplus,
|
||
1.1f, 0.935f /* FIX: was 0.9f, 0.935 matches positive slope (1.1 * 0.85) */, 0.85f, 1.0f, 0.35f, 0.12f);
|
||
|
||
/* Tone control — 5F2-A Princeton-style passive 1-pole treble bleed */
|
||
mono = single_pole_lpf(mono, &frenchie.tone_lp, frenchie.tone_coeff);
|
||
|
||
/* 6V6 SE Class A power amp */
|
||
frenchie.power_amp.drive = frenchie.gain * 0.55f;
|
||
mono = frenchie_power_amp_step(&frenchie.power_amp, mono, bplus);
|
||
|
||
/* Noise gate */
|
||
mono = frenchie_gate_step(&frenchie.gate, mono);
|
||
|
||
/* Output clamp */
|
||
return clamp(mono * frenchie.out_level, -0.95f, 0.95f);
|
||
}
|