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:
NMIError 2020-07-27 14:43:21 +02:00 committed by Thierry Crozat
parent a984176b12
commit 7857df2ea9
9 changed files with 2163 additions and 1189 deletions

View File

@ -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)
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

665
audio/mt32gm.h Normal file
View 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

View File

@ -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);
}