[Scummvm-git-logs] scummvm master -> 62387f8d380f7d76a1514b72377e904c011a4642

athrxx athrxx at scummvm.org
Fri May 29 15:50:18 UTC 2020


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:
62387f8d38 MIDI/KYRA: Miles channel locking and GM support (#2273)


Commit: 62387f8d380f7d76a1514b72377e904c011a4642
    https://github.com/scummvm/scummvm/commit/62387f8d380f7d76a1514b72377e904c011a4642
Author: NMIError (60350957+NMIError at users.noreply.github.com)
Date: 2020-05-29T17:50:14+02:00

Commit Message:
MIDI/KYRA: Miles channel locking and GM support (#2273)

* MIDI/KYRA: Add channel locking and GM to XMIDI

This improves support for XMIDI channel locking and moves it from the KYRA
engine to the generic Miles MIDI driver. To support this, a source parameter
is added to the XMIDI parser and Miles driver so the driver can distinguish
several XMIDI parser instances sending MIDI events. I've also added GM support
to the generic Miles MIDI driver.

The previous implementation of channel locking did not always track and restore
controller values properly when unlocking a channel. Specifically, at the start
of Legend of Kyrandia, when Brandon talks to the tree, the tree "creaking"
sound effect uses some channels from the background music. The volume of the
music channels was not correctly restored, resulting in some instruments being
much louder then others.
Another issue was that volume was not always properly set when locking a
channel. In the Legend of Kyrandia intro, when Brandon is lifted up to the
house, some sound effects were missing because MIDI channel volume was 0.
This new implementation fixes these issues.

* MIDI: Suppress Miles controller warnings

Several Miles controller MIDI messages generate warnings in the XMIDI parser,
even though they are handled by the Miles drivers. I've removed these.

* MIDI: Fix Codacy issues

Changed paths:
  A audio/miles_midi.cpp
  R audio/miles_mt32.cpp
  R engines/kyra/sound/drivers/midi.cpp
  R engines/kyra/sound/drivers/midi.h
    audio/mididrv.cpp
    audio/mididrv.h
    audio/midiparser.cpp
    audio/midiparser.h
    audio/midiparser_xmidi.cpp
    audio/miles.h
    audio/module.mk
    engines/kyra/engine/kyra_v1.cpp
    engines/kyra/module.mk
    engines/kyra/sound/sound_intern.h
    engines/kyra/sound/sound_pc_midi.cpp


diff --git a/audio/mididrv.cpp b/audio/mididrv.cpp
index 54be3d0d2b..005fb9f06d 100644
--- a/audio/mididrv.cpp
+++ b/audio/mididrv.cpp
@@ -57,6 +57,22 @@ const byte MidiDriver::_gmToMt32[128] = {
 	101, 103, 100, 120, 117, 113,  99, 128, 128, 128, 128, 124, 123, 128, 128, 128, // 7x
 };
 
+// These are the power-on default instruments of the Roland MT-32 family.
+const byte MidiDriver::_mt32DefaultInstruments[8] = {
+	0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A
+};
+
+// These are the power-on default panning settings for channels 2-9 of the Roland MT-32 family.
+// Internally, the MT-32 has 15 panning positions (0-E with 7 being center).
+// This has been translated to the equivalent MIDI panning values (0-127).
+// These are used for setting default panning on GM devices when using them with MT-32 data.
+// Note that MT-32 panning is reversed compared to the MIDI specification. This is not reflected
+// here; the driver is expected to flip these values based on the _reversePanning variable.
+const byte MidiDriver::_mt32DefaultPanning[8] = {
+	// 7,    8,    7,    8,    4,    A,    0,    E 
+	0x40, 0x49,	0x40, 0x49, 0x25, 0x5B, 0x00, 0x7F
+};
+
 // This is the drum map for the Roland Sound Canvas SC-55 v1.xx. It had a fallback mechanism 
 // to correct invalid drumkit selections. Some games rely on this mechanism to select the 
 // correct Roland GS drumkit. Use this map to emulate this mechanism.
@@ -431,18 +447,186 @@ MidiDriver::DeviceHandle MidiDriver::getDeviceHandle(const Common::String &ident
 	return 0;
 }
 
+void MidiDriver::initMT32(bool initForGM) {
+	sendMT32Reset();
+
+	if (initForGM) {
+		// Set up MT-32 for GM data.
+		// This is based on Roland's GM settings for MT-32.
+		debug("Initializing MT-32 for General MIDI data");
+
+		byte buffer[17];
+
+		// Roland MT-32 SysEx for system area
+		memcpy(&buffer[0], "\x41\x10\x16\x12\x10\x00", 6);
+
+		// Set reverb parameters:
+		// - Mode 2 (Plate)
+		// - Time 3
+		// - Level 4
+		memcpy(&buffer[6], "\x01\x02\x03\x04\x66", 5);
+		sysEx(buffer, 11);
+
+		// Set partial reserve to match SC-55
+		memcpy(&buffer[6], "\x04\x08\x04\x04\x03\x03\x03\x03\x02\x02\x4C", 11);
+		sysEx(buffer, 17);
+
+		// Use MIDI instrument channels 1-8 instead of 2-9
+		memcpy(&buffer[6], "\x0D\x00\x01\x02\x03\x04\x05\x06\x07\x09\x3E", 11);
+		sysEx(buffer, 17);
+
+		// The MT-32 has reversed stereo panning compared to the MIDI spec.
+		// GM does use panning as specified by the MIDI spec.
+		_reversePanning = true;
+
+		int i;
+
+		// Set default GM panning (center on all channels)
+		for (i = 0; i < 8; ++i) {
+			send((0x40 << 16) | (10 << 8) | (0xB0 | i));
+		}
+
+		// Set default GM instruments (0 on all channels).
+		// This is expected to be mapped to the MT-32 equivalent by the driver.
+		for (i = 0; i < 8; ++i) {
+			send((0 << 8) | (0xC0 | i));
+		}
+
+		// Set Pitch Bend Sensitivity to 2 semitones.
+		for (i = 0; i < 8; ++i) {
+			setPitchBendRange(i, 2);
+		}
+		setPitchBendRange(9, 2);
+	}
+}
+
 void MidiDriver::sendMT32Reset() {
 	static const byte resetSysEx[] = { 0x41, 0x10, 0x16, 0x12, 0x7F, 0x00, 0x00, 0x01, 0x00 };
 	sysEx(resetSysEx, sizeof(resetSysEx));
 	g_system->delayMillis(100);
 }
 
+void MidiDriver::initGM(bool initForMT32, bool enableGS) {
+	sendGMReset();
+
+	if (initForMT32) {
+		// Set up the GM device for MT-32 MIDI data.
+		// Based on iMuse implementation (which is based on Roland's MT-32 settings for GS)
+		debug("Initializing GM device for MT-32 MIDI data");
+
+		// The MT-32 has reversed stereo panning compared to the MIDI spec.
+		// GM does use panning as specified by the MIDI spec.
+		_reversePanning = true;
+
+		int i;
+
+		// Set the default panning for the MT-32 instrument channels.
+		for (i = 1; i < 9; ++i) {
+			send((_mt32DefaultPanning[i - 1] << 16) | (10 << 8) | (0xB0 | i));
+		}
+
+		// Set Channels 1-16 Reverb to 64, which is the
+		// equivalent of MT-32 default Reverb Level 5
+		for (i = 0; i < 16; ++i)
+			send((64 << 16) | (91 << 8) | (0xB0 | i));
+
+		// Set Channels 1-16 Chorus to 0. The MT-32 has no chorus capability.
+		// (This is probably the default for many GM devices with chorus anyway.)
+		for (i = 0; i < 16; ++i)
+			send((0 << 16) | (93 << 8) | (0xB0 | i));
+
+		// Set Channels 1-16 Pitch Bend Sensitivity to 12 semitones.
+		for (i = 0; i < 16; ++i) {
+			setPitchBendRange(i, 12);
+		}
+
+		if (enableGS) {
+			// GS specific settings for MT-32 instrument mapping.
+			debug("Additional initialization of GS device for MT-32 MIDI data");
+
+			// Note: All Roland GS devices support CM-64/32L maps
+
+			// Set Percussion Channel to SC-55 Map (CC#32, 01H), then
+			// Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
+			// Bank select MSB: bank 0
+			getPercussionChannel()->controlChange(0, 0);
+			// Bank select LSB: map 1 (SC-55)
+			getPercussionChannel()->controlChange(32, 1);
+			// Patch change: 127 (CM-64/32L)
+			send(127 << 8 | 0xC0 | 9);
+
+			// Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
+			for (i = 0; i < 16; ++i) {
+				if (i == getPercussionChannel()->getNumber())
+					continue;
+				// Bank select MSB: bank 127 (CM-64/32L)
+				send((127 << 16) | (0 << 8) | (0xB0 | i));
+				// Bank select LSB: map 1 (SC-55)
+				send((1 << 16) | (32 << 8) | (0xB0 | i));
+				// Patch change: 0 (causes bank select to take effect)
+				send((0 << 16) | (0 << 8) | (0xC0 | i));
+			}
+
+			byte buffer[12];
+
+			// Roland GS SysEx ID
+			memcpy(&buffer[0], "\x41\x10\x42\x12", 4);
+
+			// Set channels 1-16 Mod. LFO1 Pitch Depth to 4
+			memcpy(&buffer[4], "\x40\x20\x04\x04\x18", 5);
+			for (i = 0; i < 16; ++i) {
+				buffer[5] = 0x20 + i;
+				buffer[8] = 0x18 - i;
+				sysEx(buffer, 9);
+			}
+
+			// In Roland's GS MT-32 emulation settings, percussion channel expression
+			// is locked at 80. This corrects a difference in volume of the SC-55 MT-32
+			// drum kit vs the drums of the MT-32. However, this approach has a problem:
+			// the MT-32 supports expression on the percussion channel, so MIDI data
+			// which uses this will play incorrectly. So instead, percussion channel
+			// volume will be scaled by the driver by a factor 80/127.
+			// Strangely, the regular GM drum kit does have a volume that matches the
+			// MT-32 drums, so scaling is only necessary when using GS MT-32 emulation.
+			_scaleGSPercussionVolumeToMT32 = true;
+
+			// Change Reverb settings (as used by Roland):
+			// - Character: 0
+			// - Pre-LPF: 4
+			// - Level: 35h
+			// - Time: 6Ah
+			memcpy(&buffer[4], "\x40\x01\x31\x00\x04\x35\x6A\x6B", 8);
+			sysEx(buffer, 12);
+		}
+
+		// Set the default MT-32 patches. For non-GS devices these are expected to be
+		// mapped to the GM equivalents by the driver.
+		for (i = 1; i < 9; ++i) {
+			send((_mt32DefaultInstruments[i - 1] << 8) | (0xC0 | i));
+		}
+
+		// Regarding Master Tune: 442 kHz was intended for the MT-32 family, but
+		// apparently due to a firmware bug the master tune was actually 440 kHz for
+		// all models (see MUNT source code for more details). So master tune is left
+		// at 440 kHz for GM devices playing MT-32 MIDI data.
+	}
+}
+
 void MidiDriver::sendGMReset() {
-	static const byte resetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 };
-	sysEx(resetSysEx, sizeof(resetSysEx));
+	static const byte gmResetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 };
+	sysEx(gmResetSysEx, sizeof(gmResetSysEx));
 	g_system->delayMillis(100);
-}
 
+	// Send a Roland GS reset. This will be ignored by pure GM units,
+	// but will enable certain GS features on units that support them.
+	// This is especially useful for some Yamaha units, which are put
+	// in XG mode after a GM reset, which has some compatibility
+	// problems with GS features like instrument banks and
+	// GS-exclusive drum sounds.
+	static const byte gsResetSysEx[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41 };
+	sysEx(gsResetSysEx, sizeof(gsResetSysEx));
+	g_system->delayMillis(100);
+}
 
 void MidiDriver_BASE::midiDumpInit() {
 	g_system->displayMessageOnOSD(_("Starting MIDI dump"));
@@ -554,6 +738,10 @@ void MidiDriver_BASE::send(byte status, byte firstOp, byte secondOp) {
 	send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
 }
 
+void MidiDriver_BASE::send(int8 source, byte status, byte firstOp, byte secondOp) {
+	send(source, status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
+}
+
 void MidiDriver::midiDriverCommonSend(uint32 b) {
 	if (_midiDumpEnable) {
 		midiDumpDo(b);
diff --git a/audio/mididrv.h b/audio/mididrv.h
index e6a2f65ede..9f7383e3ab 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -99,6 +99,12 @@ public:
 	 */
 	virtual void send(uint32 b) = 0;
 
+	/**
+	 * Send a MIDI command from a specific source. If the MIDI driver
+	 * does not support multiple sources, the source parameter is
+	 * ignored.
+	 */
+	virtual void send(int8 source, uint32 b) { send(b); }
 
 	/**
 	 * Output a midi command to the midi stream. Convenience wrapper
@@ -109,6 +115,13 @@ public:
 	 */
 	void send(byte status, byte firstOp, byte secondOp);
 	
+	/**
+	 * Send a MIDI command from a specific source. If the MIDI driver
+	 * does not support multiple sources, the source parameter is
+	 * ignored.
+	 */
+	void send(int8 source, byte status, byte firstOp, byte secondOp);
+
 	/**
 	 * Transmit a sysEx to the midi device.
 	 *
@@ -124,6 +137,12 @@ public:
 	// TODO: Document this.
 	virtual void metaEvent(byte type, byte *data, uint16 length) { }
 
+	/**
+	 * Send a meta event from a specific source. If the MIDI driver
+	 * does not support multiple sources, the source parameter is
+	 * ignored.
+	 */
+	virtual void metaEvent(int8 source, byte type, byte *data, uint16 length) { metaEvent(type, data, length); }
 protected:
 
 	/**
@@ -207,6 +226,11 @@ public:
 	/** Common operations to be done by all drivers on start of sysEx */
 	void midiDriverCommonSysEx(const byte *msg, uint16 length);
 
+protected:
+	// True if stereo panning should be reversed.
+	bool _reversePanning;
+	// True if GS percussion channel volume should be scaled to match MT-32 volume.
+	bool _scaleGSPercussionVolumeToMT32;
 
 private:
 	// If detectDevice() detects MT32 and we have a preferred MT32 device
@@ -217,10 +241,14 @@ private:
 	static bool _forceTypeMT32;
 
 public:
+	MidiDriver() : _reversePanning(false),
+					_scaleGSPercussionVolumeToMT32(false) { }
 	virtual ~MidiDriver() { }
 
 	static const byte _mt32ToGm[128];
 	static const byte _gmToMt32[128];
+	static const byte _mt32DefaultInstruments[8];
+	static const byte _mt32DefaultPanning[8];
 	// Map for correcting Roland GS drumkit numbers.
 	static const uint8 _gsDrumkitFallbackMap[128];
 
@@ -273,11 +301,28 @@ public:
 		send(0xB0 | channel, 100, 127);
 	}
 
+	/**
+	 * Initializes the MT-32 MIDI device. The device will be reset and, 
+	 * if the parameter is specified, set up for General MIDI data.
+	 * @param initForGM True if the MT-32 should be initialized for GM mapping
+	 */
+	void initMT32(bool initForGM);
+
 	/**
 	 * Send a Roland MT-32 reset sysEx to the midi device.
 	 */
 	void sendMT32Reset();
 
+	/**
+	 * Initializes the General MIDI device. The device will be reset.
+	 * If the initForMT32 parameter is specified, the device will be set up for
+	 * MT-32 MIDI data. If the device supports Roland GS, the enableGS
+	 * parameter can be specified for enhanced GS MT-32 compatiblity.
+	 * @param initForMT32 True if the device should be initialized for MT-32 mapping
+	 * @param enableGS True if the device should be initialized for GS MT-32 mapping
+	 */
+	void initGM(bool initForMT32, bool enableGS);
+
 	/**
 	 * Send a General MIDI reset sysEx to the midi device.
 	 */
diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp
index 4731d1f982..063547265a 100644
--- a/audio/midiparser.cpp
+++ b/audio/midiparser.cpp
@@ -42,6 +42,7 @@ _autoLoop(false),
 _smartJump(false),
 _centerPitchWheelOnUnload(false),
 _sendSustainOffOnNotesOff(false),
+_disableAllNotesOffMidiEvents(false),
 _numTracks(0),
 _activeTrack(255),
 _abortParse(false),
@@ -68,6 +69,9 @@ void MidiParser::property(int prop, int value) {
 	case mpSendSustainOffOnNotesOff:
 		_sendSustainOffOnNotesOff = (value != 0);
 		break;
+	case mpDisableAllNotesOffMidiEvents:
+		_disableAllNotesOffMidiEvents = (value != 0);
+		break;
 	default:
 		break;
 	}
@@ -77,6 +81,10 @@ void MidiParser::sendToDriver(uint32 b) {
 	_driver->send(b);
 }
 
+void MidiParser::sendMetaEventToDriver(byte type, byte *data, uint16 length) {
+	_driver->metaEvent(type, data, length);
+}
+
 void MidiParser::setTempo(uint32 tempo) {
 	_tempo = tempo;
 	if (_ppqn)
@@ -255,7 +263,7 @@ bool MidiParser::processEvent(const EventInfo &info, bool fireEvents) {
 			} else {
 				stopPlaying();
 				if (fireEvents)
-					_driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
+					sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
 			}
 			return false;
 		} else if (info.ext.type == 0x51) {
@@ -264,7 +272,7 @@ bool MidiParser::processEvent(const EventInfo &info, bool fireEvents) {
 			}
 		}
 		if (fireEvents)
-			_driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
+			sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
 	} else {
 		if (fireEvents)
 			sendToDriver(info.event, info.basic.param1, info.basic.param2);
@@ -298,13 +306,15 @@ void MidiParser::allNotesOff() {
 	}
 	_hangingNotesCount = 0;
 
-	// To be sure, send an "All Note Off" event (but not all MIDI devices
-	// support this...).
+	if (!_disableAllNotesOffMidiEvents) {
+		// To be sure, send an "All Note Off" event (but not all MIDI devices
+		// support this...).
 
-	for (i = 0; i < 16; ++i) {
-		sendToDriver(0xB0 | i, 0x7b, 0); // All notes off
-		if (_sendSustainOffOnNotesOff)
-			sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608)
+		for (i = 0; i < 16; ++i) {
+			sendToDriver(0xB0 | i, 0x7b, 0); // All notes off
+			if (_sendSustainOffOnNotesOff)
+				sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608)
+		}
 	}
 
 	memset(_activeNotes, 0, sizeof(_activeNotes));
diff --git a/audio/midiparser.h b/audio/midiparser.h
index 2cca56b14c..ff3cc987c0 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -278,6 +278,7 @@ protected:
 	bool   _smartJump;      ///< Support smart expiration of hanging notes when jumping
 	bool   _centerPitchWheelOnUnload;  ///< Center the pitch wheels when unloading a song
 	bool   _sendSustainOffOnNotesOff;   ///< Send a sustain off on a notes off event, stopping hanging notes
+	bool   _disableAllNotesOffMidiEvents;   ///< Don't send All Notes Off MIDI messages
 	byte  *_tracks[120];    ///< Multi-track MIDI formats are supported, up to 120 tracks.
 	byte   _numTracks;     ///< Count of total tracks for multi-track MIDI formats. 1 for single-track formats.
 	byte   _activeTrack;   ///< Keeps track of the currently active track, in multi-track formats.
@@ -304,6 +305,7 @@ protected:
 	void sendToDriver(byte status, byte firstOp, byte secondOp) {
 		sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
 	}
+	virtual void sendMetaEventToDriver(byte type, byte *data, uint16 length);
 
 	/**
 	 * Platform independent BE uint32 read-and-advance.
@@ -365,7 +367,17 @@ public:
 		 * Sends a sustain off event when a notes off event is triggered.
 		 * Stops hanging notes.
 		 */
-		 mpSendSustainOffOnNotesOff = 5
+		 mpSendSustainOffOnNotesOff = 5,
+
+		 /**
+		  * Prevent sending out all notes off events on all channels when
+		  * playback of a track is stopped. This option is useful when
+		  * multiple sources are used; otherwise stopping playback of one
+		  * source will interrupt playback of the other sources.
+		  * Any active notes registered by this parser will still be turned
+		  * off.
+		  */
+		 mpDisableAllNotesOffMidiEvents = 6
 	};
 
 public:
@@ -396,7 +408,7 @@ public:
 	static void defaultXMidiCallback(byte eventData, void *refCon);
 
 	static MidiParser *createParser_SMF();
-	static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, XMidiNewTimbreListProc newTimbreListProc = NULL, MidiDriver_BASE *newTimbreListDriver = NULL);
+	static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, XMidiNewTimbreListProc newTimbreListProc = NULL, MidiDriver_BASE *newTimbreListDriver = NULL, int source = -1);
 	static MidiParser *createParser_QT();
 	static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
 };
diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp
index cea42addc1..0e421d5c4b 100644
--- a/audio/midiparser_xmidi.cpp
+++ b/audio/midiparser_xmidi.cpp
@@ -21,6 +21,7 @@
  */
 
 #include "audio/midiparser.h"
+#include "audio/mididrv.h"
 #include "common/textconsole.h"
 #include "common/util.h"
 
@@ -40,6 +41,15 @@ protected:
 	Loop _loop[4];
 	int _loopCount;
 
+	/**
+	 * The source number to use when sending MIDI messages to the driver.
+	 * When using multiple sources, use source 0 and higher. This must be
+	 * used when source volume or channel locking is used.
+	 * By default this is -1, which means the parser is the only source
+	 * of MIDI messages and multiple source functionality is disabled.
+	 */
+	int8 _source;
+
 	XMidiCallbackProc _callbackProc;
 	void *_callbackData;
 
@@ -68,17 +78,21 @@ protected:
 		_loopCount = -1;
 	}
 
+	void sendToDriver(uint32 b) override;
+	void sendMetaEventToDriver(byte type, byte *data, uint16 length) override;
 public:
-	MidiParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver) {
-		_callbackProc = proc;
-		_callbackData = data;
-		_loopCount = -1;
-		_newTimbreListProc = newTimbreListProc;
-		_newTimbreListDriver = newTimbreListDriver;
+	MidiParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver, int8 source = -1) :
+			_callbackProc(proc),
+			_callbackData(data),
+			_newTimbreListProc(newTimbreListProc),
+			_newTimbreListDriver(newTimbreListDriver),
+			_source(source),
+			_loopCount(-1),
+			_activeTrackTimbreList(NULL),
+			_activeTrackTimbreListSize(0) {
+		memset(_loop, 0, sizeof(_loop));
 		memset(_tracksTimbreList, 0, sizeof(_tracksTimbreList));
 		memset(_tracksTimbreListSize, 0, sizeof(_tracksTimbreListSize));
-		_activeTrackTimbreList = NULL;
-		_activeTrackTimbreListSize = 0;
 	}
 	~MidiParser_XMIDI() { }
 
@@ -175,11 +189,14 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
 		case 0x70:	// XMIDI_CONTROLLER_VOICE_PROT
 		case 0x71:	// XMIDI_CONTROLLER_TIMBRE_PROT
 		case 0x72:	// XMIDI_CONTROLLER_BANK_CHANGE
+			// These controllers are handled in the Miles drivers
+			break;
+
 		case 0x73:	// XMIDI_CONTROLLER_IND_CTRL_PREFIX
 		case 0x76:	// XMIDI_CONTROLLER_CLEAR_BB_COUNT
 		case 0x78:	// XMIDI_CONTROLLER_SEQ_BRANCH_INDEX
 		default:
-			if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) {
+			if (info.basic.param1 >= 0x73 && info.basic.param1 <= 0x78) {
 				warning("Unsupported XMIDI controller %d (0x%2x)",
 					info.basic.param1, info.basic.param1);
 			}
@@ -405,10 +422,26 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
 	return false;
 }
 
+void MidiParser_XMIDI::sendToDriver(uint32 b) {
+	if (_source < 0) {
+		MidiParser::sendToDriver(b);
+	} else {
+		_driver->send(_source, b);
+	}
+}
+
+void MidiParser_XMIDI::sendMetaEventToDriver(byte type, byte *data, uint16 length) {
+	if (_source < 0) {
+		MidiParser::sendMetaEventToDriver(type, data, length);
+	} else {
+		_driver->metaEvent(_source, type, data, length);
+	}
+}
+
 void MidiParser::defaultXMidiCallback(byte eventData, void *data) {
 	warning("MidiParser: defaultXMidiCallback(%d)", eventData);
 }
 
-MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver) {
-	return new MidiParser_XMIDI(proc, data, newTimbreListProc, newTimbreListDriver);
+MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver, int source) {
+	return new MidiParser_XMIDI(proc, data, newTimbreListProc, newTimbreListDriver, source);
 }
diff --git a/audio/miles.h b/audio/miles.h
index 23d5998fba..dbc4b835df 100644
--- a/audio/miles.h
+++ b/audio/miles.h
@@ -25,16 +25,20 @@
 
 #include "audio/mididrv.h"
 #include "common/error.h"
+#include "common/mutex.h"
 #include "common/stream.h"
 
 namespace Audio {
 
 #define MILES_MIDI_CHANNEL_COUNT 16
+#define MILES_RHYTHM_CHANNEL 9
 
 // Miles Audio supported controllers for control change messages
 #define MILES_CONTROLLER_SELECT_PATCH_BANK 114
 #define MILES_CONTROLLER_PROTECT_VOICE 112
 #define MILES_CONTROLLER_PROTECT_TIMBRE 113
+#define MILES_CONTROLLER_LOCK_CHANNEL 110
+#define MILES_CONTROLLER_PROTECT_CHANNEL 111
 #define MILES_CONTROLLER_MODULATION 1
 #define MILES_CONTROLLER_VOLUME 7
 #define MILES_CONTROLLER_EXPRESSION 11
@@ -43,6 +47,10 @@ namespace Audio {
 #define MILES_CONTROLLER_PITCH_RANGE 6
 #define MILES_CONTROLLER_RESET_ALL 121
 #define MILES_CONTROLLER_ALL_NOTES_OFF 123
+#define MILES_CONTROLLER_OMNI_ON 124
+#define MILES_CONTROLLER_OMNI_OFF 125
+#define MILES_CONTROLLER_MONO_ON 126
+#define MILES_CONTROLLER_POLY_ON 127
 #define MILES_CONTROLLER_PATCH_REVERB 59
 #define MILES_CONTROLLER_PATCH_BENDER 60
 #define MILES_CONTROLLER_REVERB_MODE 61
@@ -72,9 +80,340 @@ namespace Audio {
 // Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
 #define MILES_PITCHBENDER_DEFAULT 0x2000
 
+// The maximum number of sources sending MIDI data to this driver.
+// This is based on the requirements of the KYRA engine, but can be increased if
+// necessary.
+#define MILES_MAXIMUM_SOURCES 4
+// Maximum number of tracked active notes for the MT-32
+// This is the maximum polyphony of the MT-32 plus some overhead (MIDI data may send
+// more notes than the MT-32 can handle simultaneously).
+#define MILES_MT32_ACTIVE_NOTES 48
+// Maximum number of tracked active notes for GM
+// This is the maximum polyphony of the SC-88 and AWE64 plus some overhead (MIDI data
+// may send more notes than the GM device can handle simultaneously).
+#define MILES_GM_ACTIVE_NOTES 96
+
+#define MILES_MT32_PATCHES_COUNT 128
+#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
+
+#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14
+#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58
+#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4
+#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT))
+
+struct MilesMT32InstrumentEntry {
+	byte bankId;
+	byte patchId;
+	byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1];
+	byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1];
+};
+
+class MidiDriver_Miles_Midi : public MidiDriver {
+public:
+	MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
+	virtual ~MidiDriver_Miles_Midi();
+
+public:
+	// MidiDriver
+	int open() override;
+	// Open the Miles driver using the specified MidiDriver instance.
+	int open(MidiDriver *driver, bool nativeMT32);
+	void close() override;
+	bool isOpen() const override { return _isOpen; }
+
+	using MidiDriver_BASE::send;
+	void send(uint32 b) override;
+	void send(int8 source, uint32 b) override;
+	void sysEx(const byte *msg, uint16 length) override;
+	void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
+
+	/**
+	 * De-initialize a source. Call this after playing a track or sound effect using this source.
+	 * This will unlock and unprotect channels used by this source and stop any active notes
+	 * from this source.
+	 * Automatically executed when an End Of Track meta event is received.
+	 */
+	void deinitSource(uint8 source);
+	/**
+	 * Set the volume for this source. This will be used to scale the volume values in the MIDI
+	 * data from this source. Expected volume values are 0 - 256.
+	 * Note that source volume remains set for the source number even after deinitializing the
+	 * source. If the same source numbers are consistently used for music and SFX sources, the
+	 * source volume will only need to be set once.
+	 */
+	void setSourceVolume(uint8 source, uint16 volume);
+
+	/** Stops all notes currently playing on the MIDI device. */
+	void allNotesOff();
+
+	MidiChannel *allocateChannel() override {
+		if (_driver)
+			return _driver->allocateChannel();
+		return NULL;
+	}
+	MidiChannel *getPercussionChannel() override {
+		if (_driver)
+			return _driver->getPercussionChannel();
+		return NULL;
+	}
+
+	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
+		if (_driver)
+			_driver->setTimerCallback(timer_param, timer_proc);
+	}
+
+	uint32 getBaseTempo() override {
+		if (_driver) {
+			return _driver->getBaseTempo();
+		}
+		return 1000000 / _baseFreq;
+	}
+
+protected:
+	Common::Mutex _mutex;
+	MidiDriver *_driver;
+	bool _isOpen;
+	// The type of MIDI data supplied to the driver: MT-32 or General MIDI.
+	MusicType _midiType;
+	// True if the MIDI output is an MT-32 (hardware or 100% emulated),
+	// false if the MIDI output is a General MIDI device.
+	bool _nativeMT32;
+	// True if the General MIDI output supports Roland GS for improved MT-32 mapping.
+	bool _enableGS;
+
+	// Bitmask of the MIDI channels in use by the output device
+	uint16 _outputChannelMask;
+
+	int _baseFreq;
+
+public:
+	void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize);
+
+private:
+	void initMidiDevice();
+
+	void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
+
+	uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index);
+
+	void writeRhythmSetup(byte note, byte customTimbreId);
+	void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId);
+	void writePatchByte(byte patchId, byte index, byte patchValue);
+	void writeToSystemArea(byte index, byte value);
+
+	const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId);
+	int16 searchCustomTimbre(byte patchBank, byte patchId);
+
+	void setupPatch(byte patchBank, byte patchId);
+	int16 installCustomTimbre(byte patchBank, byte patchId);
+
+	bool isOutputChannelUsed(uint8 outputChannel) { return _outputChannelMask & (1 << outputChannel); }
+
+private:
+	/**
+	 * This stores the values of the MIDI controllers for
+	 * a MIDI channel. It is used to keep track of controller
+	 * values while a channel is locked, so they can be
+	 * restored when the channel is unlocked.
+	 */
+	struct MidiChannelControlData {
+		// The source that last sent an event to this channel
+		int8 source;
+		// True if the source volume has been applied to this channel
+		bool sourceVolumeApplied;
+
+		byte program;
+
+		uint16 pitchWheel;
+
+		byte modulation;
+		// The volume specified by the MIDI data
+		byte volume;
+		// The volume scaled using the source volume
+		byte scaledVolume;
+		byte panPosition;
+		byte expression;
+		bool sustain;
+
+		// Custom timbre data
+		byte   currentPatchBank;
+
+		bool   usingCustomTimbre;
+		byte   currentCustomTimbreId;
+
+		MidiChannelControlData() : source(-1),
+			sourceVolumeApplied(false),
+			program(0),
+			pitchWheel(MILES_PITCHBENDER_DEFAULT),
+			modulation(0),
+			volume(0xFF),
+			scaledVolume(0x64),
+			panPosition(0x40),
+			expression(0x7F),
+			sustain(false),
+			currentPatchBank(0),
+			usingCustomTimbre(false),
+			currentCustomTimbreId(0) { }
+	};
+
+	struct MidiChannelEntry {
+		// True if this channel is locked. A locked channel will
+		// only accept MIDI messages from the source that locked it.
+		bool   locked;
+		// The channel in the MIDI data of the lock source that
+		// is assigned to this locked output channel. This is a
+		// reverse lookup for MidiSource::channelMap.
+		// -1 if the channel is not locked.
+		int8   lockDataChannel;
+		// True if this channel is protected from locking.
+		// The channel can still be locked, but unprotected
+		// channels will be prioritized.
+		bool   lockProtected;
+		// The source that protected this channel from locking.
+		// -1 if the channel is not protected.
+		int8   protectedSource;
+
+		// The number of notes currently active on the channel.
+		uint8  activeNotes;
+
+		// The MIDI controller values currently used by the channel.
+		MidiChannelControlData currentData;
+		// The MIDI controller values set by the sources which are
+		// not currently using the channel because it is locked.
+		// These values will be set on the channel when the channel
+		// is unlocked.
+		MidiChannelControlData unlockData;
+
+		MidiChannelEntry() : locked(false),
+			lockDataChannel(-1),
+			lockProtected(false),
+			protectedSource(-1),
+			activeNotes(0) { }
+	};
+
+	/**
+	 * Send out a control change MIDI message using the specified data.
+	 * @param controlData The new MIDI controller value will be set on this MidiChannelControlData
+	 * @param sendMessage True if the message should be sent out to the device
+	 */
+	void controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool sendMessage);
+	/**
+	 * Removes active notes from the active notes registration on the specified channel.
+	 * @param sustainedNotes True if only sustained notes should be removed; otherwise only regular active notes will be removed
+	 */
+	void removeActiveNotes(uint8 outputChannel, bool sustainedNotes);
+	/**
+	 * Find and lock an output channel and reserve it for the specified
+	 * source. The output channel will be mapped to the specified data
+	 * channel.
+	 */
+	void lockChannel(uint8 source, uint8 dataChannel);
+	/**
+	 * Find an output channel to lock. This will be based on the number
+	 * of active notes on the channels and whether the channel is
+	 * protected or not.
+	 * @param useProtectedChannels When true, protected channels are considered for locking
+	 * @returns The output channel to lock, or -1 if no channel is available
+	 */
+	int8 findLockChannel(bool useProtectedChannels = false);
+	/**
+	 * Unlock an output channel. This will stop all notes on the channel,
+	 * restore the controller values and make it available to other sources.
+	 */
+	void unlockChannel(uint8 outputChannel);
+	/**
+	 * Send a program change MIDI message using the specified data.
+	 * @param controlData The new program value will be set on this MidiChannelControlData
+	 * @param sendMessage True if the message should be sent out to the device
+	 */
+	void programChange(byte outputChannel, byte patchId, MidiChannelControlData &controlData, bool sendMessage);
+
+	void stopNotesOnChannel(uint8 outputChannelNumber);
+
+	struct MidiCustomTimbreEntry {
+		bool   used;
+		bool   protectionEnabled;
+		byte   currentPatchBank;
+		byte   currentPatchId;
+
+		uint32 lastUsedNoteCounter;
+
+		MidiCustomTimbreEntry() : used(false),
+			protectionEnabled(false),
+			currentPatchBank(0),
+			currentPatchId(0),
+			lastUsedNoteCounter(0) {}
+	};
+
+	struct MilesMT32SysExQueueEntry {
+		uint32 targetAddress;
+		byte   dataPos;
+		byte   data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator
+
+		MilesMT32SysExQueueEntry() : targetAddress(0),
+			dataPos(0) {
+			memset(data, 0, sizeof(data));
+		}
+	};
+
+	/**
+	 * This stores data about a specific source of MIDI data.
+	 */
+	struct MidiSource {
+		// The source volume as set by ScummVM (music/SFX volume)
+		uint16 volume;
+		// The mapping of MIDI data channels to output channels
+		// for this source.
+		int8 channelMap[MILES_MIDI_CHANNEL_COUNT];
+
+		MidiSource() : volume(256) {
+			memset(channelMap, 0, sizeof(channelMap));
+		}
+	};
+
+	// stores information about all MIDI channels
+	MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
+
+	// stores information about all custom timbres
+	MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT];
+
+	byte _patchesBank[MILES_MT32_PATCHES_COUNT];
+
+	// holds all instruments
+	MilesMT32InstrumentEntry *_instrumentTablePtr;
+	uint16                   _instrumentTableCount;
+
+	uint32 _noteCounter; // used to figure out, which timbres are outdated
+
+	// SysEx Queues
+	MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT];
+
+	// MIDI sources sending messages to this driver.
+	MidiSource _sources[MILES_MAXIMUM_SOURCES];
+
+	struct ActiveNote {
+		int8 source;
+		uint8 channel;
+		uint8 note;
+		bool sustain;
+
+		ActiveNote() : source(0x7F),
+			channel(0xFF),
+			note(0xFF),
+			sustain(false) { }
+	};
+
+	// The maximum number of active notes that have to be tracked for this MIDI device.
+	uint8 _maximumActiveNotes;
+	// Tracks the notes being played by the MIDI device.
+	ActiveNote *_activeNotes;
+};
+
 extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr);
 
