AUDIO: Small MIDI driver enhancements and fixes

This commit adds the following functionality to the MIDI drivers:

- Add checking if a driver is ready to process MIDI events for a specific
source (rather than any source). To facilitate this, SysExes can now be sent
using a source number. This is stored with the SysEx data in the SysEx queue
and can be checked when isReady is called with a source number.
- Allow specifying controller default values per MIDI channel. Currently this
is only implemented for program.
- The OPL dynamic channel allocation algorithm will now respect statically
allocated channels, in case a subclass uses combined static and dynamic channel
allocation.

It also fixes the following bugs:

- Instrument remapping can now be specified using const arrays.
- OPL instrument writing code is refactored to a separate function.
- OPL note on with velocity 0 would be handled as a note off, and then
continued to be processed as a note on.
- OPL writeFrequency would always write key on bit, even if the note is not
active.
- MT-32 default channel volume was incorrect.
This commit is contained in:
Coen Rampen 2022-04-19 22:20:51 +02:00
parent 00a907f524
commit 6a9fc73962
10 changed files with 186 additions and 96 deletions

View file

@ -78,7 +78,7 @@ void AdLibBnkInstrumentOperatorDefinition::toOplInstrumentOperatorDefinition(Opl
operatorDef.waveformSelect = waveformSelect;
}
void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition& instrumentDef) {
void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition &instrumentDef) {
instrumentDef.fourOperator = false;
operator0.toOplInstrumentOperatorDefinition(instrumentDef.operator0, waveformSelect0);
@ -637,9 +637,11 @@ void MidiDriver_ADLIB_Multisource::noteOff(uint8 channel, uint8 note, uint8 velo
}
void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
if (velocity == 0)
if (velocity == 0) {
// Note on with velocity 0 is a note off.
noteOff(channel, note, velocity, source);
return;
}
InstrumentInfo instrument = determineInstrument(channel, source, note);
// If rhythm mode is on and the note is on the rhythm channel, this note
@ -687,20 +689,9 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
activeNote->instrumentId = instrument.instrumentId;
activeNote->instrumentDef = instrument.instrumentDef;
// Calculate operator volumes and write operator definitions to
// the OPL registers.
for (int i = 0; i < instrument.instrumentDef->getNumberOfOperators(); i++) {
uint16 operatorOffset = determineOperatorRegisterOffset(oplChannel, i, instrument.instrumentDef->rhythmType, instrument.instrumentDef->fourOperator);
const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
writeRegister(OPL_REGISTER_BASE_DECAY_ATTACK + operatorOffset, operatorDef.decayAttack);
writeRegister(OPL_REGISTER_BASE_RELEASE_SUSTAIN + operatorOffset, operatorDef.releaseSustain);
writeRegister(OPL_REGISTER_BASE_WAVEFORMSELECT + operatorOffset, operatorDef.waveformSelect);
}
// Write out the instrument definition, volume and panning.
writeInstrument(oplChannel, instrument);
// Determine and write panning and write feedback and connection.
writePanning(oplChannel, instrument.instrumentDef->rhythmType);
// Calculate and write frequency and block and write key on bit.
writeFrequency(oplChannel, instrument.instrumentDef->rhythmType);
@ -768,10 +759,6 @@ void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller
}
void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) {
if (_instrumentRemapping && channel != MIDI_RHYTHM_CHANNEL)
// Apply instrument remapping (if specified) to instrument channels.
program = _instrumentRemapping[program];
// Just set the MIDI program value; this event does not affect active notes.
_controlData[source][channel].program = program;
}
@ -861,8 +848,8 @@ void MidiDriver_ADLIB_Multisource::applyControllerDefaults(uint8 source) {
}
} else {
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
if (_controllerDefaults.program >= 0) {
_controlData[source][i].program = _controllerDefaults.program;
if (_controllerDefaults.program[i] >= 0) {
_controlData[source][i].program = _controllerDefaults.program[i];
}
if (_controllerDefaults.channelPressure >= 0) {
_controlData[source][i].channelPressure = _controllerDefaults.channelPressure;
@ -1093,12 +1080,15 @@ void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
}
}
if (_rhythmMode && !_rhythmModeIgnoreNoteOffs && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
bool rhythmChanged = false;
for (int i = 0; i < 5; i++) {
if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
_activeRhythmNotes[i].noteActive = false;
rhythmChanged = true;
}
}
writeRhythm();
if (rhythmChanged)
writeRhythm();
}
_activeNotesMutex.unlock();
@ -1303,7 +1293,11 @@ MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::deter
} else {
// On non-rhythm channels, use the active instrument (program) on the
// MIDI channel.
instrument.instrumentId = _controlData[source][channel].program;
byte program = _controlData[source][channel].program;
if (_instrumentRemapping)
// Apply instrument remapping (if specified).
program = _instrumentRemapping[program];
instrument.instrumentId = program;
instrument.instrumentDef = &_instrumentBank[instrument.instrumentId];
instrument.oplNote = note;
}
@ -1332,6 +1326,10 @@ uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 sour
uint32 inactiveNoteCounter = 0xFFFF, instrumentNoteCounter = 0xFFFF, lowestNoteCounter = 0xFFFF;
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].channelAllocated)
// Channel has been statically allocated. Try the next channel.
continue;
if (_activeNotes[oplChannel].noteCounterValue == 0) {
// This channel is unused. No need to look any further.
unusedChannel = oplChannel;
@ -1538,7 +1536,7 @@ int32 MidiDriver_ADLIB_Multisource::calculatePitchBend(uint8 channel, uint8 sour
return pitchBend;
}
uint8 MidiDriver_ADLIB_Multisource::calculateVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition& instrumentDef, uint8 operatorNum) {
uint8 MidiDriver_ADLIB_Multisource::calculateVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
// Get the volume (level) for this operator from the instrument definition.
uint8 operatorDefVolume = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
@ -1754,6 +1752,26 @@ uint16 MidiDriver_ADLIB_Multisource::determineChannelRegisterOffset(uint8 oplCha
return offset + (oplChannel % numChannelsPerSet);
}
void MidiDriver_ADLIB_Multisource::writeInstrument(uint8 oplChannel, InstrumentInfo instrument) {
ActiveNote *activeNote = (instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1]);
activeNote->instrumentDef = instrument.instrumentDef;
// Calculate operator volumes and write operator definitions to
// the OPL registers.
for (int i = 0; i < instrument.instrumentDef->getNumberOfOperators(); i++) {
uint16 operatorOffset = determineOperatorRegisterOffset(oplChannel, i, instrument.instrumentDef->rhythmType, instrument.instrumentDef->fourOperator);
const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
writeRegister(OPL_REGISTER_BASE_DECAY_ATTACK + operatorOffset, operatorDef.decayAttack);
writeRegister(OPL_REGISTER_BASE_RELEASE_SUSTAIN + operatorOffset, operatorDef.releaseSustain);
writeRegister(OPL_REGISTER_BASE_WAVEFORMSELECT + operatorOffset, operatorDef.waveformSelect);
}
// Determine and write panning and write feedback and connection.
writePanning(oplChannel, instrument.instrumentDef->rhythmType);
}
void MidiDriver_ADLIB_Multisource::writeKeyOff(uint8 oplChannel, OplInstrumentRhythmType rhythmType, bool forceWrite) {
_activeNotesMutex.lock();
@ -1858,7 +1876,7 @@ void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel, OplInstrumen
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(frequency >> 8) | (rhythmType == RHYTHM_TYPE_UNDEFINED ? OPL_MASK_KEYON : 0));
(frequency >> 8) | (rhythmType == RHYTHM_TYPE_UNDEFINED && activeNote->noteActive ? OPL_MASK_KEYON : 0));
_activeNotesMutex.unlock();
}
@ -1866,9 +1884,11 @@ void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel, OplInstrumen
void MidiDriver_ADLIB_Multisource::writeRegister(uint16 reg, uint8 value, bool forceWrite) {
//debug("Writing register %X %X", reg, value);
// Write the value to the register if forceWrite is specified or if the
// new register value is different from the current value.
if (forceWrite || _shadowRegisters[reg] != value) {
// Write the value to the register if it is a timer register, if forceWrite
// is specified or if the new register value is different from the current
// value.
if ((reg >= 1 && reg <= 3) || (_oplType == OPL::Config::kDualOpl2 && reg >= 0x101 && reg <= 0x103) ||
forceWrite || _shadowRegisters[reg] != value) {
_shadowRegisters[reg] = value;
_opl->writeReg(reg, value);
}

View file

@ -606,15 +606,15 @@ public:
uint32 property(int prop, uint32 param) override;
uint32 getBaseTempo() override;
/**
* This driver does not use MidiChannel objects, so this function returns 0.
* This driver does not use MidiChannel objects, so this function returns nullptr.
*
* @return 0
* @return nullptr
*/
MidiChannel *allocateChannel() override;
/**
* This driver does not use MidiChannel objects, so this function returns 0.
* This driver does not use MidiChannel objects, so this function returns nullptr.
*
* @return 0
* @return nullptr
*/
MidiChannel *getPercussionChannel() override;
@ -1030,7 +1030,14 @@ protected:
* @return The offset to the base register for this channel.
*/
uint16 determineChannelRegisterOffset(uint8 oplChannel, bool fourOperator = false);
/**
* Writes the specified instrument definition to the specified OPL channel.
* It will calculate volume and panning if necessary.
*
* @param oplChannel The OPL channel on which to write the instrument.
* @param instrument The data of the instrument to write.
*/
void writeInstrument(uint8 oplChannel, InstrumentInfo instrument);
/**
* Sets the key on bit to false for the specified OPL channel or rhythm
* instrument and updates _activeNotes or _activeRhythmNotes with the new
@ -1076,7 +1083,7 @@ protected:
* calculated and written. Use type undefined to calculate panning for a
* melodic instrument.
*/
void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
virtual void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
/**
* Calculates the frequency for the active note on the specified OPL
* channel or of the specified rhythm type (@see calculateFrequency) and
@ -1088,7 +1095,7 @@ protected:
* be calculated and written. Use type undefined to calculate the frequency
* for a melodic instrument.
*/
void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
virtual void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
/**
* Writes the specified value to the specified OPL register.

View file

@ -244,8 +244,11 @@ public:
* A driver implementation might need time to prepare playback of
* a track. Use this function to check if the driver is ready to
* receive MIDI events.
*
* @param source Check if the driver is ready to receive events from this
* specific source. Specify -1 to check readiness regardless of source.
*/
virtual bool isReady() { return true; }
virtual bool isReady(int8 source = -1) { return true; }
protected:

View file

@ -37,19 +37,20 @@ MidiDriver_Multisource::MidiSource::MidiSource() :
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) { }
// The -1 value indicates no default value should be set on the controller.
instrumentBank(-1),
drumkit(-1),
channelPressure(-1),
pitchBend(-1),
modulation(-1),
volume(-1),
panning(-1),
expression(-1),
sustain(-1),
rpn(-1),
pitchBendSensitivity(-1) {
Common::fill(program, program + ARRAYSIZE(program), -1);
}
MidiDriver_Multisource::MidiDriver_Multisource() :
_instrumentRemapping(nullptr),
@ -238,7 +239,7 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, in
// corresponding to the specified controller.
switch (type) {
case CONTROLLER_DEFAULT_PROGRAM:
_controllerDefaults.program = value;
Common::fill(_controllerDefaults.program, _controllerDefaults.program + ARRAYSIZE(_controllerDefaults.program), value);
break;
case CONTROLLER_DEFAULT_INSTRUMENT_BANK:
_controllerDefaults.instrumentBank = value;
@ -279,6 +280,19 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, in
}
}
void MidiDriver_Multisource::setControllerDefaults(ControllerDefaultType type, int16 *value) {
// Set the specified default values on _controllerDefaults on the field
// corresponding to the specified controller.
switch (type) {
case CONTROLLER_DEFAULT_PROGRAM:
Common::copy(value, value + ARRAYSIZE(_controllerDefaults.program), _controllerDefaults.program);
break;
default:
warning("MidiDriver_Multisource::setControllerDefaults - Unsupported controller default type %i", type);
break;
}
}
void MidiDriver_Multisource::clearControllerDefault(ControllerDefaultType type) {
// Reset the default value for this controller to -1.
setControllerDefault(type, -1);
@ -347,7 +361,7 @@ void MidiDriver_Multisource::setSourceNeutralVolume(uint8 source, uint16 volume)
_sources[source].neutralVolume = volume;
}
void MidiDriver_Multisource::setInstrumentRemapping(byte *instrumentRemapping) {
void MidiDriver_Multisource::setInstrumentRemapping(const byte *instrumentRemapping) {
_instrumentRemapping = instrumentRemapping;
}

View file

@ -189,7 +189,7 @@ protected:
// 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 program[16];
int8 instrumentBank;
int8 drumkit;
@ -353,6 +353,20 @@ public:
* @param value The default value which should be set.
*/
void setControllerDefault(ControllerDefaultType type, int16 value);
/**
* Specify a default value for a controller which should be set when a new
* track is started. This expects an array of values, each of which will
* be used as the default for the corresponding MIDI channel.
*
* This is currently only supported for program.
*
* See setControllerDefault for more details.
*
* @param type The controller which should be reset.
* @param values The default values which should be set. Must be a 16 value
* array.
*/
void setControllerDefaults(ControllerDefaultType type, int16 *values);
/**
* Clears a previously set default value for the specified controller.
*
@ -373,7 +387,7 @@ public:
* @param instrumentRemapping The instrument map that should be used for
* remapping, or nullptr to disable remapping.
*/
void setInstrumentRemapping(byte *instrumentRemapping);
void setInstrumentRemapping(const byte *instrumentRemapping);
/**
* Applies the user volume settings to the MIDI driver. MIDI channel
@ -439,7 +453,7 @@ protected:
ControllerDefaults _controllerDefaults;
// Map for arbitrary instrument remapping.
byte *_instrumentRemapping;
const byte *_instrumentRemapping;
// True if the driver should scale MIDI channel volume to the user
// specified volume settings.

View file

@ -1224,8 +1224,8 @@ void MidiDriver_Miles_AdLib::applyControllerDefaults(uint8 source) {
return;
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
if (_controllerDefaults.program >= 0) {
_midiChannels[i].currentProgram = _controllerDefaults.program;
if (_controllerDefaults.program[i] >= 0) {
_midiChannels[i].currentProgram = _controllerDefaults.program[i];
}
if (_controllerDefaults.pitchBend >= 0) {
_midiChannels[i].currentPitchBender = _controllerDefaults.pitchBend;

View file

@ -378,6 +378,24 @@ void MidiDriver_MT32GM::close() {
}
}
bool MidiDriver_MT32GM::isReady(int8 source) {
Common::StackLock lock(_sysExQueueMutex);
// For an unspecified source, just return if the queue is empty or not.
if (source < 0)
return _sysExQueue.empty();
// For a specific source, check if there is a SysEx for that source in the
// queue.
for (Common::ListInternal::Iterator<SysExData> it = _sysExQueue.begin();
it != _sysExQueue.end(); it++) {
if (it->source == source)
return false;
}
return true;
}
uint32 MidiDriver_MT32GM::property(int prop, uint32 param) {
switch (prop) {
case PROP_MIDI_DATA_REVERSE_PANNING:
@ -458,36 +476,36 @@ void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControl
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);
controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, _controllerDefaults.instrumentBank, source, controlData);
controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData);
}
if (_controllerDefaults.program >= 0 && controlData.program != _controllerDefaults.program) {
send(source, MIDI_COMMAND_PROGRAM_CHANGE | outputChannel, _controllerDefaults.program, 0);
if (_controllerDefaults.program[outputChannel] >= 0 && controlData.program != _controllerDefaults.program[outputChannel]) {
programChange(outputChannel, _controllerDefaults.program[outputChannel], source, controlData);
}
} else {
// Apply default drumkit only to the rhythm channel.
if (_controllerDefaults.drumkit >= 0 && controlData.program != _controllerDefaults.drumkit) {
send(source, MIDI_COMMAND_PROGRAM_CHANGE | outputChannel, _controllerDefaults.drumkit, 0);
programChange(outputChannel, _controllerDefaults.drumkit, source, controlData);
}
}
if (_controllerDefaults.channelPressure >= 0 && controlData.channelPressure != _controllerDefaults.channelPressure) {
send(source, MIDI_COMMAND_CHANNEL_AFTERTOUCH | outputChannel, _controllerDefaults.channelPressure, 0);
channelAftertouch(outputChannel, _controllerDefaults.channelPressure, source, controlData);
}
if (_controllerDefaults.pitchBend >= 0 && controlData.pitchWheel != _controllerDefaults.pitchBend) {
send(source, MIDI_COMMAND_PITCH_BEND | outputChannel, _controllerDefaults.pitchBend & 0x7F, _controllerDefaults.pitchBend >> 7);
pitchBend(outputChannel, _controllerDefaults.pitchBend & 0x7F, _controllerDefaults.pitchBend >> 7, source, controlData);
}
if (_controllerDefaults.modulation >= 0 && controlData.modulation != _controllerDefaults.modulation) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_MODULATION, _controllerDefaults.modulation);
controlChange(outputChannel, MIDI_CONTROLLER_MODULATION, _controllerDefaults.modulation, source, controlData);
}
if (_controllerDefaults.volume >= 0 && controlData.volume != _controllerDefaults.volume) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_VOLUME, _controllerDefaults.volume);
controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, _controllerDefaults.volume, source, controlData);
}
if (_controllerDefaults.panning >= 0 && controlData.panPosition != _controllerDefaults.panning) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_PANNING, _controllerDefaults.panning);
controlChange(outputChannel, MIDI_CONTROLLER_PANNING, _controllerDefaults.panning, source, controlData);
}
if (_controllerDefaults.expression >= 0 && controlData.expression != _controllerDefaults.expression) {
send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_EXPRESSION, _controllerDefaults.expression);
controlChange(outputChannel, MIDI_CONTROLLER_EXPRESSION, _controllerDefaults.expression, source, controlData);
}
// RPN will be changed by setting pitch bend sensitivity, so store the
@ -495,10 +513,10 @@ void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControl
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);
controlChange(outputChannel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8, source, controlData);
controlChange(outputChannel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF, source, controlData);
controlChange(outputChannel, MIDI_CONTROLLER_DATA_ENTRY_MSB, _controllerDefaults.pitchBendSensitivity, source, controlData);
controlChange(outputChannel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0, source, controlData);
if (rpn != controlData.rpn)
// Active RPN was changed; reset it to previous value (or default).
setRpn = true;
@ -510,8 +528,8 @@ void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControl
}
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);
controlChange(outputChannel, MIDI_CONTROLLER_RPN_MSB, rpn >> 8, source, controlData);
controlChange(outputChannel, MIDI_CONTROLLER_RPN_LSB, rpn & 0xFF, source, controlData);
}
}
@ -712,16 +730,16 @@ void MidiDriver_MT32GM::removeActiveNotes(uint8 outputChannel, bool sustainedNot
}
void MidiDriver_MT32GM::programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
if (_instrumentRemapping && outputChannel != MIDI_RHYTHM_CHANNEL)
// Apply instrument remapping (if specified) to instrument channels.
patchId = _instrumentRemapping[patchId];
// remember patch id for the current MIDI-channel
controlData.program = patchId;
if (channelLockedByOtherSource)
return;
if (_instrumentRemapping && outputChannel != MIDI_RHYTHM_CHANNEL)
// Apply instrument remapping (if specified) to instrument channels.
patchId = _instrumentRemapping[patchId];
if (_midiType == MT_MT32) {
if (outputChannel == MIDI_RHYTHM_CHANNEL &&
!(!_nativeMT32 && _enableGS && patchId == 0x7F)) {
@ -881,17 +899,18 @@ uint16 MidiDriver_MT32GM::sysExNoDelay(const byte *msg, uint16 length) {
return delay;
}
void MidiDriver_MT32GM::sysExQueue(const byte *msg, uint16 length) {
void MidiDriver_MT32GM::sysExQueue(const byte *msg, uint16 length, int8 source) {
SysExData sysEx;
memcpy(sysEx.data, msg, length);
sysEx.length = length;
sysEx.source = source;
_sysExQueueMutex.lock();
_sysExQueue.push(sysEx);
_sysExQueue.push_back(sysEx);
_sysExQueueMutex.unlock();
}
uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay) {
uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay, int8 source) {
if (!_nativeMT32)
// MT-32 SysExes have no effect on GM devices.
return 0;
@ -934,7 +953,7 @@ uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32
sysExMessage[sysExPos++] = sysExChecksum & 0x7F;
if (queue) {
sysExQueue(sysExMessage, sysExPos);
sysExQueue(sysExMessage, sysExPos, source);
} else if (!delay) {
return sysExNoDelay(sysExMessage, sysExPos);
} else {
@ -1071,6 +1090,20 @@ int8 MidiDriver_MT32GM::mapSourceChannel(uint8 source, uint8 dataChannel) {
void MidiDriver_MT32GM::deinitSource(uint8 source) {
assert(source < MAXIMUM_SOURCES);
_sysExQueueMutex.lock();
// Remove any pending SysExes for this source from the queue.
Common::ListInternal::Iterator<SysExData> it = _sysExQueue.begin();
while (it != _sysExQueue.end()) {
if (it->source == source) {
it = _sysExQueue.erase(it);
} else {
it++;
}
}
_sysExQueueMutex.unlock();
MidiDriver_Multisource::deinitSource(source);
// Free channels which were used by this source.
@ -1082,7 +1115,7 @@ void MidiDriver_MT32GM::deinitSource(uint8 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);
controlChange(i, MIDI_CONTROLLER_SUSTAIN, _controllerDefaults.sustain, _controlData[i]->source, *_controlData[i]);
}
_controlData[i]->source = -1;
@ -1133,8 +1166,9 @@ void MidiDriver_MT32GM::onTimer() {
if (!_sysExQueue.empty() && _sysExDelay == 0) {
// Ready to send next SysEx message to the MIDI device
SysExData sysEx = _sysExQueue.pop();
SysExData sysEx = _sysExQueue.front();
_sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
_sysExQueue.pop_front();
}
_sysExQueueMutex.unlock();

View file

@ -24,8 +24,8 @@
#include "audio/mididrv.h"
#include "audio/mididrv_ms.h"
#include "common/list.h"
#include "common/mutex.h"
#include "common/queue.h"
/**
* @defgroup audio_mt32_gm MIDI driver for MT-32 and GM
@ -112,7 +112,7 @@ class MidiDriver_MT32GM : public MidiDriver_Multisource {
public:
static const byte MT32_DEFAULT_INSTRUMENTS[8];
static const byte MT32_DEFAULT_PANNING[8];
static const uint8 MT32_DEFAULT_CHANNEL_VOLUME = 98;
static const uint8 MT32_DEFAULT_CHANNEL_VOLUME = 102;
static const uint8 GM_DEFAULT_CHANNEL_VOLUME = 100;
// Map for correcting Roland GS drumkit numbers.
static const uint8 GS_DRUMKIT_FALLBACK_MAP[128];
@ -239,7 +239,8 @@ protected:
struct SysExData {
byte data[270];
uint16 length;
SysExData() : length(0) {
int8 source;
SysExData() : length(0), source(-1) {
memset(data, 0, sizeof(data));
}
};
@ -259,10 +260,7 @@ public:
virtual int open(MidiDriver *driver, bool nativeMT32);
void close() override;
bool isOpen() const override { return _isOpen; }
bool isReady() override {
Common::StackLock lock(_sysExQueueMutex);
return _sysExQueue.empty();
}
bool isReady(int8 source = -1) override;
uint32 property(int prop, uint32 param) override;
using MidiDriver_BASE::send;
@ -277,7 +275,7 @@ public:
* MIDI messages (not using the queue) should not be sent until the queue
* is empty.
*/
void sysExQueue(const byte *msg, uint16 length);
void sysExQueue(const byte *msg, uint16 length, int8 source = -1);
/**
* Write data to an MT-32 memory location using a SysEx message.
* This function will add the necessary header and checksum bytes.
@ -297,7 +295,7 @@ public:
* it is the caller's responsibility to make sure that the next SysEx is
* not sent before this time has passed.
*/
uint16 sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue = false, bool delay = true);
uint16 sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue = false, bool delay = true, int8 source = -1);
void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
void stopAllNotes(bool stopSustainedNotes = false) override;
@ -584,7 +582,7 @@ protected:
// SysEx message can be sent.
uint32 _sysExDelay;
// Queue of SysEx messages to be sent to the MIDI device.
Common::Queue<SysExData> _sysExQueue;
Common::List<SysExData> _sysExQueue;
// Mutex for write access to the SysEx queue.
Common::Mutex _sysExQueueMutex;
};

View file

@ -512,8 +512,8 @@ void MusicPlayerXMI::stopAllNotes(bool stopSustainedNotes) {
_driver->stopAllNotes(stopSustainedNotes);
}
bool MusicPlayerXMI::isReady() {
return _driver ? _driver->isReady() : false;
bool MusicPlayerXMI::isReady(int8 source) {
return _driver ? _driver->isReady(source) : false;
}
void MusicPlayerXMI::updateVolume() {

View file

@ -155,7 +155,7 @@ public:
if (_milesXmidiTimbres)
_milesXmidiTimbres->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
};
bool isReady() override;
bool isReady(int8 source = -1) override;
void setUserVolume(uint16 volume) override;