Spore is firmware that turns an Electrosmith Daisy Seed in a Cleveland Music Co. Hothouse enclosure into a multi-mode instrument:
- Synth - playable voice over USB MIDI (analog/wavetable osc → filter → envelope)
- Granular - granular texture from the built-in audio input, with freeze
- Generative - self-playing "Krell" voice (random-walk pitch, evolving envelopes)
…plus a shared modulation engine (hardware-RNG LFOs / sample & hold / 6-slot mod matrix), a tempo-synced delay + clock, and an analog sensor input on a free ADC pin.
Configure it live from its companion browser editor,
Propagator -
running at https://rainybit-code.github.io/propagator/. See
docs/MIDI_PROTOCOL.md for the CC/SysEx contract the two share.
Spore currently targets one platform:
- MCU board: Electrosmith Daisy Seed (STM32H750, stereo codec, SDRAM).
- Built with: libDaisy + DaisySP (vendored as submodules).
- Enclosure: the Cleveland Music Co. Hothouse DIY kit - chosen for ease of use, since it brings out 6 knobs, 3 toggles, 2 footswitches, 2 LEDs and stereo 1/4" I/O off the shelf.
| Control | Function |
|---|---|
| Toggle 1 | Mode: UP Synth · MIDDLE Granular · DOWN Generative (use a 3-position switch) |
| Toggle 2 | Per-mode variant (waveform / pitch-quantize / scale) |
| Toggle 3 | FX: UP off · MIDDLE delay · DOWN reverb |
| Knobs 1–6 | Per-mode macros - see src/config/params.h |
| Footsw 1 | Tap = engage/bypass · Hold = edit FX (knobs become FX controls) |
| Footsw 2 | Mode action - Granular freeze, Generative re-seed |
| LED 1 / 2 | Engaged state (mid = editing FX) / active FX |
Presets - three per mode, saved in QSPI. Hold Footsw 2 to enter preset mode, then flip Toggle 2 to recall slot 1/2/3 for the current mode (so the slot is the variant position). While holding Footsw 2, tap Footsw 1 to save the current sound to the selected slot. The LEDs show the active preset: right = 1, left = 2, both = 3.
Enter DFU for flashing over MIDI (CC 119 ≥ 64, e.g. from Propagator) or with the Daisy Seed's BOOT + RESET buttons.
Audio: Hothouse stereo in and out (1/4" jacks). Synth & Generative ignore the input (they generate); Granular records and reuses it.
Global FX is a decoupled delay + reverb block on the Synth and Granular output
(selected by Toggle 3); Generative uses its own dedicated reverb instead, so the global
FX is bypassed there. Hold Footswitch 1 to remap the 6 knobs to FX (mix / delay time /
feedback / tone / reverb decay / damping) - soft-takeover means nothing jumps when you
let go. See src/fx/effects.h.
Each mode is its own small audio graph. Knobs/CC set the parameters; the shared mod engine (LFOs · random S&H · chaos · analog sensor) drives the 6-slot mod matrix that moves them. A safety limiter hard-clamps the output of every mode.
IN · USB MIDI (note + velocity)
│
▼
┌─ VOICE · polyphonic, up to 6 ─────────────────────────────────────────┐
│ OSC analog: 2-4 detuned unison + sub │
│ or wavetable: scan · FM · wavefold + sub │
│ SHAPE osc ─► drive (sat) ─► filter (SVF 2-pole | Moog 4-pole) │
│ AMP ─► ADSR x velocity · filter-env ─► cutoff │
└───────────────────────────────────────────────────────────────────────┘
│ voices summed + stereo-panned
▼
┌─ GLOBAL FX · Toggle 3 ────────────────────────────────────────────────┐
│ off | delay | reverb │
└───────────────────────────────────────────────────────────────────────┘
│
▼
┌─ MASTER OUT · filter + volume (post-FX) ──────────────────────────────┐
│ filter (off | LP | BP | HP) · cutoff · res ─► volume │
└───────────────────────────────────────────────────────────────────────┘
│
▼
┌─ LIMITER ─────────────────────────────────────────────────────────────┐
│ hard-clamp +/-1 ─► OUT · L / R │
└───────────────────────────────────────────────────────────────────────┘
┌─ MOD MATRIX · 6 slots ────────────────────────────────────────────────┐
│ SRC LFO1 · LFO2 · Rnd S&H · Chaos · Steps · Sensor · Vel · Key │
│ DST cutoff · pitch · wt-scan · drive · sub · FM · amp · LFO-rate │
└───────────────────────────────────────────────────────────────────────┘
IN · AUDIO (1/4" jack)
│
▼
┌─ CAPTURE ─────────────────────────────────────────────────────────┐
│ record ─► SDRAM ring buffer (FOOTSW 2 = freeze) │
└───────────────────────────────────────────────────────────────────┘
│
▼
┌─ GRAINS · up to 12 ───────────────────────────────────────────────┐
│ read @ pitch · Hann window · summed ─► wet │
└───────────────────────────────────────────────────────────────────┘
│
▼
┌─ DRY / WET MIX ───────────────────────────────────────────────────┐
│ dry (live input) + wet (grains) │
└───────────────────────────────────────────────────────────────────┘
│
▼
┌─ GLOBAL FX · Toggle 3 ────────────────────────────────────────────┐
│ off | delay | reverb │
└───────────────────────────────────────────────────────────────────┘
│
▼
┌─ MASTER OUT · filter + volume (post-FX) ──────────────────────────┐
│ filter (off | LP | BP | HP) · cutoff · res ─► volume │
└───────────────────────────────────────────────────────────────────┘
│
▼
┌─ LIMITER ─────────────────────────────────────────────────────────┐
│ hard-clamp +/-1 ─► OUT · L / R │
└───────────────────────────────────────────────────────────────────┘
┌─ MODULATION ──────────────────────────────────────────────────────┐
│ density ◄ Chaos · pitch-spread ◄ Sensor + random │
└───────────────────────────────────────────────────────────────────┘
┌─ SEQUENCER (internal) ────────────────────────────────────────────────────┐
│ slow clock ─► random-walk pitch ─► seed note / chord │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌─ VOICE · up to 5 (same engine as Synth) ─────────────────────────────────┐
│ osc ─► drive ─► filter (SVF | Moog) ─► amp (ADSR) │
└───────────────────────────────────────────────────────────────────────────┘
│ voices summed
▼
┌─ DEDICATED REVERB · lush (global FX bypassed) ────────────────────────────┐
│ built-in stereo reverb │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌─ MASTER OUT · filter + volume (post-reverb) ──────────────────────────────┐
│ filter (off | LP | BP | HP) · cutoff · res ─► volume │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
┌─ LIMITER ─────────────────────────────────────────────────────────────────┐
│ hard-clamp +/-1 ─► OUT · L / R │
└───────────────────────────────────────────────────────────────────────────┘
┌─ MODULATION ──────────────────────────────────────────────────────────────┐
│ filter sway ◄ Chaos · scan drift ◄ Chaos · centre ◄ Sensor │
└───────────────────────────────────────────────────────────────────────────┘
# pulls libDaisy + DaisySP too
git clone --recurse-submodules https://github.com/rainybit-code/spore.git- Toolchain - install ARM GCC +
make+dfu-util. Easiest is the official Daisy toolchain; on Windows you can instead drop a portable xPack ARM GCC + windows-build-tools into a localtoolchain/folder (gitignored) andscripts/env.*will put it on PATH automatically.dfu-utilis only needed to flash, not to build. VS Code tasks + IntelliSense are wired in.vscode/. - Libraries - wired as git submodules under
lib/. One command fetches them and builds libDaisy + DaisySP (re-run after a submodule update):scripts/setup.sh # Windows: scripts\setup.ps1 - Build:
scripts/build.sh(or.ps1, or VS Code task build) →build/daisy_synth.bin. - Install the bootloader (one time): Spore runs from SRAM via the Daisy bootloader
(it outgrew the 128 KB internal flash). Put the Daisy in DFU mode (hold BOOT, tap
RESET) and run
scripts/install-bootloader.sh(or.ps1) once. - Flash the app (written to QSPI; the bootloader copies it to SRAM and boots in ~10 ms):
- Easiest — Propagator: it reboots the device into the bootloader (MIDI CC 118) and flashes over WebUSB. Fully automated, no button timing.
- Manual: run
scripts/flash.sh(or.ps1) with the device in the bootloader's DFU. A freshly-bootloadered board (no app yet) waits there automatically; an already-running board can be put back into DFU by sending CC 118 ≥ 64 (or just use Propagator).
src/
main.cpp wiring: mode dispatch, controls, MIDI, audio callback
hothouse.h/.cpp Hothouse hardware proxy (vendored, GPL-3.0)
config/params.h ★ ALL tunable parameters (ranges, rates, sizes)
modes/ synth_mode / granular_mode / generative_mode (one each)
mod/modulation.h random LFO / sample&hold / random walk / RNG
fx/effects.h decoupled global FX: delay + reverb (Toggle 3)
io/ controls, knobs (shift-layer soft-takeover), midi_in,
clock (MIDI clock), sensors (analog ADC)
lib/ libDaisy + DaisySP (git submodules)
scripts/ setup / build / flash / install-bootloader / clean / release (.sh + .ps1)
pd/ Pure Data sketches for prototyping DSP ideas
Tuning: every number worth changing lives in src/config/params.h. Edit a value
→ reflash, or turn a knob live (ranges are defined there too). Adding a mode = drop a
class implementing IMode (src/modes/mode.h) into the array in main.cpp.
- Analog sensor (LDR / FSR / flex / expression / CV) - wired to free A0 = D15;
handled in
src/io/sensors.h(extends the ADC; more channels: A9=D24, A11=D28). Feeds the modulation routing in each mode'sControl(); neutral (no effect) until the hardware is connected.
See docs/IDEAS.md for the tiered backlog (more inputs, a wireless
modulator, future modes, presets…).
Contributions are welcome - see CONTRIBUTING.md for the project
layout, coding conventions, the IMode extension pattern, and the build/format/PR flow.
Formatting is enforced by .clang-format (run clang-format -i before
committing); CI builds the firmware and checks formatting on every PR.
CI (GitHub Actions, .github/workflows/firmware.yml)
builds the firmware on every push/PR. Versioning is SemVer via git
tags; pushing a vX.Y.Z tag builds and publishes a GitHub Release with
spore-vX.Y.Z.{bin,hex,elf} attached, using the matching CHANGELOG.md
section as the release notes. The release also includes spore-vX.Y.Z-bootloader.bin (the
unmodified Daisy bootloader from libDaisy) so Propagator can install the bootloader and flash
the app in one place — bootloader → internal flash 0x08000000, app → QSPI 0x90040000.
# 1. add your notes under "## [Unreleased]" in CHANGELOG.md
# 2. cut the release in one command (moves notes -> dated heading, commits, tags, pushes)
# Windows: scripts\release.ps1 v0.2.0
scripts/release.sh v0.2.0Flash a release .bin after the bootloader is installed (see Getting started): reset
into the bootloader's DFU window, then scripts/flash.*, or drag the .bin into the
Daisy Web Programmer (which writes it through the bootloader).
GPL-3.0-or-later. Copyright (C) 2026 Joakim Langkilde. See LICENSE.
The licence is GPL because the project compiles in the Hothouse hardware proxy
(src/hothouse.cpp / .h), which is GPL-3.0 © Cleveland Music Co. - included from
HothouseExamples with its header
intact. libDaisy and
DaisySP (both MIT, © Electrosmith) are pulled
in as submodules under lib/, not redistributed here.
In the interest of transparency: this project was built with substantial help from AI. Code, documentation, and design were generated and iterated with Claude (Claude Code) under human direction and review.
