Skip to content

mixer: don't sync the frequency of stopped channels - #7

Open
gmcnaught wants to merge 1 commit into
n64brew:previewfrom
gmcnaught:mixer-stop-limit-test
Open

gmcnaught wants to merge 1 commit into
n64brew:previewfrom
gmcnaught:mixer-stop-limit-test

Conversation

@gmcnaught

@gmcnaught gmcnaught commented Oct 29, 2026 •

Copy link
Copy Markdown

Fixes #6.

mixer_emit_round re-applies the frequency of every channel below hi_ch at the start of each round, and mixer_ch_apply_freq checks it against the channel limit. mixer_ch_stop keeps the last frequency, so lowering the limit of a stopped channel made the next poll assert, although nothing was playing above the new limit. xm64player_close does exactly that on each of its channels (mixer_ch_stop, then mixer_ch_set_limits(ch, 0, 0, 0)).

This PR makes mixer_ch_sync_freq return early for stopped channels (!c->ptr).

  • The round already skips stopped channels when it fetches and mixes, so their step is unused.
  • mixer_ch_play sets the frequency again. A limit change that frees the channel buffer also clears wave_uuid, so the next play takes the new-waveform path, which calls mixer_ch_set_freq(ch, wave->frequency).
  • Clearing the frequency in mixer_ch_stop instead would break replaying the same waveform. That path reuses the kept uuid and never sets the frequency, so it would play at 0 Hz.

Test

tests/test_wav64.c gains test_mixer_stop_lower_limit. It plays a PCM waveform at 1.5× the output rate under a 2× limit, stops the channel, resets the limit to the default and polls. It then replays at the output rate and checks that the output is not silent. It runs last in testrom_wav64, because without the fix it stops the ROM on the assertion.

testrom_wav64.z64 in ares:

  • Before (f26e1c1): ASSERTION FAILED ... frequency 66142.5 exceeds configured limit 44095.0 on channel 2, from mixer_poll in the new test. Every earlier section ran without a failure.
  • After: ALL TESTS PASSED (407).

As a regression check, I also built a game against this branch. It plays two xm64 modules at once, plus sound effects. In ares it passed its N64 self-test and all 33 of its replays, which run with the music on. That game uses xm64player_stop and never calls xm64player_close, so it does not exercise the fixed path itself. Nothing was tested on hardware.

https://claude.ai/code/session_01JRLT3zFXKe5nUc64aTuPEo

mixer_emit_round re-applies the frequency of every channel below hi_ch at
the start of each round, and mixer_ch_apply_freq checks it against the
channel limit. mixer_ch_stop keeps the last frequency, so lowering the
limit of a stopped channel made the next poll assert, although nothing
was playing above the new limit:

  frequency 66142.5 exceeds configured limit 44095.0 on channel 2

xm64player_close does exactly this on each of its channels (stop, then
mixer_ch_set_limits back to the defaults), so closing a player that had
played a note faster than the output rate asserted a round later.

A stopped channel's step is not used by the round, and mixer_ch_play
sets the frequency again (a limit change that frees the channel buffer
also drops the waveform uuid, so play takes the new-waveform path). Skip
stopped channels in mixer_ch_sync_freq.

tests/test_wav64.c: test_mixer_stop_lower_limit reproduces it with a
plain PCM waveform.

Claude-Session: https://claude.ai/code/session_01JRLT3zFXKe5nUc64aTuPEo
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