/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "audio/miles.h"

#include "common/config-manager.h"
#include "common/file.h"
#include "common/mutex.h"
#include "common/system.h"
#include "common/textconsole.h"

namespace Audio {

// Miles Audio MT-32 / General MIDI driver
//

#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127

const byte milesMT32SysExChansSetup[] = {
	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
};

const byte milesMT32SysExPartialReserveTable[] = {
	0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04
};

const byte milesMT32SysExInitReverb[] = {
	0x00, 0x03, 0x02 // Reverb mode 0, reverb time 3, reverb level 2
};

MidiDriver_Miles_Midi::MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) :
		MidiDriver_MT32GM(midiType), _noteCounter(0), _milesVersion(MILES_VERSION_2) {
	memset(_patchesBank, 0, sizeof(_patchesBank));

	_instrumentTablePtr = instrumentTablePtr;
	_instrumentTableCount = instrumentTableCount;

	setSourceNeutralVolume(MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME);
	setSourceVolume(MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME);
}

MidiDriver_Miles_Midi::~MidiDriver_Miles_Midi() {
	if (_instrumentTablePtr)
		delete[] _instrumentTablePtr;

	for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
		if (_midiChannels[i].unlockData)
			delete _midiChannels[i].unlockData;
	}
}

void MidiDriver_Miles_Midi::initControlData() {
	for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
		_controlData[i] = _midiChannels[i].currentData = new MilesMidiChannelControlData();
		_midiChannels[i].unlockData = new MilesMidiChannelControlData();
		_controlData[i]->volume = _controlData[i]->scaledVolume =
			(_nativeMT32 ? MT32_DEFAULT_CHANNEL_VOLUME : GM_DEFAULT_CHANNEL_VOLUME);
		if (_nativeMT32 && i >= 1 && i <= 8) {
			_midiChannels[i].currentData->program = MT32_DEFAULT_INSTRUMENTS[i - 1];
			_midiChannels[i].currentData->panPosition = MT32_DEFAULT_PANNING[i - 1];
		}
	}
}

void MidiDriver_Miles_Midi::initMidiDevice() {
	MidiDriver_MT32GM::initMidiDevice();

	// Additional Miles AIL specific initialization

	if (_midiType == MT_MT32 && _nativeMT32) {
		// init part/channel assignments
		sysExMT32(milesMT32SysExChansSetup, 9, (0x10 << 14) | (0x00 << 7) | 0x0D, false, true);

		// partial reserve table
		sysExMT32(milesMT32SysExPartialReserveTable, 9, (0x10 << 14) | (0x00 << 7) | 0x04, false, true);

		// init reverb
		sysExMT32(milesMT32SysExInitReverb, 3, (0x10 << 14) | (0x00 << 7) | 0x01, false, true);
	}

	// Set Miles default controller values
	if (_milesVersion == MILES_VERSION_2) {
		// Note that AIL/MSS apparently did not get full support for GM until
		// version 3.00 in 09/1994. Many games used the MT-32 driver to
		// implement GM support. As a result, default parameters were only sent
		// out on the MT-32 channels (2-10). Also, the default MT-32 instrument
		// numbers were set on GM devices, even though they map to different
		// instruments. This is reproduced here to prevent possible issues with
		// games that depend on this behavior.

		for (int i = 1; i < 10; ++i) {
			// Volume 7F (max)
			send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_VOLUME, 0x7F);
			if (_midiType == MT_MT32) {
				// Panning center - not the MT-32 default for all channels
				send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_PANNING, 0x40);
			}
			// Patch
			if (_midiType == MT_GM && i != MIDI_RHYTHM_CHANNEL) {
				// Send the MT-32 default instrument numbers out to GM devices.
				send(-1, MIDI_COMMAND_PROGRAM_CHANGE | i, MT32_DEFAULT_INSTRUMENTS[i - 1], 0);
			}
			// The following settings are also sent out by the AIL driver:
			// - Modulation 0
			// - Expression 7F (max)
			// - Sustain off
			// - Pitch bend neutral
			// These are the default MT-32 and GM settings, so it is not
			// necessary to send these.
		}
	} else {
		// MSS 3 initialization
		for (int i = (_midiType == MT_GM ? 0 : 1); i < (_midiType == MT_GM ? MIDI_CHANNEL_COUNT : 10); ++i) {
			// Patch
			if (_midiType == MT_MT32 && i != MIDI_RHYTHM_CHANNEL) {
				// Set instrument to 0.
				send(-1, MIDI_COMMAND_PROGRAM_CHANGE | i, 0, 0);
			}
			// Volume 7F (max)
			send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_VOLUME, 0x7F);
			if (_midiType == MT_MT32) {
				// Panning center - not the MT-32 default for all channels
				send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_PANNING, 0x40);
			}
			if (_midiType == MT_GM) {
				// Reverb 28h
				send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_REVERB, 0x28);
			}
			// Pitch bend range 2 semitones
			// TODO Some games seem to initialize this to a different value, so
			// this might need to be configurable.
			send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_RPN_LSB, 0x00);
			send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_RPN_MSB, 0x00);
			if (_midiType == MT_GM) {
				// MT-32 does not respond to the LSB, so only send it out for GM
				send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0x00);
			}
			send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 0x02);

			// MSS 3 also sets the following settings:
			// - Program 0 (also for GM)
			// - Pitch bend neutral
			// - Modulation 0
			// - Panning center (also for GM)
			// - Expression 7F
			// - Sustain off
			// - Chorus 0
			// These are the default settings, so it is not necessary to send
			// these.
		}
	}
}

// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
	assert(source < MAXIMUM_SOURCES);

	byte command = b & 0xf0;
	byte dataChannel = b & 0xf;
	byte op1 = (b >> 8) & 0xff;
	byte op2 = (b >> 16) & 0xff;
	byte outputChannel = source < 0 ? dataChannel : _channelMap[source][dataChannel];

	MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel];
	// Only send the message to the MIDI device if the channel is not locked or
	// if the source that locked the channel is sending the message
	bool channelLockedByOtherSource = source >= 0 && outputChannelEntry.locked &&
		outputChannelEntry.currentData->source != source;
	// Track controller changes on the current data if the MIDI message is sent out,
	// or on the unlock data otherwise.
	MilesMidiChannelControlData &controlData = channelLockedByOtherSource ?
		*outputChannelEntry.unlockData : *outputChannelEntry.currentData;

	if (command == MIDI_COMMAND_CONTROL_CHANGE && op1 == MILES_CONTROLLER_LOCK_CHANNEL) {
		// The lock channel controller will allocate an output channel to use
		// to send the events on this data channel. In this case, the data
		// channel should not be assigned to the source, because it will not
		// actually be used to send MIDI events. processEvent will assign the
		// data channel to the source, so it is bypassed and controlChange is
		// called directly.
		controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
	} else {
		processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource);
	}

	if (command == MIDI_COMMAND_NOTE_OFF || command == MIDI_COMMAND_NOTE_ON || command == MIDI_COMMAND_PITCH_BEND ||
		command == MIDI_COMMAND_POLYPHONIC_AFTERTOUCH || command == MIDI_COMMAND_CHANNEL_AFTERTOUCH) {
		_noteCounter++;
		if (controlData.usingCustomTimbre) {
			// Remember that this timbre got used now
			_customTimbres[controlData.currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
		}
	}
}

