Files
Linus Torvalds 87f86b8b43 Give the one LED all three of the things that want it
The attention brightness setting stopped doing anything visible when
the second LED went away.  It had been shown by

	settings_effect.intense = settings_effect.active_pot == 4;

which lit LED2 while you were sitting on that pot - and LED2 does not
exist any more.  So the one setting whose whole purpose is to change
how the LED looks became the one setting you could not see yourself
change.

While fixing that, deal with the other two users of the same
brightness, because there are exactly three and they had never been
written down together:

 - the output hitting full scale
 - the audio core missing its DMA deadline
 - now, previewing the attention setting itself

Keep all three sharing the LED, on purpose.  With one bit of light
there is no way to spell out which it is, but the two failures sound
completely different and the ear does the work: clipping follows how
hard you play and may well be something you want, while sample loss
means too many effects are stacked up and everything has gone to mush
and stays there.  "Something is wrong, listen" is the part worth
signalling.

What was actually wrong was the *internal* conflation, and that goes:
missing a deadline used to set the clipping flag, so the pedal told the
world it was clipping when the signal had been nowhere near full scale,
and you went looking for a level problem.  Now 'samples_dropped' is
only a count and 'output_clipped' is only clipping, which also means
MIDI_CC_AUDIO_CLIPPING finally means what it says.  A smart LED will
have colours to spend and can start telling them apart for real.

The timing gets written down while here, because it reads like an
accident and is not.  These flags are set on the audio core at 48kHz
and cleared by update_ui() at about 25Hz, and that asymmetry is the
whole mechanism.  A clipped sample lasts twenty microseconds, which no
eye will catch; holding the flag until the next UI tick stretches it to
40ms, which is about the shortest thing worth showing a human.  And it
does the averaging for free - clip one sample in a hundred and the LED
stays solidly lit, because the audio core sets the flag far faster than
the UI clears it, so "once in a while" and "all the time" look
different without counting or filtering anything.  Both properties
disappear if this is ever moved to a faster loop.

The preview holds for half a second rather than the single 40ms tick a
change would otherwise get, so a nudge from the web app is visible and
not just theoretically visible.

The three live in status.h, which is what they are and which only this
translation unit includes - types.h is also pulled in by the USB code,
where they would be nothing but unused-variable warnings.  They are
named for what they hold and share one type, rather than being 'clipping'
and 'dropped' and disagreeing about signedness for no reason.

Settings pots get names while here.  pot[4] was the attention level and
pot[5] the tuning, and neither said so.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-07-28 16:25:30 -07:00

76 lines
3.0 KiB
C

#ifndef STATUS_H
#define STATUS_H
const char *current_status = "Booting";
static inline void report_status(const char *msg)
{
current_status = msg;
}
// The difference between "report status" and "report info" is that
// informational messages will not overwrite existing pending
// messages. So they update the current status only if it was NULL.
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);
}
//
// Three things want the one LED's attention, and they 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.
//
// 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 - MIDI_CC_AUDIO_CLIPPING means clipping
// and nothing else, and 'samples_dropped' is a count, not a flag. 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
static const char *get_status(void)
{
return __atomic_exchange_n(&current_status, NULL, __ATOMIC_RELAXED);
}
#endif