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>
192 lines
4.9 KiB
C
192 lines
4.9 KiB
C
//
|
|
// This is the "ui" for now - really just for very random testing
|
|
//
|
|
#include "eeprom.h"
|
|
|
|
static void move_pot(struct effect *effect, int dir)
|
|
{
|
|
if (!dir)
|
|
return;
|
|
|
|
int new_active = effect->active_pot;
|
|
do {
|
|
new_active += dir;
|
|
if (new_active < 0)
|
|
new_active = 9;
|
|
else if (new_active > 9)
|
|
new_active = 0;
|
|
if (new_active == effect->active_pot)
|
|
return;
|
|
} while (!effect->pots[new_active].label);
|
|
effect->active_pot = new_active;
|
|
}
|
|
|
|
struct pot_range { int min, max; };
|
|
|
|
static const struct pot_range get_pot_range(const struct pot_descr *pot)
|
|
{
|
|
int min = 0, max = 120;
|
|
|
|
if (pot->enum_names) {
|
|
min = 0;
|
|
for (max = 0; pot->enum_names[max+1]; max++)
|
|
/* nothing */;
|
|
}
|
|
return (struct pot_range) { min, max };
|
|
}
|
|
|
|
// Note that the "__atomic" part isn't actually about SMP, just the
|
|
// interrupts
|
|
//
|
|
// Also note how the 'select' rotary low bits are ignored but allowed
|
|
// to accumulate - but cleared if something else happens.
|
|
static bool read_pots(struct effect *effect, unsigned char *pots)
|
|
{
|
|
const struct pot_descr *pot = effect->pots + effect->active_pot;
|
|
const struct pot_range range = get_pot_range(pot);
|
|
|
|
// For small ranges, don't make the rotary so twitchy
|
|
int ignore_low_bits = 0;
|
|
if (range.max - range.min < 25)
|
|
ignore_low_bits = 2;
|
|
|
|
int mask = (1 << ignore_low_bits)-1;
|
|
int val = __atomic_fetch_and(&rotary_value, mask, __ATOMIC_RELAXED);
|
|
val >>= ignore_low_bits;
|
|
|
|
if (!val) {
|
|
// Ignore low two bits of rotary select
|
|
int select = __atomic_fetch_and(&rotary_select, 3, __ATOMIC_RELAXED);
|
|
select &= ~3;
|
|
|
|
if (select) {
|
|
int dir = (select < 0) ? -1 : 1;
|
|
|
|
move_pot(effect, dir);
|
|
return true;
|
|
}
|
|
|
|
// No rotary changes. Shaft pressed?
|
|
//
|
|
// Clear and ignore the long press, it's the result
|
|
// of "hold and rotate"
|
|
unsigned int both = (1u << ROTARY_SWITCH) | (1u << LONGPRESS(ROTARY_SWITCH));
|
|
unsigned int sw = __atomic_fetch_and(&switch_val, ~both, __ATOMIC_RELAXED);
|
|
if (!(sw & (1u << ROTARY_SWITCH)))
|
|
return false;
|
|
move_pot(effect, 1);
|
|
return true;
|
|
}
|
|
|
|
val += pots[effect->active_pot];
|
|
if (val < range.min)
|
|
val = range.min;
|
|
else if (val > range.max)
|
|
val = range.max;
|
|
|
|
pots[effect->active_pot] = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Human perception isn't linear, but neither
|
|
// is LED intensity, particularly since we're
|
|
// typically driving the LED at the lower range
|
|
// of the current range
|
|
//
|
|
// Random map from 0..1 to 0..4096 that works
|
|
// for the LED I have happened to pick
|
|
static int led_pwm_mapping(float pwm)
|
|
{
|
|
return lrintf(pwm * sqrtf(pwm) * PWM_WRAP);
|
|
}
|
|
|
|
static void set_led(int pin, bool on, bool intense)
|
|
{
|
|
int level = 0;
|
|
if (on || intense) {
|
|
float pwm = intense ? settings.led_intense : settings.led_pwm;
|
|
level = led_pwm_mapping(pwm);
|
|
}
|
|
|
|
pwm_set_gpio_level(pin, level);
|
|
}
|
|
|
|
// 'update_ui()' is called every few ms to react to user events.
|
|
static void update_ui(void)
|
|
{
|
|
static int effect_idx = 0;
|
|
static int last_active_pot = -1;
|
|
|
|
struct effect *effect = effects[effect_idx];
|
|
|
|
// Stomp: enable/disable all effects
|
|
if (switch_pressed(STOMP_SWITCH)) {
|
|
switch_clear(STOMP_SWITCH);
|
|
disable_all = EFF_ENABLE_STEPS * !disable_all;
|
|
send_midi_cc(MIDI_CC_GLOBAL_ENABLE, disable_all ? 0 : 127);
|
|
}
|
|
|
|
// Which effect is being edited is decided over MIDI now that the
|
|
// encoder that used to do it is gone.
|
|
int idx = current_midi_effect_idx;
|
|
|
|
if (idx != effect_idx) {
|
|
effect_idx = idx;
|
|
effect = effects[idx];
|
|
last_active_pot = -1; // Force active_pot update on screen switch
|
|
|
|
send_midi_pc(effect_idx);
|
|
}
|
|
|
|
//
|
|
// One LED: lit while the pedal is passing effects, bright when
|
|
// something wants your attention. See status.h for why all three
|
|
// of those share the one brightness.
|
|
//
|
|
// 'samples_dropped' is not cleared here - the main loop drains it just
|
|
// after this, and reports the count over MIDI.
|
|
//
|
|
set_led(LED_GPIO, !disable_all,
|
|
output_clipped || samples_dropped || attention_preview);
|
|
|
|
static uint8_t last_clipped = 0;
|
|
static uint8_t last_intense = 0;
|
|
if (output_clipped != last_clipped) {
|
|
send_midi_cc(MIDI_CC_AUDIO_CLIPPING, output_clipped ? 127 : 0);
|
|
last_clipped = output_clipped;
|
|
}
|
|
if (effect->intense != last_intense) {
|
|
send_midi_cc(MIDI_CC_EFFECT_INTENSE, effect->intense ? 127 : 0);
|
|
last_intense = effect->intense;
|
|
}
|
|
|
|
effect->intense = 0;
|
|
output_clipped = 0;
|
|
if (attention_preview)
|
|
attention_preview--;
|
|
|
|
unsigned int seq = effect->seq;
|
|
unsigned char *cur_pot = effect->pot_values[seq & 1];
|
|
unsigned char *new_pot = effect->pot_values[!(seq & 1)];
|
|
memcpy(new_pot, cur_pot, 10);
|
|
|
|
// If something changed, let the other CPU know
|
|
if (read_pots(effect, new_pot)) {
|
|
for (int i=0; i<10; i++) {
|
|
int val = new_pot[i];
|
|
int old_val = cur_pot[i];
|
|
if (val != old_val) {
|
|
send_sysex_set_param(effect_idx, i+1, val);
|
|
}
|
|
}
|
|
smp_store_release(&effect->seq, seq + 1);
|
|
}
|
|
|
|
if (effect->active_pot != last_active_pot) {
|
|
send_midi_cc(MIDI_CC_ACTIVE_POT, effect->active_pot);
|
|
last_active_pot = effect->active_pot;
|
|
}
|
|
}
|