# Daisy Pod spike — scope + status **Status:** **code complete, host-verified, awaiting hardware.** The firmware crate [`rust/pm-daisy`](../rust/pm-daisy/) builds for the target and the shared engine is proven on the host (see "What's built" below). Hardware was purchased — flash + listen when it arrives. **Target hardware: Daisy *Pod*** (a Daisy Seed on a dev board with audio jack, buttons, knobs, encoder, RGB LEDs). The Seed is the brain; this spike uses its audio out + the Seed USER LED. The Pod's extra controls are a documented next step (`rust/pm-daisy/README.md`). **Decision it informs:** is the [Electrosmith Daisy](https://electro-smith.com/products/pod) platform (STM32H750, Cortex-M7 @ 480 MHz + onboard 24-bit audio codec) the right home for the audio engine *if* PolyMeter ever grows past "metronome" into a real-time audio workstation (live input FX, heavy polyphony, recording)? Today the answer is "RP2350 is the pick" (see [rust-port.md](rust-port.md)) — this spike exists to get a **first-hand, low-stakes** read on the alternative, not to commit to it. ## What's built (this is done) | Piece | Where | Verified | |---|---|---| | Shared self-running sequencer `Player` | `rust/pm-synth/src/lib.rs` | host build + render | | `pm-synth` made `#![no_std]` (libm float math) | same | builds host **and** thumbv7em | | Host preview / "simulator" → `pm-daisy-preview.wav` | `rust/synthrender` | renders; 48 kHz, on-grid onsets | | Daisy firmware (heap, board init, SAI-DMA callback, beat LED) | `rust/pm-daisy/` | **cross-compiles + links**, ~87 KB (fits 128 KB flash), all 3 codec revisions build | | Flashing + revision docs | `rust/pm-daisy/README.md` | — | **The host preview IS the simulator.** Full STM32H7 emulation (Renode) exists but won't faithfully model the audio codec — useless for "does it sound right." Instead, because the engine is shared, `synthrender` drives the *exact* `Player` the firmware runs and writes `pm-daisy-preview.wav` — a bit-identical preview of the hardware output (before its codec). Run `cargo run` in `rust/synthrender` and listen. When the Pod arrives, flashing should reproduce that WAV out the jack. This is a **digital/DSP-home** experiment only. It deliberately does **not** touch the heirloom pro-analog chain (THAT receivers/drivers, low-jitter clock, mute relay) from [`hardware/DESIGN.md`](../hardware/DESIGN.md) — the Daisy's onboard codec is a consumer part. The spike answers "does the engine feel at home on this chip," not "is this the heirloom audio path." ## The bet, in one sentence Almost the entire engine already transfers: `track-format` is `no_std` and RP2350-proven, `pm-synth` is pure `f32` with no real dependencies, **the STM32H750 has a hardware FPU so those `f32` voices run natively** (no fixed-point rewrite — the opposite of the RP2350's Cortex-M0+ situation flagged in `pm-synth/Cargo.toml`), and `synthrender/src/main.rs`'s render loop *is* the audio callback. So the spike is mostly **transport bring-up**, not an engine rewrite. ## What transfers for free vs. what's new | Piece | Source | Spike work | |---|---|---| | Track parse + schedule | `rust/track-format` (`#![no_std]`, builds for RP2350) | **none** — use verbatim | | Drum voices / click engine | `rust/pm-synth` (`Synth::new` / `trigger(name,level)` / `next_sample()->f32`) | **small** — see "no_std-ify" below | | Render→audio loop shape | `rust/synthrender/src/main.rs` `render()` | **adapt** — pre-rendered buffer → streaming callback | | Audio transport (codec + SAI) | — | **new** — the actual spike | | Toolchain / flash / logs | reuse the probe-rs + defmt workflow from `pm-grid`/`pm-kit` ([probe-flash.md](../rust/probe-flash.md)) | **setup** | ### no_std-ifying `pm-synth` (mechanical, ~½ day) `pm-synth` already uses `alloc::vec` and `core::f32::consts` — it's half-way there. To build for the target it needs: 1. `#![no_std]` + `extern crate alloc;` at the crate root. 2. **`libm`** for the float methods it calls as inherent fns (`.sin()`, `.cos()`, `.powf()`, `.tanh()`, `.floor()` — see `pm-synth/src/lib.rs:19,21,49,56,85,221`). In `no_std` these aren't inherent on `f32`; route them through `libm::{sinf,cosf,powf,tanhf,floorf}` (or the `num-traits` `Float` shim). Gate with `#[cfg(feature = "std")]` so the host `synthrender` keeps building. 3. A **global allocator** on-device (`embedded-alloc`), because `trigger()` does `alloc::vec![…]` per voice. The STM32H750 has ample RAM (≥1 MB SRAM, +64 MB SDRAM on the Seed), so a small heap is trivial — **but** allocating in the audio path is a real-time smell. Whether it glitches is itself a useful spike finding (see Decision criteria); production would pre-allocate fixed voices. ### Streaming the render loop (~½ day) `synthrender` pre-renders an entire `Vec`. On-device, keep a running sample counter `n` and a click index `ci`, and inside the SAI block callback do per-frame what the host loop does per-sample: advance `t_ns`, `trigger()` any clicks whose `time_ns <= t_ns`, then `next_sample()`. The engine is 48 kHz mono; the codec is stereo — duplicate the sample to L/R. Loop the pattern by resetting `n`/`ci` at `master_bar_ns * bars`. ## Phases (time-boxed ~2 days) 1. **Toolchain + flash hello (½ day).** Add `rust/pm-daisy` (thin BSP binary). Target `thumbv7em-none-eabihf`. Pick the Rust BSP: **`daisy`** (zlosynth) or **`libdaisy`** for a blocking SAI-DMA callback, or **`daisy-embassy`** if we want async. Blink + a defmt "hello" over RTT to confirm flashing. Flash via **USB DFU** (no extra hardware — Daisy boots to DFU) *or* probe-rs RTT with the Pi Debug Probe we already use. 2. **Codec test tone (½ day).** Stand up the SAI audio callback at 48 kHz and emit a sine — confirms the codec init (match the Seed revision: **AK4556** rev4 / **WM8731** Seed 1.1 / **PCM3060** Seed 1.2/2 — the BSP crate selects this; verify the rev at purchase) and that audio comes out the line jack. 3. **Drop in the engine (½ day).** Depend on `track-format` + `pm-synth`, no_std-ify `pm-synth`, port the streaming loop. Play a hardcoded program (`t124;kick909:4;clap909:4=.X.X;hat909:4/2=.X.X.X.X`, the same vector `synthrender` already auditions) on a loop. 4. **Measure + write up (½ day).** Capture findings against the criteria below; append a verdict to this file and a one-line pointer in `rust-port.md`. ## Deliverables - `rust/pm-daisy/` — a thin BSP crate playing a hardcoded 909 pattern out the Daisy line jack. - `pm-synth` building `no_std` (host `synthrender` still builds — guarded by a `std` feature). - A **findings verdict** appended here: go/no-go, with the numbers below. ## Decision criteria (what the verdict answers) - **Sound:** clean line-out, no audible glitches/zipper noise on the looping pattern. - **Timing:** clicks land tight (callback keeps up; no underruns logged). - **Alloc-in-callback:** does `trigger()`'s per-voice `alloc::vec!` cause dropouts under polyphony (the `poly` demo vector)? If yes → production needs fixed pre-alloc voices (true on *any* target — useful regardless of chip choice). - **Porting friction:** how much of the ~2-day estimate was real? Low friction + the codec "just working" = Daisy is a credible workstation home. High friction / ecosystem fights = confirms RP2350. ## Non-goals (explicitly out of scope for the spike) USB-MIDI, live-sync/SysEx, display/LEDs, set lists, buttons, the pro-analog chain, fixed-point/ production voice allocation, A/B firmware update. The spike proves **one thing**: the click engine sings on this chip with acceptable effort. ## Risks / unknowns - **Codec revision mismatch** — confirm which Seed rev (AK4556/WM8731/PCM3060) ships; the BSP crate must match. Verify at purchase ([verify-datasheets memory]). - **`pm-synth` float port** — mechanical but touches every voice; the `std`-feature gate keeps the host renderer green so the conformance story is unaffected. - **Heap in the audio path** — flagged above; the spike is exactly how we learn if it matters. - **Sunk-cost honesty** — even a *great* result doesn't move the metronome off RP2350 today; it only de-risks a *future* workstation pivot. Keep that framing in the verdict. ## References - [Daisy Seed product page + datasheet](https://electro-smith.com/products/daisy-seed) — STM32H750, 24-bit/96 kHz codec, FPU - Rust BSPs: [`daisy` (zlosynth)](https://github.com/zlosynth/daisy) · [`libdaisy`](https://crates.io/crates/libdaisy) · [`daisy-embassy`](https://crates.io/crates/daisy-embassy) - [libDaisy audio-callback model](https://electro-smith.github.io/libDaisy/md_doc_2md_2__a3___getting-_started-_audio.html) (interleaved/non-interleaved; the C reference for the SAI callback shape) - Reuse targets in-repo: `rust/track-format`, `rust/pm-synth`, `rust/synthrender/src/main.rs`