void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue,
	int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
	assert(source < MAXIMUM_SOURCES);

	MilesMidiChannelControlData &milesControlData = channelLockedByOtherSource ?
		*_midiChannels[outputChannel].unlockData : *_midiChannels[outputChannel].currentData;

	// XMIDI controllers
	switch (controllerNumber) {
	case MILES_CONTROLLER_SELECT_PATCH_BANK:
		milesControlData.currentPatchBank = controllerValue;
		return;

	case MILES_CONTROLLER_PROTECT_TIMBRE:
		if (milesControlData.usingCustomTimbre) {
			// custom timbre set on current channel
			_customTimbres[milesControlData.currentCustomTimbreId].protectionEnabled = controllerValue >= 64;
		}
		return;

	case MILES_CONTROLLER_LOCK_CHANNEL:
		if (source >= 0) {
			if (controllerValue >= 0x40) {
				lockChannel(source, outputChannel);
			} else {
				unlockChannel(outputChannel);
			}
		}
		return;

	case MILES_CONTROLLER_PROTECT_CHANNEL:
		if (source >= 0 && !_midiChannels[outputChannel].locked) {
			_midiChannels[outputChannel].lockProtected = controllerValue >= 0x40;
			_midiChannels[outputChannel].protectedSource = controllerValue >= 0x40 ? source : -1;
		}
		return;

	default:
		break;
	}

	// XMIDI MT-32 specific controllers
	if (_midiType == MT_MT32 && _nativeMT32) {
		switch (controllerNumber) {
		case MILES_CONTROLLER_PATCH_REVERB:
			writePatchByte(milesControlData.program, 6, controllerValue);
			if (!channelLockedByOtherSource)
				_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (milesControlData.program << 8));
			return;

		case MILES_CONTROLLER_PATCH_BENDER:
			writePatchByte(milesControlData.program, 4, controllerValue);
			if (!channelLockedByOtherSource)
				_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (milesControlData.program << 8));
			return;

		case MILES_CONTROLLER_REVERB_MODE:
			writeToSystemArea(1, controllerValue);
			return;

		case MILES_CONTROLLER_REVERB_TIME:
			writeToSystemArea(2, controllerValue);
			return;

		case MILES_CONTROLLER_REVERB_LEVEL:
			writeToSystemArea(3, controllerValue);
			return;

		case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
			if (milesControlData.usingCustomTimbre) {
				// custom timbre is set on current channel
				writeRhythmSetup(controllerValue, milesControlData.currentCustomTimbreId);
			}
			return;
		default:
			break;
		}
	}

	// XMIDI MT-32 SysEx controllers
	if (_midiType == MT_MT32 && (controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) &&
			(controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
		if (!_nativeMT32)
			return;

		// send SysEx
		byte sysExQueueNr = 0;

		// figure out which queue is accessed
		controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
		while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA) {
			sysExQueueNr++;
			controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA + 1);
		}
		assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);

		byte sysExPos = _milesSysExQueues[sysExQueueNr].dataPos;
		bool sysExSend = false;

		switch(controllerNumber) {
		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
			_milesSysExQueues[sysExQueueNr].targetAddress &= 0x003FFF;
			_milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 14);
			break;
		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
			_milesSysExQueues[sysExQueueNr].targetAddress &= 0x1FC07F;
			_milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 7);
			break;
		case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
			_milesSysExQueues[sysExQueueNr].targetAddress &= 0x1FFF80;
			_milesSysExQueues[sysExQueueNr].targetAddress |= controllerValue;
			break;
		case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
			if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
				// Space left? put current byte into queue
				_milesSysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
				sysExPos++;
				_milesSysExQueues[sysExQueueNr].dataPos = sysExPos;
				if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
					// overflow? -> send it now
					sysExSend = true;
				}
			}
			break;
		case MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA:
			if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
				// Space left? put current byte into queue
				_milesSysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
				sysExPos++;
				// Do not increment dataPos. Subsequent Final Data commands will
				// re-send the last address byte with the new controller value.
				sysExSend = true;
			}
			break;
		default:
			assert(0);
		}

		if (sysExSend) {
			if (sysExPos > 0) {
				// data actually available? -> send it
				sysExMT32(_milesSysExQueues[sysExQueueNr].data, sysExPos, _milesSysExQueues[sysExQueueNr].targetAddress, false, true);

				// Adjust target address to point at the final data byte, or at the
				// end of the current data in case of an overflow
				_milesSysExQueues[sysExQueueNr].targetAddress += _milesSysExQueues[sysExQueueNr].dataPos;

				// reset queue data buffer
				_milesSysExQueues[sysExQueueNr].dataPos = 0;
			}
		}
		return;
	}

	if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
		// XMIDI controllers? Don't send these to the MIDI device
		return;
	}

	// Handle other controllers and send message (if necessary)
	MidiDriver_MT32GM::controlChange(outputChannel, controllerNumber, controllerValue, source, milesControlData, channelLockedByOtherSource);
}

