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>
129 lines
5.4 KiB
C
129 lines
5.4 KiB
C
// NAME: Test Tone [TESTTONE]
|
|
// PRIORITY: 128
|
|
// MIX: LINEAR
|
|
// DEFAULT_MIX: 1.0
|
|
// POT: "Level" LINEAR(-90.0 0.0) = -20.0 dBFS
|
|
// INFO: How loud the tone is, as dBFS out. Fully down is digital
|
|
// INFO: silence rather than a very quiet tone, so it can be used to
|
|
// INFO: drive the next pedal's input with a terminated nothing.
|
|
// POT: "Freq" EXPONENTIAL(13.75 14080.0) = 440.0 Hz
|
|
// INFO: Ten octaves in 120 steps, so one step is a semitone and the
|
|
// INFO: middle of the range is A440 exactly.
|
|
// POT: "Shape" ENUM(Sine Triangle Saw Noise) = Sine
|
|
//
|
|
// A signal generator, in the pedal.
|
|
//
|
|
// This is a test instrument rather than an effect, and it is here because
|
|
// the bench instrument it replaces cannot be reached from a test. The
|
|
// hardware suite in Validation/ has to be *told* what its generator is set
|
|
// to - `--ptp 0.100 --freq 440` - and every number it reports is quoted
|
|
// against that claim rather than against a measurement. So when a capture
|
|
// comes out wrong there is no way to tell a pedal that broke from a bench
|
|
// that moved, and the noise floor it measures includes whatever the
|
|
// generator contributes, because the generator cannot be switched off.
|
|
//
|
|
// A pedal generating into another pedal fixes all three at once: the
|
|
// stimulus is set over MIDI, it can be turned off, and it is produced by
|
|
// neither the host nor the pedal being measured.
|
|
//
|
|
// It ignores its input, which is the whole point. Two pedals patched
|
|
// output-to-input in both directions are an oscillator - noise goes round
|
|
// the loop, gains a little each time, and both of them sit there clipping.
|
|
// At full mix this replaces the input rather than adding to it, so routing
|
|
// it on one of the two breaks the loop at a known place, and does it
|
|
// through the ordinary mix control rather than through anything that had
|
|
// to be invented for it. Hence DEFAULT_MIX: 1.0 - the useful setting is
|
|
// the whole output, and anything less is for hearing the tone alongside
|
|
// what you are playing, which is the unusual case.
|
|
//
|
|
// Priority puts it late, between the amp and the cab. That is only where
|
|
// it lands when nothing says otherwise: routing order is set explicitly,
|
|
// so feeding a tone *into* the rest of the chain is a routing message and
|
|
// not a reason to place it early. Late is the better default because the
|
|
// common use is measuring what comes out of the jack, and the fewer things
|
|
// between the generator and the jack the better.
|
|
//
|
|
// Two things do sit between it and the jack whatever the routing, both in
|
|
// the signal chain and neither avoidable from here: the master Volume
|
|
// scales it, and a bypassed pedal crossfades back to its input. So a test
|
|
// asserting an absolute level has to set Volume rather than assume it.
|
|
// The gate does not, being upstream of every routed effect.
|
|
//
|
|
// The shapes are the LFO's, at audio rate. Nothing about a phase
|
|
// accumulator cares which side of 20Hz it is running on, and set_lfo_freq()
|
|
// is deliberately unclamped, so this is the existing oscillator asked for a
|
|
// bigger number - and the sine is the quarter-table with interpolation that
|
|
// every modulated effect already uses, so the tone is exactly as clean as
|
|
// the vibrato is. The first three enum values are in lfo_type order on
|
|
// purpose; the fourth is not an LFO at all.
|
|
//
|
|
// Noise is the odd one out and is worth having: a sweep measures one
|
|
// frequency at a time and needs a run per point, while noise measures the
|
|
// whole response at once, and it is also the honest stimulus for anything
|
|
// asking how a filter behaves rather than where its corner is. xorshift32
|
|
// rather than rand(), which is a call out of the audio sections that
|
|
// check-audio.py refuses - correctly, since it would be a jump into flash
|
|
// on every sample. Full period, uniform rather than gaussian, and the
|
|
// state is seeded once at boot rather than at every init so that moving a
|
|
// pot does not restart the sequence.
|
|
|
|
static struct {
|
|
struct lfo_state lfo;
|
|
float amp;
|
|
enum lfo_type type;
|
|
int noise;
|
|
u32 rng;
|
|
} testtone;
|
|
|
|
static void testtone_init(unsigned char pot[10])
|
|
{
|
|
set_lfo_freq(&testtone.lfo, testtone_freq_pot(pot));
|
|
|
|
//
|
|
// Fully down is off. The bottom of the range is -90dBFS, which is
|
|
// twenty decibels under the quietest noise floor either board has
|
|
// measured and so is nearly nothing - but "nearly nothing" is the
|
|
// wrong answer for the pedal at the far end of the cable, which is
|
|
// trying to find out how quiet it can be. Same convention as the
|
|
// gate's threshold, for the same reason.
|
|
//
|
|
testtone.amp = pot[TESTTONE_LEVEL] ?
|
|
db_to_level(testtone_level_pot(pot)) : 0.0f;
|
|
|
|
testtone.noise = pot[TESTTONE_SHAPE] == 3;
|
|
testtone.type = testtone.noise ? lfo_sinewave : pot[TESTTONE_SHAPE];
|
|
|
|
if (!testtone.rng)
|
|
testtone.rng = 2463534242u;
|
|
}
|
|
|
|
static float testtone_step(float in)
|
|
{
|
|
// Deliberately: a generator is not a function of its input
|
|
(void) in;
|
|
|
|
if (testtone.noise) {
|
|
u32 x = testtone.rng;
|
|
|
|
x ^= x << 13;
|
|
x ^= x >> 17;
|
|
x ^= x << 5;
|
|
testtone.rng = x;
|
|
|
|
return testtone.amp * (2.0f * u32_to_fraction(x) - 1.0f);
|
|
}
|
|
|
|
float v = lfo_step(&testtone.lfo, testtone.type);
|
|
|
|
//
|
|
// The saw comes back as 0 to 1 where the other two come back as -1
|
|
// to 1 - it is the raw phase, which is what makes it useful to the
|
|
// tremolo and useless to us. Centring it here rather than in
|
|
// lfo.h, because that range is the point of it there.
|
|
//
|
|
if (testtone.type == lfo_sawtooth)
|
|
v = 2.0f * v - 1.0f;
|
|
|
|
return testtone.amp * v;
|
|
}
|