1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
|
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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 compatibility.
*
* @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<SysExData> _sysExQueue;
// Mutex for write access to the SysEx queue.
Common::Mutex _sysExQueueMutex;
};
/** @} */
#endif
|