SDL_mixer: Make sure the MIDI callback isn't running while we stop the song

From 2f56d28d6846be046ff5ba14ef7968375b1ac4f7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 27 May 2022 10:51:03 -0700
Subject: [PATCH] Make sure the MIDI callback isn't running while we stop the
 song

Fixes https://github.com/libsdl-org/SDL_mixer/issues/392
---
 src/codecs/native_midi/native_midi_win32.c | 42 +++++++++++++---------
 1 file changed, 26 insertions(+), 16 deletions(-)

diff --git a/src/codecs/native_midi/native_midi_win32.c b/src/codecs/native_midi/native_midi_win32.c
index 10f0c731..6bccd6a2 100644
--- a/src/codecs/native_midi/native_midi_win32.c
+++ b/src/codecs/native_midi/native_midi_win32.c
@@ -41,6 +41,7 @@ struct _NativeMidiSong {
   Uint16 ppqn;
   int Size;
   int NewPos;
+  SDL_mutex *mutex;
 };
 
 static UINT MidiDevice=MIDI_MAPPER;
@@ -164,31 +165,37 @@ static void MIDItoStream(NativeMidiSong *song, MIDIEvent *evntlist)
 void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, DWORD_PTR dwInstance,
                         DWORD_PTR dwParam1, DWORD_PTR dwParam2 )
 {
+    NativeMidiSong *song = (NativeMidiSong *)dwInstance;
     (void)hMidi;
-    (void)dwInstance;
     (void)dwParam2;
 
+    if (!song) {
+        return;
+    }
+
+    SDL_LockMutex(song->mutex);
     switch( uMsg )
     {
     case MOM_DONE:
-      if ((currentsong->MusicLoaded) && (dwParam1 == (DWORD_PTR)&currentsong->MidiStreamHdr[currentsong->CurrentHdr]))
-        BlockOut(currentsong);
+      if ((song->MusicLoaded) && (dwParam1 == (DWORD_PTR)&song->MidiStreamHdr[song->CurrentHdr]))
+        BlockOut(song);
       break;
     case MOM_POSITIONCB:
-      if ((currentsong->MusicLoaded) && (dwParam1 == (DWORD_PTR)&currentsong->MidiStreamHdr[currentsong->CurrentHdr])) {
-        if (currentsong->Loops) {
-          if (currentsong->Loops > 0)
-            --currentsong->Loops;
-          currentsong->NewPos=0;
-          BlockOut(currentsong);
+      if ((song->MusicLoaded) && (dwParam1 == (DWORD_PTR)&song->MidiStreamHdr[song->CurrentHdr])) {
+        if (song->Loops) {
+          if (song->Loops > 0)
+            --song->Loops;
+          song->NewPos=0;
+          BlockOut(song);
         } else {
-          currentsong->MusicPlaying=0;
+          song->MusicPlaying=0;
         }
       }
       break;
     default:
       break;
     }
+    SDL_UnlockMutex(song->mutex);
 }
 
 int native_midi_detect(void)
@@ -226,6 +233,8 @@ NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *src, int freesrc)
 
     FreeMIDIEventList(evntlist);
 
+    newsong->mutex = SDL_CreateMutex();
+
     if (freesrc) {
         SDL_RWclose(src);
     }
@@ -234,15 +243,11 @@ NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *src, int freesrc)
 
 void native_midi_freesong(NativeMidiSong *song)
 {
-  if (hMidiStream)
-  {
-    midiStreamStop(hMidiStream);
-    midiStreamClose(hMidiStream);
-  }
   if (song)
   {
     if (song->NewEvents)
       SDL_free(song->NewEvents);
+    SDL_DestroyMutex(song->mutex);
     SDL_free(song);
   }
 }
@@ -255,7 +260,7 @@ void native_midi_start(NativeMidiSong *song, int loops)
   native_midi_stop();
   if (!hMidiStream)
   {
-    merr=midiStreamOpen(&hMidiStream,&MidiDevice,(DWORD)1,(DWORD_PTR)MidiProc,(DWORD_PTR)0,CALLBACK_FUNCTION);
+    merr=midiStreamOpen(&hMidiStream,&MidiDevice,(DWORD)1,(DWORD_PTR)MidiProc,(DWORD_PTR)song,CALLBACK_FUNCTION);
     if (merr!=MMSYSERR_NOERROR)
     {
       hMidiStream = NULL; /* should I do midiStreamClose(hMidiStream) before? */
@@ -290,12 +295,17 @@ void native_midi_resume(void)
 
 void native_midi_stop(void)
 {
+  NativeMidiSong *song = currentsong;
+
   if (!hMidiStream)
     return;
+
+  SDL_LockMutex(song->mutex);
   midiStreamStop(hMidiStream);
   midiStreamClose(hMidiStream);
   currentsong=NULL;
   hMidiStream = NULL;
+  SDL_UnlockMutex(song->mutex);
 }
 
 int native_midi_active(void)