Skip to content

v0.6.1: engine changes no longer stall the audio (#36) - #38

Merged
Worldwave merged 1 commit into
mainfrom
issue36-fast-clear
Oct 5, 2026
Merged

Worldwave merged 1 commit into
mainfrom
issue36-fast-clear

Conversation

@Worldwave

Copy link
Copy Markdown
Owner

Fixes #36.

The bug

Changing engine could stall the audio for up to ~4 ms inside a 2 ms block. Plaits' engines share one block of RAM, so each engine's Reset() clears its buffers on every change. GCC sends those fills to picolibc's memset, which on this toolchain is a byte loop at ~6.6 cycles per byte:

  • Particle: 16 KB diffuser, ~1.7 ms.
  • Particle, entered with MORPH below centre: twice that. The M3f diffuser-skip override clears again when diffusion resumes.
  • String: 10 KB of delay lines, ~1.1 ms.

The fix

  • firmware/src/sp1_zero.cc: plaits::sp1_zero fills a word at a time, with byte head and tail for unaligned buffers. It's built with -fno-tree-loop-distribute-patterns so GCC can't turn it back into a memset call.
  • Two one-passage overrides: FxEngine::Clear() (Particle's diffuser, the ensemble used by String machine and Chords) and DelayLine::Reset() (String). 14 call sites, the same byte counts as before.
  • Deliberately scoped to Plaits. libc's memset is not replaced: it also zeroes .bss before main(), where a fault is a boot loop on a device with no reset pin.
  • tools/ci/check_image.py: fails an image where sp1_zero calls anything, or where Particle's or String's reset still calls memset.
  • v0.6.1: firmware/VERSION, README, issue-form placeholders.

Verification

  • Both configs: 0 warnings. Plaits build 424 420 B, links at 0x20000, check_image.py all pass.
  • Host: all 8 suites pass. Renders are bit-identical to main across 30 engine changes with MORPH crossing centre, at four arena alignments. A guard-byte test of sp1_zero covers 296 alignment/length cases.
  • Hardware, 2026-10-05: the same code, built before the version bump and labelled v0.6.0.
v0.6.0 this branch
Particle, first call after a change 1984–4000 µs 457–625 µs
String, first call after a change 1559 µs 509–511 µs
Worst block, Particle/String round trips up to 248 % 80.9 %
fail / rst 0 (6.6 ms blocks failed in development) 0

The per-block output clears, which go through the same byte loop, are #37.

🤖 Generated with Claude Code

Plaits clears its shared engine RAM on every engine change, and GCC sends
those fills to picolibc's memset -- a byte loop on this toolchain, ~6.6
cycles a byte. Particle's 16 KB diffuser took ~1.7 ms (twice that when
entered with MORPH below centre, where the M3f diffuser skip clears again),
String's delay lines ~1.1 ms, inside a 2 ms audio block.

FxEngine::Clear() and DelayLine::Reset() now call plaits::sp1_zero
(firmware/src/sp1_zero.cc, word stores) through two one-passage overrides.
Scoped to Plaits on purpose: libc's memset also zeroes .bss before main(),
so it is not replaced. check_image.py fails an image where sp1_zero calls
anything or Particle's / String's reset still calls memset.

Host: all suites pass; renders bit-identical to main across 30 engine
changes at four arena alignments. Hardware (2026-10-05): Particle's first
call 457-625 us (was up to 4000), String 509 us (was 1559), worst block
80.9 %, no stream failures. Per-block output clears are #37.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Worldwave
Worldwave merged commit ff76884 into main Oct 5, 2026
3 checks passed
@Worldwave
Worldwave deleted the issue36-fast-clear branch October 5, 2026 16:06
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.

BUG: Audio dropout when switching engines around Particle, String and Modal

1 participant