[Scummvm-cvs-logs] scummvm master -> 0655839a4ff2a3805919c0b9bfa8f035d015e641

m-kiewitz m_kiewitz at users.sourceforge.net
Sat Jun 27 22:45:57 CEST 2015


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:
0655839a4f COMMON: move Miles Audio AdLib+MT32 to common


Commit: 0655839a4ff2a3805919c0b9bfa8f035d015e641
    https://github.com/scummvm/scummvm/commit/0655839a4ff2a3805919c0b9bfa8f035d015e641
Author: Martin Kiewitz (m_kiewitz at users.sourceforge.net)
Date: 2015-06-27T22:44:39+02:00

Commit Message:
COMMON: move Miles Audio AdLib+MT32 to common

- remove Miles Audio from Sherlock engine
- put Miles Audio into common audio (namespace Audio)
- Miles Audio is used at least by the engines
TINSEL, GROOVIE, TOLTECS, SAGA and KYRA
This way it can be used by the other engines

Changed paths:
  A audio/miles.h
  A audio/miles_adlib.cpp
  A audio/miles_mt32.cpp
  R engines/sherlock/tattoo/drivers/tattoo_adlib.cpp
  R engines/sherlock/tattoo/drivers/tattoo_mididriver.h
  R engines/sherlock/tattoo/drivers/tattoo_mt32.cpp
    audio/module.mk
    engines/sherlock/module.mk
    engines/sherlock/music.cpp



