From b70ab20fc89fd3bf33fd68626c44155758c39dfc Mon Sep 17 00:00:00 2001 From: Coen Rampen Date: Fri, 12 Nov 2021 22:35:57 +0100 Subject: [PATCH] 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. --- audio/adlib_ms.cpp | 46 +++++++++++++++ audio/adlib_ms.h | 12 ++++ audio/mididrv.h | 1 + audio/mididrv_ms.cpp | 99 ++++++++++++++++++++++++++++++++ audio/mididrv_ms.h | 76 +++++++++++++++++++++++++ audio/miles_adlib.cpp | 40 +++++++++++++ audio/miles_midi.cpp | 29 +++++++--- audio/mt32gm.cpp | 127 +++++++++++++++++++++++++++++++++++++++--- audio/mt32gm.h | 74 +++++++++++++++++++++++- 9 files changed, 484 insertions(+), 20 deletions(-) diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp index 6caa62b5404..8d052829425 100644 --- a/audio/adlib_ms.cpp +++ b/audio/adlib_ms.cpp @@ -427,6 +427,7 @@ int MidiDriver_ADLIB_Multisource::open() { for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) { _controlData[i][j].volume = _defaultChannelVolume; } + applyControllerDefaults(i); } // Set default OPL register values. @@ -771,6 +772,51 @@ void MidiDriver_ADLIB_Multisource::deinitSource(uint8 source) { } _allocationMutex.unlock(); + + applyControllerDefaults(source); +} + +void MidiDriver_ADLIB_Multisource::applyControllerDefaults(uint8 source) { + if (source == 0xFF) { + // Apply controller defaults for all sources. + for (int i = 0; i < MAXIMUM_SOURCES; i++) { + applyControllerDefaults(i); + } + } else { + for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) { + if (_controllerDefaults.program >= 0) { + _controlData[source][i].program = _controllerDefaults.program; + } + if (_controllerDefaults.channelPressure >= 0) { + _controlData[source][i].channelPressure = _controllerDefaults.channelPressure; + } + if (_controllerDefaults.pitchBend >= 0) { + _controlData[source][i].pitchBend = _controllerDefaults.pitchBend; + } + if (_controllerDefaults.modulation >= 0) { + _controlData[source][i].modulation = _controllerDefaults.modulation; + } + if (_controllerDefaults.volume >= 0) { + _controlData[source][i].volume = _controllerDefaults.volume; + } + if (_controllerDefaults.panning >= 0) { + _controlData[source][i].panning = _controllerDefaults.panning; + } + if (_controllerDefaults.expression >= 0) { + _controlData[source][i].expression = _controllerDefaults.expression; + } + if (_controllerDefaults.rpn >= 0) { + _controlData[source][i].rpn = _controllerDefaults.rpn; + } + if (_controllerDefaults.pitchBendSensitivity >= 0) { + _controlData[source][i].pitchBendSensitivity = _controllerDefaults.pitchBendSensitivity; + _controlData[source][i].pitchBendSensitivityCents = 0; + } + // Controller defaults not supported by this driver: + // instrument bank, drumkit. + // Sustain is turned of by deinitSource. + } + } } void MidiDriver_ADLIB_Multisource::modulation(uint8 channel, uint8 modulation, uint8 source) { diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h index 539c616f02a..bd50630ffda 100644 --- a/audio/adlib_ms.h +++ b/audio/adlib_ms.h @@ -560,6 +560,7 @@ public: */ MidiChannel *getPercussionChannel() override; + using MidiDriver_Multisource::send; void send(int8 source, uint32 b) override; void sysEx(const byte *msg, uint16 length) override; void metaEvent(int8 source, byte type, byte *data, uint16 length) override; @@ -756,6 +757,17 @@ protected: */ virtual void allNotesOff(uint8 channel, uint8 source); + /** + * Applies the controller default settings to the controller data for the + * specified source. + * This will set all supported default values specified on _controllerDefaults + * except sustain, which is set by deinitSource. + * + * @param source The source triggering the default settings, or 0xFF to + * apply controller defaults for all sources. + */ + virtual void applyControllerDefaults(uint8 source); + /** * Recalculates and writes the frequencies of the active notes on the * specified MIDI channel and source. diff --git a/audio/mididrv.h b/audio/mididrv.h index 8f9eb2ea906..1b7e0d62ca8 100644 --- a/audio/mididrv.h +++ b/audio/mididrv.h @@ -146,6 +146,7 @@ public: static const uint16 MIDI_MASTER_TUNING_FINE_DEFAULT = 0x2000; static const uint8 MIDI_MASTER_TUNING_COARSE_DEFAULT = 0x40; + static const uint8 MT32_PITCH_BEND_SENSITIVITY_DEFAULT = 0x0C; static const uint8 GM_PITCH_BEND_SENSITIVITY_DEFAULT = 0x02; static const uint8 GS_RHYTHM_FIRST_NOTE = 0x1B; diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp index dc1d5e7b684..3a8a34a9947 100644 --- a/audio/mididrv_ms.cpp +++ b/audio/mididrv_ms.cpp @@ -37,6 +37,21 @@ MidiDriver_Multisource::MidiSource::MidiSource() : fadePassedTime(0), fadeDuration(0) { } +MidiDriver_Multisource::ControllerDefaults::ControllerDefaults() : + // The -1 value indicates no default value should be set on the controller. + program(-1), + instrumentBank(-1), + drumkit(-1), + channelPressure(-1), + pitchBend(-1), + modulation(-1), + volume(-1), + panning(-1), + expression(-1), + sustain(-1), + rpn(-1), + pitchBendSensitivity(-1) { } + MidiDriver_Multisource::MidiDriver_Multisource() : _userVolumeScaling(false), _userMusicVolume(192), @@ -185,6 +200,90 @@ void MidiDriver_Multisource::updateFading() { _fadeDelay = FADING_DELAY; } +void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type) { + // Call the overload with the General MIDI default for the controller. + switch (type) { + case CONTROLLER_DEFAULT_PITCH_BEND: + setControllerDefault(type, MIDI_PITCH_BEND_DEFAULT); + break; + case CONTROLLER_DEFAULT_VOLUME: + setControllerDefault(type, 100); + break; + case CONTROLLER_DEFAULT_PANNING: + setControllerDefault(type, MIDI_PANNING_DEFAULT); + break; + case CONTROLLER_DEFAULT_EXPRESSION: + setControllerDefault(type, MIDI_EXPRESSION_DEFAULT); + break; + case CONTROLLER_DEFAULT_RPN: + setControllerDefault(type, MIDI_RPN_NULL); + break; + case CONTROLLER_DEFAULT_PITCH_BEND_SENSITIVITY: + setControllerDefault(type, GM_PITCH_BEND_SENSITIVITY_DEFAULT); + break; + case CONTROLLER_DEFAULT_PROGRAM: + case CONTROLLER_DEFAULT_INSTRUMENT_BANK: + case CONTROLLER_DEFAULT_DRUMKIT: + case CONTROLLER_DEFAULT_CHANNEL_PRESSURE: + case CONTROLLER_DEFAULT_MODULATION: + case CONTROLLER_DEFAULT_SUSTAIN: + default: + setControllerDefault(type, 0); + break; + } +} + +void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, int16 value) { + // Set the specified default value on _controllerDefaults on the field + // corresponding to the specified controller. + switch (type) { + case CONTROLLER_DEFAULT_PROGRAM: + _controllerDefaults.program = value; + break; + case CONTROLLER_DEFAULT_INSTRUMENT_BANK: + _controllerDefaults.instrumentBank = value; + break; + case CONTROLLER_DEFAULT_DRUMKIT: + _controllerDefaults.drumkit = value; + break; + case CONTROLLER_DEFAULT_CHANNEL_PRESSURE: + _controllerDefaults.channelPressure = value; + break; + case CONTROLLER_DEFAULT_PITCH_BEND: + _controllerDefaults.pitchBend = value; + break; + case CONTROLLER_DEFAULT_MODULATION: + _controllerDefaults.modulation = value; + break; + case CONTROLLER_DEFAULT_VOLUME: + _controllerDefaults.volume = value; + break; + case CONTROLLER_DEFAULT_PANNING: + _controllerDefaults.panning = value; + break; + case CONTROLLER_DEFAULT_EXPRESSION: + _controllerDefaults.expression = value; + break; + case CONTROLLER_DEFAULT_SUSTAIN: + _controllerDefaults.sustain = value; + break; + case CONTROLLER_DEFAULT_RPN: + _controllerDefaults.rpn = value; + break; + case CONTROLLER_DEFAULT_PITCH_BEND_SENSITIVITY: + _controllerDefaults.pitchBendSensitivity = value; + break; + default: + warning("MidiDriver_Multisource::setControllerDefault - Unknown controller default type %i", type); + break; + } +} + +void MidiDriver_Multisource::clearControllerDefault(ControllerDefaultType type) { + // Reset the default value for this controller to -1. + setControllerDefault(type, -1); +} + void MidiDriver_Multisource::deinitSource(uint8 source) { abortFade(source, FADE_ABORT_TYPE_END_VOLUME); diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h index bd20ca4aadc..5fc96719e84 100644 --- a/audio/mididrv_ms.h +++ b/audio/mididrv_ms.h @@ -141,6 +141,25 @@ public: FADE_ABORT_TYPE_START_VOLUME }; + /** + * The controllers and parameters for which a default value can be set + * using setControllerDefault. + */ + enum ControllerDefaultType { + CONTROLLER_DEFAULT_PROGRAM, + CONTROLLER_DEFAULT_INSTRUMENT_BANK, + CONTROLLER_DEFAULT_DRUMKIT, + CONTROLLER_DEFAULT_CHANNEL_PRESSURE, + CONTROLLER_DEFAULT_PITCH_BEND, + CONTROLLER_DEFAULT_MODULATION, + CONTROLLER_DEFAULT_VOLUME, + CONTROLLER_DEFAULT_PANNING, + CONTROLLER_DEFAULT_EXPRESSION, + CONTROLLER_DEFAULT_SUSTAIN, + CONTROLLER_DEFAULT_RPN, + CONTROLLER_DEFAULT_PITCH_BEND_SENSITIVITY + }; + protected: // This stores data about a specific source of MIDI data. struct MidiSource { @@ -168,6 +187,28 @@ protected: MidiSource(); }; + // Stores the default values that should be set for each controller. + // -1 means no explicit default should be set for that controller. + struct ControllerDefaults { + int8 program; + int8 instrumentBank; + int8 drumkit; + + int8 channelPressure; + int16 pitchBend; + + int8 modulation; + int8 volume; + int8 panning; + int8 expression; + int8 sustain; + int16 rpn; + + int8 pitchBendSensitivity; + + ControllerDefaults(); + }; + public: MidiDriver_Multisource(); @@ -288,6 +329,38 @@ public: */ bool isFading(uint8 source); + /** + * Specify a controller which should be reset to its General MIDI default + * value when a new track is started. See the overload for more details. + * + * @param type The controller which should be reset. + */ + void setControllerDefault(ControllerDefaultType type); + /** + * Specify a default value for a controller which should be set when a new + * track is started. Use this if a game uses a MIDI controller, but does + * not consistently set it to a value at the start of every track, causing + * incorrect playback. Do not use this if a game depends on controller + * values carrying over to the next track for correct playback. + * + * This functionality will not work if the fallback MIDI source -1 is used. + * It is also necessary to call deinitSource whenever playback of a track + * is stopped, as this sets up the contoller reset. + * + * Use the setControllerDefault(ControllerDefaultType) overload if the + * General MIDI default value for the controller should be used. + * + * @param type The controller which should be reset. + * @param value The default value which should be set. + */ + void setControllerDefault(ControllerDefaultType type, int16 value); + /** + * Clears a previously set default value for the specified controller. + * + * @param type The controller for which the default value should be cleared. + */ + void clearControllerDefault(ControllerDefaultType type); + /** * Applies the user volume settings to the MIDI driver. MIDI channel * volumes will be scaled using the user volume. @@ -348,6 +421,9 @@ protected: // MIDI source data MidiSource _sources[MAXIMUM_SOURCES]; + // Default values for each controller + ControllerDefaults _controllerDefaults; + // True if the driver should scale MIDI channel volume to the user // specified volume settings. bool _userVolumeScaling; diff --git a/audio/miles_adlib.cpp b/audio/miles_adlib.cpp index 2409350a125..a6cfdcfc1d9 100644 --- a/audio/miles_adlib.cpp +++ b/audio/miles_adlib.cpp @@ -167,6 +167,7 @@ private: struct MidiChannelEntry { byte currentPatchBank; const InstrumentEntry *currentInstrumentPtr; + byte currentProgram; uint16 currentPitchBender; byte currentPitchRange; byte currentVoiceProtection; @@ -183,6 +184,7 @@ private: MidiChannelEntry() : currentPatchBank(0), currentInstrumentPtr(nullptr), + currentProgram(0), currentPitchBender(MIDI_PITCH_BEND_DEFAULT), currentPitchRange(0), currentVoiceProtection(0), @@ -285,6 +287,8 @@ private: const InstrumentEntry *searchInstrument(byte bankId, byte patchId); void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2); + + void applyControllerDefaults(uint8 source); }; MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) @@ -346,6 +350,7 @@ int MidiDriver_Miles_AdLib::open() { _isOpen = true; resetData(); + applyControllerDefaults(0xFF); _timerRate = getBaseTempo(); _opl->start(new Common::Functor0Mem(this, &MidiDriver_Miles_AdLib::onTimer)); @@ -1155,6 +1160,7 @@ void MidiDriver_Miles_AdLib::programChange(byte midiChannel, byte patchId) { // and remember it in that case for the current MIDI-channel _midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr; + _midiChannels[midiChannel].currentProgram = patchId; } const InstrumentEntry *MidiDriver_Miles_AdLib::searchInstrument(byte bankId, byte patchId) { @@ -1206,6 +1212,40 @@ void MidiDriver_Miles_AdLib::deinitSource(uint8 source) { // Stop fades and turn off non-sustained notes. MidiDriver_Multisource::deinitSource(source); + + applyControllerDefaults(source); +} + +void MidiDriver_Miles_AdLib::applyControllerDefaults(uint8 source) { + if (!(source == 0 || source == 0xFF)) + return; + + for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) { + if (_controllerDefaults.program >= 0) { + _midiChannels[i].currentProgram = _controllerDefaults.program; + } + if (_controllerDefaults.pitchBend >= 0) { + _midiChannels[i].currentPitchBender = _controllerDefaults.pitchBend; + } + if (_controllerDefaults.modulation >= 0) { + _midiChannels[i].currentModulation = _controllerDefaults.modulation; + } + if (_controllerDefaults.volume >= 0) { + _midiChannels[i].currentVolume = _controllerDefaults.volume; + } + if (_controllerDefaults.panning >= 0) { + _midiChannels[i].currentPanning = _controllerDefaults.panning; + } + if (_controllerDefaults.expression >= 0) { + _midiChannels[i].currentVolumeExpression = _controllerDefaults.expression; + } + if (_controllerDefaults.pitchBendSensitivity >= 0) { + _midiChannels[i].currentPitchRange = _controllerDefaults.pitchBendSensitivity; + } + // Controller defaults not supported by this driver: + // instrument bank, drumkit, channel pressure, RPN. + // Sustain is turned of by deinitSource. + } } void MidiDriver_Miles_AdLib::setRegister(int reg, int value) { diff --git a/audio/miles_midi.cpp b/audio/miles_midi.cpp index 1c8340cd131..04ed31bc77f 100644 --- a/audio/miles_midi.cpp +++ b/audio/miles_midi.cpp @@ -177,6 +177,8 @@ void MidiDriver_Miles_Midi::send(int8 source, uint32 b) { byte command = b & 0xf0; byte dataChannel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; byte outputChannel = source < 0 ? dataChannel : _channelMap[source][dataChannel]; MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel]; @@ -189,7 +191,17 @@ void MidiDriver_Miles_Midi::send(int8 source, uint32 b) { MilesMidiChannelControlData &controlData = channelLockedByOtherSource ? *outputChannelEntry.unlockData : *outputChannelEntry.currentData; - processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource); + if (command == MIDI_COMMAND_CONTROL_CHANGE && op1 == MILES_CONTROLLER_LOCK_CHANNEL) { + // The lock channel controller will allocate an output channel to use + // to send the events on this data channel. In this case, the data + // channel should not be assigned to the source, because it will not + // actually be used to send MIDI events. processEvent will assign the + // data channel to the source, so it is bypassed and controlChange is + // called directly. + controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource); + } else { + processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource); + } if (command == MIDI_COMMAND_NOTE_OFF || command == MIDI_COMMAND_NOTE_ON || command == MIDI_COMMAND_PITCH_BEND || command == MIDI_COMMAND_POLYPHONIC_AFTERTOUCH || command == MIDI_COMMAND_CHANNEL_AFTERTOUCH) { @@ -414,6 +426,10 @@ void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) { // Could not find a channel to lock return; + // stopNotesOnChannel will turn off sustain, so record the current sustain + // value so it can be set on the unlock data. + bool currentSustain = _midiChannels[lockChannel].currentData->sustain; + stopNotesOnChannel(lockChannel); _midiChannels[lockChannel].locked = true; @@ -421,15 +437,14 @@ void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) { _channelMap[source][dataChannel] = lockChannel; // Copy current controller values so they can be restored when unlocking the channel *_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData; + _midiChannels[lockChannel].unlockData->sustain = currentSustain; _midiChannels[lockChannel].currentData->source = source; + // Set any specified default controller values on the channel + applyControllerDefaults(source, *_midiChannels[lockChannel].currentData, lockChannel, false); + // Send volume change to apply the new source volume controlChange(lockChannel, MIDI_CONTROLLER_VOLUME, 0x7F, source, *_midiChannels[lockChannel].currentData); - - // Note that other controller values might be "inherited" from the source - // which was previously playing on the locked MIDI channel. The KYRA engine - // does not seem to take any precautions against this. - // Controllers could be set to default values here. } int8 MidiDriver_Miles_Midi::findLockChannel(bool useProtectedChannels) { @@ -901,8 +916,6 @@ void MidiDriver_Miles_Midi::deinitSource(uint8 source) { _midiChannels[i].lockProtected = false; _midiChannels[i].protectedSource = -1; } - if (_midiChannels[i].currentData->source == source) - _midiChannels[i].currentData->source = -1; if (_midiChannels[i].unlockData->source == source) _midiChannels[i].unlockData->source = -1; } diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp index 4f03197009e..11ee737c11b 100644 --- a/audio/mt32gm.cpp +++ b/audio/mt32gm.cpp @@ -197,6 +197,8 @@ void MidiDriver_MT32GM::initControlData() { _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]; @@ -417,6 +419,11 @@ void MidiDriver_MT32GM::processEvent(int8 source, uint32 b, uint8 outputChannel, // 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) { @@ -426,12 +433,13 @@ void MidiDriver_MT32GM::processEvent(int8 source, uint32 b, uint8 outputChannel, noteOnOff(outputChannel, command, op1, op2, source, controlData); break; case MIDI_COMMAND_PITCH_BEND: - controlData.pitchWheel = ((uint16)op2 << 7) | (uint16)op1; - // fall through + 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 - if (!channelLockedByOtherSource) - _driver->send(command | outputChannel, op1, op2); + channelAftertouch(outputChannel, op1, source, controlData, channelLockedByOtherSource); break; case MIDI_COMMAND_CONTROL_CHANGE: controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource); @@ -449,6 +457,67 @@ void MidiDriver_MT32GM::processEvent(int8 source, uint32 b, uint8 outputChannel, } } +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; @@ -470,6 +539,12 @@ void MidiDriver_MT32GM::noteOnOff(byte outputChannel, byte command, byte note, b _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); @@ -482,6 +557,10 @@ void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber, 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; @@ -528,14 +607,24 @@ void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber, 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 = 0x7F; + 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: @@ -750,6 +839,22 @@ byte MidiDriver_MT32GM::correctInstrumentBank(byte instrumentBank, byte patchId) 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); @@ -972,17 +1077,21 @@ void MidiDriver_MT32GM::deinitSource(uint8 source) { if (!isOutputChannelUsed(i)) continue; - if (_controlData[i]->source == source) + 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; } - - // TODO Optionally reset some controllers to their - // default values? Pitch wheel, volume, sustain... } void MidiDriver_MT32GM::applySourceVolume(uint8 source) { diff --git a/audio/mt32gm.h b/audio/mt32gm.h index 5410be3491e..735ed36891d 100644 --- a/audio/mt32gm.h +++ b/audio/mt32gm.h @@ -142,6 +142,7 @@ protected: byte program; // The Roland GS instrument bank byte instrumentBank; + byte channelPressure; byte modulation; // The volume specified by the MIDI data @@ -153,17 +154,24 @@ protected: byte expression; bool sustain; + // The currently selected Registered Parameter Number + uint16 rpn; + byte pitchBendSensitivity; + MidiChannelControlData() : source(-1), sourceVolumeApplied(false), pitchWheel(MIDI_PITCH_BEND_DEFAULT), program(0), instrumentBank(0), + channelPressure(0), modulation(0), volume(0), scaledVolume(0), - panPosition(0x40), - expression(0x7F), - sustain(false) { } + panPosition(MIDI_PANNING_DEFAULT), + expression(MIDI_EXPRESSION_DEFAULT), + sustain(false), + rpn(MIDI_RPN_NULL), + pitchBendSensitivity(0) { } }; /** @@ -370,6 +378,22 @@ protected: */ virtual void processEvent(int8 source, uint32 b, uint8 outputChannel, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); + /** + * Applies the controller default settings to the specified output channel + * for the specified source. + * This will set all default values specified on _controllerDefaults on the + * channel except sustain, which is set by deinitSource. + * + * @param source The source triggering the default settings + * @param controlData The control data set to use when setting the defaults + * @param outputChannel The output channel on which the defaults should be + * set + * @param channelLockedByOtherSource True if the output channel is locked + * by another source. This will prevent the defaults from actually being + * sent to the MIDI device, but controlData will be updated. Default is + * false. + */ + virtual void applyControllerDefaults(uint8 source, MidiChannelControlData &controlData, uint8 outputChannel, bool channelLockedByOtherSource); /** * Processes a note on or off MIDI event. * This will apply source volume if necessary, update the active note @@ -382,6 +406,20 @@ protected: */ virtual void noteOnOff(byte outputChannel, byte command, byte note, byte velocity, int8 source, MidiChannelControlData &controlData); + /** + * Processes a polyphonic aftertouch MIDI event. + * This implementation will just send the event to the MIDI device. + * + * @param outputChannel The MIDI output channel for the event + * @param note The note on which aftertouch should be applied + * @param pressure The amount of pressure which should be applied + * @param source The source of the event + * @param controlData The control data set for the MIDI channel + * @param channelLockedByOtherSource True if the output channel is locked + * by another source. Default is false. + */ + virtual void polyAftertouch(byte outputChannel, byte note, byte pressure, + int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); /** * Process a control change MIDI event. * This will update the specified control data set and apply other @@ -409,6 +447,36 @@ protected: */ virtual void programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); + /** + * Processes a channel aftertouch MIDI event. + * This whil update the specified control data set and send the event to + * the MIDI device. + * + * @param outputChannel The MIDI output channel for the event + * @param pressure The amount of pressure which should be applied + * @param source The source of the event + * @param controlData The control data set for the MIDI channel + * @param channelLockedByOtherSource True if the output channel is locked + * by another source. Default is false. + */ + virtual void channelAftertouch(byte outputChannel, byte pressure, int8 source, + MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); + /** + * Processes a pitch bend MIDI event. + * This whil update the specified control data set and send the event to + * the MIDI device. + * + * @param outputChannel The MIDI output channel for the event + * @param pitchBendLsb The pitch bend LSB + * @param pitchBendMsb The pitch bend MSB + * @param source The source of the event + * @param controlData The control data set for the MIDI channel + * @param channelLockedByOtherSource True if the output channel is locked + * by another source. Default is false. + */ + virtual void pitchBend(byte outputChannel, uint8 pitchBendLsb, uint8 pitchBendMsb, + int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); + /** * Adds a note to the active note registration. */