foxygit / doom Log in
commit c6cbb05ca638fd7d603860406eb5253d358b45e2
Author:     ceski <56656010+ceski-1@users.noreply.github.com>
AuthorDate: Fri Jun 23 14:47:15 2023 -0700
Commit:     GitHub <noreply@github.com>
CommitDate: Fri Jun 23 23:47:15 2023 +0200

    win midi: Prevent hanging notes when pausing game (#1603)

    * win midi: Prevent hanging notes when pausing game

    midiStreamPause() sends a custom notes off event and releases "hold 1 pedal" but doesn't handle other events that can cause hanging notes.

    * Use an enum instead of booleans

    * Change enum order

    * Change type and variable names

    * Use pause and stopped states

    * Use switch statement

    * Use less ambiguous function name and comments

    * Change to stopped state when actually stopped

    * Rename pause to pausing

    * Don't use default case

    * Remove cast to int
---
 src/i_winmusic.c | 73 +++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 51 insertions(+), 22 deletions(-)

diff --git a/src/i_winmusic.c b/src/i_winmusic.c
index d6acbf9b..c02aac4a 100644
--- a/src/i_winmusic.c
+++ b/src/i_winmusic.c
@@ -76,6 +76,16 @@ static byte channel_volume[MIDI_CHANNELS_PER_TRACK];
 static float volume_factor = 0.0f;
 static boolean update_volume = false;

+typedef enum
+{
+    STATE_STOPPED,
+    STATE_PLAYING,
+    STATE_PAUSING,
+    STATE_PAUSED
+} win_midi_state_t;
+
+static win_midi_state_t win_midi_state;
+
 static DWORD timediv;
 static DWORD tempo;

@@ -357,6 +367,17 @@ static void ResetVolume(void)
     }
 }

+static void SendNotesSoundOff(void)
+{
+    int i;
+
+    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
+    {
+        SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+        SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
+    }
+}
+
 static void ResetControllers(void)
 {
     int i;
@@ -396,14 +417,8 @@ static void ResetPitchBendSensitivity(void)

 static void ResetDevice(void)
 {
-    int i;
-
-    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
-    {
-        // Stop sound prior to reset to prevent volume spikes.
-        SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
-        SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
-    }
+    // Send notes/sound off prior to reset to prevent volume spikes.
+    SendNotesSoundOff();

     MIDI_ResetFallback();
     use_fallback = false;
@@ -1238,6 +1253,28 @@ static void FillBuffer(void)
         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;
@@ -1402,6 +1439,8 @@ static boolean I_WIN_InitMusic(void)
                                                     : AddToBuffer_Standard;
     MIDI_InitFallback();

+    win_midi_state = STATE_STOPPED;
+
     return true;
 }

@@ -1435,6 +1474,7 @@ static void I_WIN_StopSong(void)
     WaitForSingleObject(hPlayerThread, PLAYER_THREAD_WAIT_TIME);
     CloseHandle(hPlayerThread);
     hPlayerThread = NULL;
+    win_midi_state = STATE_STOPPED;

     if (!hMidiStream)
     {
@@ -1464,6 +1504,7 @@ static void I_WIN_PlaySong(void *handle, boolean looping)
     SetThreadPriority(hPlayerThread, THREAD_PRIORITY_TIME_CRITICAL);

     initial_playback = true;
+    win_midi_state = STATE_PLAYING;

     SetEvent(hBufferReturnEvent);

@@ -1476,34 +1517,22 @@ static void I_WIN_PlaySong(void *handle, boolean looping)

 static void I_WIN_PauseSong(void)
 {
-    MMRESULT mmr;
-
     if (!hMidiStream)
     {
         return;
     }

-    mmr = midiStreamPause(hMidiStream);
-    if (mmr != MMSYSERR_NOERROR)
-    {
-        MidiError("midiStreamPause", mmr);
-    }
+    win_midi_state = STATE_PAUSING;
 }

 static void I_WIN_ResumeSong(void)
 {
-    MMRESULT mmr;
-
     if (!hMidiStream)
     {
         return;
     }

-    mmr = midiStreamRestart(hMidiStream);
-    if (mmr != MMSYSERR_NOERROR)
-    {
-        MidiError("midiStreamRestart", mmr);
-    }
+    win_midi_state = STATE_PLAYING;
 }

 // Determine whether memory block is a .mid file