AUDIO: Move MIDI multisource to base class

This moves the multisource functionality from the MT-32/GM MIDI driver to a new
base class so it can be reused by future multisource drivers.
This commit is contained in:
Coen Rampen 2021-05-15 22:03:28 +02:00
parent d8c3e22a54
commit f660238ab5
7 changed files with 689 additions and 398 deletions

254
audio/mididrv_ms.cpp Normal file
View file

@ -0,0 +1,254 @@
/* 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.
*
*/
#include "common/config-manager.h"
#include "audio/mididrv_ms.h"
const uint8 MidiDriver_Multisource::MAXIMUM_SOURCES;
const uint16 MidiDriver_Multisource::DEFAULT_SOURCE_NEUTRAL_VOLUME;
const uint16 MidiDriver_Multisource::FADING_DELAY;
MidiDriver_Multisource::MidiSource::MidiSource() :
type(SOURCE_TYPE_UNDEFINED),
volume(DEFAULT_SOURCE_NEUTRAL_VOLUME),
neutralVolume(DEFAULT_SOURCE_NEUTRAL_VOLUME),
fadeStartVolume(0),
fadeEndVolume(0),
fadePassedTime(0),
fadeDuration(0) { }
MidiDriver_Multisource::MidiDriver_Multisource() :
_userVolumeScaling(false),
_userMusicVolume(192),
_userSfxVolume(192),
_userMute(false),
_timerRate(0),
_fadeDelay(0),
_timer_param(0),
_timer_proc(0) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
// Default source type: 0 = music, 1+ = SFX
_sources[i].type = (i == 0 ? SOURCE_TYPE_MUSIC : SOURCE_TYPE_SFX);
}
}
void MidiDriver_Multisource::send(uint32 b) {
send(-1, b);
}
uint32 MidiDriver_Multisource::property(int prop, uint32 param) {
switch (prop) {
case PROP_USER_VOLUME_SCALING:
if (param == 0xFFFF)
return _userVolumeScaling ? 1 : 0;
_userVolumeScaling = param > 0;
break;
default:
return MidiDriver::property(prop, param);
}
return 0;
}
void MidiDriver_Multisource::startFade(uint16 duration, uint16 targetVolume) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
startFade(i, duration, targetVolume);
}
}
void MidiDriver_Multisource::startFade(uint8 source, uint16 duration, uint16 targetVolume) {
Common::StackLock lock(_fadingMutex);
assert(source < MAXIMUM_SOURCES);
// Reset the number of microseconds which have passed since the start of
// the fade.
_sources[source].fadePassedTime = 0;
// Set start volume to current volume.
_sources[source].fadeStartVolume = _sources[source].volume;
_sources[source].fadeEndVolume = targetVolume;
// Convert to microseconds and set the duration. A duration > 0 will cause
// the fade to be processed by updateFading.
_sources[source].fadeDuration = duration * 1000;
}
void MidiDriver_Multisource::abortFade(FadeAbortType abortType) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
abortFade(i, abortType);
}
}
void MidiDriver_Multisource::abortFade(uint8 source, FadeAbortType abortType) {
Common::StackLock lock(_fadingMutex);
assert(source < MAXIMUM_SOURCES);
if (!isFading(source)) {
// Nothing to abort.
return;
}
// Set the fade duration to 0. This will stop the fade from being processed
// by updateFading.
_sources[source].fadeDuration = 0;
// Now set the intended end volume.
uint16 newSourceVolume;
switch (abortType) {
case FADE_ABORT_TYPE_END_VOLUME:
newSourceVolume = _sources[source].fadeEndVolume;
break;
case FADE_ABORT_TYPE_START_VOLUME:
newSourceVolume = _sources[source].fadeStartVolume;
break;
case FADE_ABORT_TYPE_CURRENT_VOLUME:
default:
return;
}
setSourceVolume(source, newSourceVolume);
}
bool MidiDriver_Multisource::isFading() {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
if (isFading(i))
return true;
}
return false;
}
bool MidiDriver_Multisource::isFading(uint8 source) {
assert(source < MAXIMUM_SOURCES);
return _sources[source].fadeDuration > 0;
}
void MidiDriver_Multisource::updateFading() {
Common::StackLock lock(_fadingMutex);
// Decrease the fade delay by the time that has passed since the last
// fading update.
_fadeDelay -= (_fadeDelay < _timerRate ? _fadeDelay : _timerRate);
bool updatedVolume = false;
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
if (_sources[i].fadeDuration > 0) {
// This source has an active fade.
// Update the time that has passed since the start of the fade.
_sources[i].fadePassedTime += _timerRate;
if (_sources[i].fadePassedTime >= _sources[i].fadeDuration) {
// The fade has finished.
// Set the end volume.
setSourceVolume(i, _sources[i].fadeEndVolume);
updatedVolume = true;
// Stop further processing of this fade.
_sources[i].fadeDuration = 0;
} else if (_fadeDelay == 0) {
// The fade has not yet finished and the fade delay has run
// down. Waiting for the fade delay prevents sending out volume
// updates on every updateFading call, which can overflow
// slower MIDI hardware.
// Set the new volume value.
setSourceVolume(i, ((_sources[i].fadePassedTime * (_sources[i].fadeEndVolume - _sources[i].fadeStartVolume)) /
_sources[i].fadeDuration) + _sources[i].fadeStartVolume);
updatedVolume = true;
}
}
}
if (updatedVolume)
// Set the fade delay to delay the next volume update.
_fadeDelay = FADING_DELAY;
}
void MidiDriver_Multisource::deinitSource(uint8 source) {
abortFade(source, FADE_ABORT_TYPE_END_VOLUME);
// Stop all active notes for this source.
stopAllNotes(source, 0xFF);
}
void MidiDriver_Multisource::setSourceType(SourceType type) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
setSourceType(i, type);
}
}
void MidiDriver_Multisource::setSourceType(uint8 source, SourceType type) {
assert(source < MAXIMUM_SOURCES);
_sources[source].type = type;
// A changed source type can mean a different user volume level should be
// used for this source. Calling applySourceVolume will apply the user
// volume.
applySourceVolume(source);
}
void MidiDriver_Multisource::setSourceVolume(uint16 volume) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
setSourceVolume(i, volume);
}
}
void MidiDriver_Multisource::setSourceVolume(uint8 source, uint16 volume) {
assert(source < MAXIMUM_SOURCES);
_sources[source].volume = volume;
// Set the volume for active notes and/or MIDI channels for this source.
applySourceVolume(source);
}
void MidiDriver_Multisource::setSourceNeutralVolume(uint16 volume) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
setSourceNeutralVolume(i, volume);
}
}
void MidiDriver_Multisource::setSourceNeutralVolume(uint8 source, uint16 volume) {
assert(source < MAXIMUM_SOURCES);
_sources[source].neutralVolume = volume;
}
void MidiDriver_Multisource::syncSoundSettings() {
// Get user volume settings.
_userMusicVolume = MIN(256, ConfMan.getInt("music_volume"));
_userSfxVolume = MIN(256, ConfMan.getInt("sfx_volume"));
_userMute = ConfMan.getBool("mute");
// Calling applySourceVolume will apply the user volume.
applySourceVolume(0xFF);
}
void MidiDriver_Multisource::onTimer() {
updateFading();
if (_timer_proc && _timer_param)
_timer_proc(_timer_param);
}

