Report finished audio buffers at the renderer update, so a speaker no longer goes silent #1181
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1181
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/audio-wave-buffer-release"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
A wave buffer the mixer finishes is now reported to the game at the next
RequestUpdateAudioRenderer(commit()), as on the console, instead of the moment the mixer thread finishes it. Until that update,GetReleasedWaveBufferdoes not return it andGetWaveBufferCountstill counts it. Onlyhost/nn_audio.cchanges (anrel_seencount per voice).Why
In about one launch in five, one speaker channel went silent and stayed silent for the rest of the session. This happens in stereo too: one stereo run lost the left channel's own sound and kept only the centre's share in it.
Ocarina's sound thread feeds its six speaker voices one after another. It reads each voice's buffer count, then takes released buffers and appends to any voice that looks short. Hookshot's mixer released buffers from its own thread at any time. When a buffer finished between two of those reads, one voice looked a buffer short and got an extra append. From then on the game kept topping that voice up whenever its count was off, until its queue reached
MAX_WBand that channel died.I found this while testing #1124 (5.1 surround). It has no issue of its own that I could find.
How it was tested
8b2e1d3.tests/test_audio_wave_buffer_release.pycompiles the actual append, released, count and play-state calls, the update queue,commit()and the mixer's buffer step, then runs a six-voice pass through them. It fails on the currentmain(count(2) == 4) and passes with the change.1f6fbdb),python -I -B tests/run_all.pygives 132 passed, 0 failed, 17 skipped on Linux.🤖 Generated with Claude Code
Looks good. Reporting finished buffers at the next RequestUpdateAudioRenderer is the safe direction (late, never early, so the game can't reuse memory the mixer is still reading), and it fits what the console does.
Checked:
nrel_seenis only moved incommit(), which runs under au_lock from the update HLE and from the 50 ms no-update fallback, so nothing is lost if the game stops updating.apply(), so they are seen by that samecommit(). Looping buffers never release. Pause is untouched.MAX_RELoverflow path inrelease_wbkeepsnrel_seenconsistent, andGetReleasedWaveBufferdecrements both counters. A fresh voice is memset, sonrel_seenstarts at 0.GetWaveBufferCountnow includes the finished-but-unreported buffers, which is exactly what stops the half-updated count mid-pass.One nit: the new test isn't in
ARGSintests/run_all.py. Every other C fixture has an entry like"--cc", "gcc"there, so the runner can skip it when no compiler is around. Without one it falls back to the bareccdefault, which won't exist on the Windows runner and would fail instead of skipping. Something like"tests/test_audio_wave_buffer_release.py": ["--cc", "gcc"],next to the other audio entries. (Also worth noting CI hasn't run on this head yet, it's waiting for approval, so the test hasn't been exercised there.)Thanks for the review. This was merged just before I pushed 3e7ee9a, so the
run_all.pyentry isn't in main. It isn't urgent: main's Windows CI (fe611e4) rantest_audio_wave_buffer_release.pyand it passed, so that runner has acc. I can open a one-line follow-up PR for the["--cc", "gcc"]entry if you'd still like it for consistency.