bool MidiDriver_Miles_Midi::addActiveNote(uint8 outputChannel, uint8 note, int8 source) {
	bool added = MidiDriver_MT32GM::addActiveNote(outputChannel, note, source);

	if (added)
		_midiChannels[outputChannel].activeNotes++;

	return added;
}

bool MidiDriver_Miles_Midi::removeActiveNote(uint8 outputChannel, uint8 note, int8 source) {
	bool removed = MidiDriver_MT32GM::removeActiveNote(outputChannel, note, source);

	if (removed) {
		if (_midiChannels[outputChannel].activeNotes == 0) {
			warning("MILES-MIDI: active notes 0 on channel %d when turning off note %x", outputChannel, note);
		} else {
			_midiChannels[outputChannel].activeNotes--;
		}
	}

	return removed;
}

void MidiDriver_Miles_Midi::removeActiveNotes(uint8 outputChannel, bool sustainedNotes) {
	Common::StackLock lock(_activeNotesMutex);

	// Remove sustained or non-sustained notes from the active notes registration
	for (int i = 0; i < _maximumActiveNotes; ++i) {
		if (_activeNotes[i].channel == outputChannel && _activeNotes[i].sustain == sustainedNotes) {
			_activeNotes[i].clear();
			if (_midiChannels[outputChannel].activeNotes == 0) {
				warning("MILES-MIDI: active notes 0 on channel %d (sustained %i) when removing active notes", outputChannel, sustainedNotes);
				continue;
			}
			--_midiChannels[outputChannel].activeNotes;
		}
	}
}

void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
	assert(source < MAXIMUM_SOURCES);

	int8 lockChannel = findLockChannel();
	if (lockChannel == -1)
		// Try again, but consider lock protected channels
		lockChannel = findLockChannel(true);
	if (lockChannel == -1)
		// Could not find a channel to lock
		return;

	// stopNotesOnChannel will turn off sustain, so record the current sustain
	// value so it can be set on the unlock data.
	bool currentSustain = _midiChannels[lockChannel].currentData->sustain;

	stopNotesOnChannel(lockChannel);

	_midiChannels[lockChannel].locked = true;
	_midiChannels[lockChannel].lockDataChannel = dataChannel;
	_channelMap[source][dataChannel] = lockChannel;
	// Copy current controller values so they can be restored when unlocking the channel
	*_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData;
	_midiChannels[lockChannel].unlockData->sustain = currentSustain;
	_midiChannels[lockChannel].currentData->source = source;

	// Set any specified default controller values on the channel
	applyControllerDefaults(source, *_midiChannels[lockChannel].currentData, lockChannel, false);

	// Send volume change to apply the new source volume
	controlChange(lockChannel, MIDI_CONTROLLER_VOLUME, 0x7F, source, *_midiChannels[lockChannel].currentData);
}

int8 MidiDriver_Miles_Midi::findLockChannel(bool useProtectedChannels) {
	// Starting at the highest (non-rhythm) channel, find the channel
	// with the least active notes that isn't already locked.
	// If useProtectedChannels is false, channels that are protected
	// from channel locking will not be considered.
	int8 potentialLockChannel = -1;
	uint8 notes = 255;
	for (int i = MIDI_CHANNEL_COUNT - 1; i >= 0; --i) {
		if (!isOutputChannelUsed(i) || i == MIDI_RHYTHM_CHANNEL || _midiChannels[i].locked ||
				(!useProtectedChannels && _midiChannels[i].lockProtected))
			continue;
		if (_midiChannels[i].activeNotes < notes) {
			potentialLockChannel = i;
			notes = _midiChannels[i].activeNotes;
		}
	}
	return potentialLockChannel;
}

