/* 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/drivers/accolade/mididriver.h"

#include "common/system.h"
#include "common/textconsole.h"

#include "audio/fmopl.h"
#include "audio/mididrv.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 operator1Register[AGOS_ADLIB_VOICES_COUNT] = {
	0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11
};

const byte 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 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 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 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 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 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 {
public:
	MidiDriver_Accolade_AdLib();
	virtual ~MidiDriver_Accolade_AdLib();

	// MidiDriver
	int open();
	void close();
	void send(uint32 b);
	MidiChannel *allocateChannel() { return NULL; }
	MidiChannel *getPercussionChannel() { return NULL; }

	bool isOpen() const { return _isOpen; }
	uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }

	void setVolume(byte volume);
	virtual uint32 property(int prop, uint32 param);

	bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);

	void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);

private:
	bool _musicDrvMode;

	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel
	byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments
	byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
	// from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument
	signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT];
	// simple mapping between MIDI key notes and MT32 key notes
	byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT];

	// from INSTR.DAT/MUSIC.DRV - adlib instrument data
	InstrumentEntry *_instrumentTable;
	byte            _instrumentCount;

	struct ChannelEntry {
		const  InstrumentEntry *currentInstrumentPtr;
		byte   currentNote;
		byte   currentA0hReg;
		byte   currentB0hReg;
		int16  volumeAdjust;

		ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0),
						currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { }
	};

	byte _percussionReg;

	OPL::OPL *_opl;
	int _masterVolume;

	Common::TimerManager::TimerProc _adlibTimerProc;
	void *_adlibTimerParam;

	bool _isOpen;

	// stores information about all FM voice channels
	ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT];

	void onTimer();

	void resetAdLib();
	void resetAdLibOperatorRegisters(byte baseRegister, byte value);
	void resetAdLibFMVoiceChannelRegisters(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()
		: _masterVolume(15), _opl(0),
		  _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) {
	memset(_channelMapping, 0, sizeof(_channelMapping));
	memset(_instrumentMapping, 0, sizeof(_instrumentMapping));
	memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust));
	memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping));

	_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() {
//	debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");

	_opl = OPL::Config::create(OPL::Config::kOpl2);

	if (!_opl)
		return -1;

	_opl->init();

	_isOpen = true;

	_opl->start(new Common::Functor0Mem<void, MidiDriver_Accolade_AdLib>(this, &MidiDriver_Accolade_AdLib::onTimer));

	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);

	return 0;
}

void MidiDriver_Accolade_AdLib::close() {
	delete _opl;
	_isOpen = false;
}

void MidiDriver_Accolade_AdLib::setVolume(byte volume) {
	_masterVolume = volume;
	//renewNotes(-1, true);
}

void MidiDriver_Accolade_AdLib::onTimer() {
	if (_adlibTimerProc)
		(*_adlibTimerProc)(_adlibTimerParam);
}

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
	resetAdLibOperatorRegisters(0x20, 0);
	resetAdLibOperatorRegisters(0x60, 0);
	resetAdLibOperatorRegisters(0x80, 0);
	resetAdLibFMVoiceChannelRegisters(0xA0, 0);
	resetAdLibFMVoiceChannelRegisters(0xB0, 0);
	resetAdLibFMVoiceChannelRegisters(0xC0, 0);
	resetAdLibOperatorRegisters(0xE0, 0);
	resetAdLibOperatorRegisters(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::resetAdLibOperatorRegisters(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::resetAdLibFMVoiceChannelRegisters(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    = _channelMapping[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 = _instrumentMapping[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::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
	_adlibTimerProc = timerProc;
	_adlibTimerParam = timerParam;
}

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 = 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 = _percussionKeyNoteMapping[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 |= 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 = 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 = 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 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 0
			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)");
				}
			}
#endif
		}
	}

	if (operatorNr == 1) {
		operatorReg = operator1Register[FMvoiceChannel];
	} else {
		operatorReg = 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 = 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 &= ~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-existent 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 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-existent instrument");
			return;
		}
		instrumentPtr = &_instrumentTable[percussionInstrumentNr];
		_channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
		_channels[FMvoiceChannel].volumeAdjust         = _instrumentVolumeAdjust[percussionInstrumentNr];
	}
}

void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
	const InstrumentEntry *instrumentPtr;
	byte op1Reg = 0;
	byte op2Reg = 0;

	if (mappedInstrumentNr >= _instrumentCount) {
		warning("ADLIB: tried to set non-existent instrument");
		return; // out of range
	}

	// setup instrument
	instrumentPtr = &_instrumentTable[mappedInstrumentNr];
	//warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);

	op1Reg = operator1Register[FMvoiceChannel];
	op2Reg = 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         = _instrumentVolumeAdjust[MIDIinstrumentNr];
}

void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) {
	_opl->writeReg(reg, 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(_channelMapping))
			return false;

		memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping));
	} else {
		// Set up straight mapping
		for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) {
			_channelMapping[channelNr] = channelNr;
		}
	}

	if (instrumentMappingSize) {
		// And these for instrument mapping
		if (instrumentMappingSize > sizeof(_instrumentMapping))
			return false;

		memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
	}
	// Set up straight mapping for the remaining data
	for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) {
		_instrumentMapping[instrumentNr] = instrumentNr;
	}

	if (instrumentVolumeAdjustSize) {
		if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust))
			return false;

		memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize);
	}

	// Get key note mapping, if available
	if (keyNoteMappingSize) {
		if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping))
			return false;

		memcpy(_percussionKeyNoteMapping, 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 InstrumentEntry[instrumentCount];
	_instrumentCount = instrumentCount;

	byte            *instrDATReadPtr    = driverData + instrumentDataOffset;
	InstrumentEntry *instrumentWritePtr = _instrumentTable;

	for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
		memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(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(Common::String driverFilename) {
	byte  *driverData = NULL;
	uint16 driverDataSize = 0;
	bool   isMusicDrvFile = false;

	MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, isMusicDrvFile);
	if (!driverData)
		error("ACCOLADE-ADLIB: error during readDriver()");

	MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib();
	if (driver) {
		if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) {
			delete driver;
			driver = nullptr;
		}
	}

	delete[] driverData;
	return driver;
}

} // End of namespace AGOS
