commit 40e7e23803ef8b7e2ac0c75046771fc9589e0329
Author: ceski <56656010+ceski-1@users.noreply.github.com>
AuthorDate: Sat Nov 11 11:59:39 2023 -0800
Commit: ceski <56656010+ceski-1@users.noreply.github.com>
CommitDate: Thu Nov 16 10:00:43 2023 -0800
win midi: Improve thread synchronization
This overhauls the states and uses critical sections to protect shared data between threads. This is intended to prevent race conditions, memory use after free, and so on. To implement this properly, some additional changes were required:
- Refactored states
- Instant pause/resume (no longer delayed until next callback)
- Midi thread is created once and destroyed on shut down (no longer created/destroyed between songs)
---
src/i_winmusic.c | 397 ++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 279 insertions(+), 118 deletions(-)
diff --git a/src/i_winmusic.c b/src/i_winmusic.c
index 524b6f3e..6985b8bb 100644
--- a/src/i_winmusic.c
+++ b/src/i_winmusic.c
@@ -78,9 +78,12 @@ static boolean update_volume = false;
typedef enum
{
+ STATE_STARTUP,
+ STATE_SHUTDOWN,
+ STATE_EXIT,
+ STATE_STOPPING,
STATE_STOPPED,
STATE_PLAYING,
- STATE_PAUSING,
STATE_PAUSED
} win_midi_state_t;
@@ -93,8 +96,9 @@ static UINT MidiDevice;
static HMIDISTRM hMidiStream;
static MIDIHDR MidiStreamHdr;
static HANDLE hBufferReturnEvent;
-static HANDLE hExitEvent;
+static HANDLE hStoppedEvent;
static HANDLE hPlayerThread;
+static CRITICAL_SECTION CriticalSection;
#define EMIDI_DEVICE (1U << EMIDI_DEVICE_GENERAL_MIDI)
@@ -159,8 +163,6 @@ static buffer_t buffer;
#define PADDED_SIZE(x) (((x) + sizeof(DWORD) - 1) & ~(sizeof(DWORD) - 1))
-static boolean initial_playback = false;
-
// Check for midiStream errors.
static void MidiError(const char *prefix, DWORD dwError)
@@ -193,6 +195,12 @@ static void CALLBACK MidiStreamProc(HMIDIOUT hMidi, UINT uMsg,
}
}
+// Allocates the buffer and prepares the MIDI header. Set during initialization
+// by the main thread. BUFFER_INITIAL_SIZE should be large enough to avoid
+// reallocation by the MIDI thread during playback, due to a known memory bug
+// with midiOutUnprepareHeader() (detected by ASan). The calling thread must
+// have exclusive access to the shared resources in this function.
+
static void AllocateBuffer(const unsigned int size)
{
MIDIHDR *hdr = &MidiStreamHdr;
@@ -224,6 +232,10 @@ static void AllocateBuffer(const unsigned int size)
}
}
+// Pads the buffer with zeros so that an integral number of DWORDs are stored.
+// Required for long messages (SysEx). Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
+
static void WriteBufferPad(void)
{
unsigned int padding = PADDED_SIZE(buffer.position);
@@ -231,6 +243,9 @@ static void WriteBufferPad(void)
buffer.position = padding;
}
+// Writes message data to buffer. Call this function from the MIDI thread only,
+// with exclusive access to shared resources.
+
static void WriteBuffer(const byte *ptr, unsigned int size)
{
if (buffer.position + size >= buffer.size)
@@ -242,6 +257,9 @@ static void WriteBuffer(const byte *ptr, unsigned int size)
buffer.position += size;
}
+// Streams out the current buffer. Call this function from the MIDI thread only,
+// with exclusive access to shared resources.
+
static void StreamOut(void)
{
MIDIHDR *hdr = &MidiStreamHdr;
@@ -257,6 +275,9 @@ static void StreamOut(void)
}
}
+// Writes a short MIDI message. Call this function from the MIDI thread only,
+// with exclusive access to shared resources.
+
static void SendShortMsg(unsigned int delta_time, byte status, byte channel,
byte param1, byte param2)
{
@@ -267,6 +288,9 @@ static void SendShortMsg(unsigned int delta_time, byte status, byte channel,
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
}
+// Writes a short MIDI message (from an event). Call this function from the MIDI
+// thread only, with exclusive access to shared resources.
+
static void SendChannelMsg(unsigned int delta_time, const midi_event_t *event,
boolean use_param2)
{
@@ -275,6 +299,9 @@ static void SendChannelMsg(unsigned int delta_time, const midi_event_t *event,
use_param2 ? event->data.channel.param2 : 0);
}
+// Writes a long MIDI message (SysEx). Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
+
static void SendLongMsg(unsigned int delta_time, const byte *ptr,
unsigned int length)
{
@@ -287,6 +314,10 @@ static void SendLongMsg(unsigned int delta_time, const byte *ptr,
WriteBufferPad();
}
+// Writes an RPN message set to NULL (0x7F). Prevents accidental data entry.
+// Call this function from the MIDI thread only, with exclusive access to shared
+// resources.
+
static void SendNullRPN(unsigned int delta_time, const midi_event_t *event)
{
const byte channel = event->data.channel.channel;
@@ -296,6 +327,9 @@ static void SendNullRPN(unsigned int delta_time, const midi_event_t *event)
MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL);
}
+// Writes a NOP message (ticks). Call this function from the MIDI thread only,
+// with exclusive access to shared resources.
+
static void SendNOPMsg(unsigned int delta_time)
{
native_event_t native_event;
@@ -305,6 +339,9 @@ static void SendNOPMsg(unsigned int delta_time)
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
}
+// Writes a NOP message (milliseconds). Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
+
static void SendDelayMsg(unsigned int time_ms)
{
// Convert ms to ticks (see "Standard MIDI Files 1.0" page 14).
@@ -312,6 +349,9 @@ static void SendDelayMsg(unsigned int time_ms)
SendNOPMsg(ticks);
}
+// Writes a tempo MIDI meta message. Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
+
static void UpdateTempo(unsigned int delta_time, const midi_event_t *event)
{
native_event_t native_event;
@@ -325,6 +365,10 @@ static void UpdateTempo(unsigned int delta_time, const midi_event_t *event)
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
}
+// Writes a MIDI volume message. The value is scaled by the volume slider. Call
+// this function from the MIDI thread only, with exclusive access to shared
+// resources.
+
static void SendManualVolumeMsg(unsigned int delta_time, byte channel,
byte volume)
{
@@ -343,12 +387,20 @@ static void SendManualVolumeMsg(unsigned int delta_time, byte channel,
channel_volume[channel] = volume;
}
+// Writes a MIDI volume message (from an event). The value is scaled by the
+// volume slider. Call this function from the MIDI thread only, with exclusive
+// access to shared resources.
+
static void SendVolumeMsg(unsigned int delta_time, const midi_event_t *event)
{
SendManualVolumeMsg(delta_time, event->data.channel.channel,
event->data.channel.param2);
}
+// Sets each channel to its saved volume level, scaled by the volume slider.
+// Call this function from the MIDI thread only, with exclusive access to shared
+// resources.
+
static void UpdateVolume(void)
{
int i;
@@ -359,6 +411,10 @@ static void UpdateVolume(void)
}
}
+// Sets each channel to the default volume level, scaled by the volume slider.
+// Call this function from the MIDI thread only, with exclusive access to shared
+// resources.
+
static void ResetVolume(void)
{
int i;
@@ -369,6 +425,11 @@ static void ResetVolume(void)
}
}
+// Writes "notes off" and "sound off" messages for each channel. Some devices
+// may support only one or the other. Held notes (sustained, etc.) are released
+// to prevent hanging notes. Call this function from the MIDI thread only, with
+// exclusive access to shared resources.
+
static void SendNotesSoundOff(void)
{
int i;
@@ -380,6 +441,10 @@ static void SendNotesSoundOff(void)
}
}
+// Resets commonly used controllers. This is only for a reset type of "none" for
+// devices that don't support SysEx resets. Call this function from the MIDI
+// thread only, with exclusive access to shared resources.
+
static void ResetControllers(void)
{
int i;
@@ -397,6 +462,10 @@ static void ResetControllers(void)
}
}
+// Resets the pitch bend sensitivity for each channel. This must be sent during
+// a reset due to an MS GS Wavetable Synth bug. Call this function from the MIDI
+// thread only, with exclusive access to shared resources.
+
static void ResetPitchBendSensitivity(void)
{
int i;
@@ -417,11 +486,12 @@ static void ResetPitchBendSensitivity(void)
}
}
+// Resets the MIDI device. Call this function before each song starts and once
+// at shut down. Call this function from the MIDI thread only, with exclusive
+// access to shared resources.
+
static void ResetDevice(void)
{
- // Send notes/sound off prior to reset to prevent volume spikes.
- SendNotesSoundOff();
-
MIDI_ResetFallback();
use_fallback = false;
@@ -449,10 +519,14 @@ static void ResetDevice(void)
ResetPitchBendSensitivity();
// Reset volume (initial playback or on shutdown if no SysEx reset).
- if (initial_playback || winmm_reset_type == RESET_TYPE_NONE)
+ // Scale by slider on initial playback, max on shutdown.
+ if (win_midi_state == STATE_STARTUP)
{
- // Scale by slider on initial playback, max on shutdown.
- volume_factor = initial_playback ? volume_factor : 1.0f;
+ ResetVolume();
+ }
+ else if (winmm_reset_type == RESET_TYPE_NONE)
+ {
+ volume_factor = 1.0f;
ResetVolume();
}
@@ -466,6 +540,8 @@ static void ResetDevice(void)
// Normally, volume is controlled by channel volume messages. Roland defined a
// special SysEx message called "part level" that is equivalent to this. MS GS
// Wavetable Synth ignores these messages, but other MIDI devices support them.
+// Returns true if there is a match. Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
static boolean IsPartLevel(const byte *msg, unsigned int length)
{
@@ -493,6 +569,10 @@ static boolean IsPartLevel(const byte *msg, unsigned int length)
return false;
}
+// Checks if the current SysEx message matches any known SysEx reset message.
+// Returns true if there is a match. Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
+
static boolean IsSysExReset(const byte *msg, unsigned int length)
{
if (length < 5)
@@ -596,6 +676,9 @@ static boolean IsSysExReset(const byte *msg, unsigned int length)
return false;
}
+// Writes a MIDI SysEx message. Call this function from the MIDI thread only,
+// with exclusive access to shared resources.
+
static void SendSysExMsg(unsigned int delta_time, const midi_event_t *event)
{
native_event_t native_event;
@@ -652,6 +735,10 @@ static void SendSysExMsg(unsigned int delta_time, const midi_event_t *event)
}
}
+// Writes a MIDI program change message. If applicable, emulates capital tone
+// fallback to fix invalid instruments. Call this function from the MIDI thread
+// only, with exclusive access to shared resources.
+
static void SendProgramMsg(unsigned int delta_time, byte channel, byte program,
const midi_fallback_t *fallback)
{
@@ -675,6 +762,9 @@ static void SendProgramMsg(unsigned int delta_time, byte channel, byte program,
}
}
+// Sets a Final Fantasy or RPG Maker loop point. Call this function from the
+// MIDI thread only, with exclusive access to shared resources.
+
static void SetLoopPoint(void)
{
unsigned int i;
@@ -688,6 +778,10 @@ static void SetLoopPoint(void)
song.saved_elapsed_time = song.elapsed_time;
}
+// Checks if the MIDI meta message contains a Final Fantasy loop marker. Call
+// this function from the MIDI thread only, with exclusive access to shared
+// resources.
+
static void CheckFFLoop(const midi_event_t *event)
{
if (event->data.meta.length == sizeof(ff_loopStart) &&
@@ -703,6 +797,9 @@ static void CheckFFLoop(const midi_event_t *event)
}
}
+// Writes an EMIDI message. Call this function from the MIDI thread only, with
+// exclusive access to shared resources.
+
static void SendEMIDI(unsigned int delta_time, const midi_event_t *event,
win_midi_track_t *track, const midi_fallback_t *fallback)
{
@@ -843,6 +940,9 @@ static void SendEMIDI(unsigned int delta_time, const midi_event_t *event,
}
}
+// Writes a MIDI meta message. Call this function from the MIDI thread only,
+// with exclusive access to shared resources.
+
static void SendMetaMsg(unsigned int delta_time, const midi_event_t *event,
win_midi_track_t *track)
{
@@ -871,6 +971,9 @@ static void SendMetaMsg(unsigned int delta_time, const midi_event_t *event,
}
}
+// AddToBuffer function for vanilla (DMX MPU-401) compatibility level. Do not
+// call this function directly. See the AddToBuffer function pointer.
+
static boolean AddToBuffer_Vanilla(unsigned int delta_time,
const midi_event_t *event,
win_midi_track_t *track)
@@ -939,6 +1042,9 @@ static boolean AddToBuffer_Vanilla(unsigned int delta_time,
return true;
}
+// AddToBuffer function for standard and full MIDI compatibility levels. Do not
+// call this function directly. See the AddToBuffer function pointer.
+
static boolean AddToBuffer_Standard(unsigned int delta_time,
const midi_event_t *event,
win_midi_track_t *track)
@@ -1153,10 +1259,19 @@ static boolean AddToBuffer_Standard(unsigned int delta_time,
return true;
}
+// Function pointer determined by the desired MIDI compatibility level. Set
+// during initialization by the main thread, then called from the MIDI thread
+// only. The calling thread must have exclusive access to the shared resources
+// in this function.
+
static boolean (*AddToBuffer)(unsigned int delta_time,
const midi_event_t *event,
win_midi_track_t *track) = AddToBuffer_Vanilla;
+// Restarts a song that uses a Final Fantasy or RPG Maker loop point. Call this
+// function from the MIDI thread only, with exclusive access to shared
+// resources.
+
static void RestartLoop(void)
{
unsigned int i;
@@ -1170,6 +1285,9 @@ static void RestartLoop(void)
song.elapsed_time = song.saved_elapsed_time;
}
+// Restarts a song that uses standard looping. Call this function from the MIDI
+// thread only, with exclusive access to shared resources.
+
static void RestartTracks(void)
{
unsigned int i;
@@ -1188,6 +1306,12 @@ static void RestartTracks(void)
song.elapsed_time = 0;
}
+// The controllers "EMIDI track exclusion" and "RPG Maker loop point" share the
+// same number (CC#111) and are not compatible with each other. As a workaround,
+// allow an RPG Maker loop point only if no other EMIDI events are present. Call
+// this function from the MIDI thread only, before the song starts, with
+// exclusive access to shared resources.
+
static boolean IsRPGLoop(void)
{
unsigned int i;
@@ -1226,52 +1350,15 @@ static boolean IsRPGLoop(void)
return (num_rpg_events == 1 && num_emidi_events == 0);
}
+// Fills the output buffer with events from the current song and then streams it
+// out. Call this function from the MIDI thread only, with exclusive access to
+// shared resources.
+
static void FillBuffer(void)
{
unsigned int i;
int num_events;
- buffer.position = 0;
-
- if (initial_playback)
- {
- ResetDevice();
- StreamOut();
- song.rpg_loop = IsRPGLoop();
- initial_playback = false;
- return;
- }
-
- if (update_volume)
- {
- update_volume = false;
- UpdateVolume();
- StreamOut();
- return;
- }
-
- switch (win_midi_state)
- {
- case STATE_PLAYING:
- break;
-
- case STATE_PAUSING:
- // Send notes/sound off to prevent hanging notes.
- SendNotesSoundOff();
- StreamOut();
- win_midi_state = STATE_PAUSED;
- return;
-
- case STATE_PAUSED:
- // Send a NOP every 100 ms while paused.
- SendDelayMsg(100);
- StreamOut();
- return;
-
- case STATE_STOPPED:
- return;
- }
-
for (num_events = 0; num_events < STREAM_MAX_EVENTS; )
{
midi_event_t *event = NULL;
@@ -1353,27 +1440,113 @@ static void FillBuffer(void)
// The Windows API documentation states: "Applications should not call any
// multimedia functions from inside the callback function, as doing so can
-// cause a deadlock." We use thread to avoid possible deadlocks.
+// cause a deadlock." We use a thread to avoid possible deadlocks.
static DWORD WINAPI PlayerProc(void)
{
- HANDLE events[2] = { hBufferReturnEvent, hExitEvent };
+ boolean keep_going = true;
- while (1)
+ while (keep_going)
{
- switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
+ if (WaitForSingleObject(hBufferReturnEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ continue;
+ }
+
+ // The MIDI thread must have exclusive access to shared resources until
+ // the end of the current loop iteration or when the thread exits.
+ EnterCriticalSection(&CriticalSection);
+
+ buffer.position = 0;
+
+ switch (win_midi_state)
{
- case WAIT_OBJECT_0:
+ case STATE_STARTUP:
+ ResetDevice();
+ StreamOut();
+ song.rpg_loop = IsRPGLoop();
+ win_midi_state = STATE_PLAYING;
+ break;
+
+ case STATE_SHUTDOWN:
+ // Send notes/sound off prior to reset to prevent volume spikes.
+ SendNotesSoundOff();
+ ResetDevice();
+ StreamOut();
+ win_midi_state = STATE_EXIT;
+ break;
+
+ case STATE_EXIT:
+ keep_going = false;
+ break;
+
+ case STATE_PLAYING:
+ if (update_volume)
+ {
+ UpdateVolume();
+ StreamOut();
+ update_volume = false;
+ break;
+ }
FillBuffer();
break;
- case WAIT_OBJECT_0 + 1:
- return 0;
+ case STATE_STOPPING:
+ // Send notes/sound off to prevent hanging notes.
+ SendNotesSoundOff();
+ StreamOut();
+ win_midi_state = STATE_STOPPED;
+ break;
+
+ case STATE_STOPPED:
+ SetEvent(hStoppedEvent);
+ break;
+
+ case STATE_PAUSED:
+ break;
}
+
+ LeaveCriticalSection(&CriticalSection);
}
+
return 0;
}
+// Restarts the MIDI stream. Call this function from the main thread only, with
+// exclusive access to shared resources.
+
+static void StreamStart(void)
+{
+ MMRESULT mmr;
+
+ SetEvent(hBufferReturnEvent);
+
+ mmr = midiStreamRestart(hMidiStream);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiError("midiStreamRestart", mmr);
+ }
+}
+
+// Turns off notes but does not release all held ones (use SendNotesSoundOff()
+// to prevent hanging notes). The output buffer is returned to the callback
+// function and flagged as MHDR_DONE. Call this function from the main thread
+// only, with exclusive access to shared resources.
+
+static void StreamStop(void)
+{
+ MMRESULT mmr;
+
+ mmr = midiStreamStop(hMidiStream);
+ if (mmr != MMSYSERR_NOERROR)
+ {
+ MidiError("midiStreamStop", mmr);
+ }
+
+ ResetEvent(hBufferReturnEvent);
+ ResetEvent(hStoppedEvent);
+}
+
static boolean I_WIN_InitMusic(void)
{
const int all_devices = midiOutGetNumDevs();
@@ -1412,14 +1585,16 @@ static boolean I_WIN_InitMusic(void)
AllocateBuffer(BUFFER_INITIAL_SIZE);
hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
- hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ hStoppedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ hPlayerThread =
+ CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) PlayerProc, 0, 0, 0);
+ SetThreadPriority(hPlayerThread, THREAD_PRIORITY_TIME_CRITICAL);
+ InitializeCriticalSectionAndSpinCount(&CriticalSection, 1024);
AddToBuffer = (winmm_complevel == COMP_VANILLA) ? AddToBuffer_Vanilla
: AddToBuffer_Standard;
MIDI_InitFallback();
- win_midi_state = STATE_STOPPED;
-
return true;
}
@@ -1432,66 +1607,43 @@ static void I_WIN_SetMusicVolume(int volume)
// Ignore holding key down in volume menu.
return;
}
-
last_volume = volume;
+ EnterCriticalSection(&CriticalSection);
volume_factor = sqrtf((float)volume / 120);
-
update_volume = song.registered;
+ LeaveCriticalSection(&CriticalSection);
}
static void I_WIN_StopSong(void)
{
- MMRESULT mmr;
-
- if (!hPlayerThread)
- {
- return;
- }
-
- SetEvent(hExitEvent);
- WaitForSingleObject(hPlayerThread, PLAYER_THREAD_WAIT_TIME);
- CloseHandle(hPlayerThread);
- hPlayerThread = NULL;
- win_midi_state = STATE_STOPPED;
-
if (!hMidiStream)
{
return;
}
- mmr = midiStreamStop(hMidiStream);
- if (mmr != MMSYSERR_NOERROR)
- {
- MidiError("midiStreamStop", mmr);
- }
+ EnterCriticalSection(&CriticalSection);
+ StreamStop();
+ win_midi_state = STATE_STOPPING;
+ StreamStart();
+ LeaveCriticalSection(&CriticalSection);
+
+ WaitForSingleObject(hStoppedEvent, PLAYER_THREAD_WAIT_TIME);
+ StreamStop();
}
static void I_WIN_PlaySong(void *handle, boolean looping)
{
- MMRESULT mmr;
-
if (!hMidiStream)
{
return;
}
+ EnterCriticalSection(&CriticalSection);
song.looping = looping;
-
- hPlayerThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlayerProc,
- 0, 0, 0);
- SetThreadPriority(hPlayerThread, THREAD_PRIORITY_TIME_CRITICAL);
-
- initial_playback = true;
- win_midi_state = STATE_PLAYING;
-
- SetEvent(hBufferReturnEvent);
-
- mmr = midiStreamRestart(hMidiStream);
- if (mmr != MMSYSERR_NOERROR)
- {
- MidiError("midiStreamRestart", mmr);
- }
+ win_midi_state = STATE_STARTUP;
+ StreamStart();
+ LeaveCriticalSection(&CriticalSection);
}
static void I_WIN_PauseSong(void)
@@ -1501,7 +1653,11 @@ static void I_WIN_PauseSong(void)
return;
}
- win_midi_state = STATE_PAUSING;
+ I_WIN_StopSong();
+
+ EnterCriticalSection(&CriticalSection);
+ win_midi_state = STATE_PAUSED;
+ LeaveCriticalSection(&CriticalSection);
}
static void I_WIN_ResumeSong(void)
@@ -1511,7 +1667,13 @@ static void I_WIN_ResumeSong(void)
return;
}
- win_midi_state = STATE_PLAYING;
+ EnterCriticalSection(&CriticalSection);
+ if (win_midi_state == STATE_PAUSED)
+ {
+ win_midi_state = STATE_PLAYING;
+ StreamStart();
+ }
+ LeaveCriticalSection(&CriticalSection);
}
// Determine whether memory block is a .mid file
@@ -1620,14 +1782,16 @@ static void *I_WIN_RegisterSong(void *data, int len)
}
song.registered = true;
- ResetEvent(hBufferReturnEvent);
- ResetEvent(hExitEvent);
-
return file;
}
static void I_WIN_UnRegisterSong(void *handle)
{
+ if (!hMidiStream)
+ {
+ return;
+ }
+
if (song.tracks)
{
unsigned int i;
@@ -1662,24 +1826,20 @@ static void I_WIN_ShutdownMusic(void)
return;
}
- I_WIN_StopSong();
+ EnterCriticalSection(&CriticalSection);
+ StreamStop();
I_WIN_UnRegisterSong(NULL);
+ win_midi_state = STATE_SHUTDOWN;
+ StreamStart();
+ LeaveCriticalSection(&CriticalSection);
- // Reset device at shutdown.
- buffer.position = 0;
- ResetDevice();
- StreamOut();
- mmr = midiStreamRestart(hMidiStream);
- if (mmr != MMSYSERR_NOERROR)
+ if (WaitForSingleObject(hPlayerThread, PLAYER_THREAD_WAIT_TIME) ==
+ WAIT_OBJECT_0)
{
- MidiError("midiStreamRestart", mmr);
- }
- WaitForSingleObject(hBufferReturnEvent, PLAYER_THREAD_WAIT_TIME);
- mmr = midiStreamStop(hMidiStream);
- if (mmr != MMSYSERR_NOERROR)
- {
- MidiError("midiStreamStop", mmr);
+ CloseHandle(hPlayerThread);
+ hPlayerThread = NULL;
}
+ StreamStop();
// Don't free the buffer to avoid calling midiOutUnprepareHeader() which
// contains a memory error (detected by ASan).
@@ -1692,7 +1852,8 @@ static void I_WIN_ShutdownMusic(void)
hMidiStream = NULL;
CloseHandle(hBufferReturnEvent);
- CloseHandle(hExitEvent);
+ CloseHandle(hStoppedEvent);
+ DeleteCriticalSection(&CriticalSection);
}
static boolean I_WIN_MusicIsPlaying(void)