/* 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 3 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, see . * */ #ifndef AUDIO_MT32GM_H #define AUDIO_MT32GM_H #include "audio/mididrv.h" #include "audio/mididrv_ms.h" #include "common/list.h" #include "common/mutex.h" /** * @defgroup audio_mt32_gm MIDI driver for MT-32 and GM * @ingroup audio * * @brief MIDI driver for MT-32 and GM compatible emulators and devices. * @{ */ /** * 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. * * - 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 * feature: * - 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. * 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. */ class MidiDriver_MT32GM : public MidiDriver_Multisource { public: static const byte MT32_DEFAULT_INSTRUMENTS[8]; static const byte MT32_DEFAULT_PANNING[8]; static const uint8 MT32_DEFAULT_CHANNEL_VOLUME = 102; static const uint8 GM_DEFAULT_CHANNEL_VOLUME = 100; // Map for correcting Roland GS drumkit numbers. static const uint8 GS_DRUMKIT_FALLBACK_MAP[128]; static const uint8 MT32_DISPLAY_NUM_CHARS = 20; static const uint32 MT32_DISPLAY_MEMORY_ADDRESS = 0x20 << 14; protected: static const uint8 MAXIMUM_MT32_ACTIVE_NOTES = 48; static const uint8 MAXIMUM_GM_ACTIVE_NOTES = 96; 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 channelPressure; 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; // The currently selected Registered Parameter Number uint16 rpn; byte pitchBendSensitivity; MidiChannelControlData() : source(-1), sourceVolumeApplied(false), pitchWheel(MIDI_PITCH_BEND_DEFAULT), program(0), instrumentBank(0), channelPressure(0), modulation(0), volume(0), scaledVolume(0), panPosition(MIDI_PANNING_DEFAULT), expression(MIDI_EXPRESSION_DEFAULT), sustain(false), rpn(MIDI_RPN_NULL), pitchBendSensitivity(0) { } }; /** * 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; int8 source; SysExData() : length(0), source(-1) { memset(data, 0, sizeof(data)); } }; 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(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(int8 source = -1) override; 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, int8 source = -1); /** * Write data to an MT-32 memory location using a SysEx message. * This function will add the necessary header and checksum bytes. * * @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, int8 source = -1); void metaEvent(int8 source, byte type, byte *data, uint16 length) override; void stopAllNotes(bool stopSustainedNotes = false) override; /** * 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. */ void deinitSource(uint8 source) override; 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); /** * Applies the controller default settings to the specified output channel * for the specified source. * This will set all default values specified on _controllerDefaults on the * channel except sustain, which is set by deinitSource. * * @param source The source triggering the default settings * @param controlData The control data set to use when setting the defaults * @param outputChannel The output channel on which the defaults should be * set * @param channelLockedByOtherSource True if the output channel is locked * by another source. This will prevent the defaults from actually being * sent to the MIDI device, but controlData will be updated. Default is * false. */ virtual void applyControllerDefaults(uint8 source, MidiChannelControlData &controlData, uint8 outputChannel, bool channelLockedByOtherSource); /** * Processes a note on or off MIDI event. * 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); /** * Processes a polyphonic aftertouch MIDI event. * This implementation will just send the event to the MIDI device. * * @param outputChannel The MIDI output channel for the event * @param note The note on which aftertouch should be applied * @param pressure The amount of pressure which should be applied * @param source The source of the event * @param controlData The control data set for the MIDI channel * @param channelLockedByOtherSource True if the output channel is locked * by another source. Default is false. */ virtual void polyAftertouch(byte outputChannel, byte note, byte pressure, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); /** * Process a control change MIDI event. * 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); /** * Processes a channel aftertouch MIDI event. * This whil update the specified control data set and send the event to * the MIDI device. * * @param outputChannel The MIDI output channel for the event * @param pressure The amount of pressure which should be applied * @param source The source of the event * @param controlData The control data set for the MIDI channel * @param channelLockedByOtherSource True if the output channel is locked * by another source. Default is false. */ virtual void channelAftertouch(byte outputChannel, byte pressure, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); /** * Processes a pitch bend MIDI event. * This whil update the specified control data set and send the event to * the MIDI device. * * @param outputChannel The MIDI output channel for the event * @param pitchBendLsb The pitch bend LSB * @param pitchBendMsb The pitch bend MSB * @param source The source of the event * @param controlData The control data set for the MIDI channel * @param channelLockedByOtherSource True if the output channel is locked * by another source. Default is false. */ virtual void pitchBend(byte outputChannel, uint8 pitchBendLsb, uint8 pitchBendMsb, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource = false); /** * Adds a note to the active note registration. */ 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); /** * 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); void applySourceVolume(uint8 source) override; void stopAllNotes(uint8 source, uint8 channel) override; /** * Runs the MIDI driver's timer related functionality. Will update volume * fades and sends messages from the SysEx queue if necessary. */ void onTimer() override; 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 this MIDI driver has been opened. bool _isOpen; // Bitmask of the MIDI channels in use by the output device. uint16 _outputChannelMask; int _baseFreq; // stores the controller values for each MIDI channel MidiChannelControlData *_controlData[MIDI_CHANNEL_COUNT]; // The mapping of MIDI data channels to output channels for each source. int8 _channelMap[MAXIMUM_SOURCES][MIDI_CHANNEL_COUNT]; // Bitmask specifying which MIDI channels are available for use by each source. uint16 _availableChannels[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 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::List _sysExQueue; // Mutex for write access to the SysEx queue. Common::Mutex _sysExQueueMutex; }; /** @} */ #endif