[Scummvm-git-logs] scummvm master -> debe30b25e666a1ef26a2550e6826cd1adba5810

NMIError noreply at scummvm.org
Fri Feb 7 13:11:02 UTC 2025


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
debe30b25e AUDIO: Add full support for type 1 SMF


Commit: debe30b25e666a1ef26a2550e6826cd1adba5810
    https://github.com/scummvm/scummvm/commit/debe30b25e666a1ef26a2550e6826cd1adba5810
Author: Coen Rampen (crampen at gmail.com)
Date: 2025-02-07T14:10:52+01:00

Commit Message:
AUDIO: Add full support for type 1 SMF

Type 1 SMF files consist of multiple tracks that are to be played simultaneously
in parallel. Support for this was previously implemented by merging these tracks
into one single track, then treating the result as a regular type 0 SMF file.

This commit adds full support for type 1 files. The parallel tracks are loaded
as subtracks of the parent track and played simultaneously in real time. This
allows for support of MIDI formats with parallel subtracks that do not use the
SMF format (Dark Seed floppy version) or that jump to offsets within each
parallel subtrack (Origin MIDI).

Changed paths:
    audio/midiparser.cpp
    audio/midiparser.h
    audio/midiparser_qt.cpp
    audio/midiparser_smf.cpp
    audio/midiparser_smf.h
    audio/midiparser_xmidi.cpp
    engines/agos/midiparser_gmf.cpp
    engines/agos/midiparser_s1d.cpp
    engines/agos/midiparser_simonwin.cpp
    engines/agos/midiparser_simonwin.h
    engines/dgds/sound/midiparser_sci.cpp
    engines/parallaction/sound_br.cpp
    engines/sci/sound/midiparser_sci.cpp
    engines/scumm/midiparser_ro.cpp
    engines/sherlock/music.cpp
    engines/ultima/nuvie/sound/midiparser_m.cpp


diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp
index dcac6970eef..e0dd921d81c 100644
--- a/audio/midiparser.cpp
+++ b/audio/midiparser.cpp
@@ -53,10 +53,12 @@ _doParse(true),
 _pause(false) {
 	memset(_activeNotes, 0, sizeof(_activeNotes));
 	memset(_tracks, 0, sizeof(_tracks));
-	_nextEvent.start = nullptr;
-	_nextEvent.delta = 0;
-	_nextEvent.event = 0;
-	_nextEvent.length = 0;
+	memset(_numSubtracks, 1, sizeof(_numSubtracks));
+	for (int i = 0; i < MAXIMUM_SUBTRACKS; i++) {
+		_nextSubtrackEvents[i].clear();
+		_nextSubtrackEvents[i].subtrack = i;
+	}
+	_nextEvent = &_nextSubtrackEvents[0];
 }
 
 void MidiParser::property(int prop, int value) {
@@ -190,12 +192,13 @@ void MidiParser::hangingNote(byte channel, byte note, uint32 timeLeft, bool recy
 void MidiParser::onTimer() {
 	uint32 endTime;
 	uint32 eventTime;
+	uint32 eventTick;
 
 	// The SysEx delay can be decreased whenever time passes,
 	// even if the parser does not parse events.
 	_sysExDelay -= (_sysExDelay > _timerRate) ? _timerRate : _sysExDelay;
 
-	if (!_position._playPos || !_driver || !_doParse || _pause || !_driver->isReady(_source))
+	if (!_position.isTracking() || !_driver || !_doParse || _pause || !_driver->isReady(_source))
 		return;
 
 	_abortParse = false;
@@ -221,9 +224,11 @@ void MidiParser::onTimer() {
 
 	bool loopEvent = false;
 	while (!_abortParse) {
-		EventInfo &info = _nextEvent;
+		EventInfo &info = *_nextEvent;
+		uint8 subtrack = info.subtrack;
 
-		eventTime = _position._lastEventTime + info.delta * _psecPerTick;
+		eventTick = _position._subtracks[subtrack]._lastEventTick + info.delta;
+		eventTime = _position._lastEventTime + (eventTick - _position._lastEventTick) * _psecPerTick;
 		if (eventTime > endTime)
 			break;
 
@@ -231,7 +236,7 @@ void MidiParser::onTimer() {
 			// Process the next info.
 			if (info.event < 0x80) {
 				warning("Bad command or running status %02X", info.event);
-				_position._playPos = nullptr;
+				_position.stopTracking();
 				return;
 			}
 
@@ -254,27 +259,91 @@ void MidiParser::onTimer() {
 		loopEvent |= info.loop;
 
 		if (!_abortParse) {
+			_position._playTime = eventTime;
 			_position._lastEventTime = eventTime;
-			_position._lastEventTick += info.delta;
-			parseNextEvent(_nextEvent);
+			_position._subtracks[subtrack]._lastEventTime = eventTime;
+
+			_position._playTick = eventTick;
+			_position._lastEventTick = eventTick;
+			_position._subtracks[subtrack]._lastEventTick = eventTick;
+
+			if (_position.isTracking(subtrack)) {
+				parseNextEvent(_nextSubtrackEvents[subtrack]);
+			}
+			determineNextEvent();
 		}
 	}
 
 	if (!_abortParse) {
 		_position._playTime = endTime;
-		_position._playTick = (_position._playTime - _position._lastEventTime) / _psecPerTick + _position._lastEventTick;
+		_position._playTick = (endTime - _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;
+			// Infinite looping will cause the tracker playtime to overflow
+			// eventually. Reset the tracker time and tick values to prevent
+			// this from happening.
+			rebaseTracking();
 		}
 	}
 }
 
+void MidiParser::rebaseTracking() {
+	uint32 earliestLastEventTick = 0xFFFFFFFF;
+	int earliestLastEventTickSubtrack = -1;
+	for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+		if (_position.isTracking(i) && _position._subtracks[i]._lastEventTick < earliestLastEventTick) {
+			earliestLastEventTick = _position._subtracks[i]._lastEventTick;
+			earliestLastEventTickSubtrack = i;
+		}
+	}
+	if (earliestLastEventTickSubtrack == -1)
+		// Shouldn't happen
+		return;
+	uint32 earliestLastEventTime = _position._subtracks[earliestLastEventTickSubtrack]._lastEventTime;
+
+	// Subtract the same value from all time and tick values to keep
+	// a common timebase.
+	for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+		if (_position.isTracking(i)) {
+			if (_position._subtracks[i]._lastEventTime >= earliestLastEventTime) {
+				_position._subtracks[i]._lastEventTime -= earliestLastEventTime;
+			} else {
+				// This shouldn't happen; maybe due to rounding?
+				// Just to be sure there is no underflow...
+				_position._subtracks[i]._lastEventTime = 0;
+			}
+			_position._subtracks[i]._lastEventTick -= earliestLastEventTick;
+		}
+	}
+	if (_position._playTime >= earliestLastEventTime) {
+		_position._playTime -= earliestLastEventTime;
+	} else {
+		_position._playTime = 0;
+	}
+	if (_position._lastEventTime >= earliestLastEventTime) {
+		_position._lastEventTime -= earliestLastEventTime;
+	} else {
+		_position._lastEventTime = 0;
+	}
+	_position._playTick -= earliestLastEventTick;
+	_position._lastEventTick -= earliestLastEventTick;
+}
+
+void MidiParser::determineNextEvent() {
+	uint32 lowestNextEventTick = 0xFFFFFFFF;
+	int lowestNextEventTickSubtrack = -1;
+	for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+		if (_position.isTracking(i)) {
+			uint32 subtrackNextEventTick = _position._subtracks[i]._lastEventTick + _nextSubtrackEvents[i].delta;
+			if (subtrackNextEventTick < lowestNextEventTick) {
+				lowestNextEventTick = subtrackNextEventTick;
+				lowestNextEventTickSubtrack = i;
+			}
+		}
+	}
+	_nextEvent = &_nextSubtrackEvents[lowestNextEventTickSubtrack >= 0 ? lowestNextEventTickSubtrack : 0];
+}
+
 bool MidiParser::processEvent(const EventInfo &info, bool fireEvents) {
 	if (info.event == 0xF0) {
 		// SysEx event
@@ -297,23 +366,31 @@ bool MidiParser::processEvent(const EventInfo &info, bool fireEvents) {
 		}
 	} else if (info.event == 0xFF) {
 		// META event
+		bool sendEventToDriver = true;
 		if (info.ext.type == 0x2F) {
 			// End of Track must be processed by us,
 			// as well as sending it to the output device.
-			if (_autoLoop) {
-				jumpToTick(0);
-			} else {
-				stopPlaying();
-				if (fireEvents)
-					sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
+			_position.stopTracking(info.subtrack);
+			if (!_position.isTracking()) {
+				// All subtracks have finished playing
+				if (_autoLoop) {
+					jumpToTick(0);
+				} else {
+					stopPlaying();
+					if (fireEvents)
+						sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
+				}
+				return false;
 			}
-			return false;
+			// Do not send End of Track to driver when there are
+			// still subtracks playing
+			sendEventToDriver = false;
 		} else if (info.ext.type == 0x51) {
 			if (info.length >= 3) {
 				setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]);
 			}
 		}
-		if (fireEvents)
+		if (fireEvents && sendEventToDriver)
 			sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
 	} else {
 		if (fireEvents)
@@ -388,12 +465,20 @@ bool MidiParser::setTrack(int track) {
 	memset(_activeNotes, 0, sizeof(_activeNotes));
 	if (_disableAutoStartPlayback)
 		_doParse = false;
+	for (int i = 0; i < MAXIMUM_SUBTRACKS; i++) {
+		_nextSubtrackEvents[i].clear();
+	}
+	_nextEvent = &_nextSubtrackEvents[0];
 
 	onTrackStart(track);
 
 	_activeTrack = track;
-	_position._playPos = _tracks[track];
-	parseNextEvent(_nextEvent);
+	for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+		_position._subtracks[i]._playPos = _tracks[_activeTrack][i];
+		parseNextEvent(_nextSubtrackEvents[i]);
+	}
+	determineNextEvent();
+
 	return true;
 }
 
@@ -407,10 +492,18 @@ void MidiParser::stopPlaying() {
 bool MidiParser::startPlaying() {
 	if (_activeTrack >= _numTracks || _pause)
 		return false;
-	if (!_position._playPos) {
-		_position._playPos = _tracks[_activeTrack];
-		parseNextEvent(_nextEvent);
+	if (!_position.isTracking()) {
+		for (int i = 0; i < MAXIMUM_SUBTRACKS; i++) {
+			_nextSubtrackEvents[i].clear();
+		}
+		_nextEvent = &_nextSubtrackEvents[0];
+		for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+			_position._subtracks[i]._playPos = _tracks[_activeTrack][i];
+			parseNextEvent(_nextSubtrackEvents[i]);
+		}
+		determineNextEvent();
 	}
+
 	_doParse = true;
 	return true;
 }
@@ -431,8 +524,8 @@ void MidiParser::hangAllActiveNotes() {
 	// accounted for every active note.
 	uint16 tempActive[128];
 	memcpy(tempActive, _activeNotes, sizeof (tempActive));
+	Tracker currentPos(_position);
 
-	uint32 advanceTick = _position._lastEventTick;
 	while (true) {
 		int i;
 		for (i = 0; i < 128; ++i)
@@ -440,26 +533,39 @@ void MidiParser::hangAllActiveNotes() {
 				break;
 		if (i == 128)
 			break;
-		parseNextEvent(_nextEvent);
-		advanceTick += _nextEvent.delta;
-		if (_nextEvent.command() == 0x8) {
-			if (tempActive[_nextEvent.basic.param1] & (1 << _nextEvent.channel())) {
-				hangingNote(_nextEvent.channel(), _nextEvent.basic.param1, (advanceTick - _position._lastEventTick) * _psecPerTick, false);
-				tempActive[_nextEvent.basic.param1] &= ~(1 << _nextEvent.channel());
+
+		if (_position.isTracking(_nextEvent->subtrack))
+			parseNextEvent(_nextSubtrackEvents[_nextEvent->subtrack]);
+		determineNextEvent();
+
+		uint8 subtrack = _nextEvent->subtrack;
+		uint32 eventTick = _position._subtracks[subtrack]._lastEventTick + _nextEvent->delta;
+		if (_nextEvent->command() == 0x8) {
+			if (tempActive[_nextEvent->basic.param1] & (1 << _nextEvent->channel())) {
+				hangingNote(_nextEvent->channel(), _nextEvent->basic.param1, (eventTick - currentPos._lastEventTick) * _psecPerTick, false);
+				tempActive[_nextEvent->basic.param1] &= ~(1 << _nextEvent->channel());
 			}
-		} else if (_nextEvent.event == 0xFF && _nextEvent.ext.type == 0x2F) {
-			// warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left");
-			for (i = 0; i < 128; ++i) {
-				for (int j = 0; j < 16; ++j) {
-					if (tempActive[i] & (1 << j)) {
-						activeNote(j, i, false);
-						sendToDriver(0x80 | j, i, 0);
+		} else if (!_position.isTracking() || (_nextEvent->event == 0xFF && _nextEvent->ext.type == 0x2F)) {
+			_position.stopTracking(subtrack);
+			if (!_position.isTracking()) {
+				// warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left");
+				for (i = 0; i < 128; ++i) {
+					for (int j = 0; j < 16; ++j) {
+						if (tempActive[i] & (1 << j)) {
+							activeNote(j, i, false);
+							sendToDriver(0x80 | j, i, 0);
+						}
 					}
 				}
+				break;
 			}
-			break;
 		}
+
+		_position._lastEventTick = eventTick;
+		_position._subtracks[subtrack]._lastEventTick = eventTick;
 	}
+
+	_position = currentPos;
 }
 
 bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool dontSendNoteOn) {
@@ -470,57 +576,89 @@ bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool d
 	_jumpingToTick = true;
 
 	Tracker currentPos(_position);
-	EventInfo currentEvent(_nextEvent);
+	EventInfo *currentEvent = _nextEvent;
+	EventInfo currentSubtrackEvents[MAXIMUM_SUBTRACKS];
+	for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+		currentSubtrackEvents[i] = _nextSubtrackEvents[i];
+	}
 
 	resetTracking();
-	_position._playPos = _tracks[_activeTrack];
-	parseNextEvent(_nextEvent);
+	for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+		_position._subtracks[i]._playPos = _tracks[_activeTrack][i];
+		parseNextEvent(_nextSubtrackEvents[i]);
+	}
+	determineNextEvent();
 	if (tick > 0) {
 		while (true) {
-			EventInfo &info = _nextEvent;
-			if (_position._lastEventTick + info.delta >= tick) {
+			EventInfo &info = *_nextEvent;
+			uint8 subtrack = info.subtrack;
+			uint32 eventTick = _position._subtracks[subtrack]._lastEventTick + info.delta;
+			if (eventTick >= tick) {
 				_position._playTime += (tick - _position._lastEventTick) * _psecPerTick;
 				_position._playTick = tick;
 				break;
 			}
 
-			_position._lastEventTick += info.delta;
-			_position._lastEventTime += info.delta * _psecPerTick;
-			_position._playTick = _position._lastEventTick;
-			_position._playTime = _position._lastEventTime;
-
 			// Some special processing for the fast-forward case
 			if (info.command() == 0x9 && dontSendNoteOn) {
 				// Don't send note on; doing so creates a "warble" with
 				// some instruments on the MT-32. Refer to bug #9262
 			} else if (info.event == 0xFF && info.ext.type == 0x2F) {
 				// End of track
-				// This means that we failed to find the right tick.
-				_position = currentPos;
-				_nextEvent = currentEvent;
-				_jumpingToTick = false;
-				return false;
+				_position.stopTracking(info.subtrack);
+				if (!_position.isTracking()) {
+					// This means that we failed to find the right tick.
+					_position = currentPos;
+					_nextEvent = currentEvent;
+					for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+						_nextSubtrackEvents[i]  = currentSubtrackEvents[i];
+					}
+					_jumpingToTick = false;
+					return false;
+				}
 			} else {
 				processEvent(info, fireEvents);
 			}
 
-			parseNextEvent(_nextEvent);
+			uint32 eventTime = _position._lastEventTime + (eventTick - _position._lastEventTick) * _psecPerTick;
+			_position._playTime = eventTime;
+			_position._lastEventTime = eventTime;
+			_position._subtracks[subtrack]._lastEventTime = eventTime;
+
+			_position._playTick = eventTick;
+			_position._lastEventTick = eventTick;
+			_position._subtracks[subtrack]._lastEventTick = eventTick;
+
+			if (_position.isTracking(subtrack)) {
+				parseNextEvent(_nextSubtrackEvents[subtrack]);
+			}
+			determineNextEvent();
 		}
 	}
 
 	if (stopNotes) {
-		if (!_smartJump || !currentPos._playPos) {
+		if (!_smartJump || !currentPos.isTracking()) {
 			allNotesOff();
 		} else {
-			EventInfo targetEvent(_nextEvent);
 			Tracker targetPosition(_position);
+			EventInfo *targetEvent = _nextEvent;
+			EventInfo targetSubtrackEvents[MAXIMUM_SUBTRACKS];
+			for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+				targetSubtrackEvents[i]  = _nextSubtrackEvents[i];
+			}
 
 			_position = currentPos;
 			_nextEvent = currentEvent;
+			for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+				_nextSubtrackEvents[i] = currentSubtrackEvents[i];
+			}
 			hangAllActiveNotes();
 
-			_nextEvent = targetEvent;
 			_position = targetPosition;
+			_nextEvent = targetEvent;
+			for (int i = 0; i < _numSubtracks[_activeTrack]; i++) {
+				_nextSubtrackEvents[i] = targetSubtrackEvents[i];
+			}
 		}
 	}
 
@@ -538,6 +676,13 @@ void MidiParser::unloadMusic() {
 	_numTracks = 0;
 	_activeTrack = 255;
 	_abortParse = true;
+	memset(_tracks, 0, sizeof(_tracks));
+	memset(_numSubtracks, 1, sizeof(_numSubtracks));
+	for (int i = 0; i < MAXIMUM_SUBTRACKS; i++) {
+		_nextSubtrackEvents[i].clear();
+		_nextSubtrackEvents[i].subtrack = i;
+	}
+	_nextEvent = &_nextSubtrackEvents[0];
 
 	if (_centerPitchWheelOnUnload) {
 		// Center the pitch wheels in preparation for the next piece of
diff --git a/audio/midiparser.h b/audio/midiparser.h
index f15391b276b..77af6acde26 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -28,6 +28,8 @@
 #include "common/endian.h"
 #include "common/stream.h"
 
+#define AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS 20
+
 class MidiDriver_BASE;
 
 /**
@@ -46,33 +48,82 @@ class MidiDriver_BASE;
 //////////////////////////////////////////////////
 
 /**
- * Maintains time and position state within a MIDI stream.
+ * Maintains time and position state within a MIDI stream, or
+ * multiple parallel MIDI streams.
  * A single Tracker struct is used by MidiParser to keep track
- * of its current position in the MIDI stream. The Tracker
+ * of its current position(s) in the MIDI stream(s). The Tracker
  * struct, however, allows alternative locations to be cached.
  * See MidiParser::jumpToTick() for an example of tracking
  * multiple locations within a MIDI stream. NOTE: It is
  * important to also maintain pre-parsed EventInfo data for
- * each Tracker location.
+ * each subtrack in each Tracker location.
  */
 struct Tracker {
-	byte * _playPos;        ///< A pointer to the next event to be parsed
-	uint32 _playTime;       ///< Current time in microseconds; may be in between event times
-	uint32 _playTick;       ///< Current MIDI tick; may be in between event ticks
+	struct SubtrackStatus {
+		byte * _playPos;        ///< A pointer to the next event to be parsed
+		uint32 _lastEventTime;  ///< The time, in microseconds, of the last event that was parsed
+		uint32 _lastEventTick;  ///< The tick at which the last parsed event occurs
+		byte   _runningStatus;  ///< Cached MIDI command, for MIDI streams that rely on implied event codes
+
+		void clear() {
+			_playPos = nullptr;
+			_lastEventTime = 0;
+			_lastEventTick = 0;
+			_runningStatus = 0;
+		}
+
+		void stopTracking() {
+			_playPos = nullptr;
+		}
+
+		bool isTracking() const {
+			return _playPos != nullptr;
+		}
+	};
+
+	uint32 _playTime;      ///< Current time in microseconds; may be in between event times
+	uint32 _playTick;      ///< Current MIDI tick; may be in between event ticks
 	uint32 _lastEventTime; ///< The time, in microseconds, of the last event that was parsed
-	uint32 _lastEventTick; ///< The tick at which the last parsed event occurs
-	byte   _runningStatus;  ///< Cached MIDI command, for MIDI streams that rely on implied event codes
+						   ///< across all subtracks
+	uint32 _lastEventTick; ///< The tick at which the last parsed event across all subtracks occurs
+	SubtrackStatus _subtracks[AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS];
 
 	Tracker() { clear(); }
 
 	/// Clears all data; used by the constructor for initialization.
 	void clear() {
-		_playPos = 0;
 		_playTime = 0;
 		_playTick = 0;
 		_lastEventTime = 0;
 		_lastEventTick = 0;
-		_runningStatus = 0;
+
+		for (int i = 0; i < AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS; i++) {
+			_subtracks[i].clear();
+		}
+	}
+
+	bool isTracking() const {
+		for (int i = 0; i < AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS; i++) {
+			if (_subtracks[i].isTracking())
+				return true;
+		}
+		return false;
+	}
+
+	bool isTracking(uint8 subtrack) const {
+		assert(subtrack < AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS);
+		return _subtracks[subtrack].isTracking();
+	}
+
+	void stopTracking() {
+		for (int i = 0; i < AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS; i++) {
+			_subtracks[i].stopTracking();
+		}
+	}
+
+	void stopTracking(uint8 subtrack) {
+		assert(subtrack < AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS);
+		_subtracks[subtrack].stopTracking();
 	}
 };
 
@@ -82,11 +133,12 @@ struct Tracker {
  * of MidiParser::parseNextEvent() each time another event is needed.
  */
 struct EventInfo {
-	byte * start; ///< Position in the MIDI stream where the event starts.
-	              ///< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta.
-	uint32 delta; ///< The number of ticks after the previous event that this event should occur.
-	byte   event; ///< Upper 4 bits are the command code, lower 4 bits are the MIDI channel.
-	              ///< For META, event == 0xFF. For SysEx, event == 0xF0.
+	byte * start;    ///< Position in the MIDI stream where the event starts.
+	                 ///< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta.
+	uint8  subtrack; ///< The subtrack containing this event.
+	uint32 delta;    ///< The number of ticks after the previous event that this event should occur.
+	byte   event;    ///< Upper 4 bits are the command code, lower 4 bits are the MIDI channel.
+	                 ///< For META, event == 0xFF. For SysEx, event == 0xF0.
 	union {
 		struct {
 			byte param1; ///< The first parameter in a simple MIDI message.
@@ -108,7 +160,20 @@ struct EventInfo {
 	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), noop(false) { basic.param1 = 0; basic.param2 = 0; ext.type = 0; ext.data = 0; }
+	void clear() {
+		start = nullptr;
+		delta = 0;
+		event = 0;
+		basic.param1 = 0;
+		basic.param2 = 0;
+		ext.type = 0;
+		ext.data = nullptr;
+		length = 0;
+		loop = false;
+		noop = false;
+	}
+
+	EventInfo() : subtrack(0) { clear(); }
 };
 
 /**
@@ -289,6 +354,7 @@ struct NoteTimer {
 class MidiParser {
 protected:
 	static const uint8 MAXIMUM_TRACKS = 120;
+	static const uint8 MAXIMUM_SUBTRACKS = AUDIO_MIDIPARSER_MAXIMUM_SUBTRACKS;
 
 	uint16    _activeNotes[128];   ///< Each uint16 is a bit mask for channels that have that note on.
 	NoteTimer _hangingNotes[32];   ///< Maintains expiration info for up to 32 notes.
@@ -307,14 +373,19 @@ protected:
 	bool   _sendSustainOffOnNotesOff;   ///< Send a sustain off on a notes off event, stopping hanging notes
 	bool   _disableAllNotesOffMidiEvents;   ///< Don't send All Notes Off MIDI messages
 	bool   _disableAutoStartPlayback;  ///< Do not automatically start playback after parsing MIDI data or setting the track
-	byte  *_tracks[MAXIMUM_TRACKS];    ///< Multi-track MIDI formats are supported, up to 120 tracks.
+	byte  *_tracks[MAXIMUM_TRACKS][MAXIMUM_SUBTRACKS]; ///< Multi-track MIDI formats are supported, up to 120 tracks with 20 subtracks each.
 	byte   _numTracks;     ///< Count of total tracks for multi-track MIDI formats. 1 for single-track formats.
+	byte   _numSubtracks[MAXIMUM_TRACKS]; ///< The number of subtracks for each track.
 	byte   _activeTrack;   ///< Keeps track of the currently active track, in multi-track formats.
 
 	Tracker _position;      ///< The current time/position in the active track.
-	EventInfo _nextEvent;  ///< The next event to transmit. Events are preparsed
-	                        ///< so each event is parsed only once; this permits
-	                        ///< simulated events in certain formats.
+	EventInfo *_nextEvent;  ///< The next event to transmit. Points to one of the _nextSubtrackEvents
+							///< entries. Will always point to _nextSubtrackEvents[0] for tracks without
+							///< subtracks.
+	EventInfo _nextSubtrackEvents[MAXIMUM_SUBTRACKS]; ///< The next event to process for each subtrack
+													  ///< of the active track. Events are preparsed
+													  ///< so each event is parsed only once; this permits
+													  ///< simulated events in certain formats.
 	bool   _abortParse;    ///< If a jump or other operation interrupts parsing, flag to abort.
 	bool   _jumpingToTick; ///< True if currently inside jumpToTick
 	bool   _doParse;       ///< True if the parser should be parsing; false if it should not be active
@@ -334,6 +405,18 @@ protected:
 	virtual void resetTracking();
 	virtual void allNotesOff();
 	virtual void parseNextEvent(EventInfo &info) = 0;
+	/**
+	 * Determines which event in the active track's subtracks
+	 * should be processed next. This is set in _nextEvent.
+	 */
+	virtual void determineNextEvent();
+	/**
+	 * Resets the track timestamps by subtracting the same value
+	 * from all tick and time values. This function is called after
+	 * the track has been (partially) looped to prevent the timestamps
+	 * from overflowing.
+	 */
+	virtual void rebaseTracking();
 	virtual bool processEvent(const EventInfo &info, bool fireEvents = true);
 
 	void activeNote(byte channel, byte note, bool active);
@@ -448,7 +531,7 @@ public:
 	virtual void setTempo(uint32 tempo);
 	virtual void onTimer();
 
-	bool isPlaying() const { return (_position._playPos != 0 && _doParse); }
+	bool isPlaying() const { return (_position.isTracking() && _doParse); }
 	/**
 	 * Start playback from the current position in the current track, or at
 	 * the beginning if there is no current position.
diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp
index 83ca3c1746b..a30b11c1f5c 100644
--- a/audio/midiparser_qt.cpp
+++ b/audio/midiparser_qt.cpp
@@ -116,7 +116,7 @@ void MidiParser_QT::parseNextEvent(EventInfo &info) {
 }
 
 uint32 MidiParser_QT::readNextEvent() {
-	if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) {
+	if (_position._subtracks[0]._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) {
 		// Manually insert end of track when we reach the end
 		EventInfo info;
 		info.event = 0xFF;
@@ -249,7 +249,7 @@ void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intP
 void MidiParser_QT::handleGeneralEvent(uint32 control) {
 	uint32 part = (control >> 16) & 0xFFF;
 	uint32 dataSize = ((control & 0xFFFF) - 2) * 4;
-	byte subType = READ_BE_UINT16(_position._playPos + dataSize) & 0x3FFF;
+	byte subType = READ_BE_UINT16(_position._subtracks[0]._playPos + dataSize) & 0x3FFF;
 
 	switch (subType) {
 	case 1:
@@ -259,7 +259,7 @@ void MidiParser_QT::handleGeneralEvent(uint32 control) {
 
 		// We have to remap channels because GM needs percussion to be on the
 		// percussion channel but QuickTime can have that anywhere.
-		definePart(part, READ_BE_UINT32(_position._playPos + 80));
+		definePart(part, READ_BE_UINT32(_position._subtracks[0]._playPos + 80));
 		break;
 	case 5: // Tune Difference
 	case 8: // MIDI Channel
@@ -271,7 +271,7 @@ void MidiParser_QT::handleGeneralEvent(uint32 control) {
 		warning("Unhandled general event %d", subType);
 	}
 
-	_position._playPos += dataSize + 4;
+	_position._subtracks[0]._playPos += dataSize + 4;
 }
 
 void MidiParser_QT::definePart(uint32 part, uint32 instrument) {
@@ -464,7 +464,7 @@ void MidiParser_QT::initCommon() {
 	assert(_numTracks > 0);
 
 	for (uint32 i = 0; i < _trackInfo.size(); i++)
-		MidiParser::_tracks[i] = _trackInfo[i].data;
+		MidiParser::_tracks[i][0] = _trackInfo[i].data;
 
 	_ppqn = _trackInfo[0].timeScale;
 	resetTracking();
@@ -506,8 +506,8 @@ byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint3
 }
 
 uint32 MidiParser_QT::readUint32() {
-	uint32 value = READ_BE_UINT32(_position._playPos);
-	_position._playPos += 4;
+	uint32 value = READ_BE_UINT32(_position._subtracks[0]._playPos);
+	_position._subtracks[0]._playPos += 4;
 	return value;
 }
 
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index a6568ee9bbe..a207f489a10 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -29,32 +29,32 @@
 static const byte commandLengths[8] = { 3, 3, 3, 3, 2, 2, 3, 0 };
 static const byte specialLengths[16] = { 0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 };
 
-MidiParser_SMF::MidiParser_SMF(int8 source) : MidiParser(source), _buffer(nullptr) {
+MidiParser_SMF::MidiParser_SMF(int8 source) : MidiParser(source) {
 	for (int i = 0; i < ARRAYSIZE(_noteChannelToTrack); i++)
 		_noteChannelToTrack[i] = -1;
 }
 
-MidiParser_SMF::~MidiParser_SMF() {
-	free(_buffer);
-}
-
 void MidiParser_SMF::parseNextEvent(EventInfo &info) {
-	info.start = _position._playPos;
-	info.delta = readVLQ(_position._playPos);
+	uint8 subtrack = info.subtrack;
+	byte *playPos = _position._subtracks[subtrack]._playPos;
+	info.start = playPos;
+	info.delta = readVLQ(playPos);
 
 	// Process the next info.
-	if ((_position._playPos[0] & 0xF0) >= 0x80)
-		info.event = *(_position._playPos++);
+	if ((playPos[0] & 0xF0) >= 0x80)
+		info.event = *(playPos++);
 	else
-		info.event = _position._runningStatus;
-	if (info.event < 0x80)
+		info.event = _position._subtracks[subtrack]._runningStatus;
+	if (info.event < 0x80) {
+		_position._subtracks[subtrack]._playPos = playPos;
 		return;
+	}
 
-	_position._runningStatus = info.event;
+	_position._subtracks[subtrack]._runningStatus = info.event;
 	switch (info.command()) {
 	case 0x9: // Note On
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		if (info.basic.param2 == 0)
 			info.event = info.channel() | 0x80;
 		info.length = 0;
@@ -62,7 +62,7 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 
 	case 0xC:
 	case 0xD:
-		info.basic.param1 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
 		info.basic.param2 = 0;
 		break;
 
@@ -70,20 +70,20 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 	case 0xA:
 	case 0xB:
 	case 0xE:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		info.length = 0;
 		break;
 
 	case 0xF: // System Common, Meta or SysEx event
 		switch (info.event & 0x0F) {
 		case 0x2: // Song Position Pointer
-			info.basic.param1 = *(_position._playPos++);
-			info.basic.param2 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
+			info.basic.param2 = *(playPos++);
 			break;
 
 		case 0x3: // Song Select
-			info.basic.param1 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
 			info.basic.param2 = 0;
 			break;
 
@@ -97,16 +97,16 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 			break;
 
 		case 0x0: // SysEx
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 
 		case 0xF: // META event
-			info.ext.type = *(_position._playPos++);
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.ext.type = *(playPos++);
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 
 		default:
@@ -118,11 +118,14 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 	default:
 		break;
 	}
+
+	_position._subtracks[subtrack]._playPos = playPos;
 }
 
 bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 	uint32 len;
 	byte midiType;
+	byte numTrackChunks;
 
 	unloadMusic();
 	byte *pos = data;
@@ -141,15 +144,13 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 			return false;
 		}
 
-		// Verify that this MIDI either is a Type 2
-		// or has only 1 track. We do not support
-		// multitrack Type 1 files.
-		_numTracks = pos[2] << 8 | pos[3];
+		numTrackChunks = pos[2] << 8 | pos[3];
 		midiType = pos[1];
-		if (midiType > 2 /*|| (midiType < 2 && _numTracks > 1)*/) {
-			warning("No support for a Type %d MIDI with %d tracks", (int)midiType, (int)_numTracks);
+		if (midiType > 2) {
+			warning("Invalid MIDI type %d", (int)midiType);
 			return false;
 		}
+		_numTracks = (midiType == 1 ? 1 : numTrackChunks);
 		_ppqn = pos[4] << 8 | pos[5];
 		pos += len;
 	} else {
@@ -164,7 +165,7 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 	}
 
 	int tracksRead = 0;
-	while (tracksRead < _numTracks) {
+	while (tracksRead < numTrackChunks) {
 		if (memcmp(pos, "MTrk", 4)) {
 			warning("Position: %p ('%c')", (void *)pos, *pos);
 			warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
@@ -172,26 +173,20 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 		}
 
 		// Skip the MTrk and length bytes
-		_tracks[tracksRead] = pos + 8;
+		if (midiType == 1) {
+			_tracks[0][tracksRead] = pos + 8;
+		}
+		else {
+			_tracks[tracksRead][0] = pos + 8;
+			_numSubtracks[tracksRead] = 1;
+		}
 		pos += 4;
 		len = read4high(pos);
 		pos += len;
 		++tracksRead;
 	}
-
-	// If this is a Type 1 MIDI, we need to now compress
-	// our tracks down into a single Type 0 track.
-	free(_buffer);
-	_buffer = nullptr;
-
 	if (midiType == 1) {
-		// FIXME: Doubled the buffer size to prevent crashes with the
-		// Inherit the Earth MIDIs. Jamieson630 said something about a
-		// better fix, but this will have to do in the meantime.
-		_buffer = (byte *)malloc(size * 2);
-		compressToType0(_tracks, _numTracks, _buffer, false);
-		_numTracks = 1;
-		_tracks[0] = _buffer;
+		_numSubtracks[0] = tracksRead;
 	}
 
 	// Note that we assume the original data passed in
@@ -234,137 +229,4 @@ int32 MidiParser_SMF::determineDataSize(Common::SeekableReadStream *stream) {
 	return stream->pos() - startPos;
 }
 
-uint32 MidiParser_SMF::compressToType0(byte *tracks[], byte numTracks, byte *buffer, bool malformedPitchBends) {
-	// We assume that buffer has been allocated
-	// to sufficient size for this operation.
-
-	// using 0xFF since it could write trackPos[0 to numTracks] here
-	// this would cause some illegal writes and could lead to segfaults
-	// (it crashed for some midis for me, they're not used in any game
-	// scummvm supports though). *Maybe* handle this in another way,
-	// it's at the moment only to be sure, that nothing goes wrong.
-	byte *trackPos[0xFF];
-	byte runningStatus[0xFF];
-	uint32 trackTimer[0xFF];
-	uint32 delta;
-	int i;
-
-	for (i = 0; i < numTracks; ++i) {
-		runningStatus[i] = 0;
-		trackPos[i] = tracks[i];
-		trackTimer[i] = readVLQ(trackPos[i]);
-		runningStatus[i] = 0;
-	}
-
-	int bestTrack;
-	uint32 length;
-	byte *output = buffer;
-	byte *pos, *pos2;
-	byte event;
-	uint32 copyBytes;
-	bool write;
-	byte activeTracks = numTracks;
-
-	while (activeTracks) {
-		write = true;
-		bestTrack = 255;
-		for (i = 0; i < numTracks; ++i) {
-			if (trackPos[i] && (bestTrack == 255 || trackTimer[i] < trackTimer[bestTrack]))
-				bestTrack = i;
-		}
-		if (bestTrack == 255) {
-			warning("Premature end of tracks");
-			break;
-		}
-
-		// Initial VLQ delta computation
-		delta = 0;
-		length = trackTimer[bestTrack];
-		for (i = 0; length; ++i) {
-			delta = (delta << 8) | (length & 0x7F) | (i ? 0x80 : 0);
-			length >>= 7;
-		}
-
-		// Process MIDI event.
-		bool implicitEvent = false;
-		copyBytes = 0;
-		pos = trackPos[bestTrack];
-		do {
-			event = *(pos++);
-			if (event < 0x80) {
-				event = runningStatus[bestTrack];
-				implicitEvent = true;
-			}
-		} while (malformedPitchBends && (event & 0xF0) == 0xE0 && pos++);
-		runningStatus[bestTrack] = event;
-
-		if (commandLengths[(event >> 4) - 8] > 0) {
-			copyBytes = commandLengths[(event >> 4) - 8];
-			if ((event & 0xf0) == MidiDriver_BASE::MIDI_COMMAND_NOTE_ON)
-				_noteChannelToTrack[event & 0x0f] = bestTrack;
-		} else if (specialLengths[(event & 0x0F)] > 0) {
-			copyBytes = specialLengths[(event & 0x0F)];
-		} else if (event == 0xF0) {
-			// SysEx
-			pos2 = pos;
-			length = readVLQ(pos);
-			copyBytes = 1 + (pos - pos2) + length;
-		} else if (event == 0xFF) {
-			// META
-			event = *(pos++);
-			if (event == 0x2F && activeTracks > 1) {
-				trackPos[bestTrack] = nullptr;
-				write = false;
-			} else {
-				pos2 = pos;
-				length = readVLQ(pos);
-				copyBytes = 2 + (pos - pos2) + length;
-			}
-			if (event == 0x2F)
-				--activeTracks;
-		} else {
-			warning("Bad MIDI command %02X", (int)event);
-			trackPos[bestTrack] = nullptr;
-		}
-
-		// Update all tracks' deltas
-		if (write) {
-			for (i = 0; i < numTracks; ++i) {
-				if (trackPos[i] && i != bestTrack)
-					trackTimer[i] -= trackTimer[bestTrack];
-			}
-		}
-
-		if (trackPos[bestTrack]) {
-			if (write) {
-				trackTimer[bestTrack] = 0;
-
-				// Write VLQ delta
-				while (delta & 0x80) {
-					*output++ = (byte)(delta & 0xFF);
-					delta >>= 8;
-				}
-				*output++ = (byte)(delta & 0xFF);
-
-				// Write MIDI data
-				if (!implicitEvent)
-					++trackPos[bestTrack];
-				--copyBytes;
-				*output++ = runningStatus[bestTrack];
-				memcpy(output, trackPos[bestTrack], copyBytes);
-				output += copyBytes;
-			}
-
-			// Fetch new VLQ delta for winning track
-			trackPos[bestTrack] += copyBytes;
-			if (activeTracks)
-				trackTimer[bestTrack] += readVLQ(trackPos[bestTrack]);
-		}
-	}
-
-	*output++ = 0x00;
-
-	return output - buffer;
-}
-
 MidiParser *MidiParser::createParser_SMF(int8 source) { return new MidiParser_SMF(source); }
diff --git a/audio/midiparser_smf.h b/audio/midiparser_smf.h
index 523833b5c55..c9cee1c2dc6 100644
--- a/audio/midiparser_smf.h
+++ b/audio/midiparser_smf.h
@@ -29,7 +29,6 @@
  */
 class MidiParser_SMF : public MidiParser {
 protected:
-	byte *_buffer;
 	int8 _noteChannelToTrack[16];
 
 protected:
@@ -49,7 +48,6 @@ protected:
 
 public:
 	MidiParser_SMF(int8 source = -1);
-	~MidiParser_SMF();
 
 	bool loadMusic(byte *data, uint32 size) override;
 
diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp
index 97941906dc3..0d862d4a9e9 100644
--- a/audio/midiparser_xmidi.cpp
+++ b/audio/midiparser_xmidi.cpp
@@ -136,7 +136,7 @@ bool MidiParser_XMIDI::jumpToIndex(uint8 index, bool stopNotes) {
 	_jumpingToTick = true;
 
 	if (stopNotes) {
-		if (!_smartJump || !_position._playPos) {
+		if (!_smartJump || !_position.isTracking()) {
 			allNotesOff();
 		} else {
 			hangAllActiveNotes();
@@ -144,8 +144,8 @@ bool MidiParser_XMIDI::jumpToIndex(uint8 index, bool stopNotes) {
 	}
 
 	resetTracking();
-	_position._playPos = _trackBranches[_activeTrack][index];
-	parseNextEvent(_nextEvent);
+	_position._subtracks[0]._playPos = _trackBranches[_activeTrack][index];
+	parseNextEvent(*_nextEvent);
 
 	_jumpingToTick = false;
 
@@ -153,17 +153,18 @@ bool MidiParser_XMIDI::jumpToIndex(uint8 index, bool stopNotes) {
 }
 
 void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
-	info.start = _position._playPos;
-	info.delta = readVLQ2(_position._playPos);
+	byte *playPos = _position._subtracks[0]._playPos;
+	info.start = playPos;
+	info.delta = readVLQ2(playPos);
 	info.loop = false;
 
 	// Process the next event.
-	info.event = *(_position._playPos++);
+	info.event = *(playPos++);
 	switch (info.event >> 4) {
 	case 0x9: // Note On
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
-		info.length = readVLQ(_position._playPos);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
+		info.length = readVLQ(playPos);
 		if (info.length == 0) {
 			// Notes with length 0 are played with a very short duration by the AIL driver.
 			// However, the MidiParser will treat notes with length 0 as "active notes"; i.e.
@@ -180,20 +181,20 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 
 	case 0xC:
 	case 0xD:
-		info.basic.param1 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
 		info.basic.param2 = 0;
 		break;
 
 	case 0x8:
 	case 0xA:
 	case 0xE:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		break;
 
 	case 0xB:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 
 		// This isn't a full XMIDI implementation, but it should
 		// hopefully be "good enough" for most things.
@@ -201,7 +202,7 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 		switch (info.basic.param1) {
 		// Simplified XMIDI looping.
 		case 0x74: {	// XMIDI_CONTROLLER_FOR_LOOP
-				byte *pos = _position._playPos;
+				byte *pos = playPos;
 				if (_loopCount < ARRAYSIZE(_loop) - 1)
 					_loopCount++;
 				else
@@ -223,11 +224,11 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 						if (--_loop[_loopCount].repeat == 0) {
 							_loopCount--;
 						} else {
-							_position._playPos = _loop[_loopCount].pos;
+							playPos = _loop[_loopCount].pos;
 							info.loop = true;
 						}
 					} else {
-						_position._playPos = _loop[_loopCount].pos;
+						playPos = _loop[_loopCount].pos;
 						info.loop = true;
 					}
 				}
@@ -273,12 +274,12 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 	case 0xF: // Meta or SysEx event
 		switch (info.event & 0x0F) {
 		case 0x2: // Song Position Pointer
-			info.basic.param1 = *(_position._playPos++);
-			info.basic.param2 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
+			info.basic.param2 = *(playPos++);
 			break;
 
 		case 0x3: // Song Select
-			info.basic.param1 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
 			info.basic.param2 = 0;
 			break;
 
@@ -292,16 +293,16 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 			break;
 
 		case 0x0: // SysEx
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 
 		case 0xF: // META event
-			info.ext.type = *(_position._playPos++);
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.ext.type = *(playPos++);
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			if (info.ext.type == 0x51 && info.length == 3) {
 				// Tempo event. We want to make these constant 500,000.
 				info.ext.data[0] = 0x07;
@@ -319,6 +320,8 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 	default:
 		break;
 	}
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 void MidiParser_XMIDI::setMidiDriver(MidiDriver_BASE *driver) {
@@ -454,17 +457,18 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
 				pos += (len + 1) & ~1;
 			} else if (!memcmp(pos, "EVNT", 4)) {
 				// Ahh! What we're looking for at last.
-				_tracks[tracksRead] = pos + 8; // Skip the EVNT and length bytes
+				_tracks[tracksRead][0] = pos + 8; // Skip the EVNT and length bytes
+				_numSubtracks[tracksRead] = 1;
 				pos += 4;
 				len = read4high(pos);
 				pos += (len + 1) & ~1;
 				// Calculate branch index positions using the track position we just found
 				for (int j = 0; j < MAXIMUM_TRACK_BRANCHES; ++j) {
 					if (branchOffsets[j] != 0) {
-						byte *branchPos = _tracks[tracksRead] + branchOffsets[j];
+						byte *branchPos = _tracks[tracksRead][0] + branchOffsets[j];
 						if (branchPos >= pos) {
 							warning("Invalid sequence branch position (after track end)");
-							branchPos = _tracks[tracksRead];
+							branchPos = _tracks[tracksRead][0];
 						}
 						_trackBranches[tracksRead][j] = branchPos;
 					}
diff --git a/engines/agos/midiparser_gmf.cpp b/engines/agos/midiparser_gmf.cpp
index 58f2f1fcffa..12e55194f14 100644
--- a/engines/agos/midiparser_gmf.cpp
+++ b/engines/agos/midiparser_gmf.cpp
@@ -30,7 +30,7 @@ MidiParser_GMF::MidiParser_GMF(int8 source, bool useDosTempos) : MidiParser_SMF(
 }
 
 void MidiParser_GMF::parseNextEvent(EventInfo &info) {
-	byte *parsePos = _position._playPos;
+	byte *parsePos = _position._subtracks[0]._playPos;
 	uint8 *start = parsePos;
 	uint32 delta = readVLQ(parsePos);
 
@@ -64,7 +64,7 @@ void MidiParser_GMF::parseNextEvent(EventInfo &info) {
 		info.ext.data = parsePos;
 		info.noop = false;
 
-		_position._playPos = parsePos;
+		_position._subtracks[0]._playPos = parsePos;
 		return;
 	}
 
@@ -84,7 +84,7 @@ void MidiParser_GMF::parseNextEvent(EventInfo &info) {
 		info.length = 0;
 		info.noop = true;
 
-		_position._playPos = parsePos;
+		_position._subtracks[0]._playPos = parsePos;
 	} else {
 		// Processing of the other events is the same as the SMF format.
 		info.noop = false;
@@ -115,7 +115,7 @@ bool MidiParser_GMF::loadMusic(byte *data, uint32 size) {
 		headerLoop = data[6] == 1;
 
 		// MIDI track data starts immediately after the GMF header.
-		_tracks[0] = data + 7;
+		_tracks[0][0] = data + 7;
 		_tracksEndPos[0] = data + size;
 	} else {
 		// Assume multi-track file.
@@ -145,11 +145,12 @@ bool MidiParser_GMF::loadMusic(byte *data, uint32 size) {
 		// Read all the track start offsets.
 		int tracksRead = 0;
 		while (tracksRead < _numTracks) {
-			_tracks[tracksRead] = trackStart + 7; // Skip 7-byte GMF header
+			_tracks[tracksRead][0] = trackStart + 7; // Skip 7-byte GMF header
 			trackStart = data + READ_LE_UINT16(pos);
 			pos += 2;
 			// Start of the next track is the end of this track.
 			_tracksEndPos[tracksRead] = trackStart;
+			_numSubtracks[tracksRead] = 1;
 
 			tracksRead++;
 		}
diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp
index c3fedff15bd..794b9d78a8e 100644
--- a/engines/agos/midiparser_s1d.cpp
+++ b/engines/agos/midiparser_s1d.cpp
@@ -89,13 +89,14 @@ uint32 MidiParser_S1D::readVLQ2(byte *&data) {
 }
 
 void MidiParser_S1D::parseNextEvent(EventInfo &info) {
-	info.start = _position._playPos;
+	byte *playPos = _position._subtracks[0]._playPos;
+	info.start = playPos;
 	info.length = 0;
-	info.delta = _noDelta ? 0 : readVLQ2(_position._playPos);
+	info.delta = _noDelta ? 0 : readVLQ2(playPos);
 	info.noop = false;
 	_noDelta = false;
 
-	info.event = *_position._playPos++;
+	info.event = *playPos++;
 	if (!(info.event & 0x80)) {
 		_noDelta = true;
 		info.event |= 0x80;
@@ -109,13 +110,13 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 	} else {
 		switch (info.command()) {
 		case 0x8: // note off
-			info.basic.param1 = *_position._playPos++;
+			info.basic.param1 = *playPos++;
 			info.basic.param2 = 0;
 			break;
 
 		case 0x9: // note on
-			info.basic.param1 = *_position._playPos++;
-			info.basic.param2 = *_position._playPos++;
+			info.basic.param1 = *playPos++;
+			info.basic.param2 = *playPos++;
 			// Rewrite note on events with velocity 0 as note off events.
 			// This is the actual meaning of this, but theoretically this
 			// should not need to be rewritten, since all MIDI devices should
@@ -131,24 +132,24 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 			// In case the stop mode(?) is set to 0x80 this will stop the
 			// track over here.
 
-			const int16 loopIterations = int8(*_position._playPos++);
+			const int16 loopIterations = int8(*playPos++);
 			if (!loopIterations) {
-				_loops[info.channel()].start = _position._playPos;
+				_loops[info.channel()].start = playPos;
 				_loops[info.channel()].noDelta = _noDelta;
 			} else {
 				if (!_loops[info.channel()].timer) {
 					if (_loops[info.channel()].start) {
 						_loops[info.channel()].timer = uint16(loopIterations);
-						_loops[info.channel()].end = _position._playPos;
+						_loops[info.channel()].end = playPos;
 
 						// Go to the start of the loop
-						_position._playPos = _loops[info.channel()].start;
+						playPos = _loops[info.channel()].start;
 						_noDelta = _loops[info.channel()].noDelta;
 						info.loop = true;
 					}
 				} else {
 					if (_loops[info.channel()].timer) {
-						_position._playPos = _loops[info.channel()].start;
+						playPos = _loops[info.channel()].start;
 						_noDelta = _loops[info.channel()].noDelta;
 						info.loop = true;
 					}
@@ -168,13 +169,13 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 			break;
 
 		case 0xC: // program change
-			info.basic.param1 = *_position._playPos++;
+			info.basic.param1 = *playPos++;
 			info.basic.param2 = 0;
 			break;
 
 		case 0xD: // jump to loop end
 			if (_loops[info.channel()].end)
-				_position._playPos = _loops[info.channel()].end;
+				playPos = _loops[info.channel()].end;
 
 			// Event has been fully processed here.
 			info.noop = true;
@@ -190,6 +191,8 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 			break;
 		}
 	}
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 bool MidiParser_S1D::processEvent(const EventInfo &info, bool fireEvents) {
@@ -260,8 +263,9 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
 
 	// And now we're at the actual data. Only one track.
 	_numTracks = 1;
+	_numSubtracks[0] = 1;
 	_data = pos;
-	_tracks[0] = pos;
+	_tracks[0][0] = pos;
 
 	// Note that we assume the original data passed in
 	// will persist beyond this call, i.e. we do NOT
diff --git a/engines/agos/midiparser_simonwin.cpp b/engines/agos/midiparser_simonwin.cpp
index 1f2c823e394..37fb38dcd69 100644
--- a/engines/agos/midiparser_simonwin.cpp
+++ b/engines/agos/midiparser_simonwin.cpp
@@ -27,15 +27,10 @@
 namespace AGOS {
 
 MidiParser_SimonWin::MidiParser_SimonWin(int8 source, bool useDosTempos) :
-	MidiParser_SMF(source), _trackData(), _useDosTempos(useDosTempos) { }
-
-MidiParser_SimonWin::~MidiParser_SimonWin() {
-	// Call unloadMusic to make sure any _trackData contents are deallocated.
-	unloadMusic();
-}
+	MidiParser_SMF(source), _useDosTempos(useDosTempos) { }
 
 void MidiParser_SimonWin::parseNextEvent(EventInfo &info) {
-	byte *parsePos = _position._playPos;
+	byte *parsePos = _position._subtracks[info.subtrack]._playPos;
 	uint8 *start = parsePos;
 	uint32 delta = readVLQ(parsePos);
 	uint8 event = *(parsePos++);
@@ -53,7 +48,7 @@ void MidiParser_SimonWin::parseNextEvent(EventInfo &info) {
 		info.length = 0;
 		info.noop = true;
 
-		_position._playPos = parsePos;
+		_position._subtracks[info.subtrack]._playPos = parsePos;
 	} else {
 		// Processing of the other events is the same as the SMF format.
 		info.noop = false;
@@ -106,7 +101,7 @@ bool MidiParser_SimonWin::loadMusic(byte *data, uint32 size) {
 	// The first byte indicates the number of tracks in the MIDI data.
 	byte *pos = data;
 	_numTracks = *(pos++);
-	if (_numTracks > 16) {
+	if (_numTracks > MAXIMUM_TRACKS) {
 		warning("MidiParser_SimonWin::loadMusic - Can only handle %d tracks but was handed %d", MAXIMUM_TRACKS, _numTracks);
 		return false;
 	}
@@ -136,20 +131,21 @@ bool MidiParser_SimonWin::loadMusic(byte *data, uint32 size) {
 
 		// Verify that this MIDI is type 0 or 1 (it is expected to be type 1).
 		uint16 numSubtracks = pos[2] << 8 | pos[3];
-		assert(numSubtracks >= 1 && numSubtracks <= 20);
+		assert(numSubtracks >= 1 && numSubtracks <= MAXIMUM_SUBTRACKS);
 		uint8 subtrackMidiType = pos[1];
 		if (subtrackMidiType >= 2) {
 			warning("MidiParser_SimonWin::loadMusic - MIDI track contained a type %d subtrack", subtrackMidiType);
 			return false;
 		}
 
+		_numSubtracks[i] = numSubtracks;
+
 		// Each track could potentially have a different PPQN, but for Simon 1
 		// and 2 all tracks have PPQN 192, so this is not a problem.
 		_ppqn = pos[4] << 8 | pos[5];
 		pos += len;
 
 		// Now determine all the MTrk (sub)track start offsets.
-		byte *subtrackStarts[20];
 		for (int j = 0; j < numSubtracks; j++) {
 			if (memcmp(pos, "MTrk", 4) != 0) {
 				warning("MidiParser_SimonWin::loadMusic - Could not find subtrack header at expected location");
@@ -158,29 +154,12 @@ bool MidiParser_SimonWin::loadMusic(byte *data, uint32 size) {
 			pos += 4;
 			uint32 subtrackLength = READ_BE_UINT32(pos);
 			pos += 4;
-			subtrackStarts[j] = pos;
-			pos += subtrackLength;
-		}
 
-		// We are now at the end of the track, so we can determine the total
-		// track length.
-		uint32 trackDataLength = pos - trackDataStart;
-
-		if (subtrackMidiType == 1) {
-			// Compress the type 1 data to type 0.
-			byte *buffer = new byte[trackDataLength * 2];
-			uint32 compressedDataLength = compressToType0(subtrackStarts, numSubtracks, buffer, true);
-			// Copy the compressed data to the _trackData array.
-			_trackData[i] = new byte[compressedDataLength];
-			memcpy(_trackData[i], buffer, compressedDataLength);
-			delete[] buffer;
-
-			_tracks[i] = _trackData[i];
-		} else {
 			// Note that we assume the original data passed in
 			// will persist beyond this call, i.e. we do NOT
 			// copy the data to our own buffer. Take warning....
-			_tracks[i] = subtrackStarts[0];
+			_tracks[i][j] = pos;
+			pos += subtrackLength;
 		}
 	}
 
@@ -191,16 +170,4 @@ bool MidiParser_SimonWin::loadMusic(byte *data, uint32 size) {
 	return true;
 }
 
-void MidiParser_SimonWin::unloadMusic() {
-	MidiParser_SMF::unloadMusic();
-
-	// Deallocate the compressed type 0 track data.
-	for (int i = 0; i < MAXIMUM_TRACKS; i++) {
-		if (_trackData[i]) {
-			delete[] _trackData[i];
-			_trackData[i] = nullptr;
-		}
-	}
-}
-
 } // End of namespace AGOS
diff --git a/engines/agos/midiparser_simonwin.h b/engines/agos/midiparser_simonwin.h
index 3adb9916418..2c3c211a757 100644
--- a/engines/agos/midiparser_simonwin.h
+++ b/engines/agos/midiparser_simonwin.h
@@ -43,18 +43,14 @@ public:
 	int32 determineDataSize(Common::SeekableReadStream *stream) override;
 
 	MidiParser_SimonWin(int8 source = -1, bool useDosTempos = false);
-	~MidiParser_SimonWin();
 
 	void setTempo(uint32 tempo) override;
 
 	bool loadMusic(byte *data, uint32 size) override;
-	void unloadMusic() override;
 
 protected:
 	void parseNextEvent(EventInfo &info) override;
 
-	byte *_trackData[MAXIMUM_TRACKS];
-
 	bool _useDosTempos;
 };
 
diff --git a/engines/dgds/sound/midiparser_sci.cpp b/engines/dgds/sound/midiparser_sci.cpp
index 1ecf4eac30d..40d280e4bfc 100644
--- a/engines/dgds/sound/midiparser_sci.cpp
+++ b/engines/dgds/sound/midiparser_sci.cpp
@@ -91,7 +91,8 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in
 	midiMixChannels();
 
 	_numTracks = 1;
-	_tracks[0] = const_cast<byte *>(_mixedData->data());
+	_numSubtracks[0] = 1;
+	_tracks[0][0] = const_cast<byte *>(_mixedData->data());
 	if (_pSnd)
 		setTrack(0);
 	_loopTick = 0;
@@ -471,33 +472,37 @@ void MidiParser_SCI::trackState(uint32 b) {
 }
 
 void MidiParser_SCI::parseNextEvent(EventInfo &info) {
-	info.start = _position._playPos;
+	byte *playPos = _position._subtracks[0]._playPos;
+
+	info.start = playPos;
 	info.delta = 0;
-	while (*_position._playPos == 0xF8) {
+	while (*playPos == 0xF8) {
 		info.delta += 240;
-		_position._playPos++;
+		playPos++;
 	}
-	info.delta += *(_position._playPos++);
+	info.delta += *(playPos++);
 
 	// Process the next info.
-	if ((_position._playPos[0] & 0xF0) >= 0x80)
-		info.event = *(_position._playPos++);
+	if ((playPos[0] & 0xF0) >= 0x80)
+		info.event = *(playPos++);
 	else
-		info.event = _position._runningStatus;
-	if (info.event < 0x80)
+		info.event = _position._subtracks[0]._runningStatus;
+	if (info.event < 0x80) {
+		_position._subtracks[0]._playPos = playPos;
 		return;
+	}
 
-	_position._runningStatus = info.event;
+	_position._subtracks[0]._runningStatus = info.event;
 	switch (info.command()) {
 	case 0xC:
 	case 0xD:
-		info.basic.param1 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
 		info.basic.param2 = 0;
 		break;
 
 	case 0xB:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		info.length = 0;
 		break;
 
@@ -505,8 +510,8 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	case 0x9:
 	case 0xA:
 	case 0xE:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		if (info.command() == 0x9 && info.basic.param2 == 0) {
 			// NoteOn with param2==0 is a NoteOff
 			info.event = info.channel() | 0x80;
@@ -517,12 +522,12 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	case 0xF: // System Common, Meta or SysEx event
 		switch (info.event & 0x0F) {
 		case 0x2: // Song Position Pointer
-			info.basic.param1 = *(_position._playPos++);
-			info.basic.param2 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
+			info.basic.param2 = *(playPos++);
 			break;
 
 		case 0x3: // Song Select
-			info.basic.param1 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
 			info.basic.param2 = 0;
 			break;
 
@@ -536,16 +541,16 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 			break;
 
 		case 0x0: // SysEx
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 
 		case 0xF: // META event
-			info.ext.type = *(_position._playPos++);
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.ext.type = *(playPos++);
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 		default:
 			warning(
@@ -556,6 +561,8 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	default:
 		break;
 	}// switch (info.command())
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 bool MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) {
diff --git a/engines/parallaction/sound_br.cpp b/engines/parallaction/sound_br.cpp
index 36b44ef4b11..2ad0c1f687c 100644
--- a/engines/parallaction/sound_br.cpp
+++ b/engines/parallaction/sound_br.cpp
@@ -90,23 +90,28 @@ public:
 };
 
 void MidiParser_MSC::parseMetaEvent(EventInfo &info) {
-	uint8 type = read1(_position._playPos);
-	uint8 len = read1(_position._playPos);
+	byte *playPos = _position._subtracks[0]._playPos;
+
+	uint8 type = read1(playPos);
+	uint8 len = read1(playPos);
 	info.ext.type = type;
 	info.length = len;
 	info.ext.data = nullptr;
 
 	if (type == 0x51) {
-		info.ext.data = _position._playPos;
+		info.ext.data = playPos;
 	} else {
 		warning("unknown meta event 0x%02X", type);
 		info.ext.type = 0;
 	}
 
-	_position._playPos += len;
+	playPos += len;
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 void MidiParser_MSC::parseMidiEvent(EventInfo &info) {
+	byte *playPos = _position._subtracks[0]._playPos;
 	uint8 type = info.command();
 
 	switch (type) {
@@ -115,13 +120,13 @@ void MidiParser_MSC::parseMidiEvent(EventInfo &info) {
 	case 0xA:
 	case 0xB:
 	case 0xE:
-		info.basic.param1 = read1(_position._playPos);
-		info.basic.param2 = read1(_position._playPos);
+		info.basic.param1 = read1(playPos);
+		info.basic.param2 = read1(playPos);
 		break;
 
 	case 0xC:
 	case 0xD:
-		info.basic.param1 = read1(_position._playPos);
+		info.basic.param1 = read1(playPos);
 		info.basic.param2 = 0;
 		break;
 
@@ -130,13 +135,15 @@ void MidiParser_MSC::parseMidiEvent(EventInfo &info) {
 	}
 
 	//if ((type == 0xB) && (info.basic.param1 == 64)) info.basic.param2 = 127;
-
+	_position._subtracks[0]._playPos = playPos;
 }
 
 void MidiParser_MSC::parseNextEvent(EventInfo &info) {
-	info.start = _position._playPos;
+	byte *playPos = _position._subtracks[0]._playPos;
 
-	if (_position._playPos >= _trackEnd) {
+	info.start = playPos;
+
+	if (playPos >= _trackEnd) {
 		// fake an end-of-track meta event
 		info.delta = 0;
 		info.event = 0xFF;
@@ -146,22 +153,23 @@ void MidiParser_MSC::parseNextEvent(EventInfo &info) {
 	}
 
 	info.length = 0;
-	info.delta = readVLQ(_position._playPos);
-	info.event = read1(_position._playPos);
+	info.delta = readVLQ(playPos);
+	info.event = read1(playPos);
 
 	if (info.event == 0xFF) {
+		_position._subtracks[0]._playPos = playPos;
 		parseMetaEvent(info);
 		return;
 	}
 
 	if (info.event < 0x80) {
-		_position._playPos--;
+		playPos--;
 		info.event = _lastEvent;
 	}
 
+	_position._subtracks[0]._playPos = playPos;
 	parseMidiEvent(info);
 	_lastEvent = info.event;
-
 }
 
 bool MidiParser_MSC::loadMusic(byte *data, uint32 size) {
@@ -186,7 +194,8 @@ bool MidiParser_MSC::loadMusic(byte *data, uint32 size) {
 	_trackEnd = data + size;
 
 	_numTracks = 1;
-	_tracks[0] = pos;
+	_numSubtracks[0] = 1;
+	_tracks[0][0] = pos;
 
 	setTempo(500000);
 	setTrack(0);
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index 4f04bb3e2d3..4f5f26ac356 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -112,7 +112,8 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in
 	}
 
 	_numTracks = 1;
-	_tracks[0] = const_cast<byte *>(_mixedData->data());
+	_numSubtracks[0] = 1;
+	_tracks[0][0] = const_cast<byte *>(_mixedData->data());
 	if (_pSnd)
 		setTrack(0);
 	_loopTick = 0;
@@ -630,33 +631,37 @@ void MidiParser_SCI::trackState(uint32 b) {
 }
 
 void MidiParser_SCI::parseNextEvent(EventInfo &info) {
-	info.start = _position._playPos;
+	byte *playPos = _position._subtracks[0]._playPos;
+
+	info.start = playPos;
 	info.delta = 0;
-	while (*_position._playPos == 0xF8) {
+	while (*playPos == 0xF8) {
 		info.delta += 240;
-		_position._playPos++;
+		playPos++;
 	}
-	info.delta += *(_position._playPos++);
+	info.delta += *(playPos++);
 
 	// Process the next info.
-	if ((_position._playPos[0] & 0xF0) >= 0x80)
-		info.event = *(_position._playPos++);
+	if ((playPos[0] & 0xF0) >= 0x80)
+		info.event = *(playPos++);
 	else
-		info.event = _position._runningStatus;
-	if (info.event < 0x80)
+		info.event = _position._subtracks[0]._runningStatus;
+	if (info.event < 0x80) {
+		_position._subtracks[0]._playPos = playPos;
 		return;
+	}
 
-	_position._runningStatus = info.event;
+	_position._subtracks[0]._runningStatus = info.event;
 	switch (info.command()) {
 	case 0xC:
 	case 0xD:
-		info.basic.param1 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
 		info.basic.param2 = 0;
 		break;
 
 	case 0xB:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		info.length = 0;
 		break;
 
@@ -664,8 +669,8 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	case 0x9:
 	case 0xA:
 	case 0xE:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		if (info.command() == 0x9 && info.basic.param2 == 0) {
 			// NoteOn with param2==0 is a NoteOff
 			info.event = info.channel() | 0x80;
@@ -676,12 +681,12 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	case 0xF: // System Common, Meta or SysEx event
 		switch (info.event & 0x0F) {
 		case 0x2: // Song Position Pointer
-			info.basic.param1 = *(_position._playPos++);
-			info.basic.param2 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
+			info.basic.param2 = *(playPos++);
 			break;
 
 		case 0x3: // Song Select
-			info.basic.param1 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
 			info.basic.param2 = 0;
 			break;
 
@@ -695,16 +700,16 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 			break;
 
 		case 0x0: // SysEx
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 
 		case 0xF: // META event
-			info.ext.type = *(_position._playPos++);
-			info.length = readVLQ(_position._playPos);
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.ext.type = *(playPos++);
+			info.length = readVLQ(playPos);
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 		default:
 			warning(
@@ -715,6 +720,8 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	default:
 		break;
 	}// switch (info.command())
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 bool MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) {
diff --git a/engines/scumm/midiparser_ro.cpp b/engines/scumm/midiparser_ro.cpp
index d08eeb5efac..3125d111525 100644
--- a/engines/scumm/midiparser_ro.cpp
+++ b/engines/scumm/midiparser_ro.cpp
@@ -55,15 +55,17 @@ void MidiParser_RO::parseNextEvent (EventInfo &info) {
 	_markerCount += _lastMarkerCount;
 	_lastMarkerCount = 0;
 
+	byte *playPos = _position._subtracks[0]._playPos;
+
 	info.delta = 0;
 	do {
-		info.start = _position._playPos;
-		info.event = *(_position._playPos++);
+		info.start = playPos;
+		info.event = *(playPos++);
 		if (info.command() == 0xA) {
 			++_lastMarkerCount;
 			info.event = 0xF0;
 		} else if (info.event == 0xF0 || info.event == 0xF1) {
-			byte delay = *(_position._playPos++);
+			byte delay = *(playPos++);
 			info.delta += delay;
 			if (info.event == 0xF1) {
 				// This event is, as far as we have been able
@@ -78,6 +80,8 @@ void MidiParser_RO::parseNextEvent (EventInfo &info) {
 		break;
 	} while (true);
 
+	_position._subtracks[0]._playPos = playPos;
+
 	// Seems to indicate EOT
 	if (info.event == 0) {
 		info.event = 0xFF;
@@ -90,16 +94,16 @@ void MidiParser_RO::parseNextEvent (EventInfo &info) {
 	if (info.event < 0x80)
 		return;
 
-	_position._runningStatus = info.event;
+	_position._subtracks[0]._runningStatus = info.event;
 	switch (info.command()) {
 	case 0xC:
-		info.basic.param1 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
 		info.basic.param2 = 0;
 		break;
 
 	case 0x8: case 0x9: case 0xB:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		if (info.command() == 0x9 && info.basic.param2 == 0)
 			info.event = info.channel() | 0x80;
 		info.length = 0;
@@ -120,6 +124,8 @@ void MidiParser_RO::parseNextEvent (EventInfo &info) {
 	default:
 		break;
 	}
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 bool MidiParser_RO::loadMusic (byte *data, uint32 size) {
@@ -132,9 +138,10 @@ bool MidiParser_RO::loadMusic (byte *data, uint32 size) {
 	}
 
 	_numTracks = 1;
+	_numSubtracks[0] = 1;
 	_autoLoop = false;
 	_ppqn = 120;
-	_tracks[0] = pos + 2;
+	_tracks[0][0] = pos + 2;
 	_markerCount = _lastMarkerCount = 0;
 
 	// Note that we assume the original data passed in
diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp
index 8f5c16c50ea..388d6a93971 100644
--- a/engines/sherlock/music.cpp
+++ b/engines/sherlock/music.cpp
@@ -79,35 +79,37 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
 
 //	warning("parseNextEvent");
 
+	byte *playPos = _position._subtracks[0]._playPos;
+
 	// there is no delta right at the start of the music data
 	// this order is essential, otherwise notes will get delayed or even go missing
-	if (_position._playPos != _tracks[0]) {
-		info.delta = *(_position._playPos++);
+	if (playPos != _tracks[0][0]) {
+		info.delta = *(playPos++);
 	} else {
 		info.delta = 0;
 	}
 
-	info.start = _position._playPos;
+	info.start = playPos;
 
-	info.event = *_position._playPos++;
+	info.event = *(playPos++);
 	//warning("Event %x", info.event);
-	_position._runningStatus = info.event;
+	_position._subtracks[0]._runningStatus = info.event;
 
 	switch (info.command()) {
 	case 0xC: { // program change
-		int idx = *_position._playPos++;
+		int idx = *playPos++;
 		info.basic.param1 = idx & 0x7f;
 		info.basic.param2 = 0;
 		}
 		break;
 	case 0xD:
-		info.basic.param1 = *_position._playPos++;
+		info.basic.param1 = *(playPos++);
 		info.basic.param2 = 0;
 		break;
 
 	case 0xB:
-		info.basic.param1 = *_position._playPos++;
-		info.basic.param2 = *_position._playPos++;
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		info.length = 0;
 		break;
 
@@ -115,8 +117,8 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
 	case 0x9:
 	case 0xA:
 	case 0xE:
-		info.basic.param1 = *(_position._playPos++);
-		info.basic.param2 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
+		info.basic.param2 = *(playPos++);
 		if (info.command() == 0x9 && info.basic.param2 == 0) {
 			// NoteOn with param2==0 is a NoteOff
 			info.event = info.channel() | 0x80;
@@ -127,7 +129,7 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
 		if (info.event == 0xFF) {
 			error("SysEx META event 0xFF");
 
-			byte type = *(_position._playPos++);
+			byte type = *(playPos++);
 			switch(type) {
 			case 0x2F:
 				// End of Track
@@ -137,7 +139,7 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
 				return;
 			case 0x51:
 				warning("TODO: 0xFF / 0x51");
-				return;
+				break;
 			default:
 				warning("TODO: 0xFF / %x Unknown", type);
 				break;
@@ -146,17 +148,17 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
 			// Official End-Of-Track signal
 			debugC(kDebugLevelMusic, "Music: System META event 0xFC");
 
-			byte type = *(_position._playPos++);
+			byte type = *(playPos++);
 			switch (type) {
 			case 0x80: // end of track, triggers looping
 				debugC(kDebugLevelMusic, "Music: META event triggered looping");
 				jumpToTick(0, true, true, false);
-				break;
+				return;
 			case 0x81: // end of track, stop playing
 				debugC(kDebugLevelMusic, "Music: META event triggered music stop");
 				stopPlaying();
 				unloadMusic();
-				break;
+				return;
 			default:
 				error("MidiParser_SH::parseNextEvent: Unknown META event 0xFC type %x", type);
 				break;
@@ -170,6 +172,8 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) {
 		warning("MidiParser_SH::parseNextEvent: Unsupported event code %x", info.event);
 		break;
 	}// switch (info.command())
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) {
@@ -194,7 +198,8 @@ bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) {
 	_trackEnd = _musData + _musDataSize;
 
 	_numTracks = 1;
-	_tracks[0] = pos;
+	_numSubtracks[0] = 1;
+	_tracks[0][0] = pos;
 
 	_ppqn = 1;
 	setTempo(16667);
diff --git a/engines/ultima/nuvie/sound/midiparser_m.cpp b/engines/ultima/nuvie/sound/midiparser_m.cpp
index 1813f329181..2a7b7ed8872 100644
--- a/engines/ultima/nuvie/sound/midiparser_m.cpp
+++ b/engines/ultima/nuvie/sound/midiparser_m.cpp
@@ -45,8 +45,9 @@ bool MidiParser_M::loadMusic(byte* data, uint32 size) {
 	unloadMusic();
 
 	// M uses only 1 track.
-	_tracks[0] = data;
+	_tracks[0][0] = data;
 	_numTracks = 1;
+	_numSubtracks[0] = 1;
 	_trackLength = size;
 
 	// The global loop defaults to the start of the M data.
@@ -71,8 +72,9 @@ void MidiParser_M::unloadMusic() {
 void MidiParser_M::onTimer() {
 	uint32 endTime;
 	uint32 eventTime;
+	uint32 eventTick;
 
-	if (!_position._playPos || !_driver || !_doParse || _pause || !_driver->isReady(_source))
+	if (!_position.isTracking() || !_driver || !_doParse || _pause || !_driver->isReady(_source))
 		return;
 
 	_abortParse = false;
@@ -80,9 +82,10 @@ void MidiParser_M::onTimer() {
 
 	bool loopEvent = false;
 	while (!_abortParse) {
-		EventInfo &info = _nextEvent;
+		EventInfo &info = *_nextEvent;
 
-		eventTime = _position._lastEventTime + info.delta * _psecPerTick;
+		eventTick = _position._subtracks[0]._lastEventTick + info.delta;
+		eventTime = _position._lastEventTime + (eventTick - _position._lastEventTick) * _psecPerTick;
 		if (eventTime > endTime)
 			break;
 
@@ -96,56 +99,64 @@ void MidiParser_M::onTimer() {
 		loopEvent |= info.loop;
 
 		if (!_abortParse) {
+			_position._playTime = eventTime;
 			_position._lastEventTime = eventTime;
-			_position._lastEventTick += info.delta;
-			parseNextEvent(_nextEvent);
+			_position._subtracks[0]._lastEventTime = eventTime;
+
+			_position._playTick = eventTick;
+			_position._lastEventTick = eventTick;
+			_position._subtracks[0]._lastEventTick = eventTick;
+
+			if (_position.isTracking(0)) {
+				parseNextEvent(_nextSubtrackEvents[0]);
+			}
+			determineNextEvent();
 		}
 	}
 
 	if (!_abortParse) {
 		_position._playTime = endTime;
-		_position._playTick = (_position._playTime - _position._lastEventTime) / _psecPerTick + _position._lastEventTick;
+		_position._playTick = (endTime - _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;
+			rebaseTracking();
 		}
 	}
 }
 
 bool MidiParser_M::processEvent(const EventInfo& info, bool fireEvents) {
+	byte *playPos = _position._subtracks[0]._playPos;
+
 	if (info.command() == 0x8 && info.channel() == 0x1) {
 		// Call subroutine
 		LoopData loopData { };
-		loopData.returnPos = _position._playPos;
+		loopData.returnPos = playPos;
 		loopData.numLoops = info.ext.data[0];
 		uint16 startOffset = READ_LE_UINT16(info.ext.data + 1);
 		assert(startOffset < _trackLength);
-		loopData.startPos = _tracks[0] + startOffset;
+		loopData.startPos = _tracks[0][0] + startOffset;
 		_loopStack->push(loopData);
-		_position._playPos = loopData.startPos;
+		playPos = loopData.startPos;
 	} else if (info.command() == 0xE) {
 		// Set loop point
-		_loopPoint = _position._playPos;
+		_loopPoint = playPos;
 	} else if (info.command() == 0xF) {
 		// Return
 		if (_loopStack->empty()) {
 			// Global loop: return to the global loop point
-			_position._playPos = _loopPoint;
+			playPos = _loopPoint;
 		} else {
 			// Subroutine loop
 			LoopData *loopData = &_loopStack->top();
 			if (loopData->numLoops > 1) {
 				// Return to the start of the subroutine data
 				loopData->numLoops--;
-				_position._playPos = loopData->startPos;
+				playPos = loopData->startPos;
 			} else {
 				// Return to the call position
-				_position._playPos = loopData->returnPos;
+				playPos = loopData->returnPos;
 				_loopStack->pop();
 			}
 		}
@@ -160,13 +171,17 @@ bool MidiParser_M::processEvent(const EventInfo& info, bool fireEvents) {
 		sendToDriver(info.event, info.basic.param1, info.basic.param2);
 	}
 
+	_position._subtracks[0]._playPos = playPos;
+
 	return true;
 }
 
 void MidiParser_M::parseNextEvent(EventInfo &info) {
-	assert(_position._playPos - _tracks[0] < _trackLength);
-	info.start = _position._playPos;
-	info.event = *(_position._playPos++);
+	byte *playPos = _position._subtracks[0]._playPos;
+	assert(playPos - _tracks[0][0] < _trackLength);
+
+	info.start = playPos;
+	info.event = *(playPos++);
 	info.delta = 0;
 	info.basic.param1 = 0;
 	info.basic.param2 = 0;
@@ -183,7 +198,7 @@ void MidiParser_M::parseNextEvent(EventInfo &info) {
 	case 0x6: // Set vibrato
 	case 0x7: // Program change
 		// These commands all have 1 data byte.
-		info.basic.param1 = *(_position._playPos++);
+		info.basic.param1 = *(playPos++);
 		break;
 
 	case 0x8: // Subcommand
@@ -193,13 +208,13 @@ void MidiParser_M::parseNextEvent(EventInfo &info) {
 			// offset in the M data to jump to (2 bytes).
 			info.ext.type = info.channel();
 			info.length = 3;
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 		case 0x2: // Delay
 			// This command is used to specify a delta time between the
 			// previous and the next event. It does nothing otherwise.
-			info.delta = *(_position._playPos++);
+			info.delta = *(playPos++);
 			info.noop = true;
 			break;
 		case 0x3: // Load instrument
@@ -208,13 +223,13 @@ void MidiParser_M::parseNextEvent(EventInfo &info) {
 			// definition (11 bytes).
 			info.ext.type = info.channel();
 			info.length = 12;
-			info.ext.data = _position._playPos;
-			_position._playPos += info.length;
+			info.ext.data = playPos;
+			playPos += info.length;
 			break;
 		case 0x5: // Fade out
 		case 0x6: // Fade in
 			// These commands have 1 data byte.
-			info.basic.param1 = *(_position._playPos++);
+			info.basic.param1 = *(playPos++);
 			break;
 		default:
 			info.noop = true;
@@ -235,6 +250,8 @@ void MidiParser_M::parseNextEvent(EventInfo &info) {
 		info.noop = true;
 		break;
 	}
+
+	_position._subtracks[0]._playPos = playPos;
 }
 
 void MidiParser_M::allNotesOff() {




More information about the Scummvm-git-logs mailing list