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>
122 lines
4.6 KiB
C
122 lines
4.6 KiB
C
// NAME: Cab Sim [CAB]
|
|
// PRIORITY: 129
|
|
// POT: "Resonance" LINEAR(0 1) = 0.5
|
|
// POT: "Presence" LINEAR(0 1) = 0.5
|
|
// POT: "Axis" LINEAR(0 1) = 0.5
|
|
// POT: "Breakup" LINEAR(0 1) = 0.0
|
|
// POT: "Chug" LINEAR(0 1) = 0.0
|
|
//
|
|
// ====================================================================
|
|
// WARNING: VERY APPROXIMATE SPEAKER EMULATION!
|
|
// ====================================================================
|
|
// This is NOT an Impulse Response (IR) loader or an exact physical
|
|
// model of a speaker cone.
|
|
//
|
|
// It is a rough IIR approximation of a classic 12-inch guitar speaker
|
|
// in a closed-back cabinet (e.g. Celestion Vintage 30) using cascaded
|
|
// biquad filters. It aims to cut the nasty "fizz" of amp distortion
|
|
// so it sounds passable through full-range studio monitors or USB
|
|
// audio, by mimicking the steep mechanical high-frequency roll-off
|
|
// and the low-end impedance bump of a real cab.
|
|
// ====================================================================
|
|
|
|
struct {
|
|
struct biquad low_cut; // 75 Hz HPF (speaker physical limit)
|
|
struct biquad thump_bp; // ~110 Hz parallel bandpass for dynamic resonance
|
|
struct biquad scoop; // ~400 Hz peaking (mid scoop)
|
|
struct biquad presence; // ~2.5 kHz peaking (speaker bite)
|
|
struct biquad hi_cut_1; // 1st half of 24dB/oct LPF
|
|
struct biquad hi_cut_2; // 2nd half of 24dB/oct LPF
|
|
|
|
struct envelope chug_env; // Envelope follower for low-frequency energy
|
|
struct single_pole_state env_lp;
|
|
struct single_pole_coeff env_coeff;
|
|
float res_gain; // Calculated linear gain for resonance
|
|
float breakup_amt;
|
|
float chug_amt;
|
|
} cab;
|
|
|
|
static inline void cab_init(unsigned char pot[10])
|
|
{
|
|
float res_pot = cab_resonance_pot(pot);
|
|
float pres_pot = cab_presence_pot(pot);
|
|
float axis_pot = cab_axis_pot(pot);
|
|
cab.breakup_amt = cab_breakup_pot(pot);
|
|
cab.chug_amt = cab_chug_pot(pot);
|
|
|
|
// Calculate base resonance gain. db_to_level expects voltage gain.
|
|
// Since we are using a parallel bandpass now, we just multiply the bandpass
|
|
// output by (A - 1) to get the equivalent peaking boost.
|
|
// 0 to +8dB. Amplitude A = 10^(dB/20).
|
|
float res_db = res_pot * 8.0f;
|
|
float res_A = db_to_level(res_db);
|
|
cab.res_gain = res_A - 1.0f;
|
|
if (cab.res_gain < 0.0f) cab.res_gain = 0.0f;
|
|
|
|
// High cut frequency sweeps from ~3500 Hz (off-axis/dark) to ~6000 Hz (on-axis/bright)
|
|
float hicut_freq = 3500.0f + axis_pot * 2500.0f;
|
|
|
|
// Envelope for chug compression (fast attack, medium release)
|
|
envelope_init(&cab.chug_env, 2.0f, 100.0f);
|
|
cab.env_coeff = single_pole_freq(150.0f); // Lowpass for envelope tracking
|
|
|
|
// 1. Low-end roll-off (12dB/octave HPF)
|
|
biquad_hpf(&cab.low_cut, 75.0f, 0.7f);
|
|
|
|
// 2. Cabinet Resonance thump (~110Hz) - Now a parallel bandpass
|
|
biquad_bpf(&cab.thump_bp, 110.0f, 1.5f);
|
|
|
|
// 3. Natural paper cone mid scoop (~400Hz, fixed -3dB)
|
|
biquad_peaking(&cab.scoop, 400.0f, 1.0f, db_to_A(-3.0f));
|
|
|
|
// 4. Upper-mid bite (~2500Hz), 0 to +6dB
|
|
biquad_peaking(&cab.presence, 2500.0f, 1.5f, db_to_A(pres_pot * 6.0f));
|
|
|
|
// 5. Steep 24dB/octave high-end roll-off (two cascaded 12dB/oct LPFs)
|
|
// A simple Q=0.7 on both gives a decent 4th-order slope.
|
|
biquad_lpf(&cab.hi_cut_1, hicut_freq, 0.7f);
|
|
biquad_lpf(&cab.hi_cut_2, hicut_freq, 0.7f);
|
|
}
|
|
|
|
static inline float cab_step(float in)
|
|
{
|
|
float out = in;
|
|
|
|
// 1. Extract low-frequency envelope for Chug compressor
|
|
float low_energy = single_pole_lpf(out, &cab.env_lp, cab.env_coeff);
|
|
float env = envelope_step(&cab.chug_env, low_energy);
|
|
|
|
// 2. Low-cut
|
|
out = biquad_step(&cab.low_cut, out);
|
|
|
|
// 3. Cone Breakup (Saturation)
|
|
// We do this BEFORE the massive 110Hz thump to prevent Intermodulation Distortion (IMD).
|
|
// If you boost bass before clipping, the bass frequencies modulate the high frequencies
|
|
// and turn chords into indistinguishable mud.
|
|
if (cab.breakup_amt > 0.0f) {
|
|
float drive_gain = 1.0f + cab.breakup_amt * 40.0f;
|
|
float driven = out * drive_gain;
|
|
float clipped = tanhf(driven + 0.2f) - 0.197375f;
|
|
out = clipped / (drive_gain * 0.961f);
|
|
}
|
|
|
|
// 4. Dynamic Thump (parallel bandpass)
|
|
float thump_sig = biquad_step(&cab.thump_bp, out);
|
|
// Chug amount determines how much the envelope reduces the resonance.
|
|
// Since guitar signals are typically ~0.1 peak, the envelope is very small.
|
|
// We multiply the envelope by 25.0 to scale it into a useful compression range.
|
|
float compression = 1.0f - (env * 25.0f * cab.chug_amt);
|
|
if (compression < 0.0f) compression = 0.0f;
|
|
out += thump_sig * (cab.res_gain * compression);
|
|
|
|
// 5. Scoop
|
|
out = biquad_step(&cab.scoop, out);
|
|
|
|
// 6. Presence & High Cut
|
|
out = biquad_step(&cab.presence, out);
|
|
out = biquad_step(&cab.hi_cut_1, out);
|
|
out = biquad_step(&cab.hi_cut_2, out);
|
|
|
|
return out;
|
|
}
|