scummvm/audio/miles_mt32.cpp
2015-07-03 02:49:49 +02:00

913 lines
28 KiB
C++

/* 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