mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-20 19:21:46 +00:00
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.
This commit is contained in:
parent
a984176b12
commit
7857df2ea9
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
140
audio/mididrv.h
140
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;
|
||||
|
256
audio/miles.h
256
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);
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@ MODULE_OBJS := \
|
||||
miles_midi.o \
|
||||
mixer.o \
|
||||
mpu401.o \
|
||||
mt32gm.o \
|
||||
musicplugin.o \
|
||||
null.o \
|
||||
timestamp.o \
|
||||
|
1173
audio/mt32gm.cpp
Normal file
1173
audio/mt32gm.cpp
Normal file
File diff suppressed because it is too large
Load Diff
665
audio/mt32gm.h
Normal file
665
audio/mt32gm.h
Normal file
@ -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
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user