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.
This commit is contained in:
Coen Rampen 2021-11-12 22:35:57 +01:00
parent 07f91ec50e
commit b70ab20fc8
9 changed files with 484 additions and 20 deletions

View file

@ -427,6 +427,7 @@ int MidiDriver_ADLIB_Multisource::open() {
for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) { for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) {
_controlData[i][j].volume = _defaultChannelVolume; _controlData[i][j].volume = _defaultChannelVolume;
} }
applyControllerDefaults(i);
} }
// Set default OPL register values. // Set default OPL register values.
@ -771,6 +772,51 @@ void MidiDriver_ADLIB_Multisource::deinitSource(uint8 source) {
} }
_allocationMutex.unlock(); _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) { void MidiDriver_ADLIB_Multisource::modulation(uint8 channel, uint8 modulation, uint8 source) {

View file

@ -560,6 +560,7 @@ public:
*/ */
MidiChannel *getPercussionChannel() override; MidiChannel *getPercussionChannel() override;
using MidiDriver_Multisource::send;
void send(int8 source, uint32 b) override; void send(int8 source, uint32 b) override;
void sysEx(const byte *msg, uint16 length) override; void sysEx(const byte *msg, uint16 length) override;
void metaEvent(int8 source, byte type, byte *data, 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); 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 * Recalculates and writes the frequencies of the active notes on the
* specified MIDI channel and source. * specified MIDI channel and source.

View file

@ -146,6 +146,7 @@ public:
static const uint16 MIDI_MASTER_TUNING_FINE_DEFAULT = 0x2000; static const uint16 MIDI_MASTER_TUNING_FINE_DEFAULT = 0x2000;
static const uint8 MIDI_MASTER_TUNING_COARSE_DEFAULT = 0x40; 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 GM_PITCH_BEND_SENSITIVITY_DEFAULT = 0x02;
static const uint8 GS_RHYTHM_FIRST_NOTE = 0x1B; static const uint8 GS_RHYTHM_FIRST_NOTE = 0x1B;

View file

@ -37,6 +37,21 @@ MidiDriver_Multisource::MidiSource::MidiSource() :
fadePassedTime(0), fadePassedTime(0),
fadeDuration(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() : MidiDriver_Multisource::MidiDriver_Multisource() :
_userVolumeScaling(false), _userVolumeScaling(false),
_userMusicVolume(192), _userMusicVolume(192),
@ -185,6 +200,90 @@ void MidiDriver_Multisource::updateFading() {
_fadeDelay = FADING_DELAY; _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) { void MidiDriver_Multisource::deinitSource(uint8 source) {
abortFade(source, FADE_ABORT_TYPE_END_VOLUME); abortFade(source, FADE_ABORT_TYPE_END_VOLUME);

View file

@ -141,6 +141,25 @@ public:
FADE_ABORT_TYPE_START_VOLUME 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: protected:
// This stores data about a specific source of MIDI data. // This stores data about a specific source of MIDI data.
struct MidiSource { struct MidiSource {
@ -168,6 +187,28 @@ protected:
MidiSource(); 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: public:
MidiDriver_Multisource(); MidiDriver_Multisource();
@ -288,6 +329,38 @@ public:
*/ */
bool isFading(uint8 source); 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 * Applies the user volume settings to the MIDI driver. MIDI channel
* volumes will be scaled using the user volume. * volumes will be scaled using the user volume.
@ -348,6 +421,9 @@ protected:
// MIDI source data // MIDI source data
MidiSource _sources[MAXIMUM_SOURCES]; MidiSource _sources[MAXIMUM_SOURCES];
// Default values for each controller
ControllerDefaults _controllerDefaults;
// True if the driver should scale MIDI channel volume to the user // True if the driver should scale MIDI channel volume to the user
// specified volume settings. // specified volume settings.
bool _userVolumeScaling; bool _userVolumeScaling;

View file

@ -167,6 +167,7 @@ private:
struct MidiChannelEntry { struct MidiChannelEntry {
byte currentPatchBank; byte currentPatchBank;
const InstrumentEntry *currentInstrumentPtr; const InstrumentEntry *currentInstrumentPtr;
byte currentProgram;
uint16 currentPitchBender; uint16 currentPitchBender;
byte currentPitchRange; byte currentPitchRange;
byte currentVoiceProtection; byte currentVoiceProtection;
@ -183,6 +184,7 @@ private:
MidiChannelEntry() : currentPatchBank(0), MidiChannelEntry() : currentPatchBank(0),
currentInstrumentPtr(nullptr), currentInstrumentPtr(nullptr),
currentProgram(0),
currentPitchBender(MIDI_PITCH_BEND_DEFAULT), currentPitchBender(MIDI_PITCH_BEND_DEFAULT),
currentPitchRange(0), currentPitchRange(0),
currentVoiceProtection(0), currentVoiceProtection(0),
@ -285,6 +287,8 @@ private:
const InstrumentEntry *searchInstrument(byte bankId, byte patchId); const InstrumentEntry *searchInstrument(byte bankId, byte patchId);
void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2); void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
void applyControllerDefaults(uint8 source);
}; };
MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount)
@ -346,6 +350,7 @@ int MidiDriver_Miles_AdLib::open() {
_isOpen = true; _isOpen = true;
resetData(); resetData();
applyControllerDefaults(0xFF);
_timerRate = getBaseTempo(); _timerRate = getBaseTempo();
_opl->start(new Common::Functor0Mem<void, MidiDriver_Miles_AdLib>(this, &MidiDriver_Miles_AdLib::onTimer)); _opl->start(new Common::Functor0Mem<void, MidiDriver_Miles_AdLib>(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 // and remember it in that case for the current MIDI-channel
_midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr; _midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr;
_midiChannels[midiChannel].currentProgram = patchId;
} }
const InstrumentEntry *MidiDriver_Miles_AdLib::searchInstrument(byte bankId, byte 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. // Stop fades and turn off non-sustained notes.
MidiDriver_Multisource::deinitSource(source); 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) { void MidiDriver_Miles_AdLib::setRegister(int reg, int value) {

View file

@ -177,6 +177,8 @@ void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
byte command = b & 0xf0; byte command = b & 0xf0;
byte dataChannel = b & 0xf; byte dataChannel = b & 0xf;
byte op1 = (b >> 8) & 0xff;
byte op2 = (b >> 16) & 0xff;
byte outputChannel = source < 0 ? dataChannel : _channelMap[source][dataChannel]; byte outputChannel = source < 0 ? dataChannel : _channelMap[source][dataChannel];
MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel]; MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel];
@ -189,7 +191,17 @@ void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
MilesMidiChannelControlData &controlData = channelLockedByOtherSource ? MilesMidiChannelControlData &controlData = channelLockedByOtherSource ?
*outputChannelEntry.unlockData : *outputChannelEntry.currentData; *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 || 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) { 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 // Could not find a channel to lock
return; 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); stopNotesOnChannel(lockChannel);
_midiChannels[lockChannel].locked = true; _midiChannels[lockChannel].locked = true;
@ -421,15 +437,14 @@ void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
_channelMap[source][dataChannel] = lockChannel; _channelMap[source][dataChannel] = lockChannel;
// Copy current controller values so they can be restored when unlocking the channel // Copy current controller values so they can be restored when unlocking the channel
*_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData; *_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData;
_midiChannels[lockChannel].unlockData->sustain = currentSustain;
_midiChannels[lockChannel].currentData->source = source; _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 // Send volume change to apply the new source volume
controlChange(lockChannel, MIDI_CONTROLLER_VOLUME, 0x7F, source, *_midiChannels[lockChannel].currentData); 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) { int8 MidiDriver_Miles_Midi::findLockChannel(bool useProtectedChannels) {
@ -901,8 +916,6 @@ void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
_midiChannels[i].lockProtected = false; _midiChannels[i].lockProtected = false;
_midiChannels[i].protectedSource = -1; _midiChannels[i].protectedSource = -1;
} }
if (_midiChannels[i].currentData->source == source)
_midiChannels[i].currentData->source = -1;
if (_midiChannels[i].unlockData->source == source) if (_midiChannels[i].unlockData->source == source)
_midiChannels[i].unlockData->source = -1; _midiChannels[i].unlockData->source = -1;
} }

View file

@ -197,6 +197,8 @@ void MidiDriver_MT32GM::initControlData() {
_controlData[i] = new MidiChannelControlData(); _controlData[i] = new MidiChannelControlData();
_controlData[i]->volume = _controlData[i]->scaledVolume = _controlData[i]->volume = _controlData[i]->scaledVolume =
(_nativeMT32 ? MT32_DEFAULT_CHANNEL_VOLUME : GM_DEFAULT_CHANNEL_VOLUME); (_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) { if (_nativeMT32 && i >= 1 && i <= 8) {
_controlData[i]->program = MT32_DEFAULT_INSTRUMENTS[i - 1]; _controlData[i]->program = MT32_DEFAULT_INSTRUMENTS[i - 1];
_controlData[i]->panPosition = MT32_DEFAULT_PANNING[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. // A new source has sent an event on this channel.
controlData.sourceVolumeApplied = false; controlData.sourceVolumeApplied = false;
controlData.source = source; 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) { switch (command) {
@ -426,12 +433,13 @@ void MidiDriver_MT32GM::processEvent(int8 source, uint32 b, uint8 outputChannel,
noteOnOff(outputChannel, command, op1, op2, source, controlData); noteOnOff(outputChannel, command, op1, op2, source, controlData);
break; break;
case MIDI_COMMAND_PITCH_BEND: case MIDI_COMMAND_PITCH_BEND:
controlData.pitchWheel = ((uint16)op2 << 7) | (uint16)op1; pitchBend(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
// fall through break;
case MIDI_COMMAND_POLYPHONIC_AFTERTOUCH: // Not supported by MT-32 or GM 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 case MIDI_COMMAND_CHANNEL_AFTERTOUCH: // Not supported by MT-32
if (!channelLockedByOtherSource) channelAftertouch(outputChannel, op1, source, controlData, channelLockedByOtherSource);
_driver->send(command | outputChannel, op1, op2);
break; break;
case MIDI_COMMAND_CONTROL_CHANGE: case MIDI_COMMAND_CONTROL_CHANGE:
controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource); 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) { void MidiDriver_MT32GM::noteOnOff(byte outputChannel, byte command, byte note, byte velocity, int8 source, MidiChannelControlData &controlData) {
if (!isOutputChannelUsed(outputChannel)) if (!isOutputChannelUsed(outputChannel))
return; return;
@ -470,6 +539,12 @@ void MidiDriver_MT32GM::noteOnOff(byte outputChannel, byte command, byte note, b
_driver->send(command | outputChannel, note, velocity); _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) { void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
assert(source < MAXIMUM_SOURCES); assert(source < MAXIMUM_SOURCES);
@ -482,6 +557,10 @@ void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber,
case MIDI_CONTROLLER_MODULATION: case MIDI_CONTROLLER_MODULATION:
controlData.modulation = controllerValue; controlData.modulation = controllerValue;
break; break;
case MIDI_CONTROLLER_DATA_ENTRY_MSB:
if (controlData.rpn == MIDI_RPN_PITCH_BEND_SENSITIVITY)
controlData.pitchBendSensitivity = controllerValue;
break;
case MIDI_CONTROLLER_VOLUME: case MIDI_CONTROLLER_VOLUME:
controlData.volume = controllerValue; controlData.volume = controllerValue;
controlData.sourceVolumeApplied = true; controlData.sourceVolumeApplied = true;
@ -528,14 +607,24 @@ void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber,
removeActiveNotes(outputChannel, true); removeActiveNotes(outputChannel, true);
} }
break; 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: case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
controlData.channelPressure = 0;
controlData.pitchWheel = MIDI_PITCH_BEND_DEFAULT; controlData.pitchWheel = MIDI_PITCH_BEND_DEFAULT;
controlData.modulation = 0; controlData.modulation = 0;
controlData.expression = 0x7F; controlData.expression = MIDI_EXPRESSION_DEFAULT;
controlData.sustain = false; controlData.sustain = false;
if (!channelLockedByOtherSource) { if (!channelLockedByOtherSource) {
removeActiveNotes(outputChannel, true); removeActiveNotes(outputChannel, true);
} }
controlData.rpn = MIDI_RPN_NULL;
break; break;
case MIDI_CONTROLLER_OMNI_ON: case MIDI_CONTROLLER_OMNI_ON:
case MIDI_CONTROLLER_OMNI_OFF: case MIDI_CONTROLLER_OMNI_OFF:
@ -750,6 +839,22 @@ byte MidiDriver_MT32GM::correctInstrumentBank(byte instrumentBank, byte patchId)
return instrumentBank != correctedBank ? correctedBank : 0xFF; 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) { void MidiDriver_MT32GM::sysEx(const byte *msg, uint16 length) {
uint16 delay = sysExNoDelay(msg, length); uint16 delay = sysExNoDelay(msg, length);
@ -972,17 +1077,21 @@ void MidiDriver_MT32GM::deinitSource(uint8 source) {
if (!isOutputChannelUsed(i)) if (!isOutputChannelUsed(i))
continue; 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; _controlData[i]->source = -1;
}
} }
_availableChannels[source] = 0xFFFF; _availableChannels[source] = 0xFFFF;
// Reset the data to output channel mapping // Reset the data to output channel mapping
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) { for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
_channelMap[source][i] = i; _channelMap[source][i] = i;
} }
// TODO Optionally reset some controllers to their
// default values? Pitch wheel, volume, sustain...
} }
void MidiDriver_MT32GM::applySourceVolume(uint8 source) { void MidiDriver_MT32GM::applySourceVolume(uint8 source) {

View file

@ -142,6 +142,7 @@ protected:
byte program; byte program;
// The Roland GS instrument bank // The Roland GS instrument bank
byte instrumentBank; byte instrumentBank;
byte channelPressure;
byte modulation; byte modulation;
// The volume specified by the MIDI data // The volume specified by the MIDI data
@ -153,17 +154,24 @@ protected:
byte expression; byte expression;
bool sustain; bool sustain;
// The currently selected Registered Parameter Number
uint16 rpn;
byte pitchBendSensitivity;
MidiChannelControlData() : source(-1), MidiChannelControlData() : source(-1),
sourceVolumeApplied(false), sourceVolumeApplied(false),
pitchWheel(MIDI_PITCH_BEND_DEFAULT), pitchWheel(MIDI_PITCH_BEND_DEFAULT),
program(0), program(0),
instrumentBank(0), instrumentBank(0),
channelPressure(0),
modulation(0), modulation(0),
volume(0), volume(0),
scaledVolume(0), scaledVolume(0),
panPosition(0x40), panPosition(MIDI_PANNING_DEFAULT),
expression(0x7F), expression(MIDI_EXPRESSION_DEFAULT),
sustain(false) { } sustain(false),
rpn(MIDI_RPN_NULL),
pitchBendSensitivity(0) { }
}; };
/** /**
@ -370,6 +378,22 @@ protected:
*/ */
virtual void processEvent(int8 source, uint32 b, uint8 outputChannel, virtual void processEvent(int8 source, uint32 b, uint8 outputChannel,
MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); 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. * Processes a note on or off MIDI event.
* This will apply source volume if necessary, update the active note * 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, virtual void noteOnOff(byte outputChannel, byte command, byte note, byte velocity,
int8 source, MidiChannelControlData &controlData); 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. * Process a control change MIDI event.
* This will update the specified control data set and apply other * 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, virtual void programChange(byte outputChannel, byte patchId, int8 source,
MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); 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. * Adds a note to the active note registration.
*/ */