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

153 lines
6.7 KiB
C

#ifndef STATUS_H
#define STATUS_H
//
// One pending message, and the whole thing is one word.
//
// This is deliberately about as simple as a diagnostic channel can be,
// and the simplicity is the feature rather than a shortcut taken on the
// way to something better.
//
// It exists for the day something goes wrong in a way that cannot be
// reasoned about, and the answer is to sprinkle "got here" markers around
// and see which ones come back - the equivalent of a printf, without a
// printf. Nothing uses it that way today and hopefully nothing ever
// will. Which is exactly why it has to stay this small: an unused
// mechanism that is one store stays correct indefinitely, and an unused
// mechanism with rules rots quietly and is discovered to be broken on the
// one day it was needed.
//
// So it has to work from cpu1, the audio core, without locking of any
// kind - and it does, today, without anything being added: a store and a
// compare-exchange, both of which armv8-m does inline, so check-audio.py
// has nothing to object to. There is no queue, no formatting and no
// second word. If two things happen, one of them is lost.
//
// **The message must point at static storage** - a string literal, or
// something else that was already there and is not going to change.
// That is the contract, and it is what makes RELAXED the right ordering:
// there is nothing to publish but the pointer, because the bytes it
// points at were visible to both cores before either of them started.
// Formatting into a buffer and reporting that would need release/acquire
// to be correct, and is not supported - on the audio core it is not even
// possible, since check-audio.py refuses the call it would take.
//
// If it ever does need to be cleverer than this - a ring, a value
// alongside the string, per-cpu slots - then this comment is the thing to
// laugh at on the way past.
//
static const char *current_status = "Booting";
//
// Something happened, and it matters more than whatever was pending.
//
// Atomic only to keep the model honest: mixing a plain store with the
// compare-exchange below and the exchange in get_status() is a data race
// however obviously fine the codegen is. RELAXED compiles to the same
// single 'str' a plain assignment does.
//
static inline void report_status(const char *msg)
{
__atomic_store_n(&current_status, msg, __ATOMIC_RELAXED);
}
//
// Something happened, but do not talk over anybody.
//
// The difference from report_status() is that this will not overwrite a
// pending message - it speaks only into silence. So a routine "did the
// thing" note cannot bury the interesting failure that was waiting to be
// read, and the caller decides which of the two it is making.
//
static inline void report_info(const char *msg)
{
const char *no_message = NULL;
__atomic_compare_exchange_n(&current_status, &no_message, msg,
false, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
}
//
// Things that want the one LED's attention, and get it.
//
// 'output_clipped' is the output hitting full scale. 'samples_dropped'
// is the audio core missing the DMA deadline. 'attention_preview' is you turning
// the attention brightness up in the settings, where the only way to
// see what you are setting is for the LED to do it.
//
// The effects' own 'intense' flags are the fourth, and reach the LED
// through show_status() rather than from here - see ui.h. They used to
// go to a second LED that this board does not have, so a closed gate or
// a compressor working was invisible by any means.
//
// They are deliberately not told apart on the LED, and that is not the
// compromise it looks like. The two failures sound completely
// different, so the ear does the disambiguating that one bit of light
// cannot: clipping tracks how hard you are playing and can be
// something you actually want, while sample loss means you have
// stacked up too many effects and everything has gone to mush and
// stays that way. "Something is wrong, listen" is the useful signal;
// which of the two it is, you can hear.
//
// They stay separate *here* so that the code and the MIDI reporting
// still know the difference - the status CCs carry clipping and the drop
// count in their own bits, and the count stays a count. A smart LED
// will have colours to spend on this and can start telling them apart.
//
// The timing is deliberate too, and worth stating because it looks
// like sloppiness otherwise. These are set on the audio core at
// 48kHz and cleared by update_ui() at about 25Hz, and that asymmetry
// is the whole mechanism:
//
// - a single clipped sample lasts 20us, which no eye will ever catch.
// Holding the flag until the next UI tick stretches it to 40ms,
// which is the shortest thing worth showing a human at all.
//
// - clip one sample in a hundred and the LED simply stays lit, because
// the audio core sets the flag far faster than the UI clears it. So
// "occasionally" and "constantly" look different without anyone
// having to filter, count or average anything: how solid the light
// looks is already a measure of how often it is happening.
//
// In other words the UI rate is not just where the LED happens to be
// updated - it is what turns an audio-rate event into something on a
// human timescale. Anything that moves this to a faster loop breaks
// both of those properties.
//
static unsigned int output_clipped;
static unsigned int samples_dropped;
static unsigned int attention_preview;
// How long the LED holds the preview, in update_ui() ticks of ~40ms
#define ATTENTION_PREVIEW_TICKS 12
//
// Meters. What the pedal can see about its own signal, which is more
// than it has ever been willing to say.
//
// Kept as levels rather than as anything already scaled for the wire,
// because the wire format is the reporting code's business and these are
// maintained at 48kHz by code that should not have to know about it.
//
// All of them decay on their own, so there is no reset handshake with
// cpu0 at all: the audio core keeps them current and whoever asks samples
// whatever is there. That is also what a meter should do. A peak that
// never falls tells you what happened once, which is no use at all while
// turning a knob and watching.
//
// Read across cores with no atomics, deliberately. These are aligned
// 32-bit words, so a read cannot tear, and the worst available outcome is
// a meter reading forty milliseconds out of date. Same bargain as
// 'samples_dropped' above, for the same reason.
//
static float meter_in; // input peak, before Trim
static float meter_floor; // and the quiet level underneath it
static float meter_out; // output peak, after Volume
static float meter_load; // of the sample period, the fraction spent working
static const char *get_status(void)
{
return __atomic_exchange_n(&current_status, NULL, __ATOMIC_RELAXED);
}
#endif