foxygit / doom Log in
commit 48868398eb0bd02cc16ba2d28828ffafbc251876
Author:     ceski <56656010+ceski-1@users.noreply.github.com>
AuthorDate: Mon May 15 12:49:11 2023 -0700
Commit:     ceski <56656010+ceski-1@users.noreply.github.com>
CommitDate: Tue May 16 09:36:21 2023 -0700

    Add MIDI compatibility levels
---
 src/i_sound.c     |   1 +
 src/i_sound.h     |   1 +
 src/i_winmusic.c  | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 src/m_config.c    |   7 ++
 src/midifile.h    |  15 ++++
 src/setup/sound.c |   2 +
 6 files changed, 238 insertions(+), 10 deletions(-)

diff --git a/src/i_sound.c b/src/i_sound.c
index 6505fa15..a892b86a 100644
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -507,6 +507,7 @@ void I_BindSoundVariables(void)
     M_BindIntVariable("gus_ram_kb",              &gus_ram_kb);
 #ifdef _WIN32
     M_BindStringVariable("winmm_midi_device",    &winmm_midi_device);
+    M_BindIntVariable("winmm_complevel",         &winmm_complevel);
     M_BindIntVariable("winmm_reset_type",        &winmm_reset_type);
     M_BindIntVariable("winmm_reset_delay",       &winmm_reset_delay);
 #endif
diff --git a/src/i_sound.h b/src/i_sound.h
index e827d72c..7674a6b7 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -272,6 +272,7 @@ extern char *music_pack_path;
 extern char *timidity_cfg_path;
 #ifdef _WIN32
 extern char *winmm_midi_device;
+extern int winmm_complevel;
 extern int winmm_reset_type;
 extern int winmm_reset_delay;
 #endif
diff --git a/src/i_winmusic.c b/src/i_winmusic.c
index c60ba430..446bf538 100644
--- a/src/i_winmusic.c
+++ b/src/i_winmusic.c
@@ -34,6 +34,13 @@
 #include "midifile.h"
 #include "midifallback.h"

+enum
+{
+    COMP_VANILLA,
+    COMP_STANDARD,
+    COMP_FULL,
+};
+
 enum
 {
     RESET_TYPE_NONE,
@@ -43,6 +50,7 @@ enum
 };

 char *winmm_midi_device = NULL;
+int winmm_complevel = COMP_VANILLA;
 int winmm_reset_type = RESET_TYPE_GM;
 int winmm_reset_delay = 0;

@@ -260,6 +268,15 @@ static void SendLongMsg(unsigned int delta_time, const byte *ptr,
     WriteBufferPad();
 }

