foxygit / doom Log in
commit 0812f49b460239c873fbe46f16717d6aedf04070
Author:     ceski <56656010+ceski-1@users.noreply.github.com>
AuthorDate: Wed Apr 26 10:19:01 2023 -0700
Commit:     GitHub <noreply@github.com>
CommitDate: Wed Apr 26 20:19:01 2023 +0300

    win midi: Detect SysEx "part level" messages (#1595)

    * win midi: Detect SysEx "part level" messages

    * Fix formatting

    * Add comment
---
 src/i_winmusic.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 57 insertions(+), 2 deletions(-)

diff --git a/src/i_winmusic.c b/src/i_winmusic.c
index 7fe780d4..6fd4e014 100644
--- a/src/i_winmusic.c
+++ b/src/i_winmusic.c
@@ -503,6 +503,36 @@ 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.
+
+static boolean IsPartLevel(const byte *msg, int length)
+{
+    if (length == 10 &&
+        msg[0] == 0x41 && // Roland
+        msg[2] == 0x42 && // GS
+        msg[3] == 0x12 && // DT1
+        msg[4] == 0x40 && // Address MSB
+        msg[5] >= 0x10 && // Address
+        msg[5] <= 0x1F && // Address
+        msg[6] == 0x19 && // Address LSB
+        msg[9] == 0xF7)   // SysEx EOX
+    {
+        const byte checksum =
+            128 - ((int) msg[4] + msg[5] + msg[6] + msg[7]) % 128;
+
+        if (msg[8] == checksum)
+        {
+            // GS Part Level (aka Channel Volume)
+            // 41 <dev> 42 12 40 <ch> 19 <vol> <sum> F7
+            return true;
+        }
+    }
+
+    return false;
+}
+
 static boolean IsSysExReset(const byte *msg, int length)
 {
     if (length < 5)
@@ -713,8 +743,33 @@ static boolean AddToBuffer(unsigned int delta_time, midi_event_t *event,
     switch ((int)event->event_type)
     {
         case MIDI_EVENT_SYSEX:
-            SendSysExMsg(delta_time, event->data.sysex.data,
-                         event->data.sysex.length);
+            if (IsPartLevel(event->data.sysex.data, event->data.sysex.length))
+            {
+                const byte *data = event->data.sysex.data;
+                byte channel;
+
+                // Convert "block number" to a channel number.
+                if (data[5] == 0x10) // Channel 10
+                {
+                    channel = 9;
+                }
+                else if (data[5] < 0x1A) // Channels 1-9
+                {
+                    channel = (data[5] & 0x0F) - 1;
+                }
+                else // Channels 11-16
+                {
+                    channel = data[5] & 0x0F;
+                }
+
+                // Replace SysEx part level message with channel volume message.
+                SendVolumeMsg(delta_time, channel, data[7]);
+            }
+            else
+            {
+                SendSysExMsg(delta_time, event->data.sysex.data,
+                             event->data.sysex.length);
+            }
             return false;

         case MIDI_EVENT_META: