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>
229 lines
7.0 KiB
C
229 lines
7.0 KiB
C
//
|
|
// Calculate biquad coefficients
|
|
//
|
|
struct biquad_coeff {
|
|
float b0, b1, b2;
|
|
float a1, a2;
|
|
};
|
|
|
|
struct biquad_state {
|
|
float x[2], y[2];
|
|
};
|
|
|
|
struct biquad {
|
|
struct biquad_coeff coeff;
|
|
struct biquad_state state;
|
|
};
|
|
|
|
// Direct form 1 may need more state than the "canonical" DF2,
|
|
// but gets noisy much quicker.
|
|
static inline float biquad_step_df1(const struct biquad_coeff *c, float in, float x[2], float y[2])
|
|
{
|
|
float out = c->b0*in + c->b1*x[0] + c->b2*x[1] - c->a1*y[0] - c->a2*y[1];
|
|
x[1] = x[0]; x[0] = in;
|
|
y[1] = y[0]; y[0] = out;
|
|
return out;
|
|
}
|
|
|
|
// A peaking biquad has a1==b1 and can avoid one multiply
|
|
static inline float biquad_peaking_step_df1(const struct biquad_coeff *c, float in, float x[2], float y[2])
|
|
{
|
|
float out = c->b0*in + c->b1*(x[0]-y[0]) + c->b2*x[1] - c->a2*y[1];
|
|
x[1] = x[0]; x[0] = in;
|
|
y[1] = y[0]; y[0] = out;
|
|
return out;
|
|
}
|
|
|
|
static inline float _biquad_peaking_step(const struct biquad_coeff *c, struct biquad_state *s, float x0)
|
|
{
|
|
return biquad_peaking_step_df1(c, x0, s->x, s->y);
|
|
}
|
|
|
|
static inline float _biquad_step(const struct biquad_coeff *c, struct biquad_state *s, float x0)
|
|
{
|
|
return biquad_step_df1(c, x0, s->x, s->y);
|
|
}
|
|
|
|
//
|
|
// Every constructor below places its poles and zeros with -2*cos(w0), so
|
|
// what it needs from the sine table is an *angle*, and cos is the worst
|
|
// possible way to ask for one down here.
|
|
//
|
|
// fastsincos() interpolates 256 entries a quarter linearly. Near zero
|
|
// phase the sine is straight and the chord is nearly exact; the cosine
|
|
// is at its maximum, where the chord sits furthest under the arc. And
|
|
// d(cos)/dw = -sin(w0), which vanishes exactly where that error is
|
|
// worst, so the angle recovered from the cosine is off by err/sin(w0).
|
|
// Asking for 20Hz got a filter at 30.7Hz, and 60Hz got 63.6Hz.
|
|
//
|
|
// So take the half angle, whose sine is the well-behaved one, and come
|
|
// back with the double-angle identities. Both come out of the one table
|
|
// lookup that was being done anyway. cos(w) is now built from a squared
|
|
// sine, which is the whole point: it carries the angle where cos does
|
|
// not. sin(w) is not fussy - it ends up in alpha, where it sets the
|
|
// bandwidth rather than the frequency - so the table's own cosine is
|
|
// good enough for that half.
|
|
//
|
|
// single-pole.h asks for the same half angle for its own reasons.
|
|
//
|
|
static inline struct sincos biquad_w0(float freq)
|
|
{
|
|
struct sincos h = fastsincos(0.5f * freq / SAMPLES_PER_SEC);
|
|
struct sincos w0;
|
|
|
|
w0.sin = 2 * h.sin * h.cos;
|
|
w0.cos = 1 - 2 * h.sin * h.sin;
|
|
|
|
return w0;
|
|
}
|
|
|
|
static inline void _biquad_lpf(struct biquad_coeff *res, float freq, float Q)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1/(1 + alpha);
|
|
float b1 = (1 - w0.cos) * a0_inv;
|
|
|
|
res->b0 = b1 / 2;
|
|
res->b1 = b1;
|
|
res->b2 = b1 / 2;
|
|
res->a1 = -2*w0.cos * a0_inv;
|
|
res->a2 = (1 - alpha) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_hpf(struct biquad_coeff *res, float freq, float Q)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1/(1 + alpha);
|
|
float b1 = (1 + w0.cos) * a0_inv;
|
|
|
|
res->b0 = b1 / 2;
|
|
res->b1 = -b1;
|
|
res->b2 = b1 / 2;
|
|
res->a1 = -2*w0.cos * a0_inv;
|
|
res->a2 = (1 - alpha) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_notch_filter(struct biquad_coeff *res, float freq, float Q)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1/(1 + alpha);
|
|
|
|
res->b0 = 1 * a0_inv;
|
|
res->b1 = -2*w0.cos * a0_inv;
|
|
res->b2 = 1 * a0_inv;
|
|
res->a1 = -2*w0.cos * a0_inv;
|
|
res->a2 = (1 - alpha) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_bpf_peak(struct biquad_coeff *res, float freq, float Q)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1/(1 + alpha);
|
|
|
|
res->b0 = Q*alpha * a0_inv;
|
|
res->b1 = 0;
|
|
res->b2 = -Q*alpha * a0_inv;
|
|
res->a1 = -2*w0.cos * a0_inv;
|
|
res->a2 = (1 - alpha) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_bpf(struct biquad_coeff *res, float freq, float Q)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1/(1 + alpha);
|
|
|
|
res->b0 = alpha * a0_inv;
|
|
res->b1 = 0;
|
|
res->b2 = -alpha * a0_inv;
|
|
res->a1 = -2*w0.cos * a0_inv;
|
|
res->a2 = (1 - alpha) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_allpass_filter(struct biquad_coeff *res, float freq, float Q)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1/(1 + alpha);
|
|
|
|
res->b0 = (1 - alpha) * a0_inv;
|
|
res->b1 = (-2*w0.cos) * a0_inv;
|
|
res->b2 = 1; // Same as a0
|
|
res->a1 = res->b1;
|
|
res->a2 = res->b0;
|
|
}
|
|
|
|
//
|
|
// 'A' is the cookbook's A: the square root of the linear gain, so a
|
|
// peaking section boosts by A*A and a shelf reaches A*A. db_to_A() is
|
|
// where one comes from.
|
|
//
|
|
// These took the gain itself and square-rooted it here, which reads more
|
|
// naturally and was a mistake. The units of the argument were invisible
|
|
// - the app's copy of these same formulas takes A - and two call sites
|
|
// had grown fudge factors from guessing which, in opposite directions.
|
|
//
|
|
static inline void _biquad_peaking(struct biquad_coeff *res, float freq, float Q, float A)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
float a0_inv = 1 / (1 + alpha/A);
|
|
|
|
res->b0 = (1 + alpha*A) * a0_inv;
|
|
res->b1 = (-2*w0.cos) * a0_inv;
|
|
res->b2 = (1 - alpha*A) * a0_inv;
|
|
res->a1 = res->b1;
|
|
res->a2 = (1 - alpha/A) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_loshelf(struct biquad_coeff *res, float freq, float Q, float A)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
|
|
float ap1 = A + 1;
|
|
float am1 = A - 1;
|
|
float sqAmin2 = 2 * sqrtf(A) * alpha;
|
|
float a0_inv = 1 / (ap1 + am1*w0.cos + sqAmin2);
|
|
|
|
res->b0 = A * (ap1 - am1*w0.cos + sqAmin2) * a0_inv;
|
|
res->b1 = 2*A* (am1 - ap1*w0.cos) * a0_inv;
|
|
res->b2 = A * (ap1 - am1*w0.cos - sqAmin2) * a0_inv;
|
|
res->a1 = -2*(am1 + ap1*w0.cos) * a0_inv;
|
|
res->a2 = (ap1 + am1*w0.cos - sqAmin2) * a0_inv;
|
|
}
|
|
|
|
static inline void _biquad_hishelf(struct biquad_coeff *res, float freq, float Q, float A)
|
|
{
|
|
const struct sincos w0 = biquad_w0(freq);
|
|
float alpha = w0.sin/(2*Q);
|
|
|
|
float ap1 = A + 1;
|
|
float am1 = A - 1;
|
|
float sqAmin2 = 2 * sqrtf(A) * alpha;
|
|
float a0_inv = 1 / (ap1 - am1*w0.cos + sqAmin2);
|
|
|
|
res->b0 = A * (ap1 + am1*w0.cos + sqAmin2) * a0_inv;
|
|
res->b1 =-2*A* (am1 + ap1*w0.cos) * a0_inv;
|
|
res->b2 = A * (ap1 + am1*w0.cos - sqAmin2) * a0_inv;
|
|
res->a1 = 2*(am1 - ap1*w0.cos) * a0_inv;
|
|
res->a2 = (ap1 - am1*w0.cos - sqAmin2) * a0_inv;
|
|
}
|
|
|
|
static inline float biquad_step(struct biquad *bq, float x0)
|
|
{ return _biquad_step(&bq->coeff, &bq->state, x0); }
|
|
|
|
#define biquad_lpf(bq,f,Q) _biquad_lpf(&(bq)->coeff,f,Q)
|
|
#define biquad_hpf(bq,f,Q) _biquad_hpf(&(bq)->coeff,f,Q)
|
|
#define biquad_notch_filter(bq,f,Q) _biquad_notch_filter(&(bq)->coeff,f,Q)
|
|
#define biquad_bpf_peak(bq,f,Q) _biquad_bpf_peak(&(bq)->coeff,f,Q)
|
|
#define biquad_bpf(bq,f,Q) _biquad_bpf(&(bq)->coeff,f,Q)
|
|
#define biquad_allpass_filter(bq,f,Q) _biquad_allpass_filter(&(bq)->coeff,f,Q)
|
|
#define biquad_peaking(bq,f,Q,A) _biquad_peaking(&(bq)->coeff,f,Q,A)
|
|
#define biquad_lowshelf(bq,f,Q,A) _biquad_loshelf(&(bq)->coeff,f,Q,A)
|
|
#define biquad_highshelf(bq,f,Q,A) _biquad_hishelf(&(bq)->coeff,f,Q,A)
|