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>
312 lines
10 KiB
C
312 lines
10 KiB
C
#ifndef BINDINGS_H
|
|
#define BINDINGS_H
|
|
|
|
//
|
|
// What the physical controls actually do.
|
|
//
|
|
// There is one rotary and one footswitch, and what a gesture on either
|
|
// of them means is looked up here rather than compiled in. That is the
|
|
// whole point: a pedal with no screen cannot tell you what its knob is
|
|
// for, so the answer has to be set from something that can, and the
|
|
// WebMIDI app is that something.
|
|
//
|
|
// Nothing here is saved. The table is rebuilt from the defaults below
|
|
// on every boot, and those defaults are exactly what the pedal did when
|
|
// the gestures were hardcoded, so an unprogrammed pedal behaves as it
|
|
// always has. Persisting it waits for the storage rework, because the
|
|
// layout should not be guessed at before the shape of what is stored
|
|
// has settled - which is also what makes this testable on the old board
|
|
// with the 2kbit part, where there is nowhere to save it to anyway.
|
|
//
|
|
|
|
//
|
|
// The gestures - what a binding is *from*.
|
|
//
|
|
// This lists the gestures that exist, not the ones that are imaginable.
|
|
// The next board's jack carries either two more footswitches or an
|
|
// analog expression pedal, and whichever it turns out to be becomes
|
|
// more entries here; the wire format has a whole byte for the id, so
|
|
// growing the list costs nothing but the entries.
|
|
//
|
|
// Turning the rotary while pressing its shaft is deliberately not one
|
|
// of these. It used to select a pot, and it cannot coexist with a long
|
|
// press on the same shaft, because holding the shaft in order to turn
|
|
// it manufactures one every time.
|
|
//
|
|
enum control_id {
|
|
CTRL_ROTARY_TURN,
|
|
CTRL_ROTARY_TAP,
|
|
CTRL_ROTARY_HOLD,
|
|
CTRL_STOMP_TAP,
|
|
CTRL_STOMP_HOLD,
|
|
NR_CONTROLS,
|
|
};
|
|
|
|
//
|
|
// ...and what a binding is *to*.
|
|
//
|
|
// ACT_POT is the only one that means anything for a control that turns,
|
|
// and the others are the only ones that mean anything for one that
|
|
// clicks. Nothing here enforces that. Binding the footswitch to a pot
|
|
// is useless rather than dangerous, and the place to make a pointless
|
|
// choice hard to express is the app, not the wire.
|
|
//
|
|
enum bind_action {
|
|
ACT_NONE,
|
|
ACT_POT, // turn it: drive the target
|
|
ACT_NEXT_POT, // step the knob's pot to the next one
|
|
ACT_RESET_POT, // put the target back to its schema default
|
|
ACT_SET_POT, // set the target to val[0]
|
|
ACT_TOGGLE_POT, // flip the target between val[0] and val[1]
|
|
ACT_BYPASS,
|
|
ACT_TUNER,
|
|
ACT_SCENE, // effect = scene id
|
|
NR_ACTIONS,
|
|
};
|
|
|
|
//
|
|
// A target of BIND_FOLLOW means "whatever the knob is turning", rather
|
|
// than a particular pot.
|
|
//
|
|
// It exists for the way back. A press that resets the knob has to
|
|
// follow the knob when the knob is rebound, or it quietly goes on
|
|
// resetting the pot you moved away from - and the whole reason that
|
|
// press exists is to be the reliable way back when you cannot see
|
|
// anything. Naming the pot twice would work right up until it did not.
|
|
//
|
|
// It is deliberately not offered for the actions that carry a value.
|
|
// A value only means something once the pot is known: 80 is unity on
|
|
// the master volume and an arbitrary number of milliseconds on a delay
|
|
// time. If you want to name a value, name the pot it belongs to.
|
|
//
|
|
// 0x7f rather than 0xff because everything here goes out over SysEx,
|
|
// where the high bit is not ours to use.
|
|
//
|
|
#define BIND_FOLLOW 0x7f
|
|
|
|
//
|
|
// 'pot' numbers a parameter the way the SysEx parameter write already
|
|
// does: 0 is the mix, 1 to 10 are the effect's own pots. Two
|
|
// conventions for "which parameter of which effect" in one firmware
|
|
// would be one too many, and the mix is worth having - toggling it
|
|
// between nothing and everything is how a footswitch turns a single
|
|
// effect on and off.
|
|
//
|
|
// 'control' is which gesture fires it, and the table is flat: a gesture
|
|
// may appear in it more than once, so one press can do several things.
|
|
// Toggling one effect's mix up while taking another's down is how you
|
|
// switch between two effects without leaving the scene, and that is not
|
|
// expressible at all when a gesture gets exactly one action.
|
|
//
|
|
// Flat rather than a list hanging off each control, because it makes
|
|
// the natural write "here is the whole table" instead of "insert into
|
|
// row K's list" - and the app already draws only what the pedal echoes
|
|
// back, so that makes both directions the same message and leaves no
|
|
// insert, delete or reorder protocol to get wrong.
|
|
//
|
|
struct rule {
|
|
unsigned char control;
|
|
unsigned char action;
|
|
unsigned char effect;
|
|
unsigned char pot;
|
|
unsigned char val[2];
|
|
};
|
|
|
|
#define MAX_RULES 16
|
|
|
|
//
|
|
// Defaults for a pedal nobody has programmed.
|
|
//
|
|
// The rotary is the master volume, which is the only thing a single
|
|
// unlabelled knob can plausibly be. It does not change what any effect
|
|
// hears - Trim and Volume are the two ends of the chain and this is the
|
|
// far one - so getting it wrong costs loudness rather than tone, and it
|
|
// is audible, which matters when there is nothing to look at. It is
|
|
// also the parameter this firmware already treats as special: CC 7 goes
|
|
// straight to it.
|
|
//
|
|
// Both rotary presses reset it, and that is deliberate rather than a
|
|
// gesture going to waste. switch_irq() sets either the short bit or
|
|
// the long one and never both, so a press held a moment too long
|
|
// arrives only as a long press. If the two did different things, a
|
|
// slightly slow press would silently do the wrong one - and this action
|
|
// exists precisely to be the way back when you cannot see what you are
|
|
// doing, so it is the last thing that should be fussy about timing.
|
|
//
|
|
// The footswitch keeps what it always did.
|
|
//
|
|
static const struct rule default_rules[] = {
|
|
{ CTRL_ROTARY_TURN, ACT_POT, 0, CHAIN_VOLUME + 1 },
|
|
{ CTRL_ROTARY_TAP, ACT_RESET_POT, BIND_FOLLOW },
|
|
{ CTRL_ROTARY_HOLD, ACT_RESET_POT, BIND_FOLLOW },
|
|
{ CTRL_STOMP_TAP, ACT_BYPASS },
|
|
{ CTRL_STOMP_HOLD, ACT_TUNER },
|
|
};
|
|
|
|
//
|
|
// Three tables, and the one everything reads.
|
|
//
|
|
// A scene's rules, the pedal-wide ones, and the defaults above. For any
|
|
// one control the most specific level that has anything to say about it
|
|
// wins outright - see resolve_rules() - and what comes out is 'rules',
|
|
// which is the table it has always been and which nothing walking it had
|
|
// to learn about any of this.
|
|
//
|
|
static struct rule scene_rules[MAX_RULES];
|
|
static unsigned int nr_scene_rules;
|
|
|
|
static struct rule global_rules[MAX_RULES];
|
|
static unsigned int nr_global_rules;
|
|
|
|
//
|
|
// Bigger than a level, because it is a sum of them: sixteen rules on one
|
|
// control in a scene plus the globals' rules for the other four is more
|
|
// than sixteen, and quietly dropping the tail would be a miserable thing
|
|
// to debug. 192 bytes.
|
|
//
|
|
#define EFFECTIVE_RULES 32
|
|
|
|
static struct rule rules[EFFECTIVE_RULES];
|
|
static unsigned int nr_rules;
|
|
|
|
//
|
|
// Does this action point at a pot, and may it say "the knob's one"?
|
|
//
|
|
static bool action_has_target(unsigned int action)
|
|
{
|
|
return action == ACT_POT || action == ACT_RESET_POT ||
|
|
action == ACT_SET_POT || action == ACT_TOGGLE_POT;
|
|
}
|
|
|
|
static bool action_may_follow(unsigned int action)
|
|
{
|
|
return action == ACT_RESET_POT;
|
|
}
|
|
|
|
//
|
|
// Take the whole table off the wire.
|
|
//
|
|
// Everything is range checked here rather than where it is used, so
|
|
// that the table can be trusted by the things that walk it. An effect
|
|
// id out of range is an array read off the end, and the app is not the
|
|
// only thing that can send one of these.
|
|
//
|
|
// A rule that does not check out is dropped rather than the batch being
|
|
// rejected. The pedal answers with what it kept, so a dropped rule
|
|
// shows up at once as a row that did not come back - which is a better
|
|
// way to be told than an error the app would have to render, and it
|
|
// stops one bad rule from losing the other fifteen.
|
|
//
|
|
static bool rule_ok(const struct rule *r)
|
|
{
|
|
if (r->control >= NR_CONTROLS || r->action >= NR_ACTIONS)
|
|
return false;
|
|
|
|
if (r->action == ACT_SCENE)
|
|
return r->effect < MAX_SCENES;
|
|
|
|
if (!action_has_target(r->action))
|
|
return true;
|
|
|
|
if (r->effect == BIND_FOLLOW)
|
|
return action_may_follow(r->action);
|
|
|
|
if (r->effect >= ARRAY_SIZE(effects) || r->pot > 10)
|
|
return false;
|
|
|
|
//
|
|
// An effect with no wet and no dry has no mix to bind to, and a
|
|
// pot with no label does not exist on that effect.
|
|
//
|
|
if (!r->pot)
|
|
return !effects[r->effect]->no_mix;
|
|
return effects[r->effect]->pots[r->pot - 1].label != NULL;
|
|
}
|
|
|
|
//
|
|
// Work out what each control actually does.
|
|
//
|
|
// Per control, the most specific level that mentions it at all wins,
|
|
// and wins completely. Not a merge: every rule naming a gesture fires
|
|
// when that gesture happens - which is how one press takes one effect's
|
|
// mix up while taking another's down - so a union would mean a scene
|
|
// could never stop the footswitch being bypass. It could only ever add
|
|
// to it.
|
|
//
|
|
// Which makes ACT_NONE worth something. A scene rule that does nothing
|
|
// still counts as the scene having spoken for that control, so it is
|
|
// how you say "nothing happens here" as distinct from "this level is
|
|
// silent, ask the next one".
|
|
//
|
|
// And deleting every global rule falls back to the compiled-in
|
|
// defaults rather than to nothing at all, so a pedal cannot be
|
|
// configured into having no way back.
|
|
//
|
|
static void resolve_rules(void)
|
|
{
|
|
nr_rules = 0;
|
|
|
|
for (unsigned int ctrl = 0; ctrl < NR_CONTROLS; ctrl++) {
|
|
const struct rule *src;
|
|
unsigned int count, n = 0;
|
|
|
|
for (unsigned int i = 0; i < nr_scene_rules; i++)
|
|
n += scene_rules[i].control == ctrl;
|
|
if (n) {
|
|
src = scene_rules;
|
|
count = nr_scene_rules;
|
|
} else {
|
|
for (unsigned int i = 0; i < nr_global_rules; i++)
|
|
n += global_rules[i].control == ctrl;
|
|
if (n) {
|
|
src = global_rules;
|
|
count = nr_global_rules;
|
|
} else {
|
|
src = default_rules;
|
|
count = ARRAY_SIZE(default_rules);
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < count; i++) {
|
|
if (src[i].control != ctrl)
|
|
continue;
|
|
if (nr_rules >= EFFECTIVE_RULES)
|
|
return;
|
|
rules[nr_rules++] = src[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Take one level's table off the wire.
|
|
//
|
|
// Everything is range checked here rather than where it is used, so
|
|
// that the resolved table can be trusted by the things that walk it.
|
|
//
|
|
static void set_rules(struct rule *dst, unsigned int *dst_count,
|
|
const uint8_t *buf, unsigned int count)
|
|
{
|
|
unsigned int kept = 0;
|
|
|
|
if (count > MAX_RULES)
|
|
count = MAX_RULES;
|
|
|
|
for (unsigned int i = 0; i < count; i++) {
|
|
const uint8_t *p = buf + i * 6;
|
|
struct rule r = { p[0], p[1], p[2], p[3], { p[4], p[5] } };
|
|
|
|
//
|
|
// Values are not checked. A pot's range is a property
|
|
// of the pot, and for a following target there is no pot
|
|
// yet to ask - so they are clamped where they are used,
|
|
// which is the only place that can always do it.
|
|
//
|
|
if (rule_ok(&r))
|
|
dst[kept++] = r;
|
|
}
|
|
*dst_count = kept;
|
|
resolve_rules();
|
|
}
|
|
|
|
#endif
|