diff --git a/audio/miles.h b/audio/miles.h
new file mode 100644
index 0000000..50fcae2
--- /dev/null
+++ b/audio/miles.h
@@ -0,0 +1,68 @@
+/* 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 AUDIO_MILES_MIDIDRIVER_H
+#define AUDIO_MILES_MIDIDRIVER_H
+
+#include "audio/mididrv.h"
+#include "common/error.h"
+
+namespace Audio {
+
+#define MILES_MIDI_CHANNEL_COUNT 16
+
+// 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_MODULATION 1
+#define MILES_CONTROLLER_VOLUME 7
+#define MILES_CONTROLLER_EXPRESSION 11
+#define MILES_CONTROLLER_PANNING 10
+#define MILES_CONTROLLER_SUSTAIN 64
+#define MILES_CONTROLLER_PITCH_RANGE 6
+#define MILES_CONTROLLER_RESET_ALL 121
+#define MILES_CONTROLLER_ALL_NOTES_OFF 123
+#define MILES_CONTROLLER_PATCH_REVERB 59
+#define MILES_CONTROLLER_PATCH_BENDER 60
+#define MILES_CONTROLLER_REVERB_MODE 61
+#define MILES_CONTROLLER_REVERB_TIME 62
+#define MILES_CONTROLLER_REVERB_LEVEL 63
+#define MILES_CONTROLLER_RHYTHM_KEY_TIMBRE 58
+
+// 3 SysEx controllers, each range 14
+#define MILES_CONTROLLER_SYSEX_RANGE_BEGIN 32
+#define MILES_CONTROLLER_SYSEX_RANGE_END 64
+
+#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110
+#define MILES_CONTROLLER_XMIDI_RANGE_END 120
+
+// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
+#define MILES_PITCHBENDER_DEFAULT 0x2000
+
+extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String instrumentDataFilename, const Common::String instrumentDataFilenameOPL3);
+
+extern MidiDriver *MidiDriver_Miles_MT32_create(const Common::String instrumentDataFilename);
+
+} // End of namespace Audio
+
+#endif // AUDIO_MILES_MIDIDRIVER_H
diff --git a/audio/miles_adlib.cpp b/audio/miles_adlib.cpp
new file mode 100644
index 0000000..63c644b
--- /dev/null
+++ b/audio/miles_adlib.cpp
@@ -0,0 +1,1135 @@
+/* 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/file.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "audio/fmopl.h"
+#include "audio/softsynth/emumidi.h"
+
+namespace Audio {
+
+// Miles Audio AdLib/OPL3 driver
+//
+// TODO: currently missing: OPL3 4-op voices
+
+#define MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX 20
+#define MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX 18
+
+#define MILES_ADLIB_PERCUSSION_BANK 127
+
+#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT 27
+#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT 100
+
+enum kMilesAdLibUpdateFlags {
+	kMilesAdLibUpdateFlags_None    = 0,
+	kMilesAdLibUpdateFlags_Reg_20  = 1 << 0,
+	kMilesAdLibUpdateFlags_Reg_40  = 1 << 1,
+	kMilesAdLibUpdateFlags_Reg_60  = 1 << 2, // register 0x6x + 0x8x
+	kMilesAdLibUpdateFlags_Reg_C0  = 1 << 3,
+	kMilesAdLibUpdateFlags_Reg_E0  = 1 << 4,
+	kMilesAdLibUpdateFlags_Reg_A0  = 1 << 5, // register 0xAx + 0xBx
+	kMilesAdLibUpdateFlags_Reg_All = 0x3F
+};
+
+uint16 milesAdLibOperator1Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
+	0x0000, 0x0001, 0x0002, 0x0008, 0x0009, 0x000A, 0x0010, 0x0011, 0x0012,
+	0x0100, 0x0101, 0x0102, 0x0108, 0x0109, 0x010A, 0x0110, 0x0111, 0x0112
+};
+
+uint16 milesAdLibOperator2Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
+	0x0003, 0x0004, 0x0005, 0x000B, 0x000C, 0x000D, 0x0013, 0x0014, 0x0015,
+	0x0103, 0x0104, 0x0105, 0x010B, 0x010C, 0x010D, 0x0113, 0x0114, 0x0115
+};
+
+uint16 milesAdLibChannelRegister[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
+	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008,
+	0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108
+};
+
+struct InstrumentEntry {
+	byte bankId;
+	byte patchId;
+	int16 transposition;
+	byte reg20op1;
+	byte reg40op1;
+	byte reg60op1;
+	byte reg80op1;
+	byte regE0op1;
+	byte reg20op2;
+	byte reg40op2;
+	byte reg60op2;
+	byte reg80op2;
+	byte regE0op2;
+	byte regC0;
+};
+
+// hardcoded, dumped from ADLIB.MDI
+uint16 milesAdLibFrequencyLookUpTable[] = {
+	0x02B2, 0x02B4, 0x02B7, 0x02B9, 0x02BC, 0x02BE, 0x02C1, 0x02C3, 0x02C6, 0x02C9, 0x02CB, 0x02CE,
+	0x02D0, 0x02D3, 0x02D6, 0x02D8, 0x02DB, 0x02DD, 0x02E0, 0x02E3, 0x02E5, 0x02E8, 0x02EB, 0x02ED,
+	0x02F0, 0x02F3, 0x02F6, 0x02F8, 0x02FB, 0x02FE, 0x0301, 0x0303, 0x0306, 0x0309, 0x030C, 0x030F,
+	0x0311, 0x0314, 0x0317, 0x031A, 0x031D, 0x0320, 0x0323, 0x0326, 0x0329, 0x032B, 0x032E, 0x0331,
+	0x0334, 0x0337, 0x033A, 0x033D, 0x0340, 0x0343, 0x0346, 0x0349, 0x034C, 0x034F, 0x0352, 0x0356,
+	0x0359, 0x035C, 0x035F, 0x0362, 0x0365, 0x0368, 0x036B, 0x036F, 0x0372, 0x0375, 0x0378, 0x037B,
+	0x037F, 0x0382, 0x0385, 0x0388, 0x038C, 0x038F, 0x0392, 0x0395, 0x0399, 0x039C, 0x039F, 0x03A3,
+	0x03A6, 0x03A9, 0x03AD, 0x03B0, 0x03B4, 0x03B7, 0x03BB, 0x03BE, 0x03C1, 0x03C5, 0x03C8, 0x03CC,
+	0x03CF, 0x03D3, 0x03D7, 0x03DA, 0x03DE, 0x03E1, 0x03E5, 0x03E8, 0x03EC, 0x03F0, 0x03F3, 0x03F7,
+	0x03FB, 0x03FE, 0xFE01, 0xFE03, 0xFE05, 0xFE07, 0xFE08, 0xFE0A, 0xFE0C, 0xFE0E, 0xFE10, 0xFE12,
+	0xFE14, 0xFE16, 0xFE18, 0xFE1A, 0xFE1C, 0xFE1E, 0xFE20, 0xFE21, 0xFE23, 0xFE25, 0xFE27, 0xFE29,
+	0xFE2B, 0xFE2D, 0xFE2F, 0xFE31, 0xFE34, 0xFE36, 0xFE38, 0xFE3A, 0xFE3C, 0xFE3E, 0xFE40, 0xFE42,
+	0xFE44, 0xFE46, 0xFE48, 0xFE4A, 0xFE4C, 0xFE4F, 0xFE51, 0xFE53, 0xFE55, 0xFE57, 0xFE59, 0xFE5C,
+	0xFE5E, 0xFE60, 0xFE62, 0xFE64, 0xFE67, 0xFE69, 0xFE6B, 0xFE6D, 0xFE6F, 0xFE72, 0xFE74, 0xFE76,
+	0xFE79, 0xFE7B, 0xFE7D, 0xFE7F, 0xFE82, 0xFE84, 0xFE86, 0xFE89, 0xFE8B, 0xFE8D, 0xFE90, 0xFE92,
+	0xFE95, 0xFE97, 0xFE99, 0xFE9C, 0xFE9E, 0xFEA1, 0xFEA3, 0xFEA5, 0xFEA8, 0xFEAA, 0xFEAD, 0xFEAF
+};
+
+// hardcoded, dumped from ADLIB.MDI
+uint16 milesAdLibVolumeSensitivityTable[] = {
+	82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127
+};
+
+
+class MidiDriver_Miles_AdLib : public MidiDriver_Emulated {
+public:
+	MidiDriver_Miles_AdLib(Audio::Mixer *mixer, InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
+	virtual ~MidiDriver_Miles_AdLib();
+
+	// MidiDriver
+	int open();
+	void close();
+	void send(uint32 b);
+	MidiChannel *allocateChannel() { return NULL; }
+	MidiChannel *getPercussionChannel() { return NULL; }
+
+	// AudioStream
+	bool isStereo() const { return _modeStereo; }
+	int getRate() const { return _mixer->getOutputRate(); }
+	int getPolyphony() const { return _modePhysicalFmVoicesCount; }
+	bool hasRhythmChannel() const { return false; }
+
+	// MidiDriver_Emulated
+	void generateSamples(int16 *buf, int len);
+
+	void setVolume(byte volume);
+	virtual uint32 property(int prop, uint32 param);
+
+private:
+	bool _modeOPL3;
+	byte _modePhysicalFmVoicesCount;
+	byte _modeVirtualFmVoicesCount;
+	bool _modeStereo;
+
+	// Structure to hold information about current status of MIDI Channels
+	struct MidiChannelEntry {
+		byte   currentPatchBank;
+		const  InstrumentEntry *currentInstrumentPtr;
+		uint16 currentPitchBender;
+		byte   currentPitchRange;
+		byte   currentVoiceProtection;
+
+		byte   currentVolume;
+		byte   currentVolumeExpression;
+
+		byte   currentPanning;
+
+		byte   currentModulation;
+		byte   currentSustain;
+
+		byte   currentActiveVoicesCount;
+
+		MidiChannelEntry() : currentPatchBank(0),
+							currentInstrumentPtr(NULL),
+							currentPitchBender(MILES_PITCHBENDER_DEFAULT),
+							currentVoiceProtection(0),
+							currentVolume(0), currentVolumeExpression(0),
+							currentPanning(0),
+							currentModulation(0),
+							currentSustain(0),
+							currentActiveVoicesCount(0) { }
+	};
+
+	// Structure to hold information about current status of virtual FM Voices
+	struct VirtualFmVoiceEntry {
+		bool   inUse;
+		byte   actualMidiChannel;
+
+		const  InstrumentEntry *currentInstrumentPtr;
+
+		bool   isPhysical;
+		byte   physicalFmVoice;
+
+		uint16 currentPriority;
+
+		byte   currentOriginalMidiNote;
+		byte   currentNote;
+		int16  currentTransposition;
+		byte   currentVelocity;
+
+		bool   sustained;
+
+		VirtualFmVoiceEntry(): inUse(false),
+								actualMidiChannel(0),
+								currentInstrumentPtr(NULL),
+								isPhysical(false), physicalFmVoice(0),
+								currentPriority(0),
+								currentOriginalMidiNote(0),
+								currentNote(0),
+								currentTransposition(0),
+								currentVelocity(0),
+								sustained(false) { }
+	};
+
+	// Structure to hold information about current status of physical FM Voices
+	struct PhysicalFmVoiceEntry {
+		bool   inUse;
+		byte   virtualFmVoice;
+
+		byte   currentB0hReg;
+
+		PhysicalFmVoiceEntry(): inUse(false),
+								virtualFmVoice(0),
+								currentB0hReg(0) { }
+	};
+
+	OPL::OPL *_opl;
+	int _masterVolume;
+
+	// stores information about all MIDI channels (not the actual OPL FM voice channels!)
+	MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
+
+	// stores information about all virtual OPL FM voices
+	VirtualFmVoiceEntry _virtualFmVoices[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
+
+	// stores information about all physical OPL FM voices
+	PhysicalFmVoiceEntry _physicalFmVoices[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX];
+
+	// holds all instruments
+	InstrumentEntry *_instrumentTablePtr;
+	uint16           _instrumentTableCount;
+
+	bool circularPhysicalAssignment;
+	byte circularPhysicalAssignmentFmVoice;
+
+protected:
+	void onTimer();
+
+private:
+	void resetData();
+	void resetAdLib();
+	void resetAdLibOperatorRegisters(byte baseRegister, byte value);
+	void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
+
+	void setRegister(int reg, int value);
+
+	int16 searchFreeVirtualFmVoiceChannel();
+	int16 searchFreePhysicalFmVoiceChannel();
+
+	void noteOn(byte midiChannel, byte note, byte velocity);
+	void noteOff(byte midiChannel, byte note);
+
+	void prioritySort();
+
+	void releaseFmVoice(byte virtualFmVoice);
+
+	void updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags);
+
+	void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
+	void programChange(byte midiChannel, byte patchId);
+
+	const InstrumentEntry *searchInstrument(byte bankId, byte patchId);
+
+	void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
+};
+
+MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(Audio::Mixer *mixer, InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount)
+	: MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) {
+
+	_instrumentTablePtr = instrumentTablePtr;
+	_instrumentTableCount = instrumentTableCount;
+
+	// Set up for OPL3, we will downgrade in case we can't create OPL3 emulator
+	// regular AdLib (OPL2) card
+	_modeOPL3 = true;
+	_modeVirtualFmVoicesCount = 20;
+	_modePhysicalFmVoicesCount = 18;
+	_modeStereo = true;
+
+	// Older Miles Audio drivers did not do a circular assign for physical FM-voices
+	// Sherlock Holmes 2 used the circular assign
+	circularPhysicalAssignment = true;
+	// this way the first circular physical FM-voice search will start at FM-voice 0
+	circularPhysicalAssignmentFmVoice = MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX;
+
+	resetData();
+}
+
+MidiDriver_Miles_AdLib::~MidiDriver_Miles_AdLib() {
+	delete[] _instrumentTablePtr; // is created in factory MidiDriver_Miles_AdLib_create()
+}
+
+int MidiDriver_Miles_AdLib::open() {
+	int rate = _mixer->getOutputRate();
+
+	if (_modeOPL3) {
+		// Try to create OPL3 first
+		_opl = OPL::Config::create(OPL::Config::kOpl3);
+	}
+	if (!_opl) {
+		// not created yet, downgrade to OPL2
+		_modeOPL3 = false;
+		_modeVirtualFmVoicesCount = 16;
+		_modePhysicalFmVoicesCount = 9;
+		_modeStereo = false;
+
+		_opl = OPL::Config::create(OPL::Config::kOpl2);
+	}
+
+	if (!_opl) {
+		// We still got nothing -> can't do anything anymore
+		return -1;
+	}
+
+	_opl->init(rate);
+
+	MidiDriver_Emulated::open();
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
+
+	resetAdLib();
+
+	return 0;
+}
+
+void MidiDriver_Miles_AdLib::close() {
+	_mixer->stopHandle(_mixerSoundHandle);
+
+	delete _opl;
+}
+
+void MidiDriver_Miles_AdLib::setVolume(byte volume) {
+	_masterVolume = volume;
+	//renewNotes(-1, true);
+}
+
+void MidiDriver_Miles_AdLib::onTimer() {
+}
+
+void MidiDriver_Miles_AdLib::resetData() {
+	memset(_midiChannels, 0, sizeof(_midiChannels));
+	memset(_virtualFmVoices, 0, sizeof(_virtualFmVoices));
+	memset(_physicalFmVoices, 0, sizeof(_physicalFmVoices));
+
+	for (byte midiChannel = 0; midiChannel < MILES_MIDI_CHANNEL_COUNT; midiChannel++) {
+		_midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT;
+		_midiChannels[midiChannel].currentPitchRange = 12;
+		// Miles Audio 2: hardcoded pitch range as a global (not channel specific), set to 12
+		// Miles Audio 3: pitch range per MIDI channel
+		_midiChannels[midiChannel].currentVolumeExpression = 127;
+	}
+
+}
+
+void MidiDriver_Miles_AdLib::resetAdLib() {
+	if (_modeOPL3) {
+		setRegister(0x105, 1); // enable OPL3
+		setRegister(0x104, 0); // activate 18 2-operator FM-voices
+	}
+
+	setRegister(0x01, 0x20); // enable waveform control on both operators
+	setRegister(0x04, 0xE0); // Timer control
+
+	setRegister(0x08, 0); // select FM music mode
+	setRegister(0xBD, 0); // disable Rhythm
+
+	// reset FM voice instrument data
+	resetAdLibOperatorRegisters(0x20, 0);
+	resetAdLibOperatorRegisters(0x60, 0);
+	resetAdLibOperatorRegisters(0x80, 0);
+	resetAdLibFMVoiceChannelRegisters(0xA0, 0);
+	resetAdLibFMVoiceChannelRegisters(0xB0, 0);
+	resetAdLibFMVoiceChannelRegisters(0xC0, 0);
+	resetAdLibOperatorRegisters(0xE0, 0);
+	resetAdLibOperatorRegisters(0x40, 0x3F);
+}
+
+void MidiDriver_Miles_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
+	byte physicalFmVoice = 0;
+
+	for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
+		setRegister(baseRegister + milesAdLibOperator1Register[physicalFmVoice], value);
+		setRegister(baseRegister + milesAdLibOperator2Register[physicalFmVoice], value);
+	}
+}
+
+void MidiDriver_Miles_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
+	byte physicalFmVoice = 0;
+
+	for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
+		setRegister(baseRegister + milesAdLibChannelRegister[physicalFmVoice], value);
+	}
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Miles_AdLib::send(uint32 b) {
+	byte command = b & 0xf0;
+	byte channel = b & 0xf;
+	byte op1 = (b >> 8) & 0xff;
+	byte op2 = (b >> 16) & 0xff;
+
+	switch (command) {
+	case 0x80:
+		noteOff(channel, op1);
+		break;
+	case 0x90:
+		noteOn(channel, op1, op2);
+		break;
+	case 0xb0: // Control change
+		controlChange(channel, op1, op2);
+		break;
+	case 0xc0: // Program Change
+		programChange(channel, op1);
+		break;
+	case 0xa0: // Polyphonic key pressure (aftertouch)
+	case 0xd0: // Channel pressure (aftertouch)
+		// Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
+		break;
+	case 0xe0:
+		pitchBendChange(channel, op1, op2);
+		break;
+	case 0xf0: // SysEx
+		warning("MILES-ADLIB: SysEx: %x", b);
+		break;
+	default:
+		warning("MILES-ADLIB: Unknown event %02x", command);
+	}
+}
+
+void MidiDriver_Miles_AdLib::generateSamples(int16 *data, int len) {
+	if (_modeStereo)
+		len *= 2;
+
+	_opl->readBuffer(data, len);
+}
+
+int16 MidiDriver_Miles_AdLib::searchFreeVirtualFmVoiceChannel() {
+	for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
+		if (!_virtualFmVoices[virtualFmVoice].inUse)
+			return virtualFmVoice;
+	}
+	return -1;
+}
+
+int16 MidiDriver_Miles_AdLib::searchFreePhysicalFmVoiceChannel() {
+	if (!circularPhysicalAssignment) {
+		// Older assign logic
+		for (byte physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
+			if (!_physicalFmVoices[physicalFmVoice].inUse)
+				return physicalFmVoice;
+		}
+	} else {
+		// Newer one
+		// Remembers last physical FM-voice and searches from that spot
+		byte physicalFmVoice = circularPhysicalAssignmentFmVoice;
+		for (byte physicalFmVoiceCount = 0; physicalFmVoiceCount < _modePhysicalFmVoicesCount; physicalFmVoiceCount++) {
+			physicalFmVoice++;
+			if (physicalFmVoice >= _modePhysicalFmVoicesCount)
+				physicalFmVoice = 0;
+			if (!_physicalFmVoices[physicalFmVoice].inUse) {
+				circularPhysicalAssignmentFmVoice = physicalFmVoice;
+				return physicalFmVoice;
+			}
+		}
+	}
+	return -1;
+}
+
+void MidiDriver_Miles_AdLib::noteOn(byte midiChannel, byte note, byte velocity) {
+	const InstrumentEntry *instrumentPtr = NULL;
+
+	if (velocity == 0) {
+		noteOff(midiChannel, note);
+		return;
+	}
+
+	if (midiChannel == 9) {
+		// percussion channel
+		// search for instrument according to given note
+		instrumentPtr = searchInstrument(MILES_ADLIB_PERCUSSION_BANK, note);
+	} else {
+		// directly get instrument of channel
+		instrumentPtr = _midiChannels[midiChannel].currentInstrumentPtr;
+	}
+	if (!instrumentPtr) {
+		warning("MILES-ADLIB: noteOn: invalid instrument");
+		return;
+	}
+
+	//warning("Note On: channel %d, note %d, velocity %d, instrument %d/%d", midiChannel, note, velocity, instrumentPtr->bankId, instrumentPtr->patchId);
+
+	// look for free virtual FM voice
+	int16 virtualFmVoice = searchFreeVirtualFmVoiceChannel();
+
+	if (virtualFmVoice == -1) {
+		// Out of virtual voices,  can't do anything about it
+		return;
+	}
+
+	// Scale back velocity
+	velocity = (velocity & 0x7F) >> 3;
+	velocity = milesAdLibVolumeSensitivityTable[velocity];
+
+	if (midiChannel != 9) {
+		_virtualFmVoices[virtualFmVoice].currentNote = note;
+		_virtualFmVoices[virtualFmVoice].currentTransposition = instrumentPtr->transposition;
+	} else {
+		// Percussion channel
+		_virtualFmVoices[virtualFmVoice].currentNote = instrumentPtr->transposition;
+		_virtualFmVoices[virtualFmVoice].currentTransposition = 0;
+	}
+
+	_virtualFmVoices[virtualFmVoice].inUse = true;
+	_virtualFmVoices[virtualFmVoice].actualMidiChannel = midiChannel;
+	_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote = note;
+	_virtualFmVoices[virtualFmVoice].currentInstrumentPtr = instrumentPtr;
+	_virtualFmVoices[virtualFmVoice].currentVelocity = velocity;
+	_virtualFmVoices[virtualFmVoice].isPhysical = false;
+	_virtualFmVoices[virtualFmVoice].sustained = false;
+	_virtualFmVoices[virtualFmVoice].currentPriority = 32767;
+
+	int16 physicalFmVoice = searchFreePhysicalFmVoiceChannel();
+	if (physicalFmVoice == -1) {
+		// None found
+		// go through priorities and reshuffle voices
+		prioritySort();
+		return;
+	}
+
+	// Another voice active on this MIDI channel
+	_midiChannels[midiChannel].currentActiveVoicesCount++;
+
+	// Mark virtual FM-Voice as being connected to physical FM-Voice
+	_virtualFmVoices[virtualFmVoice].isPhysical = true;
+	_virtualFmVoices[virtualFmVoice].physicalFmVoice = physicalFmVoice;
+
+	// Mark physical FM-Voice as being connected to virtual FM-Voice
+	_physicalFmVoices[physicalFmVoice].inUse = true;
+	_physicalFmVoices[physicalFmVoice].virtualFmVoice = virtualFmVoice;
+
+	// Update the physical FM-Voice
+	updatePhysicalFmVoice(virtualFmVoice, true, kMilesAdLibUpdateFlags_Reg_All);
+}
+
+void MidiDriver_Miles_AdLib::noteOff(byte midiChannel, byte note) {
+	//warning("Note Off: channel %d, note %d", midiChannel, note);
+
+	// Search through all virtual FM-Voices for current midiChannel + note
+	for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
+		if (_virtualFmVoices[virtualFmVoice].inUse) {
+			if ((_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) && (_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote == note)) {
+				// found one
+				if (_midiChannels[midiChannel].currentSustain >= 64) {
+					_virtualFmVoices[virtualFmVoice].sustained = true;
+					continue;
+				}
+				// 
+				releaseFmVoice(virtualFmVoice);
+			}
+		}
+	}
+}
+
+void MidiDriver_Miles_AdLib::prioritySort() {
+	byte   virtualFmVoice = 0;
+	uint16 virtualPriority = 0;
+	uint16 virtualPriorities[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
+	uint16 virtualFmVoicesCount = 0;
+	byte   midiChannel = 0;
+
+	memset(&virtualPriorities, 0, sizeof(virtualPriorities));
+
+	//warning("prioritysort");
+
+	// First calculate priorities for all virtual FM voices, that are in use
+	for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
+		if (_virtualFmVoices[virtualFmVoice].inUse) {
+			virtualFmVoicesCount++;
+
+			midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
+			if (_midiChannels[midiChannel].currentVoiceProtection >= 64) {
+				// Voice protection enabled
+				virtualPriority = 0xFFFF;
+			} else {
+				virtualPriority = _virtualFmVoices[virtualFmVoice].currentPriority;
+			}
+			byte currentActiveVoicesCount = _midiChannels[midiChannel].currentActiveVoicesCount;
+			if (virtualPriority >= currentActiveVoicesCount) {
+				virtualPriority -= _midiChannels[midiChannel].currentActiveVoicesCount;
+			} else {
+				virtualPriority = 0; // overflow, should never happen
+			}
+			virtualPriorities[virtualFmVoice] = virtualPriority;
+		}
+	}
+
+	// 	
+	while (virtualFmVoicesCount) {
+		uint16 unvoicedHighestPriority = 0;
+		byte   unvoicedHighestFmVoice = 0;
+		uint16 voicedLowestPriority = 65535;
+		byte   voicedLowestFmVoice = 0;
+
+		for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
+			if (_virtualFmVoices[virtualFmVoice].inUse) {
+				virtualPriority = virtualPriorities[virtualFmVoice];
+				if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
+					// currently not physical, so unvoiced
+					if (virtualPriority >= unvoicedHighestPriority) {
+						unvoicedHighestPriority = virtualPriority;
+						unvoicedHighestFmVoice  = virtualFmVoice;
+					}
+				} else {
+					// currently physical, so voiced
+					if (virtualPriority <= voicedLowestPriority) {
+						voicedLowestPriority = virtualPriority;
+						voicedLowestFmVoice  = virtualFmVoice;
+					}
+				}
+			}
+		}
+
+		if (unvoicedHighestPriority < voicedLowestPriority)
+			break; // We are done
+
+		if (unvoicedHighestPriority == 0)
+			break;
+
+		// Safety checks
+		assert(_virtualFmVoices[voicedLowestFmVoice].isPhysical);
+		assert(!_virtualFmVoices[unvoicedHighestFmVoice].isPhysical);
+
+		// Steal this physical voice
+		byte physicalFmVoice = _virtualFmVoices[voicedLowestFmVoice].physicalFmVoice;
+
+		//warning("MILES-ADLIB: stealing physical FM-Voice %d from virtual FM-Voice %d for virtual FM-Voice %d", physicalFmVoice, voicedLowestFmVoice, unvoicedHighestFmVoice);
+		//warning("priority old %d, priority new %d", unvoicedHighestPriority, voicedLowestPriority);
+
+		releaseFmVoice(voicedLowestFmVoice);
+
+		// Get some data of the unvoiced highest priority virtual FM Voice
+		midiChannel = _virtualFmVoices[unvoicedHighestFmVoice].actualMidiChannel;
+
+		// Another voice active on this MIDI channel
+		_midiChannels[midiChannel].currentActiveVoicesCount++;
+
+		// Mark virtual FM-Voice as being connected to physical FM-Voice
+		_virtualFmVoices[unvoicedHighestFmVoice].isPhysical = true;
+		_virtualFmVoices[unvoicedHighestFmVoice].physicalFmVoice = physicalFmVoice;
+
+		// Mark physical FM-Voice as being connected to virtual FM-Voice
+		_physicalFmVoices[physicalFmVoice].inUse = true;
+		_physicalFmVoices[physicalFmVoice].virtualFmVoice = unvoicedHighestFmVoice;
+
+		// Update the physical FM-Voice
+		updatePhysicalFmVoice(unvoicedHighestFmVoice, true, kMilesAdLibUpdateFlags_Reg_All);
+
+		virtualFmVoicesCount--;
+	}
+}
+
+void MidiDriver_Miles_AdLib::releaseFmVoice(byte virtualFmVoice) {
+	// virtual Voice not actually played? -> exit
+	if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
+		_virtualFmVoices[virtualFmVoice].inUse = false;
+		return;
+	}
+
+	byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
+	byte physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice;
+
+	// stop note from playing
+	updatePhysicalFmVoice(virtualFmVoice, false, kMilesAdLibUpdateFlags_Reg_A0);
+
+	// this virtual FM voice isn't physical anymore
+	_virtualFmVoices[virtualFmVoice].isPhysical = false;
+	_virtualFmVoices[virtualFmVoice].inUse = false;
+
+	// Remove physical FM-Voice from being active
+	_physicalFmVoices[physicalFmVoice].inUse = false;
+
+	// One less voice active on this MIDI channel
+	assert(_midiChannels[midiChannel].currentActiveVoicesCount);
+	_midiChannels[midiChannel].currentActiveVoicesCount--;	
+}
+
+void MidiDriver_Miles_AdLib::updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags) {
+	byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
+
+	if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
+		// virtual FM-Voice has no physical FM-Voice assigned? -> ignore
+		return;
+	}
+
+	byte                   physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice;
+	const InstrumentEntry *instrumentPtr = _virtualFmVoices[virtualFmVoice].currentInstrumentPtr;
+
+	uint16 op1Reg = milesAdLibOperator1Register[physicalFmVoice];
+	uint16 op2Reg = milesAdLibOperator2Register[physicalFmVoice];
+	uint16 channelReg = milesAdLibChannelRegister[physicalFmVoice];
+
+	uint16 compositeVolume = 0;
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) {
+		// Calculate new volume
+		byte midiVolume = _midiChannels[midiChannel].currentVolume;
+		byte midiVolumeExpression = _midiChannels[midiChannel].currentVolumeExpression;
+		compositeVolume = midiVolume * midiVolumeExpression * 2;
+
+		compositeVolume = compositeVolume >> 8; // get upmost 8 bits
+		if (compositeVolume)
+			compositeVolume++; // round up in case result wasn't 0
+
+		compositeVolume = compositeVolume * _virtualFmVoices[virtualFmVoice].currentVelocity * 2;
+		compositeVolume = compositeVolume >> 8; // get upmost 8 bits
+		if (compositeVolume)
+			compositeVolume++; // round up in case result wasn't 0
+	}
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_20) {
+		// Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
+		byte reg20op1 = instrumentPtr->reg20op1;
+		byte reg20op2 = instrumentPtr->reg20op2;
+
+		if (_midiChannels[midiChannel].currentModulation >= 64) {
+			// set bit 6 (Vibrato)
+			reg20op1 |= 0x40;
+			reg20op2 |= 0x40;
+		}
+		setRegister(0x20 + op1Reg, reg20op1);
+		setRegister(0x20 + op2Reg, reg20op2);
+	}
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) {
+		// Volume (Level Key Scaling / Total Level)
+		byte reg40op1 = instrumentPtr->reg40op1;
+		byte reg40op2 = instrumentPtr->reg40op2;
+
+		uint16 volumeOp1 = (~reg40op1) & 0x3F;
+		uint16 volumeOp2 = (~reg40op2) & 0x3F;
+
+		if (instrumentPtr->regC0 & 1) {
+			// operator 2 enabled
+			// scale volume factor
+			volumeOp1 = (volumeOp1 * compositeVolume) / 127;
+			// 2nd operator always scaled
+		}
+
+		volumeOp2 = (volumeOp2 * compositeVolume) / 127;
+
+		volumeOp1 = (~volumeOp1) & 0x3F; // negate it, so we get the proper value for the register
+		volumeOp2 = (~volumeOp2) & 0x3F; // ditto
+		reg40op1  = (reg40op1 & 0xC0) | volumeOp1; // keep "scaling level" and merge in our volume
+		reg40op2  = (reg40op2 & 0xC0) | volumeOp2;
+
+		setRegister(0x40 + op1Reg, reg40op1);
+		setRegister(0x40 + op2Reg, reg40op2);
+	}
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_60) {
+		// Attack Rate / Decay Rate
+		// Sustain Level / Release Rate
+		byte reg60op1 = instrumentPtr->reg60op1;
+		byte reg60op2 = instrumentPtr->reg60op2;
+		byte reg80op1 = instrumentPtr->reg80op1;
+		byte reg80op2 = instrumentPtr->reg80op2;
+
+		setRegister(0x60 + op1Reg, reg60op1);
+		setRegister(0x60 + op2Reg, reg60op2);
+		setRegister(0x80 + op1Reg, reg80op1);
+		setRegister(0x80 + op2Reg, reg80op2);
+	}
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_E0) {
+		// Waveform Select
+		byte regE0op1 = instrumentPtr->regE0op1;
+		byte regE0op2 = instrumentPtr->regE0op2;
+
+		setRegister(0xE0 + op1Reg, regE0op1);
+		setRegister(0xE0 + op2Reg, regE0op2);
+	}
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_C0) {
+		// Feedback / Algorithm
+		byte regC0 = instrumentPtr->regC0;
+
+		if (_modeOPL3) {
+			// Panning for OPL3
+			byte panning = _midiChannels[midiChannel].currentPanning;
+
+			if (panning <= MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT) {
+				regC0 |= 0x10; // left speaker only
+			} else if (panning >= MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT) {
+				regC0 |= 0x20; // right speaker only
+			} else {
+				regC0 |= 0x30; // center
+			}
+		}
+
+		setRegister(0xC0 + channelReg, regC0);
+	}
+
+	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_A0) {
+		// Frequency / Key-On
+		// Octave / F-Number / Key-On
+		if (!keyOn) {
+			// turn off note
+			byte regB0 = _physicalFmVoices[physicalFmVoice].currentB0hReg & 0x1F; // remove bit 5 "key on"
+			setRegister(0xB0 + channelReg, regB0);
+
+		} else {
+			// turn on note, calculate frequency, octave...
+			int16 pitchBender = _midiChannels[midiChannel].currentPitchBender;
+			byte  pitchRange = _midiChannels[midiChannel].currentPitchRange;
+			int16 currentNote = _virtualFmVoices[virtualFmVoice].currentNote;
+			int16 physicalNote = 0;
+			int16 halfTone = 0;
+			uint16 frequency = 0;
+			uint16 frequencyIdx = 0;
+			byte   octave = 0;
+
+			pitchBender -= 0x2000;
+			pitchBender = pitchBender >> 5; // divide by 32
+			pitchBender = pitchBender * pitchRange; // pitchrange 12: now +0x0C00 to -0xC00
+			// difference between Miles Audio 2 + 3
+			// Miles Audio 2 used a pitch range of 12, which was basically hardcoded
+			// Miles Audio 3 used an array, which got set by control change events
+
+			currentNote += _virtualFmVoices->currentTransposition;
+
+			// Normalize note
+			currentNote -= 24;
+			do {
+				currentNote += 12;
+			} while (currentNote < 0);
+			currentNote += 12;
+
+			do {
+				currentNote -= 12;
+			} while (currentNote > 95);
+
+			// combine note + pitchbender, also adjust by 8 for rounding
+			currentNote = (currentNote << 8) + pitchBender + 8;
+
+			currentNote = currentNote >> 4; // get actual note
+
+			// Normalize
+			currentNote -= (12 * 16);
+			do {
+				currentNote += (12 * 16);
+			} while (currentNote < 0);
+
+			currentNote += (12 * 16);
+			do {
+				currentNote -= (12 * 16);
+			} while (currentNote > ((96 * 16) - 1));
+
+			physicalNote = currentNote >> 4;
+
+			halfTone = physicalNote % 12; // remainder of physicalNote / 12
+
+			frequencyIdx = (halfTone << 4) + (currentNote & 0x0F);
+			assert(frequencyIdx < sizeof(milesAdLibFrequencyLookUpTable));
+			frequency = milesAdLibFrequencyLookUpTable[frequencyIdx];
+
+			octave = (physicalNote / 12) - 1;
+
+			if (frequency & 0x8000)
+				octave++;
+
+			if (octave & 0x80) {
+				octave++;
+				frequency = frequency >> 1;
+			}
+
+			byte regA0 = frequency & 0xFF;
+			byte regB0 = ((frequency >> 8) & 0x03) | (octave << 2) | 0x20;
+
+			setRegister(0xA0 + channelReg, regA0);
+			setRegister(0xB0 + channelReg, regB0);
+
+			_physicalFmVoices[physicalFmVoice].currentB0hReg = regB0;
+		}
+	}
+
+	//warning("end of update voice");
+}
+
+void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
+	uint16 registerUpdateFlags = kMilesAdLibUpdateFlags_None;
+
+	switch (controllerNumber) {
+	case MILES_CONTROLLER_SELECT_PATCH_BANK:
+		_midiChannels[midiChannel].currentPatchBank = controllerValue;
+		break;
+
+	case MILES_CONTROLLER_PROTECT_VOICE:
+		_midiChannels[midiChannel].currentVoiceProtection = controllerValue;
+		break;
+
+	case MILES_CONTROLLER_PROTECT_TIMBRE:
+		// It seems that this can get ignored, because we don't cache timbres at all
+		break;
+
+	case MILES_CONTROLLER_MODULATION:
+		_midiChannels[midiChannel].currentModulation = controllerValue;
+		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20;
+		break;
+
+	case MILES_CONTROLLER_VOLUME:
+		_midiChannels[midiChannel].currentVolume = controllerValue;
+		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
+		break;
+
+	case MILES_CONTROLLER_EXPRESSION:
+		_midiChannels[midiChannel].currentVolumeExpression = controllerValue;
+		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
+		break;
+
+	case MILES_CONTROLLER_PANNING:
+		_midiChannels[midiChannel].currentPanning = controllerValue;
+		if (_modeStereo) {
+			// Update register only in case we are in stereo mode
+			registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_C0;
+		}
+		break;
+
+	case MILES_CONTROLLER_SUSTAIN:
+		_midiChannels[midiChannel].currentSustain = controllerValue;
+		if (controllerValue < 64) {
+			// release sustain TODO
+		}
+		break;
+
+	case MILES_CONTROLLER_PITCH_RANGE:
+		// Miles Audio 3 feature
+		_midiChannels[midiChannel].currentPitchRange = controllerValue;
+		break;
+
+	case MILES_CONTROLLER_RESET_ALL:
+		_midiChannels[midiChannel].currentSustain = 0;
+		// release sustain TODO
+		_midiChannels[midiChannel].currentModulation = 0;
+		_midiChannels[midiChannel].currentVolumeExpression = 127;
+		_midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT;
+		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20 | kMilesAdLibUpdateFlags_Reg_40 | kMilesAdLibUpdateFlags_Reg_A0;
+		break;
+
+	case MILES_CONTROLLER_ALL_NOTES_OFF:
+		for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
+			if (_virtualFmVoices[virtualFmVoice].inUse) {
+				// used
+				if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
+					// by our current MIDI channel -> noteOff
+					noteOff(midiChannel, _virtualFmVoices[virtualFmVoice].currentNote);
+				}
+			}
+		}
+		break;
+
+	default:
+		//warning("MILES-ADLIB: Unsupported control change %d", controllerNumber);
+		break;
+	}
+
+	if (registerUpdateFlags) {
+		for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
+			if (_virtualFmVoices[virtualFmVoice].inUse) {
+				// used
+				if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
+					// by our current MIDI channel -> update
+					updatePhysicalFmVoice(virtualFmVoice, true, registerUpdateFlags);
+				}
+			}
+		}
+	}
+}
+
+void MidiDriver_Miles_AdLib::programChange(byte midiChannel, byte patchId) {
+	const InstrumentEntry *instrumentPtr = NULL;
+	byte patchBank = _midiChannels[midiChannel].currentPatchBank;
+
+	// we check, if we actually have data for the requested instrument...
+	instrumentPtr = searchInstrument(patchBank, patchId);
+	if (!instrumentPtr) {
+		warning("MILES-ADLIB: unknown instrument requested (%d, %d)", patchBank, patchId);
+		return;
+	}
+
+	// and remember it in that case for the current MIDI-channel
+	_midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr;
+}
+
+const InstrumentEntry *MidiDriver_Miles_AdLib::searchInstrument(byte bankId, byte patchId) {
+	const InstrumentEntry *instrumentPtr = _instrumentTablePtr;
+
+	for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
+		if ((instrumentPtr->bankId == bankId) && (instrumentPtr->patchId == patchId)) {
+			return instrumentPtr;
+		}
+		instrumentPtr++;
+	}
+
+	return NULL;
+}
+
+void MidiDriver_Miles_AdLib::pitchBendChange(byte midiChannel, byte parameter1, byte parameter2) {
+	// Miles Audio actually didn't shift parameter 2 1 down in here
+	// which means in memory it used a 15-bit pitch bender, which also means the default was 0x4000
+	if ((parameter1 & 0x80) || (parameter2 & 0x80)) {
+		warning("MILES-ADLIB: invalid pitch bend change");
+		return;
+	}
+	_midiChannels[midiChannel].currentPitchBender = parameter1 | (parameter2 << 7);
+}
+
+void MidiDriver_Miles_AdLib::setRegister(int reg, int value) {
+	if (!(reg & 0x100)) {
+		_opl->write(0x220, reg);
+		_opl->write(0x221, value);
+		//warning("OPL write %x %x (%d)", reg, value, value);
+	} else {
+		_opl->write(0x222, reg & 0xFF);
+		_opl->write(0x223, value);
+		//warning("OPL3 write %x %x (%d)", reg & 0xFF, value, value);
+	}
+}
+
+uint32 MidiDriver_Miles_AdLib::property(int prop, uint32 param) {
+	return 0;
+}
+
+MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String instrumentDataFilename, const Common::String instrumentDataFilenameOPL3) {
+	// Load adlib instrument data from file SAMPLE.AD (OPL3: SAMPLE.OPL)
+	Common::File *fileStream = new Common::File();
+	uint32        fileSize = 0;
+	byte         *fileDataPtr = NULL;
+	uint32        fileDataOffset = 0;
+	uint32        fileDataLeft = 0;
+
+	byte curBankId = 0;
+	byte curPatchId = 0;
+
+	InstrumentEntry *instrumentTablePtr = NULL;
+	uint16           instrumentTableCount = 0;
+	InstrumentEntry *instrumentPtr = NULL;
+	uint32           instrumentOffset = 0;
+	uint16           instrumentDataSize = 0;
+
+	if (!fileStream->open(instrumentDataFilename))
+		error("MILES-ADLIB: could not open instrument file");
+
+	fileSize = fileStream->size();
+
+	fileDataPtr = new byte[fileSize];
+
+	if (fileStream->read(fileDataPtr, fileSize) != fileSize)
+		error("MILES-ADLIB: 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-ADLIB: 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-ADLIB: no instruments in instrument file");
+
+	// Allocate space for instruments
+	instrumentTablePtr = new InstrumentEntry[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 != 14)
+			error("MILES-ADLIB: unsupported instrument size");
+
+		instrumentPtr->transposition = (signed char)fileDataPtr[instrumentOffset + 2];
+		instrumentPtr->reg20op1 = fileDataPtr[instrumentOffset + 3];
+		instrumentPtr->reg40op1 = fileDataPtr[instrumentOffset + 4];
+		instrumentPtr->reg60op1 = fileDataPtr[instrumentOffset + 5];
+		instrumentPtr->reg80op1 = fileDataPtr[instrumentOffset + 6];
+		instrumentPtr->regE0op1 = fileDataPtr[instrumentOffset + 7];
+		instrumentPtr->regC0    = fileDataPtr[instrumentOffset + 8];
+		instrumentPtr->reg20op2 = fileDataPtr[instrumentOffset + 9];
+		instrumentPtr->reg40op2 = fileDataPtr[instrumentOffset + 10];
+		instrumentPtr->reg60op2 = fileDataPtr[instrumentOffset + 11];
+		instrumentPtr->reg80op2 = fileDataPtr[instrumentOffset + 12];
+		instrumentPtr->regE0op2 = fileDataPtr[instrumentOffset + 13];
+
+		// Instrument read, next instrument please
+		instrumentPtr++;
+	}
+
+	// Free instrument file data
+	delete[] fileDataPtr;
+
+	return new MidiDriver_Miles_AdLib(g_system->getMixer(), instrumentTablePtr, instrumentTableCount);
+}
+
+} // End of namespace Audio
diff --git a/audio/miles_mt32.cpp b/audio/miles_mt32.cpp
new file mode 100644
index 0000000..cb039e6
--- /dev/null
+++ b/audio/miles_mt32.cpp
@@ -0,0 +1,441 @@
+/* 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
+//
+// TODO: currently missing: timbre file support (used in 7th Guest)
+
+#define MILES_MT32_PATCH_COUNT 128
+
+const byte milesMT32SysExResetParameters[] = {
+	0x01, 0xFF
+};
+
+const byte milesMT32SysExChansSetup[] = {
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF
+};
+
+const byte milesMT32SysExPartialReserveTable[] = {
+	0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, 0xFF
+};
+
+const byte milesMT32SysExInitReverb[] = {
+	0x00, 0x03, 0x02, 0xFF // Reverb mode 0, reverb time 3, reverb level 2
+};
+
+class MidiDriver_Miles_MT32 : public MidiDriver {
+public:
+	MidiDriver_Miles_MT32();
+	virtual ~MidiDriver_Miles_MT32();
+
+	// MidiDriver
+	int open();
+	void close();
+	bool isOpen() const { return _isOpen; }
+
+	void send(uint32 b);
+
+	MidiChannel *allocateChannel() {
+		if (_driver)
+			return _driver->allocateChannel();
+		return NULL;
+	}
+	MidiChannel *getPercussionChannel() {
+		if (_driver)
+			return _driver->getPercussionChannel();
+		return NULL;
+	}
+
+	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+		if (_driver)
+			_driver->setTimerCallback(timer_param, timer_proc);
+	}
+
+	uint32 getBaseTempo() {
+		if (_driver) {
+			return _driver->getBaseTempo();
+		}
+		return 1000000 / _baseFreq;
+	}
+
+protected:
+	Common::Mutex _mutex;
+	MidiDriver *_driver;
+	bool _MT32;
+	bool _nativeMT32;
+
+	bool _isOpen;
+	int _baseFreq;
+
+public:
+
+private:
+	void resetMT32();
+
+	void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
+
+	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);
+
+	void setupPatch(byte patchId, byte patchBank);
+
+private:
+	struct MidiChannelEntry {
+		byte   currentPatchBank;
+		byte   currentPatchId;
+		bool   patchIdSet;
+
+		MidiChannelEntry() : currentPatchBank(0),
+							currentPatchId(0),
+							patchIdSet(false) { }
+	};
+
+	// stores information about all MIDI channels
+	MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
+
+	byte _patchesBank[MILES_MT32_PATCH_COUNT];
+};
+
+MidiDriver_Miles_MT32::MidiDriver_Miles_MT32() {
+	_driver = NULL;
+	_isOpen = false;
+	_MT32 = false;
+	_nativeMT32 = false;
+	_baseFreq = 250;
+
+	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::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;
+
+	sysExChecksum -= sysExMessage[4];
+	sysExChecksum -= sysExMessage[5];
+	sysExChecksum -= sysExMessage[6];
+
+	sysExPos      = 7;
+	while (1) {
+		sysExByte = *dataPtr++;
+		if (sysExByte == 0xff)
+			break; // Message done
+
+		assert(sysExPos < sizeof(sysExMessage));
+		sysExMessage[sysExPos++] = sysExByte;
+		sysExChecksum -= sysExByte;
+	}
+
+	// Calculate checksum
+	assert(sysExPos < sizeof(sysExMessage));
+	sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
+
+	// Send SysEx
+	_driver->sysEx(sysExMessage, sysExPos);
+
+	// Wait the time it takes to send the SysEx data
+	uint32 delay = (sysExPos + 2) * 1000 / 3125;
+
+	// Plus an additional delay for the MT-32 rev00
+	if (_nativeMT32)
+		delay += 40;
+
+	g_system->delayMillis(delay);
+}
+
+// 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 channel = b & 0xf;
+	byte op1 = (b >> 8) & 0xff;
+	byte op2 = (b >> 16) & 0xff;
+
+	switch (command) {
+	case 0x80: // note off
+	case 0x90: // note on
+	case 0xe0: // pitch bend change
+		_driver->send(b);
+		break;
+	case 0xb0: // Control change
+		controlChange(channel, op1, op2);
+		break;
+	case 0xc0: // Program Change
+		programChange(channel, op1);
+		break;
+	case 0xa0: // Polyphonic key pressure (aftertouch)
+	case 0xd0: // Channel pressure (aftertouch)
+		// Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
+		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;
+
+	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:
+		// uses .MT data, cannot implement atm
+		return;
+
+	case MILES_CONTROLLER_PROTECT_TIMBRE:
+		// timbre .MT data, cannot implement atm
+		return;
+
+	default:
+		break;
+	}
+
+	if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
+		// send SysEx
+		warning("MILES-MT32: embedded SysEx controller %2x, value %2x", controllerNumber, controllerValue);
+		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];
+
+	// remember patch id for the current MIDI-channel
+	_midiChannels[midiChannel].currentPatchId = patchId;
+
+	if (channelPatchBank != activePatchBank) {
+		// associate patch with timbre
+		setupPatch(patchId, channelPatchBank);
+		warning("setup patch");
+	}
+
+	// Search timbre and remember it (only used when timbre file is available)
+	// TODO
+
+	// Finally send to MT32
+	_driver->send(0xC0 | midiChannel | (patchId << 8));
+}
+
+void MidiDriver_Miles_MT32::setupPatch(byte patchId, byte patchBank) {
+	byte timbreId = 0;
+
+	_patchesBank[patchId] = patchBank;
+
+	if (patchBank) {
+		// non-built-in bank
+		// TODO: search timbre
+	}
+
+	// for built-in bank (or timbres, that are not available) use default MT32 timbres
+	timbreId = patchId & 0x3F;
+	if (!(patchId & 0x40)) {
+		writePatchTimbre(patchId, 0, timbreId); // Group A
+	} else {
+		writePatchTimbre(patchId, 1, timbreId); // Group B
+	}
+}
+
+void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
+	byte   sysExData[3];
+	uint32 targetAddress = 0;
+
+	targetAddress = ((patchId << 3) << 16) | 0x000500;
+
+	sysExData[0] = timbreGroup;
+	sysExData[1] = timbreId;
+	sysExData[2] = 0xFF; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
+	byte   sysExData[2];
+	uint32 targetAddress = 0;
+
+	targetAddress = (((patchId << 3) + index ) << 16) | 0x000500;
+
+	sysExData[0] = patchValue;
+	sysExData[1] = 0xFF; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
+	byte   sysExData[2];
+	uint32 targetAddress = 0;
+
+	targetAddress = 0x100000 | index;
+
+	sysExData[0] = value;
+	sysExData[1] = 0xFF; // terminator
+
+	MT32SysEx(targetAddress, sysExData);
+}
+
+MidiDriver *MidiDriver_Miles_MT32_create(const Common::String instrumentDataFilename) {
+	// For some games there are timbre files called [something].MT
+	// Sherlock Holmes 2 doesn't have one of those
+	// so I can't implement them
+	return new MidiDriver_Miles_MT32();
+}
+
+} // End of namespace Audio
diff --git a/audio/module.mk b/audio/module.mk
index bdb71ab..abbeed6 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -9,6 +9,8 @@ MODULE_OBJS := \
 	midiparser_xmidi.o \
 	midiparser.o \
 	midiplayer.o \
+	miles_adlib.o \
+	miles_mt32.o \
 	mixer.o \
 	mpu401.o \
 	musicplugin.o \
diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk
index 704b8d2..e592baa 100644
--- a/engines/sherlock/module.mk
+++ b/engines/sherlock/module.mk
@@ -19,8 +19,6 @@ MODULE_OBJS = \
 	scalpel/scalpel_talk.o \
 	scalpel/scalpel_user_interface.o \
 	scalpel/settings.o \
-	tattoo/drivers/tattoo_adlib.o \
-	tattoo/drivers/tattoo_mt32.o \
 	tattoo/tattoo.o \
 	tattoo/tattoo_fixed_text.o \
 	tattoo/tattoo_journal.o \
diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp
index 948794a..f72eaf7 100644
--- a/engines/sherlock/music.cpp
+++ b/engines/sherlock/music.cpp
@@ -25,7 +25,8 @@
 #include "sherlock/sherlock.h"
 #include "sherlock/music.h"
 #include "sherlock/scalpel/drivers/mididriver.h"
-#include "sherlock/tattoo/drivers/tattoo_mididriver.h"
+// for Miles Audio (Sherlock Holmes 2)
+#include "audio/miles.h"
 // for 3DO digital music
 #include "audio/decoders/aiff.h"
 
@@ -269,14 +270,14 @@ Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
 			// SAMPLE.AD  -> regular AdLib instrument data
 			// SAMPLE.OPL -> OPL-3 instrument data
 			// although in case of Rose Tattoo both files are exactly the same
-			_midiDriver = MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
+			_midiDriver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
 			break;
 		case MT_MT32:
-			_midiDriver = MidiDriver_Miles_MT32_create("SAMPLE.MT");
+			_midiDriver = Audio::MidiDriver_Miles_MT32_create("SAMPLE.MT");
 			break;
 		case MT_GM:
 			if (ConfMan.getBool("native_mt32")) {
-				_midiDriver = MidiDriver_Miles_MT32_create("SAMPLE.MT");
+				_midiDriver = Audio::MidiDriver_Miles_MT32_create("SAMPLE.MT");
 				_musicType = MT_MT32;
 			}
 			break;
diff --git a/engines/sherlock/tattoo/drivers/tattoo_adlib.cpp b/engines/sherlock/tattoo/drivers/tattoo_adlib.cpp
deleted file mode 100644
index cfc9786..0000000
--- a/engines/sherlock/tattoo/drivers/tattoo_adlib.cpp
+++ /dev/null
@@ -1,1137 +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 "sherlock/sherlock.h"
-#include "sherlock/tattoo/drivers/tattoo_mididriver.h"
-
-#include "common/file.h"
-#include "common/system.h"
-#include "common/textconsole.h"
-
-#include "audio/fmopl.h"
-#include "audio/softsynth/emumidi.h"
-
-namespace Sherlock {
-
-// Miles Audio supported the following things:
-// regular AdLib OPL card
-// Dual-OPL2 <-- we don't do this atm
-// OPL3 <-- we do support this, but there is no support for 4-op voices atm
-
-#define MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX 20
-#define MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX 18
-
-#define MILES_ADLIB_PERCUSSION_BANK 127
-
-#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT 27
-#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT 100
-
-enum kMilesAdLibUpdateFlags {
-	kMilesAdLibUpdateFlags_None    = 0,
-	kMilesAdLibUpdateFlags_Reg_20  = 1 << 0,
-	kMilesAdLibUpdateFlags_Reg_40  = 1 << 1,
-	kMilesAdLibUpdateFlags_Reg_60  = 1 << 2, // register 0x6x + 0x8x
-	kMilesAdLibUpdateFlags_Reg_C0  = 1 << 3,
-	kMilesAdLibUpdateFlags_Reg_E0  = 1 << 4,
-	kMilesAdLibUpdateFlags_Reg_A0  = 1 << 5, // register 0xAx + 0xBx
-	kMilesAdLibUpdateFlags_Reg_All = 0x3F
-};
-
-uint16 milesAdLibOperator1Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
-	0x0000, 0x0001, 0x0002, 0x0008, 0x0009, 0x000A, 0x0010, 0x0011, 0x0012,
-	0x0100, 0x0101, 0x0102, 0x0108, 0x0109, 0x010A, 0x0110, 0x0111, 0x0112
-};
-
-uint16 milesAdLibOperator2Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
-	0x0003, 0x0004, 0x0005, 0x000B, 0x000C, 0x000D, 0x0013, 0x0014, 0x0015,
-	0x0103, 0x0104, 0x0105, 0x010B, 0x010C, 0x010D, 0x0113, 0x0114, 0x0115
-};
-
-uint16 milesAdLibChannelRegister[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
-	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008,
-	0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108
-};
-
-struct InstrumentEntry {
-	byte bankId;
-	byte patchId;
-	int16 transposition;
-	byte reg20op1;
-	byte reg40op1;
-	byte reg60op1;
-	byte reg80op1;
-	byte regE0op1;
-	byte reg20op2;
-	byte reg40op2;
-	byte reg60op2;
-	byte reg80op2;
-	byte regE0op2;
-	byte regC0;
-};
-
-// hardcoded, dumped from ADLIB.MDI
-uint16 milesAdLibFrequencyLookUpTable[] = {
-	0x02B2, 0x02B4, 0x02B7, 0x02B9, 0x02BC, 0x02BE, 0x02C1, 0x02C3, 0x02C6, 0x02C9, 0x02CB, 0x02CE,
-	0x02D0, 0x02D3, 0x02D6, 0x02D8, 0x02DB, 0x02DD, 0x02E0, 0x02E3, 0x02E5, 0x02E8, 0x02EB, 0x02ED,
-	0x02F0, 0x02F3, 0x02F6, 0x02F8, 0x02FB, 0x02FE, 0x0301, 0x0303, 0x0306, 0x0309, 0x030C, 0x030F,
-	0x0311, 0x0314, 0x0317, 0x031A, 0x031D, 0x0320, 0x0323, 0x0326, 0x0329, 0x032B, 0x032E, 0x0331,
-	0x0334, 0x0337, 0x033A, 0x033D, 0x0340, 0x0343, 0x0346, 0x0349, 0x034C, 0x034F, 0x0352, 0x0356,
-	0x0359, 0x035C, 0x035F, 0x0362, 0x0365, 0x0368, 0x036B, 0x036F, 0x0372, 0x0375, 0x0378, 0x037B,
-	0x037F, 0x0382, 0x0385, 0x0388, 0x038C, 0x038F, 0x0392, 0x0395, 0x0399, 0x039C, 0x039F, 0x03A3,
-	0x03A6, 0x03A9, 0x03AD, 0x03B0, 0x03B4, 0x03B7, 0x03BB, 0x03BE, 0x03C1, 0x03C5, 0x03C8, 0x03CC,
-	0x03CF, 0x03D3, 0x03D7, 0x03DA, 0x03DE, 0x03E1, 0x03E5, 0x03E8, 0x03EC, 0x03F0, 0x03F3, 0x03F7,
-	0x03FB, 0x03FE, 0xFE01, 0xFE03, 0xFE05, 0xFE07, 0xFE08, 0xFE0A, 0xFE0C, 0xFE0E, 0xFE10, 0xFE12,
-	0xFE14, 0xFE16, 0xFE18, 0xFE1A, 0xFE1C, 0xFE1E, 0xFE20, 0xFE21, 0xFE23, 0xFE25, 0xFE27, 0xFE29,
-	0xFE2B, 0xFE2D, 0xFE2F, 0xFE31, 0xFE34, 0xFE36, 0xFE38, 0xFE3A, 0xFE3C, 0xFE3E, 0xFE40, 0xFE42,
-	0xFE44, 0xFE46, 0xFE48, 0xFE4A, 0xFE4C, 0xFE4F, 0xFE51, 0xFE53, 0xFE55, 0xFE57, 0xFE59, 0xFE5C,
-	0xFE5E, 0xFE60, 0xFE62, 0xFE64, 0xFE67, 0xFE69, 0xFE6B, 0xFE6D, 0xFE6F, 0xFE72, 0xFE74, 0xFE76,
-	0xFE79, 0xFE7B, 0xFE7D, 0xFE7F, 0xFE82, 0xFE84, 0xFE86, 0xFE89, 0xFE8B, 0xFE8D, 0xFE90, 0xFE92,
-	0xFE95, 0xFE97, 0xFE99, 0xFE9C, 0xFE9E, 0xFEA1, 0xFEA3, 0xFEA5, 0xFEA8, 0xFEAA, 0xFEAD, 0xFEAF
-};
-
-// hardcoded, dumped from ADLIB.MDI
-uint16 milesAdLibVolumeSensitivityTable[] = {
-	82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127
-};
-
-
-class MidiDriver_Miles_AdLib : public MidiDriver_Emulated {
-public:
-	MidiDriver_Miles_AdLib(Audio::Mixer *mixer, InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
-	virtual ~MidiDriver_Miles_AdLib();
-
-	// MidiDriver
-	int open();
-	void close();
-	void send(uint32 b);
-	MidiChannel *allocateChannel() { return NULL; }
-	MidiChannel *getPercussionChannel() { return NULL; }
-
-	// AudioStream
-	bool isStereo() const { return _modeStereo; }
-	int getRate() const { return _mixer->getOutputRate(); }
-	int getPolyphony() const { return _modePhysicalFmVoicesCount; }
-	bool hasRhythmChannel() const { return false; }
-
-	// MidiDriver_Emulated
-	void generateSamples(int16 *buf, int len);
-
-	void setVolume(byte volume);
-	virtual uint32 property(int prop, uint32 param);
-
-private:
-	bool _modeOPL3;
-	byte _modePhysicalFmVoicesCount;
-	byte _modeVirtualFmVoicesCount;
-	bool _modeStereo;
-
-	// Structure to hold information about current status of MIDI Channels
-	struct MidiChannelEntry {
-		byte   currentPatchBank;
-		const  InstrumentEntry *currentInstrumentPtr;
-		uint16 currentPitchBender;
-		byte   currentPitchRange;
-		byte   currentVoiceProtection;
-
-		byte   currentVolume;
-		byte   currentVolumeExpression;
-
-		byte   currentPanning;
-
-		byte   currentModulation;
-		byte   currentSustain;
-
-		byte   currentActiveVoicesCount;
-
-		MidiChannelEntry() : currentPatchBank(0),
-							currentInstrumentPtr(NULL),
-							currentPitchBender(MILES_PITCHBENDER_DEFAULT),
-							currentVoiceProtection(0),
-							currentVolume(0), currentVolumeExpression(0),
-							currentPanning(0),
-							currentModulation(0),
-							currentSustain(0),
-							currentActiveVoicesCount(0) { }
-	};
-
-	// Structure to hold information about current status of virtual FM Voices
-	struct VirtualFmVoiceEntry {
-		bool   inUse;
-		byte   actualMidiChannel;
-
-		const  InstrumentEntry *currentInstrumentPtr;
-
-		bool   isPhysical;
-		byte   physicalFmVoice;
-
-		uint16 currentPriority;
-
-		byte   currentOriginalMidiNote;
-		byte   currentNote;
-		int16  currentTransposition;
-		byte   currentVelocity;
-
-		bool   sustained;
-
-		VirtualFmVoiceEntry(): inUse(false),
-								actualMidiChannel(0),
-								currentInstrumentPtr(NULL),
-								isPhysical(false), physicalFmVoice(0),
-								currentPriority(0),
-								currentOriginalMidiNote(0),
-								currentNote(0),
-								currentTransposition(0),
-								currentVelocity(0),
-								sustained(false) { }
-	};
-
-	// Structure to hold information about current status of physical FM Voices
-	struct PhysicalFmVoiceEntry {
-		bool   inUse;
-		byte   virtualFmVoice;
-
-		byte   currentB0hReg;
-
-		PhysicalFmVoiceEntry(): inUse(false),
-								virtualFmVoice(0),
-								currentB0hReg(0) { }
-	};
-
-	OPL::OPL *_opl;
-	int _masterVolume;
-
-	// stores information about all MIDI channels (not the actual OPL FM voice channels!)
-	MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
-
-	// stores information about all virtual OPL FM voices
-	VirtualFmVoiceEntry _virtualFmVoices[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
-
-	// stores information about all physical OPL FM voices
-	PhysicalFmVoiceEntry _physicalFmVoices[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX];
-
-	// holds all instruments
-	InstrumentEntry *_instrumentTablePtr;
-	uint16           _instrumentTableCount;
-
-	bool circularPhysicalAssignment;
-	byte circularPhysicalAssignmentFmVoice;
-
-protected:
-	void onTimer();
-
-private:
-	void resetData();
-	void resetAdLib();
-	void resetAdLibOperatorRegisters(byte baseRegister, byte value);
-	void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
-
-	void setRegister(int reg, int value);
-
-	int16 searchFreeVirtualFmVoiceChannel();
-	int16 searchFreePhysicalFmVoiceChannel();
-
-	void noteOn(byte midiChannel, byte note, byte velocity);
-	void noteOff(byte midiChannel, byte note);
-
-	void prioritySort();
-
-	void releaseFmVoice(byte virtualFmVoice);
-
-	void updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags);
-
-	void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
-	void programChange(byte midiChannel, byte patchId);
-
-	const InstrumentEntry *searchInstrument(byte bankId, byte patchId);
-
-	void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
-};
-
-MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(Audio::Mixer *mixer, InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount)
-	: MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) {
-
-	_instrumentTablePtr = instrumentTablePtr;
-	_instrumentTableCount = instrumentTableCount;
-
-	// Set up for OPL3, we will downgrade in case we can't create OPL3 emulator
-	// regular AdLib (OPL2) card
-	_modeOPL3 = true;
-	_modeVirtualFmVoicesCount = 20;
-	_modePhysicalFmVoicesCount = 18;
-	_modeStereo = true;
-
-	// Older Miles Audio drivers did not do a circular assign for physical FM-voices
-	// Sherlock Holmes 2 used the circular assign
-	circularPhysicalAssignment = true;
-	// this way the first circular physical FM-voice search will start at FM-voice 0
-	circularPhysicalAssignmentFmVoice = MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX;
-
-	resetData();
-}
-
-MidiDriver_Miles_AdLib::~MidiDriver_Miles_AdLib() {
-	delete[] _instrumentTablePtr; // is created in factory MidiDriver_Miles_AdLib_create()
-}
-
-int MidiDriver_Miles_AdLib::open() {
-	int rate = _mixer->getOutputRate();
-
-	if (_modeOPL3) {
-		// Try to create OPL3 first
-		_opl = OPL::Config::create(OPL::Config::kOpl3);
-	}
-	if (!_opl) {
-		// not created yet, downgrade to OPL2
-		_modeOPL3 = false;
-		_modeVirtualFmVoicesCount = 16;
-		_modePhysicalFmVoicesCount = 9;
-		_modeStereo = false;
-
-		_opl = OPL::Config::create(OPL::Config::kOpl2);
-	}
-
-	if (!_opl) {
-		// We still got nothing -> can't do anything anymore
-		return -1;
-	}
-
-	_opl->init(rate);
-
-	MidiDriver_Emulated::open();
-
-	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
-
-	resetAdLib();
-
-	return 0;
-}
-
-void MidiDriver_Miles_AdLib::close() {
-	_mixer->stopHandle(_mixerSoundHandle);
-
-	delete _opl;
-}
-
-void MidiDriver_Miles_AdLib::setVolume(byte volume) {
-	_masterVolume = volume;
-	//renewNotes(-1, true);
-}
-
-void MidiDriver_Miles_AdLib::onTimer() {
-}
-
-void MidiDriver_Miles_AdLib::resetData() {
-	memset(_midiChannels, 0, sizeof(_midiChannels));
-	memset(_virtualFmVoices, 0, sizeof(_virtualFmVoices));
-	memset(_physicalFmVoices, 0, sizeof(_physicalFmVoices));
-
-	for (byte midiChannel = 0; midiChannel < MILES_MIDI_CHANNEL_COUNT; midiChannel++) {
-		_midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT;
-		_midiChannels[midiChannel].currentPitchRange = 12;
-		// Miles Audio 2: hardcoded pitch range as a global (not channel specific), set to 12
-		// Miles Audio 3: pitch range per MIDI channel
-		_midiChannels[midiChannel].currentVolumeExpression = 127;
-	}
-
-}
-
-void MidiDriver_Miles_AdLib::resetAdLib() {
-	if (_modeOPL3) {
-		setRegister(0x105, 1); // enable OPL3
-		setRegister(0x104, 0); // activate 18 2-operator FM-voices
-	}
-
-	setRegister(0x01, 0x20); // enable waveform control on both operators
-	setRegister(0x04, 0xE0); // Timer control
-
-	setRegister(0x08, 0); // select FM music mode
-	setRegister(0xBD, 0); // disable Rhythm
-
-	// reset FM voice instrument data
-	resetAdLibOperatorRegisters(0x20, 0);
-	resetAdLibOperatorRegisters(0x60, 0);
-	resetAdLibOperatorRegisters(0x80, 0);
-	resetAdLibFMVoiceChannelRegisters(0xA0, 0);
-	resetAdLibFMVoiceChannelRegisters(0xB0, 0);
-	resetAdLibFMVoiceChannelRegisters(0xC0, 0);
-	resetAdLibOperatorRegisters(0xE0, 0);
-	resetAdLibOperatorRegisters(0x40, 0x3F);
-}
-
-void MidiDriver_Miles_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
-	byte physicalFmVoice = 0;
-
-	for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
-		setRegister(baseRegister + milesAdLibOperator1Register[physicalFmVoice], value);
-		setRegister(baseRegister + milesAdLibOperator2Register[physicalFmVoice], value);
-	}
-}
-
-void MidiDriver_Miles_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
-	byte physicalFmVoice = 0;
-
-	for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
-		setRegister(baseRegister + milesAdLibChannelRegister[physicalFmVoice], value);
-	}
-}
-
-// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
-void MidiDriver_Miles_AdLib::send(uint32 b) {
-	byte command = b & 0xf0;
-	byte channel = b & 0xf;
-	byte op1 = (b >> 8) & 0xff;
-	byte op2 = (b >> 16) & 0xff;
-
-	switch (command) {
-	case 0x80:
-		noteOff(channel, op1);
-		break;
-	case 0x90:
-		noteOn(channel, op1, op2);
-		break;
-	case 0xb0: // Control change
-		controlChange(channel, op1, op2);
-		break;
-	case 0xc0: // Program Change
-		programChange(channel, op1);
-		break;
-	case 0xa0: // Polyphonic key pressure (aftertouch)
-	case 0xd0: // Channel pressure (aftertouch)
-		// Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
-		break;
-	case 0xe0:
-		pitchBendChange(channel, op1, op2);
-		break;
-	case 0xf0: // SysEx
-		warning("MILES-ADLIB: SysEx: %x", b);
-		break;
-	default:
-		warning("MILES-ADLIB: Unknown event %02x", command);
-	}
-}
-
-void MidiDriver_Miles_AdLib::generateSamples(int16 *data, int len) {
-	if (_modeStereo)
-		len *= 2;
-
-	_opl->readBuffer(data, len);
-}
-
-int16 MidiDriver_Miles_AdLib::searchFreeVirtualFmVoiceChannel() {
-	for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
-		if (!_virtualFmVoices[virtualFmVoice].inUse)
-			return virtualFmVoice;
-	}
-	return -1;
-}
-
-int16 MidiDriver_Miles_AdLib::searchFreePhysicalFmVoiceChannel() {
-	if (!circularPhysicalAssignment) {
-		// Older assign logic
-		for (byte physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
-			if (!_physicalFmVoices[physicalFmVoice].inUse)
-				return physicalFmVoice;
-		}
-	} else {
-		// Newer one
-		// Remembers last physical FM-voice and searches from that spot
-		byte physicalFmVoice = circularPhysicalAssignmentFmVoice;
-		for (byte physicalFmVoiceCount = 0; physicalFmVoiceCount < _modePhysicalFmVoicesCount; physicalFmVoiceCount++) {
-			physicalFmVoice++;
-			if (physicalFmVoice >= _modePhysicalFmVoicesCount)
-				physicalFmVoice = 0;
-			if (!_physicalFmVoices[physicalFmVoice].inUse) {
-				circularPhysicalAssignmentFmVoice = physicalFmVoice;
-				return physicalFmVoice;
-			}
-		}
-	}
-	return -1;
-}
-
-void MidiDriver_Miles_AdLib::noteOn(byte midiChannel, byte note, byte velocity) {
-	const InstrumentEntry *instrumentPtr = NULL;
-
-	if (velocity == 0) {
-		noteOff(midiChannel, note);
-		return;
-	}
-
-	if (midiChannel == 9) {
-		// percussion channel
-		// search for instrument according to given note
-		instrumentPtr = searchInstrument(MILES_ADLIB_PERCUSSION_BANK, note);
-	} else {
-		// directly get instrument of channel
-		instrumentPtr = _midiChannels[midiChannel].currentInstrumentPtr;
-	}
-	if (!instrumentPtr) {
-		warning("MILES-ADLIB: noteOn: invalid instrument");
-		return;
-	}
-
-	//warning("Note On: channel %d, note %d, velocity %d, instrument %d/%d", midiChannel, note, velocity, instrumentPtr->bankId, instrumentPtr->patchId);
-
-	// look for free virtual FM voice
-	int16 virtualFmVoice = searchFreeVirtualFmVoiceChannel();
-
-	if (virtualFmVoice == -1) {
-		// Out of virtual voices,  can't do anything about it
-		return;
-	}
-
-	// Scale back velocity
-	velocity = (velocity & 0x7F) >> 3;
-	velocity = milesAdLibVolumeSensitivityTable[velocity];
-
-	if (midiChannel != 9) {
-		_virtualFmVoices[virtualFmVoice].currentNote = note;
-		_virtualFmVoices[virtualFmVoice].currentTransposition = instrumentPtr->transposition;
-	} else {
-		// Percussion channel
-		_virtualFmVoices[virtualFmVoice].currentNote = instrumentPtr->transposition;
-		_virtualFmVoices[virtualFmVoice].currentTransposition = 0;
-	}
-
-	_virtualFmVoices[virtualFmVoice].inUse = true;
-	_virtualFmVoices[virtualFmVoice].actualMidiChannel = midiChannel;
-	_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote = note;
-	_virtualFmVoices[virtualFmVoice].currentInstrumentPtr = instrumentPtr;
-	_virtualFmVoices[virtualFmVoice].currentVelocity = velocity;
-	_virtualFmVoices[virtualFmVoice].isPhysical = false;
-	_virtualFmVoices[virtualFmVoice].sustained = false;
-	_virtualFmVoices[virtualFmVoice].currentPriority = 32767;
-
-	int16 physicalFmVoice = searchFreePhysicalFmVoiceChannel();
-	if (physicalFmVoice == -1) {
-		// None found
-		// go through priorities and reshuffle voices
-		prioritySort();
-		return;
-	}
-
-	// Another voice active on this MIDI channel
-	_midiChannels[midiChannel].currentActiveVoicesCount++;
-
-	// Mark virtual FM-Voice as being connected to physical FM-Voice
-	_virtualFmVoices[virtualFmVoice].isPhysical = true;
-	_virtualFmVoices[virtualFmVoice].physicalFmVoice = physicalFmVoice;
-
-	// Mark physical FM-Voice as being connected to virtual FM-Voice
-	_physicalFmVoices[physicalFmVoice].inUse = true;
-	_physicalFmVoices[physicalFmVoice].virtualFmVoice = virtualFmVoice;
-
-	// Update the physical FM-Voice
-	updatePhysicalFmVoice(virtualFmVoice, true, kMilesAdLibUpdateFlags_Reg_All);
-}
-
-void MidiDriver_Miles_AdLib::noteOff(byte midiChannel, byte note) {
-	//warning("Note Off: channel %d, note %d", midiChannel, note);
-
-	// Search through all virtual FM-Voices for current midiChannel + note
-	for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
-		if (_virtualFmVoices[virtualFmVoice].inUse) {
-			if ((_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) && (_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote == note)) {
-				// found one
-				if (_midiChannels[midiChannel].currentSustain >= 64) {
-					_virtualFmVoices[virtualFmVoice].sustained = true;
-					continue;
-				}
-				// 
-				releaseFmVoice(virtualFmVoice);
-			}
-		}
-	}
-}
-
-void MidiDriver_Miles_AdLib::prioritySort() {
-	byte   virtualFmVoice = 0;
-	uint16 virtualPriority = 0;
-	uint16 virtualPriorities[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
-	uint16 virtualFmVoicesCount = 0;
-	byte   midiChannel = 0;
-
-	memset(&virtualPriorities, 0, sizeof(virtualPriorities));
-
-	//warning("prioritysort");
-
-	// First calculate priorities for all virtual FM voices, that are in use
-	for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
-		if (_virtualFmVoices[virtualFmVoice].inUse) {
-			virtualFmVoicesCount++;
-
-			midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
-			if (_midiChannels[midiChannel].currentVoiceProtection >= 64) {
-				// Voice protection enabled
-				virtualPriority = 0xFFFF;
-			} else {
-				virtualPriority = _virtualFmVoices[virtualFmVoice].currentPriority;
-			}
-			byte currentActiveVoicesCount = _midiChannels[midiChannel].currentActiveVoicesCount;
-			if (virtualPriority >= currentActiveVoicesCount) {
-				virtualPriority -= _midiChannels[midiChannel].currentActiveVoicesCount;
-			} else {
-				virtualPriority = 0; // overflow, should never happen
-			}
-			virtualPriorities[virtualFmVoice] = virtualPriority;
-		}
-	}
-
-	// 	
-	while (virtualFmVoicesCount) {
-		uint16 unvoicedHighestPriority = 0;
-		byte   unvoicedHighestFmVoice = 0;
-		uint16 voicedLowestPriority = 65535;
-		byte   voicedLowestFmVoice = 0;
-
-		for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
-			if (_virtualFmVoices[virtualFmVoice].inUse) {
-				virtualPriority = virtualPriorities[virtualFmVoice];
-				if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
-					// currently not physical, so unvoiced
-					if (virtualPriority >= unvoicedHighestPriority) {
-						unvoicedHighestPriority = virtualPriority;
-						unvoicedHighestFmVoice  = virtualFmVoice;
-					}
-				} else {
-					// currently physical, so voiced
-					if (virtualPriority <= voicedLowestPriority) {
-						voicedLowestPriority = virtualPriority;
-						voicedLowestFmVoice  = virtualFmVoice;
-					}
-				}
-			}
-		}
-
-		if (unvoicedHighestPriority < voicedLowestPriority)
-			break; // We are done
-
-		if (unvoicedHighestPriority == 0)
-			break;
-
-		// Safety checks
-		assert(_virtualFmVoices[voicedLowestFmVoice].isPhysical);
-		assert(!_virtualFmVoices[unvoicedHighestFmVoice].isPhysical);
-
-		// Steal this physical voice
-		byte physicalFmVoice = _virtualFmVoices[voicedLowestFmVoice].physicalFmVoice;
-
-		//warning("MILES-ADLIB: stealing physical FM-Voice %d from virtual FM-Voice %d for virtual FM-Voice %d", physicalFmVoice, voicedLowestFmVoice, unvoicedHighestFmVoice);
-		//warning("priority old %d, priority new %d", unvoicedHighestPriority, voicedLowestPriority);
-
-		releaseFmVoice(voicedLowestFmVoice);
-
-		// Get some data of the unvoiced highest priority virtual FM Voice
-		midiChannel = _virtualFmVoices[unvoicedHighestFmVoice].actualMidiChannel;
-
-		// Another voice active on this MIDI channel
-		_midiChannels[midiChannel].currentActiveVoicesCount++;
-
-		// Mark virtual FM-Voice as being connected to physical FM-Voice
-		_virtualFmVoices[unvoicedHighestFmVoice].isPhysical = true;
-		_virtualFmVoices[unvoicedHighestFmVoice].physicalFmVoice = physicalFmVoice;
-
-		// Mark physical FM-Voice as being connected to virtual FM-Voice
-		_physicalFmVoices[physicalFmVoice].inUse = true;
-		_physicalFmVoices[physicalFmVoice].virtualFmVoice = unvoicedHighestFmVoice;
-
-		// Update the physical FM-Voice
-		updatePhysicalFmVoice(unvoicedHighestFmVoice, true, kMilesAdLibUpdateFlags_Reg_All);
-
-		virtualFmVoicesCount--;
-	}
-}
-
-void MidiDriver_Miles_AdLib::releaseFmVoice(byte virtualFmVoice) {
-	// virtual Voice not actually played? -> exit
-	if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
-		_virtualFmVoices[virtualFmVoice].inUse = false;
-		return;
-	}
-
-	byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
-	byte physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice;
-
-	// stop note from playing
-	updatePhysicalFmVoice(virtualFmVoice, false, kMilesAdLibUpdateFlags_Reg_A0);
-
-	// this virtual FM voice isn't physical anymore
-	_virtualFmVoices[virtualFmVoice].isPhysical = false;
-	_virtualFmVoices[virtualFmVoice].inUse = false;
-
-	// Remove physical FM-Voice from being active
-	_physicalFmVoices[physicalFmVoice].inUse = false;
-
-	// One less voice active on this MIDI channel
-	assert(_midiChannels[midiChannel].currentActiveVoicesCount);
-	_midiChannels[midiChannel].currentActiveVoicesCount--;	
-}
-
-void MidiDriver_Miles_AdLib::updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags) {
-	byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
-
-	if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
-		// virtual FM-Voice has no physical FM-Voice assigned? -> ignore
-		return;
-	}
-
-	byte                   physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice;
-	const InstrumentEntry *instrumentPtr = _virtualFmVoices[virtualFmVoice].currentInstrumentPtr;
-
-	uint16 op1Reg = milesAdLibOperator1Register[physicalFmVoice];
-	uint16 op2Reg = milesAdLibOperator2Register[physicalFmVoice];
-	uint16 channelReg = milesAdLibChannelRegister[physicalFmVoice];
-
-	uint16 compositeVolume = 0;
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) {
-		// Calculate new volume
-		byte midiVolume = _midiChannels[midiChannel].currentVolume;
-		byte midiVolumeExpression = _midiChannels[midiChannel].currentVolumeExpression;
-		compositeVolume = midiVolume * midiVolumeExpression * 2;
-
-		compositeVolume = compositeVolume >> 8; // get upmost 8 bits
-		if (compositeVolume)
-			compositeVolume++; // round up in case result wasn't 0
-
-		compositeVolume = compositeVolume * _virtualFmVoices[virtualFmVoice].currentVelocity * 2;
-		compositeVolume = compositeVolume >> 8; // get upmost 8 bits
-		if (compositeVolume)
-			compositeVolume++; // round up in case result wasn't 0
-	}
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_20) {
-		// Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
-		byte reg20op1 = instrumentPtr->reg20op1;
-		byte reg20op2 = instrumentPtr->reg20op2;
-
-		if (_midiChannels[midiChannel].currentModulation >= 64) {
-			// set bit 6 (Vibrato)
-			reg20op1 |= 0x40;
-			reg20op2 |= 0x40;
-		}
-		setRegister(0x20 + op1Reg, reg20op1);
-		setRegister(0x20 + op2Reg, reg20op2);
-	}
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) {
-		// Volume (Level Key Scaling / Total Level)
-		byte reg40op1 = instrumentPtr->reg40op1;
-		byte reg40op2 = instrumentPtr->reg40op2;
-
-		uint16 volumeOp1 = (~reg40op1) & 0x3F;
-		uint16 volumeOp2 = (~reg40op2) & 0x3F;
-
-		if (instrumentPtr->regC0 & 1) {
-			// operator 2 enabled
-			// scale volume factor
-			volumeOp1 = (volumeOp1 * compositeVolume) / 127;
-			// 2nd operator always scaled
-		}
-
-		volumeOp2 = (volumeOp2 * compositeVolume) / 127;
-
-		volumeOp1 = (~volumeOp1) & 0x3F; // negate it, so we get the proper value for the register
-		volumeOp2 = (~volumeOp2) & 0x3F; // ditto
-		reg40op1  = (reg40op1 & 0xC0) | volumeOp1; // keep "scaling level" and merge in our volume
-		reg40op2  = (reg40op2 & 0xC0) | volumeOp2;
-
-		setRegister(0x40 + op1Reg, reg40op1);
-		setRegister(0x40 + op2Reg, reg40op2);
-	}
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_60) {
-		// Attack Rate / Decay Rate
-		// Sustain Level / Release Rate
-		byte reg60op1 = instrumentPtr->reg60op1;
-		byte reg60op2 = instrumentPtr->reg60op2;
-		byte reg80op1 = instrumentPtr->reg80op1;
-		byte reg80op2 = instrumentPtr->reg80op2;
-
-		setRegister(0x60 + op1Reg, reg60op1);
-		setRegister(0x60 + op2Reg, reg60op2);
-		setRegister(0x80 + op1Reg, reg80op1);
-		setRegister(0x80 + op2Reg, reg80op2);
-	}
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_E0) {
-		// Waveform Select
-		byte regE0op1 = instrumentPtr->regE0op1;
-		byte regE0op2 = instrumentPtr->regE0op2;
-
-		setRegister(0xE0 + op1Reg, regE0op1);
-		setRegister(0xE0 + op2Reg, regE0op2);
-	}
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_C0) {
-		// Feedback / Algorithm
-		byte regC0 = instrumentPtr->regC0;
-
-		if (_modeOPL3) {
-			// Panning for OPL3
-			byte panning = _midiChannels[midiChannel].currentPanning;
-
-			if (panning <= MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT) {
-				regC0 |= 0x10; // left speaker only
-			} else if (panning >= MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT) {
-				regC0 |= 0x20; // right speaker only
-			} else {
-				regC0 |= 0x30; // center
-			}
-		}
-
-		setRegister(0xC0 + channelReg, regC0);
-	}
-
-	if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_A0) {
-		// Frequency / Key-On
-		// Octave / F-Number / Key-On
-		if (!keyOn) {
-			// turn off note
-			byte regB0 = _physicalFmVoices[physicalFmVoice].currentB0hReg & 0x1F; // remove bit 5 "key on"
-			setRegister(0xB0 + channelReg, regB0);
-
-		} else {
-			// turn on note, calculate frequency, octave...
-			int16 pitchBender = _midiChannels[midiChannel].currentPitchBender;
-			byte  pitchRange = _midiChannels[midiChannel].currentPitchRange;
-			int16 currentNote = _virtualFmVoices[virtualFmVoice].currentNote;
-			int16 physicalNote = 0;
-			int16 halfTone = 0;
-			uint16 frequency = 0;
-			uint16 frequencyIdx = 0;
-			byte   octave = 0;
-
-			pitchBender -= 0x2000;
-			pitchBender = pitchBender >> 5; // divide by 32
-			pitchBender = pitchBender * pitchRange; // pitchrange 12: now +0x0C00 to -0xC00
-			// difference between Miles Audio 2 + 3
-			// Miles Audio 2 used a pitch range of 12, which was basically hardcoded
-			// Miles Audio 3 used an array, which got set by control change events
-
-			currentNote += _virtualFmVoices->currentTransposition;
-
-			// Normalize note
-			currentNote -= 24;
-			do {
-				currentNote += 12;
-			} while (currentNote < 0);
-			currentNote += 12;
-
-			do {
-				currentNote -= 12;
-			} while (currentNote > 95);
-
-			// combine note + pitchbender, also adjust by 8 for rounding
-			currentNote = (currentNote << 8) + pitchBender + 8;
-
-			currentNote = currentNote >> 4; // get actual note
-
-			// Normalize
-			currentNote -= (12 * 16);
-			do {
-				currentNote += (12 * 16);
-			} while (currentNote < 0);
-
-			currentNote += (12 * 16);
-			do {
-				currentNote -= (12 * 16);
-			} while (currentNote > ((96 * 16) - 1));
-
-			physicalNote = currentNote >> 4;
-
-			halfTone = physicalNote % 12; // remainder of physicalNote / 12
-
-			frequencyIdx = (halfTone << 4) + (currentNote & 0x0F);
-			assert(frequencyIdx < sizeof(milesAdLibFrequencyLookUpTable));
-			frequency = milesAdLibFrequencyLookUpTable[frequencyIdx];
-
-			octave = (physicalNote / 12) - 1;
-
-			if (frequency & 0x8000)
-				octave++;
-
-			if (octave & 0x80) {
-				octave++;
-				frequency = frequency >> 1;
-			}
-
-			byte regA0 = frequency & 0xFF;
-			byte regB0 = ((frequency >> 8) & 0x03) | (octave << 2) | 0x20;
-
-			setRegister(0xA0 + channelReg, regA0);
-			setRegister(0xB0 + channelReg, regB0);
-
-			_physicalFmVoices[physicalFmVoice].currentB0hReg = regB0;
-		}
-	}
-
-	//warning("end of update voice");
-}
-
-void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
-	uint16 registerUpdateFlags = kMilesAdLibUpdateFlags_None;
-
-	switch (controllerNumber) {
-	case MILES_CONTROLLER_SELECT_PATCH_BANK:
-		_midiChannels[midiChannel].currentPatchBank = controllerValue;
-		break;
-
-	case MILES_CONTROLLER_PROTECT_VOICE:
-		_midiChannels[midiChannel].currentVoiceProtection = controllerValue;
-		break;
-
-	case MILES_CONTROLLER_PROTECT_TIMBRE:
-		// It seems that this can get ignored, because we don't cache timbres at all
-		break;
-
-	case MILES_CONTROLLER_MODULATION:
-		_midiChannels[midiChannel].currentModulation = controllerValue;
-		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20;
-		break;
-
-	case MILES_CONTROLLER_VOLUME:
-		_midiChannels[midiChannel].currentVolume = controllerValue;
-		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
-		break;
-
-	case MILES_CONTROLLER_EXPRESSION:
-		_midiChannels[midiChannel].currentVolumeExpression = controllerValue;
-		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
-		break;
-
-	case MILES_CONTROLLER_PANNING:
-		_midiChannels[midiChannel].currentPanning = controllerValue;
-		if (_modeStereo) {
-			// Update register only in case we are in stereo mode
-			registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_C0;
-		}
-		break;
-
-	case MILES_CONTROLLER_SUSTAIN:
-		_midiChannels[midiChannel].currentSustain = controllerValue;
-		if (controllerValue < 64) {
-			// release sustain TODO
-		}
-		break;
-
-	case MILES_CONTROLLER_PITCH_RANGE:
-		// Miles Audio 3 feature
-		_midiChannels[midiChannel].currentPitchRange = controllerValue;
-		break;
-
-	case MILES_CONTROLLER_RESET_ALL:
-		_midiChannels[midiChannel].currentSustain = 0;
-		// release sustain TODO
-		_midiChannels[midiChannel].currentModulation = 0;
-		_midiChannels[midiChannel].currentVolumeExpression = 127;
-		_midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT;
-		registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20 | kMilesAdLibUpdateFlags_Reg_40 | kMilesAdLibUpdateFlags_Reg_A0;
-		break;
-
-	case MILES_CONTROLLER_ALL_NOTES_OFF:
-		for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
-			if (_virtualFmVoices[virtualFmVoice].inUse) {
-				// used
-				if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
-					// by our current MIDI channel -> noteOff
-					noteOff(midiChannel, _virtualFmVoices[virtualFmVoice].currentNote);
-				}
-			}
-		}
-		break;
-
-	default:
-		//warning("MILES-ADLIB: Unsupported control change %d", controllerNumber);
-		break;
-	}
-
-	if (registerUpdateFlags) {
-		for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
-			if (_virtualFmVoices[virtualFmVoice].inUse) {
-				// used
-				if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
-					// by our current MIDI channel -> update
-					updatePhysicalFmVoice(virtualFmVoice, true, registerUpdateFlags);
-				}
-			}
-		}
-	}
-}
-
-void MidiDriver_Miles_AdLib::programChange(byte midiChannel, byte patchId) {
-	const InstrumentEntry *instrumentPtr = NULL;
-	byte patchBank = _midiChannels[midiChannel].currentPatchBank;
-
-	// we check, if we actually have data for the requested instrument...
-	instrumentPtr = searchInstrument(patchBank, patchId);
-	if (!instrumentPtr) {
-		warning("MILES-ADLIB: unknown instrument requested (%d, %d)", patchBank, patchId);
-		return;
-	}
-
-	// and remember it in that case for the current MIDI-channel
-	_midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr;
-}
-
-const InstrumentEntry *MidiDriver_Miles_AdLib::searchInstrument(byte bankId, byte patchId) {
-	const InstrumentEntry *instrumentPtr = _instrumentTablePtr;
-
-	for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
-		if ((instrumentPtr->bankId == bankId) && (instrumentPtr->patchId == patchId)) {
-			return instrumentPtr;
-		}
-		instrumentPtr++;
-	}
-
-	return NULL;
-}
-
-void MidiDriver_Miles_AdLib::pitchBendChange(byte midiChannel, byte parameter1, byte parameter2) {
-	// Miles Audio actually didn't shift parameter 2 1 down in here
-	// which means in memory it used a 15-bit pitch bender, which also means the default was 0x4000
-	if ((parameter1 & 0x80) || (parameter2 & 0x80)) {
-		warning("MILES-ADLIB: invalid pitch bend change");
-		return;
-	}
-	_midiChannels[midiChannel].currentPitchBender = parameter1 | (parameter2 << 7);
-}
-
-void MidiDriver_Miles_AdLib::setRegister(int reg, int value) {
-	if (!(reg & 0x100)) {
-		_opl->write(0x220, reg);
-		_opl->write(0x221, value);
-		//warning("OPL write %x %x (%d)", reg, value, value);
-	} else {
-		_opl->write(0x222, reg & 0xFF);
-		_opl->write(0x223, value);
-		//warning("OPL3 write %x %x (%d)", reg & 0xFF, value, value);
-	}
-}
-
-uint32 MidiDriver_Miles_AdLib::property(int prop, uint32 param) {
-	return 0;
-}
-
-MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String instrumentDataFilename, const Common::String instrumentDataFilenameOPL3) {
-	// Load adlib instrument data from file SAMPLE.AD (OPL3: SAMPLE.OPL)
-	Common::File *fileStream = new Common::File();
-	uint32        fileSize = 0;
-	byte         *fileDataPtr = NULL;
-	uint32        fileDataOffset = 0;
-	uint32        fileDataLeft = 0;
-
-	byte curBankId = 0;
-	byte curPatchId = 0;
-
-	InstrumentEntry *instrumentTablePtr = NULL;
-	uint16           instrumentTableCount = 0;
-	InstrumentEntry *instrumentPtr = NULL;
-	uint32           instrumentOffset = 0;
-	uint16           instrumentDataSize = 0;
-
-	if (!fileStream->open(instrumentDataFilename))
-		error("MILES-ADLIB: could not open instrument file");
-
-	fileSize = fileStream->size();
-
-	fileDataPtr = new byte[fileSize];
-
-	if (fileStream->read(fileDataPtr, fileSize) != fileSize)
-		error("MILES-ADLIB: 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-ADLIB: 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-ADLIB: no instruments in instrument file");
-
-	// Allocate space for instruments
-	instrumentTablePtr = new InstrumentEntry[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 != 14)
-			error("MILES-ADLIB: unsupported instrument size");
-
-		instrumentPtr->transposition = (signed char)fileDataPtr[instrumentOffset + 2];
-		instrumentPtr->reg20op1 = fileDataPtr[instrumentOffset + 3];
-		instrumentPtr->reg40op1 = fileDataPtr[instrumentOffset + 4];
-		instrumentPtr->reg60op1 = fileDataPtr[instrumentOffset + 5];
-		instrumentPtr->reg80op1 = fileDataPtr[instrumentOffset + 6];
-		instrumentPtr->regE0op1 = fileDataPtr[instrumentOffset + 7];
-		instrumentPtr->regC0    = fileDataPtr[instrumentOffset + 8];
-		instrumentPtr->reg20op2 = fileDataPtr[instrumentOffset + 9];
-		instrumentPtr->reg40op2 = fileDataPtr[instrumentOffset + 10];
-		instrumentPtr->reg60op2 = fileDataPtr[instrumentOffset + 11];
-		instrumentPtr->reg80op2 = fileDataPtr[instrumentOffset + 12];
-		instrumentPtr->regE0op2 = fileDataPtr[instrumentOffset + 13];
-
-		// Instrument read, next instrument please
-		instrumentPtr++;
-	}
-
-	// Free instrument file data
-	delete[] fileDataPtr;
-
-	return new MidiDriver_Miles_AdLib(g_system->getMixer(), instrumentTablePtr, instrumentTableCount);
-}
-
-} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/drivers/tattoo_mididriver.h b/engines/sherlock/tattoo/drivers/tattoo_mididriver.h
deleted file mode 100644
index d140135..0000000
--- a/engines/sherlock/tattoo/drivers/tattoo_mididriver.h
+++ /dev/null
@@ -1,69 +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 SHERLOCK_TATTOO_DRIVERS_MIDIDRIVER_H
-#define SHERLOCK_TATTOO_DRIVERS_MIDIDRIVER_H
-
-#include "sherlock/sherlock.h"
-#include "audio/mididrv.h"
-#include "common/error.h"
-
-namespace Sherlock {
-
-#define MILES_MIDI_CHANNEL_COUNT 16
-
-// 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_MODULATION 1
-#define MILES_CONTROLLER_VOLUME 7
-#define MILES_CONTROLLER_EXPRESSION 11
-#define MILES_CONTROLLER_PANNING 10
-#define MILES_CONTROLLER_SUSTAIN 64
-#define MILES_CONTROLLER_PITCH_RANGE 6
-#define MILES_CONTROLLER_RESET_ALL 121
-#define MILES_CONTROLLER_ALL_NOTES_OFF 123
-#define MILES_CONTROLLER_PATCH_REVERB 59
-#define MILES_CONTROLLER_PATCH_BENDER 60
-#define MILES_CONTROLLER_REVERB_MODE 61
-#define MILES_CONTROLLER_REVERB_TIME 62
-#define MILES_CONTROLLER_REVERB_LEVEL 63
-#define MILES_CONTROLLER_RHYTHM_KEY_TIMBRE 58
-
-// 3 SysEx controllers, each range 14
-#define MILES_CONTROLLER_SYSEX_RANGE_BEGIN 32
-#define MILES_CONTROLLER_SYSEX_RANGE_END 64
-
-#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110
-#define MILES_CONTROLLER_XMIDI_RANGE_END 120
-
-// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
-#define MILES_PITCHBENDER_DEFAULT 0x2000
-
-extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String instrumentDataFilename, const Common::String instrumentDataFilenameOPL3);
-
-extern MidiDriver *MidiDriver_Miles_MT32_create(const Common::String instrumentDataFilename);
-
-} // End of namespace Sherlock
-
-#endif // SHERLOCK_TATTOO_DRIVERS_MIDIDRIVER_H
diff --git a/engines/sherlock/tattoo/drivers/tattoo_mt32.cpp b/engines/sherlock/tattoo/drivers/tattoo_mt32.cpp
deleted file mode 100644
index 73bc1b9..0000000
--- a/engines/sherlock/tattoo/drivers/tattoo_mt32.cpp
+++ /dev/null
@@ -1,437 +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 "sherlock/sherlock.h"
-#include "sherlock/tattoo/drivers/tattoo_mididriver.h"
-
-#include "common/config-manager.h"
-#include "common/file.h"
-#include "common/system.h"
-#include "common/textconsole.h"
-
-namespace Sherlock {
-
-#define MILES_MT32_PATCH_COUNT 128
-
-const byte milesMT32SysExResetParameters[] = {
-	0x01, 0xFF
-};
-
-const byte milesMT32SysExChansSetup[] = {
-	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF
-};
-
-const byte milesMT32SysExPartialReserveTable[] = {
-	0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, 0xFF
-};
-
-const byte milesMT32SysExInitReverb[] = {
-	0x00, 0x03, 0x02, 0xFF // Reverb mode 0, reverb time 3, reverb level 2
-};
-
-class MidiDriver_Miles_MT32 : public MidiDriver {
-public:
-	MidiDriver_Miles_MT32();
-	virtual ~MidiDriver_Miles_MT32();
-
-	// MidiDriver
-	int open();
-	void close();
-	bool isOpen() const { return _isOpen; }
-
-	void send(uint32 b);
-
-	MidiChannel *allocateChannel() {
-		if (_driver)
-			return _driver->allocateChannel();
-		return NULL;
-	}
-	MidiChannel *getPercussionChannel() {
-		if (_driver)
-			return _driver->getPercussionChannel();
-		return NULL;
-	}
-
-	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
-		if (_driver)
-			_driver->setTimerCallback(timer_param, timer_proc);
-	}
-
-	uint32 getBaseTempo() {
-		if (_driver) {
-			return _driver->getBaseTempo();
-		}
-		return 1000000 / _baseFreq;
-	}
-
-protected:
-	Common::Mutex _mutex;
-	MidiDriver *_driver;
-	bool _MT32;
-	bool _nativeMT32;
-
-	bool _isOpen;
-	int _baseFreq;
-
-public:
-
-private:
-	void resetMT32();
-
-	void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
-
-	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);
-
-	void setupPatch(byte patchId, byte patchBank);
-
-private:
-	struct MidiChannelEntry {
-		byte   currentPatchBank;
-		byte   currentPatchId;
-		bool   patchIdSet;
-
-		MidiChannelEntry() : currentPatchBank(0),
-							currentPatchId(0),
-							patchIdSet(false) { }
-	};
-
-	// stores information about all MIDI channels
-	MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
-
-	byte _patchesBank[MILES_MT32_PATCH_COUNT];
-};
-
-MidiDriver_Miles_MT32::MidiDriver_Miles_MT32() {
-	_driver = NULL;
-	_isOpen = false;
-	_MT32 = false;
-	_nativeMT32 = false;
-	_baseFreq = 250;
-
-	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::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;
-
-	sysExChecksum -= sysExMessage[4];
-	sysExChecksum -= sysExMessage[5];
-	sysExChecksum -= sysExMessage[6];
-
-	sysExPos      = 7;
-	while (1) {
-		sysExByte = *dataPtr++;
-		if (sysExByte == 0xff)
-			break; // Message done
-
-		assert(sysExPos < sizeof(sysExMessage));
-		sysExMessage[sysExPos++] = sysExByte;
-		sysExChecksum -= sysExByte;
-	}
-
-	// Calculate checksum
-	assert(sysExPos < sizeof(sysExMessage));
-	sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
-
-	// Send SysEx
-	_driver->sysEx(sysExMessage, sysExPos);
-
-	// Wait the time it takes to send the SysEx data
-	uint32 delay = (sysExPos + 2) * 1000 / 3125;
-
-	// Plus an additional delay for the MT-32 rev00
-	if (_nativeMT32)
-		delay += 40;
-
-	g_system->delayMillis(delay);
-}
-
-// 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 channel = b & 0xf;
-	byte op1 = (b >> 8) & 0xff;
-	byte op2 = (b >> 16) & 0xff;
-
-	switch (command) {
-	case 0x80: // note off
-	case 0x90: // note on
-	case 0xe0: // pitch bend change
-		_driver->send(b);
-		break;
-	case 0xb0: // Control change
-		controlChange(channel, op1, op2);
-		break;
-	case 0xc0: // Program Change
-		programChange(channel, op1);
-		break;
-	case 0xa0: // Polyphonic key pressure (aftertouch)
-	case 0xd0: // Channel pressure (aftertouch)
-		// Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
-		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;
-
-	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:
-		// uses .MT data, cannot implement atm
-		return;
-
-	case MILES_CONTROLLER_PROTECT_TIMBRE:
-		// timbre .MT data, cannot implement atm
-		return;
-
-	default:
-		break;
-	}
-
-	if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
-		// send SysEx
-		warning("MILES-MT32: embedded SysEx controller %2x, value %2x", controllerNumber, controllerValue);
-		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];
-
-	// remember patch id for the current MIDI-channel
-	_midiChannels[midiChannel].currentPatchId = patchId;
-
-	if (channelPatchBank != activePatchBank) {
-		// associate patch with timbre
-		setupPatch(patchId, channelPatchBank);
-		warning("setup patch");
-	}
-
-	// Search timbre and remember it (only used when timbre file is available)
-	// TODO
-
-	// Finally send to MT32
-	_driver->send(0xC0 | midiChannel | (patchId << 8));
-}
-
-void MidiDriver_Miles_MT32::setupPatch(byte patchId, byte patchBank) {
-	byte timbreId = 0;
-
-	_patchesBank[patchId] = patchBank;
-
-	if (patchBank) {
-		// non-built-in bank
-		// TODO: search timbre
-	}
-
-	// for built-in bank (or timbres, that are not available) use default MT32 timbres
-	timbreId = patchId & 0x3F;
-	if (!(patchId & 0x40)) {
-		writePatchTimbre(patchId, 0, timbreId); // Group A
-	} else {
-		writePatchTimbre(patchId, 1, timbreId); // Group B
-	}
-}
-
-void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
-	byte   sysExData[3];
-	uint32 targetAddress = 0;
-
-	targetAddress = ((patchId << 3) << 16) | 0x000500;
-
-	sysExData[0] = timbreGroup;
-	sysExData[1] = timbreId;
-	sysExData[2] = 0xFF; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
-	byte   sysExData[2];
-	uint32 targetAddress = 0;
-
-	targetAddress = (((patchId << 3) + index ) << 16) | 0x000500;
-
-	sysExData[0] = patchValue;
-	sysExData[1] = 0xFF; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
-	byte   sysExData[2];
-	uint32 targetAddress = 0;
-
-	targetAddress = 0x100000 | index;
-
-	sysExData[0] = value;
-	sysExData[1] = 0xFF; // terminator
-
-	MT32SysEx(targetAddress, sysExData);
-}
-
-MidiDriver *MidiDriver_Miles_MT32_create(const Common::String instrumentDataFilename) {
-	// For some games there are timbre files called [something].MT
-	// Sherlock Holmes 2 doesn't have one of those
-	// so I can't implement them
-	return new MidiDriver_Miles_MT32();
-}
-
-} // End of namespace Sherlock






More information about the Scummvm-git-logs mailing list