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 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-08 09:12:38 -05:00
parent 2f7e1806a2
commit 0531ad8a09

View File

@@ -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;