Skip to content

rainybit-code/spore

Repository files navigation

🌱 Spore - Versatile Generative Synth / FX

firmware build

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.

Propagator - the companion browser editor for Spore

Hardware / platform

Spore currently targets one platform:

Control surface

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.

Signal flow (per mode)

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.

Mode 1 - Synth · USB-MIDI played

  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  │
  └───────────────────────────────────────────────────────────────────────┘

Mode 2 - Granular · processes the audio input

  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  │
  └───────────────────────────────────────────────────────────────────┘

Mode 3 - Generative · self-playing

  ┌─ 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  │
  └───────────────────────────────────────────────────────────────────────────┘

Getting started

# pulls libDaisy + DaisySP too
git clone --recurse-submodules https://github.com/rainybit-code/spore.git
  1. 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 local toolchain/ folder (gitignored) and scripts/env.* will put it on PATH automatically. dfu-util is only needed to flash, not to build. VS Code tasks + IntelliSense are wired in .vscode/.
  2. 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
  3. Build: scripts/build.sh (or .ps1, or VS Code task build) → build/daisy_synth.bin.
  4. 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.
  5. 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).

Project layout

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.

Non-standard inputs

  • 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's Control(); neutral (no effect) until the hardware is connected.

Ideas & roadmap

See docs/IDEAS.md for the tiered backlog (more inputs, a wireless modulator, future modes, presets…).

Contributing

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.

Releases & versioning

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.0

Flash 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).

License & credits

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.

AI disclosure

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.