SDL_mixer: mixer: Lock tracks while mixing them.

From 4aab76ed760ce521501e36b07962ba18d4a73e34 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 17 Jun 2026 19:47:21 -0400
Subject: [PATCH] mixer: Lock tracks while mixing them.

They previously locked while pulling data from them, but there is a lot more
state involved with mixing after that--including a user callback--that were
running unlocked, and could explode if the app changes various track state
during that time.

Fixes #869.
---
 src/SDL_mixer.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/SDL_mixer.c b/src/SDL_mixer.c
index a89d6cdb..31cba15a 100644
--- a/src/SDL_mixer.c
+++ b/src/SDL_mixer.c
@@ -579,6 +579,7 @@ static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int a
         int group_bytes = 0;
         MIX_Track *next_track = NULL;
         for (MIX_Track *track = group->tracks; track; track = next_track) {
+            LockTrack(track);
             next_track = track->group_next;  // this won't save you from a callback going totally rogue, but it'll deal with the current track leaving the group.
 
             track->currently_inuse = true;
@@ -616,7 +617,10 @@ static void SDLCALL MixerCallback(void *userdata, SDL_AudioStream *stream, int a
             }
 
             track->currently_inuse = false;
-            if (track->destroy_requested) {  // callback asked to destroy the track while we were still using it.
+            const bool destroy_requested = track->destroy_requested;  // save this off just in case, but if the callback destroyed the track, _nothing_ else should touch it once this unlocks.
+            UnlockTrack(track);
+
+            if (destroy_requested) {  // callback asked to destroy the track while we were still using it.
                 MIX_DestroyTrack(track);  // actually kill it now.
             }
         }