diff options
Diffstat (limited to 'audio/miles_mt32.cpp')
-rw-r--r-- | audio/miles_mt32.cpp | 912 |
1 files changed, 912 insertions, 0 deletions
diff --git a/audio/miles_mt32.cpp b/audio/miles_mt32.cpp new file mode 100644 index 0000000000..dff863f119 --- /dev/null +++ b/audio/miles_mt32.cpp @@ -0,0 +1,912 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/miles.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +namespace Audio { + +// Miles Audio MT32 driver +// + +#define MILES_MT32_PATCHES_COUNT 128 +#define MILES_MT32_CUSTOMTIMBRE_COUNT 64 + +#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0 +#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127 + +#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14 +#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58 +#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4 +#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT)) + +#define MILES_MT32_SYSEX_TERMINATOR 0xFF + +struct MilesMT32InstrumentEntry { + byte bankId; + byte patchId; + byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1]; + byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1]; +}; + +const byte milesMT32SysExResetParameters[] = { + 0x01, MILES_MT32_SYSEX_TERMINATOR +}; + +const byte milesMT32SysExChansSetup[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR +}; + +const byte milesMT32SysExPartialReserveTable[] = { + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR +}; + +const byte milesMT32SysExInitReverb[] = { + 0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2 +}; + +class MidiDriver_Miles_MT32 : public MidiDriver { +public: + MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount); + virtual ~MidiDriver_Miles_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _MT32; + bool _nativeMT32; + + bool _isOpen; + int _baseFreq; + +public: + void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize); + +private: + void resetMT32(); + + void MT32SysEx(const uint32 targetAddress, const byte *dataPtr); + + uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index); + + void writeRhythmSetup(byte note, byte customTimbreId); + void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId); + void writePatchByte(byte patchId, byte index, byte patchValue); + void writeToSystemArea(byte index, byte value); + + void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue); + void programChange(byte midiChannel, byte patchId); + + const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId); + int16 searchCustomTimbre(byte patchBank, byte patchId); + + void setupPatch(byte patchBank, byte patchId); + int16 installCustomTimbre(byte patchBank, byte patchId); + +private: + struct MidiChannelEntry { + byte currentPatchBank; + byte currentPatchId; + + bool usingCustomTimbre; + byte currentCustomTimbreId; + + MidiChannelEntry() : currentPatchBank(0), + currentPatchId(0), + usingCustomTimbre(false), + currentCustomTimbreId(0) { } + }; + + struct MidiCustomTimbreEntry { + bool used; + bool protectionEnabled; + byte currentPatchBank; + byte currentPatchId; + + uint32 lastUsedNoteCounter; + + MidiCustomTimbreEntry() : used(false), + protectionEnabled(false), + currentPatchBank(0), + currentPatchId(0), + lastUsedNoteCounter(0) {} + }; + + struct MilesMT32SysExQueueEntry { + uint32 targetAddress; + byte dataPos; + byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator + + MilesMT32SysExQueueEntry() : targetAddress(0), + dataPos(0) { + memset(data, 0, sizeof(data)); + } + }; + + // stores information about all MIDI channels + MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT]; + + // stores information about all custom timbres + MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT]; + + byte _patchesBank[MILES_MT32_PATCHES_COUNT]; + + // holds all instruments + MilesMT32InstrumentEntry *_instrumentTablePtr; + uint16 _instrumentTableCount; + + uint32 _noteCounter; // used to figure out, which timbres are outdated + + // SysEx Queues + MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT]; +}; + +MidiDriver_Miles_MT32::MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) { + _instrumentTablePtr = instrumentTablePtr; + _instrumentTableCount = instrumentTableCount; + + _driver = NULL; + _isOpen = false; + _MT32 = false; + _nativeMT32 = false; + _baseFreq = 250; + + _noteCounter = 0; + + memset(_patchesBank, 0, sizeof(_patchesBank)); +} + +MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_Miles_MT32::open() { + assert(!_driver); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_MT32: + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _nativeMT32 = true; + } + break; + default: + break; + } + + if (!_nativeMT32) { + error("MILES-MT32: non-mt32 currently not supported!"); + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + if (_nativeMT32) + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + int ret = _driver->open(); + if (ret) + return ret; + + if (_nativeMT32) { + _driver->sendMT32Reset(); + + resetMT32(); + } + + return 0; +} + +void MidiDriver_Miles_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +void MidiDriver_Miles_MT32::resetMT32() { + // reset all internal parameters / patches + MT32SysEx(0x7F0000, milesMT32SysExResetParameters); + + // init part/channel assignments + MT32SysEx(0x10000D, milesMT32SysExChansSetup); + + // partial reserve table + MT32SysEx(0x100004, milesMT32SysExPartialReserveTable); + + // init reverb + MT32SysEx(0x100001, milesMT32SysExInitReverb); +} + +void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) { + byte sysExMessage[270]; + uint16 sysExPos = 0; + byte sysExByte = 0; + uint16 sysExChecksum = 0; + + memset(&sysExMessage, 0, sizeof(sysExMessage)); + + sysExMessage[0] = 0x41; // Roland + sysExMessage[1] = 0x10; + sysExMessage[2] = 0x16; // Model MT32 + sysExMessage[3] = 0x12; // Command DT1 + + sysExChecksum = 0; + + sysExMessage[4] = (targetAddress >> 16) & 0xFF; + sysExMessage[5] = (targetAddress >> 8) & 0xFF; + sysExMessage[6] = targetAddress & 0xFF; + + for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) { + assert(sysExMessage[targetAddressByte] < 0x80); // security check + sysExChecksum -= sysExMessage[targetAddressByte]; + } + + sysExPos = 7; + while (1) { + sysExByte = *dataPtr++; + if (sysExByte == MILES_MT32_SYSEX_TERMINATOR) + break; // Message done + + assert(sysExPos < sizeof(sysExMessage)); + assert(sysExByte < 0x80); // security check + sysExMessage[sysExPos++] = sysExByte; + sysExChecksum -= sysExByte; + } + + // Calculate checksum + assert(sysExPos < sizeof(sysExMessage)); + sysExMessage[sysExPos++] = sysExChecksum & 0x7f; + + // Send SysEx + _driver->sysEx(sysExMessage, sysExPos); + + // Wait the time it takes to send the SysEx data + uint32 delay = (sysExPos + 2) * 1000 / 3125; + + // Plus an additional delay for the MT-32 rev00 + if (_nativeMT32) + delay += 40; + + g_system->delayMillis(delay); +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Miles_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte midiChannel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: // note off + case 0x90: // note on + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + case 0xe0: // pitch bend change + _noteCounter++; + if (_midiChannels[midiChannel].usingCustomTimbre) { + // Remember that this timbre got used now + _customTimbres[_midiChannels[midiChannel].currentCustomTimbreId].lastUsedNoteCounter = _noteCounter; + } + _driver->send(b); + break; + case 0xb0: // Control change + controlChange(midiChannel, op1, op2); + break; + case 0xc0: // Program Change + programChange(midiChannel, op1); + break; + case 0xf0: // SysEx + warning("MILES-MT32: SysEx: %x", b); + break; + default: + warning("MILES-MT32: Unknown event %02x", command); + } +} + +void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) { + byte channelPatchId = 0; + byte channelCustomTimbreId = 0; + + switch (controllerNumber) { + case MILES_CONTROLLER_SELECT_PATCH_BANK: + _midiChannels[midiChannel].currentPatchBank = controllerValue; + return; + + case MILES_CONTROLLER_PATCH_REVERB: + channelPatchId = _midiChannels[midiChannel].currentPatchId; + + writePatchByte(channelPatchId, 6, controllerValue); + _driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change + return; + + case MILES_CONTROLLER_PATCH_BENDER: + channelPatchId = _midiChannels[midiChannel].currentPatchId; + + writePatchByte(channelPatchId, 4, controllerValue); + _driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change + return; + + case MILES_CONTROLLER_REVERB_MODE: + writeToSystemArea(1, controllerValue); + return; + + case MILES_CONTROLLER_REVERB_TIME: + writeToSystemArea(2, controllerValue); + return; + + case MILES_CONTROLLER_REVERB_LEVEL: + writeToSystemArea(3, controllerValue); + return; + + case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE: + if (_midiChannels[midiChannel].usingCustomTimbre) { + // custom timbre is set on current channel + writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId); + } + return; + + case MILES_CONTROLLER_PROTECT_TIMBRE: + if (_midiChannels[midiChannel].usingCustomTimbre) { + // custom timbre set on current channel + channelCustomTimbreId = _midiChannels[midiChannel].currentCustomTimbreId; + if (controllerValue >= 64) { + // enable protection + _customTimbres[channelCustomTimbreId].protectionEnabled = true; + } else { + // disable protection + _customTimbres[channelCustomTimbreId].protectionEnabled = false; + } + } + return; + + default: + break; + } + + if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) { + // send SysEx + byte sysExQueueNr = 0; + + // figure out which queue is accessed + controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN; + while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) { + sysExQueueNr++; + controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1); + } + assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT); + + byte sysExPos = _sysExQueues[sysExQueueNr].dataPos; + bool sysExSend = false; + + switch(controllerNumber) { + case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1: + _sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF; + _sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16); + break; + case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2: + _sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF; + _sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8); + break; + case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3: + _sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00; + _sysExQueues[sysExQueueNr].targetAddress |= controllerValue; + break; + case MILES_CONTROLLER_SYSEX_COMMAND_DATA: + if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) { + // Space left? put current byte into queue + _sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue; + sysExPos++; + _sysExQueues[sysExQueueNr].dataPos = sysExPos; + if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) { + // overflow? -> send it now + sysExSend = true; + } + } + break; + case MILES_CONTROLLER_SYSEX_COMMAND_SEND: + sysExSend = true; + break; + default: + assert(0); + } + + if (sysExSend) { + if (sysExPos > 0) { + // data actually available? -> send it + _sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator + + // Execute SysEx + MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data); + + // adjust target address to point at the end of the current data + _sysExQueues[sysExQueueNr].targetAddress += sysExPos; + // reset queue data buffer + _sysExQueues[sysExQueueNr].dataPos = 0; + } + } + return; + } + + if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) { + // XMIDI controllers? ignore those + return; + } + + _driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16)); +} + +void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) { + byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank; + byte activePatchBank = _patchesBank[patchId]; + + //warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank); + + // remember patch id for the current MIDI-channel + _midiChannels[midiChannel].currentPatchId = patchId; + + if (channelPatchBank != activePatchBank) { + // associate patch with timbre + setupPatch(channelPatchBank, patchId); + } + + // If this is a custom patch, remember customTimbreId + int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId); + if (customTimbre >= 0) { + _midiChannels[midiChannel].usingCustomTimbre = true; + _midiChannels[midiChannel].currentCustomTimbreId = customTimbre; + } else { + _midiChannels[midiChannel].usingCustomTimbre = false; + } + + // Finally send program change to MT32 + _driver->send(0xC0 | midiChannel | (patchId << 8)); +} + +int16 MidiDriver_Miles_MT32::searchCustomTimbre(byte patchBank, byte patchId) { + byte customTimbreId = 0; + + for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) { + if (_customTimbres[customTimbreId].used) { + if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) { + return customTimbreId; + } + } + } + return -1; +} + +const MilesMT32InstrumentEntry *MidiDriver_Miles_MT32::searchCustomInstrument(byte patchBank, byte patchId) { + const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) { + if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId)) + return instrumentPtr; + instrumentPtr++; + } + return NULL; +} + +void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) { + _patchesBank[patchId] = patchBank; + + if (patchBank) { + // non-built-in bank + int16 customTimbreId = searchCustomTimbre(patchBank, patchId); + if (customTimbreId >= 0) { + // now available? -> use this timbre + writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY + return; + } + } + + // for built-in bank (or timbres, that are not available) use default MT32 timbres + byte timbreId = patchId & 0x3F; + if (!(patchId & 0x40)) { + writePatchTimbre(patchId, 0, timbreId); // Group A + } else { + writePatchTimbre(patchId, 1, timbreId); // Group B + } +} + +void MidiDriver_Miles_MT32::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) { + uint16 timbreCount = 0; + uint32 expectedSize = 0; + const byte *timbreListSeeker = timbreListPtr; + + if (timbreListSize < 2) { + warning("MILES-MT32: XMIDI-TIMB chunk - not enough bytes in chunk"); + return; + } + + timbreCount = READ_LE_UINT16(timbreListPtr); + expectedSize = timbreCount * 2; + if (expectedSize > timbreListSize) { + warning("MILES-MT32: XMIDI-TIMB chunk - size mismatch"); + return; + } + + timbreListSeeker += 2; + + while (timbreCount) { + const byte patchId = *timbreListSeeker++; + const byte patchBank = *timbreListSeeker++; + int16 customTimbreId = 0; + + switch (patchBank) { + case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: + case MILES_MT32_TIMBREBANK_MELODIC_MODULE: + // ignore those 2 banks + break; + + default: + // Check, if this timbre was already loaded + customTimbreId = searchCustomTimbre(patchBank, patchId); + + if (customTimbreId < 0) { + // currently not loaded, try to install it + installCustomTimbre(patchBank, patchId); + } + } + timbreCount--; + } +} + +// +int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) { + switch(patchBank) { + case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank + case MILES_MT32_TIMBREBANK_MELODIC_MODULE: // Reserved for melodic mode + return -1; + default: + break; + } + + // Original driver did a search for custom timbre here + // and in case it was found, it would call setup_patch() + // we are called from within setup_patch(), so this isn't needed + + int16 customTimbreId = -1; + int16 leastUsedTimbreId = -1; + uint32 leastUsedTimbreNoteCounter = _noteCounter; + const MilesMT32InstrumentEntry *instrumentPtr = NULL; + + // Check, if requested instrument is actually available + instrumentPtr = searchCustomInstrument(patchBank, patchId); + if (!instrumentPtr) { + warning("MILES-MT32: instrument not found during installCustomTimbre()"); + return -1; // not found -> bail out + } + + // Look for an empty timbre slot + // or get the least used non-protected slot + for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) { + if (!_customTimbres[customTimbreNr].used) { + // found an empty slot -> use this one + customTimbreId = customTimbreNr; + break; + } else { + // used slot + if (!_customTimbres[customTimbreNr].protectionEnabled) { + // not protected + uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter; + if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) { + leastUsedTimbreId = customTimbreNr; + leastUsedTimbreNoteCounter = customTimbreNoteCounter; + } + } + } + } + + if (customTimbreId < 0) { + // no empty slot found, check if we got a least used non-protected slot + if (leastUsedTimbreId < 0) { + // everything is protected, bail out + warning("MILES-MT32: no non-protected timbre slots available during installCustomTimbre()"); + return -1; + } + customTimbreId = leastUsedTimbreId; + } + + // setup timbre slot + _customTimbres[customTimbreId].used = true; + _customTimbres[customTimbreId].currentPatchBank = patchBank; + _customTimbres[customTimbreId].currentPatchId = patchId; + _customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter; + _customTimbres[customTimbreId].protectionEnabled = false; + + uint32 targetAddress = 0x080000 | (customTimbreId << 9); + uint32 targetAddressCommon = targetAddress + 0x000000; + uint32 targetAddressPartial1 = targetAddress + 0x00000E; + uint32 targetAddressPartial2 = targetAddress + 0x000048; + uint32 targetAddressPartial3 = targetAddress + 0x000102; + uint32 targetAddressPartial4 = targetAddress + 0x00013C; + +#if 0 + byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1]; + uint16 parameterDataPos = 0; + + memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR; + + MT32SysEx(targetAddressCommon, parameterData); +#endif + + // upload common parameter data + MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter); + // upload partial parameter data + MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]); + MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]); + MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]); + MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]); + + setupPatch(patchBank, patchId); + + return customTimbreId; +} + +uint32 MidiDriver_Miles_MT32::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) { + uint16 targetAddressLSB = baseAddress & 0xFF; + uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF; + uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF; + + // add index to it, but use 7-bit of the index for each byte + targetAddressLSB += (index & 0x7F); + targetAddressKSB += ((index >> 7) & 0x7F); + targetAddressMSB += ((index >> 14) & 0x7F); + + // adjust bytes, so that none of them is above or equal 0x80 + while (targetAddressLSB >= 0x80) { + targetAddressLSB -= 0x80; + targetAddressKSB++; + } + while (targetAddressKSB >= 0x80) { + targetAddressKSB -= 0x80; + targetAddressMSB++; + } + assert(targetAddressMSB < 0x80); + + // put everything together + return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16); +} + +void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2)); + + sysExData[0] = customTimbreId; + sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) { + byte sysExData[3]; + uint32 targetAddress = 0; + + // write to patch memory (starts at 0x050000, each entry is 8 bytes) + targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3); + + sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm + sysExData[1] = timbreId; // timbre number (0-63) + sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index); + + sysExData[0] = patchValue; + sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = calculateSysExTargetAddress(0x100000, index); + + sysExData[0] = value; + sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) { + MilesMT32InstrumentEntry *instrumentTablePtr = NULL; + uint16 instrumentTableCount = 0; + + if (!instrumentDataFilename.empty()) { + // Load MT32 instrument data from file SAMPLE.MT + Common::File *fileStream = new Common::File(); + uint32 fileSize = 0; + byte *fileDataPtr = NULL; + uint32 fileDataOffset = 0; + uint32 fileDataLeft = 0; + + byte curBankId = 0; + byte curPatchId = 0; + + MilesMT32InstrumentEntry *instrumentPtr = NULL; + uint32 instrumentOffset = 0; + uint16 instrumentDataSize = 0; + + if (!fileStream->open(instrumentDataFilename)) + error("MILES-MT32: could not open instrument file '%s'", instrumentDataFilename.c_str()); + + fileSize = fileStream->size(); + + fileDataPtr = new byte[fileSize]; + + if (fileStream->read(fileDataPtr, fileSize) != fileSize) + error("MILES-MT32: error while reading instrument file"); + fileStream->close(); + delete fileStream; + + // File is like this: + // [patch:BYTE] [bank:BYTE] [patchoffset:UINT32] + // ... + // until patch + bank are both 0xFF, which signals end of header + + // First we check how many entries there are + fileDataOffset = 0; + fileDataLeft = fileSize; + while (1) { + if (fileDataLeft < 6) + error("MILES-MT32: unexpected EOF in instrument file"); + + curPatchId = fileDataPtr[fileDataOffset++]; + curBankId = fileDataPtr[fileDataOffset++]; + + if ((curBankId == 0xFF) && (curPatchId == 0xFF)) + break; + + fileDataOffset += 4; // skip over offset + instrumentTableCount++; + } + + if (instrumentTableCount == 0) + error("MILES-MT32: no instruments in instrument file"); + + // Allocate space for instruments + instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount]; + + // Now actually read all entries + instrumentPtr = instrumentTablePtr; + + fileDataOffset = 0; + fileDataLeft = fileSize; + while (1) { + curPatchId = fileDataPtr[fileDataOffset++]; + curBankId = fileDataPtr[fileDataOffset++]; + + if ((curBankId == 0xFF) && (curPatchId == 0xFF)) + break; + + instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset); + fileDataOffset += 4; + + instrumentPtr->bankId = curBankId; + instrumentPtr->patchId = curPatchId; + + instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset); + if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2)) + error("MILES-MT32: unsupported instrument size"); + + instrumentOffset += 2; + // Copy common parameter data + memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE); + instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator + instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE; + + // Copy partial parameter data + for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) { + memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator + instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + } + + // Instrument read, next instrument please + instrumentPtr++; + } + + // Free instrument file data + delete[] fileDataPtr; + } + + return new MidiDriver_Miles_MT32(instrumentTablePtr, instrumentTableCount); +} + +void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) { + MidiDriver_Miles_MT32 *driverMT32 = dynamic_cast<MidiDriver_Miles_MT32 *>(driver); + + if (driverMT32) { + driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize); + } +} + +} // End of namespace Audio |