-extern MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename);
+extern MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename);
+
+extern MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::String &instrumentDataFilename);
 
 extern void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize);
 
diff --git a/audio/miles_midi.cpp b/audio/miles_midi.cpp
new file mode 100644
index 0000000000..ad31098ae7
--- /dev/null
+++ b/audio/miles_midi.cpp
@@ -0,0 +1,1250 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/miles.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/mutex.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+namespace Audio {
+
+// Miles Audio MT-32 / General MIDI driver
+//
+
+#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
+#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
+
+#define MILES_MT32_SYSEX_TERMINATOR 0xFF
+
+const byte milesMT32SysExResetParameters[] = {
+	0x01, MILES_MT32_SYSEX_TERMINATOR
+};
+
+const byte milesMT32SysExChansSetup[] = {
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR
+};
+
+const byte milesMT32SysExPartialReserveTable[] = {
+	0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR
+};
+
+const byte milesMT32SysExInitReverb[] = {
+	0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2
+};
+
+MidiDriver_Miles_Midi::MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) :
+		_driver(NULL),
+		_isOpen(false),
+		_nativeMT32(false),
+		_enableGS(false),
+		_outputChannelMask(65535), // Channels 1-16
+		_baseFreq(250),
+		_noteCounter(0) {
+	switch (midiType) {
+	case MT_MT32:
+		_midiType = MT_MT32;
+		break;
+	case MT_GM:
+	case MT_GS: // Treat GS same as GM
+		_midiType = MT_GM;
+		break;
+	default:
+		assert(false);
+		break;
+	}
+
+	memset(_patchesBank, 0, sizeof(_patchesBank));
+
+	_instrumentTablePtr = instrumentTablePtr;
+	_instrumentTableCount = instrumentTableCount;
+
+	for (int i = 0; i < MILES_MAXIMUM_SOURCES; ++i) {
+		// Default MIDI channel mapping: data channel == output channel
+		for (int j = 0; j < MILES_MIDI_CHANNEL_COUNT; ++j) {
+			_sources[i].channelMap[j] = j;
+		}
+	}
+
+	_maximumActiveNotes = _midiType == MT_MT32 ? MILES_MT32_ACTIVE_NOTES : MILES_GM_ACTIVE_NOTES;
+	_activeNotes = new ActiveNote[_maximumActiveNotes];
+	assert(_activeNotes);
+}
+
+MidiDriver_Miles_Midi::~MidiDriver_Miles_Midi() {
+	Common::StackLock lock(_mutex);
+	if (_driver) {
+		_driver->setTimerCallback(0, 0);
+		_driver->close();
+		delete _driver;
+	}
+	_driver = NULL;
+
+	if (_activeNotes)
+		delete[] _activeNotes;
+}
+
+int MidiDriver_Miles_Midi::open() {
+	assert(!_driver);
+
+	// Setup midi driver
+	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | (_midiType == MT_MT32 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
+	MusicType deviceMusicType = MidiDriver::getMusicType(dev);
+	if (!(deviceMusicType == MT_MT32 || deviceMusicType == MT_GM || deviceMusicType == MT_GS))
+		error("MILES-MIDI: detected music device uses unsupported music type %i", deviceMusicType);
+
+	MidiDriver *driver = MidiDriver::createMidi(dev);
+	bool nativeMT32 = deviceMusicType == MT_MT32 || ConfMan.getBool("native_mt32");
+
+	return open(driver, nativeMT32);
+}
+
+int MidiDriver_Miles_Midi::open(MidiDriver *driver, bool nativeMT32) {
+	assert(!_driver);
+
+	_driver = driver;
+	_nativeMT32 = nativeMT32;
+
+	_enableGS = ConfMan.getBool("enable_gs");
+
+	if (!_driver)
+		return 255;
+
+	if (_nativeMT32)
+		_outputChannelMask = _midiType == MT_MT32 ? 1022 : 767; // Channels 2-10 / 1-8 and 10
+	_driver->property(MidiDriver::PROP_CHANNEL_MASK, _outputChannelMask);
+
+	int ret = _driver->open();
+	if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0)
+		return ret;
+
+	initMidiDevice();
+
+	return 0;
+}
+
+void MidiDriver_Miles_Midi::close() {
+	if (_driver) {
+		_driver->close();
+	}
+}
+
+void MidiDriver_Miles_Midi::initMidiDevice() {
+	if (_nativeMT32) {
+		bool initForGM = _midiType != MT_MT32;
+
+		// reset all internal parameters / patches
+		initMT32(initForGM);
+
+		if (!initForGM) {
+			// init part/channel assignments
+			MT32SysEx(0x10000D, milesMT32SysExChansSetup);
+
+			// partial reserve table
+			MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
+
+			// init reverb
+			MT32SysEx(0x100001, milesMT32SysExInitReverb);
+		}
+	} else {
+		initGM(_midiType == MT_MT32, _enableGS);
+	}
+
+	// Set Miles default controller values
+	// Note that AIL/MSS apparently did not get full support for GM until
+	// version 3.00 in 09/1994. Many games used the MT-32 driver to
+	// implement GM support. As a result, default parameters were only sent
+	// out on the MT-32 channels (2-10). Also, the default MT-32 instrument
+	// numbers were set on GM devices, even though they map to different
+	// instruments. This is reproduced here to prevent possible issues with
+	// games that depend on this behavior.
+
+	for (int i = 1; i < 10; ++i) {
+		// Volume 7F (max)
+		send(-1, 0xB0 | i, MILES_CONTROLLER_VOLUME, 0x7F);
+		if (_midiType == MT_MT32) {
+			// Panning center - not the MT-32 default for all channels
+			send(-1, 0xB0 | i, MILES_CONTROLLER_PANNING, 0x40);
+		}
+		// Patch
+		if (i != MILES_RHYTHM_CHANNEL) {
+			if (_midiType == MT_MT32) {
+				// These are the default on the MT-32; just set them on the control data
+				_midiChannels[i].currentData.program = _mt32DefaultInstruments[i - 1];
+			} else {
+				// Send the instruments out to GM devices.
+				send(-1, 0xC0 | i, _mt32DefaultInstruments[i - 1], 0);
+			}
+		}
+		// The following settings are also sent out by the AIL driver:
+		// - Modulation 0
+		// - Expression 7F (max)
+		// - Sustain off
+		// - Pitch bend neutral
+		// These are the default MT-32 and GM settings, so it is not
+		// necessary to send these.
+	}
+}
+
+void MidiDriver_Miles_Midi::sysEx(const byte *msg, uint16 length) {
+	if (!_nativeMT32 && length >= 3 && msg[0] == 0x41 && msg[2] == 0x16)
+		// MT-32 SysExes have no effect on GM devices.
+		return;
+
+	// Send SysEx
+	_driver->sysEx(msg, length);
+
+	// Wait the time it takes to send the SysEx data
+	uint32 delay = (length + 2) * 1000 / 3125;
+
+	// Plus an additional delay for the MT-32 rev00
+	if (_nativeMT32)
+		delay += 40;
+
+	g_system->delayMillis(delay);
+}
+
+void MidiDriver_Miles_Midi::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) {
+	if (!_nativeMT32)
+		// MT-32 SysExes have no effect on GM devices.
+		return;
+
+	byte   sysExMessage[270];
+	uint16 sysExPos      = 0;
+	byte   sysExByte;
+	uint16 sysExChecksum = 0;
+
+	memset(&sysExMessage, 0, sizeof(sysExMessage));
+
+	sysExMessage[0] = 0x41; // Roland
+	sysExMessage[1] = 0x10;
+	sysExMessage[2] = 0x16; // Model MT32
+	sysExMessage[3] = 0x12; // Command DT1
+
+	sysExChecksum = 0;
+
+	sysExMessage[4] = (targetAddress >> 16) & 0xFF;
+	sysExMessage[5] = (targetAddress >> 8) & 0xFF;
+	sysExMessage[6] = targetAddress & 0xFF;
+
+	for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
+		assert(sysExMessage[targetAddressByte] < 0x80); // security check
+		sysExChecksum -= sysExMessage[targetAddressByte];
+	}
+
+	sysExPos = 7;
+	while (1) {
+		sysExByte = *dataPtr++;
+		if (sysExByte == MILES_MT32_SYSEX_TERMINATOR)
+			break; // Message done
+
+		assert(sysExPos < sizeof(sysExMessage));
+		assert(sysExByte < 0x80); // security check
+		sysExMessage[sysExPos++] = sysExByte;
+		sysExChecksum -= sysExByte;
+	}
+
+	// Calculate checksum
+	assert(sysExPos < sizeof(sysExMessage));
+	sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
+
+	sysEx(sysExMessage, sysExPos);
+}
+
+void MidiDriver_Miles_Midi::metaEvent(int8 source, byte type, byte *data, uint16 length) {
+	assert(source < MILES_MAXIMUM_SOURCES);
+
+	if (type == 0x2F && source >= 0) // End of Track
+		deinitSource(source);
+
+	_driver->metaEvent(type, data, length);
+}
+
+void MidiDriver_Miles_Midi::send(uint32 b) {
+	send(-1, b);
+}
+
+	// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
+	assert(source < MILES_MAXIMUM_SOURCES);
+
+	byte command = b & 0xf0;
+	byte dataChannel = b & 0xf;
+	byte outputChannel = source < 0 ? dataChannel : _sources[source].channelMap[dataChannel];
+	MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel];
+	// Only send the message to the MIDI device if the channel is not locked or
+	// if the source that locked the channel is sending the message
+	bool sendMessage = source < 0 || !outputChannelEntry.locked ||
+		(outputChannelEntry.locked && outputChannelEntry.currentData.source == source);
+	// Track controller changes on the current data if the MIDI message is sent out,
+	// or on the unlock data otherwise.
+	MidiChannelControlData &controlData = sendMessage ? outputChannelEntry.currentData : outputChannelEntry.unlockData;
+	byte op1 = (b >> 8) & 0xff;
+	byte op2 = (b >> 16) & 0xff;
+
+	if (command != 0xF0 && controlData.source != source) {
+		// A new source has sent an event on this channel.
+		controlData.sourceVolumeApplied = false;
+		controlData.source = source;
+	}
+
+	switch (command) {
+	case 0x80: // Note Off
+	case 0x90: // Note On
+		if (sendMessage) {
+			// Note On with velocity 0 is treated as Note Off
+			bool addNote = command == 0x90 && op2 != 0;
+			if (addNote) {
+				if (source >= 0 && !controlData.sourceVolumeApplied)
+					// Source volume hasn't been applied yet. Do so now.
+					controlChange(outputChannel, MILES_CONTROLLER_VOLUME, controlData.volume, source, controlData, sendMessage);
+				// Add the new note to the active note registration
+				for (int i = 0; i < _maximumActiveNotes; ++i) {
+					ActiveNote &activeNote = _activeNotes[i];
+					if (activeNote.channel == 0xFF) {
+						// Add the new note.
+						activeNote.source = source;
+						activeNote.channel = outputChannel;
+						activeNote.note = op1;
+						activeNote.sustain = false;
+						++outputChannelEntry.activeNotes;
+						break;
+					}
+				}
+			} else {
+				// Remove the note from the active note registration
+				for (int i = 0; i < _maximumActiveNotes; ++i) {
+					ActiveNote &activeNote = _activeNotes[i];
+					if (activeNote.channel == outputChannel && activeNote.source == source && activeNote.note == op1) {
+						if (controlData.sustain) {
+							// Sustain is on, so the note should be turned off
+							// when sustain is turned off.
+							activeNote.sustain = true;
+						} else {
+							// Turn off the existing note.
+							activeNote.source = 0x7F;
+							activeNote.channel = 0xFF;
+							if (outputChannelEntry.activeNotes == 0) {
+								warning("MILES-MIDI: active notes 0 on channel %d when turning off note %x", op1, outputChannel);
+							} else {
+								--outputChannelEntry.activeNotes;
+							}
+						}
+						break;
+					}
+				}
+			}
+		}
+		// fall through
+	case 0xa0: // Polyphonic key pressure (aftertouch) (not supported by MT-32 or GM)
+	case 0xd0: // Channel pressure (aftertouch) (not supported by MT-32)
+	case 0xe0: // pitch bend change
+		if (command == 0xe0)
+			controlData.pitchWheel = ((uint16)op2 << 7) | (uint16)op1;
+		
+		_noteCounter++;
+		if (controlData.usingCustomTimbre) {
+			// Remember that this timbre got used now
+			_customTimbres[controlData.currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
+		}
+		if (sendMessage) {
+			_driver->send(command | outputChannel, op1, op2);
+		}
+		break;
+	case 0xb0: // Control change
+		controlChange(outputChannel, op1, op2, source, controlData, sendMessage);
+		break;
+	case 0xc0: // Program Change
+		programChange(outputChannel, op1, controlData, sendMessage);
+		break;
+	case 0xf0: // SysEx
+		warning("MILES-MIDI: SysEx: %x", b);
+		break;
+	default:
+		warning("MILES-MIDI: Unknown event %02x", command);
+	}
+}
+
+void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool sendMessage) {
+	assert(source < MILES_MAXIMUM_SOURCES);
+
+	// XMIDI controllers
+	switch (controllerNumber) {
+	case MILES_CONTROLLER_SELECT_PATCH_BANK:
+		controlData.currentPatchBank = controllerValue;
+		return;
+
+	case MILES_CONTROLLER_PROTECT_TIMBRE:
+		if (controlData.usingCustomTimbre) {
+			// custom timbre set on current channel
+			_customTimbres[controlData.currentCustomTimbreId].protectionEnabled = controllerValue >= 64;
+		}
+		return;
+
+	case MILES_CONTROLLER_LOCK_CHANNEL:
+		if (source >= 0) {
+			if (controllerValue >= 0x40) {
+				lockChannel(source, outputChannel);
+			} else {
+				unlockChannel(outputChannel);
+			}
+		}
+		return;
+
+	case MILES_CONTROLLER_PROTECT_CHANNEL:
+		if (source >= 0 && !_midiChannels[outputChannel].locked) {
+			_midiChannels[outputChannel].lockProtected = controllerValue >= 0x40;
+			_midiChannels[outputChannel].protectedSource = controllerValue >= 0x40 ? source : -1;
+		}
+		return;
+
+	default:
+		break;
+	}
+
+	// XMIDI MT-32 specific controllers
+	if (_nativeMT32) {
+		switch (controllerNumber) {
+		case MILES_CONTROLLER_PATCH_REVERB:
+			writePatchByte(controlData.program, 6, controllerValue);
+			if (sendMessage)
+				_driver->send(0xC0 | outputChannel | (controlData.program << 8)); // execute program change
+			return;
+
+		case MILES_CONTROLLER_PATCH_BENDER:
+			writePatchByte(controlData.program, 4, controllerValue);
+			if (sendMessage)
+				_driver->send(0xC0 | outputChannel | (controlData.program << 8)); // execute program change
+			return;
+
+		case MILES_CONTROLLER_REVERB_MODE:
+			writeToSystemArea(1, controllerValue);
+			return;
+
+		case MILES_CONTROLLER_REVERB_TIME:
+			writeToSystemArea(2, controllerValue);
+			return;
+
+		case MILES_CONTROLLER_REVERB_LEVEL:
+			writeToSystemArea(3, controllerValue);
+			return;
+
+		case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
+			if (controlData.usingCustomTimbre) {
+				// custom timbre is set on current channel
+				writeRhythmSetup(controllerValue, controlData.currentCustomTimbreId);
+			}
+			return;
+		default:
+			break;
+		}
+	}
+
+	// XMIDI MT-32 SysEx controllers
+	if (_nativeMT32 && (controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
+		// send SysEx
+		byte sysExQueueNr = 0;
+
+		// figure out which queue is accessed
+		controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
+		while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) {
+			sysExQueueNr++;
+			controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1);
+		}
+		assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);
+
+		byte sysExPos = _sysExQueues[sysExQueueNr].dataPos;
+		bool sysExSend = false;
+
+		switch(controllerNumber) {
+		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
+			_sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF;
+			_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
+			break;
+		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
+			_sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
+			_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
+			break;
+		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
+			_sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
+			_sysExQueues[sysExQueueNr].targetAddress |= controllerValue;
+			break;
+		case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
+			if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
+				// Space left? put current byte into queue
+				_sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
+				sysExPos++;
+				_sysExQueues[sysExQueueNr].dataPos = sysExPos;
+				if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
+					// overflow? -> send it now
+					sysExSend = true;
+				}
+			}
+			break;
+		case MILES_CONTROLLER_SYSEX_COMMAND_SEND:
+			sysExSend = true;
+			break;
+		default:
+			assert(0);
+		}
+
+		if (sysExSend) {
+			if (sysExPos > 0) {
+				// data actually available? -> send it
+				_sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
+
+				// Execute SysEx
+				MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data);
+
+				// adjust target address to point at the end of the current data
+				_sysExQueues[sysExQueueNr].targetAddress += sysExPos;
+				// reset queue data buffer
+				_sysExQueues[sysExQueueNr].dataPos = 0;
+			}
+		}
+		return;
+	}
+
+	if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
+		// XMIDI controllers? Don't send these to the MIDI device
+		return;
+	}
+
+	// Standard MIDI controllers
+	switch (controllerNumber) {
+	case MILES_CONTROLLER_MODULATION:
+		controlData.modulation = controllerValue;
+		break;
+	case MILES_CONTROLLER_VOLUME:
+		controlData.volume = controllerValue;
+		controlData.sourceVolumeApplied = true;
+		if (source >= 0) {
+			// Scale to source volume
+			controllerValue = (_sources[source].volume * controllerValue) >> 8;
+		}
+		if (_scaleGSPercussionVolumeToMT32 && outputChannel == MILES_RHYTHM_CHANNEL) {
+			// Scale GS percussion channel volume to MT-32 level (80/127)
+			controllerValue = (80 * controllerValue) >> 7;
+		}
+		if (controlData.scaledVolume == controllerValue) {
+			// Volume is already at this value, so no need to send it out
+			// to the MIDI device.
+			return;
+		}
+		controlData.scaledVolume = controllerValue;
+		break;
+	case MILES_CONTROLLER_PANNING:
+		if (_reversePanning) {
+			// Center panning is 0x40
+			controllerValue = 0x80 - controllerValue;
+			if (controllerValue > 0x7F)
+				controllerValue = 0x7F;
+		}
+		controlData.panPosition = controllerValue;
+		break;
+	case MILES_CONTROLLER_EXPRESSION:
+		controlData.expression = controllerValue;
+		break;
+	case MILES_CONTROLLER_RESET_ALL:
+		controlData.modulation = 0;
+		controlData.expression = 0x7F;
+		controlData.pitchWheel = MILES_PITCHBENDER_DEFAULT;
+		controlData.sustain = false;
+		if (sendMessage) {
+			removeActiveNotes(outputChannel, true);
+		}
+		break;
+	case MILES_CONTROLLER_SUSTAIN:
+		controlData.sustain = controllerValue >= 0x40;
+		if (sendMessage && !controlData.sustain) {
+			removeActiveNotes(outputChannel, true);
+		}
+		break;
+	case MILES_CONTROLLER_OMNI_ON:
+	case MILES_CONTROLLER_OMNI_OFF:
+	case MILES_CONTROLLER_MONO_ON:
+	case MILES_CONTROLLER_POLY_ON:
+		// These act as an All Notes Off on MT-32, but also turn sustain off.
+		// They are not part of GM, so should not be used in GM data.
+		if (_midiType != MT_MT32) {
+			warning("MILES-MIDI: unsupported GM controller %x", controllerNumber);
+			return;
+		}
+
+		controlData.sustain = false;
+		if (sendMessage)
+			removeActiveNotes(outputChannel, true);
+		if (!_nativeMT32) {
+			// MT-32 data on GM device.
+			// These controllers might not be supported or have side effects
+			// (changing omni or mono/poly mode). Send All Notes Off and
+			// Sustain Off instead.
+			if (sendMessage) {
+				controllerNumber = MILES_CONTROLLER_ALL_NOTES_OFF;
+				_driver->send(0xB0 | outputChannel | (MILES_CONTROLLER_SUSTAIN << 8) | (0 << 16));
+			}
+		}
+		// fall through
+	case MILES_CONTROLLER_ALL_NOTES_OFF:
+		if (sendMessage) {
+			removeActiveNotes(outputChannel, false);
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (sendMessage) {
+		_driver->send(0xB0 | outputChannel | (controllerNumber << 8) | (controllerValue << 16));
+	}
+}
+
+void MidiDriver_Miles_Midi::removeActiveNotes(uint8 outputChannel, bool sustainedNotes) {
+	// Remove sustained notes from the active notes registration
+	for (int i = 0; i < _maximumActiveNotes; ++i) {
+		if (_activeNotes[i].channel == outputChannel && _activeNotes[i].sustain == sustainedNotes) {
+			_activeNotes[i].source = 0x7F;
+			_activeNotes[i].channel = 0xFF;
+			if (_midiChannels[outputChannel].activeNotes == 0) {
+				if (sustainedNotes)
+					warning("MILES-MIDI: active notes 0 on channel %d when turning off sustained notes", outputChannel);
+				else
+					warning("MILES-MIDI: active notes 0 on channel %d when turning all notes off", outputChannel);
+				continue;
+			}
+			--_midiChannels[outputChannel].activeNotes;
+		}
+	}
+}
+
+void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
+	assert(source < MILES_MAXIMUM_SOURCES);
+
+	int8 lockChannel = findLockChannel();
+	if (lockChannel == -1)
+		// Try again, but consider lock protected channels
+		lockChannel = findLockChannel(true);
+	if (lockChannel == -1)
+		// Could not find a channel to lock
+		return;
+
+	stopNotesOnChannel(lockChannel);
+
+	_midiChannels[lockChannel].locked = true;
+	_midiChannels[lockChannel].lockDataChannel = dataChannel;
+	_sources[source].channelMap[dataChannel] = lockChannel;
+	// Copy current controller values so they can be restored when unlocking the channel
+	_midiChannels[lockChannel].unlockData = _midiChannels[lockChannel].currentData;
+	_midiChannels[lockChannel].currentData.source = source;
+
+	// Send volume change to apply the new source volume
+	controlChange(lockChannel, MILES_CONTROLLER_VOLUME, 0x7F, source, _midiChannels[lockChannel].currentData, true);
+
+	// Note that other controller values might be "inherited" from the source
+	// which was previously playing on the locked MIDI channel. The KYRA engine
+	// does not seem to take any precautions against this.
+	// Controllers could be set to default values here.
+}
+
+int8 MidiDriver_Miles_Midi::findLockChannel(bool useProtectedChannels) {
+	// Starting at the highest (non-rhythm) channel, find the channel
+	// with the least active notes that isn't already locked.
+	// If useProtectedChannels is false, channels that are protected
+	// from channel locking will not be considered.
+	int8 potentialLockChannel = -1;
+	uint8 notes = 255;
+	for (int i = MILES_MIDI_CHANNEL_COUNT - 1; i >= 0; --i) {
+		if (!isOutputChannelUsed(i) || i == MILES_RHYTHM_CHANNEL || _midiChannels[i].locked || (!useProtectedChannels && _midiChannels[i].lockProtected))
+			continue;
+		if (_midiChannels[i].activeNotes < notes) {
+			potentialLockChannel = i;
+			notes = _midiChannels[i].activeNotes;
+		}
+	}
+	return potentialLockChannel;
+}
+
+void MidiDriver_Miles_Midi::unlockChannel(uint8 outputChannel) {
+	MidiChannelEntry &channel = _midiChannels[outputChannel];
+	if (!channel.locked)
+		return;
+
+	stopNotesOnChannel(outputChannel);
+
+	// Unlock the channel
+	channel.locked = false;
+	_sources[channel.currentData.source].channelMap[channel.lockDataChannel] = channel.lockDataChannel;
+	channel.lockDataChannel = -1;
+	channel.currentData.source = channel.unlockData.source;
+
+	// Send the unlock channel data to the MIDI device to reset the channel parameters
+	if (channel.unlockData.volume != 0xFF) {
+		controlChange(outputChannel, MILES_CONTROLLER_VOLUME, channel.unlockData.volume, channel.currentData.source, channel.currentData, true);
+	} else {
+		channel.currentData.volume = 0xFF;
+	}
+	if (channel.currentData.modulation != channel.unlockData.modulation)
+		controlChange(outputChannel, MILES_CONTROLLER_MODULATION, channel.unlockData.modulation, channel.currentData.source, channel.currentData, true);
+	if (channel.currentData.panPosition != channel.unlockData.panPosition)
+	controlChange(outputChannel, MILES_CONTROLLER_PANNING, channel.unlockData.panPosition, channel.currentData.source, channel.currentData, true);
+	if (channel.currentData.expression != channel.unlockData.expression)
+		controlChange(outputChannel, MILES_CONTROLLER_EXPRESSION, channel.unlockData.expression, channel.currentData.source, channel.currentData, true);
+	if (channel.currentData.sustain != channel.unlockData.sustain)
+		controlChange(outputChannel, MILES_CONTROLLER_SUSTAIN, channel.unlockData.sustain ? 0x7F : 0x00, channel.currentData.source, channel.currentData, true);
+	if (channel.currentData.currentPatchBank != channel.unlockData.currentPatchBank)
+		controlChange(outputChannel, MILES_CONTROLLER_SELECT_PATCH_BANK, channel.unlockData.currentPatchBank, channel.currentData.source, channel.currentData, true);
+	if (channel.unlockData.program != 0xFF && (channel.currentData.program != channel.unlockData.program || channel.currentData.currentPatchBank != channel.unlockData.currentPatchBank))
+		programChange(outputChannel, channel.unlockData.program, channel.currentData, true);
+	if (channel.currentData.pitchWheel != channel.unlockData.pitchWheel)
+		send(channel.currentData.source, 0xE0 | outputChannel, channel.unlockData.pitchWheel & 0x7F, (channel.unlockData.pitchWheel >> 7) & 0x7F);
+}
+
+void MidiDriver_Miles_Midi::stopNotesOnChannel(uint8 outputChannelNumber) {
+	MidiChannelEntry &channel = _midiChannels[outputChannelNumber];
+	if (channel.currentData.sustain) {
+		controlChange(outputChannelNumber, MILES_CONTROLLER_SUSTAIN, 0, channel.currentData.source, channel.currentData, true);
+	}
+	if (channel.activeNotes > 0) {
+		controlChange(outputChannelNumber, MILES_CONTROLLER_ALL_NOTES_OFF, 0, channel.currentData.source, channel.currentData, true);
+	}
+}
+
+void MidiDriver_Miles_Midi::allNotesOff() {
+	for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
+		if (!isOutputChannelUsed(i))
+			continue;
+		_driver->send(0xB0 | i, MILES_CONTROLLER_SUSTAIN, 0);
+		_midiChannels[i].currentData.sustain = false;
+		_driver->send(0xB0 | i, MILES_CONTROLLER_ALL_NOTES_OFF, 0);
+		_midiChannels[i].activeNotes = 0;
+	}
+	for (int i = 0; i < _maximumActiveNotes; ++i) {
+		_activeNotes[i].source = 0x7F;
+		_activeNotes[i].channel = 0xFF;
+	}
+}
+
+void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, MidiChannelControlData &controlData, bool sendMessage) {
+	// remember patch id for the current MIDI-channel
+	controlData.program = patchId;
+
+	if (_midiType == MT_MT32) {
+		byte channelPatchBank = controlData.currentPatchBank;
+		byte activePatchBank = _patchesBank[patchId];
+
+		//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
+
+		if (channelPatchBank != activePatchBank) {
+			// associate patch with timbre
+			setupPatch(channelPatchBank, patchId);
+		}
+
+		// If this is a custom patch, remember customTimbreId
+		int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
+		if (customTimbre >= 0) {
+			controlData.usingCustomTimbre = true;
+			controlData.currentCustomTimbreId = customTimbre;
+		} else {
+			controlData.usingCustomTimbre = false;
+		}
+
+		if (outputChannel == MILES_RHYTHM_CHANNEL)
+			// Patch changes on the rhythm channel are used by AIL to setup custom
+			// rhythm timbres. There's no need to actually send them to the MT-32,
+			// because it won't respond to them. On GM/GS devices they might
+			// unintentionally change the drumkit.
+			return;
+
+		if (!_nativeMT32 && !_enableGS) {
+			// GM device: map the patch to GM equivalent
+			// TODO It would be nice if the patch bank could be taken into account
+			// when using a custom ScummVM game-specific MT-32 to GM mapping.
+			patchId = _mt32ToGm[patchId];
+		}
+	} else {
+		// GM/GS MIDI
+		if (outputChannel == MILES_RHYTHM_CHANNEL) {
+			// Correct possible wrong GS drumkit number
+			patchId = _gsDrumkitFallbackMap[patchId];
+		}
+		else if (_nativeMT32) {
+			// GM on an MT-32: map the patch to the MT-32 equivalent
+			patchId = _gmToMt32[patchId];
+		}
+	}
+
+	// Finally send program change to MIDI device
+	if (sendMessage) {
+		_driver->send(0xC0 | outputChannel | (patchId << 8));
+	}
+}
+
+int16 MidiDriver_Miles_Midi::searchCustomTimbre(byte patchBank, byte patchId) {
+	byte customTimbreId = 0;
+
+	for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
+		if (_customTimbres[customTimbreId].used) {
+			if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) {
+				return customTimbreId;
+			}
+		}
+	}
+	return -1;
+}
+
+const MilesMT32InstrumentEntry *MidiDriver_Miles_Midi::searchCustomInstrument(byte patchBank, byte patchId) {
+	const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;
+
+	for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
+		if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
+			return instrumentPtr;
+		instrumentPtr++;
+	}
+	return NULL;
+}
+
+void MidiDriver_Miles_Midi::setupPatch(byte patchBank, byte patchId) {
+	_patchesBank[patchId] = patchBank;
+
+	if (patchBank) {
+		// non-built-in bank
+		int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
+		if (customTimbreId >= 0) {
+			// now available? -> use this timbre
+			writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY
+			return;
+		}
+	}
+
+	// for built-in bank (or timbres, that are not available) use default MT32 timbres
+	byte timbreId = patchId & 0x3F;
+	if (!(patchId & 0x40)) {
+		writePatchTimbre(patchId, 0, timbreId); // Group A
+	} else {
+		writePatchTimbre(patchId, 1, timbreId); // Group B
+	}
+}
+
+void MidiDriver_Miles_Midi::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
+	uint16 timbreCount = 0;
+	uint32 expectedSize = 0;
+	const byte *timbreListSeeker = timbreListPtr;
+
+	if (timbreListSize < 2) {
+		warning("MILES-MIDI: XMIDI-TIMB chunk - not enough bytes in chunk");
+		return;
+	}
+
+	timbreCount = READ_LE_UINT16(timbreListPtr);
+	expectedSize = timbreCount * 2;
+	if (expectedSize > timbreListSize) {
+		warning("MILES-MIDI: XMIDI-TIMB chunk - size mismatch");
+		return;
+	}
+
+	timbreListSeeker += 2;
+
+	while (timbreCount) {
+		const byte  patchId   = *timbreListSeeker++;
+		const byte  patchBank = *timbreListSeeker++;
+		int16       customTimbreId = 0;
+
+		switch (patchBank) {
+		case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
+		case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
+			// ignore those 2 banks
+			break;
+
+		default:
+			// Check, if this timbre was already loaded
+			customTimbreId = searchCustomTimbre(patchBank, patchId);
+
+			if (customTimbreId < 0) {
+				// currently not loaded, try to install it
+				installCustomTimbre(patchBank, patchId);
+			}
+		}
+		timbreCount--;
+	}
+}
+
+//
+int16 MidiDriver_Miles_Midi::installCustomTimbre(byte patchBank, byte patchId) {
+	switch(patchBank) {
+	case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
+	case MILES_MT32_TIMBREBANK_MELODIC_MODULE:  // Reserved for melodic mode
+		return -1;
+	default:
+		break;
+	}
+
+	// Original driver did a search for custom timbre here
+	// and in case it was found, it would call setup_patch()
+	// we are called from within setup_patch(), so this isn't needed
+
+	int16 customTimbreId = -1;
+	int16 leastUsedTimbreId = -1;
+	uint32 leastUsedTimbreNoteCounter = _noteCounter;
+	const MilesMT32InstrumentEntry *instrumentPtr = NULL;
+
+	// Check, if requested instrument is actually available
+	instrumentPtr = searchCustomInstrument(patchBank, patchId);
+	if (!instrumentPtr) {
+		warning("MILES-MIDI: instrument not found during installCustomTimbre()");
+		return -1; // not found -> bail out
+	}
+
+	// Look for an empty timbre slot
+	// or get the least used non-protected slot
+	for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
+		if (!_customTimbres[customTimbreNr].used) {
+			// found an empty slot -> use this one
+			customTimbreId = customTimbreNr;
+			break;
+		} else {
+			// used slot
+			if (!_customTimbres[customTimbreNr].protectionEnabled) {
+				// not protected
+				uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
+				if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
+					leastUsedTimbreId          = customTimbreNr;
+					leastUsedTimbreNoteCounter = customTimbreNoteCounter;
+				}
+			}
+		}
+	}
+
+	if (customTimbreId < 0) {
+		// no empty slot found, check if we got a least used non-protected slot
+		if (leastUsedTimbreId < 0) {
+			// everything is protected, bail out
+			warning("MILES-MIDI: no non-protected timbre slots available during installCustomTimbre()");
+			return -1;
+		}
+		customTimbreId = leastUsedTimbreId;
+	}
+
+	// setup timbre slot
+	_customTimbres[customTimbreId].used                = true;
+	_customTimbres[customTimbreId].currentPatchBank    = patchBank;
+	_customTimbres[customTimbreId].currentPatchId      = patchId;
+	_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
+	_customTimbres[customTimbreId].protectionEnabled   = false;
+
+	uint32 targetAddress = 0x080000 | (customTimbreId << 9);
+	uint32 targetAddressCommon   = targetAddress + 0x000000;
+	uint32 targetAddressPartial1 = targetAddress + 0x00000E;
+	uint32 targetAddressPartial2 = targetAddress + 0x000048;
+	uint32 targetAddressPartial3 = targetAddress + 0x000102;
+	uint32 targetAddressPartial4 = targetAddress + 0x00013C;
+
+#if 0
+	byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
+	uint16 parameterDataPos = 0;
+
+	memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
+	parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
+	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
+	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
+	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
+	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
+	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
+	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
+	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
+	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
+	parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;
+
+	MT32SysEx(targetAddressCommon, parameterData);
+#endif
+
+	// upload common parameter data
+	MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter);
+	// upload partial parameter data
+	MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]);
+	MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]);
+	MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]);
+	MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]);
+
+	setupPatch(patchBank, patchId);
+
+	return customTimbreId;
+}
+
+uint32 MidiDriver_Miles_Midi::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) {
+	uint16 targetAddressLSB = baseAddress & 0xFF;
+	uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF;
+	uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF;
+
+	// add index to it, but use 7-bit of the index for each byte
+	targetAddressLSB += (index & 0x7F);
+	targetAddressKSB += ((index >> 7) & 0x7F);
+	targetAddressMSB += ((index >> 14) & 0x7F);
+
+	// adjust bytes, so that none of them is above or equal 0x80
+	while (targetAddressLSB >= 0x80) {
+		targetAddressLSB -= 0x80;
+		targetAddressKSB++;
+	}
+	while (targetAddressKSB >= 0x80) {
+		targetAddressKSB -= 0x80;
+		targetAddressMSB++;
+	}
+	assert(targetAddressMSB < 0x80);
+
+	// put everything together
+	return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16);
+}
+
+void MidiDriver_Miles_Midi::writeRhythmSetup(byte note, byte customTimbreId) {
+	byte   sysExData[2];
+	uint32 targetAddress = 0;
+
+	targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2));
+
+	sysExData[0] = customTimbreId;
+	sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_Midi::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
+	byte   sysExData[3];
+	uint32 targetAddress = 0;
+
+	// write to patch memory (starts at 0x050000, each entry is 8 bytes)
+	targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3);
+
+	sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
+	sysExData[1] = timbreId;    // timbre number (0-63)
+	sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_Midi::writePatchByte(byte patchId, byte index, byte patchValue) {
+	byte   sysExData[2];
+	uint32 targetAddress = 0;
+
+	targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index);
+
+	sysExData[0] = patchValue;
+	sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_Midi::writeToSystemArea(byte index, byte value) {
+	byte   sysExData[2];
+	uint32 targetAddress = 0;
+
+	targetAddress = calculateSysExTargetAddress(0x100000, index);
+
+	sysExData[0] = value;
+	sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) { return MidiDriver_Miles_MIDI_create(MT_MT32, instrumentDataFilename); }
+
+MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::String &instrumentDataFilename) {
+	assert(midiType == MT_MT32 || midiType == MT_GM || midiType == MT_GS);
+
+	MilesMT32InstrumentEntry *instrumentTablePtr = NULL;
+	uint16                    instrumentTableCount = 0;
+
+	if (midiType == MT_MT32 && !instrumentDataFilename.empty()) {
+		// Load MT32 instrument data from file SAMPLE.MT
+		Common::File *fileStream = new Common::File();
+		uint32        fileSize = 0;
+		byte         *fileDataPtr = NULL;
+		uint32        fileDataOffset = 0;
+		uint32        fileDataLeft = 0;
+
+		byte curBankId;
+		byte curPatchId;
+
+		MilesMT32InstrumentEntry *instrumentPtr = NULL;
+		uint32                    instrumentOffset;
+		uint16                    instrumentDataSize;
+
+		if (!fileStream->open(instrumentDataFilename))
+			error("MILES-MDI: could not open instrument file '%s'", instrumentDataFilename.c_str());
+
+		fileSize = fileStream->size();
+
+		fileDataPtr = new byte[fileSize];
+
+		if (fileStream->read(fileDataPtr, fileSize) != fileSize)
+			error("MILES-MIDI: error while reading instrument file");
+		fileStream->close();
+		delete fileStream;
+
+		// File is like this:
+		// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
+		// ...
+		// until patch + bank are both 0xFF, which signals end of header
+
+		// First we check how many entries there are
+		fileDataOffset = 0;
+		fileDataLeft = fileSize;
+		while (1) {
+			if (fileDataLeft < 6)
+				error("MILES-MIDI: unexpected EOF in instrument file");
+
+			curPatchId = fileDataPtr[fileDataOffset++];
+			curBankId  = fileDataPtr[fileDataOffset++];
+
+			if ((curBankId == 0xFF) && (curPatchId == 0xFF))
+				break;
+
+			fileDataOffset += 4; // skip over offset
+			instrumentTableCount++;
+		}
+
+		if (instrumentTableCount == 0)
+			error("MILES-MIDI: no instruments in instrument file");
+
+		// Allocate space for instruments
+		instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];
+
+		// Now actually read all entries
+		instrumentPtr = instrumentTablePtr;
+
+		fileDataOffset = 0;
+		while (1) {
+			curPatchId = fileDataPtr[fileDataOffset++];
+			curBankId  = fileDataPtr[fileDataOffset++];
+
+			if ((curBankId == 0xFF) && (curPatchId == 0xFF))
+				break;
+
+			instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
+			fileDataOffset += 4;
+
+			instrumentPtr->bankId = curBankId;
+			instrumentPtr->patchId = curPatchId;
+
+			instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
+			if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
+				error("MILES-MIDI: unsupported instrument size");
+
+			instrumentOffset += 2;
+			// Copy common parameter data
+			memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
+			instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
+			instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
+
+			// Copy partial parameter data
+			for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
+				memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
+				instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
+				instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
+			}
+
+			// Instrument read, next instrument please
+			instrumentPtr++;
+		}
+
+		// Free instrument file data
+		delete[] fileDataPtr;
+	}
+
+	return new MidiDriver_Miles_Midi(midiType, instrumentTablePtr, instrumentTableCount);
+}
+
+void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) {
+	MidiDriver_Miles_Midi *driverMT32 = dynamic_cast<MidiDriver_Miles_Midi *>(driver);
+
+	if (driverMT32) {
+		driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
+	}
+}
+
+void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
+	assert(source < MILES_MAXIMUM_SOURCES);
+
+	// Unlock and unprotect channels which were locked or protected by this source.
+	for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
+		if (!isOutputChannelUsed(i))
+			continue;
+
+		if (_midiChannels[i].currentData.source == source && _midiChannels[i].locked) {
+			unlockChannel(i);
+		}
+		if (_midiChannels[i].lockProtected && _midiChannels[i].protectedSource == source) {
+			_midiChannels[i].lockProtected = false;
+			_midiChannels[i].protectedSource = -1;
+		}
+		if (_midiChannels[i].currentData.source == source)
+			_midiChannels[i].currentData.source = -1;
+		if (_midiChannels[i].unlockData.source == source)
+			_midiChannels[i].unlockData.source = -1;
+	}
+	// Reset the data to output channel mapping
+	for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
+		_sources[source].channelMap[i] = i;
+	}
+	// Stop any active notes.
+	for (int i = 0; i < _maximumActiveNotes; ++i) {
+		if (_activeNotes[i].source == source) {
+			if (_activeNotes[i].sustain) {
+				// Turn off sustain
+				controlChange(_activeNotes[i].channel, MILES_CONTROLLER_SUSTAIN, 0x00, source, _midiChannels[_activeNotes[i].channel].currentData, true);
+			} else {
+				// Send note off
+				send(source, 0x80 | _activeNotes[i].channel, _activeNotes[i].note, 0x00);
+			}
+		}
+	}
+}
+
+void MidiDriver_Miles_Midi::setSourceVolume(uint8 source, uint16 volume) {
+	assert(source < MILES_MAXIMUM_SOURCES);
+
+	_sources[source].volume = volume;
+
+	for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
+		if (!isOutputChannelUsed(i))
+			continue;
+
+		MidiChannelEntry &channel = _midiChannels[i];
+		MidiChannelControlData *channelData = 0;
+		bool sendMessage = false;
+		// Apply the new source volume to this channel if this source is active
+		// on this channel, or if it was active on the channel before it was
+		// locked.
+		if (channel.currentData.source == source) {
+			channelData = &channel.currentData;
+			sendMessage = true;
+		} else if (channel.locked && channel.unlockData.source == source) {
+			channelData = &channel.unlockData;
+		}
+
+		if (channelData && channelData->volume != 0xFF)
+			controlChange(i, MILES_CONTROLLER_VOLUME, channelData->volume, source, *channelData, sendMessage);
+	}
+}
+
+} // End of namespace Audio
diff --git a/audio/miles_mt32.cpp b/audio/miles_mt32.cpp
deleted file mode 100644
index e61b3fbc3e..0000000000
--- a/audio/miles_mt32.cpp
+++ /dev/null
@@ -1,917 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "audio/miles.h"
-
-#include "common/config-manager.h"
-#include "common/file.h"
-#include "common/mutex.h"
-#include "common/system.h"
-#include "common/textconsole.h"
-
-namespace Audio {
-
-// Miles Audio MT32 driver
-//
-
-#define MILES_MT32_PATCHES_COUNT 128
-#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
-
-#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
-#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
-
-#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14
-#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58
-#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4
-#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT))
-
-#define MILES_MT32_SYSEX_TERMINATOR 0xFF
-
-struct MilesMT32InstrumentEntry {
-	byte bankId;
-	byte patchId;
-	byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1];
-	byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1];
-};
-
-const byte milesMT32SysExResetParameters[] = {
-	0x01, MILES_MT32_SYSEX_TERMINATOR
-};
-
-const byte milesMT32SysExChansSetup[] = {
-	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR
-};
-
-const byte milesMT32SysExPartialReserveTable[] = {
-	0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR
-};
-
-const byte milesMT32SysExInitReverb[] = {
-	0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2
-};
-
-class MidiDriver_Miles_MT32 : public MidiDriver {
-public:
-	MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
-	virtual ~MidiDriver_Miles_MT32();
-
-	// MidiDriver
-	int open() override;
-	void close() override;
-	bool isOpen() const override { return _isOpen; }
-
-	void send(uint32 b) override;
-	void sysEx(const byte* msg, uint16 length) override;
-
-	MidiChannel *allocateChannel() override {
-		if (_driver)
-			return _driver->allocateChannel();
-		return NULL;
-	}
-	MidiChannel *getPercussionChannel() override {
-		if (_driver)
-			return _driver->getPercussionChannel();
-		return NULL;
-	}
-
-	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
-		if (_driver)
-			_driver->setTimerCallback(timer_param, timer_proc);
-	}
-
-	uint32 getBaseTempo() override {
-		if (_driver) {
-			return _driver->getBaseTempo();
-		}
-		return 1000000 / _baseFreq;
-	}
-
-protected:
-	Common::Mutex _mutex;
-	MidiDriver *_driver;
-	bool _MT32;
-	bool _nativeMT32;
-
-	bool _isOpen;
-	int _baseFreq;
-
-public:
-	void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize);
-
-private:
-	void resetMT32();
-
-	void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
-
-	uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index);
-
-	void writeRhythmSetup(byte note, byte customTimbreId);
-	void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId);
-	void writePatchByte(byte patchId, byte index, byte patchValue);
-	void writeToSystemArea(byte index, byte value);
-
-	void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
-	void programChange(byte midiChannel, byte patchId);
-
-	const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId);
-	int16 searchCustomTimbre(byte patchBank, byte patchId);
-
-	void setupPatch(byte patchBank, byte patchId);
-	int16 installCustomTimbre(byte patchBank, byte patchId);
-
-private:
-	struct MidiChannelEntry {
-		byte   currentPatchBank;
-		byte   currentPatchId;
-
-		bool   usingCustomTimbre;
-		byte   currentCustomTimbreId;
-
-		MidiChannelEntry() : currentPatchBank(0),
-							currentPatchId(0),
-							usingCustomTimbre(false),
-							currentCustomTimbreId(0) { }
-	};
-
-	struct MidiCustomTimbreEntry {
-		bool   used;
-		bool   protectionEnabled;
-		byte   currentPatchBank;
-		byte   currentPatchId;
-
-		uint32 lastUsedNoteCounter;
-
-		MidiCustomTimbreEntry() : used(false),
-								protectionEnabled(false),
-								currentPatchBank(0),
-								currentPatchId(0),
-								lastUsedNoteCounter(0) {}
-	};
-
-	struct MilesMT32SysExQueueEntry {
-		uint32 targetAddress;
-		byte   dataPos;
-		byte   data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator
-
-		MilesMT32SysExQueueEntry() : targetAddress(0),
-									dataPos(0) {
-			memset(data, 0, sizeof(data));
-		}
-	};
-
-	// stores information about all MIDI channels
-	MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
-
-	// stores information about all custom timbres
-	MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT];
-
-	byte _patchesBank[MILES_MT32_PATCHES_COUNT];
-
-	// holds all instruments
-	MilesMT32InstrumentEntry *_instrumentTablePtr;
-	uint16                   _instrumentTableCount;
-
-	uint32 _noteCounter; // used to figure out, which timbres are outdated
-
-	// SysEx Queues
-	MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT];
-};
-
-MidiDriver_Miles_MT32::MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) {
-	_instrumentTablePtr = instrumentTablePtr;
-	_instrumentTableCount = instrumentTableCount;
-
-	_driver = NULL;
-	_isOpen = false;
-	_MT32 = false;
-	_nativeMT32 = false;
-	_baseFreq = 250;
-
-	_noteCounter = 0;
-
-	memset(_patchesBank, 0, sizeof(_patchesBank));
-}
-
-MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() {
-	Common::StackLock lock(_mutex);
-	if (_driver) {
-		_driver->setTimerCallback(0, 0);
-		_driver->close();
-		delete _driver;
-	}
-	_driver = NULL;
-}
-
-int MidiDriver_Miles_MT32::open() {
-	assert(!_driver);
-
-	// Setup midi driver
-	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
-	MusicType musicType = MidiDriver::getMusicType(dev);
-
-	switch (musicType) {
-	case MT_MT32:
-		_nativeMT32 = true;
-		break;
-	case MT_GM:
-		if (ConfMan.getBool("native_mt32")) {
-			_nativeMT32 = true;
-		}
-		break;
-	default:
-		break;
-	}
-
-	if (!_nativeMT32) {
-		error("MILES-MT32: non-mt32 currently not supported!");
-	}
-
-	_driver = MidiDriver::createMidi(dev);
-	if (!_driver)
-		return 255;
-
-	if (_nativeMT32)
-		_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
-
-	int ret = _driver->open();
-	if (ret)
-		return ret;
-
-	if (_nativeMT32) {
-		_driver->sendMT32Reset();
-
-		resetMT32();
-	}
-
-	return 0;
-}
-
-void MidiDriver_Miles_MT32::close() {
-	if (_driver) {
-		_driver->close();
-	}
-}
-
-void MidiDriver_Miles_MT32::resetMT32() {
-	// reset all internal parameters / patches
-	MT32SysEx(0x7F0000, milesMT32SysExResetParameters);
-
-	// init part/channel assignments
-	MT32SysEx(0x10000D, milesMT32SysExChansSetup);
-
-	// partial reserve table
-	MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
-
-	// init reverb
-	MT32SysEx(0x100001, milesMT32SysExInitReverb);
-}
-
-void MidiDriver_Miles_MT32::sysEx(const byte *msg, uint16 length) {
-	// Send SysEx
-	_driver->sysEx(msg, length);
-
-	// Wait the time it takes to send the SysEx data
-	uint32 delay = (length + 2) * 1000 / 3125;
-
-	// Plus an additional delay for the MT-32 rev00
-	if (_nativeMT32)
-		delay += 40;
-
-	g_system->delayMillis(delay);
-}
-
-void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) {
-	byte   sysExMessage[270];
-	uint16 sysExPos      = 0;
-	byte   sysExByte     = 0;
-	uint16 sysExChecksum = 0;
-
-	memset(&sysExMessage, 0, sizeof(sysExMessage));
-
-	sysExMessage[0] = 0x41; // Roland
-	sysExMessage[1] = 0x10;
-	sysExMessage[2] = 0x16; // Model MT32
-	sysExMessage[3] = 0x12; // Command DT1
-
-	sysExChecksum = 0;
-
-	sysExMessage[4] = (targetAddress >> 16) & 0xFF;
-	sysExMessage[5] = (targetAddress >> 8) & 0xFF;
-	sysExMessage[6] = targetAddress & 0xFF;
-
-	for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
-		assert(sysExMessage[targetAddressByte] < 0x80); // security check
-		sysExChecksum -= sysExMessage[targetAddressByte];
-	}
-
-	sysExPos = 7;
-	while (1) {
-		sysExByte = *dataPtr++;
-		if (sysExByte == MILES_MT32_SYSEX_TERMINATOR)
-			break; // Message done
-
-		assert(sysExPos < sizeof(sysExMessage));
-		assert(sysExByte < 0x80); // security check
-		sysExMessage[sysExPos++] = sysExByte;
-		sysExChecksum -= sysExByte;
-	}
-
-	// Calculate checksum
-	assert(sysExPos < sizeof(sysExMessage));
-	sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
-
-	sysEx(sysExMessage, sysExPos);
-}
-
-// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
-void MidiDriver_Miles_MT32::send(uint32 b) {
-	byte command = b & 0xf0;
-	byte midiChannel = b & 0xf;
-	byte op1 = (b >> 8) & 0xff;
-	byte op2 = (b >> 16) & 0xff;
-
-	switch (command) {
-	case 0x80: // note off
-	case 0x90: // note on
-	case 0xa0: // Polyphonic key pressure (aftertouch)
-	case 0xd0: // Channel pressure (aftertouch)
-	case 0xe0: // pitch bend change
-		_noteCounter++;
-		if (_midiChannels[midiChannel].usingCustomTimbre) {
-			// Remember that this timbre got used now
-			_customTimbres[_midiChannels[midiChannel].currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
-		}
-		_driver->send(b);
-		break;
-	case 0xb0: // Control change
-		controlChange(midiChannel, op1, op2);
-		break;
-	case 0xc0: // Program Change
-		programChange(midiChannel, op1);
-		break;
-	case 0xf0: // SysEx
-		warning("MILES-MT32: SysEx: %x", b);
-		break;
-	default:
-		warning("MILES-MT32: Unknown event %02x", command);
-	}
-}
-
-void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
-	byte channelPatchId = 0;
-	byte channelCustomTimbreId = 0;
-
-	switch (controllerNumber) {
-	case MILES_CONTROLLER_SELECT_PATCH_BANK:
-		_midiChannels[midiChannel].currentPatchBank = controllerValue;
-		return;
-
-	case MILES_CONTROLLER_PATCH_REVERB:
-		channelPatchId = _midiChannels[midiChannel].currentPatchId;
-
-		writePatchByte(channelPatchId, 6, controllerValue);
-		_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
-		return;
-
-	case MILES_CONTROLLER_PATCH_BENDER:
-		channelPatchId = _midiChannels[midiChannel].currentPatchId;
-
-		writePatchByte(channelPatchId, 4, controllerValue);
-		_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
-		return;
-
-	case MILES_CONTROLLER_REVERB_MODE:
-		writeToSystemArea(1, controllerValue);
-		return;
-
-	case MILES_CONTROLLER_REVERB_TIME:
-		writeToSystemArea(2, controllerValue);
-		return;
-
-	case MILES_CONTROLLER_REVERB_LEVEL:
-		writeToSystemArea(3, controllerValue);
-		return;
-
-	case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
-		if (_midiChannels[midiChannel].usingCustomTimbre) {
-			// custom timbre is set on current channel
-			writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId);
-		}
-		return;
-
-	case MILES_CONTROLLER_PROTECT_TIMBRE:
-		if (_midiChannels[midiChannel].usingCustomTimbre) {
-			// custom timbre set on current channel
-			channelCustomTimbreId = _midiChannels[midiChannel].currentCustomTimbreId;
-			if (controllerValue >= 64) {
-				// enable protection
-				_customTimbres[channelCustomTimbreId].protectionEnabled = true;
-			} else {
-				// disable protection
-				_customTimbres[channelCustomTimbreId].protectionEnabled = false;
-			}
-		}
-		return;
-
-	default:
-		break;
-	}
-
-	if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
-		// send SysEx
-		byte sysExQueueNr = 0;
-
-		// figure out which queue is accessed
-		controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
-		while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) {
-			sysExQueueNr++;
-			controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1);
-		}
-		assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);
-
-		byte sysExPos = _sysExQueues[sysExQueueNr].dataPos;
-		bool sysExSend = false;
-
-		switch(controllerNumber) {
-		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
-			_sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF;
-			_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
-			break;
-		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
-			_sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
-			_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
-			break;
-		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
-			_sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
-			_sysExQueues[sysExQueueNr].targetAddress |= controllerValue;
-			break;
-		case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
-			if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
-				// Space left? put current byte into queue
-				_sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
-				sysExPos++;
-				_sysExQueues[sysExQueueNr].dataPos = sysExPos;
-				if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
-					// overflow? -> send it now
-					sysExSend = true;
-				}
-			}
-			break;
-		case MILES_CONTROLLER_SYSEX_COMMAND_SEND:
-			sysExSend = true;
-			break;
-		default:
-			assert(0);
-		}
-
-		if (sysExSend) {
-			if (sysExPos > 0) {
-				// data actually available? -> send it
-				_sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
-
-				// Execute SysEx
-				MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data);
-
-				// adjust target address to point at the end of the current data
-				_sysExQueues[sysExQueueNr].targetAddress += sysExPos;
-				// reset queue data buffer
-				_sysExQueues[sysExQueueNr].dataPos = 0;
-			}
-		}
-		return;
-	}
-
-	if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
-		// XMIDI controllers? ignore those
-		return;
-	}
-
-	_driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16));
-}
-
-void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) {
-	byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank;
-	byte activePatchBank = _patchesBank[patchId];
-
-	//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
-
-	// remember patch id for the current MIDI-channel
-	_midiChannels[midiChannel].currentPatchId = patchId;
-
-	if (channelPatchBank != activePatchBank) {
-		// associate patch with timbre
-		setupPatch(channelPatchBank, patchId);
-	}
-
-	// If this is a custom patch, remember customTimbreId
-	int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
-	if (customTimbre >= 0) {
-		_midiChannels[midiChannel].usingCustomTimbre = true;
-		_midiChannels[midiChannel].currentCustomTimbreId = customTimbre;
-	} else {
-		_midiChannels[midiChannel].usingCustomTimbre = false;
-	}
-
-	// Finally send program change to MT32
-	_driver->send(0xC0 | midiChannel | (patchId << 8));
-}
-
-int16 MidiDriver_Miles_MT32::searchCustomTimbre(byte patchBank, byte patchId) {
-	byte customTimbreId = 0;
-
-	for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
-		if (_customTimbres[customTimbreId].used) {
-			if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) {
-				return customTimbreId;
-			}
-		}
-	}
-	return -1;
-}
-
-const MilesMT32InstrumentEntry *MidiDriver_Miles_MT32::searchCustomInstrument(byte patchBank, byte patchId) {
-	const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;
-
-	for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
-		if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
-			return instrumentPtr;
-		instrumentPtr++;
-	}
-	return NULL;
-}
-
-void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) {
-	_patchesBank[patchId] = patchBank;
-
-	if (patchBank) {
-		// non-built-in bank
-		int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
-		if (customTimbreId >= 0) {
-			// now available? -> use this timbre
-			writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY
-			return;
-		}
-	}
-
-	// for built-in bank (or timbres, that are not available) use default MT32 timbres
-	byte timbreId = patchId & 0x3F;
-	if (!(patchId & 0x40)) {
-		writePatchTimbre(patchId, 0, timbreId); // Group A
-	} else {
-		writePatchTimbre(patchId, 1, timbreId); // Group B
-	}
-}
-
-void MidiDriver_Miles_MT32::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
-	uint16 timbreCount = 0;
-	uint32 expectedSize = 0;
-	const byte *timbreListSeeker = timbreListPtr;
-
-	if (timbreListSize < 2) {
-		warning("MILES-MT32: XMIDI-TIMB chunk - not enough bytes in chunk");
-		return;
-	}
-
-	timbreCount = READ_LE_UINT16(timbreListPtr);
-	expectedSize = timbreCount * 2;
-	if (expectedSize > timbreListSize) {
-		warning("MILES-MT32: XMIDI-TIMB chunk - size mismatch");
-		return;
-	}
-
-	timbreListSeeker += 2;
-
-	while (timbreCount) {
-		const byte  patchId   = *timbreListSeeker++;
-		const byte  patchBank = *timbreListSeeker++;
-		int16       customTimbreId = 0;
-
-		switch (patchBank) {
-		case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
-		case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
-			// ignore those 2 banks
-			break;
-
-		default:
-			// Check, if this timbre was already loaded
-			customTimbreId = searchCustomTimbre(patchBank, patchId);
-
-			if (customTimbreId < 0) {
-				// currently not loaded, try to install it
-				installCustomTimbre(patchBank, patchId);
-			}
-		}
-		timbreCount--;
-	}
-}
-
-//
-int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) {
-	switch(patchBank) {
-	case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
-	case MILES_MT32_TIMBREBANK_MELODIC_MODULE:  // Reserved for melodic mode
-		return -1;
-	default:
-		break;
-	}
-
-	// Original driver did a search for custom timbre here
-	// and in case it was found, it would call setup_patch()
-	// we are called from within setup_patch(), so this isn't needed
-
-	int16 customTimbreId = -1;
-	int16 leastUsedTimbreId = -1;
-	uint32 leastUsedTimbreNoteCounter = _noteCounter;
-	const MilesMT32InstrumentEntry *instrumentPtr = NULL;
-
-	// Check, if requested instrument is actually available
-	instrumentPtr = searchCustomInstrument(patchBank, patchId);
-	if (!instrumentPtr) {
-		warning("MILES-MT32: instrument not found during installCustomTimbre()");
-		return -1; // not found -> bail out
-	}
-
-	// Look for an empty timbre slot
-	// or get the least used non-protected slot
-	for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
-		if (!_customTimbres[customTimbreNr].used) {
-			// found an empty slot -> use this one
-			customTimbreId = customTimbreNr;
-			break;
-		} else {
-			// used slot
-			if (!_customTimbres[customTimbreNr].protectionEnabled) {
-				// not protected
-				uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
-				if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
-					leastUsedTimbreId          = customTimbreNr;
-					leastUsedTimbreNoteCounter = customTimbreNoteCounter;
-				}
-			}
-		}
-	}
-
-	if (customTimbreId < 0) {
-		// no empty slot found, check if we got a least used non-protected slot
-		if (leastUsedTimbreId < 0) {
-			// everything is protected, bail out
-			warning("MILES-MT32: no non-protected timbre slots available during installCustomTimbre()");
-			return -1;
-		}
-		customTimbreId = leastUsedTimbreId;
-	}
-
-	// setup timbre slot
-	_customTimbres[customTimbreId].used                = true;
-	_customTimbres[customTimbreId].currentPatchBank    = patchBank;
-	_customTimbres[customTimbreId].currentPatchId      = patchId;
-	_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
-	_customTimbres[customTimbreId].protectionEnabled   = false;
-
-	uint32 targetAddress = 0x080000 | (customTimbreId << 9);
-	uint32 targetAddressCommon   = targetAddress + 0x000000;
-	uint32 targetAddressPartial1 = targetAddress + 0x00000E;
-	uint32 targetAddressPartial2 = targetAddress + 0x000048;
-	uint32 targetAddressPartial3 = targetAddress + 0x000102;
-	uint32 targetAddressPartial4 = targetAddress + 0x00013C;
-
-#if 0
-	byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
-	uint16 parameterDataPos = 0;
-
-	memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
-	parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
-	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
-	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
-	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
-	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
-	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
-	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
-	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
-	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
-	parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;
-
-	MT32SysEx(targetAddressCommon, parameterData);
-#endif
-
-	// upload common parameter data
-	MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter);
-	// upload partial parameter data
-	MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]);
-	MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]);
-	MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]);
-	MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]);
-
-	setupPatch(patchBank, patchId);
-
-	return customTimbreId;
-}
-
-uint32 MidiDriver_Miles_MT32::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) {
-	uint16 targetAddressLSB = baseAddress & 0xFF;
-	uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF;
-	uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF;
-
-	// add index to it, but use 7-bit of the index for each byte
-	targetAddressLSB += (index & 0x7F);
-	targetAddressKSB += ((index >> 7) & 0x7F);
-	targetAddressMSB += ((index >> 14) & 0x7F);
-
-	// adjust bytes, so that none of them is above or equal 0x80
-	while (targetAddressLSB >= 0x80) {
-		targetAddressLSB -= 0x80;
-		targetAddressKSB++;
-	}
-	while (targetAddressKSB >= 0x80) {
-		targetAddressKSB -= 0x80;
-		targetAddressMSB++;
-	}
-	assert(targetAddressMSB < 0x80);
-
-	// put everything together
-	return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16);
-}
-
-void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) {
-	byte   sysExData[2];
-	uint32 targetAddress = 0;
-
-	targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2));
-
-	sysExData[0] = customTimbreId;
-	sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
-	byte   sysExData[3];
-	uint32 targetAddress = 0;
-
-	// write to patch memory (starts at 0x050000, each entry is 8 bytes)
-	targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3);
-
-	sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
-	sysExData[1] = timbreId;    // timbre number (0-63)
-	sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
-	byte   sysExData[2];
-	uint32 targetAddress = 0;
-
-	targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index);
-
-	sysExData[0] = patchValue;
-	sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
-	byte   sysExData[2];
-	uint32 targetAddress = 0;
-
-	targetAddress = calculateSysExTargetAddress(0x100000, index);
-
-	sysExData[0] = value;
-	sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) {
-	MilesMT32InstrumentEntry *instrumentTablePtr = NULL;
-	uint16                    instrumentTableCount = 0;
-
-	if (!instrumentDataFilename.empty()) {
-		// Load MT32 instrument data from file SAMPLE.MT
-		Common::File *fileStream = new Common::File();
-		uint32        fileSize = 0;
-		byte         *fileDataPtr = NULL;
-		uint32        fileDataOffset = 0;
-		uint32        fileDataLeft = 0;
-
-		byte curBankId = 0;
-		byte curPatchId = 0;
-
-		MilesMT32InstrumentEntry *instrumentPtr = NULL;
-		uint32                    instrumentOffset = 0;
-		uint16                    instrumentDataSize = 0;
-
-		if (!fileStream->open(instrumentDataFilename))
-			error("MILES-MT32: could not open instrument file '%s'", instrumentDataFilename.c_str());
-
-		fileSize = fileStream->size();
-
-		fileDataPtr = new byte[fileSize];
-
-		if (fileStream->read(fileDataPtr, fileSize) != fileSize)
-			error("MILES-MT32: error while reading instrument file");
-		fileStream->close();
-		delete fileStream;
-
-		// File is like this:
-		// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
-		// ...
-		// until patch + bank are both 0xFF, which signals end of header
-
-		// First we check how many entries there are
-		fileDataOffset = 0;
-		fileDataLeft = fileSize;
-		while (1) {
-			if (fileDataLeft < 6)
-				error("MILES-MT32: unexpected EOF in instrument file");
-
-			curPatchId = fileDataPtr[fileDataOffset++];
-			curBankId  = fileDataPtr[fileDataOffset++];
-
-			if ((curBankId == 0xFF) && (curPatchId == 0xFF))
-				break;
-
-			fileDataOffset += 4; // skip over offset
-			instrumentTableCount++;
-		}
-
-		if (instrumentTableCount == 0)
-			error("MILES-MT32: no instruments in instrument file");
-
-		// Allocate space for instruments
-		instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];
-
-		// Now actually read all entries
-		instrumentPtr = instrumentTablePtr;
-
-		fileDataOffset = 0;
-		fileDataLeft = fileSize;
-		while (1) {
-			curPatchId = fileDataPtr[fileDataOffset++];
-			curBankId  = fileDataPtr[fileDataOffset++];
-
-			if ((curBankId == 0xFF) && (curPatchId == 0xFF))
-				break;
-
-			instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
-			fileDataOffset += 4;
-
-			instrumentPtr->bankId = curBankId;
-			instrumentPtr->patchId = curPatchId;
-
-			instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
-			if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
-				error("MILES-MT32: unsupported instrument size");
-
-			instrumentOffset += 2;
-			// Copy common parameter data
-			memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
-			instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
-			instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
-
-			// Copy partial parameter data
-			for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
-				memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
-				instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
-				instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
-			}
-
-			// Instrument read, next instrument please
-			instrumentPtr++;
-		}
-
-		// Free instrument file data
-		delete[] fileDataPtr;
-	}
-
-	return new MidiDriver_Miles_MT32(instrumentTablePtr, instrumentTableCount);
-}
-
-void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) {
-	MidiDriver_Miles_MT32 *driverMT32 = dynamic_cast<MidiDriver_Miles_MT32 *>(driver);
-
-	if (driverMT32) {
-		driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
-	}
-}
-
-} // End of namespace Audio
diff --git a/audio/module.mk b/audio/module.mk
index c2d764ace4..8a7531e9ef 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -11,7 +11,7 @@ MODULE_OBJS := \
 	midiparser.o \
 	midiplayer.o \
 	miles_adlib.o \
