winmidi: Enable SysEx messages from MIDI files (#822)

* winmidi: Enable SysEx messages from MIDI files

* Remove unused variable

* Discard incomplete SysEx messages when a song loops

* Write all 0xF0 SysEx messages, ignore all 0xF7 SysEx messages

* Remove extra delay after reset

* Remove `winmm_allow_sysex`

* Don't use compound literal
This commit is contained in:
ceski 2022-12-03 01:43:11 -08:00 committed by GitHub
parent e4a18d4aeb
commit e88086684c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 273 additions and 119 deletions

View File

@ -34,7 +34,6 @@ int winmm_reset_type = 2;
int winmm_reset_delay = 100;
int winmm_reverb_level = 40;
int winmm_chorus_level = 0;
int winmm_allow_sysex = 0;
static byte gs_reset[] = {
0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7
@ -52,8 +51,18 @@ static byte xg_system_on[] = {
0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7
};
static int timediv;
static int tempo;
static byte master_volume_msg[] = {
0xF0, 0x7F, 0x7F, 0x04, 0x01, 0x7F, 0x7F, 0xF7
};
#define DEFAULT_MASTER_VOLUME 16383
static unsigned int master_volume = DEFAULT_MASTER_VOLUME;
static int last_volume = -1;
static float volume_factor = 1.0f;
static boolean update_volume = false;
static DWORD timediv;
static DWORD tempo;
static HMIDISTRM hMidiStream;
static MIDIHDR MidiStreamHdr;
@ -88,14 +97,6 @@ typedef struct
static win_midi_song_t song;
static float volume_factor = 1.0f;
static boolean update_volume = false;
// Save the last volume for each MIDI channel.
static int channel_volume[MIDI_CHANNELS_PER_TRACK];
#define BUFFER_INITIAL_SIZE 1024
typedef struct
@ -111,7 +112,7 @@ static buffer_t buffer;
#define STREAM_MAX_EVENTS 4
#define MAKE_EVT(a, b, c, d) ((uint32_t)((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)))
#define MAKE_EVT(a, b, c, d) ((DWORD)((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)))
#define PADDED_SIZE(x) (((x) + sizeof(DWORD) - 1) & ~(sizeof(DWORD) - 1))
@ -209,19 +210,19 @@ static void StreamOut(void)
}
}
static void SendShortMsg(int type, int param1, int param2)
static void SendShortMsg(int time, int status, int channel, int param1, int param2)
{
native_event_t native_event;
native_event.dwDeltaTime = 0;
native_event.dwDeltaTime = time;
native_event.dwStreamID = 0;
native_event.dwEvent = MAKE_EVT(type, param1, param2, MEVT_SHORTMSG);
native_event.dwEvent = MAKE_EVT(status | channel, param1, param2, MEVT_SHORTMSG);
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
}
static void SendLongMsg(const byte *ptr, int length)
static void SendLongMsg(int time, const byte *ptr, int length)
{
native_event_t native_event;
native_event.dwDeltaTime = 0;
native_event.dwDeltaTime = time;
native_event.dwStreamID = 0;
native_event.dwEvent = MAKE_EVT(length, 0, 0, MEVT_LONGMSG);
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
@ -229,8 +230,10 @@ static void SendLongMsg(const byte *ptr, int length)
WriteBufferPad();
}
static void SendDelayMsg(int ticks)
static void SendDelayMsg(void)
{
// Convert ms to ticks (see "Standard MIDI Files 1.0" page 14).
int ticks = (float)winmm_reset_delay * 1000 * timediv / tempo + 0.5f;
native_event_t native_event;
native_event.dwDeltaTime = ticks;
native_event.dwStreamID = 0;
@ -238,6 +241,23 @@ static void SendDelayMsg(int ticks)
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
}
static void UpdateTempo(int time)
{
native_event_t native_event;
native_event.dwDeltaTime = time;
native_event.dwStreamID = 0;
native_event.dwEvent = MAKE_EVT(tempo, 0, 0, MEVT_TEMPO);
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
}
static void UpdateVolume(int time)
{
int volume = master_volume * volume_factor + 0.5f;
master_volume_msg[5] = volume & 0x7F;
master_volume_msg[6] = (volume >> 7) & 0x7F;
SendLongMsg(time, master_volume_msg, sizeof(master_volume_msg));
}
static void ResetDevice(void)
{
int i;
@ -245,43 +265,43 @@ static void ResetDevice(void)
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
{
// midiOutReset() sends "all notes off" and "reset all controllers."
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
// Reset pitch bend sensitivity to +/- 2 semitones and 0 cents.
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_RPN_MSB, 0);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_RPN_LSB, 0);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 2);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_RPN_MSB, 127);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_RPN_LSB, 127);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 0);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 0);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 2);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 127);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 127);
// Reset channel volume and pan.
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_MAIN_VOLUME, 100);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_PAN, 64);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_MAIN_VOLUME, 100);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_PAN, 64);
// Reset instrument.
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
SendShortMsg(MIDI_EVENT_PROGRAM_CHANGE | i, 0, 0);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
SendShortMsg(0, MIDI_EVENT_PROGRAM_CHANGE, i, 0, 0);
}
// Send SysEx reset message.
switch (winmm_reset_type)
{
case 1: // GS Reset
SendLongMsg(gs_reset, sizeof(gs_reset));
SendLongMsg(0, gs_reset, sizeof(gs_reset));
break;
case 2: // GM System On
SendLongMsg(gm_system_on, sizeof(gm_system_on));
SendLongMsg(0, gm_system_on, sizeof(gm_system_on));
break;
case 3: // GM2 System On
SendLongMsg(gm2_system_on, sizeof(gm2_system_on));
SendLongMsg(0, gm2_system_on, sizeof(gm2_system_on));
break;
case 4: // XG System On
SendLongMsg(xg_system_on, sizeof(xg_system_on));
SendLongMsg(0, xg_system_on, sizeof(xg_system_on));
break;
default: // None
@ -291,17 +311,196 @@ static void ResetDevice(void)
// Send delay after reset.
if (winmm_reset_delay > 0)
{
// Convert ms to ticks (see "Standard MIDI Files 1.0" page 14).
int ticks = (float)winmm_reset_delay * 1000 * timediv / tempo + 0.5f;
SendDelayMsg(ticks);
SendDelayMsg();
}
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
{
// Reset reverb and chorus.
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_REVERB, winmm_reverb_level);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_CHORUS, winmm_chorus_level);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_REVERB, winmm_reverb_level);
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_CHORUS, winmm_chorus_level);
}
}
static boolean IsSysExReset(const byte *msg, int length)
{
if (length < 5)
{
return false;
}
switch (msg[0])
{
case 0x41: // Roland
switch (msg[2])
{
case 0x42: // GS
switch (msg[3])
{
case 0x12: // DT1
if (length == 10 &&
msg[4] == 0x00 && // Address MSB
msg[5] == 0x00 && // Address
msg[6] == 0x7F && // Address LSB
((msg[7] == 0x00 && // Data (MODE-1)
msg[8] == 0x01) || // Checksum (MODE-1)
(msg[7] == 0x01 && // Data (MODE-2)
msg[8] == 0x00))) // Checksum (MODE-2)
{
// SC-88 System Mode Set
// 41 <dev> 42 12 00 00 7F 00 01 F7 (MODE-1)
// 41 <dev> 42 12 00 00 7F 01 00 F7 (MODE-2)
return true;
}
else if (length == 10 &&
msg[4] == 0x40 && // Address MSB
msg[5] == 0x00 && // Address
msg[6] == 0x7F && // Address LSB
msg[7] == 0x00 && // Data (GS Reset)
msg[8] == 0x41) // Checksum
{
// GS Reset
// 41 <dev> 42 12 40 00 7F 00 41 F7
return true;
}
break;
}
break;
}
break;
case 0x43: // Yamaha
switch (msg[2])
{
case 0x2B: // TG300
if (length == 9 &&
msg[3] == 0x00 && // Start Address b20 - b14
msg[4] == 0x00 && // Start Address b13 - b7
msg[5] == 0x7F && // Start Address b6 - b0
msg[6] == 0x00 && // Data
msg[7] == 0x01) // Checksum
{
// TG300 All Parameter Reset
// 43 <dev> 2B 00 00 7F 00 01 F7
return true;
}
break;
case 0x4C: // XG
if (length == 8 &&
msg[3] == 0x00 && // Address High
msg[4] == 0x00 && // Address Mid
msg[5] == 0x7E && // Address Low
msg[6] == 0x00) // Data
{
// XG System On
// 43 <dev> 4C 00 00 7E 00 F7
return true;
}
else if (length == 8 &&
msg[3] == 0x00 && // Address High
msg[4] == 0x00 && // Address Mid
msg[5] == 0x7F && // Address Low
msg[6] == 0x00) // Data
{
// XG All Parameter Reset
// 43 <dev> 4C 00 00 7F 00 F7
return true;
}
break;
}
break;
case 0x7E: // Universal Non-Real Time
switch (msg[2])
{
case 0x09: // General Midi
if (length == 5 &&
(msg[3] == 0x01 || // GM System On
msg[3] == 0x02 || // GM System Off
msg[3] == 0x03)) // GM2 System On
{
// GM System On/Off, GM2 System On
// 7E <dev> 09 01 F7
// 7E <dev> 09 02 F7
// 7E <dev> 09 03 F7
return true;
}
break;
}
break;
}
return false;
}
static boolean IsMasterVolume(const byte *msg, int length, unsigned int *volume)
{
// General Midi (7F <dev> 04 01 <lsb> <msb> F7)
if (length == 7 &&
msg[0] == 0x7F && // Universal Real Time
msg[2] == 0x04 && // Device Control
msg[3] == 0x01) // Master Volume
{
*volume = (msg[4] & 0x7F) | ((msg[5] & 0x7F) << 7);
return true;
}
// Roland (41 <dev> 42 12 40 00 04 <vol> <sum> F7)
if (length == 10 &&
msg[0] == 0x41 && // Roland
msg[2] == 0x42 && // GS
msg[3] == 0x12 && // DT1
msg[4] == 0x40 && // Address MSB
msg[5] == 0x00 && // Address
msg[6] == 0x04) // Address LSB
{
*volume = DEFAULT_MASTER_VOLUME * (msg[7] & 0x7F) / 127;
return true;
}
// Yamaha (43 <dev> 4C 00 00 04 <vol> F7)
if (length == 8 &&
msg[0] == 0x43 && // Yamaha
msg[2] == 0x4C && // XG
msg[3] == 0x00 && // Address High
msg[4] == 0x00 && // Address Mid
msg[5] == 0x04) // Address Low
{
*volume = DEFAULT_MASTER_VOLUME * (msg[6] & 0x7F) / 127;
return true;
}
return false;
}
static void SendSysExMsg(int time, const byte *data, int length)
{
native_event_t native_event;
const byte event_type = MIDI_EVENT_SYSEX;
if (IsMasterVolume(data, length, &master_volume))
{
// Found a master volume message in the MIDI file. Take this new
// value and scale it by the user's volume slider.
UpdateVolume(time);
return;
}
// Send the SysEx message.
native_event.dwDeltaTime = time;
native_event.dwStreamID = 0;
native_event.dwEvent = MAKE_EVT(length + sizeof(byte), 0, 0, MEVT_LONGMSG);
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
WriteBuffer(&event_type, sizeof(byte));
WriteBuffer(data, length);
WriteBufferPad();
if (IsSysExReset(data, length))
{
// SysEx reset also resets master volume. Take the default master
// volume and scale it by the user's volume slider.
master_volume = DEFAULT_MASTER_VOLUME;
UpdateVolume(0);
}
}
@ -316,29 +515,27 @@ static void FillBuffer(void)
{
initial_playback = false;
master_volume = DEFAULT_MASTER_VOLUME;
ResetDevice();
StreamOut();
return;
}
// If the volume has changed, stick those events at the start of this buffer.
if (update_volume)
{
update_volume = false;
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
{
int volume = (int)((float)channel_volume[i] * volume_factor + 0.5f);
SendShortMsg(MIDI_EVENT_CONTROLLER | i, MIDI_CONTROLLER_MAIN_VOLUME,
volume);
}
UpdateVolume(0);
StreamOut();
return;
}
for (num_events = 0; num_events < STREAM_MAX_EVENTS; )
{
midi_event_t *event;
DWORD data = 0;
int min_time = INT_MAX;
int idx = -1;
int delta_time;
// Look for an event with a minimal delta time.
for (i = 0; i < MIDI_NumTracks(song.file); ++i)
@ -385,6 +582,8 @@ static void FillBuffer(void)
continue;
}
delta_time = min_time - song.current_time;
switch ((int)event->event_type)
{
case MIDI_EVENT_META:
@ -392,77 +591,38 @@ static void FillBuffer(void)
{
tempo = MAKE_EVT(event->data.meta.data[2],
event->data.meta.data[1], event->data.meta.data[0], 0);
data = MAKE_EVT(tempo, 0, 0, MEVT_TEMPO);
}
break;
case MIDI_EVENT_CONTROLLER:
// Adjust volume as needed.
if (event->data.channel.param1 == MIDI_CONTROLLER_MAIN_VOLUME)
{
int volume = event->data.channel.param2;
channel_volume[event->data.channel.channel] = volume;
volume = (int)((float)volume * volume_factor + 0.5f);
data = MAKE_EVT(event->event_type | event->data.channel.channel,
event->data.channel.param1, (volume & 0x7F),
MEVT_SHORTMSG);
UpdateTempo(delta_time);
break;
}
// Fall through.
continue;
case MIDI_EVENT_CONTROLLER:
case MIDI_EVENT_NOTE_OFF:
case MIDI_EVENT_NOTE_ON:
case MIDI_EVENT_AFTERTOUCH:
case MIDI_EVENT_PITCH_BEND:
data = MAKE_EVT(event->event_type | event->data.channel.channel,
event->data.channel.param1, event->data.channel.param2,
MEVT_SHORTMSG);
SendShortMsg(delta_time, event->event_type, event->data.channel.channel,
event->data.channel.param1, event->data.channel.param2);
break;
case MIDI_EVENT_PROGRAM_CHANGE:
case MIDI_EVENT_CHAN_AFTERTOUCH:
data = MAKE_EVT(event->event_type | event->data.channel.channel,
event->data.channel.param1, 0, MEVT_SHORTMSG);
SendShortMsg(delta_time, event->event_type, event->data.channel.channel,
event->data.channel.param1, 0);
break;
case MIDI_EVENT_SYSEX:
case MIDI_EVENT_SYSEX_SPLIT:
if (winmm_allow_sysex)
{
uint32_t length = event->data.sysex.length + sizeof(byte);
data = MAKE_EVT(length, 0, 0, MEVT_LONGMSG);
}
else
{
// Preserve timing with a NOP.
data = MAKE_EVT(0, 0, 0, MEVT_NOP);
}
break;
SendSysExMsg(delta_time, event->data.sysex.data, event->data.sysex.length);
song.current_time = min_time;
StreamOut();
return;
default:
continue;
}
if (data)
{
native_event_t native_event;
native_event.dwDeltaTime = min_time - song.current_time;
native_event.dwStreamID = 0;
native_event.dwEvent = data;
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
if (winmm_allow_sysex && (event->event_type == MIDI_EVENT_SYSEX ||
event->event_type == MIDI_EVENT_SYSEX_SPLIT))
{
WriteBuffer((byte *)&event->event_type, sizeof(byte));
WriteBuffer(event->data.sysex.data, event->data.sysex.length);
WriteBufferPad();
}
num_events++;
song.current_time = min_time;
}
num_events++;
song.current_time = min_time;
}
if (num_events)
@ -518,6 +678,14 @@ static boolean I_WIN_InitMusic(void)
static void I_WIN_SetMusicVolume(int volume)
{
if (last_volume == volume)
{
// Ignore holding key down in volume menu.
return;
}
last_volume = volume;
volume_factor = sqrtf((float)volume / 15);
update_volume = true;
@ -637,12 +805,6 @@ static void *I_WIN_RegisterSong(void *data, int len)
return NULL;
}
// Initialize channels volume.
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
{
channel_volume[i] = 100;
}
prop_timediv.cbStruct = sizeof(MIDIPROPTIMEDIV);
prop_timediv.dwTimeDiv = MIDI_GetFileTimeDivision(song.file);
mmr = midiStreamProperty(hMidiStream, (LPBYTE)&prop_timediv,

View File

@ -99,7 +99,6 @@ extern int winmm_reset_type;
extern int winmm_reset_delay;
extern int winmm_reverb_level;
extern int winmm_chorus_level;
extern int winmm_allow_sysex;
#endif
extern boolean demobar;
extern boolean smoothlight;
@ -2349,13 +2348,6 @@ default_t defaults[] = {
{40}, {0, 127}, number, ss_none, wad_no,
"fine tune default reverb level for native MIDI"
},
{
"winmm_allow_sysex",
(config_t *) &winmm_allow_sysex, NULL,
{0}, {0, 1}, number, ss_none, wad_no,
"Allow SysEx messages from MIDI files for native MIDI"
},
#endif
// [FG] uncapped rendering frame rate