void MidiDriver_Miles_Midi::unlockChannel(uint8 outputChannel) {
	MidiChannelEntry &channel = _midiChannels[outputChannel];
	if (!channel.locked)
		return;

	stopNotesOnChannel(outputChannel);

	// Unlock the channel
	channel.locked = false;
	_channelMap[channel.currentData->source][channel.lockDataChannel] = channel.lockDataChannel;
	channel.lockDataChannel = -1;
	channel.currentData->source = channel.unlockData->source;

	// Send the unlock channel data to the MIDI device to reset the channel parameters
	if (channel.unlockData->volume != 0xFF) {
		controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, channel.unlockData->volume, channel.currentData->source, *channel.currentData);
	} else {
		channel.currentData->volume = 0xFF;
	}
	if (channel.currentData->modulation != channel.unlockData->modulation)
		controlChange(outputChannel, MIDI_CONTROLLER_MODULATION, channel.unlockData->modulation, channel.currentData->source, *channel.currentData);
	if (channel.currentData->panPosition != channel.unlockData->panPosition)
		controlChange(outputChannel, MIDI_CONTROLLER_PANNING, channel.unlockData->panPosition, channel.currentData->source, *channel.currentData);
	if (channel.currentData->expression != channel.unlockData->expression)
		controlChange(outputChannel, MIDI_CONTROLLER_EXPRESSION, channel.unlockData->expression, channel.currentData->source, *channel.currentData);
	if (channel.currentData->sustain != channel.unlockData->sustain)
		controlChange(outputChannel, MIDI_CONTROLLER_SUSTAIN, channel.unlockData->sustain ? 0x7F : 0x00, channel.currentData->source, *channel.currentData);
	if (channel.currentData->currentPatchBank != channel.unlockData->currentPatchBank)
		controlChange(outputChannel, MILES_CONTROLLER_SELECT_PATCH_BANK, channel.unlockData->currentPatchBank,
			channel.currentData->source, *channel.currentData);
	if (channel.unlockData->program != 0xFF && (channel.currentData->program != channel.unlockData->program ||
			channel.currentData->currentPatchBank != channel.unlockData->currentPatchBank))
		programChange(outputChannel, channel.unlockData->program, channel.currentData->source, *channel.currentData);
	if (channel.currentData->pitchWheel != channel.unlockData->pitchWheel)
		send(channel.currentData->source, MIDI_COMMAND_PITCH_BEND | outputChannel,
			channel.unlockData->pitchWheel & 0x7F, (channel.unlockData->pitchWheel >> 7) & 0x7F);
}

void MidiDriver_Miles_Midi::stopNotesOnChannel(uint8 outputChannelNumber) {
	MidiChannelEntry &channel = _midiChannels[outputChannelNumber];
	if (channel.currentData->sustain) {
		controlChange(outputChannelNumber, MIDI_CONTROLLER_SUSTAIN, 0, channel.currentData->source, *channel.currentData);
	}
	if (channel.activeNotes > 0) {
		controlChange(outputChannelNumber, MIDI_CONTROLLER_ALL_NOTES_OFF, 0, channel.currentData->source, *channel.currentData);
	}
}

void MidiDriver_Miles_Midi::stopAllNotes(bool stopSustainedNotes) {
	MidiDriver_MT32GM::stopAllNotes(stopSustainedNotes);

	for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
		if (isOutputChannelUsed(i))
			_midiChannels[i].activeNotes = 0;
	}
}

void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, int8 source,
		MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
	MilesMidiChannelControlData &milesControlData = channelLockedByOtherSource ?
		*_midiChannels[outputChannel].unlockData : *_midiChannels[outputChannel].currentData;

	if (_midiType == MT_MT32) {
		byte channelPatchBank = milesControlData.currentPatchBank;
		byte activePatchBank = _patchesBank[patchId];

		//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);

		if (channelPatchBank != activePatchBank) {
			// associate patch with timbre
			setupPatch(channelPatchBank, patchId);
		}

		// If this is a custom patch, remember customTimbreId
		int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
		if (customTimbre >= 0) {
			milesControlData.usingCustomTimbre = true;
			milesControlData.currentCustomTimbreId = customTimbre;
		} else {
			milesControlData.usingCustomTimbre = false;
		}
	}

	MidiDriver_MT32GM::programChange(outputChannel, patchId, source, milesControlData, channelLockedByOtherSource);
}

int16 MidiDriver_Miles_Midi::searchCustomTimbre(byte patchBank, byte patchId) {
	byte customTimbreId = 0;

	for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
		if (_customTimbres[customTimbreId].used) {
			if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) &&
					(_customTimbres[customTimbreId].currentPatchId == patchId)) {
				return customTimbreId;
			}
		}
	}
	return -1;
}

