scummvm/audio/miles_midi.cpp
2023-12-24 13:19:25 +01:00

971 lines
35 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/miles.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/mutex.h"
#include "common/system.h"
#include "common/textconsole.h"
namespace Audio {
// Miles Audio MT-32 / General MIDI driver
//
#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
const byte milesMT32SysExChansSetup[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
};
const byte milesMT32SysExPartialReserveTable[] = {
0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04
};
const byte milesMT32SysExInitReverb[] = {
0x00, 0x03, 0x02 // Reverb mode 0, reverb time 3, reverb level 2
};
MidiDriver_Miles_Midi::MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) :
MidiDriver_MT32GM(midiType), _noteCounter(0), _milesVersion(MILES_VERSION_2) {
memset(_patchesBank, 0, sizeof(_patchesBank));
_instrumentTablePtr = instrumentTablePtr;
_instrumentTableCount = instrumentTableCount;
setSourceNeutralVolume(MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME);
setSourceVolume(MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME);
}
MidiDriver_Miles_Midi::~MidiDriver_Miles_Midi() {
if (_instrumentTablePtr)
delete[] _instrumentTablePtr;
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (_midiChannels[i].unlockData)
delete _midiChannels[i].unlockData;
}
}
void MidiDriver_Miles_Midi::initControlData() {
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
_controlData[i] = _midiChannels[i].currentData = new MilesMidiChannelControlData();
_midiChannels[i].unlockData = new MilesMidiChannelControlData();
_controlData[i]->volume = _controlData[i]->scaledVolume =
(_nativeMT32 ? MT32_DEFAULT_CHANNEL_VOLUME : GM_DEFAULT_CHANNEL_VOLUME);
if (_nativeMT32 && i >= 1 && i <= 8) {
_midiChannels[i].currentData->program = MT32_DEFAULT_INSTRUMENTS[i - 1];
_midiChannels[i].currentData->panPosition = MT32_DEFAULT_PANNING[i - 1];
}
}
}
void MidiDriver_Miles_Midi::initMidiDevice() {
MidiDriver_MT32GM::initMidiDevice();
// Additional Miles AIL specific initialization
if (_midiType == MT_MT32 && _nativeMT32) {
// init part/channel assignments
sysExMT32(milesMT32SysExChansSetup, 9, (0x10 << 14) | (0x00 << 7) | 0x0D, false, true);
// partial reserve table
sysExMT32(milesMT32SysExPartialReserveTable, 9, (0x10 << 14) | (0x00 << 7) | 0x04, false, true);
// init reverb
sysExMT32(milesMT32SysExInitReverb, 3, (0x10 << 14) | (0x00 << 7) | 0x01, false, true);
}
// Set Miles default controller values
if (_milesVersion == MILES_VERSION_2) {
// Note that AIL/MSS apparently did not get full support for GM until
// version 3.00 in 09/1994. Many games used the MT-32 driver to
// implement GM support. As a result, default parameters were only sent
// out on the MT-32 channels (2-10). Also, the default MT-32 instrument
// numbers were set on GM devices, even though they map to different
// instruments. This is reproduced here to prevent possible issues with
// games that depend on this behavior.
for (int i = 1; i < 10; ++i) {
// Volume 7F (max)
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_VOLUME, 0x7F);
if (_midiType == MT_MT32) {
// Panning center - not the MT-32 default for all channels
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_PANNING, 0x40);
}
// Patch
if (_midiType == MT_GM && i != MIDI_RHYTHM_CHANNEL) {
// Send the MT-32 default instrument numbers out to GM devices.
send(-1, MIDI_COMMAND_PROGRAM_CHANGE | i, MT32_DEFAULT_INSTRUMENTS[i - 1], 0);
}
// The following settings are also sent out by the AIL driver:
// - Modulation 0
// - Expression 7F (max)
// - Sustain off
// - Pitch bend neutral
// These are the default MT-32 and GM settings, so it is not
// necessary to send these.
}
} else {
// MSS 3 initialization
for (int i = (_midiType == MT_GM ? 0 : 1); i < (_midiType == MT_GM ? MIDI_CHANNEL_COUNT : 10); ++i) {
// Patch
if (_midiType == MT_MT32 && i != MIDI_RHYTHM_CHANNEL) {
// Set instrument to 0.
send(-1, MIDI_COMMAND_PROGRAM_CHANGE | i, 0, 0);
}
// Volume 7F (max)
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_VOLUME, 0x7F);
if (_midiType == MT_MT32) {
// Panning center - not the MT-32 default for all channels
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_PANNING, 0x40);
}
if (_midiType == MT_GM) {
// Reverb 28h
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_REVERB, 0x28);
}
// Pitch bend range 2 semitones
// TODO Some games seem to initialize this to a different value, so
// this might need to be configurable.
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_RPN_LSB, 0x00);
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_RPN_MSB, 0x00);
if (_midiType == MT_GM) {
// MT-32 does not respond to the LSB, so only send it out for GM
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0x00);
}
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 0x02);
// MSS 3 also sets the following settings:
// - Program 0 (also for GM)
// - Pitch bend neutral
// - Modulation 0
// - Panning center (also for GM)
// - Expression 7F
// - Sustain off
// - Chorus 0
// These are the default settings, so it is not necessary to send
// these.
}
}
}
// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
assert(source < MAXIMUM_SOURCES);
byte command = b & 0xf0;
byte dataChannel = b & 0xf;
byte op1 = (b >> 8) & 0xff;
byte op2 = (b >> 16) & 0xff;
byte outputChannel = source < 0 ? dataChannel : _channelMap[source][dataChannel];
MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel];
// Only send the message to the MIDI device if the channel is not locked or
// if the source that locked the channel is sending the message
bool channelLockedByOtherSource = source >= 0 && outputChannelEntry.locked &&
outputChannelEntry.currentData->source != source;
// Track controller changes on the current data if the MIDI message is sent out,
// or on the unlock data otherwise.
MilesMidiChannelControlData &controlData = channelLockedByOtherSource ?
*outputChannelEntry.unlockData : *outputChannelEntry.currentData;
if (command == MIDI_COMMAND_CONTROL_CHANGE && op1 == MILES_CONTROLLER_LOCK_CHANNEL) {
// The lock channel controller will allocate an output channel to use
// to send the events on this data channel. In this case, the data
// channel should not be assigned to the source, because it will not
// actually be used to send MIDI events. processEvent will assign the
// data channel to the source, so it is bypassed and controlChange is
// called directly.
controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
} else {
processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource);
}
if (command == MIDI_COMMAND_NOTE_OFF || command == MIDI_COMMAND_NOTE_ON || command == MIDI_COMMAND_PITCH_BEND ||
command == MIDI_COMMAND_POLYPHONIC_AFTERTOUCH || command == MIDI_COMMAND_CHANNEL_AFTERTOUCH) {
_noteCounter++;
if (controlData.usingCustomTimbre) {
// Remember that this timbre got used now
_customTimbres[controlData.currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
}
}
}
void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue,
int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
assert(source < MAXIMUM_SOURCES);
MilesMidiChannelControlData &milesControlData = channelLockedByOtherSource ?
*_midiChannels[outputChannel].unlockData : *_midiChannels[outputChannel].currentData;
// XMIDI controllers
switch (controllerNumber) {
case MILES_CONTROLLER_SELECT_PATCH_BANK:
milesControlData.currentPatchBank = controllerValue;
return;
case MILES_CONTROLLER_PROTECT_TIMBRE:
if (milesControlData.usingCustomTimbre) {
// custom timbre set on current channel
_customTimbres[milesControlData.currentCustomTimbreId].protectionEnabled = controllerValue >= 64;
}
return;
case MILES_CONTROLLER_LOCK_CHANNEL:
if (source >= 0) {
if (controllerValue >= 0x40) {
lockChannel(source, outputChannel);
} else {
unlockChannel(outputChannel);
}
}
return;
case MILES_CONTROLLER_PROTECT_CHANNEL:
if (source >= 0 && !_midiChannels[outputChannel].locked) {
_midiChannels[outputChannel].lockProtected = controllerValue >= 0x40;
_midiChannels[outputChannel].protectedSource = controllerValue >= 0x40 ? source : -1;
}
return;
default:
break;
}
// XMIDI MT-32 specific controllers
if (_midiType == MT_MT32 && _nativeMT32) {
switch (controllerNumber) {
case MILES_CONTROLLER_PATCH_REVERB:
writePatchByte(milesControlData.program, 6, controllerValue);
if (!channelLockedByOtherSource)
_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (milesControlData.program << 8));
return;
case MILES_CONTROLLER_PATCH_BENDER:
writePatchByte(milesControlData.program, 4, controllerValue);
if (!channelLockedByOtherSource)
_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (milesControlData.program << 8));
return;
case MILES_CONTROLLER_REVERB_MODE:
writeToSystemArea(1, controllerValue);
return;
case MILES_CONTROLLER_REVERB_TIME:
writeToSystemArea(2, controllerValue);
return;
case MILES_CONTROLLER_REVERB_LEVEL:
writeToSystemArea(3, controllerValue);
return;
case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
if (milesControlData.usingCustomTimbre) {
// custom timbre is set on current channel
writeRhythmSetup(controllerValue, milesControlData.currentCustomTimbreId);
}
return;
default:
break;
}
}
// XMIDI MT-32 SysEx controllers
if (_midiType == MT_MT32 && (controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) &&
(controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
if (!_nativeMT32)
return;
// send SysEx
byte sysExQueueNr = 0;
// figure out which queue is accessed
controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA) {
sysExQueueNr++;
controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA + 1);
}
assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);
byte sysExPos = _milesSysExQueues[sysExQueueNr].dataPos;
bool sysExSend = false;
switch(controllerNumber) {
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
_milesSysExQueues[sysExQueueNr].targetAddress &= 0x003FFF;
_milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 14);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
_milesSysExQueues[sysExQueueNr].targetAddress &= 0x1FC07F;
_milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 7);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
_milesSysExQueues[sysExQueueNr].targetAddress &= 0x1FFF80;
_milesSysExQueues[sysExQueueNr].targetAddress |= controllerValue;
break;
case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
// Space left? put current byte into queue
_milesSysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
sysExPos++;
_milesSysExQueues[sysExQueueNr].dataPos = sysExPos;
if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
// overflow? -> send it now
sysExSend = true;
}
}
break;
case MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA:
if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
// Space left? put current byte into queue
_milesSysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
sysExPos++;
// Do not increment dataPos. Subsequent Final Data commands will
// re-send the last address byte with the new controller value.
sysExSend = true;
}
break;
default:
assert(0);
}
if (sysExSend) {
if (sysExPos > 0) {
// data actually available? -> send it
sysExMT32(_milesSysExQueues[sysExQueueNr].data, sysExPos, _milesSysExQueues[sysExQueueNr].targetAddress, false, true);
// Adjust target address to point at the final data byte, or at the
// end of the current data in case of an overflow
_milesSysExQueues[sysExQueueNr].targetAddress += _milesSysExQueues[sysExQueueNr].dataPos;
// reset queue data buffer
_milesSysExQueues[sysExQueueNr].dataPos = 0;
}
}
return;
}
if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
// XMIDI controllers? Don't send these to the MIDI device
return;
}
// Handle other controllers and send message (if necessary)
MidiDriver_MT32GM::controlChange(outputChannel, controllerNumber, controllerValue, source, milesControlData, channelLockedByOtherSource);
}
bool MidiDriver_Miles_Midi::addActiveNote(uint8 outputChannel, uint8 note, int8 source) {
bool added = MidiDriver_MT32GM::addActiveNote(outputChannel, note, source);
if (added)
_midiChannels[outputChannel].activeNotes++;
return added;
}
bool MidiDriver_Miles_Midi::removeActiveNote(uint8 outputChannel, uint8 note, int8 source) {
bool removed = MidiDriver_MT32GM::removeActiveNote(outputChannel, note, source);
if (removed) {
if (_midiChannels[outputChannel].activeNotes == 0) {
warning("MILES-MIDI: active notes 0 on channel %d when turning off note %x", outputChannel, note);
} else {
_midiChannels[outputChannel].activeNotes--;
}
}
return removed;
}
void MidiDriver_Miles_Midi::removeActiveNotes(uint8 outputChannel, bool sustainedNotes) {
Common::StackLock lock(_activeNotesMutex);
// Remove sustained or non-sustained notes from the active notes registration
for (int i = 0; i < _maximumActiveNotes; ++i) {
if (_activeNotes[i].channel == outputChannel && _activeNotes[i].sustain == sustainedNotes) {
_activeNotes[i].clear();
if (_midiChannels[outputChannel].activeNotes == 0) {
warning("MILES-MIDI: active notes 0 on channel %d (sustained %i) when removing active notes", outputChannel, sustainedNotes);
continue;
}
--_midiChannels[outputChannel].activeNotes;
}
}
}
void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
assert(source < MAXIMUM_SOURCES);
int8 lockChannel = findLockChannel();
if (lockChannel == -1)
// Try again, but consider lock protected channels
lockChannel = findLockChannel(true);
if (lockChannel == -1)
// Could not find a channel to lock
return;
// stopNotesOnChannel will turn off sustain, so record the current sustain
// value so it can be set on the unlock data.
bool currentSustain = _midiChannels[lockChannel].currentData->sustain;
stopNotesOnChannel(lockChannel);
_midiChannels[lockChannel].locked = true;
_midiChannels[lockChannel].lockDataChannel = dataChannel;
_channelMap[source][dataChannel] = lockChannel;
// Copy current controller values so they can be restored when unlocking the channel
*_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData;
_midiChannels[lockChannel].unlockData->sustain = currentSustain;
_midiChannels[lockChannel].currentData->source = source;
// Set any specified default controller values on the channel
applyControllerDefaults(source, *_midiChannels[lockChannel].currentData, lockChannel, false);
// Send volume change to apply the new source volume
controlChange(lockChannel, MIDI_CONTROLLER_VOLUME, 0x7F, source, *_midiChannels[lockChannel].currentData);
}
int8 MidiDriver_Miles_Midi::findLockChannel(bool useProtectedChannels) {
// Starting at the highest (non-rhythm) channel, find the channel
// with the least active notes that isn't already locked.
// If useProtectedChannels is false, channels that are protected
// from channel locking will not be considered.
int8 potentialLockChannel = -1;
uint8 notes = 255;
for (int i = MIDI_CHANNEL_COUNT - 1; i >= 0; --i) {
if (!isOutputChannelUsed(i) || i == MIDI_RHYTHM_CHANNEL || _midiChannels[i].locked ||
(!useProtectedChannels && _midiChannels[i].lockProtected))
continue;
if (_midiChannels[i].activeNotes < notes) {
potentialLockChannel = i;
notes = _midiChannels[i].activeNotes;
}
}
return potentialLockChannel;
}
void MidiDriver_Miles_Midi::unlockChannel(uint8 outputChannel) {
MidiChannelEntry &channel = _midiChannels[outputChannel];
if (!channel.locked)
return;
stopNotesOnChannel(outputChannel);
// Unlock the channel
channel.locked = false;
_channelMap[channel.currentData->source][channel.lockDataChannel] = channel.lockDataChannel;
channel.lockDataChannel = -1;
channel.currentData->source = channel.unlockData->source;
// Send the unlock channel data to the MIDI device to reset the channel parameters
if (channel.unlockData->volume != 0xFF) {
controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, channel.unlockData->volume, channel.currentData->source, *channel.currentData);
} else {
channel.currentData->volume = 0xFF;
}
if (channel.currentData->modulation != channel.unlockData->modulation)
controlChange(outputChannel, MIDI_CONTROLLER_MODULATION, channel.unlockData->modulation, channel.currentData->source, *channel.currentData);
if (channel.currentData->panPosition != channel.unlockData->panPosition)
controlChange(outputChannel, MIDI_CONTROLLER_PANNING, channel.unlockData->panPosition, channel.currentData->source, *channel.currentData);
if (channel.currentData->expression != channel.unlockData->expression)
controlChange(outputChannel, MIDI_CONTROLLER_EXPRESSION, channel.unlockData->expression, channel.currentData->source, *channel.currentData);
if (channel.currentData->sustain != channel.unlockData->sustain)
controlChange(outputChannel, MIDI_CONTROLLER_SUSTAIN, channel.unlockData->sustain ? 0x7F : 0x00, channel.currentData->source, *channel.currentData);
if (channel.currentData->currentPatchBank != channel.unlockData->currentPatchBank)
controlChange(outputChannel, MILES_CONTROLLER_SELECT_PATCH_BANK, channel.unlockData->currentPatchBank,
channel.currentData->source, *channel.currentData);
if (channel.unlockData->program != 0xFF && (channel.currentData->program != channel.unlockData->program ||
channel.currentData->currentPatchBank != channel.unlockData->currentPatchBank))
programChange(outputChannel, channel.unlockData->program, channel.currentData->source, *channel.currentData);
if (channel.currentData->pitchWheel != channel.unlockData->pitchWheel)
send(channel.currentData->source, MIDI_COMMAND_PITCH_BEND | outputChannel,
channel.unlockData->pitchWheel & 0x7F, (channel.unlockData->pitchWheel >> 7) & 0x7F);
}
void MidiDriver_Miles_Midi::stopNotesOnChannel(uint8 outputChannelNumber) {
MidiChannelEntry &channel = _midiChannels[outputChannelNumber];
if (channel.currentData->sustain) {
controlChange(outputChannelNumber, MIDI_CONTROLLER_SUSTAIN, 0, channel.currentData->source, *channel.currentData);
}
if (channel.activeNotes > 0) {
controlChange(outputChannelNumber, MIDI_CONTROLLER_ALL_NOTES_OFF, 0, channel.currentData->source, *channel.currentData);
}
}
void MidiDriver_Miles_Midi::stopAllNotes(bool stopSustainedNotes) {
MidiDriver_MT32GM::stopAllNotes(stopSustainedNotes);
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (isOutputChannelUsed(i))
_midiChannels[i].activeNotes = 0;
}
}
void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, int8 source,
MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
MilesMidiChannelControlData &milesControlData = channelLockedByOtherSource ?
*_midiChannels[outputChannel].unlockData : *_midiChannels[outputChannel].currentData;
if (_midiType == MT_MT32) {
byte channelPatchBank = milesControlData.currentPatchBank;
byte activePatchBank = _patchesBank[patchId];
//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
if (channelPatchBank != activePatchBank) {
// associate patch with timbre
setupPatch(channelPatchBank, patchId);
}
// If this is a custom patch, remember customTimbreId
int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
if (customTimbre >= 0) {
milesControlData.usingCustomTimbre = true;
milesControlData.currentCustomTimbreId = customTimbre;
} else {
milesControlData.usingCustomTimbre = false;
}
}
MidiDriver_MT32GM::programChange(outputChannel, patchId, source, milesControlData, channelLockedByOtherSource);
}
int16 MidiDriver_Miles_Midi::searchCustomTimbre(byte patchBank, byte patchId) {
byte customTimbreId = 0;
for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
if (_customTimbres[customTimbreId].used) {
if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) &&
(_customTimbres[customTimbreId].currentPatchId == patchId)) {
return customTimbreId;
}
}
}
return -1;
}
const MilesMT32InstrumentEntry *MidiDriver_Miles_Midi::searchCustomInstrument(byte patchBank, byte patchId) {
const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;
for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
return instrumentPtr;
instrumentPtr++;
}
return nullptr;
}
void MidiDriver_Miles_Midi::setupPatch(byte patchBank, byte patchId, bool useSysExQueue) {
_patchesBank[patchId] = patchBank;
if (patchBank) {
// non-built-in bank
int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
if (customTimbreId >= 0) {
// now available? -> use this timbre
writePatchTimbre(patchId, 2, customTimbreId, useSysExQueue); // Group MEMORY
return;
}
}
// for built-in bank (or timbres, that are not available) use default MT32 timbres
byte timbreId = patchId & 0x3F;
if (!(patchId & 0x40)) {
writePatchTimbre(patchId, 0, timbreId, useSysExQueue); // Group A
} else {
writePatchTimbre(patchId, 1, timbreId, useSysExQueue); // Group B
}
}
void MidiDriver_Miles_Midi::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
if (_midiType != MT_MT32)
// Some GM files contain timbre chunks, but custom patches cannot
// be loaded on a GM device.
return;
uint16 timbreCount = 0;
uint32 expectedSize = 0;
const byte *timbreListSeeker = timbreListPtr;
if (timbreListSize < 2) {
warning("MILES-MIDI: XMIDI-TIMB chunk - not enough bytes in chunk");
return;
}
timbreCount = READ_LE_UINT16(timbreListPtr);
expectedSize = timbreCount * 2;
if (expectedSize > timbreListSize) {
warning("MILES-MIDI: XMIDI-TIMB chunk - size mismatch");
return;
}
timbreListSeeker += 2;
while (timbreCount) {
const byte patchId = *timbreListSeeker++;
const byte patchBank = *timbreListSeeker++;
int16 customTimbreId = 0;
switch (patchBank) {
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
// ignore those 2 banks
break;
default:
// Check, if this timbre was already loaded
customTimbreId = searchCustomTimbre(patchBank, patchId);
if (customTimbreId < 0) {
// currently not loaded, try to install it
installCustomTimbre(patchBank, patchId);
}
}
timbreCount--;
}
}
//
int16 MidiDriver_Miles_Midi::installCustomTimbre(byte patchBank, byte patchId) {
switch(patchBank) {
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
case MILES_MT32_TIMBREBANK_MELODIC_MODULE: // Reserved for melodic mode
return -1;
default:
break;
}
// Original driver did a search for custom timbre here
// and in case it was found, it would call setup_patch()
// we are called from within setup_patch(), so this isn't needed
int16 customTimbreId = -1;
int16 leastUsedTimbreId = -1;
uint32 leastUsedTimbreNoteCounter = _noteCounter;
const MilesMT32InstrumentEntry *instrumentPtr = nullptr;
// Check, if requested instrument is actually available
instrumentPtr = searchCustomInstrument(patchBank, patchId);
if (!instrumentPtr) {
warning("MILES-MIDI: instrument not found during installCustomTimbre()");
return -1; // not found -> bail out
}
// Look for an empty timbre slot
// or get the least used non-protected slot
for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
if (!_customTimbres[customTimbreNr].used) {
// found an empty slot -> use this one
customTimbreId = customTimbreNr;
break;
} else {
// used slot
if (!_customTimbres[customTimbreNr].protectionEnabled) {
// not protected
uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
leastUsedTimbreId = customTimbreNr;
leastUsedTimbreNoteCounter = customTimbreNoteCounter;
}
}
}
}
if (customTimbreId < 0) {
// no empty slot found, check if we got a least used non-protected slot
if (leastUsedTimbreId < 0) {
// everything is protected, bail out
warning("MILES-MIDI: no non-protected timbre slots available during installCustomTimbre()");
return -1;
}
customTimbreId = leastUsedTimbreId;
}
// setup timbre slot
_customTimbres[customTimbreId].used = true;
_customTimbres[customTimbreId].currentPatchBank = patchBank;
_customTimbres[customTimbreId].currentPatchId = patchId;
_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
_customTimbres[customTimbreId].protectionEnabled = false;
uint32 targetAddress = ((0x08 << 14) | (0x00 << 7) | 0x00) + (customTimbreId * 0x100);
uint32 targetAddressCommon = targetAddress;
uint32 targetAddressPartial1 = targetAddress + 0x0E;
uint32 targetAddressPartial2 = targetAddress + 0x48;
uint32 targetAddressPartial3 = targetAddress + 0x82;
uint32 targetAddressPartial4 = targetAddress + 0xBC;
#if 0
byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
uint16 parameterDataPos = 0;
memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;
MT32SysEx(targetAddressCommon, parameterData);
#endif
// upload common parameter data
sysExMT32(instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE, targetAddressCommon, true);
// upload partial parameter data
sysExMT32(instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial1, true);
sysExMT32(instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial2, true);
sysExMT32(instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial3, true);
sysExMT32(instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial4, true);
setupPatch(patchBank, patchId, true);
return customTimbreId;
}
void MidiDriver_Miles_Midi::writeRhythmSetup(byte note, byte customTimbreId) {
byte sysExData[1];
uint32 targetAddress = (0x03 << 14) | (0x01 << 7) | 0x10;
targetAddress += ((note - 24) << 2);
sysExData[0] = customTimbreId;
sysExMT32(sysExData, 1, targetAddress);
}
void MidiDriver_Miles_Midi::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId, bool useSysExQueue) {
byte sysExData[2];
uint32 targetAddress = (0x05 << 14) | (0x00 << 7) | 0x00;
// write to patch memory (starts at 0x050000, each entry is 8 bytes)
targetAddress += (patchId << 3);
sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
sysExData[1] = timbreId; // timbre number (0-63)
sysExMT32(sysExData, 2, targetAddress, useSysExQueue);
}
void MidiDriver_Miles_Midi::writePatchByte(byte patchId, byte index, byte patchValue) {
byte sysExData[1];
uint32 targetAddress = (0x05 << 14) | (0x00 << 7) | 0x00;
targetAddress += (patchId << 3) + index;
sysExData[0] = patchValue;
sysExMT32(sysExData, 1, targetAddress);
}
void MidiDriver_Miles_Midi::writeToSystemArea(byte index, byte value) {
byte sysExData[1];
uint32 targetAddress = (0x10 << 14) | (0x00 << 7) | 0x00;
targetAddress += index;
sysExData[0] = value;
sysExMT32(sysExData, 1, targetAddress);
}
MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::Path &instrumentDataFilename) {
return MidiDriver_Miles_MIDI_create(MT_MT32, instrumentDataFilename);
}
MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::Path &instrumentDataFilename) {
assert(midiType == MT_MT32 || midiType == MT_GM || midiType == MT_GS);
MilesMT32InstrumentEntry *instrumentTablePtr = nullptr;
uint16 instrumentTableCount = 0;
if (midiType == MT_MT32 && !instrumentDataFilename.empty()) {
// Load MT32 instrument data from file SAMPLE.MT
Common::File *fileStream = new Common::File();
uint32 fileSize = 0;
byte *fileDataPtr = nullptr;
uint32 fileDataOffset = 0;
uint32 fileDataLeft = 0;
byte curBankId;
byte curPatchId;
MilesMT32InstrumentEntry *instrumentPtr = nullptr;
uint32 instrumentOffset;
uint16 instrumentDataSize;
if (!fileStream->open(instrumentDataFilename))
error("MILES-MIDI: could not open instrument file '%s'", instrumentDataFilename.toString(Common::Path::kNativeSeparator).c_str());
fileSize = fileStream->size();
fileDataPtr = new byte[fileSize];
if (fileStream->read(fileDataPtr, fileSize) != fileSize)
error("MILES-MIDI: error while reading instrument file");
fileStream->close();
delete fileStream;
// File is like this:
// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
// ...
// until patch + bank are both 0xFF, which signals end of header
// First we check how many entries there are
fileDataOffset = 0;
fileDataLeft = fileSize;
while (1) {
if (fileDataLeft < 6)
error("MILES-MIDI: unexpected EOF in instrument file");
curPatchId = fileDataPtr[fileDataOffset++];
curBankId = fileDataPtr[fileDataOffset++];
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
break;
fileDataOffset += 4; // skip over offset
instrumentTableCount++;
}
if (instrumentTableCount == 0)
error("MILES-MIDI: no instruments in instrument file");
// Allocate space for instruments
instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];
// Now actually read all entries
instrumentPtr = instrumentTablePtr;
fileDataOffset = 0;
while (1) {
curPatchId = fileDataPtr[fileDataOffset++];
curBankId = fileDataPtr[fileDataOffset++];
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
break;
instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
fileDataOffset += 4;
instrumentPtr->bankId = curBankId;
instrumentPtr->patchId = curPatchId;
instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
error("MILES-MIDI: unsupported instrument size");
instrumentOffset += 2;
// Copy common parameter data
memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
// Copy partial parameter data
for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
}
// Instrument read, next instrument please
instrumentPtr++;
}
// Free instrument file data
delete[] fileDataPtr;
}
return new MidiDriver_Miles_Midi(midiType, instrumentTablePtr, instrumentTableCount);
}
void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
// Unlock and unprotect channels which were locked or protected by this source.
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
if (_midiChannels[i].currentData->source == source && _midiChannels[i].locked) {
unlockChannel(i);
}
if (_midiChannels[i].lockProtected && _midiChannels[i].protectedSource == source) {
_midiChannels[i].lockProtected = false;
_midiChannels[i].protectedSource = -1;
}
if (_midiChannels[i].unlockData->source == source)
_midiChannels[i].unlockData->source = -1;
}
MidiDriver_MT32GM::deinitSource(source);
}
void MidiDriver_Miles_Midi::applySourceVolume(uint8 source) {
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
MidiChannelEntry &channel = _midiChannels[i];
MilesMidiChannelControlData *channelData = nullptr;
bool channelLockedByOtherSource = false;
// Apply the new source volume to this channel if this source is active
// on this channel, or if it was active on the channel before it was
// locked.
if (source == 0xFF || (channel.currentData && channel.currentData->source == source)) {
channelData = channel.currentData;
} else if (channel.locked && channel.unlockData && channel.unlockData->source == source) {
channelData = channel.unlockData;
channelLockedByOtherSource = true;
}
if (channelData && channelData->volume != 0xFF)
controlChange(i, MIDI_CONTROLLER_VOLUME, channelData->volume, channelData->source, *channelData, channelLockedByOtherSource);
}
}
uint32 MidiDriver_Miles_Midi::property(int prop, uint32 param) {
switch (prop) {
case PROP_MILES_VERSION:
if (param == 0xFFFF)
return _milesVersion;
switch (param) {
case MILES_VERSION_3:
_milesVersion = MILES_VERSION_3;
break;
case MILES_VERSION_2:
default:
_milesVersion = MILES_VERSION_2;
}
break;
default:
return MidiDriver_Multisource::property(prop, param);
}
return 0;
}
} // End of namespace Audio