# 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<i16>`. 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`
</content>
</invoke>
