mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-13 12:39:56 +00:00
1181 lines
39 KiB
C++
1181 lines
39 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/>.
|
|
*
|
|
*/
|
|
|
|
#ifndef AUDIO_MIDI_H
|
|
#define AUDIO_MIDI_H
|
|
|
|
#include "audio/mt32gm.h"
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/debug.h"
|
|
|
|
// The initialization of the static const integral data members is done in the class definition,
|
|
// but we still need to provide a definition if they are odr-used.
|
|
const uint8 MidiDriver_MT32GM::MT32_DEFAULT_CHANNEL_VOLUME;
|
|
const uint8 MidiDriver_MT32GM::GM_DEFAULT_CHANNEL_VOLUME;
|
|
const uint8 MidiDriver_MT32GM::MAXIMUM_MT32_ACTIVE_NOTES;
|
|
const uint8 MidiDriver_MT32GM::MAXIMUM_GM_ACTIVE_NOTES;
|
|
|
|
// These are the power-on default instruments of the Roland MT-32 family.
|
|
const byte MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS[8] = {
|
|
0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A
|
|
};
|
|
|
|
// These are the power-on default panning settings for channels 2-9 of the Roland MT-32 family.
|
|
// Internally, the MT-32 has 15 panning positions (0-E with 7 being center).
|
|
// This has been translated to the equivalent MIDI panning values (0-127).
|
|
// These are used for setting default panning on GM devices when using them with MT-32 data.
|
|
// Note that MT-32 panning is reversed compared to the MIDI specification. This is not reflected
|
|
// here; the driver is expected to flip these values based on the _midiDeviceReversePanning
|
|
// variable.
|
|
const byte MidiDriver_MT32GM::MT32_DEFAULT_PANNING[8] = {
|
|
// 7, 8, 7, 8, 4, A, 0, E
|
|
0x40, 0x49, 0x40, 0x49, 0x25, 0x5B, 0x00, 0x7F
|
|
};
|
|
|
|
// This is the drum map for the Roland Sound Canvas SC-55 v1.xx. It had a fallback mechanism
|
|
// to correct invalid drumkit selections. Some games rely on this mechanism to select the
|
|
// correct Roland GS drumkit. Use this map to emulate this mechanism.
|
|
// E.g. correct invalid drumkit 50: GS_DRUMKIT_FALLBACK_MAP[50] == 48
|
|
const uint8 MidiDriver_MT32GM::GS_DRUMKIT_FALLBACK_MAP[128] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, // STANDARD
|
|
8, 8, 8, 8, 8, 8, 8, 8, // ROOM
|
|
16, 16, 16, 16, 16, 16, 16, 16, // POWER
|
|
24, 25, 24, 24, 24, 24, 24, 24, // ELECTRONIC; TR-808 (25)
|
|
32, 32, 32, 32, 32, 32, 32, 32, // JAZZ
|
|
40, 40, 40, 40, 40, 40, 40, 40, // BRUSH
|
|
48, 48, 48, 48, 48, 48, 48, 48, // ORCHESTRA
|
|
56, 56, 56, 56, 56, 56, 56, 56, // SFX
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined (fall back to STANDARD)
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
|
|
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
|
|
0, 0, 0, 0, 0, 0, 0, 127 // No drumkit defined; CM-64/32L (127)
|
|
};
|
|
|
|
const uint8 MidiDriver_MT32GM::MT32_DISPLAY_NUM_CHARS;
|
|
const uint32 MidiDriver_MT32GM::MT32_DISPLAY_MEMORY_ADDRESS;
|
|
|
|
// Callback hooked up to the driver wrapped by the MIDI driver
|
|
// object. Executes onTimer and the external callback set by
|
|
// the setTimerCallback function.
|
|
void MidiDriver_MT32GM::timerCallback(void *data) {
|
|
MidiDriver_MT32GM *driver = (MidiDriver_MT32GM *)data;
|
|
driver->onTimer();
|
|
}
|
|
|
|
MidiDriver_MT32GM::MidiDriver_MT32GM(MusicType midiType) :
|
|
_driver(nullptr),
|
|
_nativeMT32(false),
|
|
_enableGS(false),
|
|
_midiDataReversePanning(false),
|
|
_midiDeviceReversePanning(false),
|
|
_scaleGSPercussionVolumeToMT32(false),
|
|
_isOpen(false),
|
|
_outputChannelMask(65535), // Channels 1-16
|
|
_baseFreq(250),
|
|
_sysExDelay(0) {
|
|
memset(_controlData, 0, sizeof(_controlData));
|
|
|
|
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:
|
|
error("MidiDriver_MT32GM: Unsupported music type %i", midiType);
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
|
|
_availableChannels[i] = 0;
|
|
// Default MIDI channel mapping: data channel == output channel
|
|
for (int j = 0; j < MIDI_CHANNEL_COUNT; ++j) {
|
|
_channelMap[i][j] = j;
|
|
}
|
|
}
|
|
|
|
// Default MT-32 <> GM instrument mappings.
|
|
_mt32ToGMInstrumentMap = _mt32ToGm;
|
|
_gmToMT32InstrumentMap = _gmToMt32;
|
|
|
|
_maximumActiveNotes = _midiType == MT_MT32 ? MAXIMUM_MT32_ACTIVE_NOTES : MAXIMUM_GM_ACTIVE_NOTES;
|
|
_activeNotes = new ActiveNote[_maximumActiveNotes];
|
|
assert(_activeNotes);
|
|
}
|
|
|
|
MidiDriver_MT32GM::~MidiDriver_MT32GM() {
|
|
if (_driver) {
|
|
_driver->setTimerCallback(nullptr, nullptr);
|
|
_driver->close();
|
|
delete _driver;
|
|
}
|
|
_driver = nullptr;
|
|
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
delete _controlData[i];
|
|
}
|
|
if (_activeNotes)
|
|
delete[] _activeNotes;
|
|
}
|
|
|
|
int MidiDriver_MT32GM::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("MidiDriver_MT32GM: 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_MT32GM::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);
|
|
|
|
initControlData();
|
|
initMidiDevice();
|
|
syncSoundSettings();
|
|
|
|
_isOpen = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MidiDriver_MT32GM::initControlData() {
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
_controlData[i] = new MidiChannelControlData();
|
|
_controlData[i]->volume = _controlData[i]->scaledVolume =
|
|
(_nativeMT32 ? MT32_DEFAULT_CHANNEL_VOLUME : GM_DEFAULT_CHANNEL_VOLUME);
|
|
_controlData[i]->pitchBendSensitivity =
|
|
(_nativeMT32 ? MT32_PITCH_BEND_SENSITIVITY_DEFAULT : GM_PITCH_BEND_SENSITIVITY_DEFAULT);
|
|
if (_nativeMT32 && i >= 1 && i <= 8) {
|
|
_controlData[i]->program = MT32_DEFAULT_INSTRUMENTS[i - 1];
|
|
_controlData[i]->panPosition = MT32_DEFAULT_PANNING[i - 1];
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::initMidiDevice() {
|
|
if (_nativeMT32) {
|
|
initMT32(_midiType != MT_MT32);
|
|
} else {
|
|
initGM(_midiType == MT_MT32, _enableGS);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::initMT32(bool initForGM) {
|
|
sendMT32Reset();
|
|
|
|
if (initForGM) {
|
|
// Set up MT-32 for GM data.
|
|
// This is based on Roland's GM settings for MT-32.
|
|
debug("Initializing MT-32 for General MIDI data");
|
|
|
|
byte buffer[17];
|
|
|
|
// Roland MT-32 SysEx for system area
|
|
memcpy(&buffer[0], "\x41\x10\x16\x12\x10\x00", 6);
|
|
|
|
// Set reverb parameters:
|
|
// - Mode 2 (Plate)
|
|
// - Time 3
|
|
// - Level 4
|
|
memcpy(&buffer[6], "\x01\x02\x03\x04\x66", 5);
|
|
sysEx(buffer, 11);
|
|
|
|
// Set partial reserve to match SC-55
|
|
memcpy(&buffer[6], "\x04\x08\x04\x04\x03\x03\x03\x03\x02\x02\x4C", 11);
|
|
sysEx(buffer, 17);
|
|
|
|
// Use MIDI instrument channels 1-8 instead of 2-9
|
|
memcpy(&buffer[6], "\x0D\x00\x01\x02\x03\x04\x05\x06\x07\x09\x3E", 11);
|
|
sysEx(buffer, 17);
|
|
|
|
// The MT-32 has reversed stereo panning compared to the MIDI spec.
|
|
// GM does use panning as specified by the MIDI spec.
|
|
_midiDeviceReversePanning = true;
|
|
|
|
int i;
|
|
|
|
// Set default GM panning (center on all channels)
|
|
for (i = 0; i < 8; ++i) {
|
|
send((0x40 << 16) | (MIDI_CONTROLLER_PANNING << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
|
|
}
|
|
|
|
// Set default GM instruments (0 on all channels).
|
|
// This is expected to be mapped to the MT-32 equivalent by the driver.
|
|
for (i = 0; i < 8; ++i) {
|
|
send((0 << 8) | (MIDI_COMMAND_PROGRAM_CHANGE | i));
|
|
}
|
|
|
|
// Set Pitch Bend Sensitivity to 2 semitones.
|
|
for (i = 0; i < 8; ++i) {
|
|
setPitchBendRange(i, 2);
|
|
}
|
|
setPitchBendRange(MIDI_RHYTHM_CHANNEL, 2);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::initGM(bool initForMT32, bool enableGS) {
|
|
sendGMReset();
|
|
|
|
if (initForMT32) {
|
|
// Set up the GM device for MT-32 MIDI data.
|
|
// Based on iMuse implementation (which is based on Roland's MT-32 settings for GS)
|
|
debug("Initializing GM device for MT-32 MIDI data");
|
|
|
|
// The MT-32 has reversed stereo panning compared to the MIDI spec.
|
|
// GM does use panning as specified by the MIDI spec.
|
|
_midiDeviceReversePanning = true;
|
|
|
|
int i;
|
|
|
|
// Set the default panning for the MT-32 instrument channels.
|
|
for (i = 1; i < 9; ++i) {
|
|
send((MT32_DEFAULT_PANNING[i - 1] << 16) | (MIDI_CONTROLLER_PANNING << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
|
|
}
|
|
|
|
// Set Channels 1-16 Reverb to 64, which is the
|
|
// equivalent of MT-32 default Reverb Level 5
|
|
for (i = 0; i < 16; ++i)
|
|
send((64 << 16) | (MIDI_CONTROLLER_REVERB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
|
|
|
|
// Set Channels 1-16 Chorus to 0. The MT-32 has no chorus capability.
|
|
// (This is probably the default for many GM devices with chorus anyway.)
|
|
for (i = 0; i < 16; ++i)
|
|
send((0 << 16) | (MIDI_CONTROLLER_CHORUS << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
|
|
|
|
// Set Channels 1-16 Pitch Bend Sensitivity to 12 semitones.
|
|
for (i = 0; i < 16; ++i) {
|
|
setPitchBendRange(i, 12);
|
|
}
|
|
|
|
if (enableGS) {
|
|
// GS specific settings for MT-32 instrument mapping.
|
|
debug("Additional initialization of GS device for MT-32 MIDI data");
|
|
|
|
// Note: All Roland GS devices support CM-64/32L maps
|
|
|
|
if (getPercussionChannel() != nullptr) {
|
|
// Set Percussion Channel to SC-55 Map (CC#32, 01H), then
|
|
// Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
|
|
// Bank select MSB: bank 0
|
|
getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
|
|
// Bank select LSB: map 1 (SC-55)
|
|
getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_LSB, 1);
|
|
}
|
|
// Patch change: 127 (CM-64/32L)
|
|
send((127 << 8) | MIDI_COMMAND_PROGRAM_CHANGE | 9);
|
|
|
|
// Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
|
|
for (i = 0; i < 16; ++i) {
|
|
if (getPercussionChannel() != nullptr && i == getPercussionChannel()->getNumber())
|
|
continue;
|
|
// Bank select MSB: bank 127 (CM-64/32L)
|
|
send((127 << 16) | (MIDI_CONTROLLER_BANK_SELECT_MSB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
|
|
// Bank select LSB: map 1 (SC-55)
|
|
send((1 << 16) | (MIDI_CONTROLLER_BANK_SELECT_LSB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
|
|
// Patch change: 0 (causes bank select to take effect)
|
|
send((0 << 16) | (0 << 8) | (MIDI_COMMAND_PROGRAM_CHANGE | i));
|
|
}
|
|
|
|
byte buffer[12];
|
|
|
|
// Roland GS SysEx ID
|
|
memcpy(&buffer[0], "\x41\x10\x42\x12", 4);
|
|
|
|
// Set channels 1-16 Mod. LFO1 Pitch Depth to 4
|
|
memcpy(&buffer[4], "\x40\x20\x04\x04\x18", 5);
|
|
for (i = 0; i < 16; ++i) {
|
|
buffer[5] = 0x20 + i;
|
|
buffer[8] = 0x18 - i;
|
|
sysEx(buffer, 9);
|
|
}
|
|
|
|
// In Roland's GS MT-32 emulation settings, percussion channel expression
|
|
// is locked at 80. This corrects a difference in volume of the SC-55 MT-32
|
|
// drum kit vs the drums of the MT-32. However, this approach has a problem:
|
|
// the MT-32 supports expression on the percussion channel, so MIDI data
|
|
// which uses this will play incorrectly. So instead, percussion channel
|
|
// volume will be scaled by the driver by a factor 80/127.
|
|
// Strangely, the regular GM drum kit does have a volume that matches the
|
|
// MT-32 drums, so scaling is only necessary when using GS MT-32 emulation.
|
|
_scaleGSPercussionVolumeToMT32 = true;
|
|
|
|
// Change Reverb settings (as used by Roland):
|
|
// - Character: 0
|
|
// - Pre-LPF: 4
|
|
// - Level: 35h
|
|
// - Time: 6Ah
|
|
memcpy(&buffer[4], "\x40\x01\x31\x00\x04\x35\x6A\x6B", 8);
|
|
sysEx(buffer, 12);
|
|
}
|
|
|
|
// Set the default MT-32 patches. For non-GS devices these are expected to be
|
|
// mapped to the GM equivalents by the driver.
|
|
for (i = 1; i < 9; ++i) {
|
|
send((MT32_DEFAULT_INSTRUMENTS[i - 1] << 8) | (MIDI_COMMAND_PROGRAM_CHANGE | i));
|
|
}
|
|
|
|
// Regarding Master Tune: 442 kHz was intended for the MT-32 family, but
|
|
// apparently due to a firmware bug the master tune was actually 440 kHz for
|
|
// all models (see MUNT source code for more details). So master tune is left
|
|
// at 440 kHz for GM devices playing MT-32 MIDI data.
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::close() {
|
|
if (_driver) {
|
|
_driver->close();
|
|
}
|
|
}
|
|
|
|
bool MidiDriver_MT32GM::isReady(int8 source) {
|
|
Common::StackLock lock(_sysExQueueMutex);
|
|
|
|
// For an unspecified source, just return if the queue is empty or not.
|
|
if (source < 0)
|
|
return _sysExQueue.empty();
|
|
|
|
// For a specific source, check if there is a SysEx for that source in the
|
|
// queue.
|
|
for (Common::ListInternal::Iterator<SysExData> it = _sysExQueue.begin();
|
|
it != _sysExQueue.end(); it++) {
|
|
if (it->source == source)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32 MidiDriver_MT32GM::property(int prop, uint32 param) {
|
|
switch (prop) {
|
|
case PROP_MIDI_DATA_REVERSE_PANNING:
|
|
if (param == 0xFFFF)
|
|
return _midiDataReversePanning ? 1 : 0;
|
|
_midiDataReversePanning = param > 0;
|
|
break;
|
|
default:
|
|
return MidiDriver_Multisource::property(prop, param);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void MidiDriver_MT32GM::send(uint32 b) {
|
|
send(-1, b);
|
|
}
|
|
|
|
void MidiDriver_MT32GM::send(int8 source, uint32 b) {
|
|
byte dataChannel = b & 0xf;
|
|
int8 outputChannel = source < 0 ? dataChannel : mapSourceChannel(source, dataChannel);
|
|
|
|
MidiChannelControlData &controlData = *_controlData[outputChannel];
|
|
|
|
processEvent(source, b, outputChannel, controlData);
|
|
}
|
|
|
|
// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
|
|
void MidiDriver_MT32GM::processEvent(int8 source, uint32 b, uint8 outputChannel, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
|
|
assert(source < MAXIMUM_SOURCES);
|
|
|
|
byte command = b & 0xf0;
|
|
byte op1 = (b >> 8) & 0xff;
|
|
byte op2 = (b >> 16) & 0xff;
|
|
|
|
if (command != MIDI_COMMAND_SYSTEM && controlData.source != source) {
|
|
// A new source has sent an event on this channel.
|
|
controlData.sourceVolumeApplied = false;
|
|
controlData.source = source;
|
|
|
|
if (source >= 0)
|
|
// If the new source is a real MIDI source, apply controller
|
|
// default values.
|
|
applyControllerDefaults(source, controlData, outputChannel, channelLockedByOtherSource);
|
|
}
|
|
|
|
switch (command) {
|
|
case MIDI_COMMAND_NOTE_OFF:
|
|
case MIDI_COMMAND_NOTE_ON:
|
|
if (!channelLockedByOtherSource)
|
|
noteOnOff(outputChannel, command, op1, op2, source, controlData);
|
|
break;
|
|
case MIDI_COMMAND_PITCH_BEND:
|
|
pitchBend(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
|
|
break;
|
|
case MIDI_COMMAND_POLYPHONIC_AFTERTOUCH: // Not supported by MT-32 or GM
|
|
polyAftertouch(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
|
|
break;
|
|
case MIDI_COMMAND_CHANNEL_AFTERTOUCH: // Not supported by MT-32
|
|
channelAftertouch(outputChannel, op1, source, controlData, channelLockedByOtherSource);
|
|
break;
|
|
case MIDI_COMMAND_CONTROL_CHANGE:
|
|
controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
|
|
break;
|
|
case MIDI_COMMAND_PROGRAM_CHANGE:
|
|
programChange(outputChannel, op1, source, controlData, channelLockedByOtherSource);
|
|
break;
|
|
case MIDI_COMMAND_SYSTEM:
|
|
// The only supported system event is SysEx and that should be sent using the sysEx functions.
|
|
warning("MidiDriver_MT32GM: send received system event (not processed): %x", b);
|
|
break;
|
|
default:
|
|
warning("MidiDriver_MT32GM: Received unknown event %02x", command);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControlData &controlData, uint8 outputChannel, bool channelLockedByOtherSource) {
|
|
if (outputChannel != MIDI_RHYTHM_CHANNEL) {
|
|
// Apply default bank and program only to melodic channels.
|
|
if (_controllerDefaults.instrumentBank >= 0 && controlData.instrumentBank != _controllerDefaults.instrumentBank) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, _controllerDefaults.instrumentBank, source, controlData);
|
|
controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData);
|
|
}
|
|
if (_controllerDefaults.program[outputChannel] >= 0 && controlData.program != _controllerDefaults.program[outputChannel]) {
|
|
programChange(outputChannel, _controllerDefaults.program[outputChannel], source, controlData);
|
|
}
|
|
} else {
|
|
// Apply default drumkit only to the rhythm channel.
|
|
if (_controllerDefaults.drumkit >= 0 && controlData.program != _controllerDefaults.drumkit) {
|
|
programChange(outputChannel, _controllerDefaults.drumkit, source, controlData);
|
|
}
|
|
}
|
|
if (_controllerDefaults.channelPressure >= 0 && controlData.channelPressure != _controllerDefaults.channelPressure) {
|
|
channelAftertouch(outputChannel, _controllerDefaults.channelPressure, source, controlData);
|
|
}
|
|
if (_controllerDefaults.pitchBend >= 0 && controlData.pitchWheel != _controllerDefaults.pitchBend) {
|
|
pitchBend(outputChannel, _controllerDefaults.pitchBend & 0x7F, _controllerDefaults.pitchBend >> 7, source, controlData);
|
|
}
|
|
|
|
if (_controllerDefaults.modulation >= 0 && controlData.modulation != _controllerDefaults.modulation) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_MODULATION, _controllerDefaults.modulation, source, controlData);
|
|
}
|
|
if (_controllerDefaults.volume >= 0 && controlData.volume != _controllerDefaults.volume) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, _controllerDefaults.volume, source, controlData);
|
|
}
|
|
if (_controllerDefaults.panning >= 0 && controlData.panPosition != _controllerDefaults.panning) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_PANNING, _controllerDefaults.panning, source, controlData);
|
|
}
|
|
if (_controllerDefaults.expression >= 0 && controlData.expression != _controllerDefaults.expression) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_EXPRESSION, _controllerDefaults.expression, source, controlData);
|
|
}
|
|
|
|
// RPN will be changed by setting pitch bend sensitivity, so store the
|
|
// current value.
|
|
uint16 rpn = controlData.rpn;
|
|
bool setRpn = false;
|
|
if (_controllerDefaults.pitchBendSensitivity >= 0 && controlData.pitchBendSensitivity != _controllerDefaults.pitchBendSensitivity) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8, source, controlData);
|
|
controlChange(outputChannel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF, source, controlData);
|
|
controlChange(outputChannel, MIDI_CONTROLLER_DATA_ENTRY_MSB, _controllerDefaults.pitchBendSensitivity, source, controlData);
|
|
controlChange(outputChannel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0, source, controlData);
|
|
if (rpn != controlData.rpn)
|
|
// Active RPN was changed; reset it to previous value (or default).
|
|
setRpn = true;
|
|
}
|
|
if (_controllerDefaults.rpn >= 0 && rpn != _controllerDefaults.rpn) {
|
|
// Set RPN to the specified default value.
|
|
rpn = _controllerDefaults.rpn;
|
|
setRpn = true;
|
|
}
|
|
|
|
if (setRpn) {
|
|
controlChange(outputChannel, MIDI_CONTROLLER_RPN_MSB, rpn >> 8, source, controlData);
|
|
controlChange(outputChannel, MIDI_CONTROLLER_RPN_LSB, rpn & 0xFF, source, controlData);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::noteOnOff(byte outputChannel, byte command, byte note, byte velocity, int8 source, MidiChannelControlData &controlData) {
|
|
if (!isOutputChannelUsed(outputChannel))
|
|
return;
|
|
|
|
// Note On with velocity 0 is treated as Note Off
|
|
bool addNote = command == MIDI_COMMAND_NOTE_ON && velocity != 0;
|
|
if (addNote) {
|
|
if (source >= 0 && !controlData.sourceVolumeApplied)
|
|
// Source volume hasn't been applied yet. Do so now.
|
|
controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, controlData.volume, source, controlData);
|
|
|
|
// Add the new note to the active note registration
|
|
addActiveNote(outputChannel, note, source);
|
|
} else {
|
|
// Remove the note from the active note registration
|
|
removeActiveNote(outputChannel, note, source);
|
|
}
|
|
|
|
_driver->send(command | outputChannel, note, velocity);
|
|
}
|
|
|
|
void MidiDriver_MT32GM::polyAftertouch(byte outputChannel, byte note, byte pressure,
|
|
int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
|
|
if (!channelLockedByOtherSource)
|
|
_driver->send(MIDI_COMMAND_CHANNEL_AFTERTOUCH | outputChannel, note, pressure);
|
|
}
|
|
|
|
void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
|
|
assert(source < MAXIMUM_SOURCES);
|
|
|
|
// Standard MIDI controllers
|
|
switch (controllerNumber) {
|
|
case MIDI_CONTROLLER_BANK_SELECT_MSB:
|
|
// Keep track of the current bank for each channel
|
|
controlData.instrumentBank = controllerValue;
|
|
break;
|
|
case MIDI_CONTROLLER_MODULATION:
|
|
controlData.modulation = controllerValue;
|
|
break;
|
|
case MIDI_CONTROLLER_DATA_ENTRY_MSB:
|
|
if (controlData.rpn == MIDI_RPN_PITCH_BEND_SENSITIVITY)
|
|
controlData.pitchBendSensitivity = controllerValue;
|
|
break;
|
|
case MIDI_CONTROLLER_VOLUME:
|
|
controlData.volume = controllerValue;
|
|
controlData.sourceVolumeApplied = true;
|
|
if (source >= 0) {
|
|
// Scale to source volume
|
|
controllerValue = (controllerValue * _sources[source].volume) / _sources[source].neutralVolume;
|
|
}
|
|
if (_userVolumeScaling) {
|
|
if (_userMute) {
|
|
controllerValue = 0;
|
|
} else {
|
|
// Scale to user volume
|
|
uint16 userVolume = _sources[source].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume; // Treat SOURCE_TYPE_UNDEFINED as music
|
|
controllerValue = (controllerValue * userVolume) >> 8;
|
|
}
|
|
}
|
|
if (_scaleGSPercussionVolumeToMT32 && outputChannel == MIDI_RHYTHM_CHANNEL) {
|
|
// Scale GS percussion channel volume to MT-32 level (80/127)
|
|
controllerValue = (80 * controllerValue) >> 7;
|
|
}
|
|
// Source volume scaling might clip volume, so reduce to maximum
|
|
controllerValue = MIN(controllerValue, (byte)0x7F);
|
|
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 MIDI_CONTROLLER_PANNING:
|
|
if (_midiDeviceReversePanning != _midiDataReversePanning) {
|
|
// Center panning is 0x40
|
|
controllerValue = 0x80 - controllerValue;
|
|
if (controllerValue > 0x7F)
|
|
controllerValue = 0x7F;
|
|
}
|
|
break;
|
|
case MIDI_CONTROLLER_EXPRESSION:
|
|
controlData.expression = controllerValue;
|
|
break;
|
|
case MIDI_CONTROLLER_SUSTAIN:
|
|
controlData.sustain = controllerValue >= 0x40;
|
|
if (!channelLockedByOtherSource && !controlData.sustain) {
|
|
removeActiveNotes(outputChannel, true);
|
|
}
|
|
break;
|
|
case MIDI_CONTROLLER_RPN_LSB:
|
|
controlData.rpn &= 0xFF00;
|
|
controlData.rpn |= controllerValue;
|
|
break;
|
|
case MIDI_CONTROLLER_RPN_MSB:
|
|
controlData.rpn &= 0x00FF;
|
|
controlData.rpn |= controllerValue << 8;
|
|
break;
|
|
case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
|
|
controlData.channelPressure = 0;
|
|
controlData.pitchWheel = MIDI_PITCH_BEND_DEFAULT;
|
|
controlData.modulation = 0;
|
|
controlData.expression = MIDI_EXPRESSION_DEFAULT;
|
|
controlData.sustain = false;
|
|
if (!channelLockedByOtherSource) {
|
|
removeActiveNotes(outputChannel, true);
|
|
}
|
|
controlData.rpn = MIDI_RPN_NULL;
|
|
break;
|
|
case MIDI_CONTROLLER_OMNI_ON:
|
|
case MIDI_CONTROLLER_OMNI_OFF:
|
|
case MIDI_CONTROLLER_MONO_ON:
|
|
case MIDI_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("MidiDriver_MT32GM: unsupported GM controller %x", controllerNumber);
|
|
return;
|
|
}
|
|
|
|
controlData.sustain = false;
|
|
if (!channelLockedByOtherSource)
|
|
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 (!channelLockedByOtherSource) {
|
|
controllerNumber = MIDI_CONTROLLER_ALL_NOTES_OFF;
|
|
_driver->send(MIDI_COMMAND_CONTROL_CHANGE | outputChannel | (MIDI_CONTROLLER_SUSTAIN << 8) | (0 << 16));
|
|
}
|
|
}
|
|
// fall through
|
|
case MIDI_CONTROLLER_ALL_NOTES_OFF:
|
|
if (!channelLockedByOtherSource)
|
|
removeActiveNotes(outputChannel, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!channelLockedByOtherSource)
|
|
_driver->send(MIDI_COMMAND_CONTROL_CHANGE | outputChannel | (controllerNumber << 8) | (controllerValue << 16));
|
|
}
|
|
|
|
bool MidiDriver_MT32GM::addActiveNote(uint8 outputChannel, uint8 note, int8 source) {
|
|
Common::StackLock lock(_activeNotesMutex);
|
|
|
|
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 = note;
|
|
activeNote.sustain = false;
|
|
return true;
|
|
}
|
|
}
|
|
warning("MidiDriver_MT32GM: Could not add active note %x on channel %i", note, outputChannel);
|
|
return false;
|
|
}
|
|
|
|
bool MidiDriver_MT32GM::removeActiveNote(uint8 outputChannel, uint8 note, int8 source) {
|
|
Common::StackLock lock(_activeNotesMutex);
|
|
|
|
for (int i = 0; i < _maximumActiveNotes; ++i) {
|
|
ActiveNote &activeNote = _activeNotes[i];
|
|
if (activeNote.channel == outputChannel && activeNote.source == source && activeNote.note == note) {
|
|
if (_controlData[outputChannel]->sustain) {
|
|
// Sustain is on, so the note should be turned off
|
|
// when sustain is turned off.
|
|
activeNote.sustain = true;
|
|
return false;
|
|
} else {
|
|
// Turn off the existing note.
|
|
activeNote.clear();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
//warning("MidiDriver_MT32GM: Could not find active note %x on channel %i when removing", note, outputChannel);
|
|
return false;
|
|
}
|
|
|
|
void MidiDriver_MT32GM::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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
|
|
// remember patch id for the current MIDI-channel
|
|
controlData.program = patchId;
|
|
|
|
if (channelLockedByOtherSource)
|
|
return;
|
|
|
|
if (_instrumentRemapping && outputChannel != MIDI_RHYTHM_CHANNEL)
|
|
// Apply instrument remapping (if specified) to instrument channels.
|
|
patchId = _instrumentRemapping[patchId];
|
|
|
|
if (_midiType == MT_MT32) {
|
|
if (outputChannel == MIDI_RHYTHM_CHANNEL &&
|
|
!(!_nativeMT32 && _enableGS && patchId == 0x7F)) {
|
|
// Patch changes on the rhythm channel do nothing on an MT-32.
|
|
// On GM/GS devices they might unintentionally change the drumkit.
|
|
// Exception: changing the drumkit to the MT-32 drumkit on a GS
|
|
// device.
|
|
return;
|
|
}
|
|
|
|
if (!_nativeMT32 && !_enableGS) {
|
|
// GM device: map the patch to GM equivalent
|
|
patchId = mapMT32InstrumentToGM(patchId);
|
|
}
|
|
} else {
|
|
// GM/GS MIDI
|
|
if (outputChannel == MIDI_RHYTHM_CHANNEL) {
|
|
// Correct possible wrong GS drumkit number
|
|
patchId = GS_DRUMKIT_FALLBACK_MAP[patchId];
|
|
} else if (!_nativeMT32) {
|
|
// Correct possible wrong bank / instrument variation
|
|
byte correctedBank = correctInstrumentBank(controlData.instrumentBank, patchId);
|
|
if (correctedBank != 0xFF) {
|
|
// Send out a bank select for the corrected bank number
|
|
controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, correctedBank, source, controlData);
|
|
controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData);
|
|
}
|
|
} else {
|
|
// GM on an MT-32: map the patch to the MT-32 equivalent
|
|
patchId = mapGMInstrumentToMT32(patchId);
|
|
}
|
|
}
|
|
|
|
// Finally send program change to MIDI device
|
|
_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (patchId << 8));
|
|
}
|
|
|
|
byte MidiDriver_MT32GM::mapMT32InstrumentToGM(byte mt32Instrument) {
|
|
return _mt32ToGMInstrumentMap[mt32Instrument];
|
|
}
|
|
|
|
byte MidiDriver_MT32GM::mapGMInstrumentToMT32(byte gmInstrument) {
|
|
return _gmToMT32InstrumentMap[gmInstrument];
|
|
}
|
|
|
|
byte MidiDriver_MT32GM::correctInstrumentBank(byte instrumentBank, byte patchId) {
|
|
if (instrumentBank == 0 || patchId >= 120 || instrumentBank >= 64)
|
|
// Usually, no bank select has been sent and no correction is
|
|
// necessary.
|
|
// No correction is performed on banks 64-127 or on the SFX
|
|
// instruments (120-127).
|
|
return 0xFF;
|
|
|
|
// Determine the intended bank. This emulates the behavior of the
|
|
// Roland SC-55 v1.2x. Instruments have 2, 1 or 0 sub-capital tones.
|
|
// Depending on the selected bank and the selected instrument, the
|
|
// bank will "fall back" to a sub-capital tone or to the capital
|
|
// tone (bank 0).
|
|
byte correctedBank = 0xFF;
|
|
|
|
switch (patchId) {
|
|
case 25: // Steel-String Guitar / 12-string Guitar / Mandolin
|
|
// This instrument has 2 sub-capital tones. Bank selects 17-63
|
|
// are corrected to the second sub-capital tone at 16.
|
|
if (instrumentBank >= 16) {
|
|
correctedBank = 16;
|
|
break;
|
|
}
|
|
// Corrections for values below 16 are handled below.
|
|
|
|
// fall through
|
|
case 4: // Electric Piano 1 / Detuned Electric Piano 1
|
|
case 5: // Electric Piano 2 / Detuned Electric Piano 2
|
|
case 6: // Harpsichord / Coupled Harpsichord
|
|
case 14: // Tubular-bell / Church Bell
|
|
case 16: // Organ 1 / Detuned Organ 1
|
|
case 17: // Organ 2 / Detuned Organ 2
|
|
case 19: // Church Organ 1 / Church Organ 2
|
|
case 21: // Accordion Fr / Accordion It
|
|
case 24: // Nylon-string Guitar / Ukelele
|
|
case 26: // Jazz Guitar / Hawaiian Guitar
|
|
case 27: // Clean Guitar / Chorus Guitar
|
|
case 28: // Muted Guitar / Funk Guitar
|
|
case 30: // Distortion Guitar / Feedback Guitar
|
|
case 31: // Guitar Harmonics / Guitar Feedback
|
|
case 38: // Synth Bass 1 / Synth Bass 3
|
|
case 39: // Synth Bass 2 / Synth Bass 4
|
|
case 48: // Strings / Orchestra
|
|
case 50: // Synth Strings 1 / Synth Strings 3
|
|
case 61: // Brass 1 / Brass 2
|
|
case 62: // Synth Brass 1 / Synth Brass 3
|
|
case 63: // Synth Brass 2 / Synth Brass 4
|
|
case 80: // Square Wave / Sine Wave
|
|
case 107: // Koto / Taisho Koto
|
|
case 115: // Woodblock / Castanets
|
|
case 116: // Taiko / Concert BD
|
|
case 117: // Melodic Tom 1 / Melodic Tom 2
|
|
case 118: // Synth Drum / 808 Tom
|
|
// These instruments have one sub-capital tone. Bank selects 9-63
|
|
// are corrected to the sub-capital tone at 8.
|
|
if (instrumentBank >= 8) {
|
|
correctedBank = 8;
|
|
break;
|
|
}
|
|
// Corrections for values below 8 are handled below.
|
|
|
|
// fall through
|
|
default:
|
|
// The other instruments only have a capital tone. Bank selects
|
|
// 1-63 are corrected to the capital tone.
|
|
correctedBank = 0;
|
|
break;
|
|
}
|
|
|
|
// Return the corrected bank, or 0xFF if no correction is needed.
|
|
return instrumentBank != correctedBank ? correctedBank : 0xFF;
|
|
}
|
|
|
|
void MidiDriver_MT32GM::channelAftertouch(byte outputChannel, byte pressure, int8 source,
|
|
MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
|
|
controlData.channelPressure = pressure;
|
|
|
|
if (!channelLockedByOtherSource)
|
|
_driver->send(MIDI_COMMAND_CHANNEL_AFTERTOUCH | outputChannel, pressure, 0);
|
|
}
|
|
|
|
void MidiDriver_MT32GM::pitchBend(byte outputChannel, uint8 pitchBendLsb, uint8 pitchBendMsb,
|
|
int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
|
|
controlData.pitchWheel = ((uint16)pitchBendMsb << 7) | (uint16)pitchBendLsb;
|
|
|
|
if (!channelLockedByOtherSource)
|
|
_driver->send(MIDI_COMMAND_PITCH_BEND | outputChannel, pitchBendLsb, pitchBendMsb);
|
|
}
|
|
|
|
void MidiDriver_MT32GM::sysEx(const byte *msg, uint16 length) {
|
|
uint16 delay = sysExNoDelay(msg, length);
|
|
|
|
if (delay > 0)
|
|
g_system->delayMillis(delay);
|
|
}
|
|
|
|
uint16 MidiDriver_MT32GM::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_MT32GM::sysExQueue(const byte *msg, uint16 length, int8 source) {
|
|
SysExData sysEx;
|
|
memcpy(sysEx.data, msg, length);
|
|
sysEx.length = length;
|
|
sysEx.source = source;
|
|
|
|
_sysExQueueMutex.lock();
|
|
_sysExQueue.push_back(sysEx);
|
|
_sysExQueueMutex.unlock();
|
|
}
|
|
|
|
uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay, int8 source) {
|
|
if (!_nativeMT32)
|
|
// MT-32 SysExes have no effect on GM devices.
|
|
return 0;
|
|
|
|
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 >> 14) & 0x7F;
|
|
sysExMessage[5] = (targetAddress >> 7) & 0x7F;
|
|
sysExMessage[6] = targetAddress & 0x7F;
|
|
|
|
for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
|
|
assert(sysExMessage[targetAddressByte] < 0x80); // security check
|
|
sysExChecksum -= sysExMessage[targetAddressByte];
|
|
}
|
|
|
|
sysExPos = 7;
|
|
for (int i = 0; i < length; ++i) {
|
|
sysExByte = *msg++;
|
|
|
|
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 (queue) {
|
|
sysExQueue(sysExMessage, sysExPos, source);
|
|
} else if (!delay) {
|
|
return sysExNoDelay(sysExMessage, sysExPos);
|
|
} else {
|
|
sysEx(sysExMessage, sysExPos);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MidiDriver_MT32GM::metaEvent(int8 source, byte type, byte *data, uint16 length) {
|
|
assert(source < MAXIMUM_SOURCES);
|
|
|
|
if (type == 0x2F && source >= 0) // End of Track
|
|
deinitSource(source);
|
|
|
|
_driver->metaEvent(type, data, length);
|
|
}
|
|
|
|
void MidiDriver_MT32GM::stopAllNotes(bool stopSustainedNotes) {
|
|
if (!_driver)
|
|
return;
|
|
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
if (!isOutputChannelUsed(i))
|
|
continue;
|
|
|
|
if (stopSustainedNotes) {
|
|
_driver->send(MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_SUSTAIN, 0);
|
|
_controlData[i]->sustain = false;
|
|
}
|
|
_driver->send(MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
|
|
}
|
|
|
|
_activeNotesMutex.lock();
|
|
|
|
for (int i = 0; i < _maximumActiveNotes; ++i) {
|
|
_activeNotes[i].clear();
|
|
}
|
|
|
|
_activeNotesMutex.unlock();
|
|
}
|
|
|
|
|
|
void MidiDriver_MT32GM::clearSysExQueue() {
|
|
Common::StackLock lock(_sysExQueueMutex);
|
|
|
|
_sysExQueue.clear();
|
|
}
|
|
|
|
MidiChannel *MidiDriver_MT32GM::allocateChannel() {
|
|
if (_driver)
|
|
return _driver->allocateChannel();
|
|
return nullptr;
|
|
}
|
|
|
|
MidiChannel *MidiDriver_MT32GM::getPercussionChannel() {
|
|
if (_driver)
|
|
return _driver->getPercussionChannel();
|
|
return nullptr;
|
|
}
|
|
|
|
bool MidiDriver_MT32GM::isOutputChannelUsed(int8 outputChannel) {
|
|
return outputChannel >= 0 && outputChannel < MIDI_CHANNEL_COUNT &&
|
|
_outputChannelMask & (1 << outputChannel);
|
|
}
|
|
|
|
uint32 MidiDriver_MT32GM::getBaseTempo() {
|
|
if (_driver) {
|
|
return _driver->getBaseTempo();
|
|
}
|
|
return 1000000 / _baseFreq;
|
|
}
|
|
|
|
bool MidiDriver_MT32GM::allocateSourceChannels(uint8 source, uint8 numChannels) {
|
|
assert(source < MAXIMUM_SOURCES);
|
|
|
|
deinitSource(source);
|
|
|
|
_allocationMutex.lock();
|
|
|
|
uint16 claimedChannels = 0;
|
|
if (numChannels > 0) {
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
if (!isOutputChannelUsed(i) || i == MIDI_RHYTHM_CHANNEL)
|
|
continue;
|
|
|
|
if (_controlData[i]->source == -1) {
|
|
claimedChannels |= (1 << i);
|
|
numChannels--;
|
|
}
|
|
if (numChannels == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (numChannels > 0) {
|
|
// Not enough channels available.
|
|
_allocationMutex.unlock();
|
|
return false;
|
|
}
|
|
|
|
// Allocate the channels.
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
if ((claimedChannels >> i) & 1) {
|
|
_controlData[i]->source = source;
|
|
}
|
|
// Clear the source channel mapping.
|
|
if (i != MIDI_RHYTHM_CHANNEL)
|
|
_channelMap[source][i] = -1;
|
|
}
|
|
|
|
_allocationMutex.unlock();
|
|
|
|
_availableChannels[source] = claimedChannels;
|
|
|
|
return true;
|
|
}
|
|
|
|
int8 MidiDriver_MT32GM::mapSourceChannel(uint8 source, uint8 dataChannel) {
|
|
int8 outputChannel = _channelMap[source][dataChannel];
|
|
if (outputChannel == -1) {
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
if ((_availableChannels[source] >> i) & 1) {
|
|
_availableChannels[source] &= ~(1 << i);
|
|
_channelMap[source][dataChannel] = i;
|
|
outputChannel = i;
|
|
break;
|
|
}
|
|
}
|
|
if (outputChannel == -1) {
|
|
warning("MidiDriver_MT32GM: Insufficient available channels for source %i", source);
|
|
}
|
|
}
|
|
return outputChannel;
|
|
}
|
|
|
|
void MidiDriver_MT32GM::deinitSource(uint8 source) {
|
|
assert(source < MAXIMUM_SOURCES);
|
|
|
|
_sysExQueueMutex.lock();
|
|
|
|
// Remove any pending SysExes for this source from the queue.
|
|
Common::ListInternal::Iterator<SysExData> it = _sysExQueue.begin();
|
|
while (it != _sysExQueue.end()) {
|
|
if (it->source == source) {
|
|
it = _sysExQueue.erase(it);
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
|
|
_sysExQueueMutex.unlock();
|
|
|
|
MidiDriver_Multisource::deinitSource(source);
|
|
|
|
// Free channels which were used by this source.
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
if (!isOutputChannelUsed(i))
|
|
continue;
|
|
|
|
if (_controlData[i]->source == source) {
|
|
// Set the sustain default value if it is specified (typically
|
|
// sustain would be turned off).
|
|
if (_controllerDefaults.sustain >= 0 && _controlData[i]->sustain != (_controllerDefaults.sustain >= 0x40)) {
|
|
controlChange(i, MIDI_CONTROLLER_SUSTAIN, _controllerDefaults.sustain, _controlData[i]->source, *_controlData[i]);
|
|
}
|
|
|
|
_controlData[i]->source = -1;
|
|
}
|
|
}
|
|
_availableChannels[source] = 0xFFFF;
|
|
// Reset the data to output channel mapping
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
_channelMap[source][i] = i;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::applySourceVolume(uint8 source) {
|
|
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
|
|
if (!isOutputChannelUsed(i))
|
|
continue;
|
|
|
|
if (source == 0xFF || _controlData[i]->source == source)
|
|
controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, _controlData[i]->source, *_controlData[i]);
|
|
}
|
|
}
|
|
|
|
void MidiDriver_MT32GM::stopAllNotes(uint8 source, uint8 channel) {
|
|
_activeNotesMutex.lock();
|
|
|
|
for (int i = 0; i < _maximumActiveNotes; ++i) {
|
|
if ((source == 0xFF || _activeNotes[i].source == source) &&
|
|
(channel == 0xFF || _activeNotes[i].channel == channel)) {
|
|
if (_activeNotes[i].sustain) {
|
|
// Turn off sustain
|
|
controlChange(_activeNotes[i].channel, MIDI_CONTROLLER_SUSTAIN, 0x00, _activeNotes[i].source, *_controlData[i]);
|
|
} else {
|
|
// Send note off
|
|
noteOnOff(_activeNotes[i].channel, MIDI_COMMAND_NOTE_OFF, _activeNotes[i].note, 0x00, _activeNotes[i].source, *_controlData[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
_activeNotesMutex.unlock();
|
|
}
|
|
|
|
void MidiDriver_MT32GM::onTimer() {
|
|
MidiDriver_Multisource::onTimer();
|
|
|
|
_sysExQueueMutex.lock();
|
|
|
|
_sysExDelay -= (_sysExDelay > _timerRate) ? _timerRate : _sysExDelay;
|
|
|
|
if (!_sysExQueue.empty() && _sysExDelay == 0) {
|
|
// Ready to send next SysEx message to the MIDI device
|
|
SysExData sysEx = _sysExQueue.front();
|
|
_sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
|
|
_sysExQueue.pop_front();
|
|
}
|
|
|
|
_sysExQueueMutex.unlock();
|
|
}
|
|
|
|
#endif
|