Skip to content

fix(audio): avoid overfilling backend audio buffer#6594

Open
banteg wants to merge 2 commits into
HarbourMasters:developfrom
banteg:fix-coreaudio-crackling
Open

fix(audio): avoid overfilling backend audio buffer#6594
banteg wants to merge 2 commits into
HarbourMasters:developfrom
banteg:fix-coreaudio-crackling

Conversation

@banteg
Copy link
Copy Markdown

@banteg banteg commented May 5, 2026

Summary

This completes the CoreAudio crackling fix from the Shipwright side.

The companion libultraship PR fixes the backend ring buffer itself:
Kenix3/libultraship#1098

This PR fixes the producer side in Shipwright: before generating another audio-engine buffer, the audio thread now checks whether the backend can accept the smallest next burst. If the backend ring is already too full, it skips that wake instead of producing PCM that the backend would have to reject or truncate.

Why

The adaptive producer loop added headroom for frame jitter by running extra audio ticks until the reservoir reaches DesiredBuffered. That solves underproduction, but it can also briefly outrun the backend consumer. On CoreAudio, overfilling the backend ring exposed audible crackles, dropouts, and repeats because already-generated PCM could be dropped at the backend boundary.

Keeping the producer from generating a chunk that cannot fit preserves audio continuity. The libultraship PR also preserves chunk boundaries defensively, but the app-side producer guard is what avoids advancing the audio engine when there is no room to enqueue the result.

Testing

  • Built locally by compiling soh/soh/OTRGlobals.cpp and relinking build-cmake/soh/soh-macos.
  • Manual CoreAudio testing: the producer guard eliminated the remaining dropout/crackle/repeat issue after the libultraship CoreAudio backend fixes.

Build Artifacts

banteg added 2 commits May 4, 2026 16:56
the audio producer is woken once per Graph_ProcessGfxCommands call and
pushes a single ~1680-sample burst sized for the current R_UPDATE_RATE.
two layered issues caused crackling on macos over hdmi:

- DesiredBuffered = 1680 was exactly one producer burst, so any video
  frame jitter emptied the device buffer between wakes -- audible as
  clicks. bump it to 4096 (~128 ms at 32 khz).

- on the file-select screen R_UPDATE_RATE is 1, which expects the
  producer to wake at ~60 hz. when the user's display rate is 20 fps
  the producer only wakes 20 times/s and structurally underproduces
  by 3x, no reservoir size can cover that. fix by running additional
  audio engine ticks within the same outer call until the reservoir
  is back at desired (capped at 6 iterations). each tick advances
  internal audio time by num_samples / sample_rate and the consumer
  drains them at the same rate, so audio still plays at normal speed.

also bumps the libultraship submodule to fix-coreaudio-crackling, which
replaces the locked ring buffer in the coreaudio backend with a
lock-free spsc ring (eliminating the priority-inversion path) and fixes
the wrap-equality + whole-chunk-drop bugs that produced clicks even on
the built-in speaker path.

verified on hdmi: underflow count over a 65 s session dropped from
1023 to 13 (only at startup), buffer level stays in the 3000-5500
frame range, never empties.
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