Forward-looking backlog for Spore — open ideas and parked work, tagged by priority.
Shipped features live in CHANGELOG.md, not here.
Legend: 🔜 next ·
- 💡 More analog inputs — A9/D24 and A11/D28 are also free; bump
AnalogSensors::kNumSensorsand add pins. Could host an expression-pedal jack or a couple of light/touch sensors at once. 🅱️ Motion IMU over I2C (D11/D12) — deferred (tier 2).io/imu.his a ready slot. Tilt → filter sweep, shake → grain spray / added randomness.- 🅲 Wireless "spinning top" modulator — a battery puck (MCU + 6-axis IMU + radio) you spin to bring the sound alive; as it slows it precesses/wobbles (deepening modulation) then topples (a trigger). A physical, decaying, generative control source. See "Wireless accessory" below.
- Daisy has no built-in radio → needs a link.
- Transport options (recommended first):
- nRF24L01+ (2.4 GHz, SPI) — recommended. Cheap (~$2/pair), low latency, Daisy can be the receiver directly over SPI (no bridge MCU on the device). Top = small MCU + IMU + nRF24 + LiPo (e.g. a Seeed XIAO).
- ESP-NOW — ESP32 in the top + ESP32 on the device, bridged to Daisy over UART. Very simple, good latency, but adds a chip on the device side.
- BLE — XIAO nRF52840 Sense (IMU built in) is tidy on the top side, but Daisy can't easily be a BLE central → needs a receiver module. Most work.
- Mapping (the "wobble"): spin rate → modulation rate/intensity; precession / nutation (wobble) → a wandering LFO that naturally grows as the top slows; topple/stop → gesture trigger or mode nudge.
- Architecture fit: slots into
io/like the IMU — aWirelessSensorfeedingModContext. The firmware is already shaped for an extra modulation source.
- 💡 More FX / FX modes — shimmer (pitch-shifted reverb), modulated/chorus delay, ping-pong, per-mode FX presets, or a second FX slot (delay → reverb in series).
- 💡 More modes — implement
IMode(modes/mode.h) and add to the array inmain.cpp. Candidates: drone/oscillator bank, resonator, wavefolder, sample looper. - 💡 Mod-matrix extensions — let a wireless source and the Generative seed wire
the matrix slots (the 6-slot matrix itself exists — see
MODULATION.md). - 💡 Granular v2 — stereo grains, panning spread, reverse grains, pitch-quantize to a scale, grain-density envelope.
- 💡 Generative v2 — Euclidean rhythm engine, probabilistic gates, multiple voices, chord/scale-aware walks.
Full protocol spec: MIDI_PROTOCOL.md.
- 🔜 2-way sync (SysEx) — full patch dump/load so the editor mirrors the device.
Needs the central
Patchstore. - 🔜 Preset librarian — patches saved in the browser (JSON/localStorage) and
on the device in QSPI (
PersistentStorage) so they survive power-off. - 🅲 Sample loading — upload an audio sample over chunked SysEx into QSPI
(8 MB), read back memory-mapped, played by a sample-player source (feeds granular
or a new mode). Biggest piece; the real driver for using QSPI as data storage
(note: data in QSPI does NOT require
BOOT_QSPI— code can stay in internal flash). - 🅲 USB audio (UAC, composite with MIDI) — make the device a USB audio
interface (record dry/wet into a DAW, use as a computer FX processor). Feasible:
the STM32H750 USB is full-speed (stereo 48k/24-bit fits) and the ST AUDIO
class + CompositeBuilder source is already vendored in libDaisy — but libDaisy
doesn't wire it up (it only wraps CDC/MIDI/MSC). Build-it-yourself: add the
AudioControl/AudioStreaming descriptors, an
usbd_audio_ifimplementation, a ring buffer between the SAI callback and USB endpoints, and an async feedback endpoint to sync the codec clock to USB SOF (the hard part — drift/clicks otherwise), all composed with MIDIStreaming. Substantial + experimental; can only be tuned on hardware; adds buffering latency vs the analog jacks. Not needed for standalone use (the Hothouse has analog stereo I/O). - 💡 protobuf for the protocol — instead of hand-rolled SysEx byte layouts, define
messages in
.protoand codegen both ends (nanopb on firmware, protobuf.js in the browser); frame the encoded bytes in SysEx (7→8-bit). Schema-driven, versioned, less manual parsing. Parked — adds a dependency + codegen step; revisit once the message set stabilizes.
- 🔜 Build-verify on hardware — install ARM toolchain,
scripts/build-libs, thenscripts/flash. Confirm audio passthrough + each mode. - 💡 Preset save/recall — persist the live
Patchto QSPI flash (PersistentStorage); footswitch combo or a knob to select slots. - 💡 LED feedback — richer status (blink on generative triggers, freeze indicator, mode color/brightness language).
- 💡 Pure Data prototyping — sketch new DSP/generative ideas in
pd/first, then port to a mode.
Audio runs a 48-sample block @ 48 kHz → ~1 ms / ~480k cycles per callback on the
M7. Shipped this round: FPU flush-to-zero (FPSCR.FZ in main.cpp — fixed the
reverb-tail denormal crackle) and a CpuLoadMeter reported over SysEx 0x02/0x42
and shown live in Propagator. Measure with that meter before/after any of the below.
- 🔜
-ffast-mathand/or-O3(next experiment — try with the meter). Currently-O2(libDaisy default). Relaxed float math is typically a few-to-~15 % on this DSP; rounding differs slightly but inaudible here. Add toCFLAGS/OPTin the projectMakefile; A/B with the CPU meter. Safer subset:-fno-math-errno -ffp-contract=fast. - 🔜 Bigger audio block (next experiment).
params::audio::kBlockSize48 → 64/96 amortizes per-block fixed overhead (control reads, LFO ticks, call setup). Cost is latency: 48→96 = 1 ms→2 ms (still low). One-line change. - 💡 Cap voices / unison. Synth's dominant cost is oscillators: 6 voices × up to
4 unison + sub ≈ 30 osc/sample worst case (
dsp/voice.h). Lower the max, or auto-reduce when the global reverb is engaged. Linear CPU savings. - 💡 Cheaper filter / oscillator per patch.
MoogLadder≈ 2× theSvf;WAVE_POLYBLEP_*pays for anti-aliasing the wavetable engine already bakes in. Defaulting toward the cheaper option where a patch allows trims per-voice cost. 🅱️ ITCM hot-loop placement — do this with a device in hand (boot-copy bug = hard-fault, unverifiable blind). The linker script has no.itcm_textsection, so it needs: a vendored copy ofSTM32H750IB_flash.lds(pointLDSCRIPTat it from the Makefile —?=, same no-submodule-edit trick asusb_identity.c), an.itcm_textsection> ITCMRAM AT > FLASH, a boot-time copy loop inmain()(startup only inits.data/.bss), and asection(".itcm_text")attr on the hot fn (e.g.Voice::Process, forced non-inline). NOTE: DTCM data is not worth it — libDaisy's owndaisy_core.hsays DTCM is "on par with internal SRAM w/ cache enabled", and the wavetables already live in cached SRAM; DTCM also shares the 128 KB region with the stack. ITCM code is the only placement lever with a real (jitter) upside here.
Currently APP_TYPE = BOOT_NONE: the app runs from the 128 KB internal flash
(~94% today — delay + reverb only cost ~2 KB of flash because ReverbSc's big
buffer lives in SDRAM). Keep it this way until code actually outgrows 128 KB — no
bootloader, simplest flashing. Note: preset-save does NOT require leaving
internal flash (PersistentStorage keeps data in QSPI regardless of where
code runs).
When code outgrows 128 KB, libDaisy offers (set APP_TYPE + flash the Daisy
bootloader once via make program-boot):
🅱️ BOOT_SRAM— runs from AXI SRAM, ~512 KB, full speed (no XIP jitter). First choice when we just need more room. Bootloader reloads app at power-up.- 🅲
BOOT_QSPI— execute-in-place from the 8 MB QSPI, ~8 MB code space. M7 I-cache hides most of the QSPI latency (proven on shipping products) but can jitter on cold/large code paths; slower boot. Reserve for when we want megabytes in flash — e.g. baked-in wavetables / sample buffers for the granular & sample-playback ideas. That's the real future driver here.
Switching is a one-line APP_TYPE change in the Makefile + dfu-util.
- 💡 Bring expansion pins out cleanly — small header or jack(s) for the analog sensor / I2C IMU / wireless receiver; enclosure grommet for cabling.
- 💡 Decide mono vs stereo build to match the purchased Hothouse variant.