From 710fa552887ed4dd49b5c35ae3e597b8f34e25bd Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 20 Jan 2016 18:37:05 -0600 Subject: [PATCH] Port recent SMF changes to XMI and HMI: - Handle SysEx messages instead of ignoring them. - Don't lose time for unhandled events with delays. --- src/sound/i_musicinterns.h | 10 ++--- src/sound/music_hmi_midiout.cpp | 75 +++++++++++++++++++++++++++------ src/sound/music_xmi_midiout.cpp | 73 +++++++++++++++++++++++++++----- 3 files changed, 128 insertions(+), 30 deletions(-) diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 52364ab587..ccbab55f74 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -600,9 +600,9 @@ public: protected: void Heapify(); - unsigned int Parent(unsigned int i) { return (i + 1u) / 2u - 1u; } - unsigned int Left(unsigned int i) { return (i + 1u) * 2u - 1u; } - unsigned int Right(unsigned int i) { return (i + 1u) * 2u; } + unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; } + unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; } + unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; } }; class HMISong : public MIDIStreamer @@ -630,7 +630,7 @@ protected: struct TrackInfo; void ProcessInitialMetaEvents (); - DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay); + DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom); TrackInfo *FindNextDue (); static DWORD ReadVarLenHMI(TrackInfo *); @@ -673,7 +673,7 @@ protected: void AdvanceSong(DWORD time); void ProcessInitialMetaEvents(); - DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay); + DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay, ptrdiff_t room, bool &sysex_noroom); EventSource FindNextDue(); BYTE *MusHeader; diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index ca3ae09054..1796b1372c 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -523,7 +523,12 @@ DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) // Play all events for this tick. do { - DWORD *new_events = SendCommand(events, TrackDue, time); + bool sysex_noroom = false; + DWORD *new_events = SendCommand(events, TrackDue, time, max_event_p - events, sysex_noroom); + if (sysex_noroom) + { + return events; + } TrackDue = FindNextDue(); if (new_events != events) { @@ -568,7 +573,7 @@ void HMISong::AdvanceTracks(DWORD time) // //========================================================================== -DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) +DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom) { DWORD len; BYTE event, data1 = 0, data2 = 0; @@ -584,10 +589,20 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) return events + 3; } + sysex_noroom = false; + size_t start_p = track->TrackP; + CHECK_FINISHED event = track->TrackBegin[track->TrackP++]; CHECK_FINISHED + // The actual event type will be filled in below. If it's not a NOP, + // the events pointer will be advanced once the actual event is written. + // Otherwise, we do it at the end of the function. + events[0] = delay; + events[1] = 0; + events[2] = MEVT_NOP << 24; + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe) { // Normal short message @@ -626,17 +641,10 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) data2 = VolumeControllerChange(event & 15, data2); } - events[0] = delay; - events[1] = 0; if (event != MIDI_META) { events[2] = event | (data1<<8) | (data2<<16); } - else - { - events[2] = MEVT_NOP << 24; - } - events += 3; if (ReadVarLen == ReadVarLenHMI && (event & 0x70) == (MIDI_NOTEON & 0x70)) { // HMI note on events include the time until an implied note off event. @@ -645,13 +653,41 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) } else { - // Skip SysEx events just because I don't want to bother with them. - // The old MIDI player ignored them too, so this won't break - // anything that played before. + // SysEx events could potentially not have enough room in the buffer... if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = ReadVarLen(track); - track->TrackP += len; + if (len >= (MAX_EVENTS-1)*3*4) + { // This message will never fit. Throw it away. + track->TrackP += len; + } + else if (len + 12 >= (size_t)room * 4) + { // Not enough room left in this buffer. Backup and wait for the next one. + track->TrackP = start_p; + sysex_noroom = true; + return events; + } + else + { + BYTE *msg = (BYTE *)&events[3]; + if (event == MIDI_SYSEX) + { // Need to add the SysEx marker to the message. + events[2] = (MEVT_LONGMSG << 24) | (len + 1); + *msg++ = MIDI_SYSEX; + } + else + { + events[2] = (MEVT_LONGMSG << 24) | len; + } + memcpy(msg, &track->TrackBegin[track->TrackP], len); + msg += len; + // Must pad with 0 + while ((size_t)msg & 3) + { + *msg++ = 0; + } + track->TrackP += len; + } } else if (event == MIDI_META) { @@ -677,7 +713,6 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) events[0] = delay; events[1] = 0; events[2] = (MEVT_TEMPO << 24) | Tempo; - events += 3; break; } track->TrackP += len; @@ -720,6 +755,18 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) { track->Delay = ReadVarLen(track); } + // Advance events pointer unless this is a non-delaying NOP. + if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP) + { + if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG) + { + events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2); + } + else + { + events += 3; + } + } return events; } diff --git a/src/sound/music_xmi_midiout.cpp b/src/sound/music_xmi_midiout.cpp index 71246f5184..fbbf4db533 100644 --- a/src/sound/music_xmi_midiout.cpp +++ b/src/sound/music_xmi_midiout.cpp @@ -337,7 +337,12 @@ DWORD *XMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) // Play all events for this tick. do { - DWORD *new_events = SendCommand(events, EventDue, time); + bool sysex_noroom = false; + DWORD *new_events = SendCommand(events, EventDue, time, max_event_p - events, sysex_noroom); + if (sysex_noroom) + { + return events; + } EventDue = FindNextDue(); if (new_events != events) { @@ -382,7 +387,7 @@ void XMISong::AdvanceSong(DWORD time) // //========================================================================== -DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) +DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay, ptrdiff_t room, bool &sysex_noroom) { DWORD len; BYTE event, data1 = 0, data2 = 0; @@ -399,10 +404,20 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) TrackInfo *track = CurrSong; + sysex_noroom = false; + size_t start_p = track->EventP; + CHECK_FINISHED event = track->EventChunk[track->EventP++]; CHECK_FINISHED + // The actual event type will be filled in below. If it's not a NOP, + // the events pointer will be advanced once the actual event is written. + // Otherwise, we do it at the end of the function. + events[0] = delay; + events[1] = 0; + events[2] = MEVT_NOP << 24; + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND) { // Normal short message @@ -499,10 +514,6 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) { events[2] = event | (data1<<8) | (data2<<16); } - else - { - events[2] = MEVT_NOP << 24; - } events += 3; @@ -513,13 +524,41 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) } else { - // Skip SysEx events just because I don't want to bother with them. - // The old MIDI player ignored them too, so this won't break - // anything that played before. + // SysEx events could potentially not have enough room in the buffer... if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { - len = track->ReadVarLen (); - track->EventP += len; + len = track->ReadVarLen(); + if (len >= (MAX_EVENTS-1)*3*4) + { // This message will never fit. Throw it away. + track->EventP += len; + } + else if (len + 12 >= (size_t)room * 4) + { // Not enough room left in this buffer. Backup and wait for the next one. + track->EventP = start_p; + sysex_noroom = true; + return events; + } + else + { + BYTE *msg = (BYTE *)&events[3]; + if (event == MIDI_SYSEX) + { // Need to add the SysEx marker to the message. + events[2] = (MEVT_LONGMSG << 24) | (len + 1); + *msg++ = MIDI_SYSEX; + } + else + { + events[2] = (MEVT_LONGMSG << 24) | len; + } + memcpy(msg, &track->EventChunk[track->EventP++], len); + msg += len; + // Must pad with 0 + while ((size_t)msg & 3) + { + *msg++ = 0; + } + track->EventP += len; + } } else if (event == MIDI_META) { @@ -551,6 +590,18 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) { track->Delay = track->ReadDelay(); } + // Advance events pointer unless this is a non-delaying NOP. + if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP) + { + if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG) + { + events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2); + } + else + { + events += 3; + } + } return events; }