0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-08-19 05:33:46 +00:00
Files
torvalds-GuitarPedal/Effects/compressor.h
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

71 lines
1.8 KiB
C

// NAME: Compressor [COMPRESSOR]
// PRIORITY: 30
// POT: "Level" LINEAR(-60.0 -10.0) = -35.0 dB
// POT: "Attack" LINEAR(2.0 100.0) = 15.0 ms
// POT: "Release" LINEAR(50.0 500.0) = 150.0 ms
// POT: "Ratio" EXPONENTIAL(1.0 20.0) = 4.8 x
// POT: "Boost" LINEAR(0.0 24.0) = 6.0 dB
//
// Compressor Effect
//
static struct {
// Pot values (pre-computed for step calculation convenience)
float level, ratio, boost;
// Envelope follower
struct envelope envelope;
// Compressor state
float compression;
} compressor = {
.level = 1.0,
.compression = 1.0
};
static inline void compressor_init(unsigned char pot[10])
{
float level_db = compressor_level_pot(pot);
compressor.level = db_to_level(level_db);
float attack_ms = compressor_attack_pot(pot);
float release_ms = compressor_release_pot(pot);
envelope_init(&compressor.envelope, attack_ms, release_ms);
float ratio = compressor_ratio_pot(pot);
compressor.ratio = 1.0f - (1.0f / ratio);
float boost_db = compressor_boost_pot(pot);
compressor.boost = db_to_level(boost_db);
}
// This may be overkill, but we're doing this by the book
static inline float mypow(float a, float b)
{
return pow2(log2f(a) * b);
}
static inline float compressor_step(float in)
{
// Envelope follower
float env = envelope_step(&compressor.envelope, in);
// Compression calculation.
//
// When we go over the compression level, we calculate a
// target compression multiplier:
//
// target = (level / env) ^ (1 - 1/ratio)
//
float target = 1.0f;
if (env > compressor.level) {
target = mypow(compressor.level / env, compressor.ratio);
compressor_effect.intense = 1;
}
// ... and then we smooth the compression factor to prevent clicks
compressor.compression = linear(0.01f, compressor.compression, target);
return in * compressor.compression * compressor.boost;
}