+static void SendNullRPN(unsigned int delta_time, const midi_event_t *event)
+{
+    const byte channel = event->data.channel.channel;
+    SendShortMsg(delta_time, MIDI_EVENT_CONTROLLER, channel,
+                 MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL);
+    SendShortMsg(0, MIDI_EVENT_CONTROLLER, channel,
+                 MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL);
+}
+
 static void SendNOPMsg(unsigned int delta_time)
 {
     native_event_t native_event;
@@ -383,7 +400,7 @@ static void ResetDevice(void)

         case RESET_TYPE_GS:
             SendLongMsg(0, gs_reset, sizeof(gs_reset));
-            use_fallback = true;
+            use_fallback = (winmm_complevel != COMP_VANILLA);
             break;

         case RESET_TYPE_XG:
@@ -812,30 +829,109 @@ static void SendMetaMsg(unsigned int delta_time, const midi_event_t *event,
             break;

         case MIDI_META_MARKER:
-            CheckFFLoop(event);
+            if (winmm_complevel != COMP_VANILLA)
+            {
+                CheckFFLoop(event);
+            }
+            SendNOPMsg(delta_time);
+            break;
+
+        default:
+            SendNOPMsg(delta_time);
+            break;
+    }
+}
+
+static boolean AddToBuffer_Vanilla(unsigned int delta_time,
+                                   const midi_event_t *event,
+                                   win_midi_track_t *track)
+{
+    switch ((int) event->event_type)
+    {
+        case MIDI_EVENT_SYSEX:
             SendNOPMsg(delta_time);
+            return false;
+
+        case MIDI_EVENT_META:
+            SendMetaMsg(delta_time, event, track);
+            break;
+
+        case MIDI_EVENT_CONTROLLER:
+            switch (event->data.channel.param1)
+            {
+                case MIDI_CONTROLLER_BANK_SELECT_MSB:
+                case MIDI_CONTROLLER_BANK_SELECT_LSB:
+                    // DMX has broken bank select support and runs in GM mode.
+                    SendChannelMsg(delta_time, event, false);
+                    break;
+
+                case MIDI_CONTROLLER_MODULATION:
+                case MIDI_CONTROLLER_PAN:
+                case MIDI_CONTROLLER_EXPRESSION:
+                case MIDI_CONTROLLER_HOLD1_PEDAL:
+                case MIDI_CONTROLLER_SOFT_PEDAL:
+                case MIDI_CONTROLLER_REVERB:
+                case MIDI_CONTROLLER_CHORUS:
+                case MIDI_CONTROLLER_ALL_SOUND_OFF:
+                case MIDI_CONTROLLER_ALL_NOTES_OFF:
+                    SendChannelMsg(delta_time, event, true);
+                    break;
+
+                case MIDI_CONTROLLER_VOLUME_MSB:
+                    SendVolumeMsg(delta_time, event);
+                    break;
+
+                case MIDI_CONTROLLER_RESET_ALL_CTRLS:
+                    // MS GS Wavetable Synth resets volume if param2 isn't zero.
+                    SendChannelMsg(delta_time, event, false);
+                    break;
+
+                default:
+                    SendNOPMsg(delta_time);
+                    break;
+            }
+            break;
+
+        case MIDI_EVENT_NOTE_OFF:
+        case MIDI_EVENT_NOTE_ON:
+        case MIDI_EVENT_PITCH_BEND:
+            SendChannelMsg(delta_time, event, true);
+            break;
+
+        case MIDI_EVENT_PROGRAM_CHANGE:
+            SendChannelMsg(delta_time, event, false);
             break;

         default:
             SendNOPMsg(delta_time);
             break;
     }
+
+    return true;
 }

