How it works
Three threads around one chain
Section titled “Three threads around one chain”- UI thread (Slint). Every edit goes through
AppState, the single source of truth. Structural changes (add, remove, move a block, load a preset) produce aChainSpec; live changes (a knob, a bypass, an A/B or mix change) produce aChainCommand. - Chain builder thread. It instantiates the plugins and models of a
ChainSpec, off the audio thread, and hands the finished chain over, replaying the live commands received meanwhile. - JACK callback. Tuner, then effects, then looper, with preallocated buffers. A new chain is crossfaded in over 20 ms; the old one is dropped off the audio thread.
Real-time rules
Section titled “Real-time rules”The audio callback never allocates memory and never waits on a lock: it only uses try_lock and
atomics. Model loading, file reading and plugin instantiation all happen on the builder thread.
Long impulse responses are split: the first 170 ms are convolved in the callback, the rest on a
worker thread with a deadline, so the callback’s cost stays flat.
Scenes, knobs and bypasses are commands applied to the running chain: no rebuild, so delays and reverbs ring on. Bypass uses a 10 ms ramp; delays and reverbs fade their input and let their output ring out.
The OS image
Section titled “The OS image”A Buildroot image with BusyBox init, no systemd and no desktop:
- a
PREEMPT_RTkernel (Raspberry Pi 6.12, or mainline 6.12 on x86_64); - jackd at real-time priority 80 on the audio interface, waiting for one if none is plugged;
- the UI full screen on DRM/KMS, without X11 or Wayland, restarted with the audio;
- NetworkManager, chrony and dropbear, and nothing else.
| Partition | Mounted | |
|---|---|---|
FAT PEDALBOARD |
/boot, read-only |
pedalboard.txt, authorized_keys |
| System A and B | /, read-only |
Never written while running, so a power cut cannot damage it |
| Data | /data |
Presets, models, plugins, networks; created on first boot, checked at every boot |
Updates
Section titled “Updates”Updates are RAUC bundles signed with the release key.
Each image records OS_ID, a fingerprint of everything in the system but the app. A release
whose OS_ID matches the running system is installed as an app update: the app alone,
extracted to /data/app, tried at its next start and kept once it has run 30 seconds. Otherwise
it is a system update: written to the other slot and tried once with the firmware’s tryboot
flag on the Pi, or GRUB’s environment on x86_64, then kept once the UI has run 30 seconds.
The app runs as an unprivileged user. It downloads and checks the bundle, then asks a small root service to install it; RAUC checks the signature.
Code layout
Section titled “Code layout”src/├── audio/ JACK callback, chain switcher, tuner, looper├── plugins/ lilv host, LV2 features, NAM, impulse responses, small DSP blocks├── state/ AppState: chain, presets, scenes, setlists, session├── presets/ Preset and setlist storage├── tone3000/ TONE3000 account, library, pictures├── midi.rs MIDI parsing, the Helix-style map, clock├── update.rs Release check, download, request to the OS updater└── …_ui.rs Glue between each Settings card or page and the UIui/ Slint UI: pages, components, themenative/ C wrapper around NeuralAudioos/ Buildroot tree: boards, packages, init scripts, RAUC