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>
342 lines
9.8 KiB
C
342 lines
9.8 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "pico/stdlib.h"
|
|
#include "pico/bootrom.h"
|
|
#include "hardware/watchdog.h"
|
|
#include "pico/multicore.h"
|
|
|
|
#include "hardware/gpio.h"
|
|
#include "hardware/pio.h"
|
|
#include "hardware/i2c.h"
|
|
#include "hardware/pwm.h"
|
|
#include "hardware/clocks.h"
|
|
#include "hardware/dma.h"
|
|
#include "hardware/timer.h"
|
|
|
|
#include "board.h"
|
|
|
|
#include "status.h"
|
|
#include "debounce.pio.h"
|
|
#include "rotary.pio.h"
|
|
#include "i2s.pio.h"
|
|
|
|
#define PIO0_I2S_TX_SM 0
|
|
#define PIO0_I2S_RX_SM 1
|
|
#define PIO0_WS2812_SM 2
|
|
|
|
//
|
|
// After the state machine numbering above, which it uses.
|
|
//
|
|
// Only on a board that has them. pixels.h is the whole WS2812B driver
|
|
// and is written against NR_LEDS and WS2812_GPIO, so on a board with a
|
|
// plain PWM LED there is nothing here to compile - every caller is
|
|
// already behind the same #ifdef.
|
|
//
|
|
#ifdef WS2812_GPIO
|
|
#include "pixels.h"
|
|
#endif
|
|
|
|
// PIO1 runs one debounce state machine per switch, and the state
|
|
// machine index is the switch id - see switch.h. PIO2 has the one
|
|
// rotary encoder.
|
|
#define ROTARY_SM 0
|
|
|
|
#define PWM_WRAP 4096 // Entirely arbitrary
|
|
|
|
#include "Audio/types.h"
|
|
#include "Audio/util.h"
|
|
#include "Audio/envelope.h"
|
|
#include "Audio/single-pole.h"
|
|
#include "Audio/biquad.h"
|
|
#include "Audio/fft.h"
|
|
#include "Audio/analyze.h"
|
|
#include "tac5112.h"
|
|
|
|
#include "midi/midi.h"
|
|
#include "midi/uart.h"
|
|
#include "tusb.h"
|
|
#include "usb-audio.h"
|
|
#include "switch.h"
|
|
|
|
static int tuner_mode = 0;
|
|
static volatile int user_interaction = 0;
|
|
|
|
#include "Audio/effect.h"
|
|
|
|
uint8_t effect_chain[MAX_ROUTED_EFFECTS];
|
|
uint8_t routed_effect_count = 0;
|
|
|
|
#include "effect-state.h"
|
|
|
|
#include "scene.h"
|
|
#include "hardware.h"
|
|
#include "exp.h"
|
|
#include "midi/sysex.h"
|
|
|
|
#include "ui.h"
|
|
|
|
static inline void enable_ftz(void)
|
|
{
|
|
// FZ bit (24) in FPSCR flushes subnormal results to zero in hardware,
|
|
// covering every float op in the audio chain. Without it, any feedback
|
|
// path that decays into sub-1e-38 range causes a 5-20x FPU slowdown on
|
|
// Cortex-M33 (VFPv5 handles subnormals in hardware, not via trap, but
|
|
// still at a significant penalty). Must be set per-core.
|
|
uint32_t fpscr;
|
|
|
|
fpscr = __builtin_arm_get_fpscr();
|
|
fpscr |= 1u << 24;
|
|
__builtin_arm_set_fpscr(fpscr);
|
|
}
|
|
|
|
static void __audio_func(audio_processing)(void)
|
|
{
|
|
enable_ftz();
|
|
init_meters();
|
|
for (;;)
|
|
make_one_noise();
|
|
}
|
|
|
|
unsigned get_audio_samples(int32_t *buffer, unsigned nr)
|
|
{
|
|
return get_output_samples((s32 *)buffer, nr);
|
|
}
|
|
|
|
static void init_effects(void)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(effects); i++) {
|
|
struct effect *effect = effects[i];
|
|
reset_effect(effect);
|
|
}
|
|
|
|
//
|
|
// The settings first, because they are the pedal's and not the
|
|
// scene's - which of them is the MIDI channel should not depend
|
|
// on which scene happens to load next.
|
|
//
|
|
load_globals();
|
|
|
|
//
|
|
// No fallback for a store with nothing in it, because there is
|
|
// nothing to fall back to. A slot that fails its hash, or was
|
|
// never written, simply does not load and every effect keeps the
|
|
// defaults reset_effect() just gave it - which leaves a new pedal
|
|
// with everything at its default and nothing routed. That is the
|
|
// right answer, and guessing a chain would be worse.
|
|
//
|
|
load_scene(0);
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(effects); i++) {
|
|
struct effect *effect = effects[i];
|
|
effect->init(effect->pot_values[0]);
|
|
}
|
|
}
|
|
|
|
#include "tuner.h"
|
|
|
|
//
|
|
// How long boot is allowed to take before it is called a hang.
|
|
//
|
|
// Everything between here and the main loop is i2c probes with 2ms
|
|
// timeouts, some table building and a flash read, so the real figure is
|
|
// milliseconds and this is all margin. It has to stay well clear of
|
|
// the truth in the other direction too: a boot that legitimately took
|
|
// longer than this would reach BOOTSEL instead of playing, which is a
|
|
// worse failure than the one being guarded against.
|
|
//
|
|
#define BOOT_WATCHDOG_MS 3000
|
|
|
|
int main()
|
|
{
|
|
//
|
|
// A pedal that hangs before its main loop is a brick.
|
|
//
|
|
// It has no USB, so it is not a device and not in BOOTSEL and the
|
|
// host logs nothing at all - from the other end nothing was ever
|
|
// plugged in. It has no audio either, so the only way back is the
|
|
// BOOTSEL button, and on a board in an enclosure that is a screw-
|
|
// driver. This has been seen occasionally for a long time without
|
|
// ever being pinned down, which is partly because every occurrence
|
|
// destroys the evidence and costs a disassembly.
|
|
//
|
|
// So the hang is made survivable rather than diagnosed: arm a
|
|
// watchdog before anything that could hang, and if the previous
|
|
// boot never got far enough to disarm it, ask the bootrom for
|
|
// BOOTSEL instead of trying again. A hung pedal then comes back
|
|
// as something picotool can talk to, and the next attempt costs a
|
|
// reflash rather than a screwdriver.
|
|
//
|
|
// watchdog_enable_caused_reboot() is specifically a *timeout*, not
|
|
// any reset - a deliberate watchdog_reboot() sets a different
|
|
// magic - so this cannot be tripped by anything asking for a
|
|
// restart on purpose.
|
|
//
|
|
//
|
|
// The one thing that runs earlier than the watchdog can.
|
|
//
|
|
// The guard below is armed *here*, so it cannot catch anything that
|
|
// happens before it - the bootrom, crt0, the clock and PLL setup,
|
|
// XIP coming up. A hang there is invisible to it, and that is not
|
|
// hypothetical: the sixth occurrence came back as no USB device and
|
|
// nothing in BOOTSEL, which is what a watchdog that never got armed
|
|
// looks like from outside.
|
|
//
|
|
// So light the LED first, with a plain GPIO write. PWM wants a
|
|
// clock configured and this deliberately wants nothing at all -
|
|
// it is here to run before everything, including the thing that
|
|
// exists to catch failures. init_pwm_pins() takes the pin over
|
|
// later and keeps it lit; the first set_led() in the main loop is
|
|
// what finally turns it into a status light.
|
|
//
|
|
// Which makes the lamp answer a question nothing else can:
|
|
//
|
|
// dark and stays dark never reached main() at all
|
|
// lit and stays lit reached main(), hung during boot
|
|
// lit, then dims booted; that is the UI taking over
|
|
//
|
|
//
|
|
// ...on a board that has a plain LED on that pin. The usb-stomp
|
|
// board does not: LED_GPIO there is the stomp switch, wired
|
|
// straight to ground with no series resistor, so driving it high
|
|
// and then standing on the switch is a short. Those boards have
|
|
// three WS2812Bs instead, which cannot be lit this early - they
|
|
// want PIO, DMA and a configured clock, all of which is the thing
|
|
// this lamp exists to run before.
|
|
//
|
|
#ifndef WS2812_GPIO
|
|
gpio_init(LED_GPIO);
|
|
gpio_set_dir(LED_GPIO, GPIO_OUT);
|
|
gpio_put(LED_GPIO, 1);
|
|
#endif
|
|
|
|
if (watchdog_enable_caused_reboot())
|
|
reset_usb_boot(0, 0);
|
|
|
|
watchdog_enable(BOOT_WATCHDOG_MS, false);
|
|
|
|
enable_ftz();
|
|
|
|
init_i2s();
|
|
init_ws2812();
|
|
init_sw_pins();
|
|
init_pwm_pins();
|
|
init_rotary_encoder();
|
|
#ifdef EXP_TIP_GPIO
|
|
exp_init();
|
|
#endif
|
|
init_i2c_bus(i2c0, 400, I2C0_SDA, I2C0_SCL);
|
|
init_i2c_bus(i2c1, 400, I2C1_SDA, I2C1_SCL);
|
|
|
|
//
|
|
// Before init_usb(), because it decides what the pedal enumerates
|
|
// as. USB is a hotplug bus and the host may already be attached
|
|
// and waiting, so the name wants to exist before the device does.
|
|
// The i2c buses above are all this needs.
|
|
//
|
|
probe_hardware();
|
|
|
|
init_usb();
|
|
uart_midi_init();
|
|
|
|
absolute_time_t now = get_absolute_time();
|
|
|
|
absolute_time_t next_ui_update = delayed_by_ms(now, 50);
|
|
|
|
//
|
|
// Early boards need their codec set up; the current ones strap it
|
|
// in hardware. Unconditional because the first thing it does is
|
|
// ask whether there is a TAC5112 there to talk to, which is the
|
|
// same question as whether this is one of those boards.
|
|
//
|
|
tac5112_init();
|
|
|
|
init_effects();
|
|
|
|
multicore_launch_core1(audio_processing);
|
|
|
|
//
|
|
// Booted. Everything from here is the main loop, which has its own
|
|
// ways of going wrong and is not what this was guarding.
|
|
//
|
|
watchdog_disable();
|
|
|
|
for (;;) {
|
|
absolute_time_t now = get_absolute_time();
|
|
|
|
//
|
|
// Everything the outside world asks for is taken in
|
|
// here, and acted on here, so a sender further down can
|
|
// never have the state it is reporting changed under it.
|
|
//
|
|
tud_task();
|
|
usb_midi_poll();
|
|
uart_midi_poll();
|
|
|
|
sysex_send_identity();
|
|
sysex_send_telemetry();
|
|
#ifdef EXP_TIP_GPIO
|
|
sysex_send_exp();
|
|
#endif
|
|
sysex_send_schema();
|
|
sysex_send_state_dump();
|
|
sysex_send_status();
|
|
|
|
//
|
|
// Hand the queue whatever USB will take right now.
|
|
//
|
|
// Between the senders above and usb_audio_task() below on
|
|
// purpose. A sender builds a whole reply into the queue in
|
|
// one pass, which is what keeps it from reporting a mixture
|
|
// of before and after; this hands over a few packets of it
|
|
// and returns the moment the endpoint is full. So a reply
|
|
// the size of the schema costs many short passes through
|
|
// here instead of one long one, and the audio endpoint gets
|
|
// fed on time in between them.
|
|
//
|
|
midi_tx_drain();
|
|
usb_audio_task();
|
|
|
|
// Claim 25Hz screen updates
|
|
if (now > next_ui_update) {
|
|
next_ui_update = delayed_by_ms(now, 40);
|
|
|
|
//
|
|
// Whatever the switches are bound to.
|
|
//
|
|
// Ahead of the tuner check on purpose: update_ui()
|
|
// does not run in tuner mode, and a gesture bound to
|
|
// ACT_TUNER has to be able to turn it off again. A
|
|
// side effect is that a switch now acts while the
|
|
// tuner is up rather than being queued until it is
|
|
// dismissed, which is the more predictable of the
|
|
// two behaviours anyway.
|
|
//
|
|
handle_switch_bindings();
|
|
|
|
//
|
|
// Are we in tuner mode?
|
|
//
|
|
// The transition out is caught here rather than at
|
|
// the three places that clear tuner_mode, because
|
|
// this is the one spot that sees every route out of
|
|
// it - a footswitch, a CC, or anything added later -
|
|
// and what is owed on the way out is the same
|
|
// whichever it was.
|
|
//
|
|
static bool was_tuning = false;
|
|
|
|
if (tuner_mode) {
|
|
was_tuning = true;
|
|
tuner_mode_ui();
|
|
continue;
|
|
}
|
|
if (was_tuning) {
|
|
was_tuning = false;
|
|
tuner_silence();
|
|
}
|
|
|
|
update_ui();
|
|
}
|
|
}
|
|
}
|