-static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
-                           win_midi_track_t *track)
+static boolean AddToBuffer_Standard(unsigned int delta_time,
+                                    const midi_event_t *event,
+                                    win_midi_track_t *track)
 {
     midi_fallback_t fallback = {FALLBACK_NONE, 0};

     if (use_fallback)
     {
-        MIDI_CheckFallback(event, &fallback, true);
+        MIDI_CheckFallback(event, &fallback, winmm_complevel == COMP_FULL);
     }

     switch ((int) event->event_type)
     {
         case MIDI_EVENT_SYSEX:
-            SendSysExMsg(delta_time, event);
+            if (winmm_complevel == COMP_FULL)
+            {
+                SendSysExMsg(delta_time, event);
+            }
+            else
+            {
+                SendNOPMsg(delta_time);
+            }
             return false;

         case MIDI_EVENT_META:
@@ -851,11 +947,28 @@ static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
         return true;
     }

-    switch ((int)event->event_type)
+    switch ((int) event->event_type)
     {
         case MIDI_EVENT_CONTROLLER:
             switch (event->data.channel.param1)
             {
+                case MIDI_CONTROLLER_BANK_SELECT_MSB:
+                case MIDI_CONTROLLER_MODULATION:
+                case MIDI_CONTROLLER_DATA_ENTRY_MSB:
+                case MIDI_CONTROLLER_PAN:
+                case MIDI_CONTROLLER_EXPRESSION:
+                case MIDI_CONTROLLER_DATA_ENTRY_LSB:
+                case MIDI_CONTROLLER_HOLD1_PEDAL:
+                case MIDI_CONTROLLER_SOFT_PEDAL:
+                case MIDI_CONTROLLER_REVERB:
+                case MIDI_CONTROLLER_CHORUS:
+                case MIDI_CONTROLLER_ALL_SOUND_OFF:
+                case MIDI_CONTROLLER_ALL_NOTES_OFF:
+                case MIDI_CONTROLLER_POLY_MODE_OFF:
+                case MIDI_CONTROLLER_POLY_MODE_ON:
+                    SendChannelMsg(delta_time, event, true);
+                    break;
+
                 case MIDI_CONTROLLER_VOLUME_MSB:
                     if (track->emidi_volume)
                     {
@@ -876,6 +989,65 @@ static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
                                    fallback.type != FALLBACK_BANK_LSB);
                     break;

+                case MIDI_CONTROLLER_NRPN_LSB:
+                case MIDI_CONTROLLER_NRPN_MSB:
+                    if (winmm_complevel == COMP_FULL)
+                    {
+                        SendChannelMsg(delta_time, event, true);
+                    }
+                    else
+                    {
+                        // MS GS Wavetable Synth nulls RPN for any NRPN.
+                        SendNullRPN(delta_time, event);
+                    }
+                    break;
+
+                case MIDI_CONTROLLER_RPN_LSB:
+                    switch (event->data.channel.param2)
+                    {
+                        case MIDI_RPN_PITCH_BEND_SENS_LSB:
+                        case MIDI_RPN_FINE_TUNING_LSB:
+                        case MIDI_RPN_COARSE_TUNING_LSB:
+                        case MIDI_RPN_NULL:
+                            SendChannelMsg(delta_time, event, true);
+                            break;
+
+                        default:
+                            if (winmm_complevel == COMP_FULL)
+                            {
+                                SendChannelMsg(delta_time, event, true);
+                            }
+                            else
+                            {
+                                // MS GS Wavetable Synth ignores other RPNs.
+                                SendNullRPN(delta_time, event);
+                            }
+                            break;
+                    }
+                    break;
+
+                case MIDI_CONTROLLER_RPN_MSB:
+                    switch (event->data.channel.param2)
+                    {
+                        case MIDI_RPN_MSB:
+                        case MIDI_RPN_NULL:
+                            SendChannelMsg(delta_time, event, true);
+                            break;
+
+                        default:
+                            if (winmm_complevel == COMP_FULL)
+                            {
+                                SendChannelMsg(delta_time, event, true);
+                            }
+                            else
+                            {
+                                // MS GS Wavetable Synth ignores other RPNs.
+                                SendNullRPN(delta_time, event);
+                            }
+                            break;
+                    }
+                    break;
+
                 case EMIDI_CONTROLLER_TRACK_DESIGNATION:
                 case EMIDI_CONTROLLER_TRACK_EXCLUSION:
                 case EMIDI_CONTROLLER_PROGRAM_CHANGE:
@@ -893,14 +1065,20 @@ static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
                     break;

                 default:
-                    SendChannelMsg(delta_time, event, true);
+                    if (winmm_complevel == COMP_FULL)
+                    {
+                        SendChannelMsg(delta_time, event, true);
+                    }
+                    else
+                    {
+                        SendNOPMsg(delta_time);
+                    }
                     break;
             }
             break;

         case MIDI_EVENT_NOTE_OFF:
         case MIDI_EVENT_NOTE_ON:
-        case MIDI_EVENT_AFTERTOUCH:
         case MIDI_EVENT_PITCH_BEND:
             SendChannelMsg(delta_time, event, true);
             break;
@@ -917,8 +1095,26 @@ static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
             }
             break;

+        case MIDI_EVENT_AFTERTOUCH:
+            if (winmm_complevel == COMP_FULL)
+            {
+                SendChannelMsg(delta_time, event, true);
+            }
+            else
+            {
+                SendNOPMsg(delta_time);
+            }
+            break;
+
         case MIDI_EVENT_CHAN_AFTERTOUCH:
-            SendChannelMsg(delta_time, event, false);
+            if (winmm_complevel == COMP_FULL)
+            {
+                SendChannelMsg(delta_time, event, false);
+            }
+            else
+            {
+                SendNOPMsg(delta_time);
+            }
             break;

         default:
@@ -929,6 +1125,10 @@ static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
     return true;
 }

