Skip to content

Spatializer phase 1: daisy-spatializer app + KEMAR HRIR pipeline - #68

Merged
twitzelbos merged 2 commits into
mainfrom
spatializer-app
Aug 16, 2026
Merged

Spatializer phase 1: daisy-spatializer app + KEMAR HRIR pipeline#68
twitzelbos merged 2 commits into
mainfrom
spatializer-app

Conversation

@twitzelbos

Copy link
Copy Markdown
Owner

Summary

Phase 1 of the binaural spatializer (deliverable 3): a mono guitar patched to the left input is placed behind and to the left on headphones. Builds on the DSP foundation in #67 (this PR is based on that branch; rebase to main once #67 merges).

The measured HRIR — not a pan/delay — is what images behind: the interaural time/level and pinna cues live in the data. That's why this needs a real dataset (MIT KEMAR).

tools/hrir-gen — the HRIR data pipeline

uv-managed tool that fetches the MIT KEMAR compact set (Gardner & Martin, MIT Media Lab 1994; publicly redistributable), reads a direction, mirrors right-hemisphere measurements to the left by swapping ears, resamples 44.1→48 kHz (scipy resample_poly 160/147), and joint-normalizes each pair (ILD-preserving). Emits generated Rust (hrir_data.rs). It prints each exported pair's spatial cue as a sanity check:

BEHIND_LEFT: 140 taps @ 48000 Hz  ITD +19 samp (L leads if +)  ILD +11.2 dB (L louder if +)

Both cues point left → a rear-left image. The .kemar download is cached + gitignored, so a firmware build needs no network.

crates/daisy-spatializer — the XIP app

In the DMA1_STR1 audio callback: mono (left in) → SampleFifo reblock 48→64 → StereoConvolver(HRIR_L, HRIR_R) fully wet → reblock 64→48 → stereo out. DSP state (the convolver + its static scratch sized by a const twin of required_scratch, plus three reblock FIFOs) lives in statics owned by the callback, built before start() unmasks the ISR. #[pre_init] sets the MPU + L1 caches + DWT; it recovers the bootloader's CoreClocks (an XIP app can't freeze()) and brings up the codec via daisy-audio (AK4556/WM8731/PCM3060 auto-detect, or seed3 = TAC5242).

Renode

spatializer_xip.robot boots the renode_test build from QSPI XIP and checks the PC7 heartbeat — Reset + pre_init + main (incl. building the ~10 KB convolver into static scratch) run from FLASH. Guards the __ebss/startup-lockup class for this app. Green.

Verification

  • clippy -D warnings on all three feature combos (codec default / seed3 / renode_test) + cargo fmt --check clean.
  • spatializer_xip.robot green (PC7 samples: [0,1,0,1,0,0,1,0]).
  • HRIR data cue verified: +19-sample ITD (L leads), +11.2 dB ILD (L louder).

Status / next

Actual on-headphones imaging is HARDWARE-ONLY (needs a real codec + the clock hand-off), pending board time. Then phases 2–5: the room (externalization via distance/air-absorption/reverb), a second source, live positioning from the Hothouse knobs (HRIR interpolation), and the BRIR/head-tracking stretch.

🤖 Generated with Claude Code

twitzelbos and others added 2 commits August 16, 2026 16:16
…o OUT)

Second slice of the spatializer (deliverable 3), on the DSP foundation from the
previous PR. Places a mono guitar behind-and-left on headphones.

- tools/hrir-gen — uv-managed tool that fetches the MIT KEMAR compact HRIR set
  (Gardner & Martin, MIT Media Lab 1994; publicly redistributable), reads a
  direction, mirrors right-hemisphere measurements to the left by swapping ears,
  resamples 44.1→48 kHz (scipy resample_poly 160/147), and joint-normalizes each
  pair (ILD-preserving). Emits a generated Rust source. It reports the spatial
  cue of each exported pair as a sanity check; the baked "behind-left" pair reads
  ITD +19 samples (left ear leads) and ILD +11.2 dB (left louder) — both cues
  point left, exactly a rear-left image. The .kemar download is cached + gitignored
  so a firmware build needs no network.

- crates/daisy-spatializer — XIP app. In the DMA1_STR1 audio callback: mono (left
  input) → SampleFifo reblock 48→64 → StereoConvolver(HRIR_L, HRIR_R) fully wet →
  reblock 64→48 → stereo out. DSP state (convolver + its static scratch, sized by
  a const twin of required_scratch; three reblock FIFOs) lives in statics owned by
  the callback, built before start() unmasks the ISR. #[pre_init] sets the MPU +
  L1 caches + DWT and it recovers the bootloader's CoreClocks (an XIP app can't
  freeze its own), then brings up the codec via daisy-audio (AK4556/WM8731/PCM3060
  auto-detect, or seed3=TAC5242). The measured HRIR — not a pan/delay — is what
  images behind: the interaural time/level/pinna cues live in the data.

- renode/spatializer_xip.robot — boots the renode_test build from QSPI XIP and
  checks the PC7 heartbeat, proving Reset + pre_init + main (incl. building the
  ~10 KB convolver into static scratch) run from FLASH. Guards the __ebss /
  startup-lockup class for this app. Green.

Verify: clippy -D warnings on all three feature combos (codec default / seed3 /
renode_test) + fmt clean; XIP boot smoke green. Actual on-headphones imaging is
HARDWARE-ONLY (needs a real codec + the clock hand-off), pending board time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… fmt)

The generated hrir_data.rs failed `cargo fmt --check`: the tool emitted the
module-level `#![allow(...)]` as one line, but rustfmt reflows a 3-item attribute
to multi-line. Emit it pre-formatted so regenerating the data never breaks fmt.
No data change (only the attribute layout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@twitzelbos
twitzelbos changed the base branch from spatializer-dsp-foundation to main August 16, 2026 23:28
@twitzelbos
twitzelbos merged commit 130671e into main Aug 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant