From 32e8ec5b3ab294f8936a763cc16423ac8ab2f8f6 Mon Sep 17 00:00:00 2001 From: Martin Kiewitz Date: Sun, 28 Jun 2015 23:14:03 +0200 Subject: AUDIO: Miles Audio MT32 timbre file support for games, that do not have a MT32 timbre file, simply pass an empty filename to the Miles-MT32-factory. --- audio/miles_mt32.cpp | 350 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 321 insertions(+), 29 deletions(-) (limited to 'audio') diff --git a/audio/miles_mt32.cpp b/audio/miles_mt32.cpp index cb039e6ea3..760fcd484e 100644 --- a/audio/miles_mt32.cpp +++ b/audio/miles_mt32.cpp @@ -34,7 +34,20 @@ namespace Audio { // // TODO: currently missing: timbre file support (used in 7th Guest) -#define MILES_MT32_PATCH_COUNT 128 +#define MILES_MT32_PATCHES_COUNT 128 +#define MILES_MT32_CUSTOMTIMBRE_COUNT 64 + +#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)) + +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, 0xFF @@ -54,7 +67,7 @@ const byte milesMT32SysExInitReverb[] = { class MidiDriver_Miles_MT32 : public MidiDriver { public: - MidiDriver_Miles_MT32(); + MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount); virtual ~MidiDriver_Miles_MT32(); // MidiDriver @@ -103,6 +116,7 @@ private: void MT32SysEx(const uint32 targetAddress, const byte *dataPtr); + 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); @@ -110,32 +124,68 @@ private: void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue); void programChange(byte midiChannel, byte patchId); - void setupPatch(byte patchId, byte patchBank); + 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 patchIdSet; + + bool usingCustomTimbre; + byte currentCustomTimbreId; MidiChannelEntry() : currentPatchBank(0), currentPatchId(0), - patchIdSet(false) { } + 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) {} }; // stores information about all MIDI channels MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT]; - byte _patchesBank[MILES_MT32_PATCH_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 }; -MidiDriver_Miles_MT32::MidiDriver_Miles_MT32() { +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)); } @@ -267,25 +317,28 @@ void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *da // 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 channel = b & 0xf; + 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(channel, op1, op2); + controlChange(midiChannel, op1, op2); break; case 0xc0: // Program Change - programChange(channel, op1); - break; - case 0xa0: // Polyphonic key pressure (aftertouch) - case 0xd0: // Channel pressure (aftertouch) - // Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver + programChange(midiChannel, op1); break; case 0xf0: // SysEx warning("MILES-MT32: SysEx: %x", b); @@ -297,6 +350,7 @@ void MidiDriver_Miles_MT32::send(uint32 b) { 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: @@ -330,11 +384,24 @@ void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumbe return; case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE: - // uses .MT data, cannot implement atm + if (_midiChannels[midiChannel].usingCustomTimbre) { + // custom timbre is set on current channel + writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId); + } return; case MILES_CONTROLLER_PROTECT_TIMBRE: - // timbre .MT data, cannot implement atm + 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: @@ -364,29 +431,65 @@ void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) { if (channelPatchBank != activePatchBank) { // associate patch with timbre - setupPatch(patchId, channelPatchBank); - warning("setup patch"); + setupPatch(channelPatchBank, patchId); } - // Search timbre and remember it (only used when timbre file is available) - // TODO + // 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 to MT32 + // Finally send program change to MT32 _driver->send(0xC0 | midiChannel | (patchId << 8)); } -void MidiDriver_Miles_MT32::setupPatch(byte patchId, byte patchBank) { - byte timbreId = 0; +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; + } + return NULL; +} + +void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) { _patchesBank[patchId] = patchBank; if (patchBank) { // non-built-in bank - // TODO: search timbre + int16 customTimbreId = searchCustomTimbre(patchBank, patchId); + if (customTimbreId < 0) { + // currently not loaded, try to install it + // Miles Audio didn't do this here, I'm not exactly sure when it called the install code + customTimbreId = installCustomTimbre(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 - timbreId = patchId & 0x3F; + byte timbreId = patchId & 0x3F; if (!(patchId & 0x40)) { writePatchTimbre(patchId, 0, timbreId); // Group A } else { @@ -394,6 +497,97 @@ void MidiDriver_Miles_MT32::setupPatch(byte patchId, byte patchBank) { } } +// +int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) { + switch(patchBank) { + case 0: // Standard Roland MT32 bank + case 127: // 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 = this->searchCustomInstrument(patchBank, patchId); + if (!instrumentPtr) { + 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 + 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; + + // 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]); + + return customTimbreId; +} + +void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = 0x030110 + ((note - 24) << 2); + + sysExData[0] = customTimbreId; + sysExData[1] = 0xFF; // terminator + + MT32SysEx(targetAddress, sysExData); +} + void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) { byte sysExData[3]; uint32 targetAddress = 0; @@ -432,10 +626,108 @@ void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) { } MidiDriver *MidiDriver_Miles_MT32_create(const Common::String instrumentDataFilename) { - // For some games there are timbre files called [something].MT - // Sherlock Holmes 2 doesn't have one of those - // so I can't implement them - return new MidiDriver_Miles_MT32(); + 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] = 0xFF; // 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] = 0xFF; // 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); } } // End of namespace Audio -- cgit v1.2.3