Add MIDI compatibility levels

winmm_complevel:
0: Vanilla (Emulates DMX MPU-401 mode)
1: Standard (Emulates MS GS Synth) (Default)
2: Full (Send everything to device, including SysEx)
This commit is contained in:
ceski 2023-05-13 02:25:27 -07:00
parent ba358c81a0
commit ca8dc6c3e1
3 changed files with 238 additions and 20 deletions

View File

@ -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_device = "";
int winmm_complevel = COMP_STANDARD;
int winmm_reset_type = RESET_TYPE_GM;
int winmm_reset_delay = 0;
@ -263,6 +271,14 @@ static void SendLongMsg(unsigned int delta_time, const byte *ptr,
WriteBufferPad();
}
static void SendNullRPN(unsigned int delta_time, byte 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;
@ -375,6 +391,9 @@ static void ResetDevice(void)
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
}
MIDI_ResetFallback();
use_fallback = false;
switch (winmm_reset_type)
{
case RESET_TYPE_NONE:
@ -387,8 +406,7 @@ static void ResetDevice(void)
case RESET_TYPE_GS:
SendLongMsg(0, gs_reset, sizeof(gs_reset));
MIDI_ResetFallback();
use_fallback = true;
use_fallback = (winmm_complevel != COMP_VANILLA);
break;
case RESET_TYPE_XG:
@ -590,7 +608,7 @@ static void SendSysExMsg(unsigned int delta_time, const midi_event_t *event)
// Disable instrument fallback and give priority to MIDI file. Fallback
// assumes GS (SC-55 level) and the MIDI file could be GM, GM2, XG, or
// GS (SC-88 or higher). Preserve the composer's intent.
if (winmm_reset_type == RESET_TYPE_GS)
if (use_fallback)
{
MIDI_ResetFallback();
use_fallback = false;
@ -806,7 +824,10 @@ 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;
@ -816,20 +837,96 @@ static void SendMetaMsg(unsigned int delta_time, const midi_event_t *event,
}
}
static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
win_midi_track_t *track)
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_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:
@ -850,6 +947,23 @@ static boolean AddToBuffer(unsigned int delta_time, const midi_event_t *event,
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)
{
@ -870,6 +984,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->data.channel.channel);
}
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->data.channel.channel);
}
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->data.channel.channel);
}
break;
}
break;
case EMIDI_CONTROLLER_TRACK_DESIGNATION:
case EMIDI_CONTROLLER_TRACK_EXCLUSION:
case EMIDI_CONTROLLER_PROGRAM_CHANGE:
@ -887,14 +1060,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;
@ -911,8 +1090,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:
@ -923,6 +1120,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_Standard;
static void RestartLoop(void)
{
unsigned int i;
@ -1194,15 +1395,9 @@ static boolean I_WIN_InitMusic(int device)
hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (winmm_reset_type == RESET_TYPE_GS)
{
MIDI_InitFallback();
use_fallback = true;
}
else
{
use_fallback = false;
}
AddToBuffer = (winmm_complevel == COMP_VANILLA) ? AddToBuffer_Vanilla
: AddToBuffer_Standard;
MIDI_InitFallback();
printf("Windows MIDI Init: Using '%s'.\n", winmm_device);

View File

@ -93,6 +93,7 @@ extern int mus_gain;
#endif
#if defined(_WIN32)
extern char *winmm_device;
extern int winmm_complevel;
extern int winmm_reset_type;
extern int winmm_reset_delay;
#endif
@ -505,6 +506,13 @@ default_t defaults[] = {
"Native MIDI device"
},
{
"winmm_complevel",
(config_t *) &winmm_complevel, NULL,
{1}, {0, 2}, number, ss_none, wad_no,
"Native MIDI compatibility level (0 = Vanilla, 1 = Standard, 2 = Full)"
},
{
"winmm_reset_type",
(config_t *) &winmm_reset_type, NULL,

View File

@ -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