# NEUROPOD MIDI-fed dual-path neuro bass synthesizer for the Electrosmith Daisy Pod (Rev5, Seed / STM32H750), with a host-side measurement harness that drives it over MIDI, records its analog output, and asserts numeric pass criteria. - **Contract**: [docs/NEUROPOD_SPEC_v1.2.md](docs/NEUROPOD_SPEC_v1.2.md) (v1.1 + the T0-audit amendments, all changelogged) - **Hardware audit**: [docs/HW_AUDIT.md](docs/HW_AUDIT.md) - **Rig & calibration**: [docs/CAL.md](docs/CAL.md) ## Rig ``` Host (macOS) ── USB ──► EP-136 K.O. Sidekick (multi mode, cue enabled) │ │ ch1 in L/R ◄── Pod OUT L (SUB) / R (GROWL) │ │ cue out ────► Pod IN L/R (EXT stimulus) │ └ USB captures: 0/1 master, 2/3 main bus (Pod), │ 4/5 cue echo (digital stimulus reference) └── USB ─────────────► Pod (Seed port): DFU at boot, class-compliant USB-MIDI + SysEx telemetry at runtime microSD (FAT32/MBR) in the Pod: /foley/*.wav -> 32 SDRAM slots at boot ``` Channel map lives in `host/nphost.toml` — never in test code. ## Build & flash (firmware) ```bash cd NEUROPOD/firmware make # app -> QSPI image (Daisy bootloader, APP_TYPE=BOOT_QSPI) make program-boot # ONE-TIME: install the Daisy bootloader (device in DFU) make program-dfu # flash the app (device in bootloader DFU) ``` Hands-free reflash: dev builds jump to the bootloader on **CC 118 = 127** (infinite DFU window), so `make program-dfu` needs no button presses. `make NP_RELEASE=1` strips telemetry + dev CCs (115/116/118). Native DSP unit tests (no hardware): `make -C firmware/test`. ## Harness ```bash cd NEUROPOD/host python3 -m venv .venv && .venv/bin/pip install -e ".[dev]" .venv/bin/nphost devices # sanity: EP-136 + "Daisy Seed Built In" caffeinate -i .venv/bin/nphost cal # rig calibration (cached in .cal/) ``` Run one stage: ```bash caffeinate -i .venv/bin/pytest tests/test_t07_mod.py -v ``` Full hardware suite (~8 min): `caffeinate -i .venv/bin/pytest tests/ -q`. Headless (CI) tests — analysis self-tests, telemetry codec, report logic, FatFS-in-callback source scan — run without the rig and are never skipped. ### Reports & goldens Every hardware test writes `reports//.json` (measurements, tolerances, verdict). Golden references are committed under `golden/`: ```bash .venv/bin/nphost report --last # table of the most recent run .venv/bin/nphost report --diff # compare against golden/ .venv/bin/nphost report --bless # promote the last run to golden/ (explicit) ``` ## Authorized gate derates (restore when the rig improves) | Gate | Spec | Current | Restore condition | |---|---|---|---| | T4 alias floor (hardware) | −60 dB | −45 dB HW (+ rig-health < −55 dBFS); −60 dB enforced natively | break the USB ground loop (isolated hub / balanced lines) | | T9 SD load time | < 5 s | < 20 s | modern Class-10/UHS card (loader measured optimal at 252 KB DMA bursts) | ## Web editor `webapp/index.html` — single-file Web MIDI editor (Chrome/Edge). Open it directly (`file://` works) or serve it: ```bash cd NEUROPOD/webapp && python3 -m http.server 8123 ``` Grant the MIDI + SysEx permission when prompted; it auto-connects to "Daisy Seed Built In". Features: full CC map with real-unit readouts (Hz/ct/ms, firmware mapping formulas mirrored), packed-CC decomposition (shapers, texture flags, slot rate/div), live SysEx telemetry (CPU, clock, grains, active preset, motion state, scene RMS trims), device slots (click = recall, **alt-click = write editor state into the slot** via PC + full push + CC 119), local preset library with JSON export/import, init patch, factory restore, and a play keyboard (A–L keys, Z/X octave). Motion engine surface (spec changelog #15): XY morph pad (CC 2/3), scene select + alt-click capture, dice bar (tone/family/motion/seq/nudge), spice/chaos/gesture/morph-time, bank select + motion-store save/revert/factory-restore/reset, a full sequencer editor (transport, pattern config, step grid with per-step scene/note/glide/gate/morph/ curve/cond/gesture over SysEx, user p-lock writer, seeds), freeze panel (mix/repitch/playback mode/slices/arm/undo/clear) and frequency-shifter sliders. The step grid is a **write-only mirror** — dice rolls and pattern loads change the device without updating the view. ## FX section (spec changelog #9) Growl-path-only post chain (sub stays clean): CC 93 growl level; two serial FX slots — CC 94/98 type (OFF/DRIVE/CHORUS/REVERB), 95/99 p1, 96/100 p2, 97/101 mix. Texture voicing: CC 103 pre-injection high-pass (off/20 Hz–2 kHz), CC 104 bake gain (0..+12 dB into the band shapers). CPU valve order: shaper OS 4x->2x first (2x alias floor −97.7 dB native), then grains 16->8. **NOLOAD-section rule (hard-won):** `.dtcmram_bss` and `DSY_SDRAM_BSS` objects are never zeroed by startup — C++ member initializers on statics placed there silently don't happen. Anything in those sections must be fully initialized by explicit code before the audio callback starts (see `FxSlot::Init` / `Freeverb::Attach`). ## Motion engine (spec changelog #15) Scene-based motion layer per the Motion Engine Spec v1.1 delta ([docs/MOTION_SPEC_v1.1.md](docs/MOTION_SPEC_v1.1.md); reconciliation in spec changelog #15): capture scenes A–D (explore mode drunk-walk + encoder capture, RMS-normalized), morph on a vector plane (CC 2/3) with per-param interpolation domains, on-device 64-step sequencer (scenes, standalone notes, gates/glides, trig conditions, Elektron-style signed p-lock deltas), 4-level seeded dice (tone/family/motion/sequence + nudge), punch-in recording into user locks with slide playback, bar-quantized freeze/resample generations, Bode frequency shifter, gesture bus with velocity mapping. - **Surface**: hold encoder >1.5 s to toggle motion UI ↔ legacy pages. Motion CCs: 2 morph X, 3 macro Y/motion, 4 scene sel, 5 morph time, 6 spice, 8 chaos, 10 dice (value=level), 11 gesture, 12/13 freeze mix/repitch, 14/15 shifter Hz/mix. Everything deeper is SysEx `F0 7D 4E 50 …` (`host/nphost/motion.py` has builders) — NRPN was impossible (CCs 6/38/98/99 taken). - **Persistence**: 16 banks + 16 patterns at QSPI 0x740000; patterns store seeds + user locks, the generative layer re-derives on load. - **Tests**: `make -C firmware/test` (dice stats, seed determinism, lock exclusion, SSB rejection, freeze timing, persistence round-trip); `tests/test_t17_motion.py` for the rig gates (morph safety, loudness invariant, pitch coherence, punch/freeze round-trips). ## Known manual checks - Encoder click with MIDI active (PB6 share) — verified via telemetry `enc`. - Knob pages / pickup / preset menu — physical (spec §8); knobs write only on motion, MIDI and knobs share `ApplyCC`. - Boot without SD card hangs in the SD probe (T9 known issue; time-boxed probe is future work). Boot WITH card verified. - Preset save (`CC 119` / Button-2 hold in menu) stalls audio briefly (QSPI erase under XIP) — recall is glitch-gated, save is not.