[Scummvm-git-logs] scummvm master -> 1bd9e614174c9984765976642c4ff3c349b4741d
criezy
criezy at scummvm.org
Sat Nov 28 17:59:21 UTC 2020
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
7857df2ea9 MIDI: MT-32 / GM driver
4834651115 MIDI: Add multisource support to SMF parser
1bd9e61417 MIDI: Fix virtual destructor and nullptr warnings
Commit: 7857df2ea949b593d2dd454cb5d1268946fb1b86
https://github.com/scummvm/scummvm/commit/7857df2ea949b593d2dd454cb5d1268946fb1b86
Author: NMIError (crampen at gmail.com)
Date: 2020-11-28T17:59:15Z
Commit Message:
MIDI: MT-32 / GM driver
This adds a new MidiDriver subclass with functionality
for MIDI based devices like the MT-32 and GM devices
or emulators.
Changed paths:
A audio/mt32gm.cpp
A audio/mt32gm.h
audio/mididrv.cpp
audio/mididrv.h
audio/miles.h
audio/miles_adlib.cpp
audio/miles_midi.cpp
audio/module.mk
engines/sci/sound/drivers/midi.cpp
diff --git a/audio/mididrv.cpp b/audio/mididrv.cpp
index 02fb25a1e8..01827d95e0 100644
--- a/audio/mididrv.cpp
+++ b/audio/mididrv.cpp
@@ -57,45 +57,6 @@ const byte MidiDriver::_gmToMt32[128] = {
101, 103, 100, 120, 117, 113, 99, 128, 128, 128, 128, 124, 123, 128, 128, 128, // 7x
};
-// These are the power-on default instruments of the Roland MT-32 family.
-const byte MidiDriver::_mt32DefaultInstruments[8] = {
- 0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A
-};
-
-// These are the power-on default panning settings for channels 2-9 of the Roland MT-32 family.
-// Internally, the MT-32 has 15 panning positions (0-E with 7 being center).
-// This has been translated to the equivalent MIDI panning values (0-127).
-// These are used for setting default panning on GM devices when using them with MT-32 data.
-// Note that MT-32 panning is reversed compared to the MIDI specification. This is not reflected
-// here; the driver is expected to flip these values based on the _reversePanning variable.
-const byte MidiDriver::_mt32DefaultPanning[8] = {
- // 7, 8, 7, 8, 4, A, 0, E
- 0x40, 0x49, 0x40, 0x49, 0x25, 0x5B, 0x00, 0x7F
-};
-
-// This is the drum map for the Roland Sound Canvas SC-55 v1.xx. It had a fallback mechanism
-// to correct invalid drumkit selections. Some games rely on this mechanism to select the
-// correct Roland GS drumkit. Use this map to emulate this mechanism.
-// E.g. correct invalid drumkit 50: _gsDrumkitFallbackMap[50] == 48
-const uint8 MidiDriver::_gsDrumkitFallbackMap[128] = {
- 0, 0, 0, 0, 0, 0, 0, 0, // STANDARD
- 8, 8, 8, 8, 8, 8, 8, 8, // ROOM
- 16, 16, 16, 16, 16, 16, 16, 16, // POWER
- 24, 25, 24, 24, 24, 24, 24, 24, // ELECTRONIC; TR-808 (25)
- 32, 32, 32, 32, 32, 32, 32, 32, // JAZZ
- 40, 40, 40, 40, 40, 40, 40, 40, // BRUSH
- 48, 48, 48, 48, 48, 48, 48, 48, // ORCHESTRA
- 56, 56, 56, 56, 56, 56, 56, 56, // SFX
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined (fall back to STANDARD)
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
- 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
- 0, 0, 0, 0, 0, 0, 0, 127 // No drumkit defined; CM-64/32L (127)
-};
-
static const struct {
uint32 type;
const char *guio;
@@ -467,171 +428,12 @@ MidiDriver::DeviceHandle MidiDriver::getDeviceHandle(const Common::String &ident
return 0;
}
-void MidiDriver::initMT32(bool initForGM) {
- sendMT32Reset();
-
- if (initForGM) {
- // Set up MT-32 for GM data.
- // This is based on Roland's GM settings for MT-32.
- debug("Initializing MT-32 for General MIDI data");
-
- byte buffer[17];
-
- // Roland MT-32 SysEx for system area
- memcpy(&buffer[0], "\x41\x10\x16\x12\x10\x00", 6);
-
- // Set reverb parameters:
- // - Mode 2 (Plate)
- // - Time 3
- // - Level 4
- memcpy(&buffer[6], "\x01\x02\x03\x04\x66", 5);
- sysEx(buffer, 11);
-
- // Set partial reserve to match SC-55
- memcpy(&buffer[6], "\x04\x08\x04\x04\x03\x03\x03\x03\x02\x02\x4C", 11);
- sysEx(buffer, 17);
-
- // Use MIDI instrument channels 1-8 instead of 2-9
- memcpy(&buffer[6], "\x0D\x00\x01\x02\x03\x04\x05\x06\x07\x09\x3E", 11);
- sysEx(buffer, 17);
-
- // The MT-32 has reversed stereo panning compared to the MIDI spec.
- // GM does use panning as specified by the MIDI spec.
- _reversePanning = true;
-
- int i;
-
- // Set default GM panning (center on all channels)
- for (i = 0; i < 8; ++i) {
- send((0x40 << 16) | (10 << 8) | (0xB0 | i));
- }
-
- // Set default GM instruments (0 on all channels).
- // This is expected to be mapped to the MT-32 equivalent by the driver.
- for (i = 0; i < 8; ++i) {
- send((0 << 8) | (0xC0 | i));
- }
-
- // Set Pitch Bend Sensitivity to 2 semitones.
- for (i = 0; i < 8; ++i) {
- setPitchBendRange(i, 2);
- }
- setPitchBendRange(9, 2);
- }
-}
-
void MidiDriver::sendMT32Reset() {
static const byte resetSysEx[] = { 0x41, 0x10, 0x16, 0x12, 0x7F, 0x00, 0x00, 0x01, 0x00 };
sysEx(resetSysEx, sizeof(resetSysEx));
g_system->delayMillis(100);
}
-void MidiDriver::initGM(bool initForMT32, bool enableGS) {
- sendGMReset();
-
- if (initForMT32) {
- // Set up the GM device for MT-32 MIDI data.
- // Based on iMuse implementation (which is based on Roland's MT-32 settings for GS)
- debug("Initializing GM device for MT-32 MIDI data");
-
- // The MT-32 has reversed stereo panning compared to the MIDI spec.
- // GM does use panning as specified by the MIDI spec.
- _reversePanning = true;
-
- int i;
-
- // Set the default panning for the MT-32 instrument channels.
- for (i = 1; i < 9; ++i) {
- send((_mt32DefaultPanning[i - 1] << 16) | (10 << 8) | (0xB0 | i));
- }
-
- // Set Channels 1-16 Reverb to 64, which is the
- // equivalent of MT-32 default Reverb Level 5
- for (i = 0; i < 16; ++i)
- send((64 << 16) | (91 << 8) | (0xB0 | i));
-
- // Set Channels 1-16 Chorus to 0. The MT-32 has no chorus capability.
- // (This is probably the default for many GM devices with chorus anyway.)
- for (i = 0; i < 16; ++i)
- send((0 << 16) | (93 << 8) | (0xB0 | i));
-
- // Set Channels 1-16 Pitch Bend Sensitivity to 12 semitones.
- for (i = 0; i < 16; ++i) {
- setPitchBendRange(i, 12);
- }
-
- if (enableGS) {
- // GS specific settings for MT-32 instrument mapping.
- debug("Additional initialization of GS device for MT-32 MIDI data");
-
- // Note: All Roland GS devices support CM-64/32L maps
-
- // Set Percussion Channel to SC-55 Map (CC#32, 01H), then
- // Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
- // Bank select MSB: bank 0
- getPercussionChannel()->controlChange(0, 0);
- // Bank select LSB: map 1 (SC-55)
- getPercussionChannel()->controlChange(32, 1);
- // Patch change: 127 (CM-64/32L)
- send(127 << 8 | 0xC0 | 9);
-
- // Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
- for (i = 0; i < 16; ++i) {
- if (i == getPercussionChannel()->getNumber())
- continue;
- // Bank select MSB: bank 127 (CM-64/32L)
- send((127 << 16) | (0 << 8) | (0xB0 | i));
- // Bank select LSB: map 1 (SC-55)
- send((1 << 16) | (32 << 8) | (0xB0 | i));
- // Patch change: 0 (causes bank select to take effect)
- send((0 << 16) | (0 << 8) | (0xC0 | i));
- }
-
- byte buffer[12];
-
- // Roland GS SysEx ID
- memcpy(&buffer[0], "\x41\x10\x42\x12", 4);
-
- // Set channels 1-16 Mod. LFO1 Pitch Depth to 4
- memcpy(&buffer[4], "\x40\x20\x04\x04\x18", 5);
- for (i = 0; i < 16; ++i) {
- buffer[5] = 0x20 + i;
- buffer[8] = 0x18 - i;
- sysEx(buffer, 9);
- }
-
- // In Roland's GS MT-32 emulation settings, percussion channel expression
- // is locked at 80. This corrects a difference in volume of the SC-55 MT-32
- // drum kit vs the drums of the MT-32. However, this approach has a problem:
- // the MT-32 supports expression on the percussion channel, so MIDI data
- // which uses this will play incorrectly. So instead, percussion channel
- // volume will be scaled by the driver by a factor 80/127.
- // Strangely, the regular GM drum kit does have a volume that matches the
- // MT-32 drums, so scaling is only necessary when using GS MT-32 emulation.
- _scaleGSPercussionVolumeToMT32 = true;
-
- // Change Reverb settings (as used by Roland):
- // - Character: 0
- // - Pre-LPF: 4
- // - Level: 35h
- // - Time: 6Ah
- memcpy(&buffer[4], "\x40\x01\x31\x00\x04\x35\x6A\x6B", 8);
- sysEx(buffer, 12);
- }
-
- // Set the default MT-32 patches. For non-GS devices these are expected to be
- // mapped to the GM equivalents by the driver.
- for (i = 1; i < 9; ++i) {
- send((_mt32DefaultInstruments[i - 1] << 8) | (0xC0 | i));
- }
-
- // Regarding Master Tune: 442 kHz was intended for the MT-32 family, but
- // apparently due to a firmware bug the master tune was actually 440 kHz for
- // all models (see MUNT source code for more details). So master tune is left
- // at 440 kHz for GM devices playing MT-32 MIDI data.
- }
-}
-
void MidiDriver::sendGMReset() {
static const byte gmResetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 };
sysEx(gmResetSysEx, sizeof(gmResetSysEx));
@@ -648,79 +450,6 @@ void MidiDriver::sendGMReset() {
g_system->delayMillis(100);
}
-byte MidiDriver::correctInstrumentBank(byte outputChannel, byte patchId) {
- if (_gsBank[outputChannel] == 0 || patchId >= 120 || _gsBank[outputChannel] >= 64)
- // Usually, no bank select has been sent and no correction is
- // necessary.
- // No correction is performed on banks 64-127 or on the SFX
- // instruments (120-127).
- return 0xFF;
-
- // Determine the intended bank. This emulates the behavior of the
- // Roland SC-55 v1.2x. Instruments have 2, 1 or 0 sub-capital tones.
- // Depending on the selected bank and the selected instrument, the
- // bank will "fall back" to a sub-capital tone or to the capital
- // tone (bank 0).
- byte correctedBank = 0xFF;
-
- switch (patchId) {
- case 25: // Steel-String Guitar / 12-string Guitar / Mandolin
- // This instrument has 2 sub-capital tones. Bank selects 17-63
- // are corrected to the second sub-capital tone at 16.
- if (_gsBank[outputChannel] >= 16) {
- correctedBank = 16;
- break;
- }
- // Corrections for values below 16 are handled below.
-
- // fall through
- case 4: // Electric Piano 1 / Detuned Electric Piano 1
- case 5: // Electric Piano 2 / Detuned Electric Piano 2
- case 6: // Harpsichord / Coupled Harpsichord
- case 14: // Tubular-bell / Church Bell
- case 16: // Organ 1 / Detuned Organ 1
- case 17: // Organ 2 / Detuned Organ 2
- case 19: // Church Organ 1 / Church Organ 2
- case 21: // Accordion Fr / Accordion It
- case 24: // Nylon-string Guitar / Ukelele
- case 26: // Jazz Guitar / Hawaiian Guitar
- case 27: // Clean Guitar / Chorus Guitar
- case 28: // Muted Guitar / Funk Guitar
- case 30: // Distortion Guitar / Feedback Guitar
- case 31: // Guitar Harmonics / Guitar Feedback
- case 38: // Synth Bass 1 / Synth Bass 3
- case 39: // Synth Bass 2 / Synth Bass 4
- case 48: // Strings / Orchestra
- case 50: // Synth Strings 1 / Synth Strings 3
- case 61: // Brass 1 / Brass 2
- case 62: // Synth Brass 1 / Synth Brass 3
- case 63: // Synth Brass 2 / Synth Brass 4
- case 80: // Square Wave / Sine Wave
- case 107: // Koto / Taisho Koto
- case 115: // Woodblock / Castanets
- case 116: // Taiko / Concert BD
- case 117: // Melodic Tom 1 / Melodic Tom 2
- case 118: // Synth Drum / 808 Tom
- // These instruments have one sub-capital tone. Bank selects 9-63
- // are corrected to the sub-capital tone at 8.
- if (_gsBank[outputChannel] >= 8) {
- correctedBank = 8;
- break;
- }
- // Corrections for values below 8 are handled below.
-
- // fall through
- default:
- // The other instruments only have a capital tone. Bank selects
- // 1-63 are corrected to the capital tone.
- correctedBank = 0;
- break;
- }
-
- // Return the corrected bank, or 0xFF if no correction is needed.
- return _gsBank[outputChannel] != correctedBank ? correctedBank : 0xFF;
-}
-
void MidiDriver_BASE::midiDumpInit() {
g_system->displayMessageOnOSD(_("Starting MIDI dump"));
_midiDumpCache.clear();
@@ -837,9 +566,9 @@ void MidiDriver_BASE::send(int8 source, byte status, byte firstOp, byte secondOp
void MidiDriver_BASE::stopAllNotes(bool stopSustainedNotes) {
for (int i = 0; i < 16; ++i) {
- send(0xB0 | i, 0x7B, 0); // All notes off
+ send(0xB0 | i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
if (stopSustainedNotes)
- send(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608)
+ send(0xB0 | i, MIDI_CONTROLLER_SUSTAIN, 0); // Also send a sustain off event (bug #3116608)
}
}
diff --git a/audio/mididrv.h b/audio/mididrv.h
index 6f37125cd7..acba961ccf 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -91,6 +91,44 @@ enum MidiDriverFlags {
*/
class MidiDriver_BASE {
public:
+ static const uint8 MIDI_CHANNEL_COUNT = 16;
+ static const uint8 MIDI_RHYTHM_CHANNEL = 9;
+
+ static const byte MIDI_COMMAND_NOTE_OFF = 0x80;
+ static const byte MIDI_COMMAND_NOTE_ON = 0x90;
+ static const byte MIDI_COMMAND_POLYPHONIC_AFTERTOUCH = 0xA0;
+ static const byte MIDI_COMMAND_CONTROL_CHANGE = 0xB0;
+ static const byte MIDI_COMMAND_PROGRAM_CHANGE = 0xC0;
+ static const byte MIDI_COMMAND_CHANNEL_AFTERTOUCH = 0xD0;
+ static const byte MIDI_COMMAND_PITCH_BEND = 0xE0;
+ static const byte MIDI_COMMAND_SYSTEM = 0xF0;
+
+ static const byte MIDI_CONTROLLER_BANK_SELECT_MSB = 0x00;
+ static const byte MIDI_CONTROLLER_MODULATION = 0x01;
+ static const byte MIDI_CONTROLLER_DATA_ENTRY_MSB = 0x06;
+ static const byte MIDI_CONTROLLER_VOLUME = 0x07;
+ static const byte MIDI_CONTROLLER_PANNING = 0x0A;
+ static const byte MIDI_CONTROLLER_EXPRESSION = 0x0B;
+ static const byte MIDI_CONTROLLER_BANK_SELECT_LSB = 0x20;
+ static const byte MIDI_CONTROLLER_DATA_ENTRY_LSB = 0x26;
+ static const byte MIDI_CONTROLLER_SUSTAIN = 0x40;
+ static const byte MIDI_CONTROLLER_REVERB = 0x5B;
+ static const byte MIDI_CONTROLLER_CHORUS = 0x5D;
+ static const byte MIDI_CONTROLLER_RPN_LSB = 0x64;
+ static const byte MIDI_CONTROLLER_RPN_MSB = 0x65;
+ static const byte MIDI_CONTROLLER_RESET_ALL_CONTROLLERS = 0x79;
+ static const byte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
+ static const byte MIDI_CONTROLLER_OMNI_ON = 0x7C;
+ static const byte MIDI_CONTROLLER_OMNI_OFF = 0x7D;
+ static const byte MIDI_CONTROLLER_MONO_ON = 0x7E;
+ static const byte MIDI_CONTROLLER_POLY_ON = 0x7F;
+
+ static const byte MIDI_RPN_PITCH_BEND_SENSITIVITY_MSB = 0x00;
+ static const byte MIDI_RPN_PITCH_BEND_SENSITIVITY_LSB = 0x00;
+ static const byte MIDI_RPN_NULL = 0x7F;
+
+ static const uint16 MIDI_PITCH_BEND_DEFAULT = 0x2000;
+
MidiDriver_BASE();
virtual ~MidiDriver_BASE();
@@ -270,14 +308,6 @@ public:
/** Common operations to be done by all drivers on start of sysEx */
void midiDriverCommonSysEx(const byte *msg, uint16 length);
-protected:
- // True if stereo panning should be reversed.
- bool _reversePanning;
- // True if GS percussion channel volume should be scaled to match MT-32 volume.
- bool _scaleGSPercussionVolumeToMT32;
- // The currently selected GS instrument bank / variation for each channel.
- byte _gsBank[16];
-
private:
// If detectDevice() detects MT32 and we have a preferred MT32 device
// we use this to force getMusicType() to return MT_MT32 so that we don't
@@ -287,18 +317,10 @@ private:
static bool _forceTypeMT32;
public:
- MidiDriver() : _reversePanning(false),
- _scaleGSPercussionVolumeToMT32(false) {
- memset(_gsBank, 0, sizeof(_gsBank));
- }
virtual ~MidiDriver() { }
static const byte _mt32ToGm[128];
static const byte _gmToMt32[128];
- static const byte _mt32DefaultInstruments[8];
- static const byte _mt32DefaultPanning[8];
- // Map for correcting Roland GS drumkit numbers.
- static const uint8 _gsDrumkitFallbackMap[128];
/**
* Error codes returned by open.
@@ -316,7 +338,36 @@ public:
PROP_OLD_ADLIB = 2,
PROP_CHANNEL_MASK = 3,
// HACK: Not so nice, but our SCUMM AdLib code is in audio/
- PROP_SCUMM_OPL3 = 4
+ PROP_SCUMM_OPL3 = 4,
+ /**
+ * Set this to enable or disable scaling of the MIDI channel
+ * volume with the user volume settings (including setting it
+ * to 0 when Mute All is selected). This is currently
+ * implemented in the MT-32/GM drivers (regular and Miles AIL).
+ *
+ * Default is enabled for the regular driver, and disabled for
+ * the Miles AIL driver.
+ */
+ PROP_USER_VOLUME_SCALING = 5,
+ /**
+ * Set this property to indicate that the MIDI data used by the
+ * game has reversed stereo panning compared to its intended
+ * device. The MT-32 has reversed stereo panning compared to
+ * the MIDI specification and some game developers chose to
+ * stick to the MIDI specification.
+ *
+ * Do not confuse this with the _midiDeviceReversePanning flag,
+ * which indicates that the output MIDI device has reversed
+ * stereo panning compared to the intended MIDI device targeted
+ * by the MIDI data. This is set by the MT-32/GM driver when
+ * MT-32 data is played on a GM device or the other way around.
+ * Both flags can be set, which results in no change to the
+ * panning.
+ *
+ * Set this property before opening the driver, to make sure
+ * that the default panning is set correctly.
+ */
+ PROP_MIDI_DATA_REVERSE_PANNING = 6
};
/**
@@ -341,36 +392,19 @@ public:
// HIGH-LEVEL SEMANTIC METHODS
virtual void setPitchBendRange(byte channel, uint range) {
- send(0xB0 | channel, 101, 0);
- send(0xB0 | channel, 100, 0);
- send(0xB0 | channel, 6, range);
- send(0xB0 | channel, 38, 0);
- send(0xB0 | channel, 101, 127);
- send(0xB0 | channel, 100, 127);
+ send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY_MSB);
+ send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY_LSB);
+ send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_DATA_ENTRY_MSB, range); // Semi-tones
+ send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0); // Cents
+ send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL);
+ send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL);
}
- /**
- * Initializes the MT-32 MIDI device. The device will be reset and,
- * if the parameter is specified, set up for General MIDI data.
- * @param initForGM True if the MT-32 should be initialized for GM mapping
- */
- void initMT32(bool initForGM);
-
/**
* Send a Roland MT-32 reset sysEx to the midi device.
*/
void sendMT32Reset();
- /**
- * Initializes the General MIDI device. The device will be reset.
- * If the initForMT32 parameter is specified, the device will be set up for
- * MT-32 MIDI data. If the device supports Roland GS, the enableGS
- * parameter can be specified for enhanced GS MT-32 compatiblity.
- * @param initForMT32 True if the device should be initialized for MT-32 mapping
- * @param enableGS True if the device should be initialized for GS MT-32 mapping
- */
- void initGM(bool initForMT32, bool enableGS);
-
/**
* Send a General MIDI reset sysEx to the midi device.
*/
@@ -393,18 +427,6 @@ public:
// Does this driver accept soundFont data?
virtual bool acceptsSoundFontData() { return false; }
-
-protected:
- /**
- * Checks if the currently selected GS bank / instrument variation
- * on the specified channel is valid for the specified patch.
- * If this is not the case, the correct bank will be returned which
- * can be set by sending a bank select message. If no correction is
- * needed, 0xFF will be returned.
- * This emulates the fallback functionality of the Roland SC-55 v1.2x,
- * on which some games rely to correct wrong bank selects.
- */
- byte correctInstrumentBank(byte outputChannel, byte patchId);
};
class MidiChannel {
@@ -425,17 +447,17 @@ public:
// Control Change messages
virtual void controlChange(byte control, byte value) = 0;
- virtual void modulationWheel(byte value) { controlChange(1, value); }
- virtual void volume(byte value) { controlChange(7, value); }
- virtual void panPosition(byte value) { controlChange(10, value); }
+ virtual void modulationWheel(byte value) { controlChange(MidiDriver::MIDI_CONTROLLER_MODULATION, value); }
+ virtual void volume(byte value) { controlChange(MidiDriver::MIDI_CONTROLLER_VOLUME, value); }
+ virtual void panPosition(byte value) { controlChange(MidiDriver::MIDI_CONTROLLER_PANNING, value); }
virtual void pitchBendFactor(byte value) = 0;
virtual void transpose(int8 value) {}
virtual void detune(byte value) { controlChange(17, value); }
virtual void priority(byte value) { }
- virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); }
- virtual void effectLevel(byte value) { controlChange(91, value); }
- virtual void chorusLevel(byte value) { controlChange(93, value); }
- virtual void allNotesOff() { controlChange(123, 0); }
+ virtual void sustain(bool value) { controlChange(MidiDriver::MIDI_CONTROLLER_SUSTAIN, value ? 1 : 0); }
+ virtual void effectLevel(byte value) { controlChange(MidiDriver::MIDI_CONTROLLER_REVERB, value); }
+ virtual void chorusLevel(byte value) { controlChange(MidiDriver::MIDI_CONTROLLER_CHORUS, value); }
+ virtual void allNotesOff() { controlChange(MidiDriver::MIDI_CONTROLLER_ALL_NOTES_OFF, 0); }
// SysEx messages
virtual void sysEx_customInstrument(uint32 type, const byte *instr) = 0;
diff --git a/audio/miles.h b/audio/miles.h
index 8337dcd982..883eb36c6b 100644
--- a/audio/miles.h
+++ b/audio/miles.h
@@ -24,6 +24,8 @@
#define AUDIO_MILES_MIDIDRIVER_H
#include "audio/mididrv.h"
+#include "audio/mt32gm.h"
+
#include "common/error.h"
#include "common/mutex.h"
#include "common/queue.h"
@@ -31,29 +33,13 @@
namespace Audio {
-#define MILES_MIDI_CHANNEL_COUNT 16
-#define MILES_RHYTHM_CHANNEL 9
-
// Miles Audio supported controllers for control change messages
#define MILES_CONTROLLER_SELECT_PATCH_BANK 114
#define MILES_CONTROLLER_PROTECT_VOICE 112
#define MILES_CONTROLLER_PROTECT_TIMBRE 113
#define MILES_CONTROLLER_LOCK_CHANNEL 110
#define MILES_CONTROLLER_PROTECT_CHANNEL 111
-#define MILES_CONTROLLER_BANK_SELECT_MSB 0
-#define MILES_CONTROLLER_BANK_SELECT_LSB 32
-#define MILES_CONTROLLER_MODULATION 1
-#define MILES_CONTROLLER_VOLUME 7
-#define MILES_CONTROLLER_EXPRESSION 11
-#define MILES_CONTROLLER_PANNING 10
-#define MILES_CONTROLLER_SUSTAIN 64
#define MILES_CONTROLLER_PITCH_RANGE 6
-#define MILES_CONTROLLER_RESET_ALL 121
-#define MILES_CONTROLLER_ALL_NOTES_OFF 123
-#define MILES_CONTROLLER_OMNI_ON 124
-#define MILES_CONTROLLER_OMNI_OFF 125
-#define MILES_CONTROLLER_MONO_ON 126
-#define MILES_CONTROLLER_POLY_ON 127
#define MILES_CONTROLLER_PATCH_REVERB 59
#define MILES_CONTROLLER_PATCH_BENDER 60
#define MILES_CONTROLLER_REVERB_MODE 61
@@ -80,22 +66,6 @@ namespace Audio {
#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110
#define MILES_CONTROLLER_XMIDI_RANGE_END 120
-// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
-#define MILES_PITCHBENDER_DEFAULT 0x2000
-
-// The maximum number of sources sending MIDI data to this driver.
-// This is based on the requirements of the KYRA engine, but can be increased if
-// necessary.
-#define MILES_MAXIMUM_SOURCES 4
-// Maximum number of tracked active notes for the MT-32
-// This is the maximum polyphony of the MT-32 plus some overhead (MIDI data may send
-// more notes than the MT-32 can handle simultaneously).
-#define MILES_MT32_ACTIVE_NOTES 48
-// Maximum number of tracked active notes for GM
-// This is the maximum polyphony of the SC-88 and AWE64 plus some overhead (MIDI data
-// may send more notes than the GM device can handle simultaneously).
-#define MILES_GM_ACTIVE_NOTES 96
-
#define MILES_MT32_PATCHES_COUNT 128
#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
@@ -104,11 +74,15 @@ namespace Audio {
#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4
#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT))
+// Some engines using Miles assume a source neutral
+// volume of 256, so use this by default.
+#define MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME 256
+
struct MilesMT32InstrumentEntry {
byte bankId;
byte patchId;
- byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1];
- byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1];
+ byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE];
+ byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE];
};
/**
@@ -130,28 +104,16 @@ public:
* this process has finished.
*/
virtual void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) = 0;
- virtual ~MidiDriver_Miles_Xmidi_Timbres() { }
};
-class MidiDriver_Miles_Midi : public MidiDriver, public MidiDriver_Miles_Xmidi_Timbres {
+class MidiDriver_Miles_Midi : public MidiDriver_MT32GM, public MidiDriver_Miles_Xmidi_Timbres {
public:
MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
- virtual ~MidiDriver_Miles_Midi();
+ ~MidiDriver_Miles_Midi();
public:
- // MidiDriver
- int open() override;
- // Open the Miles driver using the specified MidiDriver instance.
- int open(MidiDriver *driver, bool nativeMT32);
- void close() override;
- bool isOpen() const override { return _isOpen; }
-
- using MidiDriver_BASE::send;
- void send(uint32 b) override;
+ using MidiDriver_MT32GM::send;
void send(int8 source, uint32 b) override;
- void sysEx(const byte *msg, uint16 length) override;
- uint16 sysExNoDelay(const byte *msg, uint16 length) override;
- void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
/**
* De-initialize a source. Call this after playing a track or sound effect using this source.
@@ -159,7 +121,7 @@ public:
* from this source.
* Automatically executed when an End Of Track meta event is received.
*/
- void deinitSource(uint8 source);
+ void deinitSource(uint8 source) override;
/**
* Set the volume for this source. This will be used to scale the volume values in the MIDI
* data from this source. Expected volume values are 0 - 256.
@@ -167,64 +129,17 @@ public:
* source. If the same source numbers are consistently used for music and SFX sources, the
* source volume will only need to be set once.
*/
- void setSourceVolume(uint8 source, uint16 volume);
+ void setSourceVolume(uint8 source, uint16 volume) override;
void stopAllNotes(bool stopSustainedNotes = false) override;
- MidiChannel *allocateChannel() override {
- if (_driver)
- return _driver->allocateChannel();
- return NULL;
- }
- MidiChannel *getPercussionChannel() override {
- if (_driver)
- return _driver->getPercussionChannel();
- return NULL;
- }
-
- void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
- _timer_param = timer_param;
- _timer_proc = timer_proc;
- }
-
- void onTimer();
-
- uint32 getBaseTempo() override {
- if (_driver) {
- return _driver->getBaseTempo();
- }
- return 1000000 / _baseFreq;
- }
+ void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) override;
protected:
- Common::Mutex _mutex;
- MidiDriver *_driver;
- bool _isOpen;
- // The type of MIDI data supplied to the driver: MT-32 or General MIDI.
- MusicType _midiType;
- // True if the MIDI output is an MT-32 (hardware or 100% emulated),
- // false if the MIDI output is a General MIDI device.
- bool _nativeMT32;
- // True if the General MIDI output supports Roland GS for improved MT-32 mapping.
- bool _enableGS;
-
- // Bitmask of the MIDI channels in use by the output device
- uint16 _outputChannelMask;
-
- int _baseFreq;
- uint32 _timerRate;
-
-public:
- void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) override;
- bool isReady() override { return _sysExQueue.empty(); }
+ void initControlData() override;
+ void initMidiDevice() override;
private:
- void initMidiDevice();
-
- void MT32SysEx(const uint32 targetAddress, const byte *dataPtr, bool useSysExQueue = false);
-
- uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index);
-
void writeRhythmSetup(byte note, byte customTimbreId);
void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId, bool useSysExQueue = false);
void writePatchByte(byte patchId, byte index, byte patchValue);
@@ -236,8 +151,6 @@ private:
void setupPatch(byte patchBank, byte patchId, bool useSysExQueue = false);
int16 installCustomTimbre(byte patchBank, byte patchId);
- bool isOutputChannelUsed(uint8 outputChannel) { return _outputChannelMask & (1 << outputChannel); }
-
private:
/**
* This stores the values of the MIDI controllers for
@@ -245,42 +158,14 @@ private:
* values while a channel is locked, so they can be
* restored when the channel is unlocked.
*/
- struct MidiChannelControlData {
- // The source that last sent an event to this channel
- int8 source;
- // True if the source volume has been applied to this channel
- bool sourceVolumeApplied;
-
- byte program;
-
- uint16 pitchWheel;
-
- byte modulation;
- // The volume specified by the MIDI data
- byte volume;
- // The volume scaled using the source volume
- byte scaledVolume;
- byte panPosition;
- byte expression;
- bool sustain;
-
+ struct MilesMidiChannelControlData : MidiChannelControlData {
// Custom timbre data
byte currentPatchBank;
bool usingCustomTimbre;
byte currentCustomTimbreId;
- MidiChannelControlData() : source(-1),
- sourceVolumeApplied(false),
- program(0),
- pitchWheel(MILES_PITCHBENDER_DEFAULT),
- modulation(0),
- volume(0xFF),
- scaledVolume(0x64),
- panPosition(0x40),
- expression(0x7F),
- sustain(false),
- currentPatchBank(0),
+ MilesMidiChannelControlData() : currentPatchBank(0),
usingCustomTimbre(false),
currentCustomTimbreId(0) { }
};
@@ -306,18 +191,20 @@ private:
uint8 activeNotes;
// The MIDI controller values currently used by the channel.
- MidiChannelControlData currentData;
+ MilesMidiChannelControlData *currentData;
// The MIDI controller values set by the sources which are
// not currently using the channel because it is locked.
// These values will be set on the channel when the channel
// is unlocked.
- MidiChannelControlData unlockData;
+ MilesMidiChannelControlData *unlockData;
MidiChannelEntry() : locked(false),
lockDataChannel(-1),
lockProtected(false),
protectedSource(-1),
- activeNotes(0) { }
+ activeNotes(0),
+ currentData(0),
+ unlockData(0) { }
};
/**
@@ -325,12 +212,14 @@ private:
* @param controlData The new MIDI controller value will be set on this MidiChannelControlData
* @param sendMessage True if the message should be sent out to the device
*/
- void controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool sendMessage);
+ void controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false) override;
+ bool addActiveNote(uint8 outputChannel, uint8 note, int8 source) override;
+ bool removeActiveNote(uint8 outputChannel, uint8 note, int8 source) override;
/**
* Removes active notes from the active notes registration on the specified channel.
* @param sustainedNotes True if only sustained notes should be removed; otherwise only regular active notes will be removed
*/
- void removeActiveNotes(uint8 outputChannel, bool sustainedNotes);
+ void removeActiveNotes(uint8 outputChannel, bool sustainedNotes) override;
/**
* Find and lock an output channel and reserve it for the specified
* source. The output channel will be mapped to the specified data
@@ -355,7 +244,7 @@ private:
* @param controlData The new program value will be set on this MidiChannelControlData
* @param sendMessage True if the message should be sent out to the device
*/
- void programChange(byte outputChannel, byte patchId, uint8 source, MidiChannelControlData &controlData, bool sendMessage);
+ void programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false) override;
void stopNotesOnChannel(uint8 outputChannelNumber);
@@ -377,7 +266,7 @@ private:
struct MilesMT32SysExQueueEntry {
uint32 targetAddress;
byte dataPos;
- byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator
+ byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE];
MilesMT32SysExQueueEntry() : targetAddress(0),
dataPos(0) {
@@ -385,23 +274,8 @@ private:
}
};
- /**
- * This stores data about a specific source of MIDI data.
- */
- struct MidiSource {
- // The source volume as set by ScummVM (music/SFX volume)
- uint16 volume;
- // The mapping of MIDI data channels to output channels
- // for this source.
- int8 channelMap[MILES_MIDI_CHANNEL_COUNT];
-
- MidiSource() : volume(256) {
- memset(channelMap, 0, sizeof(channelMap));
- }
- };
-
// stores information about all MIDI channels
- MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
+ MidiChannelEntry _midiChannels[MIDI_CHANNEL_COUNT];
// stores information about all custom timbres
MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT];
@@ -416,76 +290,6 @@ private:
// Queues for Miles SysEx controllers
MilesMT32SysExQueueEntry _milesSysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT];
-
- // MIDI sources sending messages to this driver.
- MidiSource _sources[MILES_MAXIMUM_SOURCES];
-
- struct ActiveNote {
- int8 source;
- uint8 channel;
- uint8 note;
- bool sustain;
-
- ActiveNote() : source(0x7F),
- channel(0xFF),
- note(0xFF),
- sustain(false) { }
- };
-
- // The maximum number of active notes that have to be tracked for this MIDI device.
- uint8 _maximumActiveNotes;
- // Tracks the notes being played by the MIDI device.
- ActiveNote *_activeNotes;
-
- /**
- * Stores data which is to be transmitted as a SysEx message
- * to a MIDI device. Neither data nor length should include
- * the SysEx start and stop bytes.
- */
- struct SysExData {
- byte data[270];
- uint16 length;
- SysExData() : length(0) {
- memset(data, 0, sizeof(data));
- }
- };
-
- // The number of microseconds to wait before sending the
- // next SysEx message.
- uint32 _sysExDelay;
-
- /**
- * Queue of SysEx messages that must be sent to the
- * MIDI device. Used by processXMIDITimbreChunk to
- * send SysEx messages before starting playback of
- * a track.
- *
- * Sending other MIDI messages to the driver should
- * be suspended until all SysEx messages in the
- * queue have been sent to the MIDI device. Use the
- * isReady function to check if the driver is ready
- * to receive other messages.
- */
- Common::Queue<SysExData> _sysExQueue;
-
- // Mutex for write access to the SysEx queue.
- Common::Mutex _sysExQueueMutex;
-
- // External timer callback
- void *_timer_param;
- Common::TimerManager::TimerProc _timer_proc;
-
-public:
- // Callback hooked up to the driver wrapped by the
- // Miles MIDI driver object. Executes onTimer and
- // the external callback set by the
- // setTimerCallback function.
- static void timerCallback(void *data) {
- MidiDriver_Miles_Midi *driver = (MidiDriver_Miles_Midi *) data;
- driver->onTimer();
- if (driver->_timer_proc && driver->_timer_param)
- driver->_timer_proc(driver->_timer_param);
- }
};
extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr);
diff --git a/audio/miles_adlib.cpp b/audio/miles_adlib.cpp
index 0bf0bdc9d1..0e7ad7e3af 100644
--- a/audio/miles_adlib.cpp
+++ b/audio/miles_adlib.cpp
@@ -162,7 +162,7 @@ private:
MidiChannelEntry() : currentPatchBank(0),
currentInstrumentPtr(NULL),
- currentPitchBender(MILES_PITCHBENDER_DEFAULT),
+ currentPitchBender(MIDI_PITCH_BEND_DEFAULT),
currentPitchRange(0),
currentVoiceProtection(0),
currentVolume(0), currentVolumeExpression(0),
@@ -224,7 +224,7 @@ private:
bool _isOpen;
// stores information about all MIDI channels (not the actual OPL FM voice channels!)
- MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
+ MidiChannelEntry _midiChannels[MIDI_CHANNEL_COUNT];
// stores information about all virtual OPL FM voices
VirtualFmVoiceEntry _virtualFmVoices[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
@@ -348,7 +348,7 @@ void MidiDriver_Miles_AdLib::resetData() {
ARRAYCLEAR(_virtualFmVoices);
ARRAYCLEAR(_physicalFmVoices);
- for (byte midiChannel = 0; midiChannel < MILES_MIDI_CHANNEL_COUNT; midiChannel++) {
+ for (byte midiChannel = 0; midiChannel < MIDI_CHANNEL_COUNT; midiChannel++) {
// defaults, were sent to driver during driver initialization
_midiChannels[midiChannel].currentVolume = 0x7F;
_midiChannels[midiChannel].currentPanning = 0x40; // center
@@ -356,7 +356,7 @@ void MidiDriver_Miles_AdLib::resetData() {
// Miles Audio 2: hardcoded pitch range as a global (not channel specific), set to 12
// Miles Audio 3: pitch range per MIDI channel
- _midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT;
+ _midiChannels[midiChannel].currentPitchBender = MIDI_PITCH_BEND_DEFAULT;
_midiChannels[midiChannel].currentPitchRange = 12;
}
@@ -924,22 +924,22 @@ void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumb
// It seems that this can get ignored, because we don't cache timbres at all
break;
- case MILES_CONTROLLER_MODULATION:
+ case MIDI_CONTROLLER_MODULATION:
_midiChannels[midiChannel].currentModulation = controllerValue;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20;
break;
- case MILES_CONTROLLER_VOLUME:
+ case MIDI_CONTROLLER_VOLUME:
_midiChannels[midiChannel].currentVolume = controllerValue;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
break;
- case MILES_CONTROLLER_EXPRESSION:
+ case MIDI_CONTROLLER_EXPRESSION:
_midiChannels[midiChannel].currentVolumeExpression = controllerValue;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
break;
- case MILES_CONTROLLER_PANNING:
+ case MIDI_CONTROLLER_PANNING:
_midiChannels[midiChannel].currentPanning = controllerValue;
if (_modeStereo) {
// Update register only in case we are in stereo mode
@@ -947,7 +947,7 @@ void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumb
}
break;
- case MILES_CONTROLLER_SUSTAIN:
+ case MIDI_CONTROLLER_SUSTAIN:
_midiChannels[midiChannel].currentSustain = controllerValue;
if (controllerValue < 64) {
releaseSustain(midiChannel);
@@ -959,16 +959,16 @@ void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumb
_midiChannels[midiChannel].currentPitchRange = controllerValue;
break;
- case MILES_CONTROLLER_RESET_ALL:
+ case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
_midiChannels[midiChannel].currentSustain = 0;
releaseSustain(midiChannel);
_midiChannels[midiChannel].currentModulation = 0;
_midiChannels[midiChannel].currentVolumeExpression = 127;
- _midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT;
+ _midiChannels[midiChannel].currentPitchBender = MIDI_PITCH_BEND_DEFAULT;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20 | kMilesAdLibUpdateFlags_Reg_40 | kMilesAdLibUpdateFlags_Reg_A0;
break;
- case MILES_CONTROLLER_ALL_NOTES_OFF:
+ case MIDI_CONTROLLER_ALL_NOTES_OFF:
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
// used
diff --git a/audio/miles_midi.cpp b/audio/miles_midi.cpp
index fb68762dbe..23740b6768 100644
--- a/audio/miles_midi.cpp
+++ b/audio/miles_midi.cpp
@@ -36,149 +36,70 @@ namespace Audio {
#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
-#define MILES_MT32_SYSEX_TERMINATOR 0xFF
-
-/*
-const byte milesMT32SysExResetParameters[] = {
- 0x01, MILES_MT32_SYSEX_TERMINATOR
-};
-*/
-
const byte milesMT32SysExChansSetup[] = {
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
};
const byte milesMT32SysExPartialReserveTable[] = {
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR
+ 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04
};
const byte milesMT32SysExInitReverb[] = {
- 0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2
+ 0x00, 0x03, 0x02 // Reverb mode 0, reverb time 3, reverb level 2
};
MidiDriver_Miles_Midi::MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) :
- _driver(NULL),
- _isOpen(false),
- _nativeMT32(false),
- _enableGS(false),
- _outputChannelMask(65535), // Channels 1-16
- _baseFreq(250),
- _timerRate(0),
- _noteCounter(0),
- _sysExDelay(0),
- _timer_param(0),
- _timer_proc(0) {
- switch (midiType) {
- case MT_MT32:
- _midiType = MT_MT32;
- break;
- case MT_GM:
- case MT_GS: // Treat GS same as GM
- _midiType = MT_GM;
- break;
- default:
- assert(false);
- break;
- }
-
- memset(_gsBank, 0, sizeof(_gsBank));
+ MidiDriver_MT32GM(midiType), _noteCounter(0) {
memset(_patchesBank, 0, sizeof(_patchesBank));
_instrumentTablePtr = instrumentTablePtr;
_instrumentTableCount = instrumentTableCount;
- for (int i = 0; i < MILES_MAXIMUM_SOURCES; ++i) {
- // Default MIDI channel mapping: data channel == output channel
- for (int j = 0; j < MILES_MIDI_CHANNEL_COUNT; ++j) {
- _sources[i].channelMap[j] = j;
- }
- }
+ // Disable user volume scaling by default. Most (all?)
+ // engines using Miles implement this themselves. Can
+ // be turned on using the property function.
+ _userVolumeScaling = false;
- _maximumActiveNotes = _midiType == MT_MT32 ? MILES_MT32_ACTIVE_NOTES : MILES_GM_ACTIVE_NOTES;
- _activeNotes = new ActiveNote[_maximumActiveNotes];
- assert(_activeNotes);
+ setSourceNeutralVolume(MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME);
}
MidiDriver_Miles_Midi::~MidiDriver_Miles_Midi() {
- Common::StackLock lock(_mutex);
- if (_driver) {
- _driver->setTimerCallback(0, 0);
- _driver->close();
- delete _driver;
- }
- _driver = NULL;
-
- if (_activeNotes)
- delete[] _activeNotes;
-}
-
-int MidiDriver_Miles_Midi::open() {
- assert(!_driver);
-
- // Setup midi driver
- MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | (_midiType == MT_MT32 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
- MusicType deviceMusicType = MidiDriver::getMusicType(dev);
- if (!(deviceMusicType == MT_MT32 || deviceMusicType == MT_GM || deviceMusicType == MT_GS))
- error("MILES-MIDI: detected music device uses unsupported music type %i", deviceMusicType);
-
- MidiDriver *driver = MidiDriver::createMidi(dev);
- bool nativeMT32 = deviceMusicType == MT_MT32 || ConfMan.getBool("native_mt32");
-
- return open(driver, nativeMT32);
-}
-
-int MidiDriver_Miles_Midi::open(MidiDriver *driver, bool nativeMT32) {
- assert(!_driver);
+ if (_instrumentTablePtr)
+ delete[] _instrumentTablePtr;
- _driver = driver;
- _nativeMT32 = nativeMT32;
-
- _enableGS = ConfMan.getBool("enable_gs");
-
- if (!_driver)
- return 255;
-
- if (_nativeMT32)
- _outputChannelMask = _midiType == MT_MT32 ? 1022 : 767; // Channels 2-10 / 1-8 and 10
- _driver->property(MidiDriver::PROP_CHANNEL_MASK, _outputChannelMask);
-
- int ret = _driver->open();
- if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0)
- return ret;
-
- _timerRate = _driver->getBaseTempo();
- _driver->setTimerCallback(this, timerCallback);
-
- initMidiDevice();
-
- return 0;
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (_midiChannels[i].unlockData)
+ delete _midiChannels[i].unlockData;
+ }
}
-void MidiDriver_Miles_Midi::close() {
- if (_driver) {
- _driver->close();
+void MidiDriver_Miles_Midi::initControlData() {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ _controlData[i] = _midiChannels[i].currentData = new MilesMidiChannelControlData();
+ _midiChannels[i].unlockData = new MilesMidiChannelControlData();
+ _controlData[i]->volume = _controlData[i]->scaledVolume =
+ (_nativeMT32 ? MT32_DEFAULT_CHANNEL_VOLUME : GM_DEFAULT_CHANNEL_VOLUME);
+ if (_nativeMT32 && i >= 1 && i <= 8) {
+ _midiChannels[i].currentData->program = MT32_DEFAULT_INSTRUMENTS[i - 1];
+ _midiChannels[i].currentData->panPosition = MT32_DEFAULT_PANNING[i - 1];
+ }
}
}
void MidiDriver_Miles_Midi::initMidiDevice() {
- if (_nativeMT32) {
- bool initForGM = _midiType != MT_MT32;
+ MidiDriver_MT32GM::initMidiDevice();
- // reset all internal parameters / patches
- initMT32(initForGM);
+ // Additional Miles AIL specific initialization
- if (!initForGM) {
- // init part/channel assignments
- MT32SysEx(0x10000D, milesMT32SysExChansSetup);
+ if (_midiType == MT_MT32 && _nativeMT32) {
+ // init part/channel assignments
+ sysExMT32(milesMT32SysExChansSetup, 9, (0x10 << 14) | (0x00 << 7) | 0x0D, false, true);
- // partial reserve table
- MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
+ // partial reserve table
+ sysExMT32(milesMT32SysExPartialReserveTable, 9, (0x10 << 14) | (0x00 << 7) | 0x04, false, true);
- // init reverb
- MT32SysEx(0x100001, milesMT32SysExInitReverb);
- }
- } else {
- initGM(_midiType == MT_MT32, _enableGS);
+ // init reverb
+ sysExMT32(milesMT32SysExInitReverb, 3, (0x10 << 14) | (0x00 << 7) | 0x01, false, true);
}
// Set Miles default controller values
@@ -192,19 +113,16 @@ void MidiDriver_Miles_Midi::initMidiDevice() {
for (int i = 1; i < 10; ++i) {
// Volume 7F (max)
- send(-1, 0xB0 | i, MILES_CONTROLLER_VOLUME, 0x7F);
+ send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_VOLUME, 0x7F);
if (_midiType == MT_MT32) {
// Panning center - not the MT-32 default for all channels
- send(-1, 0xB0 | i, MILES_CONTROLLER_PANNING, 0x40);
+ send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_PANNING, 0x40);
}
// Patch
- if (i != MILES_RHYTHM_CHANNEL) {
- if (_midiType == MT_MT32) {
- // These are the default on the MT-32; just set them on the control data
- _midiChannels[i].currentData.program = _mt32DefaultInstruments[i - 1];
- } else {
- // Send the instruments out to GM devices.
- send(-1, 0xC0 | i, _mt32DefaultInstruments[i - 1], 0);
+ if (i != MIDI_RHYTHM_CHANNEL) {
+ if (_midiType == MT_GM) {
+ // Send the MT-32 default instrument numbers out to GM devices.
+ send(-1, MIDI_COMMAND_PROGRAM_CHANGE | i, MT32_DEFAULT_INSTRUMENTS[i - 1], 0);
}
}
// The following settings are also sent out by the AIL driver:
@@ -217,215 +135,53 @@ void MidiDriver_Miles_Midi::initMidiDevice() {
}
}
-void MidiDriver_Miles_Midi::sysEx(const byte *msg, uint16 length) {
- uint16 delay = sysExNoDelay(msg, length);
-
- if (delay > 0)
- g_system->delayMillis(delay);
-}
-
-uint16 MidiDriver_Miles_Midi::sysExNoDelay(const byte *msg, uint16 length) {
- if (!_nativeMT32 && length >= 3 && msg[0] == 0x41 && msg[2] == 0x16)
- // MT-32 SysExes have no effect on GM devices.
- return 0;
-
- // Send SysEx
- _driver->sysEx(msg, length);
-
- // Wait the time it takes to send the SysEx data
- uint16 delay = (length + 2) * 1000 / 3125;
-
- // Plus an additional delay for the MT-32 rev00
- if (_nativeMT32)
- delay += 40;
-
- return delay;
-}
-
-void MidiDriver_Miles_Midi::MT32SysEx(const uint32 targetAddress, const byte *dataPtr, bool useSysExQueue) {
- if (!_nativeMT32)
- // MT-32 SysExes have no effect on GM devices.
- return;
-
- byte sysExMessage[270];
- uint16 sysExPos = 0;
- byte sysExByte;
- uint16 sysExChecksum = 0;
-
- memset(&sysExMessage, 0, sizeof(sysExMessage));
-
- sysExMessage[0] = 0x41; // Roland
- sysExMessage[1] = 0x10;
- sysExMessage[2] = 0x16; // Model MT32
- sysExMessage[3] = 0x12; // Command DT1
-
- sysExChecksum = 0;
-
- sysExMessage[4] = (targetAddress >> 16) & 0xFF;
- sysExMessage[5] = (targetAddress >> 8) & 0xFF;
- sysExMessage[6] = targetAddress & 0xFF;
-
- for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
- assert(sysExMessage[targetAddressByte] < 0x80); // security check
- sysExChecksum -= sysExMessage[targetAddressByte];
- }
-
- sysExPos = 7;
- while (1) {
- sysExByte = *dataPtr++;
- if (sysExByte == MILES_MT32_SYSEX_TERMINATOR)
- break; // Message done
-
- assert(sysExPos < sizeof(sysExMessage));
- assert(sysExByte < 0x80); // security check
- sysExMessage[sysExPos++] = sysExByte;
- sysExChecksum -= sysExByte;
- }
-
- // Calculate checksum
- assert(sysExPos < sizeof(sysExMessage));
- sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
-
- if (useSysExQueue) {
- SysExData sysEx;
- memcpy(sysEx.data, sysExMessage, sysExPos);
- sysEx.length = sysExPos;
-
- _sysExQueueMutex.lock();
- _sysExQueue.push(sysEx);
- _sysExQueueMutex.unlock();
- } else {
- sysEx(sysExMessage, sysExPos);
- }
-}
-
-void MidiDriver_Miles_Midi::metaEvent(int8 source, byte type, byte *data, uint16 length) {
- assert(source < MILES_MAXIMUM_SOURCES);
-
- if (type == 0x2F && source >= 0) // End of Track
- deinitSource(source);
-
- _driver->metaEvent(type, data, length);
-}
-
-void MidiDriver_Miles_Midi::send(uint32 b) {
- send(-1, b);
-}
-
- // MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
- assert(source < MILES_MAXIMUM_SOURCES);
+ assert(source < MAXIMUM_SOURCES);
byte command = b & 0xf0;
byte dataChannel = b & 0xf;
byte outputChannel = source < 0 ? dataChannel : _sources[source].channelMap[dataChannel];
+
MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel];
// Only send the message to the MIDI device if the channel is not locked or
// if the source that locked the channel is sending the message
- bool sendMessage = source < 0 || !outputChannelEntry.locked ||
- (outputChannelEntry.locked && outputChannelEntry.currentData.source == source);
+ bool channelLockedByOtherSource = source >= 0 && outputChannelEntry.locked &&
+ outputChannelEntry.currentData->source != source;
// Track controller changes on the current data if the MIDI message is sent out,
// or on the unlock data otherwise.
- MidiChannelControlData &controlData = sendMessage ? outputChannelEntry.currentData : outputChannelEntry.unlockData;
- byte op1 = (b >> 8) & 0xff;
- byte op2 = (b >> 16) & 0xff;
-
- if (command != 0xF0 && controlData.source != source) {
- // A new source has sent an event on this channel.
- controlData.sourceVolumeApplied = false;
- controlData.source = source;
- }
+ MilesMidiChannelControlData &controlData = channelLockedByOtherSource ?
+ *outputChannelEntry.unlockData : *outputChannelEntry.currentData;
- switch (command) {
- case 0x80: // Note Off
- case 0x90: // Note On
- if (sendMessage) {
- // Note On with velocity 0 is treated as Note Off
- bool addNote = command == 0x90 && op2 != 0;
- if (addNote) {
- if (source >= 0 && !controlData.sourceVolumeApplied)
- // Source volume hasn't been applied yet. Do so now.
- controlChange(outputChannel, MILES_CONTROLLER_VOLUME, controlData.volume, source, controlData, sendMessage);
- // Add the new note to the active note registration
- for (int i = 0; i < _maximumActiveNotes; ++i) {
- ActiveNote &activeNote = _activeNotes[i];
- if (activeNote.channel == 0xFF) {
- // Add the new note.
- activeNote.source = source;
- activeNote.channel = outputChannel;
- activeNote.note = op1;
- activeNote.sustain = false;
- ++outputChannelEntry.activeNotes;
- break;
- }
- }
- } else {
- // Remove the note from the active note registration
- for (int i = 0; i < _maximumActiveNotes; ++i) {
- ActiveNote &activeNote = _activeNotes[i];
- if (activeNote.channel == outputChannel && activeNote.source == source && activeNote.note == op1) {
- if (controlData.sustain) {
- // Sustain is on, so the note should be turned off
- // when sustain is turned off.
- activeNote.sustain = true;
- } else {
- // Turn off the existing note.
- activeNote.source = 0x7F;
- activeNote.channel = 0xFF;
- if (outputChannelEntry.activeNotes == 0) {
- warning("MILES-MIDI: active notes 0 on channel %d when turning off note %x", op1, outputChannel);
- } else {
- --outputChannelEntry.activeNotes;
- }
- }
- break;
- }
- }
- }
- }
- // fall through
- case 0xa0: // Polyphonic key pressure (aftertouch) (not supported by MT-32 or GM)
- case 0xd0: // Channel pressure (aftertouch) (not supported by MT-32)
- case 0xe0: // pitch bend change
- if (command == 0xe0)
- controlData.pitchWheel = ((uint16)op2 << 7) | (uint16)op1;
+ 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) {
_noteCounter++;
if (controlData.usingCustomTimbre) {
// Remember that this timbre got used now
_customTimbres[controlData.currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
}
- if (sendMessage) {
- _driver->send(command | outputChannel, op1, op2);
- }
- break;
- case 0xb0: // Control change
- controlChange(outputChannel, op1, op2, source, controlData, sendMessage);
- break;
- case 0xc0: // Program Change
- programChange(outputChannel, op1, source, controlData, sendMessage);
- break;
- case 0xf0: // SysEx
- warning("MILES-MIDI: SysEx: %x", b);
- break;
- default:
- warning("MILES-MIDI: Unknown event %02x", command);
}
}
-void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool sendMessage) {
- assert(source < MILES_MAXIMUM_SOURCES);
+void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue,
+ int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
+ assert(source < MAXIMUM_SOURCES);
+
+ MilesMidiChannelControlData &milesControlData = channelLockedByOtherSource ?
+ *_midiChannels[outputChannel].unlockData : *_midiChannels[outputChannel].currentData;
// XMIDI controllers
switch (controllerNumber) {
case MILES_CONTROLLER_SELECT_PATCH_BANK:
- controlData.currentPatchBank = controllerValue;
+ milesControlData.currentPatchBank = controllerValue;
return;
case MILES_CONTROLLER_PROTECT_TIMBRE:
- if (controlData.usingCustomTimbre) {
+ if (milesControlData.usingCustomTimbre) {
// custom timbre set on current channel
- _customTimbres[controlData.currentCustomTimbreId].protectionEnabled = controllerValue >= 64;
+ _customTimbres[milesControlData.currentCustomTimbreId].protectionEnabled = controllerValue >= 64;
}
return;
@@ -454,15 +210,15 @@ void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNum
if (_midiType == MT_MT32 && _nativeMT32) {
switch (controllerNumber) {
case MILES_CONTROLLER_PATCH_REVERB:
- writePatchByte(controlData.program, 6, controllerValue);
- if (sendMessage)
- _driver->send(0xC0 | outputChannel | (controlData.program << 8)); // execute program change
+ writePatchByte(milesControlData.program, 6, controllerValue);
+ if (!channelLockedByOtherSource)
+ _driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (milesControlData.program << 8));
return;
case MILES_CONTROLLER_PATCH_BENDER:
- writePatchByte(controlData.program, 4, controllerValue);
- if (sendMessage)
- _driver->send(0xC0 | outputChannel | (controlData.program << 8)); // execute program change
+ writePatchByte(milesControlData.program, 4, controllerValue);
+ if (!channelLockedByOtherSource)
+ _driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (milesControlData.program << 8));
return;
case MILES_CONTROLLER_REVERB_MODE:
@@ -478,9 +234,9 @@ void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNum
return;
case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
- if (controlData.usingCustomTimbre) {
+ if (milesControlData.usingCustomTimbre) {
// custom timbre is set on current channel
- writeRhythmSetup(controllerValue, controlData.currentCustomTimbreId);
+ writeRhythmSetup(controllerValue, milesControlData.currentCustomTimbreId);
}
return;
default:
@@ -489,7 +245,8 @@ void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNum
}
// XMIDI MT-32 SysEx controllers
- if (_midiType == MT_MT32 && (controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
+ if (_midiType == MT_MT32 && (controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) &&
+ (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
if (!_nativeMT32)
return;
@@ -509,15 +266,15 @@ void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNum
switch(controllerNumber) {
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
- _milesSysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF;
- _milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
+ _milesSysExQueues[sysExQueueNr].targetAddress &= 0x003FFF;
+ _milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 14);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
- _milesSysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
- _milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
+ _milesSysExQueues[sysExQueueNr].targetAddress &= 0x1FC07F;
+ _milesSysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 7);
break;
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
- _milesSysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
+ _milesSysExQueues[sysExQueueNr].targetAddress &= 0x1FFF80;
_milesSysExQueues[sysExQueueNr].targetAddress |= controllerValue;
break;
case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
@@ -549,27 +306,11 @@ void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNum
if (sysExSend) {
if (sysExPos > 0) {
// data actually available? -> send it
- _milesSysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
-
- // Execute SysEx
- MT32SysEx(_milesSysExQueues[sysExQueueNr].targetAddress, _milesSysExQueues[sysExQueueNr].data);
+ sysExMT32(_milesSysExQueues[sysExQueueNr].data, sysExPos, _milesSysExQueues[sysExQueueNr].targetAddress, false, true);
// Adjust target address to point at the final data byte, or at the
// end of the current data in case of an overflow
- // Note that the address bytes are actually 7 bits
- byte addressByte1 = (_milesSysExQueues[sysExQueueNr].targetAddress & 0xFF0000) >> 16;
- byte addressByte2 = (_milesSysExQueues[sysExQueueNr].targetAddress & 0x00FF00) >> 8;
- byte addressByte3 = _milesSysExQueues[sysExQueueNr].targetAddress & 0x0000FF;
- addressByte3 += _milesSysExQueues[sysExQueueNr].dataPos;
- if (addressByte3 > 0x7F) {
- addressByte3 -= 0x80;
- addressByte2++;
- }
- if (addressByte2 > 0x7F) {
- addressByte2 -= 0x80;
- addressByte1++;
- }
- _milesSysExQueues[sysExQueueNr].targetAddress = addressByte1 << 16 | addressByte2 << 8 | addressByte3;
+ _milesSysExQueues[sysExQueueNr].targetAddress += _milesSysExQueues[sysExQueueNr].dataPos;
// reset queue data buffer
_milesSysExQueues[sysExQueueNr].dataPos = 0;
@@ -583,110 +324,42 @@ void MidiDriver_Miles_Midi::controlChange(byte outputChannel, byte controllerNum
return;
}
- // Standard MIDI controllers
- switch (controllerNumber) {
- case MILES_CONTROLLER_BANK_SELECT_MSB:
- // Keep track of the current bank for each channel
- _gsBank[outputChannel] = controllerValue;
- break;
- case MILES_CONTROLLER_MODULATION:
- controlData.modulation = controllerValue;
- break;
- case MILES_CONTROLLER_VOLUME:
- controlData.volume = controllerValue;
- controlData.sourceVolumeApplied = true;
- if (source >= 0) {
- // Scale to source volume
- controllerValue = (_sources[source].volume * controllerValue) >> 8;
- }
- if (_scaleGSPercussionVolumeToMT32 && outputChannel == MILES_RHYTHM_CHANNEL) {
- // Scale GS percussion channel volume to MT-32 level (80/127)
- controllerValue = (80 * controllerValue) >> 7;
- }
- if (controlData.scaledVolume == controllerValue) {
- // Volume is already at this value, so no need to send it out
- // to the MIDI device.
- return;
- }
- controlData.scaledVolume = controllerValue;
- break;
- case MILES_CONTROLLER_PANNING:
- if (_reversePanning) {
- // Center panning is 0x40
- controllerValue = 0x80 - controllerValue;
- if (controllerValue > 0x7F)
- controllerValue = 0x7F;
- }
- controlData.panPosition = controllerValue;
- break;
- case MILES_CONTROLLER_EXPRESSION:
- controlData.expression = controllerValue;
- break;
- case MILES_CONTROLLER_RESET_ALL:
- controlData.modulation = 0;
- controlData.expression = 0x7F;
- controlData.pitchWheel = MILES_PITCHBENDER_DEFAULT;
- controlData.sustain = false;
- if (sendMessage) {
- removeActiveNotes(outputChannel, true);
- }
- break;
- case MILES_CONTROLLER_SUSTAIN:
- controlData.sustain = controllerValue >= 0x40;
- if (sendMessage && !controlData.sustain) {
- removeActiveNotes(outputChannel, true);
- }
- break;
- case MILES_CONTROLLER_OMNI_ON:
- case MILES_CONTROLLER_OMNI_OFF:
- case MILES_CONTROLLER_MONO_ON:
- case MILES_CONTROLLER_POLY_ON:
- // These act as an All Notes Off on MT-32, but also turn sustain off.
- // They are not part of GM, so should not be used in GM data.
- if (_midiType != MT_MT32) {
- warning("MILES-MIDI: unsupported GM controller %x", controllerNumber);
- return;
- }
+ // Handle other controllers and send message (if necessary)
+ MidiDriver_MT32GM::controlChange(outputChannel, controllerNumber, controllerValue, source, milesControlData, channelLockedByOtherSource);
+}
- controlData.sustain = false;
- if (sendMessage)
- removeActiveNotes(outputChannel, true);
- if (!_nativeMT32) {
- // MT-32 data on GM device.
- // These controllers might not be supported or have side effects
- // (changing omni or mono/poly mode). Send All Notes Off and
- // Sustain Off instead.
- if (sendMessage) {
- controllerNumber = MILES_CONTROLLER_ALL_NOTES_OFF;
- _driver->send(0xB0 | outputChannel | (MILES_CONTROLLER_SUSTAIN << 8) | (0 << 16));
- }
- }
- // fall through
- case MILES_CONTROLLER_ALL_NOTES_OFF:
- if (sendMessage) {
- removeActiveNotes(outputChannel, false);
+bool MidiDriver_Miles_Midi::addActiveNote(uint8 outputChannel, uint8 note, int8 source) {
+ bool added = MidiDriver_MT32GM::addActiveNote(outputChannel, note, source);
+
+ if (added)
+ _midiChannels[outputChannel].activeNotes++;
+
+ return added;
+}
+
+bool MidiDriver_Miles_Midi::removeActiveNote(uint8 outputChannel, uint8 note, int8 source) {
+ bool removed = MidiDriver_MT32GM::removeActiveNote(outputChannel, note, source);
+
+ if (removed) {
+ if (_midiChannels[outputChannel].activeNotes == 0) {
+ warning("MILES-MIDI: active notes 0 on channel %d when turning off note %x", outputChannel, note);
+ } else {
+ _midiChannels[outputChannel].activeNotes--;
}
- break;
- default:
- break;
}
- if (sendMessage) {
- _driver->send(0xB0 | outputChannel | (controllerNumber << 8) | (controllerValue << 16));
- }
+ return removed;
}
void MidiDriver_Miles_Midi::removeActiveNotes(uint8 outputChannel, bool sustainedNotes) {
- // Remove sustained notes from the active notes registration
+ Common::StackLock lock(_activeNotesMutex);
+
+ // Remove sustained or non-sustained notes from the active notes registration
for (int i = 0; i < _maximumActiveNotes; ++i) {
if (_activeNotes[i].channel == outputChannel && _activeNotes[i].sustain == sustainedNotes) {
- _activeNotes[i].source = 0x7F;
- _activeNotes[i].channel = 0xFF;
+ _activeNotes[i].clear();
if (_midiChannels[outputChannel].activeNotes == 0) {
- if (sustainedNotes)
- warning("MILES-MIDI: active notes 0 on channel %d when turning off sustained notes", outputChannel);
- else
- warning("MILES-MIDI: active notes 0 on channel %d when turning all notes off", outputChannel);
+ warning("MILES-MIDI: active notes 0 on channel %d (sustained %i) when removing active notes", outputChannel, sustainedNotes);
continue;
}
--_midiChannels[outputChannel].activeNotes;
@@ -695,7 +368,7 @@ void MidiDriver_Miles_Midi::removeActiveNotes(uint8 outputChannel, bool sustaine
}
void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
- assert(source < MILES_MAXIMUM_SOURCES);
+ assert(source < MAXIMUM_SOURCES);
int8 lockChannel = findLockChannel();
if (lockChannel == -1)
@@ -711,11 +384,11 @@ void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
_midiChannels[lockChannel].lockDataChannel = dataChannel;
_sources[source].channelMap[dataChannel] = lockChannel;
// Copy current controller values so they can be restored when unlocking the channel
- _midiChannels[lockChannel].unlockData = _midiChannels[lockChannel].currentData;
- _midiChannels[lockChannel].currentData.source = source;
+ *_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData;
+ _midiChannels[lockChannel].currentData->source = source;
// Send volume change to apply the new source volume
- controlChange(lockChannel, MILES_CONTROLLER_VOLUME, 0x7F, source, _midiChannels[lockChannel].currentData, true);
+ 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
@@ -730,8 +403,9 @@ int8 MidiDriver_Miles_Midi::findLockChannel(bool useProtectedChannels) {
// from channel locking will not be considered.
int8 potentialLockChannel = -1;
uint8 notes = 255;
- for (int i = MILES_MIDI_CHANNEL_COUNT - 1; i >= 0; --i) {
- if (!isOutputChannelUsed(i) || i == MILES_RHYTHM_CHANNEL || _midiChannels[i].locked || (!useProtectedChannels && _midiChannels[i].lockProtected))
+ for (int i = MIDI_CHANNEL_COUNT - 1; i >= 0; --i) {
+ if (!isOutputChannelUsed(i) || i == MIDI_RHYTHM_CHANNEL || _midiChannels[i].locked ||
+ (!useProtectedChannels && _midiChannels[i].lockProtected))
continue;
if (_midiChannels[i].activeNotes < notes) {
potentialLockChannel = i;
@@ -750,66 +424,61 @@ void MidiDriver_Miles_Midi::unlockChannel(uint8 outputChannel) {
// Unlock the channel
channel.locked = false;
- _sources[channel.currentData.source].channelMap[channel.lockDataChannel] = channel.lockDataChannel;
+ _sources[channel.currentData->source].channelMap[channel.lockDataChannel] = channel.lockDataChannel;
channel.lockDataChannel = -1;
- channel.currentData.source = channel.unlockData.source;
+ channel.currentData->source = channel.unlockData->source;
// Send the unlock channel data to the MIDI device to reset the channel parameters
- if (channel.unlockData.volume != 0xFF) {
- controlChange(outputChannel, MILES_CONTROLLER_VOLUME, channel.unlockData.volume, channel.currentData.source, channel.currentData, true);
+ if (channel.unlockData->volume != 0xFF) {
+ controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, channel.unlockData->volume, channel.currentData->source, *channel.currentData);
} else {
- channel.currentData.volume = 0xFF;
- }
- if (channel.currentData.modulation != channel.unlockData.modulation)
- controlChange(outputChannel, MILES_CONTROLLER_MODULATION, channel.unlockData.modulation, channel.currentData.source, channel.currentData, true);
- if (channel.currentData.panPosition != channel.unlockData.panPosition)
- controlChange(outputChannel, MILES_CONTROLLER_PANNING, channel.unlockData.panPosition, channel.currentData.source, channel.currentData, true);
- if (channel.currentData.expression != channel.unlockData.expression)
- controlChange(outputChannel, MILES_CONTROLLER_EXPRESSION, channel.unlockData.expression, channel.currentData.source, channel.currentData, true);
- if (channel.currentData.sustain != channel.unlockData.sustain)
- controlChange(outputChannel, MILES_CONTROLLER_SUSTAIN, channel.unlockData.sustain ? 0x7F : 0x00, channel.currentData.source, channel.currentData, true);
- if (channel.currentData.currentPatchBank != channel.unlockData.currentPatchBank)
- controlChange(outputChannel, MILES_CONTROLLER_SELECT_PATCH_BANK, channel.unlockData.currentPatchBank, channel.currentData.source, channel.currentData, true);
- if (channel.unlockData.program != 0xFF && (channel.currentData.program != channel.unlockData.program || channel.currentData.currentPatchBank != channel.unlockData.currentPatchBank))
- programChange(outputChannel, channel.unlockData.program, channel.currentData.source, channel.currentData, true);
- if (channel.currentData.pitchWheel != channel.unlockData.pitchWheel)
- send(channel.currentData.source, 0xE0 | outputChannel, channel.unlockData.pitchWheel & 0x7F, (channel.unlockData.pitchWheel >> 7) & 0x7F);
+ channel.currentData->volume = 0xFF;
+ }
+ if (channel.currentData->modulation != channel.unlockData->modulation)
+ controlChange(outputChannel, MIDI_CONTROLLER_MODULATION, channel.unlockData->modulation, channel.currentData->source, *channel.currentData);
+ if (channel.currentData->panPosition != channel.unlockData->panPosition)
+ controlChange(outputChannel, MIDI_CONTROLLER_PANNING, channel.unlockData->panPosition, channel.currentData->source, *channel.currentData);
+ if (channel.currentData->expression != channel.unlockData->expression)
+ controlChange(outputChannel, MIDI_CONTROLLER_EXPRESSION, channel.unlockData->expression, channel.currentData->source, *channel.currentData);
+ if (channel.currentData->sustain != channel.unlockData->sustain)
+ controlChange(outputChannel, MIDI_CONTROLLER_SUSTAIN, channel.unlockData->sustain ? 0x7F : 0x00, channel.currentData->source, *channel.currentData);
+ if (channel.currentData->currentPatchBank != channel.unlockData->currentPatchBank)
+ controlChange(outputChannel, MILES_CONTROLLER_SELECT_PATCH_BANK, channel.unlockData->currentPatchBank,
+ channel.currentData->source, *channel.currentData);
+ if (channel.unlockData->program != 0xFF && (channel.currentData->program != channel.unlockData->program ||
+ channel.currentData->currentPatchBank != channel.unlockData->currentPatchBank))
+ programChange(outputChannel, channel.unlockData->program, channel.currentData->source, *channel.currentData);
+ if (channel.currentData->pitchWheel != channel.unlockData->pitchWheel)
+ send(channel.currentData->source, MIDI_COMMAND_PITCH_BEND | outputChannel,
+ channel.unlockData->pitchWheel & 0x7F, (channel.unlockData->pitchWheel >> 7) & 0x7F);
}
void MidiDriver_Miles_Midi::stopNotesOnChannel(uint8 outputChannelNumber) {
MidiChannelEntry &channel = _midiChannels[outputChannelNumber];
- if (channel.currentData.sustain) {
- controlChange(outputChannelNumber, MILES_CONTROLLER_SUSTAIN, 0, channel.currentData.source, channel.currentData, true);
+ if (channel.currentData->sustain) {
+ controlChange(outputChannelNumber, MIDI_CONTROLLER_SUSTAIN, 0, channel.currentData->source, *channel.currentData);
}
if (channel.activeNotes > 0) {
- controlChange(outputChannelNumber, MILES_CONTROLLER_ALL_NOTES_OFF, 0, channel.currentData.source, channel.currentData, true);
+ controlChange(outputChannelNumber, MIDI_CONTROLLER_ALL_NOTES_OFF, 0, channel.currentData->source, *channel.currentData);
}
}
void MidiDriver_Miles_Midi::stopAllNotes(bool stopSustainedNotes) {
- for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
- if (!isOutputChannelUsed(i))
- continue;
+ MidiDriver_MT32GM::stopAllNotes(stopSustainedNotes);
- if (stopSustainedNotes) {
- _driver->send(0xB0 | i, MILES_CONTROLLER_SUSTAIN, 0);
- _midiChannels[i].currentData.sustain = false;
- }
- _driver->send(0xB0 | i, MILES_CONTROLLER_ALL_NOTES_OFF, 0);
- _midiChannels[i].activeNotes = 0;
- }
- for (int i = 0; i < _maximumActiveNotes; ++i) {
- _activeNotes[i].source = 0x7F;
- _activeNotes[i].channel = 0xFF;
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (isOutputChannelUsed(i))
+ _midiChannels[i].activeNotes = 0;
}
}
-void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, uint8 source, MidiChannelControlData &controlData, bool sendMessage) {
- // remember patch id for the current MIDI-channel
- controlData.program = patchId;
+void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, int8 source,
+ MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
+ MilesMidiChannelControlData &milesControlData = channelLockedByOtherSource ?
+ *_midiChannels[outputChannel].unlockData : *_midiChannels[outputChannel].currentData;
if (_midiType == MT_MT32) {
- byte channelPatchBank = controlData.currentPatchBank;
+ byte channelPatchBank = milesControlData.currentPatchBank;
byte activePatchBank = _patchesBank[patchId];
//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
@@ -822,48 +491,14 @@ void MidiDriver_Miles_Midi::programChange(byte outputChannel, byte patchId, uint
// If this is a custom patch, remember customTimbreId
int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
if (customTimbre >= 0) {
- controlData.usingCustomTimbre = true;
- controlData.currentCustomTimbreId = customTimbre;
- } else {
- controlData.usingCustomTimbre = false;
- }
-
- if (outputChannel == MILES_RHYTHM_CHANNEL)
- // Patch changes on the rhythm channel are used by AIL to setup custom
- // rhythm timbres. There's no need to actually send them to the MT-32,
- // because it won't respond to them. On GM/GS devices they might
- // unintentionally change the drumkit.
- return;
-
- if (!_nativeMT32 && !_enableGS) {
- // GM device: map the patch to GM equivalent
- // TODO It would be nice if the patch bank could be taken into account
- // when using a custom ScummVM game-specific MT-32 to GM mapping.
- patchId = _mt32ToGm[patchId];
- }
- } else {
- // GM/GS MIDI
- if (outputChannel == MILES_RHYTHM_CHANNEL) {
- // Correct possible wrong GS drumkit number
- patchId = _gsDrumkitFallbackMap[patchId];
- } else if (!_nativeMT32) {
- // Correct possible wrong bank / instrument variation
- byte correctedBank = correctInstrumentBank(outputChannel, patchId);
- if (correctedBank != 0xFF) {
- // Send out a bank select for the corrected bank number
- controlChange(outputChannel, MILES_CONTROLLER_BANK_SELECT_MSB, correctedBank, source, controlData, sendMessage);
- controlChange(outputChannel, MILES_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData, sendMessage);
- }
+ milesControlData.usingCustomTimbre = true;
+ milesControlData.currentCustomTimbreId = customTimbre;
} else {
- // GM on an MT-32: map the patch to the MT-32 equivalent
- patchId = _gmToMt32[patchId];
+ milesControlData.usingCustomTimbre = false;
}
}
- // Finally send program change to MIDI device
- if (sendMessage) {
- _driver->send(0xC0 | outputChannel | (patchId << 8));
- }
+ MidiDriver_MT32GM::programChange(outputChannel, patchId, source, milesControlData, channelLockedByOtherSource);
}
int16 MidiDriver_Miles_Midi::searchCustomTimbre(byte patchBank, byte patchId) {
@@ -871,7 +506,8 @@ int16 MidiDriver_Miles_Midi::searchCustomTimbre(byte patchBank, byte patchId) {
for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
if (_customTimbres[customTimbreId].used) {
- if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) {
+ if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) &&
+ (_customTimbres[customTimbreId].currentPatchId == patchId)) {
return customTimbreId;
}
}
@@ -1023,12 +659,12 @@ int16 MidiDriver_Miles_Midi::installCustomTimbre(byte patchBank, byte patchId) {
_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
_customTimbres[customTimbreId].protectionEnabled = false;
- uint32 targetAddress = 0x080000 | (customTimbreId << 9);
- uint32 targetAddressCommon = targetAddress + 0x000000;
- uint32 targetAddressPartial1 = targetAddress + 0x00000E;
- uint32 targetAddressPartial2 = targetAddress + 0x000048;
- uint32 targetAddressPartial3 = targetAddress + 0x000102;
- uint32 targetAddressPartial4 = targetAddress + 0x00013C;
+ uint32 targetAddress = ((0x08 << 14) | (0x00 << 7) | 0x00) + (customTimbreId * 0x100);
+ uint32 targetAddressCommon = targetAddress;
+ uint32 targetAddressPartial1 = targetAddress + 0x0E;
+ uint32 targetAddressPartial2 = targetAddress + 0x48;
+ uint32 targetAddressPartial3 = targetAddress + 0x82;
+ uint32 targetAddressPartial4 = targetAddress + 0xBC;
#if 0
byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
@@ -1050,94 +686,67 @@ int16 MidiDriver_Miles_Midi::installCustomTimbre(byte patchBank, byte patchId) {
#endif
// upload common parameter data
- MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter, true);
+ sysExMT32(instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE, targetAddressCommon, true);
// upload partial parameter data
- MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0], true);
- MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1], true);
- MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2], true);
- MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3], true);
+ sysExMT32(instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial1, true);
+ sysExMT32(instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial2, true);
+ sysExMT32(instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial3, true);
+ sysExMT32(instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE, targetAddressPartial4, true);
setupPatch(patchBank, patchId, true);
return customTimbreId;
}
-uint32 MidiDriver_Miles_Midi::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) {
- uint16 targetAddressLSB = baseAddress & 0xFF;
- uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF;
- uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF;
-
- // add index to it, but use 7-bit of the index for each byte
- targetAddressLSB += (index & 0x7F);
- targetAddressKSB += ((index >> 7) & 0x7F);
- targetAddressMSB += ((index >> 14) & 0x7F);
-
- // adjust bytes, so that none of them is above or equal 0x80
- while (targetAddressLSB >= 0x80) {
- targetAddressLSB -= 0x80;
- targetAddressKSB++;
- }
- while (targetAddressKSB >= 0x80) {
- targetAddressKSB -= 0x80;
- targetAddressMSB++;
- }
- assert(targetAddressMSB < 0x80);
-
- // put everything together
- return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16);
-}
-
void MidiDriver_Miles_Midi::writeRhythmSetup(byte note, byte customTimbreId) {
- byte sysExData[2];
- uint32 targetAddress = 0;
+ byte sysExData[1];
+ uint32 targetAddress = (0x03 << 14) | (0x01 << 7) | 0x10;
- targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2));
+ targetAddress += ((note - 24) << 2);
sysExData[0] = customTimbreId;
- sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
- MT32SysEx(targetAddress, sysExData);
+ sysExMT32(sysExData, 1, targetAddress);
}
void MidiDriver_Miles_Midi::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId, bool useSysExQueue) {
- byte sysExData[3];
- uint32 targetAddress = 0;
+ byte sysExData[2];
+ uint32 targetAddress = (0x05 << 14) | (0x00 << 7) | 0x00;
// write to patch memory (starts at 0x050000, each entry is 8 bytes)
- targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3);
+ targetAddress += (patchId << 3);
sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
sysExData[1] = timbreId; // timbre number (0-63)
- sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator
- MT32SysEx(targetAddress, sysExData, useSysExQueue);
+ sysExMT32(sysExData, 2, targetAddress, useSysExQueue);
}
void MidiDriver_Miles_Midi::writePatchByte(byte patchId, byte index, byte patchValue) {
- byte sysExData[2];
- uint32 targetAddress = 0;
+ byte sysExData[1];
+ uint32 targetAddress = (0x05 << 14) | (0x00 << 7) | 0x00;
- targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index);
+ targetAddress += (patchId << 3) + index;
sysExData[0] = patchValue;
- sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
- MT32SysEx(targetAddress, sysExData);
+ sysExMT32(sysExData, 1, targetAddress);
}
void MidiDriver_Miles_Midi::writeToSystemArea(byte index, byte value) {
- byte sysExData[2];
- uint32 targetAddress = 0;
+ byte sysExData[1];
+ uint32 targetAddress = (0x10 << 14) | (0x00 << 7) | 0x00;
- targetAddress = calculateSysExTargetAddress(0x100000, index);
+ targetAddress += index;
sysExData[0] = value;
- sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
- MT32SysEx(targetAddress, sysExData);
+ sysExMT32(sysExData, 1, targetAddress);
}
-MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) { return MidiDriver_Miles_MIDI_create(MT_MT32, instrumentDataFilename); }
+MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) {
+ return MidiDriver_Miles_MIDI_create(MT_MT32, instrumentDataFilename);
+}
MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::String &instrumentDataFilename) {
assert(midiType == MT_MT32 || midiType == MT_GM || midiType == MT_GS);
@@ -1224,13 +833,11 @@ MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Co
instrumentOffset += 2;
// Copy common parameter data
memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
- instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
// Copy partial parameter data
for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
- instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
}
@@ -1246,79 +853,51 @@ MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Co
}
void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
- assert(source < MILES_MAXIMUM_SOURCES);
-
// Unlock and unprotect channels which were locked or protected by this source.
- for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
- if (_midiChannels[i].currentData.source == source && _midiChannels[i].locked) {
+ if (_midiChannels[i].currentData->source == source && _midiChannels[i].locked) {
unlockChannel(i);
}
if (_midiChannels[i].lockProtected && _midiChannels[i].protectedSource == 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;
- }
- // Reset the data to output channel mapping
- for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
- _sources[source].channelMap[i] = i;
- }
- // Stop any active notes.
- for (int i = 0; i < _maximumActiveNotes; ++i) {
- if (_activeNotes[i].source == source) {
- if (_activeNotes[i].sustain) {
- // Turn off sustain
- controlChange(_activeNotes[i].channel, MILES_CONTROLLER_SUSTAIN, 0x00, source, _midiChannels[_activeNotes[i].channel].currentData, true);
- } else {
- // Send note off
- send(source, 0x80 | _activeNotes[i].channel, _activeNotes[i].note, 0x00);
- }
- }
+ if (_midiChannels[i].currentData->source == source)
+ _midiChannels[i].currentData->source = -1;
+ if (_midiChannels[i].unlockData->source == source)
+ _midiChannels[i].unlockData->source = -1;
}
+
+ MidiDriver_MT32GM::deinitSource(source);
}
void MidiDriver_Miles_Midi::setSourceVolume(uint8 source, uint16 volume) {
- assert(source < MILES_MAXIMUM_SOURCES);
+ assert(source < MAXIMUM_SOURCES);
_sources[source].volume = volume;
- for (int i = 0; i < MILES_MIDI_CHANNEL_COUNT; ++i) {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
MidiChannelEntry &channel = _midiChannels[i];
- MidiChannelControlData *channelData = 0;
- bool sendMessage = false;
+ MilesMidiChannelControlData *channelData = 0;
+ bool channelLockedByOtherSource = false;
// Apply the new source volume to this channel if this source is active
// on this channel, or if it was active on the channel before it was
// locked.
- if (channel.currentData.source == source) {
- channelData = &channel.currentData;
- sendMessage = true;
- } else if (channel.locked && channel.unlockData.source == source) {
- channelData = &channel.unlockData;
+ if (channel.currentData->source == source) {
+ channelData = channel.currentData;
+ } else if (channel.locked && channel.unlockData->source == source) {
+ channelData = channel.unlockData;
+ channelLockedByOtherSource = true;
}
if (channelData && channelData->volume != 0xFF)
- controlChange(i, MILES_CONTROLLER_VOLUME, channelData->volume, source, *channelData, sendMessage);
- }
-}
-
-void MidiDriver_Miles_Midi::onTimer() {
- Common::StackLock lock(_sysExQueueMutex);
-
- _sysExDelay -= (_sysExDelay > _timerRate) ? _timerRate : _sysExDelay;
-
- if (!_sysExQueue.empty() && _sysExDelay == 0) {
- // Ready to send next SysEx message to the MIDI device
- SysExData sysEx = _sysExQueue.pop();
- _sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
+ controlChange(i, MIDI_CONTROLLER_VOLUME, channelData->volume, source, *channelData, channelLockedByOtherSource);
}
}
diff --git a/audio/module.mk b/audio/module.mk
index 375c98b515..4456f87a3b 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -14,6 +14,7 @@ MODULE_OBJS := \
miles_midi.o \
mixer.o \
mpu401.o \
+ mt32gm.o \
musicplugin.o \
null.o \
timestamp.o \
diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
new file mode 100644
index 0000000000..67cd66b0a8
--- /dev/null
+++ b/audio/mt32gm.cpp
@@ -0,0 +1,1173 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AUDIO_MIDI_H
+#define AUDIO_MIDI_H
+
+#include "audio/mt32gm.h"
+
+#include "common/config-manager.h"
+#include "common/debug.h"
+
+// These are the power-on default instruments of the Roland MT-32 family.
+const byte MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS[8] = {
+ 0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A
+};
+
+// These are the power-on default panning settings for channels 2-9 of the Roland MT-32 family.
+// Internally, the MT-32 has 15 panning positions (0-E with 7 being center).
+// This has been translated to the equivalent MIDI panning values (0-127).
+// These are used for setting default panning on GM devices when using them with MT-32 data.
+// Note that MT-32 panning is reversed compared to the MIDI specification. This is not reflected
+// here; the driver is expected to flip these values based on the _midiDeviceReversePanning
+// variable.
+const byte MidiDriver_MT32GM::MT32_DEFAULT_PANNING[8] = {
+ // 7, 8, 7, 8, 4, A, 0, E
+ 0x40, 0x49, 0x40, 0x49, 0x25, 0x5B, 0x00, 0x7F
+};
+
+// This is the drum map for the Roland Sound Canvas SC-55 v1.xx. It had a fallback mechanism
+// to correct invalid drumkit selections. Some games rely on this mechanism to select the
+// correct Roland GS drumkit. Use this map to emulate this mechanism.
+// E.g. correct invalid drumkit 50: GS_DRUMKIT_FALLBACK_MAP[50] == 48
+const uint8 MidiDriver_MT32GM::GS_DRUMKIT_FALLBACK_MAP[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, // STANDARD
+ 8, 8, 8, 8, 8, 8, 8, 8, // ROOM
+ 16, 16, 16, 16, 16, 16, 16, 16, // POWER
+ 24, 25, 24, 24, 24, 24, 24, 24, // ELECTRONIC; TR-808 (25)
+ 32, 32, 32, 32, 32, 32, 32, 32, // JAZZ
+ 40, 40, 40, 40, 40, 40, 40, 40, // BRUSH
+ 48, 48, 48, 48, 48, 48, 48, 48, // ORCHESTRA
+ 56, 56, 56, 56, 56, 56, 56, 56, // SFX
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined (fall back to STANDARD)
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
+ 0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
+ 0, 0, 0, 0, 0, 0, 0, 127 // No drumkit defined; CM-64/32L (127)
+};
+
+MidiDriver_MT32GM::MidiDriver_MT32GM(MusicType midiType) :
+ _driver(0),
+ _nativeMT32(false),
+ _enableGS(false),
+ _midiDataReversePanning(false),
+ _midiDeviceReversePanning(false),
+ _scaleGSPercussionVolumeToMT32(false),
+ _userVolumeScaling(true),
+ _userMusicVolume(192),
+ _userSfxVolume(192),
+ _userMute(false),
+ _isOpen(false),
+ _outputChannelMask(65535), // Channels 1-16
+ _baseFreq(250),
+ _timerRate(0),
+ _fadeDelay(0),
+ _sysExDelay(0),
+ _timer_param(0),
+ _timer_proc(0) {
+ memset(_controlData, 0, sizeof(_controlData));
+
+ switch (midiType) {
+ case MT_MT32:
+ _midiType = MT_MT32;
+ break;
+ case MT_GM:
+ case MT_GS: // Treat GS same as GM
+ _midiType = MT_GM;
+ break;
+ default:
+ error("MidiDriver_MT32GM: Unsupported music type %i", midiType);
+ break;
+ }
+
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ // Default source type: 0 = music, 1+ = SFX
+ _sources[i].type = i == 0 ? SOURCE_TYPE_MUSIC : SOURCE_TYPE_SFX;
+ // Default MIDI channel mapping: data channel == output channel
+ for (int j = 0; j < MIDI_CHANNEL_COUNT; ++j) {
+ _sources[i].channelMap[j] = j;
+ }
+ }
+
+ // Default MT-32 <> GM instrument mappings.
+ _mt32ToGMInstrumentMap = _mt32ToGm;
+ _gmToMT32InstrumentMap = _gmToMt32;
+
+ _maximumActiveNotes = _midiType == MT_MT32 ? MAXIMUM_MT32_ACTIVE_NOTES : MAXIMUM_GM_ACTIVE_NOTES;
+ _activeNotes = new ActiveNote[_maximumActiveNotes];
+ assert(_activeNotes);
+}
+
+MidiDriver_MT32GM::~MidiDriver_MT32GM() {
+ if (_driver) {
+ _driver->setTimerCallback(0, 0);
+ _driver->close();
+ delete _driver;
+ }
+ _driver = 0;
+
+ if (_controlData) {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ delete _controlData[i];
+ }
+ }
+ if (_activeNotes)
+ delete[] _activeNotes;
+}
+
+int MidiDriver_MT32GM::open() {
+ assert(!_driver);
+
+ // Setup midi driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | (_midiType == MT_MT32 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
+ MusicType deviceMusicType = MidiDriver::getMusicType(dev);
+ if (!(deviceMusicType == MT_MT32 || deviceMusicType == MT_GM || deviceMusicType == MT_GS))
+ error("MidiDriver_MT32GM: detected music device uses unsupported music type %i", deviceMusicType);
+
+ MidiDriver *driver = MidiDriver::createMidi(dev);
+ bool nativeMT32 = deviceMusicType == MT_MT32 || ConfMan.getBool("native_mt32");
+
+ return open(driver, nativeMT32);
+}
+
+int MidiDriver_MT32GM::open(MidiDriver *driver, bool nativeMT32) {
+ assert(!_driver);
+
+ _driver = driver;
+ _nativeMT32 = nativeMT32;
+
+ _enableGS = ConfMan.getBool("enable_gs");
+
+ if (!_driver)
+ return 255;
+
+ if (_nativeMT32)
+ _outputChannelMask = _midiType == MT_MT32 ? 1022 : 767; // Channels 2-10 / 1-8 and 10
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, _outputChannelMask);
+
+ int ret = _driver->open();
+ if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0)
+ return ret;
+
+ _timerRate = _driver->getBaseTempo();
+ _driver->setTimerCallback(this, timerCallback);
+
+ initControlData();
+ initMidiDevice();
+ syncSoundSettings();
+
+ _isOpen = true;
+
+ return 0;
+}
+
+void MidiDriver_MT32GM::initControlData() {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ _controlData[i] = new MidiChannelControlData();
+ _controlData[i]->volume = _controlData[i]->scaledVolume =
+ (_nativeMT32 ? MT32_DEFAULT_CHANNEL_VOLUME : GM_DEFAULT_CHANNEL_VOLUME);
+ if (_nativeMT32 && i >= 1 && i <= 8) {
+ _controlData[i]->program = MT32_DEFAULT_INSTRUMENTS[i - 1];
+ _controlData[i]->panPosition = MT32_DEFAULT_PANNING[i - 1];
+ }
+ }
+}
+
+void MidiDriver_MT32GM::initMidiDevice() {
+ if (_nativeMT32) {
+ initMT32(_midiType != MT_MT32);
+ } else {
+ initGM(_midiType == MT_MT32, _enableGS);
+ }
+}
+
+void MidiDriver_MT32GM::initMT32(bool initForGM) {
+ sendMT32Reset();
+
+ if (initForGM) {
+ // Set up MT-32 for GM data.
+ // This is based on Roland's GM settings for MT-32.
+ debug("Initializing MT-32 for General MIDI data");
+
+ byte buffer[17];
+
+ // Roland MT-32 SysEx for system area
+ memcpy(&buffer[0], "\x41\x10\x16\x12\x10\x00", 6);
+
+ // Set reverb parameters:
+ // - Mode 2 (Plate)
+ // - Time 3
+ // - Level 4
+ memcpy(&buffer[6], "\x01\x02\x03\x04\x66", 5);
+ sysEx(buffer, 11);
+
+ // Set partial reserve to match SC-55
+ memcpy(&buffer[6], "\x04\x08\x04\x04\x03\x03\x03\x03\x02\x02\x4C", 11);
+ sysEx(buffer, 17);
+
+ // Use MIDI instrument channels 1-8 instead of 2-9
+ memcpy(&buffer[6], "\x0D\x00\x01\x02\x03\x04\x05\x06\x07\x09\x3E", 11);
+ sysEx(buffer, 17);
+
+ // The MT-32 has reversed stereo panning compared to the MIDI spec.
+ // GM does use panning as specified by the MIDI spec.
+ _midiDeviceReversePanning = true;
+
+ int i;
+
+ // Set default GM panning (center on all channels)
+ for (i = 0; i < 8; ++i) {
+ send((0x40 << 16) | (MIDI_CONTROLLER_PANNING << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
+ }
+
+ // Set default GM instruments (0 on all channels).
+ // This is expected to be mapped to the MT-32 equivalent by the driver.
+ for (i = 0; i < 8; ++i) {
+ send((0 << 8) | (MIDI_COMMAND_PROGRAM_CHANGE | i));
+ }
+
+ // Set Pitch Bend Sensitivity to 2 semitones.
+ for (i = 0; i < 8; ++i) {
+ setPitchBendRange(i, 2);
+ }
+ setPitchBendRange(MIDI_RHYTHM_CHANNEL, 2);
+ }
+}
+
+void MidiDriver_MT32GM::initGM(bool initForMT32, bool enableGS) {
+ sendGMReset();
+
+ if (initForMT32) {
+ // Set up the GM device for MT-32 MIDI data.
+ // Based on iMuse implementation (which is based on Roland's MT-32 settings for GS)
+ debug("Initializing GM device for MT-32 MIDI data");
+
+ // The MT-32 has reversed stereo panning compared to the MIDI spec.
+ // GM does use panning as specified by the MIDI spec.
+ _midiDeviceReversePanning = true;
+
+ int i;
+
+ // Set the default panning for the MT-32 instrument channels.
+ for (i = 1; i < 9; ++i) {
+ send((MT32_DEFAULT_PANNING[i - 1] << 16) | (MIDI_CONTROLLER_PANNING << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
+ }
+
+ // Set Channels 1-16 Reverb to 64, which is the
+ // equivalent of MT-32 default Reverb Level 5
+ for (i = 0; i < 16; ++i)
+ send((64 << 16) | (MIDI_CONTROLLER_REVERB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
+
+ // Set Channels 1-16 Chorus to 0. The MT-32 has no chorus capability.
+ // (This is probably the default for many GM devices with chorus anyway.)
+ for (i = 0; i < 16; ++i)
+ send((0 << 16) | (MIDI_CONTROLLER_CHORUS << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
+
+ // Set Channels 1-16 Pitch Bend Sensitivity to 12 semitones.
+ for (i = 0; i < 16; ++i) {
+ setPitchBendRange(i, 12);
+ }
+
+ if (enableGS) {
+ // GS specific settings for MT-32 instrument mapping.
+ debug("Additional initialization of GS device for MT-32 MIDI data");
+
+ // Note: All Roland GS devices support CM-64/32L maps
+
+ // Set Percussion Channel to SC-55 Map (CC#32, 01H), then
+ // Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
+ // Bank select MSB: bank 0
+ getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
+ // Bank select LSB: map 1 (SC-55)
+ getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_LSB, 1);
+ // Patch change: 127 (CM-64/32L)
+ send(127 << 8 | MIDI_COMMAND_PROGRAM_CHANGE | 9);
+
+ // Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
+ for (i = 0; i < 16; ++i) {
+ if (i == getPercussionChannel()->getNumber())
+ continue;
+ // Bank select MSB: bank 127 (CM-64/32L)
+ send((127 << 16) | (MIDI_CONTROLLER_BANK_SELECT_MSB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
+ // Bank select LSB: map 1 (SC-55)
+ send((1 << 16) | (MIDI_CONTROLLER_BANK_SELECT_LSB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
+ // Patch change: 0 (causes bank select to take effect)
+ send((0 << 16) | (0 << 8) | (MIDI_COMMAND_PROGRAM_CHANGE | i));
+ }
+
+ byte buffer[12];
+
+ // Roland GS SysEx ID
+ memcpy(&buffer[0], "\x41\x10\x42\x12", 4);
+
+ // Set channels 1-16 Mod. LFO1 Pitch Depth to 4
+ memcpy(&buffer[4], "\x40\x20\x04\x04\x18", 5);
+ for (i = 0; i < 16; ++i) {
+ buffer[5] = 0x20 + i;
+ buffer[8] = 0x18 - i;
+ sysEx(buffer, 9);
+ }
+
+ // In Roland's GS MT-32 emulation settings, percussion channel expression
+ // is locked at 80. This corrects a difference in volume of the SC-55 MT-32
+ // drum kit vs the drums of the MT-32. However, this approach has a problem:
+ // the MT-32 supports expression on the percussion channel, so MIDI data
+ // which uses this will play incorrectly. So instead, percussion channel
+ // volume will be scaled by the driver by a factor 80/127.
+ // Strangely, the regular GM drum kit does have a volume that matches the
+ // MT-32 drums, so scaling is only necessary when using GS MT-32 emulation.
+ _scaleGSPercussionVolumeToMT32 = true;
+
+ // Change Reverb settings (as used by Roland):
+ // - Character: 0
+ // - Pre-LPF: 4
+ // - Level: 35h
+ // - Time: 6Ah
+ memcpy(&buffer[4], "\x40\x01\x31\x00\x04\x35\x6A\x6B", 8);
+ sysEx(buffer, 12);
+ }
+
+ // Set the default MT-32 patches. For non-GS devices these are expected to be
+ // mapped to the GM equivalents by the driver.
+ for (i = 1; i < 9; ++i) {
+ send((MT32_DEFAULT_INSTRUMENTS[i - 1] << 8) | (MIDI_COMMAND_PROGRAM_CHANGE | i));
+ }
+
+ // Regarding Master Tune: 442 kHz was intended for the MT-32 family, but
+ // apparently due to a firmware bug the master tune was actually 440 kHz for
+ // all models (see MUNT source code for more details). So master tune is left
+ // at 440 kHz for GM devices playing MT-32 MIDI data.
+ }
+}
+
+void MidiDriver_MT32GM::close() {
+ if (_driver) {
+ _driver->close();
+ }
+}
+
+uint32 MidiDriver_MT32GM::property(int prop, uint32 param) {
+ switch (prop) {
+ case PROP_USER_VOLUME_SCALING:
+ if (param == 0xFFFF)
+ return _userVolumeScaling ? 1 : 0;
+ _userVolumeScaling = param > 0;
+ break;
+ case PROP_MIDI_DATA_REVERSE_PANNING:
+ if (param == 0xFFFF)
+ return _midiDataReversePanning ? 1 : 0;
+ _midiDataReversePanning = param > 0;
+ break;
+ default:
+ MidiDriver::property(prop, param);
+ break;
+ }
+ return 0;
+}
+
+void MidiDriver_MT32GM::send(uint32 b) {
+ send(-1, b);
+}
+
+void MidiDriver_MT32GM::send(int8 source, uint32 b) {
+ byte dataChannel = b & 0xf;
+ int8 outputChannel = source < 0 ? dataChannel : mapSourceChannel(source, dataChannel);
+
+ MidiChannelControlData &controlData = *_controlData[outputChannel];
+
+ processEvent(source, b, outputChannel, controlData);
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_MT32GM::processEvent(int8 source, uint32 b, uint8 outputChannel, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
+ assert(source < MAXIMUM_SOURCES);
+
+ byte command = b & 0xf0;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ if (command != MIDI_COMMAND_SYSTEM && controlData.source != source) {
+ // A new source has sent an event on this channel.
+ controlData.sourceVolumeApplied = false;
+ controlData.source = source;
+ }
+
+ switch (command) {
+ case MIDI_COMMAND_NOTE_OFF:
+ case MIDI_COMMAND_NOTE_ON:
+ if (!channelLockedByOtherSource)
+ noteOnOff(outputChannel, command, op1, op2, source, controlData);
+ break;
+ case MIDI_COMMAND_PITCH_BEND:
+ controlData.pitchWheel = ((uint16)op2 << 7) | (uint16)op1;
+ // fall through
+ case MIDI_COMMAND_POLYPHONIC_AFTERTOUCH: // Not supported by MT-32 or GM
+ case MIDI_COMMAND_CHANNEL_AFTERTOUCH: // Not supported by MT-32
+ if (!channelLockedByOtherSource)
+ _driver->send(command | outputChannel, op1, op2);
+ break;
+ case MIDI_COMMAND_CONTROL_CHANGE:
+ controlChange(outputChannel, op1, op2, source, controlData, channelLockedByOtherSource);
+ break;
+ case MIDI_COMMAND_PROGRAM_CHANGE:
+ programChange(outputChannel, op1, source, controlData, channelLockedByOtherSource);
+ break;
+ case MIDI_COMMAND_SYSTEM:
+ // The only supported system event is SysEx and that should be sent using the sysEx functions.
+ warning("MidiDriver_MT32GM: send received system event (not processed): %x", b);
+ break;
+ default:
+ warning("MidiDriver_MT32GM: Received unknown event %02x", command);
+ break;
+ }
+}
+
+void MidiDriver_MT32GM::noteOnOff(byte outputChannel, byte command, byte note, byte velocity, int8 source, MidiChannelControlData &controlData) {
+ if (!isOutputChannelUsed(outputChannel))
+ return;
+
+ // Note On with velocity 0 is treated as Note Off
+ bool addNote = command == MIDI_COMMAND_NOTE_ON && velocity != 0;
+ if (addNote) {
+ if (source >= 0 && !controlData.sourceVolumeApplied)
+ // Source volume hasn't been applied yet. Do so now.
+ controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, controlData.volume, source, controlData);
+
+ // Add the new note to the active note registration
+ addActiveNote(outputChannel, note, source);
+ } else {
+ // Remove the note from the active note registration
+ removeActiveNote(outputChannel, note, source);
+ }
+
+ _driver->send(command | outputChannel, note, velocity);
+}
+
+void MidiDriver_MT32GM::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
+ assert(source < MAXIMUM_SOURCES);
+
+ // Standard MIDI controllers
+ switch (controllerNumber) {
+ case MIDI_CONTROLLER_BANK_SELECT_MSB:
+ // Keep track of the current bank for each channel
+ controlData.instrumentBank = controllerValue;
+ break;
+ case MIDI_CONTROLLER_MODULATION:
+ controlData.modulation = controllerValue;
+ break;
+ case MIDI_CONTROLLER_VOLUME:
+ controlData.volume = controllerValue;
+ controlData.sourceVolumeApplied = true;
+ if (source >= 0) {
+ // Scale to source volume
+ controllerValue = (controllerValue * _sources[source].volume) / _sources[source].neutralVolume;
+ }
+ if (_userVolumeScaling) {
+ if (_userMute) {
+ controllerValue = 0;
+ } else {
+ // Scale to user volume
+ uint16 userVolume = _sources[source].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume; // Treat SOURCE_TYPE_UNDEFINED as music
+ controllerValue = (controllerValue * userVolume) >> 8;
+ }
+ }
+ if (_scaleGSPercussionVolumeToMT32 && outputChannel == MIDI_RHYTHM_CHANNEL) {
+ // Scale GS percussion channel volume to MT-32 level (80/127)
+ controllerValue = (80 * controllerValue) >> 7;
+ }
+ // Source volume scaling might clip volume, so reduce to maximum
+ controllerValue = MIN(controllerValue, (byte)0x7F);
+ if (controlData.scaledVolume == controllerValue) {
+ // Volume is already at this value, so no need to send it out
+ // to the MIDI device.
+ return;
+ }
+ controlData.scaledVolume = controllerValue;
+ break;
+ case MIDI_CONTROLLER_PANNING:
+ if (_midiDeviceReversePanning != _midiDataReversePanning) {
+ // Center panning is 0x40
+ controllerValue = 0x80 - controllerValue;
+ if (controllerValue > 0x7F)
+ controllerValue = 0x7F;
+ }
+ break;
+ case MIDI_CONTROLLER_EXPRESSION:
+ controlData.expression = controllerValue;
+ break;
+ case MIDI_CONTROLLER_SUSTAIN:
+ controlData.sustain = controllerValue >= 0x40;
+ if (!channelLockedByOtherSource && !controlData.sustain) {
+ removeActiveNotes(outputChannel, true);
+ }
+ break;
+ case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
+ controlData.pitchWheel = MIDI_PITCH_BEND_DEFAULT;
+ controlData.modulation = 0;
+ controlData.expression = 0x7F;
+ controlData.sustain = false;
+ if (!channelLockedByOtherSource) {
+ removeActiveNotes(outputChannel, true);
+ }
+ break;
+ case MIDI_CONTROLLER_OMNI_ON:
+ case MIDI_CONTROLLER_OMNI_OFF:
+ case MIDI_CONTROLLER_MONO_ON:
+ case MIDI_CONTROLLER_POLY_ON:
+ // These act as an All Notes Off on MT-32, but also turn sustain off.
+ // They are not part of GM, so should not be used in GM data.
+ if (_midiType != MT_MT32) {
+ warning("MidiDriver_MT32GM: unsupported GM controller %x", controllerNumber);
+ return;
+ }
+
+ controlData.sustain = false;
+ if (!channelLockedByOtherSource)
+ removeActiveNotes(outputChannel, true);
+ if (!_nativeMT32) {
+ // MT-32 data on GM device.
+ // These controllers might not be supported or have side effects
+ // (changing omni or mono/poly mode). Send All Notes Off and
+ // Sustain Off instead.
+ if (!channelLockedByOtherSource) {
+ controllerNumber = MIDI_CONTROLLER_ALL_NOTES_OFF;
+ _driver->send(MIDI_COMMAND_CONTROL_CHANGE | outputChannel | (MIDI_CONTROLLER_SUSTAIN << 8) | (0 << 16));
+ }
+ }
+ // fall through
+ case MIDI_CONTROLLER_ALL_NOTES_OFF:
+ if (!channelLockedByOtherSource)
+ removeActiveNotes(outputChannel, false);
+ break;
+ default:
+ break;
+ }
+
+ if (!channelLockedByOtherSource)
+ _driver->send(MIDI_COMMAND_CONTROL_CHANGE | outputChannel | (controllerNumber << 8) | (controllerValue << 16));
+}
+
+bool MidiDriver_MT32GM::addActiveNote(uint8 outputChannel, uint8 note, int8 source) {
+ Common::StackLock lock(_activeNotesMutex);
+
+ for (int i = 0; i < _maximumActiveNotes; ++i) {
+ ActiveNote &activeNote = _activeNotes[i];
+ if (activeNote.channel == 0xFF) {
+ // Add the new note.
+ activeNote.source = source;
+ activeNote.channel = outputChannel;
+ activeNote.note = note;
+ activeNote.sustain = false;
+ return true;
+ }
+ }
+ warning("MidiDriver_MT32GM: Could not add active note %x on channel %i", note, outputChannel);
+ return false;
+}
+
+bool MidiDriver_MT32GM::removeActiveNote(uint8 outputChannel, uint8 note, int8 source) {
+ Common::StackLock lock(_activeNotesMutex);
+
+ for (int i = 0; i < _maximumActiveNotes; ++i) {
+ ActiveNote &activeNote = _activeNotes[i];
+ if (activeNote.channel == outputChannel && activeNote.source == source && activeNote.note == note) {
+ if (_controlData[outputChannel]->sustain) {
+ // Sustain is on, so the note should be turned off
+ // when sustain is turned off.
+ activeNote.sustain = true;
+ return false;
+ } else {
+ // Turn off the existing note.
+ activeNote.clear();
+ return true;
+ }
+ }
+ }
+ //warning("MidiDriver_MT32GM: Could not find active note %x on channel %i when removing", note, outputChannel);
+ return false;
+}
+
+void MidiDriver_MT32GM::removeActiveNotes(uint8 outputChannel, bool sustainedNotes) {
+ Common::StackLock lock(_activeNotesMutex);
+
+ // Remove sustained or non-sustained notes from the active notes registration
+ for (int i = 0; i < _maximumActiveNotes; ++i) {
+ if (_activeNotes[i].channel == outputChannel && _activeNotes[i].sustain == sustainedNotes) {
+ _activeNotes[i].clear();
+ }
+ }
+}
+
+void MidiDriver_MT32GM::programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
+ // remember patch id for the current MIDI-channel
+ controlData.program = patchId;
+
+ if (channelLockedByOtherSource)
+ return;
+
+ if (_midiType == MT_MT32) {
+ if (outputChannel == MIDI_RHYTHM_CHANNEL)
+ // Patch changes on the rhythm channel do nothing on an MT-32.
+ // On GM/GS devices they might unintentionally change the drumkit.
+ return;
+
+ if (!_nativeMT32 && !_enableGS) {
+ // GM device: map the patch to GM equivalent
+ patchId = mapMT32InstrumentToGM(patchId);
+ }
+ } else {
+ // GM/GS MIDI
+ if (outputChannel == MIDI_RHYTHM_CHANNEL) {
+ // Correct possible wrong GS drumkit number
+ patchId = GS_DRUMKIT_FALLBACK_MAP[patchId];
+ } else if (!_nativeMT32) {
+ // Correct possible wrong bank / instrument variation
+ byte correctedBank = correctInstrumentBank(controlData.instrumentBank, patchId);
+ if (correctedBank != 0xFF) {
+ // Send out a bank select for the corrected bank number
+ controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, correctedBank, source, controlData);
+ controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData);
+ }
+ } else {
+ // GM on an MT-32: map the patch to the MT-32 equivalent
+ patchId = mapGMInstrumentToMT32(patchId);
+ }
+ }
+
+ // Finally send program change to MIDI device
+ _driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (patchId << 8));
+}
+
+byte MidiDriver_MT32GM::mapMT32InstrumentToGM(byte mt32Instrument) {
+ return _mt32ToGMInstrumentMap[mt32Instrument];
+}
+
+byte MidiDriver_MT32GM::mapGMInstrumentToMT32(byte gmInstrument) {
+ return _gmToMT32InstrumentMap[gmInstrument];
+}
+
+byte MidiDriver_MT32GM::correctInstrumentBank(byte instrumentBank, byte patchId) {
+ if (instrumentBank == 0 || patchId >= 120 || instrumentBank >= 64)
+ // Usually, no bank select has been sent and no correction is
+ // necessary.
+ // No correction is performed on banks 64-127 or on the SFX
+ // instruments (120-127).
+ return 0xFF;
+
+ // Determine the intended bank. This emulates the behavior of the
+ // Roland SC-55 v1.2x. Instruments have 2, 1 or 0 sub-capital tones.
+ // Depending on the selected bank and the selected instrument, the
+ // bank will "fall back" to a sub-capital tone or to the capital
+ // tone (bank 0).
+ byte correctedBank = 0xFF;
+
+ switch (patchId) {
+ case 25: // Steel-String Guitar / 12-string Guitar / Mandolin
+ // This instrument has 2 sub-capital tones. Bank selects 17-63
+ // are corrected to the second sub-capital tone at 16.
+ if (instrumentBank >= 16) {
+ correctedBank = 16;
+ break;
+ }
+ // Corrections for values below 16 are handled below.
+
+ // fall through
+ case 4: // Electric Piano 1 / Detuned Electric Piano 1
+ case 5: // Electric Piano 2 / Detuned Electric Piano 2
+ case 6: // Harpsichord / Coupled Harpsichord
+ case 14: // Tubular-bell / Church Bell
+ case 16: // Organ 1 / Detuned Organ 1
+ case 17: // Organ 2 / Detuned Organ 2
+ case 19: // Church Organ 1 / Church Organ 2
+ case 21: // Accordion Fr / Accordion It
+ case 24: // Nylon-string Guitar / Ukelele
+ case 26: // Jazz Guitar / Hawaiian Guitar
+ case 27: // Clean Guitar / Chorus Guitar
+ case 28: // Muted Guitar / Funk Guitar
+ case 30: // Distortion Guitar / Feedback Guitar
+ case 31: // Guitar Harmonics / Guitar Feedback
+ case 38: // Synth Bass 1 / Synth Bass 3
+ case 39: // Synth Bass 2 / Synth Bass 4
+ case 48: // Strings / Orchestra
+ case 50: // Synth Strings 1 / Synth Strings 3
+ case 61: // Brass 1 / Brass 2
+ case 62: // Synth Brass 1 / Synth Brass 3
+ case 63: // Synth Brass 2 / Synth Brass 4
+ case 80: // Square Wave / Sine Wave
+ case 107: // Koto / Taisho Koto
+ case 115: // Woodblock / Castanets
+ case 116: // Taiko / Concert BD
+ case 117: // Melodic Tom 1 / Melodic Tom 2
+ case 118: // Synth Drum / 808 Tom
+ // These instruments have one sub-capital tone. Bank selects 9-63
+ // are corrected to the sub-capital tone at 8.
+ if (instrumentBank >= 8) {
+ correctedBank = 8;
+ break;
+ }
+ // Corrections for values below 8 are handled below.
+
+ // fall through
+ default:
+ // The other instruments only have a capital tone. Bank selects
+ // 1-63 are corrected to the capital tone.
+ correctedBank = 0;
+ break;
+ }
+
+ // Return the corrected bank, or 0xFF if no correction is needed.
+ return instrumentBank != correctedBank ? correctedBank : 0xFF;
+}
+
+void MidiDriver_MT32GM::sysEx(const byte *msg, uint16 length) {
+ uint16 delay = sysExNoDelay(msg, length);
+
+ if (delay > 0)
+ g_system->delayMillis(delay);
+}
+
+uint16 MidiDriver_MT32GM::sysExNoDelay(const byte *msg, uint16 length) {
+ if (!_nativeMT32 && length >= 3 && msg[0] == 0x41 && msg[2] == 0x16)
+ // MT-32 SysExes have no effect on GM devices.
+ return 0;
+
+ // Send SysEx
+ _driver->sysEx(msg, length);
+
+ // Wait the time it takes to send the SysEx data
+ uint16 delay = (length + 2) * 1000 / 3125;
+
+ // Plus an additional delay for the MT-32 rev00
+ if (_nativeMT32)
+ delay += 40;
+
+ return delay;
+}
+
+void MidiDriver_MT32GM::sysExQueue(const byte *msg, uint16 length) {
+ SysExData sysEx;
+ memcpy(sysEx.data, msg, length);
+ sysEx.length = length;
+
+ _sysExQueueMutex.lock();
+ _sysExQueue.push(sysEx);
+ _sysExQueueMutex.unlock();
+}
+
+uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay) {
+ if (!_nativeMT32)
+ // MT-32 SysExes have no effect on GM devices.
+ return 0;
+
+ byte sysExMessage[270];
+ uint16 sysExPos = 0;
+ byte sysExByte;
+ uint16 sysExChecksum = 0;
+
+ memset(&sysExMessage, 0, sizeof(sysExMessage));
+
+ sysExMessage[0] = 0x41; // Roland
+ sysExMessage[1] = 0x10;
+ sysExMessage[2] = 0x16; // Model MT32
+ sysExMessage[3] = 0x12; // Command DT1
+
+ sysExChecksum = 0;
+
+ sysExMessage[4] = (targetAddress >> 14) & 0x7F;
+ sysExMessage[5] = (targetAddress >> 7) & 0x7F;
+ sysExMessage[6] = targetAddress & 0x7F;
+
+ for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
+ assert(sysExMessage[targetAddressByte] < 0x80); // security check
+ sysExChecksum -= sysExMessage[targetAddressByte];
+ }
+
+ sysExPos = 7;
+ for (int i = 0; i < length; ++i) {
+ sysExByte = *msg++;
+
+ assert(sysExPos < sizeof(sysExMessage));
+ assert(sysExByte < 0x80); // security check
+ sysExMessage[sysExPos++] = sysExByte;
+ sysExChecksum -= sysExByte;
+ }
+
+ // Calculate checksum
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExChecksum & 0x7F;
+
+ if (queue) {
+ sysExQueue(sysExMessage, sysExPos);
+ } else if (!delay) {
+ return sysExNoDelay(sysExMessage, sysExPos);
+ } else {
+ sysEx(sysExMessage, sysExPos);
+ }
+
+ return 0;
+}
+
+void MidiDriver_MT32GM::metaEvent(int8 source, byte type, byte *data, uint16 length) {
+ assert(source < MAXIMUM_SOURCES);
+
+ if (type == 0x2F && source >= 0) // End of Track
+ deinitSource(source);
+
+ _driver->metaEvent(type, data, length);
+}
+
+void MidiDriver_MT32GM::stopAllNotes(bool stopSustainedNotes) {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (!isOutputChannelUsed(i))
+ continue;
+
+ if (stopSustainedNotes) {
+ _driver->send(MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_SUSTAIN, 0);
+ _controlData[i]->sustain = false;
+ }
+ _driver->send(MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+ }
+
+ _activeNotesMutex.lock();
+
+ for (int i = 0; i < _maximumActiveNotes; ++i) {
+ _activeNotes[i].clear();
+ }
+
+ _activeNotesMutex.unlock();
+}
+
+void MidiDriver_MT32GM::startFade(uint16 duration, uint16 targetVolume) {
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ startFade(i, duration, targetVolume);
+ }
+}
+
+void MidiDriver_MT32GM::startFade(uint8 source, uint16 duration, uint16 targetVolume) {
+ assert(source < MAXIMUM_SOURCES);
+
+ _fadingMutex.lock();
+
+ _sources[source].fadePassedTime = 0;
+ _sources[source].fadeStartVolume = _sources[source].volume;
+ _sources[source].fadeEndVolume = targetVolume;
+ _sources[source].fadeDuration = duration * 1000;
+
+ _fadingMutex.unlock();
+}
+
+void MidiDriver_MT32GM::abortFade(FadeAbortType abortType) {
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ abortFade(i, abortType);
+ }
+}
+
+void MidiDriver_MT32GM::abortFade(uint8 source, FadeAbortType abortType) {
+ assert(source < MAXIMUM_SOURCES);
+
+ if (!isFading(source)) {
+ return;
+ }
+
+ _fadingMutex.lock();
+
+ _sources[source].fadeDuration = 0;
+ uint16 newSourceVolume;
+ switch (abortType) {
+ case FADE_ABORT_TYPE_END_VOLUME:
+ newSourceVolume = _sources[source].fadeEndVolume;
+ break;
+ case FADE_ABORT_TYPE_START_VOLUME:
+ newSourceVolume = _sources[source].fadeStartVolume;
+ break;
+ case FADE_ABORT_TYPE_CURRENT_VOLUME:
+ default:
+ _fadingMutex.unlock();
+ return;
+ }
+ setSourceVolume(source, newSourceVolume);
+
+ _fadingMutex.unlock();
+}
+
+bool MidiDriver_MT32GM::isFading() {
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ if (isFading(i))
+ return true;
+ }
+ return false;
+}
+
+bool MidiDriver_MT32GM::isFading(uint8 source) {
+ assert(source < MAXIMUM_SOURCES);
+
+ return _sources[source].fadeDuration > 0;
+}
+
+void MidiDriver_MT32GM::updateFading() {
+ Common::StackLock lock(_fadingMutex);
+
+ _fadeDelay -= _fadeDelay < _timerRate ? _fadeDelay : _timerRate;
+
+ bool updatedVolume = false;
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+
+ if (_sources[i].fadeDuration > 0) {
+ _sources[i].fadePassedTime += _timerRate;
+
+ if (_sources[i].fadePassedTime >= _sources[i].fadeDuration) {
+ // Fade has finished
+ setSourceVolume(i, _sources[i].fadeEndVolume);
+ updatedVolume = true;
+ _sources[i].fadeDuration = 0;
+ } else if (_fadeDelay == 0) {
+ setSourceVolume(i, ((_sources[i].fadePassedTime * (_sources[i].fadeEndVolume - _sources[i].fadeStartVolume)) /
+ _sources[i].fadeDuration) + _sources[i].fadeStartVolume);
+ updatedVolume = true;
+ }
+ }
+ }
+
+ if (updatedVolume)
+ _fadeDelay = FADING_DELAY;
+}
+
+void MidiDriver_MT32GM::clearSysExQueue() {
+ Common::StackLock lock(_sysExQueueMutex);
+
+ _sysExQueue.clear();
+}
+
+MidiChannel *MidiDriver_MT32GM::allocateChannel() {
+ if (_driver)
+ return _driver->allocateChannel();
+ return 0;
+}
+
+MidiChannel *MidiDriver_MT32GM::getPercussionChannel() {
+ if (_driver)
+ return _driver->getPercussionChannel();
+ return 0;
+}
+
+bool MidiDriver_MT32GM::isOutputChannelUsed(int8 outputChannel) {
+ return outputChannel >= 0 && outputChannel < MIDI_CHANNEL_COUNT &&
+ _outputChannelMask & (1 << outputChannel);
+}
+
+uint32 MidiDriver_MT32GM::getBaseTempo() {
+ if (_driver) {
+ return _driver->getBaseTempo();
+ }
+ return 1000000 / _baseFreq;
+}
+
+bool MidiDriver_MT32GM::allocateSourceChannels(uint8 source, uint8 numChannels) {
+ assert(source < MAXIMUM_SOURCES);
+
+ deinitSource(source);
+
+ _allocationMutex.lock();
+
+ uint16 claimedChannels = 0;
+ if (numChannels > 0) {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (!isOutputChannelUsed(i) || i == MIDI_RHYTHM_CHANNEL)
+ continue;
+
+ if (_controlData[i]->source == -1) {
+ claimedChannels |= (1 << i);
+ numChannels--;
+ }
+ if (numChannels == 0)
+ break;
+ }
+ }
+
+ if (numChannels > 0) {
+ // Not enough channels available.
+ _allocationMutex.unlock();
+ return false;
+ }
+
+ // Allocate the channels.
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if ((claimedChannels >> i) & 1) {
+ _controlData[i]->source = source;
+ }
+ // Clear the source channel mapping.
+ if (i != MIDI_RHYTHM_CHANNEL)
+ _sources[source].channelMap[i] = -1;
+ }
+
+ _allocationMutex.unlock();
+
+ _sources[source].availableChannels = claimedChannels;
+
+ return true;
+}
+
+int8 MidiDriver_MT32GM::mapSourceChannel(uint8 source, uint8 dataChannel) {
+ int8 outputChannel = _sources[source].channelMap[dataChannel];
+ if (outputChannel == -1) {
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if ((_sources[source].availableChannels >> i) & 1) {
+ _sources[source].availableChannels &= ~(1 << i);
+ _sources[source].channelMap[dataChannel] = i;
+ outputChannel = i;
+ break;
+ }
+ }
+ if (outputChannel == -1) {
+ warning("MidiDriver_MT32GM: Insufficient available channels for source %i", source);
+ }
+ }
+ return outputChannel;
+}
+
+void MidiDriver_MT32GM::deinitSource(uint8 source) {
+ assert(source < MAXIMUM_SOURCES);
+
+ abortFade(source, FADE_ABORT_TYPE_END_VOLUME);
+
+ // Free channels which were used by this source.
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (!isOutputChannelUsed(i))
+ continue;
+
+ if (_controlData[i]->source == source)
+ _controlData[i]->source = -1;
+ }
+ _sources[source].availableChannels = 0xFFFF;
+ // Reset the data to output channel mapping
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ _sources[source].channelMap[i] = i;
+ }
+
+ _activeNotesMutex.lock();
+
+ // Stop any active notes.
+ for (int i = 0; i < _maximumActiveNotes; ++i) {
+ if (_activeNotes[i].source == source) {
+ if (_activeNotes[i].sustain) {
+ // Turn off sustain
+ controlChange(_activeNotes[i].channel, MIDI_CONTROLLER_SUSTAIN, 0x00, source, *_controlData[i]);
+ } else {
+ // Send note off
+ noteOnOff(_activeNotes[i].channel, MIDI_COMMAND_NOTE_OFF, _activeNotes[i].note, 0x00, source, *_controlData[i]);
+ }
+ }
+ }
+
+ _activeNotesMutex.unlock();
+
+ // TODO Optionally reset some controllers to their
+ // default values? Pitch wheel, volume, sustain...
+}
+
+void MidiDriver_MT32GM::setSourceType(SourceType type) {
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ setSourceType(i, type);
+ }
+}
+
+void MidiDriver_MT32GM::setSourceType(uint8 source, SourceType type) {
+ assert(source < MAXIMUM_SOURCES);
+
+ _sources[source].type = type;
+
+ // Make sure music/sfx volume gets applied
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (!isOutputChannelUsed(i))
+ continue;
+
+ if (_controlData[i]->source == source)
+ controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, source, *_controlData[i]);
+ }
+}
+
+void MidiDriver_MT32GM::setSourceVolume(uint16 volume) {
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ setSourceVolume(i, volume);
+ }
+}
+
+void MidiDriver_MT32GM::setSourceVolume(uint8 source, uint16 volume) {
+ assert(source < MAXIMUM_SOURCES);
+
+ _sources[source].volume = volume;
+
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (!isOutputChannelUsed(i))
+ continue;
+
+ if (_controlData[i]->source == source)
+ controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, source, *_controlData[i]);
+ }
+}
+
+void MidiDriver_MT32GM::setSourceNeutralVolume(uint16 volume) {
+ for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
+ setSourceNeutralVolume(i, volume);
+ }
+}
+
+void MidiDriver_MT32GM::setSourceNeutralVolume(uint8 source, uint16 volume) {
+ assert(source < MAXIMUM_SOURCES);
+
+ _sources[source].neutralVolume = volume;
+}
+
+void MidiDriver_MT32GM::syncSoundSettings() {
+ _userMusicVolume = MIN(256, ConfMan.getInt("music_volume"));
+ _userSfxVolume = MIN(256, ConfMan.getInt("sfx_volume"));
+ _userMute = ConfMan.getBool("mute");
+
+ // Make sure music/sfx volume gets applied
+ for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
+ if (!isOutputChannelUsed(i))
+ continue;
+
+ controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, _controlData[i]->source, *_controlData[i]);
+ }
+}
+
+void MidiDriver_MT32GM::onTimer() {
+ updateFading();
+
+ _sysExQueueMutex.lock();
+
+ _sysExDelay -= (_sysExDelay > _timerRate) ? _timerRate : _sysExDelay;
+
+ if (!_sysExQueue.empty() && _sysExDelay == 0) {
+ // Ready to send next SysEx message to the MIDI device
+ SysExData sysEx = _sysExQueue.pop();
+ _sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
+ }
+
+ _sysExQueueMutex.unlock();
+}
+
+#endif
diff --git a/audio/mt32gm.h b/audio/mt32gm.h
new file mode 100644
index 0000000000..933b52e8e7
--- /dev/null
+++ b/audio/mt32gm.h
@@ -0,0 +1,665 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef AUDIO_MT32GM_H
+#define AUDIO_MT32GM_H
+
+#include "audio/mididrv.h"
+#include "common/mutex.h"
+#include "common/queue.h"
+
+/*
+ * MIDI driver for MT-32 and GM compatible emulators and devices.
+ *
+ * This class contains some commonly needed functionality for these devices and
+ * the MIDI data that targets them. It wraps the MidiDriver instance that does
+ * the actual communication with the MT-32 or GM device.
+ *
+ * This driver has the following features:
+ *
+ * - MIDI device initialization
+ * Construct the driver with the type of MIDI data that will be sent to it.
+ * When the driver is opened, it will create an output MIDI driver appropriate
+ * for the user configuration settings and the type of MIDI data. You can also
+ * create the output MIDI driver yourself and pass it to the open function.
+ * The driver will take care of initializing the MIDI device and setting up
+ * for playback of MT-32 data on a GM/GS device or the other way around.
+ *
+ * - MT-32 <> GM conversion
+ * If the incoming MIDI data has been set to MT-32 and the output device is
+ * GM, the driver will map MT-32 instruments to GM equivalents. GM playback
+ * on an MT-32 device is also supported. Set the _mt32ToGMInstrumentMap and
+ * _gmToMT32InstrumentMap variables to override the standard instrument maps,
+ * or override the mapMT32InstrumentToGM and mapGMInstrumentToMT32 functions
+ * for more advanced mapping algorithms.
+ *
+ * - User volume settings
+ * The driver will scale the MIDI channel volume using the user specified
+ * volume settings. Just call syncSoundSettings when the user has changed the
+ * volume settings. Set the USER_VOLUME_SCALING property to false to disable
+ * this functionality.
+ *
+ * - Reverse stereo
+ * If the game has MIDI data with reversed stereo compared to the targeted
+ * output device, set the MIDI_DATA_REVERSE_PANNING property to reverse
+ * stereo. The driver wil automatically reverse stereo when MT-32 data is
+ * sent to a GM/GS device or the other way around.
+ *
+ * - Correct Roland GS bank and drumkit selects
+ * Some games' MIDI data relies on a feature of the Roland SC-55 MIDI module
+ * which automatically corrects invalid bank selects and drumkit program
+ * changes. The driver replicates this feature to ensure correct instrument
+ * banks and drumkits on other hardware or softsynths.
+ *
+ * - SysEx queue
+ * The sysExQueue function will queue a SysEx message and return immediately.
+ * You can send more messages to the queue while the driver sends the
+ * messages asynchronously with the necessary delays to the MIDI device. Use
+ * the isReady function to check if the device has received all messages and
+ * is ready to start playback. Use this instead of the sysEx function to
+ * prevent the main game loop from being blocked while the driver waits the
+ * necessary amount of time for the MIDI device to process the message.
+ * Use clearSysExQueue to remove all messages from the queue, in case device
+ * initialization has to be aborted.
+*
+ * - Multiple MIDI sources
+ * If the game plays multiple streams of MIDI data at the same time, each
+ * stream can be marked with a source number. This enables the following
+ * features:
+ * - Channel mapping
+ * If multiple sources use the same MIDI channels, the driver can map the
+ * data channels to different output channels to avoid conflicts. Use
+ * allocateSourceChannels to allocate output channels to a source. The
+ * data channels are automatically mapped to the allocated output channels
+ * during playback. The allocated channels are freed when the source is
+ * deinitialized; this is done automatically when an End Of Track MIDI event
+ * is received, or manually by calling deinitSource.
+ * If you only have one source of MIDI data or the sources do not use
+ * conflicting channels, you do not need to allocate channels - the channels
+ * in the MIDI data will be used directly. If you do use this feature, you
+ * have to use it for all MIDI sources to avoid channel conflicts.
+ * The standard channel allocation scheme will allocate the available output
+ * channels with the lowest numbers and will fail if not enough channels are
+ * available. You can override the allocateSourceChannels and
+ * mapSourceChannel functions to customize the allocation and mapping
+ * algorithms.
+ * Note that you can also use the "standard" way of allocating channels
+ * using the allocateChannel function and MidiChannel objects. These two
+ * methods are not coordinated in any way, so don't use both at the same
+ * time.
+ * - Music/SFX volume
+ * Using setSourceType a MIDI source can be designated as music or sound
+ * effects. The driver will then apply the appropriate user volume setting
+ * to the MIDI channel volume. This setting sticks after deinitializing a
+ * source, so if you use the same source numbers for the same types of MIDI
+ * data, you don't need to set the source type repeatedly. The default setup
+ * is music for source 0 and SFX for sources 1 and higher.
+ * - Source volume
+ * If the game changes the volume of the MIDI playback, you can use
+ * setSourceVolume to set the volume level for a source. The driver will
+ * then adjust the current MIDI channel volume and any received MIDI volume
+ * controller messages. Use setSourceNeutralVolume to set the neutral volume
+ * for a source (MIDI volume is not changed when source volume is at this
+ * level; if it is lower or higher, MIDI volume is reduced or increased).
+ * - Volume fading
+ * If the game needs to gradually change the volume of the MIDI playback
+ * (typically for a fade-out), you can use the startFade function. You can
+ * check the status of the fade using isFading, and abort a fade using
+ * abortFade. An active fade is automatically aborted when the fading source
+ * is deinitialized.
+ * The fading functionality uses the source volume, so you should not set
+ * this while a fade is active. After the fade the source volume will remain
+ * at the target level, so if you perform f.e. a fade-out, the source volume
+ * will remain at 0. If you want to start playback again using this source,
+ * use setSourceVolume to set the correct playback volume.
+ * Note that when you stop MIDI playback, notes will not be immediately
+ * silent but will gradually die out ("release"). So if you fade out a
+ * source, stop playback, and immediately reset the source volume, the
+ * note release will be audible. It is recommended to wait about 0.5s
+ * before resetting the source volume.
+ */
+class MidiDriver_MT32GM : public MidiDriver {
+public:
+ static const uint8 MAXIMUM_SOURCES = 10;
+ static const uint16 DEFAULT_SOURCE_NEUTRAL_VOLUME = 255;
+
+ 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 GM_DEFAULT_CHANNEL_VOLUME = 100;
+ // Map for correcting Roland GS drumkit numbers.
+ static const uint8 GS_DRUMKIT_FALLBACK_MAP[128];
+
+protected:
+ static const uint8 MAXIMUM_MT32_ACTIVE_NOTES = 48;
+ static const uint8 MAXIMUM_GM_ACTIVE_NOTES = 96;
+
+ // Timeout between updates of the channel volume for fades (25ms)
+ static const uint16 FADING_DELAY = 25 * 1000;
+
+public:
+ enum SourceType {
+ SOURCE_TYPE_UNDEFINED,
+ SOURCE_TYPE_MUSIC,
+ SOURCE_TYPE_SFX
+ };
+
+ enum FadeAbortType {
+ FADE_ABORT_TYPE_END_VOLUME,
+ FADE_ABORT_TYPE_CURRENT_VOLUME,
+ FADE_ABORT_TYPE_START_VOLUME
+ };
+protected:
+ /**
+ * This stores the values of the MIDI controllers for
+ * a MIDI channel. It is used to keep track of controller
+ * values while a channel is locked, so they can be
+ * restored when the channel is unlocked.
+ */
+ struct MidiChannelControlData {
+ // The source that last sent an event to this channel
+ int8 source;
+ // True if the source volume has been applied to this channel
+ bool sourceVolumeApplied;
+
+ uint16 pitchWheel;
+ byte program;
+ // The Roland GS instrument bank
+ byte instrumentBank;
+
+ byte modulation;
+ // The volume specified by the MIDI data
+ byte volume;
+ // The volume set on the MIDI device. This is scaled using the source
+ // volume and optionally the user-specified volume setting.
+ byte scaledVolume;
+ byte panPosition;
+ byte expression;
+ bool sustain;
+
+ MidiChannelControlData() : source(-1),
+ sourceVolumeApplied(false),
+ pitchWheel(MIDI_PITCH_BEND_DEFAULT),
+ program(0),
+ instrumentBank(0),
+ modulation(0),
+ volume(0),
+ scaledVolume(0),
+ panPosition(0x40),
+ expression(0x7F),
+ sustain(false) { }
+ };
+
+ /**
+ * This stores data about a specific source of MIDI data.
+ */
+ struct MidiSource {
+ // Whether this source sends music or SFX MIDI data.
+ SourceType type;
+ // The source volume (relative volume for this source as defined by the game).
+ // Default is the default neutral value (255).
+ uint16 volume;
+ // The source volume level at which no scaling is performed (volume as defined
+ // in MIDI data is used directly). Volume values below this decrease volume,
+ // values above increase volume (up to the maximum MIDI channel volume).
+ // Set this to match the volume values used by the game engine to avoid having
+ // to convert them. Default value is 255; minimum value is 1.
+ uint16 neutralVolume;
+ // The volume level at which the fade started.
+ uint16 fadeStartVolume;
+ // The target volume level for the fade.
+ uint16 fadeEndVolume;
+ // How much us has passed since the start of the fade.
+ int32 fadePassedTime;
+ // The total duration of the fade (us).
+ int32 fadeDuration;
+ // The mapping of MIDI data channels to output channels for this source.
+ int8 channelMap[MIDI_CHANNEL_COUNT];
+ // Bitmask specifying which MIDI channels are available for use by this source.
+ uint16 availableChannels;
+
+ MidiSource() : type(SOURCE_TYPE_UNDEFINED), volume(DEFAULT_SOURCE_NEUTRAL_VOLUME),
+ neutralVolume(DEFAULT_SOURCE_NEUTRAL_VOLUME), fadeStartVolume(0),
+ fadeEndVolume(0), fadePassedTime(0), fadeDuration(0), availableChannels(0xFFFF) {
+ memset(channelMap, 0, sizeof(channelMap));
+ }
+ };
+
+ /**
+ * This stores information about a note currently playing on the MIDI
+ * device.
+ */
+ struct ActiveNote {
+ int8 source;
+ uint8 channel;
+ uint8 note;
+ // True if the note is sustained. The note will turn off when the
+ // sustain controller for the MIDI channel is turned off.
+ bool sustain;
+
+ ActiveNote() { clear(); }
+
+ void clear() {
+ source = 0x7F;
+ channel = 0xFF;
+ note = 0xFF;
+ sustain = false;
+ }
+
+ };
+
+ /**
+ * Stores data which is to be transmitted as a SysEx message to a MIDI
+ * device. Neither data nor length should include the SysEx start and stop
+ * bytes.
+ */
+ struct SysExData {
+ byte data[270];
+ uint16 length;
+ SysExData() : length(0) {
+ memset(data, 0, sizeof(data));
+ }
+ };
+
+public:
+ MidiDriver_MT32GM(MusicType midiType);
+ ~MidiDriver_MT32GM();
+
+ // MidiDriver interface
+ int open() override;
+ // Open the driver wrapping the specified MidiDriver instance.
+ virtual int open(MidiDriver *driver, bool nativeMT32);
+ void close() override;
+ bool isOpen() const override { return _isOpen; }
+ bool isReady() override { return _sysExQueue.empty(); }
+ uint32 property(int prop, uint32 param) override;
+
+ using MidiDriver_BASE::send;
+ void send(uint32 b) override;
+ void send(int8 source, uint32 b) override;
+ void sysEx(const byte *msg, uint16 length) override;
+ uint16 sysExNoDelay(const byte *msg, uint16 length) override;
+ /**
+ * Puts a SysEx message on the SysEx queue. The message will be sent when
+ * the device is ready to receive it, without blocking the thread.
+ * Use the isReady function to determine if the SysEx has been sent. Other
+ * MIDI messages (not using the queue) should not be sent until the queue
+ * is empty.
+ */
+ void sysExQueue(const byte *msg, uint16 length);
+ /**
+ * Write data to an MT-32 memory location using a SysEx message.
+ * This function will add the necessary header and checksum bytes.
+ *
+ * @param msg Pointer to the data to write to a memory location
+ * @param length The data length
+ * @param targetAddress The start memory address in 8 bit format.
+ * Note that MT-32 memory addresses are sometimes specified in 7 bit format;
+ * these must be converted (f.e. System Area: 10 00 00 -> 04 00 00).
+ * @param queue Specify this parameter to use the SysEx queue to send the
+ * message (see sysExQueue for more information).
+ * @param delay Set this to false to disable the delay to ensure that the
+ * MT-32 has enough time to process the message. This parameter has no
+ * effect if queue is true.
+ * @return The delay in ms that must pass before the next SysEx message is
+ * sent to the MT-32. If delay or queue is true this will be 0; otherwise
+ * 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);
+ void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
+
+ void stopAllNotes(bool stopSustainedNotes = false) override;
+ /**
+ * Starts a fade for all sources.
+ * See the source-specific startFade function for more information.
+ */
+ void startFade(uint16 duration, uint16 targetVolume);
+ /**
+ * Starts a fade for a source. This will linearly increase or decrease the
+ * volume of the MIDI channels used by the source to the specified target
+ * value over the specified length of time.
+ *
+ * @param source The source to fade
+ * @param duration The fade duration in ms
+ * @param targetVolume The volume at the end of the fade
+ */
+ void startFade(uint8 source, uint16 duration, uint16 targetVolume);
+ /**
+ * Aborts any active fades for all sources.
+ * See the source-specific abortFade function for more information.
+ */
+ void abortFade(FadeAbortType abortType = FADE_ABORT_TYPE_END_VOLUME);
+ /**
+ * Aborts an active fade for a source. Depending on the abort type, the
+ * volume will remain at the current value or be set to the start or end
+ * volume. If there is no active fade for the specified source, this
+ * function does nothing.
+ *
+ * @param source The source that should have its fade aborted
+ * @param abortType How to set the volume when aborting the fade (default:
+ * set to the target fade volume).
+ */
+ void abortFade(uint8 source, FadeAbortType abortType = FADE_ABORT_TYPE_END_VOLUME);
+ /**
+ * Returns true if any source has an active fade.
+ */
+ bool isFading();
+ /**
+ * Returns true if the specified source has an active fade.
+ */
+ bool isFading(uint8 source);
+ /**
+ * Removes all SysEx messages in the SysEx queue.
+ */
+ void clearSysExQueue();
+ MidiChannel *allocateChannel() override;
+ MidiChannel *getPercussionChannel() override;
+ uint32 getBaseTempo() override;
+
+ /**
+ * Allocates a number of MIDI channels for use by the specified source.
+ * By default this implements a simple algorithm which allocates the
+ * unallocated channel(s) with the lowest numbers. The channel numbers in
+ * the MIDI data sent by this source will be mapped to the allocated MIDI
+ * output channels. The function can be overridden to implement more
+ * complex channel allocation algorithms.
+ * Channels are freed when the source is deinitialized.
+ * Note that sources are not required to allocate channels, so if sources
+ * use conflicting MIDI channels, make sure to use this function
+ * consistently.
+ *
+ * @param source The source for which to allocate channels
+ * @param numChannels The number of channels to allocate
+ * @return True if allocation was successful, false otherwise (usually
+ * because insufficent channels were available)
+ */
+ virtual bool allocateSourceChannels(uint8 source, uint8 numChannels);
+ /**
+ * Deinitializes a source. This will abort active fades, free any output
+ * channels allocated to the source and stop active notes.
+ */
+ virtual void deinitSource(uint8 source);
+ /**
+ * Sets the type for all sources (music or SFX).
+ */
+ void setSourceType(SourceType type);
+ /**
+ * Sets the type for a specific sources (music or SFX).
+ */
+ void setSourceType(uint8 source, SourceType type);
+ /**
+ * Sets the volume for all sources.
+ */
+ void setSourceVolume(uint16 volume);
+ /**
+ * Sets the volume for this source. The volume values in the MIDI data sent
+ * by this source will be scaled by the source volume.
+ */
+ virtual void setSourceVolume(uint8 source, uint16 volume);
+ void setSourceNeutralVolume(uint16 volume);
+ /**
+ * Sets the neutral volume for this source. If the source volume is at this
+ * level, the volume values in the MIDI data sent by this source will not
+ * be changed. At source volumes below or above this value, the MIDI volume
+ * values will be decreased or increased accordingly.
+ */
+ void setSourceNeutralVolume(uint8 source, uint16 volume);
+ /**
+ * Applies the user volume settings to the MIDI driver. MIDI channel volumes
+ * will be scaled using the user volume.
+ * This function must be called by the engine when the user has changed the
+ * volume settings.
+ */
+ void syncSoundSettings();
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
+ _timer_param = timer_param;
+ _timer_proc = timer_proc;
+ }
+ /**
+ * Runs the MIDI driver's timer related functionality. Will update volume
+ * fades and sends messages from the SysEx queue if necessary.
+ */
+ virtual void onTimer();
+
+protected:
+ /**
+ * This will initialize the _controlData array with the default values for
+ * MT-32 or GM (depending on the _nativeMT32 value).
+ */
+ virtual void initControlData();
+ /**
+ * Initializes the MIDI device. Will call initMT32 or initGM.
+ */
+ virtual void initMidiDevice();
+ /**
+ * Initializes the MT-32 MIDI device. The device will be reset and,
+ * if the parameter is specified, set up for General MIDI data.
+ *
+ * @param initForGM True if the MT-32 should be initialized for GM mapping
+ */
+ virtual void initMT32(bool initForGM);
+ /**
+ * Initializes the General MIDI device. The device will be reset.
+ * If the initForMT32 parameter is specified, the device will be set up for
+ * MT-32 MIDI data. If the device supports Roland GS, the enableGS
+ * parameter can be specified for enhanced GS MT-32 compatiblity.
+ *
+ * @param initForMT32 True if the device should be initialized for MT-32 mapping
+ * @param enableGS True if the device should be initialized for GS MT-32 mapping
+ */
+ virtual void initGM(bool initForMT32, bool enableGS);
+ /**
+ * Processes a MIDI event. The type of event is determined and the
+ * corresponding function is called to handle the event.
+ * This function is called after mapping the MIDI data channel to an output
+ * channel, so the specified output channel is used and not the channel in
+ * the event bytes.
+ *
+ * @param source The source of the event
+ * @param b The event MIDI bytes
+ * @param outputChannel The output channel for the event
+ * @param controlData The control data set to use when processing the event
+ * @param channelLockedByOtherSource True if the output channel is locked
+ * by another source. This will prevent the event from actually being sent
+ * to the MIDI device, but controlData will be updated. Default is false.
+ */
+ virtual void processEvent(int8 source, uint32 b, uint8 outputChannel,
+ MidiChannelControlData &controlData, bool channelLockedByOtherSource = false);
+ /**
+ * Processes a note on or off MIDI event.
+ * This will apply source volume if necessary, update the active note
+ * registration and send the event to the MIDI device.
+ *
+ * @param outputChannel The MIDI output channel for the event
+ * @param command The MIDI command byte
+ * @param controlData The control data set that will be used for applying
+ * source volume
+ */
+ virtual void noteOnOff(byte outputChannel, byte command, byte note, byte velocity,
+ int8 source, MidiChannelControlData &controlData);
+ /**
+ * Process a control change MIDI event.
+ * This will update the specified control data set and apply other
+ * processing if necessary, and then send the event to the MIDI device.
+ *
+ * @param outputChannel The MIDI output channel for the event
+ * @param controlData The control data set that the new controller value
+ * should be stored on
+ * @param channelLockedByOtherSource True if the output channel is locked
+ * by another source. Default is false.
+ */
+ virtual void controlChange(byte outputChannel, byte controllerNumber, byte controllerValue,
+ int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false);
+ /**
+ * Process a program change MIDI event.
+ * This will update the specified control data set, apply MT-32 <> GM
+ * instrument mapping and other processing, and send the event to the MIDI
+ * device.
+ *
+ * @param outputChannel The MIDI output channel for the event
+ * @param controlData The control data set that the new program value
+ * should be stored on
+ * @param channelLockedByOtherSource True if the output channel is locked
+ * by another source. Default is false.
+ */
+ virtual void programChange(byte outputChannel, byte patchId, int8 source,
+ MidiChannelControlData &controlData, bool channelLockedByOtherSource = false);
+ /**
+ * Adds a note to the active note registration.
+ */
+ virtual bool addActiveNote(uint8 outputChannel, uint8 note, int8 source);
+ /**
+ * Removes a note from the active note registration.
+ */
+ virtual bool removeActiveNote(uint8 outputChannel, uint8 note, int8 source);
+ /**
+ * Removes all sustained or all non-sustained notes on the specified MIDI
+ * channel from the active note registration.
+ */
+ virtual void removeActiveNotes(uint8 outputChannel, bool sustainedNotes);
+ /**
+ * Returns true if the MIDI device uses the specified MIDI channel.
+ */
+ bool isOutputChannelUsed(int8 outputChannel);
+ /**
+ * Maps the specified MT-32 instrument to an equivalent GM instrument.
+ * This implementation looks up the instrument in the _mt32ToGMInstrumentMap
+ * array. Override this function to implement more complex mapping schemes.
+ */
+ virtual byte mapMT32InstrumentToGM(byte mt32Instrument);
+ /**
+ * Maps the specified GM instrument to an equivalent MT-32 instrument.
+ * This implementation looks up the instrument in the _gmToMT32InstrumentMap
+ * array. Override this function to implement more complex mapping schemes.
+ */
+ virtual byte mapGMInstrumentToMT32(byte gmInstrument);
+ /**
+ * Checks if the currently selected GS bank / instrument variation
+ * on the specified channel is valid for the specified patch.
+ * If this is not the case, the correct bank will be returned which
+ * can be set by sending a bank select message. If no correction is
+ * needed, 0xFF will be returned.
+ * This emulates the fallback functionality of the Roland SC-55 v1.2x,
+ * on which some games rely to correct wrong bank selects.
+ */
+ byte correctInstrumentBank(byte outputChannel, byte patchId);
+
+ /**
+ * Processes active fades and sets new volume values if necessary.
+ */
+ void updateFading();
+
+ /**
+ * Returns the MIDI output channel mapped to the specified data channel.
+ * If the data channel has not been mapped yet, a new mapping to one of the
+ * output channels available to the source will be created.
+ *
+ * @param source The source using the data channel
+ * @param dataChannel The data channel to map
+ * @return The mapped output channel, or -1 if no mapping is possible
+ */
+ virtual int8 mapSourceChannel(uint8 source, uint8 dataChannel);
+
+ Common::Mutex _fadingMutex; // For operations on fades
+ Common::Mutex _allocationMutex; // For operations on MIDI channel allocation
+ Common::Mutex _activeNotesMutex; // For operations on active notes registration
+
+ // The wrapped MIDI driver.
+ MidiDriver *_driver;
+ // The type of MIDI data supplied to the driver: MT-32 or General MIDI.
+ MusicType _midiType;
+ // True if the MIDI output is an MT-32 (hardware or 100% emulated),
+ // false if the MIDI output is a General MIDI device.
+ bool _nativeMT32;
+ // True if the General MIDI output supports Roland GS for improved MT-32 mapping.
+ bool _enableGS;
+ // Indicates if the stereo panning in the MIDI data is reversed
+ // compared to the stereo panning of the intended MIDI device.
+ bool _midiDataReversePanning;
+ // Indicates if the stereo panning of the output MIDI device is
+ // reversed compared to the stereo panning of the type of MIDI
+ // device targeted by the MIDI data (i.e. MT-32 data playing on
+ // a GM device or the other way around).
+ bool _midiDeviceReversePanning;
+ // True if GS percussion channel volume should be scaled to match MT-32 volume.
+ bool _scaleGSPercussionVolumeToMT32;
+
+ // True if the driver should scale MIDI channel volume to the user specified
+ // volume settings.
+ bool _userVolumeScaling;
+
+ // User volume settings
+ uint16 _userMusicVolume;
+ uint16 _userSfxVolume;
+ bool _userMute;
+
+ // True if this MIDI driver has been opened.
+ bool _isOpen;
+ // Bitmask of the MIDI channels in use by the output device.
+ uint16 _outputChannelMask;
+ int _baseFreq;
+ uint32 _timerRate;
+
+ // stores the controller values for each MIDI channel
+ MidiChannelControlData *_controlData[MIDI_CHANNEL_COUNT];
+
+ MidiSource _sources[MAXIMUM_SOURCES];
+
+ // Maps used for MT-32 <> GM instrument mapping. Set these to an alternate
+ // 128 byte array to customize the mapping.
+ const byte *_mt32ToGMInstrumentMap;
+ const byte *_gmToMT32InstrumentMap;
+ // The maximum active notes for the current MIDI device.
+ uint8 _maximumActiveNotes;
+ // Active note registration
+ ActiveNote *_activeNotes;
+
+ // The number of microseconds to wait before the next fading step.
+ uint16 _fadeDelay;
+
+ // The current number of microseconds that have to elapse before the next
+ // SysEx message can be sent.
+ uint32 _sysExDelay;
+ // Queue of SysEx messages to be sent to the MIDI device.
+ Common::Queue<SysExData> _sysExQueue;
+ // Mutex for write access to the SysEx queue.
+ Common::Mutex _sysExQueueMutex;
+
+ // External timer callback
+ void *_timer_param;
+ Common::TimerManager::TimerProc _timer_proc;
+
+public:
+ // Callback hooked up to the driver wrapped by the MIDI driver
+ // object. Executes onTimer and the external callback set by
+ // the setTimerCallback function.
+ static void timerCallback(void *data) {
+ MidiDriver_MT32GM *driver = (MidiDriver_MT32GM *)data;
+ driver->onTimer();
+ if (driver->_timer_proc && driver->_timer_param)
+ driver->_timer_proc(driver->_timer_param);
+ }
+};
+
+#endif
diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp
index 73ce45f35d..955d671aa7 100644
--- a/engines/sci/sound/drivers/midi.cpp
+++ b/engines/sci/sound/drivers/midi.cpp
@@ -28,6 +28,7 @@
#include "common/system.h"
#include "audio/mididrv.h"
+#include "audio/mt32gm.h"
#include "sci/resource.h"
#include "sci/engine/features.h"
@@ -418,7 +419,7 @@ void MidiPlayer_Midi::setPatch(int channel, int patch) {
// Some GM devices support the GS drumkits as well.
// Apply drumkit fallback to correct invalid drumkit numbers.
- patchToSend = patch < 128 ? _driver->_gsDrumkitFallbackMap[patch] : 0;
+ patchToSend = patch < 128 ? MidiDriver_MT32GM::GS_DRUMKIT_FALLBACK_MAP[patch] : 0;
_channels[channel].patch = patchToSend;
debugC(kDebugLevelSound, "[Midi] Selected drumkit %i (requested %i)", patchToSend, patch);
}
Commit: 4834651115aa721c0630ddd167813d6105429972
https://github.com/scummvm/scummvm/commit/4834651115aa721c0630ddd167813d6105429972
Author: NMIError (crampen at gmail.com)
Date: 2020-11-28T17:59:15Z
Commit Message:
MIDI: Add multisource support to SMF parser
This allows the SMF parser to work with MidiDriver implementations that support
multiple MIDI sources. You can set the source number when constructing the
parser. This is then passed along when MIDI messages are sent to the driver.
Changed paths:
audio/midiparser.h
audio/midiparser_smf.cpp
diff --git a/audio/midiparser.h b/audio/midiparser.h
index 27ed23c2a2..bad770aa40 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -483,7 +483,7 @@ public:
static void defaultXMidiCallback(byte eventData, void *refCon);
- static MidiParser *createParser_SMF();
+ static MidiParser *createParser_SMF(int8 source = -1);
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, int source = -1);
static MidiParser *createParser_QT();
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index e1d574e219..7e1c5f1f22 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "audio/mididrv.h"
#include "audio/midiparser.h"
#include "common/textconsole.h"
#include "common/util.h"
@@ -31,13 +32,24 @@ class MidiParser_SMF : public MidiParser {
protected:
byte *_buffer;
bool _malformedPitchBends;
+ /**
+ * The source number to use when sending MIDI messages to the driver.
+ * When using multiple sources, use source 0 and higher. This must be
+ * used when source volume or channel locking is used.
+ * By default this is -1, which means the parser is the only source
+ * of MIDI messages and multiple source functionality is disabled.
+ */
+ int8 _source;
protected:
void compressToType0();
void parseNextEvent(EventInfo &info);
+ void sendToDriver(uint32 b) override;
+ void sendMetaEventToDriver(byte type, byte *data, uint16 length) override;
+
public:
- MidiParser_SMF() : _buffer(0), _malformedPitchBends(false) {}
+ MidiParser_SMF(int8 source = -1) : _buffer(0), _malformedPitchBends(false), _source(source) { }
~MidiParser_SMF();
bool loadMusic(byte *data, uint32 size);
@@ -385,4 +397,20 @@ void MidiParser_SMF::compressToType0() {
*output++ = 0x00;
}
-MidiParser *MidiParser::createParser_SMF() { return new MidiParser_SMF; }
+void MidiParser_SMF::sendToDriver(uint32 b) {
+ if (_source < 0) {
+ MidiParser::sendToDriver(b);
+ } else {
+ _driver->send(_source, b);
+ }
+}
+
+void MidiParser_SMF::sendMetaEventToDriver(byte type, byte *data, uint16 length) {
+ if (_source < 0) {
+ MidiParser::sendMetaEventToDriver(type, data, length);
+ } else {
+ _driver->metaEvent(_source, type, data, length);
+ }
+}
+
+MidiParser *MidiParser::createParser_SMF(int8 source) { return new MidiParser_SMF(source); }
Commit: 1bd9e614174c9984765976642c4ff3c349b4741d
https://github.com/scummvm/scummvm/commit/1bd9e614174c9984765976642c4ff3c349b4741d
Author: NMIError (crampen at gmail.com)
Date: 2020-11-28T17:59:15Z
Commit Message:
MIDI: Fix virtual destructor and nullptr warnings
Changed paths:
audio/miles.h
audio/mt32gm.cpp
diff --git a/audio/miles.h b/audio/miles.h
index 883eb36c6b..9773a9b735 100644
--- a/audio/miles.h
+++ b/audio/miles.h
@@ -92,6 +92,8 @@ struct MilesMT32InstrumentEntry {
*/
class MidiDriver_Miles_Xmidi_Timbres {
public:
+ virtual ~MidiDriver_Miles_Xmidi_Timbres() { }
+
/**
* Processes the timbre chunk specified for a track
* in an XMIDI file. This will load the necessary
diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
index 67cd66b0a8..3528a06f35 100644
--- a/audio/mt32gm.cpp
+++ b/audio/mt32gm.cpp
@@ -296,18 +296,20 @@ void MidiDriver_MT32GM::initGM(bool initForMT32, bool enableGS) {
// Note: All Roland GS devices support CM-64/32L maps
- // Set Percussion Channel to SC-55 Map (CC#32, 01H), then
- // Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
- // Bank select MSB: bank 0
- getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
- // Bank select LSB: map 1 (SC-55)
- getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_LSB, 1);
+ if (getPercussionChannel() != 0) {
+ // Set Percussion Channel to SC-55 Map (CC#32, 01H), then
+ // Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
+ // Bank select MSB: bank 0
+ getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
+ // Bank select LSB: map 1 (SC-55)
+ getPercussionChannel()->controlChange(MIDI_CONTROLLER_BANK_SELECT_LSB, 1);
+ }
// Patch change: 127 (CM-64/32L)
send(127 << 8 | MIDI_COMMAND_PROGRAM_CHANGE | 9);
// Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
for (i = 0; i < 16; ++i) {
- if (i == getPercussionChannel()->getNumber())
+ if (getPercussionChannel() != 0 && i == getPercussionChannel()->getNumber())
continue;
// Bank select MSB: bank 127 (CM-64/32L)
send((127 << 16) | (MIDI_CONTROLLER_BANK_SELECT_MSB << 8) | (MIDI_COMMAND_CONTROL_CHANGE | i));
More information about the Scummvm-git-logs
mailing list