365
audio/mididrv_ms.h Normal file
View file

@ -0,0 +1,365 @@
/* 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_MIDIDRV_MS_H
#define AUDIO_MIDIDRV_MS_H
#include "common/mutex.h"
#include "audio/mididrv.h"
/**
* Abstract base class for MIDI drivers supporting multiple simultaneous
* sources of MIDI data.
*
* These drivers support the following features:
*
* - 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. When a source has finished
* playing, it must be deinitialized to release any resources allocated to
* it. This is done automatically when an End Of Track MIDI meta event is
* received, or manually by calling deinitSource.
* Using source numbers enables the following features:
* - 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.
*
* - User volume settings
* The driver can 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 true to enable
* this functionality.
*
* A driver extending this class must implement the following functions:
* - send(source, data): process a MIDI event for a specific source.
* - stopAllNotes(source, channel): stop all active notes for a source and/or
* MIDI channel (called when a source is deinitialized).
* - applySourceVolume(source): set the current source volume on active notes
* and/or MIDI channels.
*/
class MidiDriver_Multisource : public MidiDriver {
public:
/**
* The maximum number of sources supported. This can be increased if
* necessary, but this will consume more memory and processing time.
*/
static const uint8 MAXIMUM_SOURCES = 10;
/**
* The default neutral volume level for a source. If the source volume is
* set to this level, the volume levels in the MIDI data are used directly;
* if source volume is lower or higher, output volume is decreased or
* increased, respectively. Use @see setSourceNeutralVolume to change the
* default neutral volume.
*/
static const uint16 DEFAULT_SOURCE_NEUTRAL_VOLUME = 255;
protected:
// Timeout between updates of the channel volume for fades (25ms)
static const uint16 FADING_DELAY = 25 * 1000;
public:
/**
* The type of audio produced by a MIDI source (music or sound effects).
*/
enum SourceType {
/**
* Source type not specified (generally treated as music).
*/
SOURCE_TYPE_UNDEFINED,
/**
* Source produces music.
*/
SOURCE_TYPE_MUSIC,
/**
* Source produces sound effects.
*/
SOURCE_TYPE_SFX
};
/**
* Specifies what happens to the volume when a fade is aborted.
*/
enum FadeAbortType {
/**
* The volume is set to the fade's end volume level.
*/
FADE_ABORT_TYPE_END_VOLUME,
/**
* The volume remains at the current level.
*/
FADE_ABORT_TYPE_CURRENT_VOLUME,
/**
* The volume is reset to the fade's start volume level.
*/
FADE_ABORT_TYPE_START_VOLUME
};
protected:
// 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 the 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 time (microseconds) has passed since the start of the fade.
int32 fadePassedTime;
// The total duration of the fade (microseconds).
int32 fadeDuration;
MidiSource();
};
public:
MidiDriver_Multisource();
// MidiDriver functions
using MidiDriver_BASE::send;
void send(uint32 b) override;
void send(int8 source, uint32 b) override = 0;
uint32 property(int prop, uint32 param) override;
/**
* Deinitializes a source. This will abort active fades and stop any active
* notes.
*
* @param source The source to deinitialize.
*/
virtual void deinitSource(uint8 source);
/**
* Sets the type for all sources (music or SFX).
*
* @param type The new type for all sources.
*/
void setSourceType(SourceType type);
/**
* Sets the type for a specific sources (music or SFX).
*
* @param source The source for which the type should be set.
* @param type The new type for the specified source.
*/
void setSourceType(uint8 source, SourceType type);
/**
* Sets the source volume for all sources.
*
* @param volume The new source 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.
*
* @param source The source for which the source volume should be set.
* @param volume The new source volume for the specified source.
*/
void setSourceVolume(uint8 source, uint16 volume);
/**
* Sets the neutral volume for all sources. See the source-specific
* setSourceNeutralVolume function for details.
*
* @param volume The new neutral volume for all sources.
*/
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.
*
* @param source The source for which the neutral volume should be set.
* @param volume The new neutral volume for the specified source.
*/
void setSourceNeutralVolume(uint8 source, uint16 volume);
/**
* Starts a fade for all sources.
* See the source-specific startFade function for more information.
*
* @param duration The fade duration in milliseconds
* @param targetVolume The volume at the end of the fade
*/
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 milliseconds
* @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.
*
* @param abortType How to set the volume when aborting the fade (default:
* set to the target fade volume).
*/
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);
/**
* Check if any source has an active fade.
*
* @return True if any source has an active fade.
*/
bool isFading();
/**
* Check if the specified source has an active fade.
*
* @return True if the specified source has an active fade.
*/
bool isFading(uint8 source);
/**
* 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();
using MidiDriver::stopAllNotes;
/**
* Stops all active notes (including sustained notes) for the specified
* source and MIDI channel. For both source and channel the value 0xFF can
* be specified, in which case active notes will be stopped for all sources
* and/or MIDI channels.
*
* @param source The source for which all notes should be stopped, or all
* sources if 0xFF is specified.
* @param channel The MIDI channel on which all notes should be stopped, or
* all channels if 0xFF is specified.
*/
virtual void stopAllNotes(uint8 source, uint8 channel) = 0;
/**
* Sets a callback which will be called whenever the driver's timer
* callback is called by the underlying emulator or hardware driver. The
* callback will only be called when the driver is open. Use
* @see getBaseTempo to get the delay between each callback invocation.
*
* @param timer_param A parameter that will be passed to the callback
* function. Optional.
* @param timer_proc The function that should be called.
*/
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
_timer_param = timer_param;
_timer_proc = timer_proc;
}
protected:
/**
* Applies the current source volume to the active notes and/or MIDI
* channels of the specified source. 0xFF can be specified to apply the
* source volume for all sources.
*
* @param source The source for which the source volume should be applied,
* or all sources if 0xFF is specified.
*/
virtual void applySourceVolume(uint8 source) = 0;
/**
* Processes active fades and sets new volume values if necessary.
*/
void updateFading();
/**
* Runs the MIDI driver's timer related functionality. Will update volume
* fades and calls the timer callback if necessary.
*/
virtual void onTimer();
// MIDI source data
MidiSource _sources[MAXIMUM_SOURCES];
// 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;
Common::Mutex _fadingMutex; // For operations on fades
// The number of microseconds to wait before the next fading step.
uint16 _fadeDelay;
// The number of microseconds between timer callback invocations.
uint32 _timerRate;
// External timer callback
void *_timer_param;
Common::TimerManager::TimerProc _timer_proc;
};
#endif

