From 0531ad8a09cc38fdeb1d268e701d5ffbe7f2b73b Mon Sep 17 00:00:00 2001 From: chrg6 Date: Sun, 8 Nov 2026 09:12:38 -0500 Subject: [PATCH] Do not mix a menu sound past its end when it restarts during the mix. menu_sound_play restarts a voice in place for a repeated cursor tick. When that happened between the mixer's loads of the voice's position and its sound, the old position was used with the new, shorter sound: the frame count wrapped and the mixer read past the sound's samples. The mixer now checks that the position is still the voice's and inside the sound. Co-Authored-By: Claude Opus 5.5 --- runtime/menu_sound.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/menu_sound.c b/runtime/menu_sound.c index 444c4a2f..5a0f505c 100644 --- a/runtime/menu_sound.c +++ b/runtime/menu_sound.c @@ -177,8 +177,11 @@ void menu_sound_mix(float *out, uint32_t frames, int channels, int rate) { for (int v = 0; v < VOICES; v++) { int pos = atomic_load_explicit(&voices[v].pos, memory_order_acquire); if (pos < 0) continue; - int which = atomic_load_explicit(&voices[v].sound, memory_order_relaxed); + int which = atomic_load_explicit(&voices[v].sound, memory_order_acquire); const Sound *s = &sounds[which]; + // menu_sound_play may restart the voice between the two loads: pos would then belong to the old sound, past + // the end of a shorter new one; such a voice is mixed from the next call on + if (atomic_load_explicit(&voices[v].pos, memory_order_acquire) != pos || (uint32_t)pos >= s->frames) continue; if (g < 0) g = volume(); float vg = which == MENU_SOUND_CURSOR ? g * CURSOR_GAIN : g; uint32_t n = s->frames - (uint32_t)pos < frames ? s->frames - (uint32_t)pos : frames;