[Scummvm-cvs-logs] scummvm master -> d24c68c739dadd1404d937f9a21d93e8841a09ee

m-kiewitz m_kiewitz at users.sourceforge.net
Sun Jun 21 00:46:00 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:
d24c68c739 AGOS: implement Accolade AdLib + MT32 music drivers


Commit: d24c68c739dadd1404d937f9a21d93e8841a09ee
    https://github.com/scummvm/scummvm/commit/d24c68c739dadd1404d937f9a21d93e8841a09ee
Author: Martin Kiewitz (m_kiewitz at users.sourceforge.net)
Date: 2015-06-21T00:45:45+02:00

Commit Message:
AGOS: implement Accolade AdLib + MT32 music drivers

- both known variants are supported (INSTR.DAT + MUSIC.DRV)
- INSTR.DAT/MUSIC.DRV holds channel mapping, instrument mapping, etc.
- fixed bug inside S1D MidiParser, that ruined some instrument changes
0xFC header was seen as 2 byte header, but it's 4 bytes in Elvira 2
and 5 bytes in Waxworks / Simon 1 demo
- dynamic channel allocation for the MUSIC.DRV adlib driver is not
implemented atm, simply because at least the demos of Waxworks and
Simon 1 do not use this feature
- sound effects of Waxworks are not implemented atm
- note: the game "Altered Destiny" uses Accolade INSTR.DAT variant too

Changed paths:
  A engines/agos/drivers/accolade/adlib.cpp
  A engines/agos/drivers/accolade/mididriver.h
  A engines/agos/drivers/accolade/mt32.cpp
    engines/agos/agos.cpp
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/midiparser_s1d.cpp
    engines/agos/module.mk



diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 6eda2eb..8952e64 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -585,7 +585,9 @@ Common::Error AGOSEngine::init() {
 		((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) ||
 		(getPlatform() == Common::kPlatformDOS)) {
 
-		int ret = _midi->open(getGameType());
+		bool isDemo = (getFeatures() & GF_DEMO) ? true : false;
+
+		int ret = _midi->open(getGameType(), isDemo);
 		if (ret)
 			warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));
 
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
new file mode 100644
index 0000000..2ef1264
--- /dev/null
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -0,0 +1,873 @@
+/* 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 "agos/agos.h"
+#include "agos/drivers/accolade/mididriver.h"
+
+#include "common/file.h"
+#include "common/mutex.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "audio/fmopl.h"
+#include "audio/softsynth/emumidi.h"
+
+namespace AGOS {
+
+#define AGOS_ADLIB_VOICES_COUNT 11
+#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6
+#define AGOS_ADLIB_VOICES_PERCUSSION_START 6
+#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5
+#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9
+
+// 5 instruments on top of the regular MIDI ones
+// used by the MUSIC.DRV variant for percussion instruments
+#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5
+
+const byte adlib_Operator1Register[AGOS_ADLIB_VOICES_COUNT] = {
+	0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11
+};
+
+const byte adlib_Operator2Register[AGOS_ADLIB_VOICES_COUNT] = {
+	0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+// percussion:
+//  voice  6 - base drum - also uses operator 13h
+//  voice  7 - snare drum
+//  voice  8 - tom tom
+//  voice  9 - cymbal
+//  voice 10 - hi hat
+const byte adlib_percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = {
+	0x10, 0x08, 0x04, 0x02, 0x01
+};
+
+// hardcoded, dumped from Accolade music system
+// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently
+const byte adlib_percussionKeyNoteChannelTable[] = {
+	0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08,
+	0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F,
+	0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+	0x08, 0x08, 0x08, 0x08, 0x0A, 0x0F, 0x0F, 0x08, 0x0F, 0x08
+};
+
+struct adlib_InstrumentEntry {
+	byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
+	byte reg40op1; // Level Key Scaling / Total Level
+	byte reg60op1; // Attack Rate / Decay Rate
+	byte reg80op1; // Sustain Level / Release Rate
+	byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
+	byte reg40op2; // Level Key Scaling / Total Level
+	byte reg60op2; // Attack Rate / Decay Rate
+	byte reg80op2; // Sustain Level / Release Rate
+	byte regC0;    // Feedback / Algorithm, bit 0 - set -> both operators in use
+};
+
+// hardcoded, dumped from Accolade music system (INSTR.DAT variant)
+const uint16 adlib_FrequencyLookUpTable[12] = {
+	0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF,
+	0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B
+};
+
+// hardcoded, dumped from Accolade music system (MUSIC.DRV variant)
+const uint16 adlib_FrequencyLookUpTableMusicDrv[12] = {
+	0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB,
+	0x0306, 0x0334, 0x0365, 0x0399, 0x03CF
+};
+
+//
+// Accolade adlib music driver
+//
+// Remarks:
+//
+// There are at least 2 variants of this sound system.
+// One for the games Elvira 1 + Elvira 2
+// It seems it was also used for the game "Altered Destiny"
+// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo
+//
+// First one uses the file INSTR.DAT for instrument data, channel mapping etc.
+// Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc.
+//
+// The second variant supported dynamic channel allocation for the FM voice channels, but this
+// feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too.
+//
+// I have currently not implemented dynamic channel allocation.
+
+class MidiDriver_Accolade_AdLib : public MidiDriver_Emulated {
+public:
+	MidiDriver_Accolade_AdLib(Audio::Mixer *mixer);
+	virtual ~MidiDriver_Accolade_AdLib();
+
+	// MidiDriver
+	int open();
+	void close();
+	void send(uint32 b);
+	MidiChannel *allocateChannel() { return NULL; }
+	MidiChannel *getPercussionChannel() { return NULL; }
+
+	// AudioStream
+	bool isStereo() const { return false; }
+	int getRate() const { return _mixer->getOutputRate(); }
+	int getPolyphony() const { return AGOS_ADLIB_VOICES_COUNT; }
+	bool hasRhythmChannel() const { return false; }
+
+	// MidiDriver_Emulated
+	void generateSamples(int16 *buf, int len);
+
+	void setVolume(byte volume);
+	virtual uint32 property(int prop, uint32 param);
+
+	bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
+
+private:
+	bool _musicDrvMode;
+
+	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel
+	byte _MIDIchannelMapping[AGOS_MIDI_CHANNEL_COUNT];
+	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments
+	byte _MIDIinstrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
+	// from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument
+	signed char _MIDIinstrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT];
+	// simple mapping between MIDI key notes and MT32 key notes
+	byte _MIDIpercussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT];
+
+	// from INSTR.DAT/MUSIC.DRV - adlib instrument data
+	adlib_InstrumentEntry *_instrumentTable;
+	byte                   _instrumentCount;
+
+	struct adlib_ChannelEntry {
+		const  adlib_InstrumentEntry *currentInstrumentPtr;
+		byte   currentNote;
+		byte   currentA0hReg;
+		byte   currentB0hReg;
+		int16  volumeAdjust;
+
+		adlib_ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0),
+								currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { }
+	};
+
+	byte _percussionReg;
+
+	OPL::OPL *_opl;
+	int _masterVolume;
+
+	// points to a MIDI channel for each of the new voice channels
+	byte _voiceChannelMapping[AGOS_ADLIB_VOICES_COUNT];
+
+	// stores information about all FM voice channels
+	adlib_ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT];
+
+protected:
+	void onTimer();
+
+private:
+	void resetAdLib();
+	void resetAdLib_OperatorRegisters(byte baseRegister, byte value);
+	void resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value);
+
+	void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
+	void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
+	void setRegister(int reg, int value);
+	void noteOn(byte FMvoiceChannel, byte note, byte velocity);
+	void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte adjustedVelocity);
+	void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote);
+};
+
+MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib(Audio::Mixer *mixer)
+		: MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) {
+	memset(_MIDIchannelMapping, 0, sizeof(_MIDIchannelMapping));
+	memset(_MIDIinstrumentMapping, 0, sizeof(_MIDIinstrumentMapping));
+	memset(_MIDIinstrumentVolumeAdjust, 0, sizeof(_MIDIinstrumentVolumeAdjust));
+	memset(_MIDIpercussionKeyNoteMapping, 0, sizeof(_MIDIpercussionKeyNoteMapping));
+
+	_instrumentTable = NULL;
+	_instrumentCount = 0;
+	_musicDrvMode = false;
+	_percussionReg = 0x20;
+}
+
+MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() {
+	if (_instrumentTable) {
+		delete[] _instrumentTable;
+		_instrumentCount = 0;
+	}
+}
+
+int MidiDriver_Accolade_AdLib::open() {
+	int rate = _mixer->getOutputRate();
+
+//	debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");
+
+	_opl = OPL::Config::create(OPL::Config::kOpl2);
+
+	if (!_opl)
+		return -1;
+
+	_opl->init(rate);
+
+	MidiDriver_Emulated::open();
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
+
+	resetAdLib();
+
+	// Finally set up default instruments
+	for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) {
+		if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+			// Regular FM voices with instrument 0
+			programChangeSetInstrument(FMvoiceNr, 0, 0);
+		} else {
+			byte percussionInstrument;
+			if (!_musicDrvMode) {
+				// INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5
+				percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1;
+			} else {
+				// MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84
+				percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80;
+			}
+			programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument);
+		}
+	}
+
+	// driver initialization does this here:
+	// INSTR.DAT
+	// noteOn(9, 0x29, 0);
+	// noteOff(9, 0x26, false);
+	// MUSIC.DRV
+	// noteOn(9, 0x26, 0);
+	// noteOff(9, 0x26, false);
+
+	warning("End of open()");
+	return 0;
+}
+
+void MidiDriver_Accolade_AdLib::close() {
+	_mixer->stopHandle(_mixerSoundHandle);
+
+	delete _opl;
+}
+
+void MidiDriver_Accolade_AdLib::setVolume(byte volume) {
+	_masterVolume = volume;
+	//renewNotes(-1, true);
+}
+
+void MidiDriver_Accolade_AdLib::onTimer() {
+}
+
+void MidiDriver_Accolade_AdLib::resetAdLib() {
+	// The original driver sent 0x00 to register 0x00 up to 0xF5
+	setRegister(0xBD, 0x00); // Disable rhythm
+
+	// reset FM voice instrument data
+	resetAdLib_OperatorRegisters(0x20, 0);
+	resetAdLib_OperatorRegisters(0x60, 0);
+	resetAdLib_OperatorRegisters(0x80, 0);
+	resetAdLib_FMVoiceChannelRegisters(0xA0, 0);
+	resetAdLib_FMVoiceChannelRegisters(0xB0, 0);
+	resetAdLib_FMVoiceChannelRegisters(0xC0, 0);
+	resetAdLib_OperatorRegisters(0xE0, 0);
+	resetAdLib_OperatorRegisters(0x40, 0x3F); // original driver sent 0x00
+
+	setRegister(0x01, 0x20); // enable waveform control on both operators
+	setRegister(0x04, 0x60); // Timer control
+
+	setRegister(0x08, 0);    // select FM music mode
+	setRegister(0xBD, 0x20); // Enable rhythm
+
+	// reset our percussion register
+	_percussionReg = 0x20;
+}
+
+void MidiDriver_Accolade_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte value) {
+	byte operatorIndex;
+
+	for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) {
+		switch (operatorIndex) {
+		case 0x06:
+		case 0x07:
+		case 0x0E:
+		case 0x0F:
+			break;
+		default:
+			setRegister(baseRegister + operatorIndex, value);
+		}
+	}
+}
+
+void MidiDriver_Accolade_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value) {
+	byte FMvoiceChannel;
+
+	for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+		setRegister(baseRegister + FMvoiceChannel, value);
+	}
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Accolade_AdLib::send(uint32 b) {
+	byte command = b & 0xf0;
+	byte channel = b & 0xf;
+	byte op1 = (b >> 8) & 0xff;
+	byte op2 = (b >> 16) & 0xff;
+
+	byte mappedChannel    = _MIDIchannelMapping[channel];
+	byte mappedInstrument = 0;
+
+	// Ignore everything that is outside of our channel range
+	if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT)
+		return;
+
+	switch (command) {
+	case 0x80:
+		noteOff(mappedChannel, op1, false);
+		break;
+	case 0x90:
+		// Convert noteOn with velocity 0 to a noteOff
+		if (op2 == 0)
+			return noteOff(mappedChannel, op1, false);
+
+		noteOn(mappedChannel, op1, op2);
+		break;
+	case 0xb0: // Control change
+		// Doesn't seem to be implemented
+		break;
+	case 0xc0: // Program Change
+		mappedInstrument = _MIDIinstrumentMapping[op1];
+		programChange(mappedChannel, mappedInstrument, op1);
+		break;
+	case 0xa0: // Polyphonic key pressure (aftertouch)
+	case 0xd0: // Channel pressure (aftertouch)
+		// Aftertouch doesn't seem to be implemented
+		break;
+	case 0xe0:
+		// No pitch bend change
+		break;
+	case 0xf0: // SysEx
+		warning("ADLIB: SysEx: %x", b);
+		break;
+	default:
+		warning("ADLIB: Unknown event %02x", command);
+	}
+}
+
+void MidiDriver_Accolade_AdLib::generateSamples(int16 *data, int len) {
+	_opl->readBuffer(data, len);
+}
+
+void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) {
+	byte adjustedNote     = note;
+	byte adjustedVelocity = velocity;
+	byte regValueA0h      = 0;
+	byte regValueB0h      = 0;
+
+	// adjust velocity
+	int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust;
+	channelVolumeAdjust += adjustedVelocity;
+	channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F);
+
+	// TODO: adjust to global volume
+	// original drivers had a global volume variable, which was 0 for full volume, -64 for half volume
+	// and -128 for mute
+
+	adjustedVelocity = channelVolumeAdjust;
+
+	if (!_musicDrvMode) {
+		// INSTR.DAT
+		// force note-off
+		noteOff(FMvoiceChannel, note, true);
+
+	} else {
+		// MUSIC.DRV
+		if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+			// force note-off, but only for actual FM voice channels
+			noteOff(FMvoiceChannel, note, true);
+		}
+	}
+
+	if (FMvoiceChannel != 9) {
+		// regular FM voice
+
+		if (!_musicDrvMode) {
+			// INSTR.DAT: adjust key note
+			while (adjustedNote < 24)
+				adjustedNote += 12;
+			adjustedNote -= 12;
+		}
+
+	} else {
+		// percussion channel
+		// MUSIC.DRV variant didn't do this adjustment, it directly used a pointer
+		adjustedNote -= 36;
+		if (adjustedNote > 40) { // Security check
+			warning("ADLIB: bad percussion channel note");
+			return;
+		}
+
+		byte percussionChannel = adlib_percussionKeyNoteChannelTable[adjustedNote];
+		if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT)
+			return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug
+
+		// Map the keynote accordingly
+		adjustedNote = _MIDIpercussionKeyNoteMapping[adjustedNote];
+		// Now overwrite the FM voice channel
+		FMvoiceChannel = percussionChannel;
+	}
+
+	if (!_musicDrvMode) {
+		// INSTR.DAT
+
+		// Save this key note
+		_channels[FMvoiceChannel].currentNote = adjustedNote;
+
+		adjustedVelocity += 24;
+		if (adjustedVelocity > 120)
+			adjustedVelocity = 120;
+		adjustedVelocity = adjustedVelocity >> 1; // divide by 2
+
+	} else {
+		// MUSIC.DRV
+		adjustedVelocity = adjustedVelocity >> 1; // divide by 2
+	}
+
+	// Set volume of voice channel
+	noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity);
+	if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+		// Set second operator for FM voices + first percussion
+		noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity);
+	}
+
+	if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+		// Percussion
+		byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START;
+
+		// Enable bit of the requested percussion type
+		assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT);
+		_percussionReg |= adlib_percussionBits[percussionIdx];
+		setRegister(0xBD, _percussionReg);
+	}
+
+	if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) {
+		// FM voice, Base Drum, Snare Drum + Tom Tom
+		byte adlibNote = adjustedNote;
+		byte adlibOctave = 0;
+		byte adlibFrequencyIdx = 0;
+		uint16 adlibFrequency = 0;
+
+		if (!_musicDrvMode) {
+			// INSTR.DAT
+			if (adlibNote >= 0x60)
+				adlibNote = 0x5F;
+
+			adlibOctave = (adlibNote / 12) - 1;
+			adlibFrequencyIdx = adlibNote % 12;
+			adlibFrequency = adlib_FrequencyLookUpTable[adlibFrequencyIdx];
+
+			if (adlibFrequency & 0x8000)
+				adlibOctave++;
+			if (adlibOctave & 0x80) {
+				adlibOctave++;
+				adlibFrequency = adlibFrequency >> 1;
+			}
+
+		} else {
+			// MUSIC.DRV variant
+			if (adlibNote >= 19)
+				adlibNote -= 19;
+
+			adlibOctave = (adlibNote / 12);
+			adlibFrequencyIdx = adlibNote % 12;
+			// additional code, that will lookup octave and do a multiplication with it
+			// noteOn however calls the frequency calculation in a way that it multiplies with 0
+			adlibFrequency = adlib_FrequencyLookUpTableMusicDrv[adlibFrequencyIdx];
+		}
+
+		regValueA0h = adlibFrequency & 0xFF;
+		regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2);
+		if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+			// set Key-On flag for regular FM voices, but not for percussion
+			regValueB0h |= 0x20;
+		}
+
+		setRegister(0xA0 + FMvoiceChannel, regValueA0h);
+		setRegister(0xB0 + FMvoiceChannel, regValueB0h);
+		_channels[FMvoiceChannel].currentA0hReg = regValueA0h;
+		_channels[FMvoiceChannel].currentB0hReg = regValueB0h;
+
+		if (_musicDrvMode) {
+			// MUSIC.DRV
+			if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) {
+				_channels[FMvoiceChannel].currentNote = adjustedNote;
+			}
+		}
+	}
+}
+
+// 100% the same for INSTR.DAT and MUSIC.DRV variants
+// except for a bug, that was introduced for MUSIC.DRV
+void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte adjustedVelocity) {
+	byte operatorReg = 0;
+	byte regValue40h = 0;
+	const adlib_InstrumentEntry *curInstrument = NULL;
+
+	regValue40h = (63 - adjustedVelocity) & 0x3F;
+
+	if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) {
+		// first operator of FM voice channels or first percussion channel
+		curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr;
+		if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound
+			// only one does, instrument wants fixed volume
+			if (operatorNr == 1) {
+				regValue40h = curInstrument->reg40op1;
+			} else {
+				regValue40h = curInstrument->reg40op2;
+			}
+
+			// not sure, if we are supposed to implement these bugs, or not
+			if (!_musicDrvMode) {
+				// Table is 16 bytes instead of 18 bytes
+				if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) {
+					regValue40h = 0;
+					warning("volume set bug (original)");
+				}
+			}
+			if (_musicDrvMode) {
+				// MUSIC.DRV variant has a bug, which will overwrite these registers
+				// for all operators above 11 / 0Bh, which means percussion will always
+				// get a value of 0 (the table holding those bytes was 12 bytes instead of 18
+				if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+					regValue40h = 0;
+					warning("volume set bug (original)");
+				}
+			}
+		}
+	}
+
+	if (operatorNr == 1) {
+		operatorReg = adlib_Operator1Register[FMvoiceChannel];
+	} else {
+		operatorReg = adlib_Operator2Register[FMvoiceChannel];
+	}
+	assert(operatorReg != 0xFF); // Security check
+	setRegister(0x40 + operatorReg, regValue40h);
+}
+
+void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) {
+	byte adjustedNote = note;
+	byte regValueB0h = 0;
+
+	if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+		// regular FM voice
+
+		if (!_musicDrvMode) {
+			// INSTR.DAT: adjust key note
+			while (adjustedNote < 24)
+				adjustedNote += 12;
+			adjustedNote -= 12;
+		}
+
+		if (!dontCheckNote) {
+			// check, if current note is also the current actually playing channel note
+			if (_channels[FMvoiceChannel].currentNote != adjustedNote)
+				return; // not the same -> ignore this note off command
+		}
+
+		regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit
+		setRegister(0xB0 + FMvoiceChannel, regValueB0h);
+
+	} else {
+		// percussion
+		adjustedNote -= 36;
+		if (adjustedNote > 40) { // Security check
+			warning("ADLIB: bad percussion channel note");
+			return;
+		}
+
+		byte percussionChannel = adlib_percussionKeyNoteChannelTable[adjustedNote];
+		if (percussionChannel > AGOS_ADLIB_VOICES_COUNT)
+			return;
+
+		byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START;
+
+		// Disable bit of the requested percussion type
+		assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT);
+		_percussionReg &= ~adlib_percussionBits[percussionIdx];
+		setRegister(0xBD, _percussionReg);
+	}
+}
+
+void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
+	if (mappedInstrumentNr >= _instrumentCount) {
+		warning("ADLIB: tried to set non-existant instrument");
+		return; // out of range
+	}
+
+	// setup instrument
+	//warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);
+
+	if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+		// Regular FM voice
+		programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr);
+
+	} else {
+		// Percussion
+		// set default instrument (again)
+		byte percussionInstrumentNr = 0;
+		const adlib_InstrumentEntry *instrumentPtr;
+
+		if (!_musicDrvMode) {
+			// INSTR.DAT: percussion default instruments start at instrument 1
+			percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1;
+		} else {
+			// MUSIC.DRV: percussion default instruments start at instrument 0x80
+			percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80;
+		}
+		if (percussionInstrumentNr >= _instrumentCount) {
+			warning("ADLIB: tried to set non-existant instrument");
+			return;
+		}
+		instrumentPtr = &_instrumentTable[percussionInstrumentNr];
+		_channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
+		_channels[FMvoiceChannel].volumeAdjust         = _MIDIinstrumentVolumeAdjust[percussionInstrumentNr];
+	}
+}
+
+void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
+	const adlib_InstrumentEntry *instrumentPtr;
+	byte op1Reg = 0;
+	byte op2Reg = 0;
+
+	if (mappedInstrumentNr >= _instrumentCount) {
+		warning("ADLIB: tried to set non-existant instrument");
+		return; // out of range
+	}
+
+	// setup instrument
+	instrumentPtr = &_instrumentTable[mappedInstrumentNr];
+	//warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);
+
+	op1Reg = adlib_Operator1Register[FMvoiceChannel];
+	op2Reg = adlib_Operator2Register[FMvoiceChannel];
+
+	setRegister(0x20 + op1Reg, instrumentPtr->reg20op1);
+	setRegister(0x40 + op1Reg, instrumentPtr->reg40op1);
+	setRegister(0x60 + op1Reg, instrumentPtr->reg60op1);
+	setRegister(0x80 + op1Reg, instrumentPtr->reg80op1);
+
+	if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
+		// set 2nd operator as well for FM voices and first percussion voice
+		setRegister(0x20 + op2Reg, instrumentPtr->reg20op2);
+		setRegister(0x40 + op2Reg, instrumentPtr->reg40op2);
+		setRegister(0x60 + op2Reg, instrumentPtr->reg60op2);
+		setRegister(0x80 + op2Reg, instrumentPtr->reg80op2);
+
+		if (!_musicDrvMode) {
+			// set Feedback / Algorithm as well
+			setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
+		} else {
+			if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
+				// set Feedback / Algorithm as well for regular FM voices only
+				setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
+			}
+		}
+	}
+
+	// Remember instrument
+	_channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
+	_channels[FMvoiceChannel].volumeAdjust         = _MIDIinstrumentVolumeAdjust[MIDIinstrumentNr];
+}
+
+void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) {
+	_opl->write(0x220, reg);
+	_opl->write(0x221, value);
+	//warning("OPL %x %x (%d)", reg, value, value);
+}
+
+uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) {
+	return 0;
+}
+
+// Called right at the start, we get an INSTR.DAT entry
+bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
+	uint16 channelMappingOffset         = 0;
+	uint16 channelMappingSize           = 0;
+	uint16 instrumentMappingOffset      = 0;
+	uint16 instrumentMappingSize        = 0;
+	uint16 instrumentVolumeAdjustOffset = 0;
+	uint16 instrumentVolumeAdjustSize   = 0;
+	uint16 keyNoteMappingOffset         = 0;
+	uint16 keyNoteMappingSize           = 0;
+	uint16 instrumentCount              = 0;
+	uint16 instrumentDataOffset         = 0;
+	uint16 instrumentDataSize           = 0;
+	uint16 instrumentEntrySize          = 0;
+
+	if (!useMusicDrvFile) {
+		// INSTR.DAT: we expect at least 354 bytes
+		if (driverDataSize < 354)
+			return false;
+
+		// Data is like this:
+		// 128 bytes  instrument mapping
+		// 128 bytes  instrument volume adjust (signed!)
+		//  16 bytes  unknown
+		//  16 bytes  channel mapping
+		//  64 bytes  key note mapping (not used for MT32)
+		//   1 byte   instrument count
+		//   1 byte   bytes per instrument
+		//   x bytes  no instruments used for MT32
+
+		channelMappingOffset         = 256 + 16;
+		channelMappingSize           = 16;
+		instrumentMappingOffset      = 0;
+		instrumentMappingSize        = 128;
+		instrumentVolumeAdjustOffset = 128;
+		instrumentVolumeAdjustSize   = 128;
+		keyNoteMappingOffset         = 256 + 16 + 16;
+		keyNoteMappingSize           = 64;
+
+		byte instrDatInstrumentCount    = driverData[256 + 16 + 16 + 64];
+		byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1];
+
+		// We expect 9 bytes per instrument
+		if (instrDatBytesPerInstrument != 9)
+			return false;
+		// And we also expect at least one adlib instrument
+		if (!instrDatInstrumentCount)
+			return false;
+
+		instrumentCount      = instrDatInstrumentCount;
+		instrumentDataOffset = 256 + 16 + 16 + 64 + 2;
+		instrumentDataSize   = instrDatBytesPerInstrument * instrDatInstrumentCount;
+		instrumentEntrySize  = instrDatBytesPerInstrument;
+
+	} else {
+		// MUSIC.DRV: we expect at least 468 bytes
+		if (driverDataSize < 468)
+			return false;
+
+		// music.drv is basically a driver, but with a few fixed locations for certain data
+
+		channelMappingOffset         = 396;
+		channelMappingSize           = 16;
+		instrumentMappingOffset      = 140;
+		instrumentMappingSize        = 128;
+		instrumentVolumeAdjustOffset = 140 + 128;
+		instrumentVolumeAdjustSize   = 128;
+		keyNoteMappingOffset         = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn)
+		keyNoteMappingSize           = 64;
+
+		// seems to have used 128 + 5 instruments
+		// 128 regular ones and an additional 5 for percussion
+		instrumentCount         = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT;
+		instrumentDataOffset    = 722;
+		instrumentEntrySize     = 9;
+		instrumentDataSize      = instrumentCount * instrumentEntrySize;
+	}
+
+	// Channel mapping
+	if (channelMappingSize) {
+		// Get these 16 bytes for MIDI channel mapping
+		if (channelMappingSize != sizeof(_MIDIchannelMapping))
+			return false;
+
+		memcpy(_MIDIchannelMapping, driverData + channelMappingOffset, sizeof(_MIDIchannelMapping));
+	} else {
+		// Set up straight mapping
+		for (uint16 channelNr = 0; channelNr < sizeof(_MIDIchannelMapping); channelNr++) {
+			_MIDIchannelMapping[channelNr] = channelNr;
+		}
+	}
+
+	if (instrumentMappingSize) {
+		// And these for instrument mapping
+		if (instrumentMappingSize > sizeof(_MIDIinstrumentMapping))
+			return false;
+
+		memcpy(_MIDIinstrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
+	}
+	// Set up straight mapping for the remaining data
+	for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_MIDIinstrumentMapping); instrumentNr++) {
+		_MIDIinstrumentMapping[instrumentNr] = instrumentNr;
+	}
+
+	if (instrumentVolumeAdjustSize) {
+		if (instrumentVolumeAdjustSize != sizeof(_MIDIinstrumentVolumeAdjust))
+			return false;
+
+		memcpy(_MIDIinstrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize);
+	}
+
+	// Get key note mapping, if available
+	if (keyNoteMappingSize) {
+		if (keyNoteMappingSize != sizeof(_MIDIpercussionKeyNoteMapping))
+			return false;
+
+		memcpy(_MIDIpercussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize);
+	}
+
+	// Check, if there are enough bytes left to hold all instrument data
+	if (driverDataSize < (instrumentDataOffset + instrumentDataSize))
+		return false;
+
+	// We release previous instrument data, just in case
+	if (_instrumentTable)
+		delete[] _instrumentTable;
+
+	_instrumentTable = new adlib_InstrumentEntry[instrumentCount];
+	_instrumentCount = instrumentCount;
+
+	byte                  *instrDATReadPtr    = driverData + instrumentDataOffset;
+	adlib_InstrumentEntry *instrumentWritePtr = _instrumentTable;
+
+	for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
+		memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(adlib_InstrumentEntry));
+		instrDATReadPtr += instrumentEntrySize;
+		instrumentWritePtr++;
+	}
+
+	// Enable MUSIC.DRV-Mode (slightly different behaviour)
+	if (useMusicDrvFile)
+		_musicDrvMode = true;
+
+	if (_musicDrvMode) {
+		// Extra code for MUSIC.DRV
+
+		// This was done during "programChange" in the original driver
+		instrumentWritePtr = _instrumentTable;
+		for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
+			instrumentWritePtr->reg80op1 |= 0x03; // set release rate
+			instrumentWritePtr->reg80op2 |= 0x03;
+			instrumentWritePtr++;
+		}
+	}
+	return true;
+}
+
+MidiDriver *MidiDriver_Accolade_AdLib_create() {
+	return new MidiDriver_Accolade_AdLib(g_system->getMixer());
+}
+
+bool MidiDriver_Accolade_AdLib_setupInstruments(MidiDriver *driver, byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
+	return static_cast<MidiDriver_Accolade_AdLib *>(driver)->setupInstruments(driverData, driverDataSize, useMusicDrvFile);
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h
new file mode 100644
index 0000000..81064ec
--- /dev/null
+++ b/engines/agos/drivers/accolade/mididriver.h
@@ -0,0 +1,45 @@
+/* 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 AGOS_SOFTSEQ_MIDIDRIVER_H
+#define AGOS_SOFTSEQ_MIDIDRIVER_H
+
+#include "agos/agos.h"
+#include "audio/mididrv.h"
+#include "common/error.h"
+
+namespace AGOS {
+
+#define AGOS_MIDI_CHANNEL_COUNT 16
+#define AGOS_MIDI_INSTRUMENT_COUNT 128
+
+#define AGOS_MIDI_KEYNOTE_COUNT 64
+
+extern MidiDriver *MidiDriver_Accolade_AdLib_create();
+extern bool MidiDriver_Accolade_AdLib_setupInstruments(MidiDriver *driver, byte *instrDATData, uint16 instrDATDataSize, bool useMusicDrvFile);
+
+extern MidiDriver *MidiDriver_Accolade_MT32_create();
+extern bool MidiDriver_Accolade_MT32_setupInstruments(MidiDriver *driver, byte *instrDATData, uint16 instrDATDataSize, bool useMusicDrvFile);
+
+} // End of namespace AGOS
+
+#endif // AGOS_SOFTSEQ_MIDIDRIVER_H
diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp
new file mode 100644
index 0000000..6567d1d
--- /dev/null
+++ b/engines/agos/drivers/accolade/mt32.cpp
@@ -0,0 +1,267 @@
+/* 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 "agos/agos.h"
+#include "agos/drivers/accolade/mididriver.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/mutex.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+namespace AGOS {
+
+class MidiDriver_Accolade_MT32 : public MidiDriver {
+public:
+	MidiDriver_Accolade_MT32();
+	virtual ~MidiDriver_Accolade_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;
+
+private:
+	// simple mapping between MIDI channel and MT32 channel
+	byte _MIDIchannelMapping[AGOS_MIDI_CHANNEL_COUNT];
+	// simple mapping between MIDI instruments and MT32 instruments
+	byte _MIDIinstrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
+
+public:
+	bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
+
+	void MT32SysEx(const byte *&dataPtr, int32 &bytesLeft);
+};
+
+MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() {
+	_driver = NULL;
+	_isOpen = false;
+	_MT32 = false;
+	_nativeMT32 = false;
+	_baseFreq = 250;
+
+	memset(_MIDIchannelMapping, 0, sizeof(_MIDIchannelMapping));
+	memset(_MIDIinstrumentMapping, 0, sizeof(_MIDIinstrumentMapping));
+	//memset(_MIDIkeyNoteMapping, 0, sizeof(_MIDIkeyNoteMapping));
+}
+
+MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() {
+	Common::StackLock lock(_mutex);
+	if (_driver) {
+		_driver->setTimerCallback(0, 0);
+		_driver->close();
+		delete _driver;
+	}
+	_driver = NULL;
+}
+
+int MidiDriver_Accolade_MT32::open() {
+	assert(!_driver);
+
+//	debugC(kDebugLevelMT32Driver, "MT32: starting driver");
+
+	// Setup midi driver
+	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
+	MusicType musicType = MidiDriver::getMusicType(dev);
+
+	switch (musicType) {
+	case MT_MT32:
+		_MT32       = true;
+		_nativeMT32 = false;
+		break;
+	case MT_GM:
+		if (ConfMan.getBool("native_mt32")) {
+			_MT32       = true;
+			_nativeMT32 = true;
+		}
+		break;
+	default:
+		break;
+	}
+
+	_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 (_MT32)
+		_driver->sendMT32Reset();
+	else
+		_driver->sendGMReset();
+
+	return 0;
+}
+
+void MidiDriver_Accolade_MT32::close() {
+	if (_driver) {
+		_driver->close();
+	}
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Accolade_MT32::send(uint32 b) {
+	byte command = b & 0xf0;
+	byte channel = b & 0xf;
+
+	if (command == 0xF0) {
+		if (_driver) {
+			_driver->send(b);
+		}
+		return;
+	}
+
+	byte mappedChannel = _MIDIchannelMapping[channel];
+
+	if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) {
+		// channel mapped to an actual MIDI channel, so use that one
+		b = (b & 0xFFFFFFF0) | mappedChannel;
+		if (command == 0xC0) {
+			// Program change
+			// Figure out the requested instrument
+			byte midiInstrument = (b >> 8) & 0xFF;
+			byte mappedInstrument = _MIDIinstrumentMapping[midiInstrument];
+			// And replace it
+			b = (b & 0xFFFF00FF) | (mappedInstrument << 8);
+		}
+
+		if (_driver) {
+			_driver->send(b);
+		}
+	}
+}
+
+// Called right at the start, we get an INSTR.DAT entry
+bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
+	uint16 channelMappingOffset = 0;
+	uint16 channelMappingSize = 0;
+	uint16 instrumentMappingOffset = 0;
+	uint16 instrumentMappingSize = 0;
+
+	if (!useMusicDrvFile) {
+		// INSTR.DAT: we expect at least 354 bytes
+		if (driverDataSize < 354)
+			return false;
+
+		// Data is like this:
+		// 128 bytes  instrument mapping
+		// 128 bytes  instrument volume adjust (signed!) (not used for MT32)
+		//  16 bytes  unknown
+		//  16 bytes  channel mapping
+		//  64 bytes  key note mapping (not really used for MT32)
+		//   1 byte   instrument count
+		//   1 byte   bytes per instrument
+		//   x bytes  no instruments used for MT32
+
+		channelMappingOffset    = 256 + 16;
+		channelMappingSize      = 16;
+		instrumentMappingOffset = 0;
+		instrumentMappingSize   = 128;
+
+	} else {
+		// MUSIC.DRV: we expect at least 468 bytes
+		if (driverDataSize < 468)
+			return false;
+
+		channelMappingOffset    = 396;
+		channelMappingSize      = 16;
+		instrumentMappingOffset = 140;
+		instrumentMappingSize   = 128;
+	}
+
+	// Channel mapping
+	if (channelMappingSize) {
+		// Get these 16 bytes for MIDI channel mapping
+		if (channelMappingSize != sizeof(_MIDIchannelMapping))
+			return false;
+
+		memcpy(_MIDIchannelMapping, driverData + channelMappingOffset, sizeof(_MIDIchannelMapping));
+	} else {
+		// Set up straight mapping
+		for (uint16 channelNr = 0; channelNr < sizeof(_MIDIchannelMapping); channelNr++) {
+			_MIDIchannelMapping[channelNr] = channelNr;
+		}
+	}
+
+	if (instrumentMappingSize) {
+		// And these for instrument mapping
+		if (instrumentMappingSize > sizeof(_MIDIinstrumentMapping))
+			return false;
+
+		memcpy(_MIDIinstrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
+	}
+	// Set up straight mapping for the remaining data
+	for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_MIDIinstrumentMapping); instrumentNr++) {
+		_MIDIinstrumentMapping[instrumentNr] = instrumentNr;
+	}
+	return true;
+}
+
+MidiDriver *MidiDriver_Accolade_MT32_create() {
+	return new MidiDriver_Accolade_MT32();
+}
+
+bool MidiDriver_Accolade_MT32_setupInstruments(MidiDriver *driver, byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile) {
+	return static_cast<MidiDriver_Accolade_MT32 *>(driver)->setupInstruments(instrumentData, instrumentDataSize, useMusicDrvFile);
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index e5875a8..d75b7b8 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -27,6 +27,8 @@
 #include "agos/agos.h"
 #include "agos/midi.h"
 
+#include "agos/drivers/accolade/mididriver.h"
+
 namespace AGOS {
 
 
@@ -57,6 +59,8 @@ MidiPlayer::MidiPlayer() {
 	_loopTrackDefault = false;
 	_queuedTrack = 255;
 	_loopQueuedTrack = 0;
+
+	_accolade_mode = false;
 }
 
 MidiPlayer::~MidiPlayer() {
@@ -73,11 +77,244 @@ MidiPlayer::~MidiPlayer() {
 	unloadAdlibPatches();
 }
 
-int MidiPlayer::open(int gameType) {
+int MidiPlayer::open(int gameType, bool isDemo) {
 	// Don't call open() twice!
 	assert(!_driver);
 
-	// Setup midi driver
+	bool accolade_useMusicDrvFile = false;
+	MusicType accolade_musicType = MT_INVALID;
+
+	switch (gameType) {
+	case GType_ELVIRA1:
+	case GType_ELVIRA2:
+		_accolade_mode = true;
+		break;
+	case GType_WW:
+		_accolade_mode = true;
+		accolade_useMusicDrvFile = true;
+		break;
+	case GType_SIMON1:
+		if (isDemo) {
+			_accolade_mode = true;
+			accolade_useMusicDrvFile = true;
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (_accolade_mode) {
+		MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+		accolade_musicType = MidiDriver::getMusicType(dev);
+
+		switch (accolade_musicType) {
+		case MT_ADLIB:
+		case MT_MT32:
+			break;
+		case MT_GM:
+			if (ConfMan.getBool("native_mt32")) {
+				// Real MT32
+				accolade_musicType = MT_MT32;
+			} else {
+				_accolade_mode = false;
+			}
+			break;
+		default:
+			_accolade_mode = false;
+			break;
+		}
+	}
+
+	if (_accolade_mode) {
+		// Setup midi driver
+		switch (accolade_musicType) {
+		case MT_ADLIB:
+			_driver = MidiDriver_Accolade_AdLib_create();
+			break;
+		case MT_MT32:
+			_driver = MidiDriver_Accolade_MT32_create();
+			break;
+		default:
+			assert(0);
+			break;
+		}
+		if (!_driver)
+			return 255;
+
+		byte  *instrumentData = NULL;
+		uint16 instrumentDataSize = 0;
+
+		if (!accolade_useMusicDrvFile) {
+			// Elvira 1 / Elvira 2: read INSTR.DAT
+			Common::File *instrDatStream = new Common::File();
+
+			if (!instrDatStream->open("INSTR.DAT")) {
+				error("INSTR.DAT: unable to open file");
+			}
+
+			uint32 streamSize = instrDatStream->size();
+			uint32 streamLeft = streamSize;
+			uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib
+			uint16 chunkSize  = 0;
+
+			switch (accolade_musicType) {
+			case MT_ADLIB:
+				skipChunks = 0;
+				break;
+			case MT_MT32:
+				skipChunks = 1; // Skip one entry for MT32
+				break;
+			default:
+				assert(0);
+				break;
+			}
+
+			do {
+				if (streamLeft < 2)
+					error("INSTR.DAT: unexpected EOF");
+
+				chunkSize = instrDatStream->readUint16LE();
+				streamLeft -= 2;
+
+				if (streamLeft < chunkSize)
+					error("INSTR.DAT: unexpected EOF");
+
+				if (skipChunks) {
+					// Skip the chunk
+					instrDatStream->skip(chunkSize);
+					streamLeft -= chunkSize;
+
+					skipChunks--;
+				}
+			} while (skipChunks);
+
+			// Seek over the ASCII string until there is a NUL terminator
+			byte curByte = 0;
+
+			do {
+				if (chunkSize == 0)
+					error("INSTR.DAT: no actual instrument data found");
+
+				curByte = instrDatStream->readByte();
+				chunkSize--;
+			} while (curByte);
+
+			instrumentDataSize = chunkSize;
+
+			// Read the requested instrument data entry
+			instrumentData = new byte[instrumentDataSize];
+			instrDatStream->read(instrumentData, instrumentDataSize);
+
+			instrDatStream->close();
+			delete instrDatStream;
+
+		} else {
+			// Waxworks / Simon 1 demo: Read MUSIC.DRV
+			Common::File *musicDrvStream = new Common::File();
+
+			if (!musicDrvStream->open("MUSIC.DRV")) {
+				error("MUSIC.DRV: unable to open file");
+			}
+
+			uint32 streamSize = musicDrvStream->size();
+			uint32 streamLeft = streamSize;
+			uint16 getChunk   = 0; // 4 for MT32, 2 for AdLib
+
+			switch (accolade_musicType) {
+			case MT_ADLIB:
+				getChunk = 2;
+				break;
+			case MT_MT32:
+				getChunk = 4;
+				break;
+			default:
+				assert(0);
+				break;
+			}
+
+			if (streamLeft < 2)
+				error("MUSIC.DRV: unexpected EOF");
+
+			uint16 chunkCount = musicDrvStream->readUint16LE();
+			streamLeft -= 2;
+
+			if (getChunk >= chunkCount)
+				error("MUSIC.DRV: required chunk not available");
+
+			uint16 headerOffset = 2 + (28 * getChunk);
+			streamLeft -= (28 * getChunk);
+
+			if (streamLeft < 28)
+				error("MUSIC.DRV: unexpected EOF");
+
+			// Seek to required chunk
+			musicDrvStream->seek(headerOffset);
+			musicDrvStream->skip(20); // skip over name
+			streamLeft -= 20;
+
+			uint16 musicDrvSignature = musicDrvStream->readUint16LE();
+			uint16 musicDrvType = musicDrvStream->readUint16LE();
+			uint16 chunkOffset = musicDrvStream->readUint16LE();
+			uint16 chunkSize   = musicDrvStream->readUint16LE();
+
+			// Security checks
+			if (musicDrvSignature != 0xFEDC)
+				error("MUSIC.DRV: chunk signature mismatch");
+			if (musicDrvType != 1)
+				error("MUSIC.DRV: not a music driver");
+			if (chunkOffset >= streamSize)
+				error("MUSIC.DRV: driver chunk points outside of file");
+
+			streamLeft = streamSize - chunkOffset;
+			if (streamLeft < chunkSize)
+				error("MUSIC.DRV: driver chunk is larger than file");
+
+			instrumentDataSize = chunkSize;
+
+			// Read the requested instrument data entry
+			instrumentData = new byte[instrumentDataSize];
+
+			musicDrvStream->seek(chunkOffset);
+			musicDrvStream->read(instrumentData, instrumentDataSize);
+
+			musicDrvStream->close();
+			delete musicDrvStream;
+		}
+
+		// Pass the instrument data to the driver
+		bool instrumentSuccess = false;
+
+		switch (accolade_musicType) {
+		case MT_ADLIB:
+			instrumentSuccess = MidiDriver_Accolade_AdLib_setupInstruments(_driver, instrumentData, instrumentDataSize, accolade_useMusicDrvFile);
+			break;
+		case MT_MT32:
+			instrumentSuccess = MidiDriver_Accolade_MT32_setupInstruments(_driver, instrumentData, instrumentDataSize, accolade_useMusicDrvFile);
+			break;
+		default:
+			assert(0);
+			break;
+		}
+		delete[] instrumentData;
+
+		if (!instrumentSuccess) {
+			// driver did not like the contents
+			if (!accolade_useMusicDrvFile)
+				error("INSTR.DAT: contents not acceptable");
+			else
+				error("MUSIC.DRV: contents not acceptable");
+		}
+
+		int ret = _driver->open();
+		if (ret == 0) {
+			// Reset is done inside our MIDI driver
+			_driver->setTimerCallback(this, &onTimer);
+		}
+
+		//setTimerRate(_driver->getBaseTempo());
+		return 0;
+	}
+
 	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
 	_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
 
@@ -113,6 +350,12 @@ void MidiPlayer::send(uint32 b) {
 	if (!_current)
 		return;
 
+	if (_accolade_mode) {
+		// Send directly to Accolade driver
+		_driver->send(b);
+		return;
+	}
+
 	byte channel = (byte)(b & 0x0F);
 	if ((b & 0xFFF0) == 0x07B0) {
 		// Adjust volume changes by master music and master sfx volume.
@@ -146,8 +389,10 @@ void MidiPlayer::send(uint32 b) {
 		_current->volume[channel] = 127;
 	}
 
+	// Allocate channels if needed
 	if (!_current->channel[channel])
 		_current->channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
+
 	if (_current->channel[channel]) {
 		if (channel == 9) {
 			if (_current == &_sfx)
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index 7e78bfe..e7fcadc 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -113,12 +113,14 @@ public:
 	void setVolume(int musicVol, int sfxVol);
 
 public:
-	int open(int gameType);
+	int open(int gameType, bool isDemo);
 
 	// MidiDriver_BASE interface implementation
 	virtual void send(uint32 b);
 	virtual void metaEvent(byte type, byte *data, uint16 length);
 
+private:
+	bool _accolade_mode;
 };
 
 } // End of namespace AGOS
diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp
index c2c08bf..f07ef5b 100644
--- a/engines/agos/midiparser_s1d.cpp
+++ b/engines/agos/midiparser_s1d.cpp
@@ -181,10 +181,22 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
 
 	// The original actually just ignores the first two bytes.
 	byte *pos = data;
-	if (*(pos++) != 0xFC)
-		debug(1, "Expected 0xFC header but found 0x%02X instead", (int) *pos);
-
-	pos += 1;
+	if (*pos == 0xFC) {
+		// SysEx found right at the start
+		// this seems to happen since Elvira 2, we currently ignore it
+		// the original Accolade code does see 0xFC as end of track, which means there must have been a change
+		if ((pos[1] == 0x29) && (pos[2] == 0x07) && (pos[3] == 0x01)) {
+			// Security check
+			// Last byte is either 0x00 or 0x01. Maybe some looping indicator?
+			pos += 5; // Waxworks / Simon 1 demo
+		} else {
+			if ((pos[1] == 0x04) && (pos[2] == 0x06) && (pos[3] == 06)) {
+				pos += 4; // Elvira 2
+			} else {
+				warning("0xFC startup without proper signature");
+			}
+		}
+	}
 
 	// And now we're at the actual data. Only one track.
 	_numTracks = 1;
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index 7069d80..ae9d5cb 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -1,6 +1,8 @@
 MODULE := engines/agos
 
 MODULE_OBJS := \
+	drivers/accolade/adlib.o \
+	drivers/accolade/mt32.o \
 	agos.o \
 	charset.o \
 	charset-fontdata.o \






More information about the Scummvm-git-logs mailing list