-	miles_mt32.o \
+	miles_midi.o \
 	mixer.o \
 	mpu401.o \
 	musicplugin.o \
diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp
index a2579fa4d1..70e366fc81 100644
--- a/engines/kyra/engine/kyra_v1.cpp
+++ b/engines/kyra/engine/kyra_v1.cpp
@@ -116,22 +116,22 @@ Common::Error KyraEngine_v1::init() {
 			// Users who want PC speaker sound always have to select this individually for all
 			// Kyra games.
 			MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_PCSPK | MDT_MIDI | MDT_ADLIB | ((_flags.gameID == GI_KYRA2 || _flags.gameID == GI_LOL) ? MDT_PREFER_GM : MDT_PREFER_MT32));
-			if (MidiDriver::getMusicType(dev) == MT_ADLIB) {
+			const MusicType musicType = MidiDriver::getMusicType(dev);
+			if (musicType == MT_ADLIB) {
 				_sound = new SoundPC_v1(this, _mixer, Sound::kAdLib);
 			} else {
 				Sound::kType type;
-				const MusicType midiType = MidiDriver::getMusicType(dev);
 
-				if (midiType == MT_PCSPK || midiType == MT_NULL)
+				if (musicType == MT_PCSPK || musicType == MT_NULL)
 					type = Sound::kPCSpkr;
-				else if (midiType == MT_MT32 || ConfMan.getBool("native_mt32"))
+				else if (musicType == MT_MT32 || ConfMan.getBool("native_mt32"))
 					type = Sound::kMidiMT32;
 				else
 					type = Sound::kMidiGM;
 
 				MidiDriver *driver = 0;
 
-				if (MidiDriver::getMusicType(dev) == MT_PCSPK) {
+				if (musicType == MT_PCSPK) {
 					driver = new MidiDriver_PCSpeaker(_mixer);
 				} else {
 					driver = MidiDriver::createMidi(dev);
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk
index 84fffb7ff8..3e0b23f1ec 100644
--- a/engines/kyra/module.mk
+++ b/engines/kyra/module.mk
@@ -71,7 +71,6 @@ MODULE_OBJS := \
 	sound/sound_lok.o \
 	sound/drivers/adlib.o \
 	sound/drivers/audstream.o \
-	sound/drivers/midi.o \
 	sound/drivers/pcspeaker_v2.o \
 	text/text.o \
 	text/text_lok.o \
diff --git a/engines/kyra/sound/drivers/midi.cpp b/engines/kyra/sound/drivers/midi.cpp
deleted file mode 100644
index ad0ebe5836..0000000000
--- a/engines/kyra/sound/drivers/midi.cpp
+++ /dev/null
@@ -1,369 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "kyra/sound/drivers/midi.h"
-
-namespace Kyra {
-
-MidiOutput::MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32) : _system(system), _output(output) {
-	_isMT32 = isMT32;
-	_defaultMT32 = defaultMT32;
-
-	int ret = _output->open();
-	if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0)
-		error("Couldn't open midi driver");
-
-	static const Controller defaultControllers[] = {
-		{ 0x07, 0x7F }, { 0x01, 0x00 }, { 0x0A, 0x40 },
-		{ 0x0B, 0x7F }, { 0x40, 0x00 }, { 0x72, 0x00 },
-		{ 0x6E, 0x00 }, { 0x6F, 0x00 }, { 0x70, 0x00 }
-	};
-
-	static const byte defaultPrograms[] = {
-		0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A, 0xFF
-	};
-
-	static const byte sysEx1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-	static const byte sysEx2[] = { 3, 4, 3, 4, 3, 4, 3, 4, 4 };
-	static const byte sysEx3[] = { 0, 3, 2 };
-
-	if (_isMT32) {
-		sendSysEx(0x7F, 0x00, 0x00, sysEx1, 1);
-		sendSysEx(0x10, 0x00, 0x0D, sysEx1, 9);
-		sendSysEx(0x10, 0x00, 0x04, sysEx2, 9);
-		sendSysEx(0x10, 0x00, 0x01, sysEx3, 3);
-	} else {
-		_output->sendGMReset();
-	}
-
-	memset(_channels, 0, sizeof(_channels));
-	for (int i = 0; i < 16; ++i) {
-		for (int j = 0; j < 9; ++j)
-			_channels[i].controllers[j] = defaultControllers[j];
-		_channels[i].pitchWheel = -1;
-		_channels[i].program = 0xFF;
-	}
-
-	for (int i = 0; i < 9; ++i) {
-		for (int j = 1; j <= 9; ++j)
-			sendIntern(0xB0, j, defaultControllers[i].controller, defaultControllers[i].value);
-	}
-
-	for (int i = 1; i <= 9; ++i) {
-		sendIntern(0xE0, i, 0x00, 0x40);
-		if (defaultPrograms[i - 1] != 0xFF)
-			sendIntern(0xC0, i, defaultPrograms[i - 1], 0x00);
-	}
-
-	for (int i = 0; i < 4; ++i) {
-		_sources[i].volume = 256;
-		initSource(i);
-	}
-}
-
-
-MidiOutput::~MidiOutput() {
-	_output->close();
-	delete _output;
-}
-
-void MidiOutput::send(uint32 b) {
-	const byte event = b & 0xF0;
-	const byte channel = b & 0x0F;
-	byte param1 = (b >>  8) & 0xFF;
-	byte param2 = (b >> 16) & 0xFF;
-
-	if (event == 0xE0) {							// Pitch-Wheel
-		_channels[channel].pitchWheel =
-		_sources[_curSource].channelPW[channel] = (param2 << 8) | param1;
-	} else if (event == 0xC0) {						// Program change
-		_channels[channel].program =
-		_sources[_curSource].channelProgram[channel] = param1;
-	} else if (event == 0xB0) {						// Controller change
-		for (int i = 0; i < 9; ++i) {
-			Controller &cont = _sources[_curSource].controllers[channel][i];
-			if (cont.controller == param1) {
-				cont.value = param2;
-				break;
-			}
-		}
-
-		if (param1 == 0x07) {
-			param2 = (param2 * _sources[_curSource].volume) >> 8;
-		} else if (param1 == 0x6E) {	// Lock Channel
-			if (param2 >= 0x40) {	// Lock Channel
-				int chan = lockChannel();
-				if (chan < 0)
-					chan = channel;
-				_sources[_curSource].channelMap[channel] = chan;
-			} else {				// Unlock Channel
-				stopNotesOnChannel(channel);
-				unlockChannel(_sources[_curSource].channelMap[channel]);
-				_sources[_curSource].channelMap[channel] = channel;
-			}
-		} else if (param1 == 0x6F) {	// Protect Channel
-			if (param2 >= 0x40) {	// Protect Channel
-				_channels[channel].flags |= kChannelProtected;
-			} else {				// Unprotect Channel
-				_channels[channel].flags &= ~kChannelProtected;
-			}
-		} else if (param1 == 0x7B) {	// All notes off
-			// FIXME: Since the XMIDI parsers sends this
-			// on track change, we simply ignore it.
-			return;
-		}
-	} else if (event == 0x90 || event == 0x80) {	// Note On/Off
-		if (!(_channels[channel].flags & kChannelLocked)) {
-			const bool remove = (event == 0x80) || (param2 == 0x00);
-			int note = -1;
-
-			for (int i = 0; i < 32; ++i) {
-				if (remove) {
-					if (_sources[_curSource].notes[i].channel == channel &&
-						_sources[_curSource].notes[i].note == param1) {
-						note = i;
-						break;
-					}
-				} else {
-					if (_sources[_curSource].notes[i].channel == 0xFF) {
-						note = i;
-						break;
-					}
-				}
-			}
-
-			if (note != -1) {
-				if (remove) {
-					_sources[_curSource].notes[note].channel = 0xFF;
-
-					--_channels[_sources[_curSource].channelMap[channel]].noteCount;
-				} else {
-					_sources[_curSource].notes[note].channel = channel;
-					_sources[_curSource].notes[note].note = param1;
-
-					++_channels[_sources[_curSource].channelMap[channel]].noteCount;
-				}
-
-				sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2);
-			}
-		}
-		return;
-	}
-
-	if (!(_channels[channel].flags & kChannelLocked))
-		sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2);
-}
-
-void MidiOutput::sendIntern(const byte event, const byte channel, byte param1, const byte param2) {
-	byte param1ToSend = param1;
-	if (event == 0xC0) {
-		// MT32 -> GM conversion
-		if (!_isMT32 && _defaultMT32)
-			param1ToSend = MidiDriver::_mt32ToGm[param1];
-		// Program change on rhythm channel (drumkit selection)
-		else if (!_isMT32 && channel == 0x09) {
-			// Apply GS drumkit fallback to correct invalid drumkit numbers.
-			param1ToSend = MidiDriver::_gsDrumkitFallbackMap[param1];
-			debugC(7, kDebugLevelSound, "[Midi] Selected drumkit %i (requested %i)", param1ToSend, param1);
-		}
-	}
-
-	_output->send(event | channel, param1ToSend, param2);
-}
-
-void MidiOutput::sysEx(const byte *msg, uint16 length) {
-	// Wait the time it takes to send the SysEx data
-	uint32 delay = (length + 2) * 1000 / 3125;
-
-	// Plus an additional delay for the MT-32 rev00
-	if (_isMT32)
-		delay += 40;
-
-	_output->sysEx(msg, length);
-	_system->delayMillis(delay);
-}
-
-void MidiOutput::sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size) {
-	int bufferSize = 8 + size;
-	byte *outBuffer = new byte[bufferSize];
-	assert(outBuffer);
-
-	outBuffer[0] = 0x41;
-	outBuffer[1] = 0x10;
-	outBuffer[2] = 0x16;
-	outBuffer[3] = 0x12;
-
-	outBuffer[4] = p1;
-	outBuffer[5] = p2;
-	outBuffer[6] = p3;
-
-	memcpy(outBuffer + 7, buffer, size);
-
-	uint16 checkSum = p1 + p2 + p3;
-	for (int i = 0; i < size; ++i)
-		checkSum += buffer[i];
-	checkSum &= 0x7F;
-	checkSum -= 0x80;
-	checkSum = -checkSum;
-	checkSum &= 0x7F;
-
-	outBuffer[7+size] = checkSum;
-
-	sysEx(outBuffer, bufferSize);
-
-	delete[] outBuffer;
-}
-
-void MidiOutput::metaEvent(byte type, byte *data, uint16 length) {
-	if (type == 0x2F) // End of Track
-		deinitSource(_curSource);
-
-	_output->metaEvent(type, data, length);
-}
-
-void MidiOutput::setSourceVolume(int source, int volume, bool apply) {
-	_sources[source].volume = volume;
-
-	if (apply) {
-		for (int i = 0; i < 16; ++i) {
-			// Controller 0 in the state table should always be '7' aka
-			// volume control
-			byte realVol = (_sources[source].controllers[i][0].value * volume) >> 8;
-			sendIntern(0xB0, i, 0x07, realVol);
-		}
-	}
-}
-
-void MidiOutput::initSource(int source) {
-	memset(_sources[source].notes, -1, sizeof(_sources[source].notes));
-
-	for (int i = 0; i < 16; ++i) {
-		_sources[source].channelMap[i] = i;
-		_sources[source].channelProgram[i] = 0xFF;
-		_sources[source].channelPW[i] = -1;
-
-		for (int j = 0; j < 9; ++j)
-			_sources[source].controllers[i][j] = _channels[i].controllers[j];
-	}
-}
-
-void MidiOutput::deinitSource(int source) {
-	for (int i = 0; i < 16; ++i) {
-		for (int j = 0; j < 9; ++j) {
-			const Controller &cont = _sources[source].controllers[i][j];
-
-			if (cont.controller == 0x40) {
-				if (cont.value >= 0x40)
-					sendIntern(0xB0, i, 0x40, 0);
-			} else if (cont.controller == 0x6E) {
-				if (cont.value >= 0x40) {
-					stopNotesOnChannel(i);
-					unlockChannel(_sources[source].channelMap[i]);
-					_sources[source].channelMap[i] = i;
-				}
-			} else if (cont.controller == 0x6F) {
-				if (cont.value >= 0x40)
-					_channels[i].flags &= ~kChannelProtected;
-			} else if (cont.controller == 0x70) {
-				if (cont.value >= 0x40)
-					sendIntern(0xB0, i, 0x70, 0);
-			}
-		}
-	}
-}
-
-int MidiOutput::lockChannel() {
-	int channel = -1;
-	int notes = 0xFF;
-	byte flags = kChannelLocked | kChannelProtected;
-
-	while (channel == -1) {
-		for (int i = _isMT32 ? 8 : 15; i >= 1; --i) {
-			if (_channels[i].flags & flags)
-				continue;
-			if (_channels[i].noteCount < notes) {
-				channel = i;
-				notes = _channels[i].noteCount;
-			}
-		}
-
-		if (channel == -1) {
-			if (flags & kChannelProtected)
-				flags &= ~kChannelProtected;
-			else
-				break;
-		}
-	}
-
-	if (channel == -1)
-		return -1;
-
-	sendIntern(0xB0, channel, 0x40, 0);
-	stopNotesOnChannel(channel);
-	_channels[channel].noteCount = 0;
-	_channels[channel].flags |= kChannelLocked;
-
-	return channel;
-}
-
-void MidiOutput::unlockChannel(int channel) {
-	if (!(_channels[channel].flags & kChannelLocked))
-		return;
-
-	_channels[channel].flags &= ~kChannelLocked;
-	_channels[channel].noteCount = 0;
-	sendIntern(0xB0, channel, 0x40, 0);
-	sendIntern(0xB0, channel, 0x7B, 0);
-
-	for (int i = 0; i < 9; ++i) {
-		if (_channels[channel].controllers[i].value != 0xFF)
-			sendIntern(0xB0, channel, _channels[channel].controllers[i].controller, _channels[channel].controllers[i].value);
-	}
-
-	if (_channels[channel].program != 0xFF)
-		sendIntern(0xC0, channel, _channels[channel].program, 0);
-
-	if (_channels[channel].pitchWheel != -1)
-		sendIntern(0xE0, channel, _channels[channel].pitchWheel & 0xFF, (_channels[channel].pitchWheel >> 8) & 0xFF);
-}
-
-void MidiOutput::stopNotesOnChannel(int channel) {
-	for (int i = 0; i < 4; ++i) {
-		SoundSource &sound = _sources[i];
-		for (int j = 0; j < 32; ++j) {
-			if (sound.notes[j].channel == channel) {
-				sound.notes[j].channel = 0xFF;
-				sendIntern(0x80, sound.channelMap[channel], sound.notes[j].note, 0);
-				--_channels[sound.channelMap[channel]].noteCount;
-			}
-		}
-	}
-}
-
-void MidiOutput::allSoundsOff() {
-	for (int i = 0; i < 16; ++i) {
-		stopNotesOnChannel(i);
-		sendIntern(0xB0, i, 0x78, 0);
-	}
-}
-
-} // End of namespace Kyra
diff --git a/engines/kyra/sound/drivers/midi.h b/engines/kyra/sound/drivers/midi.h
deleted file mode 100644
index 60534e0f1b..0000000000
--- a/engines/kyra/sound/drivers/midi.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef KYRA_SOUND_MIDIDRIVER_H
-#define KYRA_SOUND_MIDIDRIVER_H
-
-#include "kyra/sound/sound_intern.h"
-
-namespace Kyra {
-
-class MidiOutput : public MidiDriver_BASE {
-public:
-	MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32);
-	~MidiOutput() override;
-
-	void setSourceVolume(int source, int volume, bool apply=false);
-
-	void initSource(int source);
-	void deinitSource(int source);
-	void stopNotesOnChannel(int channel);
-	void allSoundsOff();
-
-	void setSoundSource(int source) { _curSource = source; }
-
-	// MidiDriver_BASE interface
-	void send(uint32 b) override;
-	void sysEx(const byte *msg, uint16 length) override;
-	void metaEvent(byte type, byte *data, uint16 length) override;
-
-	// TODO: Get rid of the following two methods
-	void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { _output->setTimerCallback(timerParam, timerProc); }
-	uint32 getBaseTempo() { return _output->getBaseTempo(); }
-
-
-private:
-	void sendIntern(const byte event, const byte channel, byte param1, const byte param2);
-	void sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size);
-
-	OSystem *_system;
-	MidiDriver *_output;
-
-	bool _isMT32;
-	bool _defaultMT32;
-
-	struct Controller {
-		byte controller;
-		byte value;
-	};
-
-	enum {
-		kChannelLocked = 0x80,
-		kChannelProtected = 0x40
-	};
-
-	struct Channel {
-		byte flags;
-
-		byte program;
-		int16 pitchWheel;
-
-		byte noteCount;
-
-		Controller controllers[9];
-	} _channels[16];
-
-	int lockChannel();
-	void unlockChannel(int channel);
-
-	int _curSource;
-
-	struct SoundSource {
-		int volume;
-
-		int8 channelMap[16];
-		byte channelProgram[16];
-		int16 channelPW[16];
-		Controller controllers[16][9];
-
-		struct Note {
-			byte channel;
-			byte note;
-		};
-
-		Note notes[32];
-	} _sources[4];
-};
-
-} // End of namespace Kyra
-
-#endif
diff --git a/engines/kyra/sound/sound_intern.h b/engines/kyra/sound/sound_intern.h
index 5a8c0c6733..c7f724e3c9 100644
--- a/engines/kyra/sound/sound_intern.h
+++ b/engines/kyra/sound/sound_intern.h
@@ -28,6 +28,7 @@
 #include "kyra/sound/sound_pc_v1.h"
 
 #include "audio/midiparser.h"
