Skip to content

fix(ios): stop rendering audio a preset with its own sound never plays - #290

Merged
piaskowyk merged 1 commit into
mainfrom
claude/ios-skip-unused-audio-render
Sep 10, 2026
Merged

piaskowyk merged 1 commit into
mainfrom
claude/ios-skip-unused-audio-render

Conversation

@piaskowyk

@piaskowyk piaskowyk commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

What

PatternComposer.parse() finished by rendering a synthesized waveform through AudioSimulator.parsePattern, unconditionally. But play() only reaches for that buffer when the preset has no sound of its own:

if !hasSound {
  audioSimulator.play(buffer: audioBuffer)
}

So for any preset carrying real audio — every .pulsar bundle preset with an attached track — the whole render was discarded, and it ran synchronously on the main thread.

Cost

Measured on the PulsarApp fanfare preset (5187 ms, 38 discrete points): 1.81 s for 114,594 frames in a debug build. It is a per-sample loop with ~117 oscillator evaluations per frame.

That is a 1.8 s main-thread freeze on the first play of a bundle preset, and again on every scrubber seek, since seeking re-parses. Visible in the Audio Sync demo as the progress bar jumping from 0 to 2 s: the JS clock is Date.now()-based so it kept counting while the UI could not repaint.

The render is skipped entirely when AudioSimulator.playSound is false, which is the default outside DEBUG — but that default does not save a real app. playSound is also flipped by Settings.enableSound(...), and PulsarApp calls Settings.enableSound(true) on mount (app/(tabs)/index.tsx), so the render runs in release builds too. A release build compiles the loop optimised and is far quicker than the 1.81 s measured above, but the work is still wasted and still on the main thread.

Change

One line: skip the render when an audio event is present. playAudioOnly() still gets its buffer — it is only reachable from Player(audioOnly:), which parses without sound.

Tests

Pulsar-Package: 63 tests / 11 suites pass.

Independent of #291, which fixes replay silence in the same demo. The two touch parse() at opposite ends and merge cleanly in either order.

`parse()` rendered a synthesized waveform through AudioSimulator for
every pattern, but `play()` only uses that buffer when the preset has no
sound of its own. For a preset carrying real audio the render was pure
waste, and it ran on the main thread: 1.81 s for a five-second preset in
a debug build, which froze the UI on first play and on every seek.
@piaskowyk

Copy link
Copy Markdown
Member Author

Closing alongside #289 while the audio-sync investigation restarts from scratch. The measurement here stands on its own (1.81 s of discarded main-thread render for a preset whose buffer play() never reads), so this will be raised again separately once the primary bug is understood.

@piaskowyk

Copy link
Copy Markdown
Member Author

Reopening: independent of the replay bug and the evidence stands on its own — 1.81 s of main-thread render, measured, for a buffer that play() never reads when the preset has its own audio. The replay fix is now #291.

@piaskowyk piaskowyk reopened this Sep 9, 2026
@piaskowyk
piaskowyk merged commit 09a4dcc into main Sep 10, 2026
2 checks passed
@piaskowyk
piaskowyk deleted the claude/ios-skip-unused-audio-render branch September 10, 2026 07:38
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