Skip to content

Fix/sd streaming underruns - #7

Merged
jakubthedeveloper merged 6 commits into
mainfrom
fix/sd-streaming-underruns
Sep 26, 2026
Merged

jakubthedeveloper merged 6 commits into
mainfrom
fix/sd-streaming-underruns

Conversation

@jakubthedeveloper

Copy link
Copy Markdown
Owner

No description provided.

jakubthedeveloper and others added 6 commits September 26, 2026 10:52
- Mount SD at 20 MHz (fallback 10/4 MHz) instead of the 4 MHz library
  default, and allow 16 open files instead of 5.
- Count I2S DMA underruns via on_send_q_ovf and log them, together with
  slow SD reads, on Serial once per second when they occur.
- Mixer tracks absolute write/emit positions and emits once per update
  instead of scanning all 32 inputs for every consumed sample.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A card can mount at 20 MHz on marginal wiring and then fail directory and
WAV reads, leaving an empty library and no loaded preset. Read the first
128 sectors twice and fall back to 10 MHz, then 4 MHz, on any error or
mismatch. Log each rejected clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Samples longer than 5 s always streamed from SD, and the 1 MiB budget was
persisted in sampler_config.json, so a larger default would never reach
existing setups. Streaming several long samples at the 4 MHz SD clock
underruns I2S.

- Size the RAM pool from free PSRAM (minus 512 KiB) instead of the
  config; ignore and stop writing sample_ram_budget_bytes.
- Drop the 5 s per-sample threshold; pack samples shortest first when
  they do not all fit.
- Read preloads in 32 KiB chunks, log loaded count/size/time, and allow
  60 s for the boot preparation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Busy-waiting SPI reads kept sample_loader on core 0 for longer than the
5 s task watchdog while preloading several MiB. Yield between 32 KiB
chunks. Also run the SD read check at the 4 MHz fallback (informational)
and report whether a read failed or returned mismatched data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At the 4 MHz SD clock, synchronous 4 KiB refills in the audio task
(~11 ms each) outlasted the I2S DMA buffer and stalled every voice.

- StreamManager now owns all SD access on an sd_reader task (core 0,
  priority 3) and fills a 16 KiB PSRAM ring per stream (16 streams),
  emptiest first; the audio task only copies buffered PCM.
- A voice whose stream runs dry gets silence for that update instead of
  stalling the lockstep mixer; failed opens stop the voice.
- Looped SD samples keep their file open; the reader continues into the
  next iteration, so restarts need no SD access.
- Replace ValidatedWavSource with the stream's in-memory header view and
  port its tests; add loop, truncation and capacity tests.
- Log underruns, starved updates, dropped triggers and SD throughput
  once per second from the UI task; allow 20 open files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Streamed samples started 20-30 ms late: the reader had to finish any
in-progress read, open the file and fetch the first chunk.

- The classifier reserves the first 16 KiB (~185 ms) of every assigned
  sample before packing whole samples; streamed ones keep it as a head.
- SampleRamManager loads heads into the pool; the router passes them with
  StreamPath triggers.
- SdStream serves the head from RAM, then the ring; the reader starts
  after the head and skips it on each loop iteration.
- Tests: head served before any SD read, never re-read, and playback with
  a head (partial or whole, one-shot and loop) is identical to without.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jakubthedeveloper
jakubthedeveloper merged commit 19518f6 into main Sep 26, 2026
2 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