Skip to content

Report finished audio buffers at the renderer update, so a speaker no longer goes silent - #1181

Merged
hckrman101 merged 1 commit into
NotRexed:mainfrom
janvitos:fix/audio-wave-buffer-release
Nov 9, 2026
Merged

hckrman101 merged 1 commit into
NotRexed:mainfrom
janvitos:fix/audio-wave-buffer-release

Conversation

@janvitos

@janvitos janvitos commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

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, GetReleasedWaveBuffer does not return it and GetWaveBufferCount still counts it. Only host/nn_audio.c changes (a nrel_seen count 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_WB and 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

  • Machine: Linux (CachyOS), RTX 4070 SUPER, NVIDIA 620.70.06.
  • Build: a full game build with this change on 8b2e1d3.
  • Runs: repeated launches of about 40 s each, going from the title screen into gameplay on a loaded save, alternating stereo and 5.1. Each run's output was checked channel by channel.
  • Result: before the change, 3 of 8 runs lost a channel; after it, 0 of 9 did.
  • New test: tests/test_audio_wave_buffer_release.py compiles 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 current main (count(2) == 4) and passes with the change.
  • Suite: on this branch (rebased on 1f6fbdb), python -I -B tests/run_all.py gives 132 passed, 0 failed, 17 skipped on Linux.

🤖 Generated with Claude Code

…pdate, not mid-pass

Ocarina's sound thread feeds its six speaker voices one after another: it reads every voice's GetWaveBufferCount,
then for each one takes GetReleasedWaveBuffer and appends when it looks short. The mixer released buffers from its
own thread at any moment, so a buffer finishing between two of those reads made one voice look a buffer short. The
game appended it an extra one, then kept topping it up whenever its count was off, until the voice's queue reached
MAX_WB and that speaker went silent for the rest of the session. It happened in about one launch in five, in stereo
as well as 5.1 (one stereo run was left with only the centre's share in the left channel).

On the console a voice's state changes only at RequestUpdateAudioRenderer. Now a buffer the mixer finishes is
reported to the game at the next update (commit), as there: until then GetReleasedWaveBuffer does not return it and
GetWaveBufferCount still counts it.

tests/test_audio_wave_buffer_release.py compiles the actual calls, queue and mixer step around an authored six-voice
pass; it fails on the previous nn_audio.c (count 3 where the game must see 4).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@janvitos

janvitos commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review. This was merged just before I pushed 3e7ee9a, so the run_all.py entry isn't in main. It isn't urgent: main's Windows CI (fe611e4) ran test_audio_wave_buffer_release.py and it passed, so that runner has a cc. I can open a one-line follow-up PR for the ["--cc", "gcc"] entry if you'd still like it for consistency.

janvitos added a commit to janvitos/Hookshot that referenced this pull request Nov 9, 2026
…n_all.py (review of NotRexed#1181)

The test defaulted to `cc` and had no ARGS entry, so run_all.py --strict
would call `cc` on the windows-2022 job, where there normally is none.
It is now listed with ["--cc", "gcc"] next to the other audio entry and
defaults to gcc itself, so the runner skips it (or fails it under
--strict) when gcc is missing.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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