You've already forked ivan-torvalds-GuitarPedal
forked from AllSpiceMirrors/torvalds-GuitarPedal
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>
143 lines
4.0 KiB
C
143 lines
4.0 KiB
C
//
|
|
// Do the 'sample to float' and 'float to sample' processing
|
|
// together with basic noise gating
|
|
//
|
|
|
|
#define FLOAT_TO_SAMPLE_MULTIPLIER (0x80000000 / 1.0)
|
|
|
|
// Random buffer size. Note that we only expose
|
|
// half the data in the buffer so that we don't
|
|
// need to worry about new input overwriting
|
|
// the part of the buffer we're looking at.
|
|
//
|
|
// 256 samples is about 5ms worth of data at 48kHz
|
|
#define USB_OUTPUT_SIZE 512
|
|
#define USB_OUTPUT_MASK (USB_OUTPUT_SIZE-1)
|
|
|
|
static struct {
|
|
unsigned phase;
|
|
unsigned head, tail;
|
|
raw_sample_t buf[USB_OUTPUT_SIZE];
|
|
} usb_output;
|
|
|
|
//
|
|
// The audio board with the TAC5112 seems to return -1.0..1.0
|
|
// for a -1.75V .. +1.75V signal swing (3.5V peak-to-peak)
|
|
//
|
|
// But then the *output* for a -1.0..1.0 signal is the
|
|
// expected 1Vrms: -1.41 .. +1.41V (2.828V peak-to-peak)
|
|
//
|
|
// I may be doing something wrong on the analog board, or I'm
|
|
// possibly missing some TAC5112 setup detail.
|
|
//
|
|
// In the meantime, this strange SAMPLE_TO_FLOAT_MULTIPLIER
|
|
// exists to correct for whatever I'm doing wrong.
|
|
//
|
|
// The intent here is that all our internal audio processing
|
|
// is based on a 1Vrms voltage scale.
|
|
|
|
#define SAMPLE_TO_FLOAT_MULTIPLIER (3.45 / 2.82843 / 0x80000000)
|
|
|
|
static inline sample_t process_input(raw_sample_t sample)
|
|
{
|
|
sample_t val = {
|
|
.left = sample.left * SAMPLE_TO_FLOAT_MULTIPLIER,
|
|
.right = sample.right * SAMPLE_TO_FLOAT_MULTIPLIER
|
|
};
|
|
|
|
if (tuner_mode) {
|
|
analyze_process_sample(val);
|
|
val.left = val.right = 0.0;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
//
|
|
// Convert a nominal -1.0..1.0 signal to a full-scale s32 sample.
|
|
//
|
|
// Anything at or past full scale gets pinned to the end of the range.
|
|
// The test has to be on the *input*: the FP->int conversion is only
|
|
// defined for values that already fit, and testing the result can't
|
|
// work anyway, since it comes back around into range at +-2.0, +-4.0...
|
|
//
|
|
// Below full scale there's nothing to worry about: the largest float
|
|
// under 1.0 is (1 - 2^-24), so the scaled value tops out at 2^31 - 128
|
|
// and the conversion cannot overflow.
|
|
//
|
|
static inline s32 convert_output(float out)
|
|
{
|
|
if (fabsf(out) >= 1.0f) {
|
|
output_clipped = 1;
|
|
return out > 0.0f ? INT32_MAX : INT32_MIN;
|
|
}
|
|
return lrintf(out * FLOAT_TO_SAMPLE_MULTIPLIER);
|
|
}
|
|
|
|
static inline raw_sample_t process_output(sample_t out, raw_sample_t dry)
|
|
{
|
|
raw_sample_t wet = {
|
|
.left = convert_output(out.left),
|
|
.right = convert_output(out.right)
|
|
};
|
|
raw_sample_t usb;
|
|
|
|
switch (settings.usb_output) {
|
|
case LR_None: return wet;
|
|
case LR_Wet: usb = wet; break;
|
|
case LR_Dry: usb = dry; break;
|
|
default: usb.left = wet.left; usb.right = dry.left; break;
|
|
}
|
|
unsigned head = usb_output.head;
|
|
|
|
// Store the sample, *then* publish it. The other way round - which
|
|
// is what this used to do - lets cpu0 see the new head and read the
|
|
// slot before cpu1 has written it.
|
|
usb_output.buf[head & USB_OUTPUT_MASK] = usb;
|
|
smp_store_release(&usb_output.head, head + 1);
|
|
return wet;
|
|
}
|
|
|
|
static inline unsigned output_buffer_size(void)
|
|
{
|
|
unsigned nr = usb_output.head - usb_output.tail;
|
|
if (nr > USB_OUTPUT_SIZE/2)
|
|
nr = USB_OUTPUT_SIZE/2;
|
|
return nr;
|
|
}
|
|
|
|
static inline unsigned get_output_samples(s32 *buffer, unsigned nr)
|
|
{
|
|
unsigned head = smp_load_acquire(&usb_output.head);
|
|
unsigned tail = usb_output.tail;
|
|
|
|
// If more than 75% of the buffer is filled, we
|
|
// have lost sync, and we will just restart at
|
|
// the half buffer mark.
|
|
unsigned max = head - tail;
|
|
if (max > 3 * USB_OUTPUT_SIZE / 4) {
|
|
max = USB_OUTPUT_SIZE / 2;
|
|
tail = head - max;
|
|
}
|
|
|
|
// This is the max we'll copy
|
|
//
|
|
// Note that we keep 'output.tail' as
|
|
// the full 32-bit value so that we can
|
|
// tell if the head has gone way past.
|
|
if (nr > max)
|
|
nr = max;
|
|
usb_output.tail = tail + nr;
|
|
|
|
tail &= USB_OUTPUT_MASK;
|
|
unsigned batch = nr;
|
|
if (tail + batch > USB_OUTPUT_SIZE) {
|
|
batch = USB_OUTPUT_SIZE - tail;
|
|
memcpy(buffer, usb_output.buf + tail, batch * sizeof(raw_sample_t));
|
|
buffer += batch * 2;
|
|
batch = nr - batch;
|
|
tail = 0;
|
|
}
|
|
memcpy(buffer, usb_output.buf + tail, batch * sizeof(raw_sample_t));
|
|
return nr;
|
|
}
|