View file

@ -132,14 +132,6 @@ public:
* Automatically executed when an End Of Track meta event is received.
*/
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.
* Note that source volume remains set for the source number even after deinitializing the
* 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) override;
void stopAllNotes(bool stopSustainedNotes = false) override;
@ -148,6 +140,7 @@ public:
protected:
void initControlData() override;
void initMidiDevice() override;
void applySourceVolume(uint8 source) override;
private:
void writeRhythmSetup(byte note, byte customTimbreId);

View file

@ -55,11 +55,6 @@ MidiDriver_Miles_Midi::MidiDriver_Miles_Midi(MusicType midiType, MilesMT32Instru
_instrumentTablePtr = instrumentTablePtr;
_instrumentTableCount = instrumentTableCount;
// Disable user volume scaling by default. Most (all?)
// engines using Miles implement this themselves. Can
// be turned on using the property function.
_userVolumeScaling = false;
setSourceNeutralVolume(MILES_DEFAULT_SOURCE_NEUTRAL_VOLUME);
}
@ -141,7 +136,7 @@ void MidiDriver_Miles_Midi::send(int8 source, uint32 b) {
byte command = b & 0xf0;
byte dataChannel = b & 0xf;
byte outputChannel = source < 0 ? dataChannel : _sources[source].channelMap[dataChannel];
byte outputChannel = source < 0 ? dataChannel : _channelMap[source][dataChannel];
MidiChannelEntry &outputChannelEntry = _midiChannels[outputChannel];
// Only send the message to the MIDI device if the channel is not locked or
@ -382,7 +377,7 @@ void MidiDriver_Miles_Midi::lockChannel(uint8 source, uint8 dataChannel) {
_midiChannels[lockChannel].locked = true;
_midiChannels[lockChannel].lockDataChannel = dataChannel;
_sources[source].channelMap[dataChannel] = lockChannel;
_channelMap[source][dataChannel] = lockChannel;
// Copy current controller values so they can be restored when unlocking the channel
*_midiChannels[lockChannel].unlockData = *_midiChannels[lockChannel].currentData;
_midiChannels[lockChannel].currentData->source = source;
@ -424,7 +419,7 @@ void MidiDriver_Miles_Midi::unlockChannel(uint8 outputChannel) {
// Unlock the channel
channel.locked = false;
_sources[channel.currentData->source].channelMap[channel.lockDataChannel] = channel.lockDataChannel;
_channelMap[channel.currentData->source][channel.lockDataChannel] = channel.lockDataChannel;
channel.lockDataChannel = -1;
channel.currentData->source = channel.unlockData->source;
@ -874,11 +869,7 @@ void MidiDriver_Miles_Midi::deinitSource(uint8 source) {
MidiDriver_MT32GM::deinitSource(source);
}
void MidiDriver_Miles_Midi::setSourceVolume(uint8 source, uint16 volume) {
assert(source < MAXIMUM_SOURCES);
_sources[source].volume = volume;
void MidiDriver_Miles_Midi::applySourceVolume(uint8 source) {
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;

View file

@ -6,6 +6,7 @@ MODULE_OBJS := \
audiostream.o \
fmopl.o \
mididrv.o \
mididrv_ms.o \
midiparser_qt.o \
midiparser_smf.o \
midiparser_xmidi.o \

View file

@ -30,13 +30,10 @@
// The initialization of the static const integral data members is done in the class definition,
// but we still need to provide a definition if they are odr-used.
const uint8 MidiDriver_MT32GM::MAXIMUM_SOURCES;
const uint16 MidiDriver_MT32GM::DEFAULT_SOURCE_NEUTRAL_VOLUME;
const uint8 MidiDriver_MT32GM::MT32_DEFAULT_CHANNEL_VOLUME;
const uint8 MidiDriver_MT32GM::GM_DEFAULT_CHANNEL_VOLUME;
const uint8 MidiDriver_MT32GM::MAXIMUM_MT32_ACTIVE_NOTES;
const uint8 MidiDriver_MT32GM::MAXIMUM_GM_ACTIVE_NOTES;
const uint16 MidiDriver_MT32GM::FADING_DELAY;
// These are the power-on default instruments of the Roland MT-32 family.
const byte MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS[8] = {
@ -78,6 +75,14 @@ const uint8 MidiDriver_MT32GM::GS_DRUMKIT_FALLBACK_MAP[128] = {
0, 0, 0, 0, 0, 0, 0, 127 // No drumkit defined; CM-64/32L (127)
};
// Callback hooked up to the driver wrapped by the MIDI driver
// object. Executes onTimer and the external callback set by
// the setTimerCallback function.
void MidiDriver_MT32GM::timerCallback(void *data) {
MidiDriver_MT32GM *driver = (MidiDriver_MT32GM *)data;
driver->onTimer();
}
MidiDriver_MT32GM::MidiDriver_MT32GM(MusicType midiType) :
_driver(0),
_nativeMT32(false),
@ -85,18 +90,10 @@ MidiDriver_MT32GM::MidiDriver_MT32GM(MusicType midiType) :
_midiDataReversePanning(false),
_midiDeviceReversePanning(false),
_scaleGSPercussionVolumeToMT32(false),
_userVolumeScaling(true),
_userMusicVolume(192),
_userSfxVolume(192),
_userMute(false),
_isOpen(false),
_outputChannelMask(65535), // Channels 1-16
_baseFreq(250),
_timerRate(0),
_fadeDelay(0),
_sysExDelay(0),
_timer_param(0),
_timer_proc(0) {
_sysExDelay(0) {
memset(_controlData, 0, sizeof(_controlData));
switch (midiType) {
@ -113,11 +110,10 @@ MidiDriver_MT32GM::MidiDriver_MT32GM(MusicType midiType) :
}
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
// Default source type: 0 = music, 1+ = SFX
_sources[i].type = i == 0 ? SOURCE_TYPE_MUSIC : SOURCE_TYPE_SFX;
_availableChannels[i] = 0;
// Default MIDI channel mapping: data channel == output channel
for (int j = 0; j < MIDI_CHANNEL_COUNT; ++j) {
_sources[i].channelMap[j] = j;
_channelMap[i][j] = j;
}
}
@ -382,19 +378,13 @@ void MidiDriver_MT32GM::close() {
uint32 MidiDriver_MT32GM::property(int prop, uint32 param) {
switch (prop) {
case PROP_USER_VOLUME_SCALING:
if (param == 0xFFFF)
return _userVolumeScaling ? 1 : 0;
_userVolumeScaling = param > 0;
break;
case PROP_MIDI_DATA_REVERSE_PANNING:
if (param == 0xFFFF)
return _midiDataReversePanning ? 1 : 0;
_midiDataReversePanning = param > 0;
break;
default:
MidiDriver::property(prop, param);
break;
return MidiDriver_Multisource::property(prop, param);
}
return 0;
}
@ -871,100 +861,6 @@ void MidiDriver_MT32GM::stopAllNotes(bool stopSustainedNotes) {
_activeNotesMutex.unlock();
}
void MidiDriver_MT32GM::startFade(uint16 duration, uint16 targetVolume) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
startFade(i, duration, targetVolume);
}
}
void MidiDriver_MT32GM::startFade(uint8 source, uint16 duration, uint16 targetVolume) {
assert(source < MAXIMUM_SOURCES);
_fadingMutex.lock();
_sources[source].fadePassedTime = 0;
_sources[source].fadeStartVolume = _sources[source].volume;
_sources[source].fadeEndVolume = targetVolume;
_sources[source].fadeDuration = duration * 1000;
_fadingMutex.unlock();
}
void MidiDriver_MT32GM::abortFade(FadeAbortType abortType) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
abortFade(i, abortType);
}
}
void MidiDriver_MT32GM::abortFade(uint8 source, FadeAbortType abortType) {
assert(source < MAXIMUM_SOURCES);
if (!isFading(source)) {
return;
}
_fadingMutex.lock();
_sources[source].fadeDuration = 0;
uint16 newSourceVolume;
switch (abortType) {
case FADE_ABORT_TYPE_END_VOLUME:
newSourceVolume = _sources[source].fadeEndVolume;
break;
case FADE_ABORT_TYPE_START_VOLUME:
newSourceVolume = _sources[source].fadeStartVolume;
break;
case FADE_ABORT_TYPE_CURRENT_VOLUME:
default:
_fadingMutex.unlock();
return;
}
setSourceVolume(source, newSourceVolume);
_fadingMutex.unlock();
}
bool MidiDriver_MT32GM::isFading() {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
if (isFading(i))
return true;
}
return false;
}
bool MidiDriver_MT32GM::isFading(uint8 source) {
assert(source < MAXIMUM_SOURCES);
return _sources[source].fadeDuration > 0;
}
void MidiDriver_MT32GM::updateFading() {
Common::StackLock lock(_fadingMutex);
_fadeDelay -= _fadeDelay < _timerRate ? _fadeDelay : _timerRate;
bool updatedVolume = false;
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
if (_sources[i].fadeDuration > 0) {
_sources[i].fadePassedTime += _timerRate;
if (_sources[i].fadePassedTime >= _sources[i].fadeDuration) {
// Fade has finished
setSourceVolume(i, _sources[i].fadeEndVolume);
updatedVolume = true;
_sources[i].fadeDuration = 0;
} else if (_fadeDelay == 0) {
setSourceVolume(i, ((_sources[i].fadePassedTime * (_sources[i].fadeEndVolume - _sources[i].fadeStartVolume)) /
_sources[i].fadeDuration) + _sources[i].fadeStartVolume);
updatedVolume = true;
}
}
}
if (updatedVolume)
_fadeDelay = FADING_DELAY;
}
void MidiDriver_MT32GM::clearSysExQueue() {
Common::StackLock lock(_sysExQueueMutex);
@ -1031,23 +927,23 @@ bool MidiDriver_MT32GM::allocateSourceChannels(uint8 source, uint8 numChannels)
}
// Clear the source channel mapping.
if (i != MIDI_RHYTHM_CHANNEL)
_sources[source].channelMap[i] = -1;
_channelMap[source][i] = -1;
}
_allocationMutex.unlock();
_sources[source].availableChannels = claimedChannels;
_availableChannels[source] = claimedChannels;
return true;
}
int8 MidiDriver_MT32GM::mapSourceChannel(uint8 source, uint8 dataChannel) {
int8 outputChannel = _sources[source].channelMap[dataChannel];
int8 outputChannel = _channelMap[source][dataChannel];
if (outputChannel == -1) {
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if ((_sources[source].availableChannels >> i) & 1) {
_sources[source].availableChannels &= ~(1 << i);
_sources[source].channelMap[dataChannel] = i;
if ((_availableChannels[source] >> i) & 1) {
_availableChannels[source] &= ~(1 << i);
_channelMap[source][dataChannel] = i;
outputChannel = i;
break;
}
@ -1062,7 +958,7 @@ int8 MidiDriver_MT32GM::mapSourceChannel(uint8 source, uint8 dataChannel) {
void MidiDriver_MT32GM::deinitSource(uint8 source) {
assert(source < MAXIMUM_SOURCES);
abortFade(source, FADE_ABORT_TYPE_END_VOLUME);
MidiDriver_Multisource::deinitSource(source);
// Free channels which were used by this source.
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
@ -1072,102 +968,47 @@ void MidiDriver_MT32GM::deinitSource(uint8 source) {
if (_controlData[i]->source == source)
_controlData[i]->source = -1;
}
_sources[source].availableChannels = 0xFFFF;
_availableChannels[source] = 0xFFFF;
// Reset the data to output channel mapping
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
_sources[source].channelMap[i] = i;
_channelMap[source][i] = i;
}
_activeNotesMutex.lock();
// Stop any active notes.
for (int i = 0; i < _maximumActiveNotes; ++i) {
if (_activeNotes[i].source == source) {
if (_activeNotes[i].sustain) {
// Turn off sustain
controlChange(_activeNotes[i].channel, MIDI_CONTROLLER_SUSTAIN, 0x00, source, *_controlData[i]);
} else {
// Send note off
noteOnOff(_activeNotes[i].channel, MIDI_COMMAND_NOTE_OFF, _activeNotes[i].note, 0x00, source, *_controlData[i]);
}
}
}
_activeNotesMutex.unlock();
// TODO Optionally reset some controllers to their
// default values? Pitch wheel, volume, sustain...
}
void MidiDriver_MT32GM::setSourceType(SourceType type) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
setSourceType(i, type);
}
}
void MidiDriver_MT32GM::setSourceType(uint8 source, SourceType type) {
assert(source < MAXIMUM_SOURCES);
_sources[source].type = type;
// Make sure music/sfx volume gets applied
void MidiDriver_MT32GM::applySourceVolume(uint8 source) {
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
if (_controlData[i]->source == source)
controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, source, *_controlData[i]);
if (source == 0xFF || _controlData[i]->source == source)
controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, _controlData[i]->source, *_controlData[i]);
}
}
void MidiDriver_MT32GM::setSourceVolume(uint16 volume) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
setSourceVolume(i, volume);
void MidiDriver_MT32GM::stopAllNotes(uint8 source, uint8 channel) {
_activeNotesMutex.lock();
for (int i = 0; i < _maximumActiveNotes; ++i) {
if ((source == 0xFF || _activeNotes[i].source == source) &&
(channel == 0xFF || _activeNotes[i].channel == channel)) {
if (_activeNotes[i].sustain) {
// Turn off sustain
controlChange(_activeNotes[i].channel, MIDI_CONTROLLER_SUSTAIN, 0x00, _activeNotes[i].source, *_controlData[i]);
} else {
// Send note off
noteOnOff(_activeNotes[i].channel, MIDI_COMMAND_NOTE_OFF, _activeNotes[i].note, 0x00, _activeNotes[i].source, *_controlData[i]);
}
}
}
}
void MidiDriver_MT32GM::setSourceVolume(uint8 source, uint16 volume) {
assert(source < MAXIMUM_SOURCES);
_sources[source].volume = volume;
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
if (_controlData[i]->source == source)
controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, source, *_controlData[i]);
}
}
void MidiDriver_MT32GM::setSourceNeutralVolume(uint16 volume) {
for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
setSourceNeutralVolume(i, volume);
}
}
void MidiDriver_MT32GM::setSourceNeutralVolume(uint8 source, uint16 volume) {
assert(source < MAXIMUM_SOURCES);
_sources[source].neutralVolume = volume;
}
void MidiDriver_MT32GM::syncSoundSettings() {
_userMusicVolume = MIN(256, ConfMan.getInt("music_volume"));
_userSfxVolume = MIN(256, ConfMan.getInt("sfx_volume"));
_userMute = ConfMan.getBool("mute");
// Make sure music/sfx volume gets applied
for (int i = 0; i < MIDI_CHANNEL_COUNT; ++i) {
if (!isOutputChannelUsed(i))
continue;
controlChange(i, MIDI_CONTROLLER_VOLUME, _controlData[i]->volume, _controlData[i]->source, *_controlData[i]);
}
_activeNotesMutex.unlock();
}
void MidiDriver_MT32GM::onTimer() {
updateFading();
MidiDriver_Multisource::onTimer();
_sysExQueueMutex.lock();

View file

@ -24,6 +24,7 @@
#define AUDIO_MT32GM_H
#include "audio/mididrv.h"
#include "audio/mididrv_ms.h"
#include "common/mutex.h"
#include "common/queue.h"
@ -60,18 +61,12 @@
* 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
@ -88,19 +83,18 @@
* 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:
* 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; this is done automatically when an End Of Track MIDI event
* is received, or manually by calling deinitSource.
* 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
@ -114,42 +108,9 @@
* 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 {
class MidiDriver_MT32GM : public MidiDriver_Multisource {
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;
@ -161,21 +122,6 @@ 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
@ -289,6 +235,11 @@ protected:
};
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();
@ -340,45 +291,6 @@ public:
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.
*/
@ -409,49 +321,7 @@ public:
* 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();
void deinitSource(uint8 source) override;
protected:
/**
@ -576,11 +446,6 @@ protected:
*/
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
@ -592,7 +457,14 @@ protected:
*/
virtual int8 mapSourceChannel(uint8 source, uint8 dataChannel);
Common::Mutex _fadingMutex; // For operations on fades
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
@ -616,26 +488,18 @@ protected:
// 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];
// 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.
@ -646,9 +510,6 @@ protected:
// 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;
@ -656,21 +517,6 @@ protected:
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