scummvm/audio/mt32gm.cpp
2021-12-26 18:48:43 +01:00

1140 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 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();
}
}
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