+#include "audio/miles.h"
 #include "audio/softsynth/emumidi.h"
 #include "audio/softsynth/fmtowns_pc98/towns_audio.h"
 
@@ -105,7 +106,7 @@ private:
 
 	bool _nativeMT32;
 	MidiDriver *_driver;
-	MidiOutput *_output;
+	Audio::MidiDriver_Miles_Midi *_output;
 
 	Common::Mutex _mutex;
 };
diff --git a/engines/kyra/sound/sound_pc_midi.cpp b/engines/kyra/sound/sound_pc_midi.cpp
index b1a681dba7..f2c0502c42 100644
--- a/engines/kyra/sound/sound_pc_midi.cpp
+++ b/engines/kyra/sound/sound_pc_midi.cpp
@@ -20,7 +20,7 @@
  *
  */
 
-#include "kyra/sound/drivers/midi.h"
+#include "kyra/sound/sound_intern.h"
 
 #include "kyra/resource/resource.h"
 
@@ -40,11 +40,13 @@ SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *dri
 	_currentResourceSet = 0;
 	memset(&_resInfo, 0, sizeof(_resInfo));
 
-	_music = MidiParser::createParser_XMIDI();
+	_music = MidiParser::createParser_XMIDI(MidiParser::defaultXMidiCallback, NULL, NULL, NULL, 0);
 	assert(_music);
