scummvm/audio/miles_midi.cpp
2020-07-25 13:20:15 +02:00

1326 lines
44 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 MT-32 / General MIDI driver
//
#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
#define MILES_MT32_SYSEX_TERMINATOR 0xFF
/*
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
};
MidiDriver_Miles_Midi::MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) :
_driver(NULL),
_isOpen(false),
_nativeMT32(false),
_enableGS(false),
_outputChannelMask(65535), // Channels 1-16
_baseFreq(250),
_timerRate(0),
_noteCounter(0),
_sysExDelay(0),
_timer_param(0),
_timer_proc(0) {
switch (midiType) {
case MT_MT32:
_midiType = MT_MT32;
break;
case MT_GM:
case MT_GS: // Treat GS same as GM
_midiType = MT_GM;
break;
default:
assert(false);
break;
}
memset(_gsBank, 0, sizeof(_gsBank));
memset(_patchesBank, 0, sizeof(_patchesBank));
_instrumentTablePtr = instrumentTablePtr;
_instrumentTableCount = instrumentTableCount;
for (int i = 0; i < MILES_MAXIMUM_SOURCES; ++i) {
// Default MIDI channel mapping: data channel == output channel
for (int j = 0; j < MILES_MIDI_CHANNEL_COUNT; ++j) {
_sources[i].channelMap[j] = j;
}
}
_maximumActiveNotes = _midiType == MT_MT32 ? MILES_MT32_ACTIVE_NOTES : MILES_GM_ACTIVE_NOTES;
_activeNotes = new ActiveNote[_maximumActiveNotes];
assert(_activeNotes);
}
MidiDriver_Miles_Midi::~MidiDriver_Miles_Midi() {
Common::StackLock lock(_mutex);
if (_driver) {
_driver->setTimerCallback(0, 0);
_driver->close();
delete _driver;
}
_driver = NULL;
if (_activeNotes)
delete[] _activeNotes;
}
int MidiDriver_Miles_Midi::open() {
assert(!_driver);
// Setup midi driver
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | (_midiType == MT_MT32 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
MusicType deviceMusicType = MidiDriver::getMusicType(dev);
if (!(deviceMusicType == MT_MT32 || deviceMusicType == MT_GM || deviceMusicType == MT_GS))
error("MILES-MIDI: detected music device uses unsupported music type %i", deviceMusicType);
MidiDriver *driver = MidiDriver::createMidi(dev);
bool nativeMT32 = deviceMusicType == MT_MT32 || ConfMan.getBool("native_mt32");
return open(driver, nativeMT32);
}
int MidiDriver_Miles_Midi::open(MidiDriver *driver, bool nativeMT32) {
assert(!_driver);
_driver = driver;
_nativeMT32 = nativeMT32;
_enableGS = ConfMan.getBool("enable_gs");
if (!_driver)
return 255;
if (_nativeMT32)
_outputChannelMask = _midiType == MT_MT32 ? 1022 : 767; // Channels 2-10 / 1-8 and 10
_driver->property(MidiDriver::PROP_CHANNEL_MASK, _outputChannelMask);
int ret = _driver->open();
if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0)
return ret;
_timerRate = _driver->getBaseTempo();
_driver->setTimerCallback(this, timerCallback);
initMidiDevice();
return 0;
}
void MidiDriver_Miles_Midi::close() {
if (_driver) {
_driver->close();
}
}
void MidiDriver_Miles_Midi::initMidiDevice() {
if (_nativeMT32) {
bool initForGM = _midiType != MT_MT32;
// reset all internal parameters / patches
initMT32(initForGM);
if (!initForGM) {
// init part/channel assignments
MT32SysEx(0x10000D, milesMT32SysExChansSetup);
// partial reserve table
MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
// init reverb
MT32SysEx(0x100001, milesMT32SysExInitReverb);
}
} else {
initGM(_midiType == MT_MT32, _enableGS);
}
// Set Miles default controller values
// 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, 0xB0 | i, MILES_CONTROLLER_VOLUME, 0x7F);
if (_midiType == MT_MT32) {
// Panning center - not the MT-32 default for all channels
send(-1, 0xB0 | i, MILES_CONTROLLER_PANNING, 0x40);
}
// Patch
if (i != MILES_RHYTHM_CHANNEL) {
if (_midiType == MT_MT32) {
// These are the default on the MT-32; just set them on the control data
_midiChannels[i].currentData.program = _mt32DefaultInstruments[i - 1];
} else {
// Send the instruments out to GM devices.
send(-1, 0xC0 | i, _mt32DefaultInstruments[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.
}
}
void MidiDriver_Miles_Midi::sysEx(const byte *msg, uint16 length) {
uint16 delay = sysExNoDelay(msg, length);
if (delay > 0)
g_system->delayMillis(delay);
}
uint16 MidiDriver_Miles_Midi::sysExNoDelay(const byte *msg, uint16 length) {
if (!_nativeMT32 && length >= 3 && msg[0] == 0x41 && msg[2] == 0x16)
// MT-32 SysExes have no effect on GM devices.
return 0;
// Send SysEx
_driver->sysEx(msg, length);
// Wait the time it takes to send the SysEx data
uint16 delay = (length + 2) * 1000 / 3125;
// Plus an additional delay for the MT-32 rev00
if (_nativeMT32)
delay += 40;
return delay;
}
void MidiDriver_Miles_Midi::MT32SysEx(const uint32 targetAddress, const byte *dataPtr, bool useSysExQueue) {
if (!_nativeMT32)
// MT-32 SysExes have no effect on GM devices.
return;
byte sysExMessage[270];
uint16 sysExPos = 0;
byte sysExByte;
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;
if (useSysExQueue) {
SysExData sysEx;
memcpy(sysEx.data, sysExMessage, sysExPos);
sysEx.length = sysExPos;
_sysExQueueMutex.lock();
_sysExQueue.push(sysEx);
_sysExQueueMutex.unlock();
} else {
sysEx(sysExMessage, sysExPos);
}
}
void MidiDriver_Miles_Midi::metaEvent(int8 source, byte type, byte *data, uint16 length) {
assert(source < MILES_MAXIMUM_SOURCES);
if (type == 0x2F && source >= 0) // End of Track
deinitSource(source);
_driver->metaEvent(type, data, length);
}
void MidiDriver_Miles_Midi::send(uint32 b) {
send(-1, b);
}
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
assert(source < MILES_MAXIMUM_SOURCES);
byte command = b & 0xf0;
byte dataChannel = b & 0xf;
byte outputChannel = source < 0 ? dataChannel : _sources[source].channelMap[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 sendMessage = source < 0 || !outputChannelEntry.locked ||
(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.
MidiChannelControlData &controlData = sendMessage ? outputChannelEntry.currentData : outputChannelEntry.unlockData;
byte op1 = (b >> 8) & 0xff;
byte op2 = (b >> 16) & 0xff;
if (command != 0xF0 && controlData.source != source) {
// A new source has sent an event on this channel.
controlData.sourceVolumeApplied = false;
controlData.source = source;
}
switch (command) {
case 0x80: // Note Off
case 0x90: // Note On
if (sendMessage) {
// Note On with velocity 0 is treated as Note Off
bool addNote = command == 0x90 && op2 != 0;
if (addNote) {
if (source >= 0 && !controlData.sourceVolumeApplied)
// Source volume hasn't been applied yet. Do so now.
controlChange(outputChannel, MILES_CONTROLLER_VOLUME, controlData.volume, source, controlData, sendMessage);
// Add the new note to the active note registration
for (int i = 0; i < _maximumActiveNotes; ++i) {
ActiveNote &activeNote = _activeNotes[i];
if (activeNote.channel == 0xFF) {
// Add the new note.
activeNote.source = source;
activeNote.channel = outputChannel;
activeNote.note = op1;
activeNote.sustain = false;
++outputChannelEntry.activeNotes;
break;
}
}
} else {
// Remove the note from the active note registration
for (int i = 0; i < _maximumActiveNotes; ++i) {
ActiveNote &activeNote = _activeNotes[i];
if (activeNote.channel == outputChannel && activeNote.source == source && activeNote.note == op1) {
if (controlData.sustain) {
// Sustain is on, so the note should be turned off
// when sustain is turned off.
activeNote.sustain = true;
} else {
// Turn off the existing note.
activeNote.source = 0x7F;
activeNote.channel = 0xFF;
if (outputChannelEntry.activeNotes == 0) {
warning("MILES-MIDI: active notes 0 on channel %d when turning off note %x", op1, outputChannel);
} else {
--outputChannelEntry.activeNotes;
}
}
break;
}
}
}
}
// fall through
case 0xa0: // Polyphonic key pressure (aftertouch) (not supported by MT-32 or GM)
case 0xd0: // Channel pressure (aftertouch) (not supported by MT-32)
case 0xe0: // pitch bend change
if (command == 0xe0)
controlData.pitchWheel = ((uint16)op2 << 7) | (uint16)op1;
_noteCounter++;
if (controlData.usingCustomTimbre) {
// Remember that this timbre got used now
_customTimbres[controlData.currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
}
if (sendMessage) {
_driver->send(command | outputChannel, op1, op2);
}
break;
case 0xb0: // Control change
controlChange(outputChannel, op1, op2, source, controlData, sendMessage);
break;
case 0xc0: // Program Change
programChange(outputChannel, op1, source, controlData, sendMessage);
break;
case 0xf0: // SysEx
warning("MILES-MIDI: SysEx: %x", b);
break;
default:
warning("MILES-MIDI: Unknown event %02x", command);
}
}
void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool sendMessage) {
assert(source < MILES_MAXIMUM_SOURCES);
// XMIDI controllers
switch (controllerNumber) {
case MILES_CONTROLLER_SELECT_PATCH_BANK:
controlData.currentPatchBank = controllerValue;
return;
case MILES_CONTROLLER_PROTECT_TIMBRE:
if (controlData.usingCustomTimbre) {
// custom timbre set on current channel
_customTimbres[controlData.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(controlData.program, 6, controllerValue);
if (sendMessage)
_driver->send(0xC0 | outputChannel | (controlData.program << 8)); // execute program change
return;
case MILES_CONTROLLER_PATCH_BENDER:
writePatchByte(controlData.program, 4, controllerValue);
if (sendMessage)
_driver->send(0xC0 | outputChannel | (controlData.program << 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 (controlData.usingCustomTimbre) {
// custom timbre is set on current channel
writeRhythmSetup(controllerValue, controlData.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 &= 0x00FFFF;
_milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
_milesSysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
_milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
_milesSysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
_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
_milesSysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
// Execute SysEx
MT32SysEx(_milesSysExQueues[sysExQueueNr].targetAddress, _milesSysExQueues[sysExQueueNr].data);
// Adjust target address to point at the final data byte, or at the
// end of the current data in case of an overflow
// Note that the address bytes are actually 7 bits
byte addressByte1 = (_milesSysExQueues[sysExQueueNr].targetAddress & 0xFF0000) >> 16;
byte addressByte2 = (_milesSysExQueues[sysExQueueNr].targetAddress & 0x00FF00) >> 8;
byte addressByte3 = _milesSysExQueues[sysExQueueNr].targetAddress & 0x0000FF;
addressByte3 += _milesSysExQueues[sysExQueueNr].dataPos;
if (addressByte3 > 0x7F) {
addressByte3 -= 0x80;
addressByte2++;
}
if (addressByte2 > 0x7F) {
addressByte2 -= 0x80;
addressByte1++;
}
_milesSysExQueues[sysExQueueNr].targetAddress = addressByte1 << 16 | addressByte2 << 8 | addressByte3;
// 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;
}
// Standard MIDI controllers
switch (controllerNumber) {
case MILES_CONTROLLER_BANK_SELECT_MSB:
// Keep track of the current bank for each channel
_gsBank[outputChannel] = controllerValue;
break;
case MILES_CONTROLLER_MODULATION:
controlData.modulation = controllerValue;
break;
case MILES_CONTROLLER_VOLUME:
controlData.volume = controllerValue;
controlData.sourceVolumeApplied = true;
if (source >= 0) {
// Scale to source volume
controllerValue = (_sources[source].volume * controllerValue) >> 8;
}
if (_scaleGSPercussionVolumeToMT32 && outputChannel == MILES_RHYTHM_CHANNEL) {
// Scale GS percussion channel volume to MT-32 level (80/127)
controllerValue = (80 * controllerValue) >> 7;
}
if (controlData.scaledVolume == controllerValue) {
// Volume is already at this value, so no need to send it out
// to the MIDI device.
return;
}
controlData.scaledVolume = controllerValue;
break;
case MILES_CONTROLLER_PANNING:
if (_reversePanning) {
// Center panning is 0x40
controllerValue = 0x80 - controllerValue;
if (controllerValue > 0x7F)
controllerValue = 0x7F;
}
controlData.panPosition = controllerValue;
break;
case MILES_CONTROLLER_EXPRESSION:
controlData.expression = controllerValue;
break;
case MILES_CONTROLLER_RESET_ALL:
controlData.modulation = 0;
controlData.expression = 0x7F;
controlData.pitchWheel = MILES_PITCHBENDER_DEFAULT;
controlData.sustain = false;
if (sendMessage) {
removeActiveNotes(outputChannel, true);
}
break;
case MILES_CONTROLLER_SUSTAIN:
controlData.sustain = controllerValue >= 0x40;
if (sendMessage && !controlData.sustain) {
removeActiveNotes(outputChannel, true);
}
break;
case MILES_CONTROLLER_OMNI_ON:
case MILES_CONTROLLER_OMNI_OFF:
case MILES_CONTROLLER_MONO_ON:
case MILES_CONTROLLER_POLY_ON:
// These act as an All Notes Off on MT-32, but also turn sustain off.
// They are not part of GM, so should not be used in GM data.
if (_midiType != MT_MT32) {
warning("MILES-MIDI: unsupported GM controller %x", controllerNumber);
return;
}
controlData.sustain = false;
if (sendMessage)
removeActiveNotes(outputChannel, true);
if (!_nativeMT32) {
// MT-32 data on GM device.
// These controllers might not be supported or have side effects
// (changing omni or mono/poly mode). Send All Notes Off and
// Sustain Off instead.
if (sendMessage) {
controllerNumber = MILES_CONTROLLER_ALL_NOTES_OFF;
_driver->send(0xB0 | outputChannel | (MILES_CONTROLLER_SUSTAIN << 8) | (0 << 16));
}
}
// fall through
case MILES_CONTROLLER_ALL_NOTES_OFF:
if (sendMessage) {
removeActiveNotes(outputChannel, false);
}
break;
default:
break;
}
if (sendMessage) {
_driver->send(0xB0 | outputChannel | (controllerNumber << 8) | (controllerValue << 16));
}
}
void MidiDriver_Miles_Midi::removeActiveNotes(uint8 outputChannel, bool sustainedNotes) {
// Remove 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].source = 0x7F;
_activeNotes[i].channel = 0xFF;
if (_midiChannels[outputChannel].activeNotes == 0) {
if (sustainedNotes)
warning("MILES-MIDI: active notes 0 on channel %d when turning off sustained notes", outputChannel);
else
warning("MILES-MIDI: active notes 0 on channel %d when turning all notes off", outputChannel);
continue;
}
--_midiChannels[outputChannel].activeNotes;
}
}
}
void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
assert(source < MILES_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(lockChannel);
_midiChannels[lockChannel].locked = true;
_midiChannels[lockChannel].lockDataChannel = dataChannel;
_sources[source].channelMap[dataChannel] = lockChannel;
// Copy current controller values so they can be restored when unlocking the channel
_midiChannels[lockChannel].unlockData = _midiChannels[lockChannel].currentData;
_midiChannels[lockChannel].currentData.source = source;
// Send volume change to apply the new source volume
controlChange(lockChannel, MILES_CONTROLLER_VOLUME, 0x7F, source, _midiChannels[lockChannel].currentData, true);
// Note that other controller values might be "inherited" from the source
// which was previously playing on the locked MIDI channel. The KYRA engine
// does not seem to take any precautions against this.
// Controllers could be set to default values here.
}
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 = MILES_MIDI_CHANNEL_COUNT - 1; i >= 0; --i) {
if (!isOutputChannelUsed(i) || i == MILES_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;
_sources[channel.currentData.source].channelMap[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, MILES_CONTROLLER_VOLUME, channel.unlockData.volume, channel.currentData.source, channel.currentData, true);
} else {
channel.currentData.volume = 0xFF;
}
if (channel.currentData.modulation != channel.unlockData.modulation)
controlChange(outputChannel, MILES_CONTROLLER_MODULATION, channel.unlockData.modulation, channel.currentData.source, channel.currentData, true);
if (channel.currentData.panPosition != channel.unlockData.panPosition)
controlChange(outputChannel, MILES_CONTROLLER_PANNING, channel.unlockData.panPosition, channel.currentData.source, channel.currentData, true);
if (channel.currentData.expression != channel.unlockData.expression)
controlChange(outputChannel, MILES_CONTROLLER_EXPRESSION, channel.unlockData.expression, channel.currentData.source, channel.currentData, true);
if (channel.currentData.sustain != channel.unlockData.sustain)
controlChange(outputChannel, MILES_CONTROLLER_SUSTAIN, channel.unlockData.sustain ? 0x7F : 0x00, channel.currentData.source, channel.currentData, true);
if (channel.currentData.currentPatchBank != channel.unlockData.currentPatchBank)
controlChange(outputChannel, MILES_CONTROLLER_SELECT_PATCH_BANK, channel.unlockData.currentPatchBank, channel.currentData.source, channel.currentData, true);
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, true);
if (channel.currentData.pitchWheel != channel.unlockData.pitchWheel)
send(channel.currentData.source, 0xE0 | 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, MILES_CONTROLLER_SUSTAIN, 0, channel.currentData.source, channel.currentData, true);
}
if (channel.activeNotes > 0) {
controlChange(outputChannelNumber, MILES_CONTROLLER_ALL_NOTES_OFF, 0, channel.currentData.source, channel.currentData, true);
}
}
void MidiDriver_Miles_Midi::stopAllNotes(bool stopSustainedNotes) {
for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
if (stopSustainedNotes) {
_driver->send(0xB0 | i, MILES_CONTROLLER_SUSTAIN, 0);
_midiChannels[i].currentData.sustain = false;
}
_driver->send(0xB0 | i, MILES_CONTROLLER_ALL_NOTES_OFF, 0);
_midiChannels[i].activeNotes = 0;
}
for (int i = 0; i < _maximumActiveNotes; ++i) {
_activeNotes[i].source = 0x7F;
_activeNotes[i].channel = 0xFF;
}
}
void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, uint8 source, MidiChannelControlData &controlData, bool sendMessage) {
// remember patch id for the current MIDI-channel
controlData.program = patchId;
if (_midiType == MT_MT32) {
byte channelPatchBank = controlData.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) {
controlData.usingCustomTimbre = true;
controlData.currentCustomTimbreId = customTimbre;
} else {
controlData.usingCustomTimbre = false;
}
if (outputChannel == MILES_RHYTHM_CHANNEL)
// Patch changes on the rhythm channel are used by AIL to setup custom
// rhythm timbres. There's no need to actually send them to the MT-32,
// because it won't respond to them. On GM/GS devices they might
// unintentionally change the drumkit.
return;
if (!_nativeMT32 && !_enableGS) {
// GM device: map the patch to GM equivalent
// TODO It would be nice if the patch bank could be taken into account
// when using a custom ScummVM game-specific MT-32 to GM mapping.
patchId = _mt32ToGm[patchId];
}
} else {
// GM/GS MIDI
if (outputChannel == MILES_RHYTHM_CHANNEL) {
// Correct possible wrong GS drumkit number
patchId = _gsDrumkitFallbackMap[patchId];
} else if (!_nativeMT32) {
// Correct possible wrong bank / instrument variation
byte correctedBank = correctInstrumentBank(outputChannel, patchId);
if (correctedBank != 0xFF) {
// Send out a bank select for the corrected bank number
controlChange(outputChannel, MILES_CONTROLLER_BANK_SELECT_MSB, correctedBank, source, controlData, sendMessage);
controlChange(outputChannel, MILES_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData, sendMessage);
}
} else {
// GM on an MT-32: map the patch to the MT-32 equivalent
patchId = _gmToMt32[patchId];
}
}
// Finally send program change to MIDI device
if (sendMessage) {
_driver->send(0xC0 | outputChannel | (patchId << 8));
}
}
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 NULL;
}
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 = NULL;
// 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 = 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, true);
// upload partial parameter data
MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0], true);
MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1], true);
MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2], true);
MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3], true);
setupPatch(patchBank, patchId, true);
return customTimbreId;
}
uint32 MidiDriver_Miles_Midi::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_Midi::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_Midi::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId, bool useSysExQueue) {
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, useSysExQueue);
}
void MidiDriver_Miles_Midi::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_Midi::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_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) { return MidiDriver_Miles_MIDI_create(MT_MT32, instrumentDataFilename); }
MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::String &instrumentDataFilename) {
assert(midiType == MT_MT32 || midiType == MT_GM || midiType == MT_GS);
MilesMT32InstrumentEntry *instrumentTablePtr = NULL;
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 = NULL;
uint32 fileDataOffset = 0;
uint32 fileDataLeft = 0;
byte curBankId;
byte curPatchId;
MilesMT32InstrumentEntry *instrumentPtr = NULL;
uint32 instrumentOffset;
uint16 instrumentDataSize;
if (!fileStream->open(instrumentDataFilename))
error("MILES-MIDI: could not open instrument file '%s'", instrumentDataFilename.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);
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_Midi(midiType, instrumentTablePtr, instrumentTableCount);
}
void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
assert(source < MILES_MAXIMUM_SOURCES);
// Unlock and unprotect channels which were locked or protected by this source.
for (int i = 0; i < MILES_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].currentData.source == source)
_midiChannels[i].currentData.source = -1;
if (_midiChannels[i].unlockData.source == source)
_midiChannels[i].unlockData.source = -1;
}
// Reset the data to output channel mapping
for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
_sources[source].channelMap[i] = i;
}
// Stop any active notes.
for (int i = 0; i < _maximumActiveNotes; ++i) {
if (_activeNotes[i].source == source) {
if (_activeNotes[i].sustain) {
// Turn off sustain
controlChange(_activeNotes[i].channel, MILES_CONTROLLER_SUSTAIN, 0x00, source, _midiChannels[_activeNotes[i].channel].currentData, true);
} else {
// Send note off
send(source, 0x80 | _activeNotes[i].channel, _activeNotes[i].note, 0x00);
}
}
}
}
void MidiDriver_Miles_Midi::setSourceVolume(uint8 source, uint16 volume) {
assert(source < MILES_MAXIMUM_SOURCES);
_sources[source].volume = volume;
for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
MidiChannelEntry &channel = _midiChannels[i];
MidiChannelControlData *channelData = 0;
bool sendMessage = 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 (channel.currentData.source == source) {
channelData = &channel.currentData;
sendMessage = true;
} else if (channel.locked && channel.unlockData.source == source) {
channelData = &channel.unlockData;
}
if (channelData && channelData->volume != 0xFF)
controlChange(i, MILES_CONTROLLER_VOLUME, channelData->volume, source, *channelData, sendMessage);
}
}
void MidiDriver_Miles_Midi::onTimer() {
Common::StackLock lock(_sysExQueueMutex);
_sysExDelay -= (_sysExDelay > _timerRate) ? _timerRate : _sysExDelay;
if (!_sysExQueue.empty() && _sysExDelay == 0) {
// Ready to send next SysEx message to the MIDI device
SysExData sysEx = _sysExQueue.pop();
_sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
}
}
} // End of namespace Audio