MIDI: Fix MIDI parser tracker overflow

The MIDI parser tracks the number of ticks and microseconds since the start of
playback of the current track. If a track loops infinitely, the tracker
eventually overflows (after about 71 minutes). The parser would then go haywire,
sending out the MIDI events at full speed and not terminating hanging notes,
resulting in loads of polyphony overrun errors. This does not affect looping
using jumpToTick or autoLoop, because that resets the tracker.

This change fixes this for the XMIDI parser. processEvent implementations can
now set a bool flag on the event, indicating that the event has caused a loop.
The MIDI parser will then reset the ticks and microseconds on the tracker to 0,
maintaining the deltas between the previous event values and current values.
The absolute number of ticks and microseconds since track start do not seem to
be needed anywhere; only the difference with the previous event is relevant.

This should fix bugs #6275 and #4354.
This commit is contained in:
NMIError 2020-06-29 22:34:24 +02:00 committed by Eugene Sandulenko
parent 5da83d8ea0
commit da64fdc3d1
3 changed files with 22 additions and 3 deletions

View File

@ -211,6 +211,7 @@ void MidiParser::onTimer() {
}
}
bool loopEvent = false;
while (!_abortParse) {
EventInfo &info = _nextEvent;
@ -219,7 +220,6 @@ void MidiParser::onTimer() {
break;
// Process the next info.
_position._lastEventTick += info.delta;
if (info.event < 0x80) {
warning("Bad command or running status %02X", info.event);
_position._playPos = 0;
@ -241,8 +241,11 @@ void MidiParser::onTimer() {
if (!ret)
return;
loopEvent |= info.loop;
if (!_abortParse) {
_position._lastEventTime = eventTime;
_position._lastEventTick += info.delta;
parseNextEvent(_nextEvent);
}
}
@ -250,6 +253,15 @@ void MidiParser::onTimer() {
if (!_abortParse) {
_position._playTime = endTime;
_position._playTick = (_position._playTime - _position._lastEventTime) / _psecPerTick + _position._lastEventTick;
if (loopEvent) {
// One of the processed events has looped (part of) the MIDI data.
// Infinite looping will cause the tracker to overflow eventually.
// Reset the tracker positions to prevent this from happening.
_position._playTime -= _position._lastEventTime;
_position._lastEventTime = 0;
_position._playTick -= _position._lastEventTick;
_position._lastEventTick = 0;
}
}
}

View File

@ -104,9 +104,12 @@ struct EventInfo {
///< For Note On events, a non-zero value indicates that no Note Off event
///< will occur, and the MidiParser will have to generate one itself.
///< For all other events, this value should always be zero.
bool loop; ///< Indicates that this event loops (part of) the MIDI data.
byte channel() const { return event & 0x0F; } ///< Separates the MIDI channel from the event.
byte command() const { return event >> 4; } ///< Separates the command code from the event.
EventInfo() : start(0), delta(0), event(0), length(0), loop(false) { basic.param1 = 0; basic.param2 = 0; ext.type = 0; ext.data = 0; }
};
/**

View File

@ -166,6 +166,7 @@ bool MidiParser_XMIDI::jumpToIndex(uint8 index, bool stopNotes) {
void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
info.start = _position._playPos;
info.delta = readVLQ2(_position._playPos);
info.loop = false;
// Process the next event.
info.event = *(_position._playPos++);
@ -230,12 +231,15 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
} else {
// Repeat 0 means "loop forever".
if (_loop[_loopCount].repeat) {
if (--_loop[_loopCount].repeat == 0)
if (--_loop[_loopCount].repeat == 0) {
_loopCount--;
else
} else {
_position._playPos = _loop[_loopCount].pos;
info.loop = true;
}
} else {
_position._playPos = _loop[_loopCount].pos;
info.loop = true;
}
}
}