const MilesMT32InstrumentEntry *MidiDriver_Miles_Midi::searchCustomInstrument(byte patchBank, byte patchId) {
	const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;

	for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
		if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
			return instrumentPtr;
		instrumentPtr++;
	}
	return nullptr;
}

void MidiDriver_Miles_Midi::setupPatch(byte patchBank, byte patchId, bool useSysExQueue) {
	_patchesBank[patchId] = patchBank;

	if (patchBank) {
		// non-built-in bank
		int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
		if (customTimbreId >= 0) {
			// now available? -> use this timbre
			writePatchTimbre(patchId, 2, customTimbreId, useSysExQueue); // Group MEMORY
			return;
		}
	}

	// for built-in bank (or timbres, that are not available) use default MT32 timbres
	byte timbreId = patchId & 0x3F;
	if (!(patchId & 0x40)) {
		writePatchTimbre(patchId, 0, timbreId, useSysExQueue); // Group A
	} else {
		writePatchTimbre(patchId, 1, timbreId, useSysExQueue); // Group B
	}
}

void MidiDriver_Miles_Midi::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
	if (_midiType != MT_MT32)
		// Some GM files contain timbre chunks, but custom patches cannot
		// be loaded on a GM device.
		return;

	uint16 timbreCount = 0;
	uint32 expectedSize = 0;
	const byte *timbreListSeeker = timbreListPtr;

	if (timbreListSize < 2) {
		warning("MILES-MIDI: XMIDI-TIMB chunk - not enough bytes in chunk");
		return;
	}

	timbreCount = READ_LE_UINT16(timbreListPtr);
	expectedSize = timbreCount * 2;
	if (expectedSize > timbreListSize) {
		warning("MILES-MIDI: XMIDI-TIMB chunk - size mismatch");
		return;
	}

	timbreListSeeker += 2;

	while (timbreCount) {
		const byte  patchId   = *timbreListSeeker++;
		const byte  patchBank = *timbreListSeeker++;
		int16       customTimbreId = 0;

		switch (patchBank) {
		case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
		case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
			// ignore those 2 banks
			break;

		default:
			// Check, if this timbre was already loaded
			customTimbreId = searchCustomTimbre(patchBank, patchId);

			if (customTimbreId < 0) {
				// currently not loaded, try to install it
				installCustomTimbre(patchBank, patchId);
			}
		}
		timbreCount--;
	}
}

//
int16 MidiDriver_Miles_Midi::installCustomTimbre(byte patchBank, byte patchId) {
	switch(patchBank) {
	case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
	case MILES_MT32_TIMBREBANK_MELODIC_MODULE:  // Reserved for melodic mode
		return -1;
	default:
		break;
	}

	// Original driver did a search for custom timbre here
	// and in case it was found, it would call setup_patch()
	// we are called from within setup_patch(), so this isn't needed

	int16 customTimbreId = -1;
	int16 leastUsedTimbreId = -1;
	uint32 leastUsedTimbreNoteCounter = _noteCounter;
	const MilesMT32InstrumentEntry *instrumentPtr = nullptr;

	// Check, if requested instrument is actually available
	instrumentPtr = searchCustomInstrument(patchBank, patchId);
	if (!instrumentPtr) {
		warning("MILES-MIDI: instrument not found during installCustomTimbre()");
		return -1; // not found -> bail out
	}

	// Look for an empty timbre slot
	// or get the least used non-protected slot
	for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
		if (!_customTimbres[customTimbreNr].used) {
			// found an empty slot -> use this one
			customTimbreId = customTimbreNr;
			break;
		} else {
			// used slot
			if (!_customTimbres[customTimbreNr].protectionEnabled) {
				// not protected
				uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
				if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
					leastUsedTimbreId          = customTimbreNr;
					leastUsedTimbreNoteCounter = customTimbreNoteCounter;
				}
			}
		}
	}

	if (customTimbreId < 0) {
		// no empty slot found, check if we got a least used non-protected slot
		if (leastUsedTimbreId < 0) {
			// everything is protected, bail out
			warning("MILES-MIDI: no non-protected timbre slots available during installCustomTimbre()");
			return -1;
		}
		customTimbreId = leastUsedTimbreId;
	}

	// setup timbre slot
	_customTimbres[customTimbreId].used                = true;
	_customTimbres[customTimbreId].currentPatchBank    = patchBank;
	_customTimbres[customTimbreId].currentPatchId      = patchId;
	_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
	_customTimbres[customTimbreId].protectionEnabled   = false;

	uint32 targetAddress = ((0x08 << 14) | (0x00 << 7) | 0x00) + (customTimbreId * 0x100);
	uint32 targetAddressCommon   = targetAddress;
	uint32 targetAddressPartial1 = targetAddress + 0x0E;
	uint32 targetAddressPartial2 = targetAddress + 0x48;
	uint32 targetAddressPartial3 = targetAddress + 0x82;
	uint32 targetAddressPartial4 = targetAddress + 0xBC;

