aboutsummaryrefslogtreecommitdiff
path: root/audio/miles_mt32.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'audio/miles_mt32.cpp')
-rw-r--r--audio/miles_mt32.cpp912
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