+	_music->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
 	for (int i = 0; i < 3; ++i) {
-		_sfx[i] = MidiParser::createParser_XMIDI();
+		_sfx[i] = MidiParser::createParser_XMIDI(MidiParser::defaultXMidiCallback, NULL, NULL, NULL, i + 1);
 		assert(_sfx[i]);
+		_sfx[i]->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
 	}
 
 	_musicVolume = _sfxVolume = 0;
@@ -98,8 +100,12 @@ SoundMidiPC::~SoundMidiPC() {
 }
 
 bool SoundMidiPC::init() {
-	_output = new MidiOutput(_vm->_system, _driver, _nativeMT32, (_type != kMidiGM));
+	_output = Audio::MidiDriver_Miles_MIDI_create(_type == kMidiGM ? MT_GM : MT_MT32, "");
 	assert(_output);
+	int returnCode = _output->open(_driver, _nativeMT32);
+	if (returnCode > 0) {
+		return false;
+	}
 
 	updateVolumeSettings();
 
@@ -179,11 +185,11 @@ void SoundMidiPC::updateVolumeSettings() {
 	const int newMusVol = (mute ? 0 : ConfMan.getInt("music_volume"));
 	_sfxVolume = (mute ? 0 : ConfMan.getInt("sfx_volume"));
 
-	_output->setSourceVolume(0, newMusVol, newMusVol != _musicVolume);
+	_output->setSourceVolume(0, newMusVol);
 	_musicVolume = newMusVol;
 
 	for (int i = 1; i < 4; ++i)
-		_output->setSourceVolume(i, _sfxVolume, false);
+		_output->setSourceVolume(i, _sfxVolume);
 }
 
 void SoundMidiPC::initAudioResourceInfo(int set, void *info) {
@@ -224,15 +230,13 @@ void SoundMidiPC::loadSoundFile(Common::String file) {
 	// When loading a new file we stop all notes
 	// still running on our own, just to prevent
 	// glitches
-	for (int i = 0; i < 16; ++i)
-		_output->stopNotesOnChannel(i);
+	_output->allNotesOff();
 
 	delete[] _musicFile;
 	uint32 fileSize = 0;
 	_musicFile = _vm->resource()->fileData(file.c_str(), &fileSize);
 	_mFileName = file;
 
-	_output->setSoundSource(0);
 	_music->loadMusic(_musicFile, fileSize);
 	_music->stopPlaying();
 
@@ -240,7 +244,6 @@ void SoundMidiPC::loadSoundFile(Common::String file) {
 	// we setup sfx to play from music file as well
 	if (_vm->game() == GI_KYRA1) {
 		for (int i = 0; i < 3; ++i) {
-			_output->setSoundSource(i+1);
 			_sfx[i]->loadMusic(_musicFile, fileSize);
 			_sfx[i]->stopPlaying();
 		}
@@ -269,7 +272,6 @@ void SoundMidiPC::loadSfxFile(Common::String file) {
 	_sFileName = file;
 
 	for (int i = 0; i < 3; ++i) {
-		_output->setSoundSource(i+1);
 		_sfx[i]->loadMusic(_sfxFile, fileSize);
 		_sfx[i]->stopPlaying();
 	}
@@ -283,23 +285,20 @@ void SoundMidiPC::playTrack(uint8 track) {
 
 	// The following two lines are meant as a fix for bug #6314.
 	// It is on purpose that they are outside the mutex lock.
-	_output->allSoundsOff();
-	_vm->delay(250);
+	_output->allNotesOff();
+	//_vm->delay(250);
 
 	Common::StackLock lock(_mutex);
 	_fadeMusicOut = false;
 	
-	_output->setSourceVolume(0, _musicVolume, true);
+	_output->setSourceVolume(0, _musicVolume);
 
-	_output->initSource(0);
-	_output->setSourceVolume(0, _musicVolume, true);
 	_music->setTrack(track);
 }
 
 void SoundMidiPC::haltTrack() {
 	Common::StackLock lock(_mutex);
 
-	_output->setSoundSource(0);
 	_music->stopPlaying();
 	_output->deinitSource(0);
 }
@@ -317,7 +316,6 @@ void SoundMidiPC::playSoundEffect(uint8 track, uint8) {
 	Common::StackLock lock(_mutex);
 	for (int i = 0; i < 3; ++i) {
 		if (!_sfx[i]->isPlaying()) {
-			_output->initSource(i+1);
 			_sfx[i]->setTrack(track);
 			return;
 		}
@@ -328,7 +326,6 @@ void SoundMidiPC::stopAllSoundEffects() {
 	Common::StackLock lock(_mutex);
 
 	for (int i = 0; i < 3; ++i) {
-		_output->setSoundSource(i+1);
 		_sfx[i]->stopPlaying();
 		_output->deinitSource(i+1);
 	}
@@ -348,8 +345,7 @@ void SoundMidiPC::pause(bool paused) {
 		_music->setMidiDriver(0);
 		for (int i = 0; i < 3; i++)
 			_sfx[i]->setMidiDriver(0);
-		for (int i = 0; i < 16; i++)
-			_output->stopNotesOnChannel(i);
+		_output->allNotesOff();
 	} else {
 		_music->setMidiDriver(_output);
 		for (int i = 0; i < 3; ++i)
@@ -368,30 +364,27 @@ void SoundMidiPC::onTimer(void *data) {
 
 		if (midi->_fadeStartTime + musicFadeTime > midi->_vm->_system->getMillis()) {
 			int volume = (byte)((musicFadeTime - (midi->_vm->_system->getMillis() - midi->_fadeStartTime)) * midi->_musicVolume / musicFadeTime);
-			midi->_output->setSourceVolume(0, volume, true);
+			midi->_output->setSourceVolume(0, volume);
 		} else {
-			for (int i = 0; i < 16; ++i)
-				midi->_output->stopNotesOnChannel(i);
-			for (int i = 0; i < 4; ++i)
-				midi->_output->deinitSource(i);
-
-			midi->_output->setSoundSource(0);
 			midi->_music->stopPlaying();
 
 			for (int i = 0; i < 3; ++i) {
-				midi->_output->setSoundSource(i+1);
 				midi->_sfx[i]->stopPlaying();
 			}
 
+			for (int i = 0; i < 4; ++i)
+				midi->_output->deinitSource(i);
+
 			midi->_fadeMusicOut = false;
+
+			// Restore music volume
+			midi->_output->setSourceVolume(0, midi->_musicVolume);
 		}
 	}
 
-	midi->_output->setSoundSource(0);
 	midi->_music->onTimer();
 
 	for (int i = 0; i < 3; ++i) {
-		midi->_output->setSoundSource(i+1);
 		midi->_sfx[i]->onTimer();
 	}
 }




More information about the Scummvm-git-logs mailing list