0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-08-18 13:13:35 +00:00
Files
Linus Torvalds 0c1b9c3db3 Split Software/ into the four things it actually was
'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>
2026-08-11 13:48:26 -07:00

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;
}