#if 0
	byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
	uint16 parameterDataPos = 0;

	memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
	parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
	memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
	parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
	parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;

	MT32SysEx(targetAddressCommon, parameterData);
#endif

	// upload common parameter data
	sysExMT32(instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE, targetAddressCommon, true);
	// upload partial parameter data
	sysExMT32(instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial1, true);
	sysExMT32(instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial2, true);
	sysExMT32(instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial3, true);
	sysExMT32(instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial4, true);

	setupPatch(patchBank, patchId, true);

	return customTimbreId;
}

void MidiDriver_Miles_Midi::writeRhythmSetup(byte note, byte customTimbreId) {
	byte   sysExData[1];
	uint32 targetAddress = (0x03 << 14) | (0x01 << 7) | 0x10;

	targetAddress += ((note - 24) << 2);

	sysExData[0] = customTimbreId;

	sysExMT32(sysExData, 1, targetAddress);
}

void MidiDriver_Miles_Midi::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId, bool useSysExQueue) {
	byte   sysExData[2];
	uint32 targetAddress = (0x05 << 14) | (0x00 << 7) | 0x00;

	// write to patch memory (starts at 0x050000, each entry is 8 bytes)
	targetAddress += (patchId << 3);

	sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
	sysExData[1] = timbreId;    // timbre number (0-63)

	sysExMT32(sysExData, 2, targetAddress, useSysExQueue);
}

void MidiDriver_Miles_Midi::writePatchByte(byte patchId, byte index, byte patchValue) {
	byte   sysExData[1];
	uint32 targetAddress = (0x05 << 14) | (0x00 << 7) | 0x00;

	targetAddress += (patchId << 3) + index;

	sysExData[0] = patchValue;

	sysExMT32(sysExData, 1, targetAddress);
}

void MidiDriver_Miles_Midi::writeToSystemArea(byte index, byte value) {
	byte   sysExData[1];
	uint32 targetAddress = (0x10 << 14) | (0x00 << 7) | 0x00;

	targetAddress += index;

	sysExData[0] = value;

	sysExMT32(sysExData, 1, targetAddress);
}

MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) {
	return MidiDriver_Miles_MIDI_create(MT_MT32, instrumentDataFilename);
}

MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::String &instrumentDataFilename) {
	assert(midiType == MT_MT32 || midiType == MT_GM || midiType == MT_GS);

	MilesMT32InstrumentEntry *instrumentTablePtr = nullptr;
	uint16                    instrumentTableCount = 0;

	if (midiType == MT_MT32 && !instrumentDataFilename.empty()) {
		// Load MT32 instrument data from file SAMPLE.MT
		Common::File *fileStream = new Common::File();
		uint32        fileSize = 0;
		byte         *fileDataPtr = nullptr;
		uint32        fileDataOffset = 0;
		uint32        fileDataLeft = 0;

		byte curBankId;
		byte curPatchId;

		MilesMT32InstrumentEntry *instrumentPtr = nullptr;
		uint32                    instrumentOffset;
		uint16                    instrumentDataSize;

		if (!fileStream->open(instrumentDataFilename))
			error("MILES-MIDI: could not open instrument file '%s'", instrumentDataFilename.c_str());

		fileSize = fileStream->size();

		fileDataPtr = new byte[fileSize];

		if (fileStream->read(fileDataPtr, fileSize) != fileSize)
			error("MILES-MIDI: error while reading instrument file");
		fileStream->close();
		delete fileStream;

		// File is like this:
		// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
		// ...
		// until patch + bank are both 0xFF, which signals end of header

		// First we check how many entries there are
		fileDataOffset = 0;
		fileDataLeft = fileSize;
		while (1) {
			if (fileDataLeft < 6)
				error("MILES-MIDI: unexpected EOF in instrument file");

			curPatchId = fileDataPtr[fileDataOffset++];
			curBankId  = fileDataPtr[fileDataOffset++];

			if ((curBankId == 0xFF) && (curPatchId == 0xFF))
				break;

			fileDataOffset += 4; // skip over offset
			instrumentTableCount++;
		}

		if (instrumentTableCount == 0)
			error("MILES-MIDI: no instruments in instrument file");

		// Allocate space for instruments
		instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];

		// Now actually read all entries
		instrumentPtr = instrumentTablePtr;

		fileDataOffset = 0;
		while (1) {
			curPatchId = fileDataPtr[fileDataOffset++];
			curBankId  = fileDataPtr[fileDataOffset++];

			if ((curBankId == 0xFF) && (curPatchId == 0xFF))
				break;

			instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
			fileDataOffset += 4;

			instrumentPtr->bankId = curBankId;
			instrumentPtr->patchId = curPatchId;

			instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
			if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
				error("MILES-MIDI: unsupported instrument size");

			instrumentOffset += 2;
			// Copy common parameter data
			memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
			instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;

			// Copy partial parameter data
			for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
				memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
				instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
			}

			// Instrument read, next instrument please
			instrumentPtr++;
		}

		// Free instrument file data
		delete[] fileDataPtr;
	}

	return new MidiDriver_Miles_Midi(midiType, instrumentTablePtr, instrumentTableCount);
}

void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
	// Unlock and unprotect channels which were locked or protected by this source.
	for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
		if (!isOutputChannelUsed(i))
			continue;

		if (_midiChannels[i].currentData->source == source && _midiChannels[i].locked) {
			unlockChannel(i);
		}
		if (_midiChannels[i].lockProtected && _midiChannels[i].protectedSource == source) {
			_midiChannels[i].lockProtected = false;
			_midiChannels[i].protectedSource = -1;
		}
		if (_midiChannels[i].unlockData->source == source)
			_midiChannels[i].unlockData->source = -1;
	}

	MidiDriver_MT32GM::deinitSource(source);
}

void MidiDriver_Miles_Midi::applySourceVolume(uint8 source) {
	for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
		if (!isOutputChannelUsed(i))
			continue;

		MidiChannelEntry &channel = _midiChannels[i];
		MilesMidiChannelControlData *channelData = nullptr;
		bool channelLockedByOtherSource = false;
		// Apply the new source volume to this channel if this source is active
		// on this channel, or if it was active on the channel before it was
		// locked.
		if (source == 0xFF || (channel.currentData && channel.currentData->source == source)) {
			channelData = channel.currentData;
		} else if (channel.locked && channel.unlockData && channel.unlockData->source == source) {
			channelData = channel.unlockData;
			channelLockedByOtherSource = true;
		}

		if (channelData && channelData->volume != 0xFF)
			controlChange(i, MIDI_CONTROLLER_VOLUME, channelData->volume, channelData->source, *channelData, channelLockedByOtherSource);
	}
}

uint32 MidiDriver_Miles_Midi::property(int prop, uint32 param) {
	switch (prop) {
	case PROP_MILES_VERSION:
		if (param == 0xFFFF)
			return _milesVersion;

		switch (param) {
		case MILES_VERSION_3:
			_milesVersion = MILES_VERSION_3;
			break;
		case MILES_VERSION_2:
		default:
			_milesVersion = MILES_VERSION_2;
		}

		break;
	default:
		return MidiDriver_Multisource::property(prop, param);
	}
	return 0;
}

} // End of namespace Audio
