scummvm/audio/mt32gm.cpp
Coen Rampen b70ab20fc8 AUDIO: MIDI controller defaults on track start
This commit adds a feature to the multisource MIDI drivers which allows setting
a default value for most controllers. This default is then applied at the start
of every new track, which ensures controllers are in the correct state.
This can be used for games which use some controller, but do not reset it at
the start of each track, which can lead to incorrect playback.
2021-11-17 21:54:59 +01:00

1143 lines
38 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.
*
*/
#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;
if (_controlData) {
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();
}
}
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 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) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, _controllerDefaults.instrumentBank);
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
}
if (_controllerDefaults.program >= 0 && controlData.program != _controllerDefaults.program) {
send(source, MIDI_COMMAND_PROGRAM_CHANGE | outputChannel, _controllerDefaults.program, 0);
}
} else {
// Apply default drumkit only to the rhythm channel.
if (_controllerDefaults.drumkit >= 0 && controlData.program != _controllerDefaults.drumkit) {
send(source, MIDI_COMMAND_PROGRAM_CHANGE | outputChannel, _controllerDefaults.drumkit, 0);
}
}
if (_controllerDefaults.channelPressure >= 0 && controlData.channelPressure != _controllerDefaults.channelPressure) {
send(source, MIDI_COMMAND_CHANNEL_AFTERTOUCH | outputChannel, _controllerDefaults.channelPressure, 0);
}
if (_controllerDefaults.pitchBend >= 0 && controlData.pitchWheel != _controllerDefaults.pitchBend) {
send(source, MIDI_COMMAND_PITCH_BEND | outputChannel, _controllerDefaults.pitchBend & 0x7F, _controllerDefaults.pitchBend >> 7);
}
if (_controllerDefaults.modulation >= 0 && controlData.modulation != _controllerDefaults.modulation) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_MODULATION, _controllerDefaults.modulation);
}
if (_controllerDefaults.volume >= 0 && controlData.volume != _controllerDefaults.volume) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_VOLUME, _controllerDefaults.volume);
}
if (_controllerDefaults.panning >= 0 && controlData.panPosition != _controllerDefaults.panning) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_PANNING, _controllerDefaults.panning);
}
if (_controllerDefaults.expression >= 0 && controlData.expression != _controllerDefaults.expression) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_EXPRESSION, _controllerDefaults.expression);
}
// 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) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8);
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF);
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_DATA_ENTRY_MSB, _controllerDefaults.pitchBendSensitivity);
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
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) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_MSB, rpn >> 8);
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_LSB, rpn & 0xFF);
}
}
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 (_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) {
SysExData sysEx;
memcpy(sysEx.data, msg, length);
sysEx.length = length;
_sysExQueueMutex.lock();
_sysExQueue.push(sysEx);
_sysExQueueMutex.unlock();
}
uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay) {
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);
} 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) {
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);
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)) {
send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_SUSTAIN, _controllerDefaults.sustain);
}
_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.pop();
_sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
}
_sysExQueueMutex.unlock();
}
#endif