diff --git a/src/i_winmusic.c b/src/i_winmusic.c index 7890a7c7..08e65599 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_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); diff --git a/src/m_misc.c b/src/m_misc.c index a4c7fe9a..d8d4cdaf 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -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, diff --git a/src/midifile.h b/src/midifile.h index a7f1697e..a915e73f 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