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>
153 lines
6.7 KiB
C
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(¤t_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(¤t_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(¤t_status, NULL, __ATOMIC_RELAXED);
|
|
}
|
|
|
|
#endif
|