+static boolean (*AddToBuffer)(unsigned int delta_time,
+                              const midi_event_t *event,
+                              win_midi_track_t *track) = AddToBuffer_Vanilla;
+
 static void RestartLoop(void)
 {
     unsigned int i;
@@ -1181,6 +1381,8 @@ static boolean I_WIN_InitMusic(void)
     hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
     hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

+    AddToBuffer = (winmm_complevel == COMP_VANILLA) ? AddToBuffer_Vanilla
+                                                    : AddToBuffer_Standard;
     MIDI_InitFallback();

     return true;
diff --git a/src/m_config.c b/src/m_config.c
index 6a07b8eb..79dcfe88 100644
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -1058,6 +1058,13 @@ static default_t extra_defaults_list[] =

     CONFIG_VARIABLE_STRING(winmm_midi_device),

+    //!
+    // Compatibility level for native Windows MIDI, default 0. Valid values are
+    // 0 (Vanilla), 1 (Standard), 2 (Full).
+    //
+
+    CONFIG_VARIABLE_INT(winmm_complevel),
+
     //!
     // Reset device type for native Windows MIDI, default 1. Valid values are
     // 0 (None), 1 (GM Mode), 2 (GS Mode), 3 (XG Mode).
diff --git a/src/midifile.h b/src/midifile.h
index 3d06cc28..035d6f8a 100644
--- a/src/midifile.h
+++ b/src/midifile.h
@@ -23,6 +23,12 @@ typedef struct midi_track_iter_s midi_track_iter_t;

 #define MIDI_CHANNELS_PER_TRACK 16

+#define MIDI_RPN_MSB                 0x00
+#define MIDI_RPN_PITCH_BEND_SENS_LSB 0x00
+#define MIDI_RPN_FINE_TUNING_LSB     0x01
+#define MIDI_RPN_COARSE_TUNING_LSB   0x02
+#define MIDI_RPN_NULL                0x7F
+
 typedef enum
 {
     MIDI_EVENT_NOTE_OFF        = 0x80,
@@ -48,20 +54,29 @@ typedef enum
     MIDI_CONTROLLER_DATA_ENTRY_MSB  = 0x06,
     MIDI_CONTROLLER_VOLUME_MSB      = 0x07,
     MIDI_CONTROLLER_PAN             = 0x0A,
+    MIDI_CONTROLLER_EXPRESSION      = 0x0B,

     MIDI_CONTROLLER_BANK_SELECT_LSB = 0x20,
     MIDI_CONTROLLER_DATA_ENTRY_LSB  = 0x26,
     MIDI_CONTROLLER_VOLUME_LSB      = 0X27,

+    MIDI_CONTROLLER_HOLD1_PEDAL     = 0x40,
+    MIDI_CONTROLLER_SOFT_PEDAL      = 0x43,
+
     MIDI_CONTROLLER_REVERB          = 0x5B,
     MIDI_CONTROLLER_CHORUS          = 0x5D,

+    MIDI_CONTROLLER_NRPN_LSB        = 0x62,
+    MIDI_CONTROLLER_NRPN_MSB        = 0x63,
     MIDI_CONTROLLER_RPN_LSB         = 0x64,
     MIDI_CONTROLLER_RPN_MSB         = 0x65,

     MIDI_CONTROLLER_ALL_SOUND_OFF   = 0x78,
     MIDI_CONTROLLER_RESET_ALL_CTRLS = 0x79,
     MIDI_CONTROLLER_ALL_NOTES_OFF   = 0x7B,
+
+    MIDI_CONTROLLER_POLY_MODE_OFF   = 0x7E,
+    MIDI_CONTROLLER_POLY_MODE_ON    = 0x7F,
 } midi_controller_t;

 typedef enum
diff --git a/src/setup/sound.c b/src/setup/sound.c
index 82200f20..77d9c162 100644
--- a/src/setup/sound.c
+++ b/src/setup/sound.c
@@ -84,6 +84,7 @@ static char **midi_names;
 static int midi_num_devices;
 static int midi_index;
 char *winmm_midi_device = NULL;
+int winmm_complevel = 0;
 int winmm_reset_type = 1;
 int winmm_reset_delay = 0;
 #endif
@@ -357,6 +358,7 @@ void BindSoundVariables(void)
     M_BindStringVariable("timidity_cfg_path",     &timidity_cfg_path);
 #ifdef _WIN32
     M_BindStringVariable("winmm_midi_device",     &winmm_midi_device);
+    M_BindIntVariable("winmm_complevel",          &winmm_complevel);
     M_BindIntVariable("winmm_reset_type",         &winmm_reset_type);
     M_BindIntVariable("winmm_reset_delay",        &winmm_reset_delay);
 #endif