[Scummvm-git-logs] scummvm master -> f660238ab5e3d0abdbcb2804a346baf22c300504

NMIError 60350957+NMIError at users.noreply.github.com
Fri Jul 2 12:20:07 UTC 2021


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
e2a1362bf9 LURE: Improve AdLib support
d8c3e22a54 AUDIO/MIDI: Add multisource AdLib MIDI driver.
f660238ab5 AUDIO: Move MIDI multisource to base class


Commit: e2a1362bf94437bd4038e510b7abb0b35abfa612
    https://github.com/scummvm/scummvm/commit/e2a1362bf94437bd4038e510b7abb0b35abfa612
Author: Coen Rampen (crampen at gmail.com)
Date: 2021-07-02T14:16:50+02:00

Commit Message:
LURE: Improve AdLib support

This improves the AdLib support for Lure of the Temptress, which was unfinished.

Changed paths:
    engines/lure/animseq.cpp
    engines/lure/game.cpp
    engines/lure/intro.cpp
    engines/lure/scripts.cpp
    engines/lure/sound.cpp
    engines/lure/sound.h
    engines/lure/surface.cpp


diff --git a/engines/lure/animseq.cpp b/engines/lure/animseq.cpp
index 8e6713558f..e03bf71f65 100644
--- a/engines/lure/animseq.cpp
+++ b/engines/lure/animseq.cpp
@@ -217,8 +217,7 @@ AnimAbortType AnimationSequence::show() {
 		if (_pPixels < _pPixelsEnd && (_isEGA || _pLines < _pLinesEnd)) {
 			if ((soundFrame != NULL) && (soundFrame->rolandSoundId != 0xFF) && (frameCtr == 0))
 				Sound.musicInterface_Play(
-					Sound.isRoland() ? soundFrame->rolandSoundId : soundFrame->adlibSoundId,
-					soundFrame->channelNum, soundFrame->music);
+					Sound.isRoland() ? soundFrame->rolandSoundId : soundFrame->adlibSoundId, soundFrame->music);
 
 			if (_isEGA)
 				egaDecodeFrame(_pPixels);
diff --git a/engines/lure/game.cpp b/engines/lure/game.cpp
index 11b6e261a0..e79f973ce4 100644
--- a/engines/lure/game.cpp
+++ b/engines/lure/game.cpp
@@ -401,7 +401,7 @@ void Game::displayChuteAnimation() {
 	Sound.killSounds();
 
 	AnimationSequence *anim = new AnimationSequence(CHUTE_ANIM_ID, palette, true);
-	Sound.musicInterface_Play(0x40, 0, true);
+	Sound.musicInterface_Play(0x40, true);
 	AnimAbortType result = anim->show();
 	delete anim;
 
@@ -446,7 +446,7 @@ void Game::displayBarrelAnimation() {
 	mouse.cursorOff();
 
 	Sound.killSounds();
-	Sound.musicInterface_Play(0x3B, 0, true);
+	Sound.musicInterface_Play(0x3B, true);
 
 	AnimationSequence *anim = new AnimationSequence(BARREL_ANIM_ID, palette, true);
 	anim->show();
diff --git a/engines/lure/intro.cpp b/engines/lure/intro.cpp
index 6a43e96e3f..6eefbeedc8 100644
--- a/engines/lure/intro.cpp
+++ b/engines/lure/intro.cpp
@@ -246,7 +246,7 @@ bool Introduction::playMusic(uint8 soundNumber, bool fadeOut) {
 
 		if (!result) {
 			_currentSound = soundNumber;
-			Sound.musicInterface_Play(_currentSound, 0, true);
+			Sound.musicInterface_Play(_currentSound, true);
 		}
 	}
 
diff --git a/engines/lure/scripts.cpp b/engines/lure/scripts.cpp
index 6e4242619d..4239d041f5 100644
--- a/engines/lure/scripts.cpp
+++ b/engines/lure/scripts.cpp
@@ -212,7 +212,7 @@ void Script::endgameSequence(uint16 v1, uint16 v2, uint16 v3) {
 
 	Sound.killSounds();
 	if (animResult == ABORT_NONE) {
-		Sound.musicInterface_Play(Sound.isRoland() ? 0 : 0x28, 0, false);
+		Sound.musicInterface_Play(Sound.isRoland() ? 0 : 0x28, false);
 		events.interruptableDelay(5500);
 	}
 	delete anim;
@@ -224,7 +224,7 @@ void Script::endgameSequence(uint16 v1, uint16 v2, uint16 v3) {
 
 	if (!Sound.isRoland())
 		Sound.loadSection(ADLIB_ENDGAME_SOUND_RESOURCE_ID);
-	Sound.musicInterface_Play(Sound.isRoland() ? 6 : 0, 0, true);
+	Sound.musicInterface_Play(Sound.isRoland() ? 6 : 0, true);
 
 	anim = new AnimationSequence(ENDGAME_ANIM_ID + 2, p, false);
 	anim->show();
diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp
index f1b0d2f93d..cd2946fbcd 100644
--- a/engines/lure/sound.cpp
+++ b/engines/lure/sound.cpp
@@ -31,6 +31,7 @@
 #include "common/algorithm.h"
 #include "common/config-manager.h"
 #include "common/endian.h"
+#include "audio/adlib_ms.h"
 #include "audio/midiparser.h"
 
 namespace Common {
@@ -44,7 +45,6 @@ namespace Lure {
 SoundManager::SoundManager() {
 	Disk &disk = Disk::getReference();
 
-	int index;
 	_descs = disk.getEntry(SOUND_DESC_RESOURCE_ID);
 	_numDescs = _descs->size() / sizeof(SoundDescResource);
 	_soundData = NULL;
@@ -54,26 +54,19 @@ SoundManager::SoundManager() {
 	_isRoland = MidiDriver::getMusicType(dev) != MT_ADLIB;
 	_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
 
-	Common::fill(_channelsInUse, _channelsInUse + NUM_CHANNELS, false);
 	Common::fill(_sourcesInUse, _sourcesInUse + LURE_MAX_SOURCES, false);
 
 	if (_isRoland) {
 		_driver = _mt32Driver = new MidiDriver_MT32GM(MT_MT32);
 	} else {
-		_driver = MidiDriver::createMidi(dev);
+		_driver = new MidiDriver_ADLIB_Lure();
 	}
+	_driver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+
 	int statusCode = _driver->open();
 	if (statusCode)
 		error("Sound driver returned error code %d", statusCode);
 
-	if (!_isRoland) {
-		for (index = 0; index < NUM_CHANNELS; ++index) {
-			_channelsInner[index].midiChannel = _driver->allocateChannel();
-			/* 90 is power on default for midi compliant devices */
-			_channelsInner[index].volume = 90;
-		}
-	}
-
 	syncSounds();
 }
 
@@ -275,7 +268,6 @@ void SoundManager::killSounds() {
 
 	// Clear the active sounds
 	_activeSounds.clear();
-	Common::fill(_channelsInUse, _channelsInUse + NUM_CHANNELS, false);
 }
 
 void SoundManager::addSound(uint8 soundIndex, bool tidyFlag) {
@@ -295,41 +287,22 @@ void SoundManager::addSound(uint8 soundIndex, bool tidyFlag) {
 	if (_isRoland)
 		numChannels = (rec.numChannels & 3);
 	else
-		numChannels = ((rec.numChannels >> 2) & 3) + 1;
+		numChannels = ((rec.numChannels >> 2) & 3);
 
-	int channelCtr, channelCtr2;
-	for (channelCtr = 0; channelCtr <= (NUM_CHANNELS - numChannels); ++channelCtr) {
-		for (channelCtr2 = 0; channelCtr2 < numChannels; ++channelCtr2)
-			if (_channelsInUse[channelCtr + channelCtr2])
-				break;
-
-		if (channelCtr2 == numChannels)
-			break;
-	}
-
-	if (channelCtr > (NUM_CHANNELS - numChannels)) {
-		// No channels free
-		debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound - no channels free");
+	if (numChannels == 0)
+		// Don't play sounds for which 0 channels are defined.
 		return;
-	}
-
-	// Mark the found channels as in use
-	Common::fill(_channelsInUse+channelCtr, _channelsInUse+channelCtr + numChannels, true);
 
 	SoundDescResource *newEntry = new SoundDescResource();
 	newEntry->soundNumber = rec.soundNumber;
-	newEntry->channel = channelCtr;
+	newEntry->channel = rec.channel;
 	newEntry->numChannels = numChannels;
 	newEntry->flags = rec.flags;
-
-	if (_isRoland)
-		newEntry->volume = rec.volume;
-	else /* resource volumes do not seem to work well with our AdLib emu */
-		newEntry->volume = 240; /* 255 causes clipping with AdLib */
+	newEntry->volume = rec.volume;
 
 	_activeSounds.push_back(SoundList::value_type(newEntry));
 
-	musicInterface_Play(rec.soundNumber, channelCtr, false, numChannels, newEntry->volume);
+	musicInterface_Play(rec.soundNumber, false, numChannels, newEntry->volume);
 }
 
 void SoundManager::addSound2(uint8 soundIndex) {
@@ -368,7 +341,7 @@ void SoundManager::setVolume(uint8 soundNumber, uint8 volume) {
 
 	SoundDescResource *entry = findSound(soundNumber);
 	if (entry)
-		musicInterface_SetVolume(entry->channel, volume);
+		musicInterface_SetVolume(entry->soundNumber, volume);
 }
 
 uint8 SoundManager::descIndexOf(uint8 soundNumber) {
@@ -387,27 +360,7 @@ uint8 SoundManager::descIndexOf(uint8 soundNumber) {
 void SoundManager::syncSounds() {
 	musicInterface_TidySounds();
 
-	bool mute = false;
-	if (ConfMan.hasKey("mute"))
-		mute = ConfMan.getBool("mute");
-	_musicVolume = mute ? 0 : MIN(256, ConfMan.getInt("music_volume"));
-	_sfxVolume = mute ? 0 : MIN(256, ConfMan.getInt("sfx_volume"));
-
-	if (_isRoland) {
-		_mt32Driver->syncSoundSettings();
-	} else {
-		_soundMutex.lock();
-		MusicListIterator i;
-		for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
-			// FIXME This should not override the sound resource volume
-			// on the MidiMusic object.
-			if ((*i)->isMusic())
-				(*i)->setVolume(_musicVolume);
-			else
-				(*i)->setVolume(_sfxVolume);
-		}
-		_soundMutex.unlock();
-	}
+	_driver->syncSoundSettings();
 }
 
 SoundDescResource *SoundManager::findSound(uint8 soundNumber) {
@@ -439,9 +392,6 @@ void SoundManager::tidySounds() {
 			// Still playing, so move to next entry
 			++i;
 		else {
-			// Mark the channels that it used as now being free
-			Common::fill(_channelsInUse + rec.channel, _channelsInUse + rec.channel + rec.numChannels, false);
-
 			i = _activeSounds.erase(i);
 		}
 	}
@@ -471,9 +421,7 @@ void SoundManager::restoreSounds() {
 		SoundDescResource const &rec = **i;
 
 		if ((rec.numChannels != 0) && ((rec.flags & SF_RESTORE) != 0)) {
-			Common::fill(_channelsInUse + rec.channel, _channelsInUse + rec.channel + rec.numChannels, true);
-
-			musicInterface_Play(rec.soundNumber, rec.channel, false, rec.numChannels, rec.volume);
+			musicInterface_Play(rec.soundNumber, false, rec.numChannels, rec.volume);
 		}
 
 		++i;
@@ -489,41 +437,20 @@ bool SoundManager::fadeOut() {
 	// Fade out all the active sounds
 	musicInterface_TidySounds();
 
-	if (_isRoland) {
-		_mt32Driver->startFade(3000, 0);
-		while (_mt32Driver->isFading()) {
-			if (events.interruptableDelay(100)) {
-				result = ((events.type() == Common::EVENT_KEYDOWN && events.event().kbd.keycode == 27) ||
-					LureEngine::getReference().shouldQuit());
-				_mt32Driver->abortFade();
-				break;
-			}
-		}
-	} else {
-		bool inProgress = true;
-		while (inProgress) {
-			inProgress = false;
-
-			_soundMutex.lock();
-			MusicListIterator i;
-			for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
-				MidiMusic &music = **i;
-				if (music.getVolume() > 0) {
-					inProgress = true;
-					music.setVolume(music.getVolume() >= 10 ? music.getVolume() - 10 : 0);
-				}
-			}
-
-			_soundMutex.unlock();
-			g_system->delayMillis(10);
+	_driver->startFade(3000, 0);
+	while (_driver->isFading()) {
+		if (events.interruptableDelay(100)) {
+			result = ((events.type() == Common::EVENT_KEYDOWN && events.event().kbd.keycode == 27) ||
+				LureEngine::getReference().shouldQuit());
+			_driver->abortFade();
+			break;
 		}
 	}
 
 	// Kill all the sounds
 	musicInterface_KillAll();
 
-	if (_isRoland)
-		_mt32Driver->setSourceVolume(MidiDriver_MT32GM::DEFAULT_SOURCE_NEUTRAL_VOLUME);
+	_driver->setSourceVolume(MidiDriver_Multisource::DEFAULT_SOURCE_NEUTRAL_VOLUME);
 
 	return result;
 }
@@ -562,9 +489,8 @@ void SoundManager::resume() {
 // musicInterface_Play
 // Play the specified sound
 
-void SoundManager::musicInterface_Play(uint8 soundNumber, uint8 channelNumber, bool isMusic, uint8 numChannels, uint8 volume) {
-	debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_Play soundNumber=%d, channel=%d",
-		soundNumber, channelNumber);
+void SoundManager::musicInterface_Play(uint8 soundNumber, bool isMusic, uint8 numChannels, uint8 volume) {
+	debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_Play soundNumber=%d", soundNumber);
 	Game &game = Game::getReference();
 
 	if (!_soundData)
@@ -607,24 +533,22 @@ void SoundManager::musicInterface_Play(uint8 soundNumber, uint8 channelNumber, b
 	// specifying volume.
 	_soundMutex.lock();
 	int8 source = -1;
-	if (_isRoland) {
-		if (isMusic) {
-			source = 0;
-		} else {
-			for (int i = 1; i < LURE_MAX_SOURCES; ++i) {
-				if (!_sourcesInUse[i]) {
-					source = i;
-					break;
-				}
+	if (isMusic) {
+		source = 0;
+	} else {
+		for (int i = 1; i < LURE_MAX_SOURCES; ++i) {
+			if (!_sourcesInUse[i]) {
+				source = i;
+				break;
 			}
 		}
-		if (source == -1)
-			warning("Insufficient sources to play sound %i", soundNumber);
-		else
-			_sourcesInUse[source] = true;
 	}
-	MidiMusic *sound = new MidiMusic(_driver, _channelsInner, channelNumber, soundNum,
-		isMusic, loop, source, numChannels, soundStart, dataSize, volume);
+	if (source == -1)
+		warning("Insufficient sources to play sound %i", soundNumber);
+	else
+		_sourcesInUse[source] = true;
+	MidiMusic *sound = new MidiMusic(_driver, soundNum, isMusic,
+		loop, source, numChannels, soundStart, dataSize, volume);
 	_playingSounds.push_back(MusicList::value_type(sound));
 	_soundMutex.unlock();
 }
@@ -675,16 +599,16 @@ bool SoundManager::musicInterface_CheckPlaying(uint8 soundNumber) {
 // musicInterface_SetVolume
 // Sets the volume of the specified channel
 
-void SoundManager::musicInterface_SetVolume(uint8 channelNum, uint8 volume) {
-	debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_SetVolume channel=%d, volume=%d",
-		channelNum, volume);
+void SoundManager::musicInterface_SetVolume(uint8 soundNumber, uint8 volume) {
+	debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_SetVolume soundNumber=%d, volume=%d",
+		   soundNumber, volume);
 	musicInterface_TidySounds();
 
 	_soundMutex.lock();
 	MusicListIterator i;
 	for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
 		MidiMusic &music = **i;
-		if (music.channelNumber() == channelNum)
+		if (music.soundNumber() == soundNumber)
 			music.setVolume(volume);
 	}
 	_soundMutex.unlock();
@@ -761,7 +685,6 @@ void SoundManager::musicInterface_TrashReverb() {
 	debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_TrashReverb");
 
 	/*
-	// TODO Should this do anything on AdLib? It does not have reverb AFAIK
 	if (_isRoland) {
 		// Set reverb parameters to mode Room, time 1, level 0
 		static const byte sysExData[] = { 0x00, 0x00, 0x00 };
@@ -813,25 +736,19 @@ void SoundManager::doTimer() {
 
 /*------------------------------------------------------------------------*/
 
-MidiMusic::MidiMusic(MidiDriver *driver, ChannelEntry channels[NUM_CHANNELS],
-					 uint8 channelNum, uint8 soundNum, bool isMus, bool loop, int8 source, uint8 numChannels, void *soundData, uint32 size, uint8 volume) {
+MidiMusic::MidiMusic(MidiDriver_Multisource *driver, uint8 soundNum, bool isMus, bool loop,
+		int8 source, uint8 numChannels, void *soundData, uint32 size, uint8 volume) {
 	_driver = driver;
 	assert(_driver);
 	_mt32Driver = dynamic_cast<MidiDriver_MT32GM *>(_driver);
 	assert(!Sound.isRoland() || _mt32Driver);
 	_source = source;
-	_channels = channels;
 	_soundNumber = soundNum;
-	_channelNumber = channelNum;
 	_isMusic = isMus;
 	_loop = loop;
 
 	_numChannels = numChannels;
-	_volume = 0;
-
-	// Set sound resource volume (default is 80h - neutral).
-	// TODO AdLib currently does not use sound resource volume, so use fixed 240.
-	setVolume(Sound.isRoland() ? volume : 240);
+	_volume = volume;
 
 	_parser = MidiParser::createParser_SMF(source);
 	_parser->setMidiDriver(this);
@@ -871,8 +788,8 @@ MidiMusic::MidiMusic(MidiDriver *driver, ChannelEntry channels[NUM_CHANNELS],
 
 MidiMusic::~MidiMusic() {
 	_parser->unloadMusic();
-	if (Sound.isRoland() && _isPlaying)
-		_mt32Driver->deinitSource(_source);
+	if (_isPlaying)
+		_driver->deinitSource(_source);
 	delete _parser;
 	delete _decompressedSound;
 }
@@ -880,23 +797,7 @@ MidiMusic::~MidiMusic() {
 void MidiMusic::setVolume(int volume) {
 	volume = CLIP(volume, 0, 255);
 
-	if (_volume == volume)
-		return;
-
 	_volume = volume;
-
-	// MT-32 MIDI data sets channel volume using control change,
-	// so this is only needed for AdLib.
-	if (!Sound.isRoland()) {
-		volume *= _isMusic ? Sound.musicVolume() : Sound.sfxVolume();
-
-		for (int i = 0; i < _numChannels; ++i) {
-			if (_channels[_channelNumber + i].midiChannel != NULL)
-				_channels[_channelNumber + i].midiChannel->volume(
-					_channels[_channelNumber + i].volume *
-					volume / 65025);
-		}
-	}
 }
 
 void MidiMusic::playMusic() {
@@ -913,45 +814,12 @@ void MidiMusic::send(uint32 b) {
 }
 
 void MidiMusic::send(int8 source, uint32 b) {
-	byte channel;
-	if (Sound.isRoland()) {
-		// Channel mapping is handled by the driver
-		channel = b & 0x0F;
-	} else {
-		// Remap data channel to (one of) the channel(s) assigned to this player
-#ifdef SOUND_CROP_CHANNELS
-		if ((b & 0xF) >= _numChannels) return;
-		channel = _channelNumber + (byte)(b & 0x0F);
-#else
-		channel = _channelNumber + ((byte)(b & 0x0F) % _numChannels);
-#endif
-
-		if ((channel >= NUM_CHANNELS) || (_channels[channel].midiChannel == NULL))
-			return;
-	}
-
-	if ((b & 0xFFF0) == 0x07B0) {
-		// Adjust volume changes by song and master volume
-		byte volume = (byte)((b >> 16) & 0x7F);
-		_channels[channel].volume = volume;
-		if (!Sound.isRoland()) {
-			// Scale volume for AdLib only.
-			// MT-32 sound resource volume is applied to note velocity,
-			// and user volume scaling is handled by the driver.
-			// TODO AdLib might use velocity for sound resource volume
-			// as well.
-			uint16 master_volume = _isMusic ? Sound.musicVolume() : Sound.sfxVolume();
-			volume = volume * _volume * master_volume / 65025;
-		}
-		b = (b & 0xFF00FFFF) | (volume << 16);
-	} else if ((b & 0xFFF0) == 0x18B0) {
+	if ((b & 0xFFF0) == 0x18B0) {
 		if (Sound.isRoland())
 			// Some tracks use CC 18. This is undefined in the MIDI standard
 			// and does nothing on an MT-32. Not sending this to the device
 			// in case it is a GM device with non-standard behavior for this CC.
 			return;
-	} else if ((b & 0xFFF0) == 0x007BB0) {
-		// No implementation
 	} else if (((b & 0xF0) == 0x90)) {
 		// Note On
 		if (Sound.isRoland()) {
@@ -977,11 +845,7 @@ void MidiMusic::send(int8 source, uint32 b) {
 		}
 	}
 
-	if (Sound.isRoland()) {
-		_driver->send(source, b);
-	} else {
-		_channels[channel].midiChannel->send(b);
-	}
+	_driver->send(source, b);
 }
 
 void MidiMusic::metaEvent(byte type, byte *data, uint16 length) {
@@ -989,12 +853,10 @@ void MidiMusic::metaEvent(byte type, byte *data, uint16 length) {
 }
 
 void MidiMusic::metaEvent(int8 source, byte type, byte *data, uint16 length) {
-	//Only thing we care about is End of Track.
-	if (type != 0x2F)
-		return;
+	if (type == MIDI_META_END_OF_TRACK)
+		stopMusic();
 
 	_driver->metaEvent(source, type, data, length);
-	stopMusic();
 }
 
 void MidiMusic::onTimer() {
@@ -1006,8 +868,7 @@ void MidiMusic::stopMusic() {
 	debugC(ERROR_DETAILED, kLureDebugSounds, "MidiMusic::stopMusic sound %d", _soundNumber);
 	_isPlaying = false;
 	_parser->unloadMusic();
-	if (Sound.isRoland())
-		_mt32Driver->deinitSource(_source);
+	_driver->deinitSource(_source);
 }
 
 void MidiMusic::pauseMusic() {
@@ -1018,4 +879,178 @@ void MidiMusic::resumeMusic() {
 	_parser->resumePlaying();
 }
 
+// Note that the values higher than 0xF000 are one octave higher than the other
+// values. Other than that only the lower 10 bits are significant.
+const uint16 MidiDriver_ADLIB_Lure::OPL_FREQUENCY_LOOKUP[192] = {
+	0x02B2, 0x02B4, 0x02B7, 0x02B9, 0x02BC, 0x02BE, 0x02C1, 0x02C3, 0x02C6, 0x02C9, 0x02CB, 0x02CE, 0x02D0, 0x02D3, 0x02D6, 0x02D8,
+	0x02DB, 0x02DD, 0x02E0, 0x02E3, 0x02E5, 0x02E8, 0x02EB, 0x02ED, 0x02F0, 0x02F3, 0x02F6, 0x02F8, 0x02FB, 0x02FE, 0x0301, 0x0303,
+	0x0306, 0x0309, 0x030C, 0x030F, 0x0311, 0x0314, 0x0317, 0x031A, 0x031D, 0x0320, 0x0323, 0x0326, 0x0329, 0x032B, 0x032E, 0x0331,
+	0x0334, 0x0337, 0x033A, 0x033D, 0x0340, 0x0343, 0x0346, 0x0349, 0x034C, 0x034F, 0x0352, 0x0356, 0x0359, 0x035C, 0x035F, 0x0362,
+	0x0365, 0x0368, 0x036B, 0x036F, 0x0372, 0x0375, 0x0378, 0x037B, 0x037F, 0x0382, 0x0385, 0x0388, 0x038C, 0x038F, 0x0392, 0x0395,
+	0x0399, 0x039C, 0x039F, 0x03A3, 0x03A6, 0x03A9, 0x03AD, 0x03B0, 0x03B4, 0x03B7, 0x03BB, 0x03BE, 0x03C1, 0x03C5, 0x03C8, 0x03CC,
+	0x03CF, 0x03D3, 0x03D7, 0x03DA, 0x03DE, 0x03E1, 0x03E5, 0x03E8, 0x03EC, 0x03F0, 0x03F3, 0x03F7, 0x03FB, 0x03FE, 0xFE01, 0xFE03,
+	0xFE05, 0xFE07, 0xFE08, 0xFE0A, 0xFE0C, 0xFE0E, 0xFE10, 0xFE12, 0xFE14, 0xFE16, 0xFE18, 0xFE1A, 0xFE1C, 0xFE1E, 0xFE20, 0xFE21,
+	0xFE23, 0xFE25, 0xFE27, 0xFE29, 0xFE2B, 0xFE2D, 0xFE2F, 0xFE31, 0xFE34, 0xFE36, 0xFE38, 0xFE3A, 0xFE3C, 0xFE3E, 0xFE40, 0xFE42,
+	0xFE44, 0xFE46, 0xFE48, 0xFE4A, 0xFE4C, 0xFE4F, 0xFE51, 0xFE53, 0xFE55, 0xFE57, 0xFE59, 0xFE5C, 0xFE5E, 0xFE60, 0xFE62, 0xFE64,
+	0xFE67, 0xFE69, 0xFE6B, 0xFE6D, 0xFE6F, 0xFE72, 0xFE74, 0xFE76, 0xFE79, 0xFE7B, 0xFE7D, 0xFE7F, 0xFE82, 0xFE84, 0xFE86, 0xFE89,
+	0xFE8B, 0xFE8D, 0xFE90, 0xFE92, 0xFE95, 0xFE97, 0xFE99, 0xFE9C, 0xFE9E, 0xFEA1, 0xFEA3, 0xFEA5, 0xFEA8, 0xFEAA, 0xFEAD, 0xFEAF
+};
+
+MidiDriver_ADLIB_Lure::MidiDriver_ADLIB_Lure() :
+		MidiDriver_ADLIB_Multisource(OPL::Config::kOpl2),
+		_pitchBendSensitivity(1) {
+	for (int i = 0; i < LURE_MAX_SOURCES; i++) {
+		for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) {
+			_instrumentDefs[i][j] = { 0 };
+		}
+	}
+
+	// The MIDI data uses monophonic channels and the original interpreter
+	// allocates a fixed OPL channel to each MIDI channel of each source. This
+	// behavior is similar to the static allocation mode, though the actual
+	// channel allocations are a bit different.
+	_allocationMode = ALLOCATION_MODE_STATIC;
+
+	// These global settings are different from the base class defaults.
+	_modulationDepth = MODULATION_DEPTH_LOW;
+	_vibratoDepth = VIBRATO_DEPTH_LOW;
+}
+
+void MidiDriver_ADLIB_Lure::channelAftertouch(uint8 channel, uint8 pressure, uint8 source) {
+	_activeNotesMutex.lock();
+
+	// Find the active note on the specified channel.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive && _activeNotes[i].source == source &&
+				_activeNotes[i].channel == channel) {
+			// Set the velocity of the note and recalculate and write the
+			// volume.
+			_activeNotes[i].velocity = pressure;
+
+			recalculateVolumes(channel, source);
+
+			break;
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Lure::metaEvent(int8 source, byte type, byte *data, uint16 length) {
+	if (type == MIDI_META_SEQUENCER && length >= 6 &&
+			data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x3F && data[3] == 0x00) {
+		// Custom sequencer meta event
+		switch (data[4]) {
+		case 0x01:
+			// Instrument definition
+			uint8 channel;
+			channel = data[5];
+			assert(length == 0x22);
+			assert(source >= 0);
+			assert(channel < MIDI_CHANNEL_COUNT);
+
+			// Instrument definitions use the AdLib BNK format, but omit the
+			// first 2 fields.
+			AdLibBnkInstrumentDefinition bnkInstDef;
+			bnkInstDef = { 0 };
+			memcpy((uint8*)&bnkInstDef + 2, &data[6], sizeof(AdLibBnkInstrumentDefinition) - 2);
+			// Store the definition in the _instrumentDefs array.
+			bnkInstDef.toOplInstrumentDefinition(_instrumentDefs[source][channel]);
+			break;
+		case 0x02:
+			// Rhythm mode
+			
+			// data[5] == 0: off, >= 1: on.
+			// This is never turned on in the game's music data, so this is not
+			// implemented.
+			break;
+		case 0x03:
+			// Pitch bend sensitivity
+
+			_pitchBendSensitivity = data[5];
+			break;
+		default:
+			// Unknown sequencer meta event.
+			warning("MidiDriver_ADLIB_Lure::metaEvent - Unknown sequencer meta event type %X", data[4]);
+			break;
+		}
+		return;
+	}
+
+	// Use default handling for other meta events.
+	MidiDriver_ADLIB_Multisource::metaEvent(source, type, data, length);
+}
+
+MidiDriver_ADLIB_Lure::InstrumentInfo MidiDriver_ADLIB_Lure::determineInstrument(uint8 channel, uint8 source, uint8 note) {
+	InstrumentInfo instrument = { 0 };
+
+	// Lure does not use a rhythm channel.
+	instrument.oplNote = note;
+	// Get the instrument definition set by the last meta event.
+	instrument.instrumentDef = &_instrumentDefs[source][channel];
+	// Identify the instrument by source and channel.
+	instrument.instrumentId = (source << 4) | channel;
+
+	return instrument;
+}
+
+uint16 MidiDriver_ADLIB_Lure::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
+	// Lower the note by an octave. Notes in the lowest octave get clipped to 0.
+	note -= (note >= 0xC ? 0xC : note);
+
+	// The pitch bend is a number of semitones (in bits 8+) and an 8 bit
+	// fraction of a semitone (only the most significant 4 bits are used).
+	int32 pitchBend = calculatePitchBend(channel, source, 0);
+
+	// Discard the lower 4 bits of the pitch bend (the +8 is for rounding), 
+	// add the MIDI note and clip the result to the range 0-5FF. Note that
+	// MIDI notes 60-7F get clipped to 5F.
+	uint16 noteValue = CLIP((note << 4) + ((pitchBend + 8) >> 4), 0, 0x5FF);
+	// Convert the note value to octave note and octave (block).
+	uint8 octaveNote = (noteValue >> 4) % 12;
+	uint8 block = (noteValue >> 4) / 12;
+
+	// Add the note fraction to the octave note and look up the OPL frequency
+	// (F-num) value to use.
+	uint8 octaveNoteValue = (octaveNote << 4) | (noteValue & 0xF);
+	uint16 oplFrequency = OPL_FREQUENCY_LOOKUP[octaveNoteValue];
+	if (oplFrequency < 0xF000) {
+		// Lookup values which have 0 in the highest 6 bits need to be lowered
+		// one octave.
+		if (block > 0) {
+			block--;
+		} else {
+			// If the octave is already 0, bitshift the frequency to halve it.
+			oplFrequency >>= 1;
+		}
+	}
+
+	// Return the F-num and block in OPL register format.
+	return (oplFrequency & 0x3FF) | (block << 10);
+}
+
+int32 MidiDriver_ADLIB_Lure::calculatePitchBend(uint8 channel, uint8 source, uint16 oplFrequency) {
+	// Convert MIDI pitch bend value to a 14 bit signed value
+	// (range -0x2000 - 0x2000).
+	int16 pitchBend = _controlData[source][channel].pitchBend - 0x2000;
+	// Discard the lower 5 bits to turn it into a 9 bit value (-0x100 - 0x100).
+	pitchBend >>= 5;
+	// Double it for every sensitivity semitone over 1. Note that sensitivity
+	// is typically specified as 1, which will not change the value.
+	pitchBend <<= _pitchBendSensitivity - 1;
+
+	return pitchBend;
+}
+
+uint8 MidiDriver_ADLIB_Lure::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
+	uint8 operatorVolume = instrumentDef.getOperatorDefinition(operatorNum).level & OPL_MASK_LEVEL;
+
+	// Scale the instrument definition operator volume by velocity.
+	// Invert it, multiply by velocity, add 0x40 for rounding and divide by 7F.
+	uint8 invertedVolume = (((0x3F - operatorVolume) * velocity) + 0x40) >> 7;
+
+	// Invert the volume again before returning it.
+	return 0x3F - invertedVolume;
+}
+
 } // End of namespace Lure
diff --git a/engines/lure/sound.h b/engines/lure/sound.h
index 20bba1c4da..e21e92fb06 100644
--- a/engines/lure/sound.h
+++ b/engines/lure/sound.h
@@ -31,6 +31,7 @@
 #include "common/singleton.h"
 #include "common/ptr.h"
 
+#include "audio/adlib_ms.h"
 #include "audio/mididrv.h"
 #include "audio/mt32gm.h"
 
@@ -42,25 +43,18 @@ namespace Lure {
 #define NUM_CHANNELS 16
 #define LURE_MAX_SOURCES 10
 
-struct ChannelEntry {
-	MidiChannel *midiChannel;
-	uint8 volume;
-};
-
 class MidiMusic: public MidiDriver_BASE {
 private:
 	uint8 _soundNumber;
-	uint8 _channelNumber;
 	uint8 _numChannels;
 	byte _volume;
 	MemoryBlock *_decompressedSound;
 	uint8 *_soundData;
 	uint8 _soundSize;
-	MidiDriver *_driver;
+	MidiDriver_Multisource *_driver;
 	MidiDriver_MT32GM *_mt32Driver;
 	int8 _source;
 	MidiParser *_parser;
-	ChannelEntry *_channels;
 	bool _isMusic;
 	bool _loop;
 	bool _isPlaying;
@@ -71,8 +65,8 @@ private:
 	uint32 songLength(uint16 songNum) const;
 
 public:
-	MidiMusic(MidiDriver *driver, ChannelEntry channels[NUM_CHANNELS],
-		 uint8 channelNum, uint8 soundNum, bool isMus, bool loop, int8 source, uint8 numChannels, void *soundData, uint32 size, uint8 volume);
+	MidiMusic(MidiDriver_Multisource *driver, uint8 soundNum, bool isMus, bool loop,
+		int8 source, uint8 numChannels, void *soundData, uint32 size, uint8 volume);
 	~MidiMusic() override;
 	void setVolume(int volume);
 	int getVolume() const { return _volume; }
@@ -95,7 +89,6 @@ public:
 
 	void onTimer();
 
-	uint8 channelNumber() const { return _channelNumber; }
 	uint8 soundNumber() const { return _soundNumber; }
 	int8 source() const { return _source; }
 	bool isPlaying() const { return _isPlaying; }
@@ -110,7 +103,7 @@ private:
 	uint8 _soundsTotal;
 	int _numDescs;
 	SoundDescResource *soundDescs() { return (SoundDescResource *) _descs->data(); }
-	MidiDriver *_driver;
+	MidiDriver_Multisource *_driver;
 	MidiDriver_MT32GM *_mt32Driver;
 	typedef Common::List<Common::SharedPtr<SoundDescResource> > SoundList;
 	typedef SoundList::iterator SoundListIterator;
@@ -118,17 +111,12 @@ private:
 	typedef Common::List<Common::SharedPtr<MidiMusic> > MusicList;
 	typedef MusicList::iterator MusicListIterator;
 	MusicList _playingSounds;
-	ChannelEntry _channelsInner[NUM_CHANNELS];
-	bool _channelsInUse[NUM_CHANNELS];
 	bool _sourcesInUse[LURE_MAX_SOURCES];
 	bool _nativeMT32;
 	bool _isRoland;
 	Common::Mutex _soundMutex;
 	bool _paused;
 
-	uint16 _musicVolume;
-	uint16 _sfxVolume;
-
 	// Internal support methods
 	void bellsBodge();
 	void musicInterface_TidySounds();
@@ -162,20 +150,50 @@ public:
 	bool getPaused() const { return _paused; }
 	bool hasNativeMT32() const { return _nativeMT32; }
 	bool isRoland() const { return _isRoland; }
-	uint16 musicVolume() const { return _musicVolume; }
-	uint16 sfxVolume() const { return _sfxVolume; }
 
 	// The following methods implement the external sound player module
 	//void musicInterface_Initialize();
-	void musicInterface_Play(uint8 soundNumber, uint8 channelNumber, bool isMusic = false, uint8 numChannels = 4, uint8 volume = 0x80);
+	void musicInterface_Play(uint8 soundNumber, bool isMusic = false, uint8 numChannels = 4, uint8 volume = 0x80);
 	void musicInterface_Stop(uint8 soundNumber);
 	bool musicInterface_CheckPlaying(uint8 soundNumber);
-	void musicInterface_SetVolume(uint8 channelNum, uint8 volume);
+	void musicInterface_SetVolume(uint8 soundNumber, uint8 volume);
 	void musicInterface_KillAll();
 	void musicInterface_ContinuePlaying();
 	void musicInterface_TrashReverb();
 };
 
+// AdLib MidiDriver subclass implementing the behavior specific to Lure of the
+// Temptress.
+class MidiDriver_ADLIB_Lure : public MidiDriver_ADLIB_Multisource {
+protected:
+	// Lookup array for OPL frequency (F-num) values.
+	static const uint16 OPL_FREQUENCY_LOOKUP[];
+
+public:
+	MidiDriver_ADLIB_Lure();
+
+	// Channel aftertouch is used to adjust a note's volume. This is done by
+	// overriding the velocity of the active note and recalculating the volume.
+	void channelAftertouch(uint8 channel, uint8 pressure, uint8 source) override;
+	// The MIDI data uses three custom sequencer meta events; the most important
+	// one sets the instrument definition for a channel.
+	void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
+
+protected:
+	InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note)	override;
+	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
+	// Returns the number of semitones in bits 8+ and an 8 bit fraction of a
+	// semitone.
+	int32 calculatePitchBend(uint8 channel, uint8 source, uint16 oplFrequency) override;
+	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
+
+	// Stores the instrument definitions set by sequencer meta events.
+	OplInstrumentDefinition _instrumentDefs[LURE_MAX_SOURCES][MIDI_CHANNEL_COUNT];
+	// Pitch bend sensitivity in semi-tones. This is a global setting;
+	// it cannot be specified for a specific MIDI channel.
+	uint8 _pitchBendSensitivity;
+};
+
 } // End of namespace Lure
 
 #define Sound (::Lure::SoundManager::instance())
diff --git a/engines/lure/surface.cpp b/engines/lure/surface.cpp
index 2c8ae4d22d..ee54db41a1 100644
--- a/engines/lure/surface.cpp
+++ b/engines/lure/surface.cpp
@@ -1136,7 +1136,7 @@ bool RestartRestoreDialog::show() {
 	LureEngine &engine = LureEngine::getReference();
 
 	Sound.killSounds();
-	Sound.musicInterface_Play(188, 0, true);
+	Sound.musicInterface_Play(188, true);
 	mouse.setCursorNum(CURSOR_ARROW);
 
 	// See if there are any savegames that can be restored


Commit: d8c3e22a541550a98d8994ba84c56547957b435d
    https://github.com/scummvm/scummvm/commit/d8c3e22a541550a98d8994ba84c56547957b435d
Author: Coen Rampen (crampen at gmail.com)
Date: 2021-07-02T14:16:50+02:00

Commit Message:
AUDIO/MIDI: Add multisource AdLib MIDI driver.

This adds a MIDI driver for AdLib / OPL2 and OPL3 with support for multiple
simultaneous sources of MIDI data. Additionally, it is easier to subclass the
driver and customize its behavior than the existing AdLib MIDI driver. The
standard behavior matches the Windows 95 SB16 driver.

Changed paths:
  A audio/adlib_ms.cpp
  A audio/adlib_ms.h
    audio/mididrv.h
    audio/module.mk


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
new file mode 100644
index 0000000000..8731164c8d
--- /dev/null
+++ b/audio/adlib_ms.cpp
@@ -0,0 +1,1540 @@
+/* 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 "audio/adlib_ms.h"
+
+#include "common/debug.h"
+
+bool OplInstrumentDefinition::isEmpty() {
+	// An instrument with 0 attack, decay, sustain and release produces no
+	// sound. Check this as a shorthand for checking every field for 0.
+	return operator0.decayAttack == 0 && operator0.releaseSustain == 0 &&
+		   operator1.decayAttack == 0 && operator1.releaseSustain == 0 &&
+		   (!fourOperator || (operator2.decayAttack == 0 && operator2.releaseSustain == 0 &&
+							  operator3.decayAttack == 0 && operator3.releaseSustain == 0));
+}
+
+uint8 OplInstrumentDefinition::getNumberOfOperators() {
+	return fourOperator ? 4 : 2;
+}
+
+OplInstrumentOperatorDefinition &OplInstrumentDefinition::getOperatorDefinition(uint8 operatorNum) {
+	assert((!fourOperator && operatorNum < 2) || operatorNum < 4);
+
+	switch (operatorNum) {
+	case 0:
+		return operator0;
+	case 1:
+		return operator1;
+	case 2:
+		return operator2;
+	case 3:
+		return operator3;
+	default:
+		// Should not happen.
+		return operator0;
+	}
+}
+
+void AdLibBnkInstrumentOperatorDefinition::toOplInstrumentOperatorDefinition(OplInstrumentOperatorDefinition &operatorDef, uint8 waveformSelect) {
+	// Combine the separate fields of the BNK format into complete register values.
+	operatorDef.freqMultMisc = frequencyMultiplier | (keyScalingRate == 0 ? 0 : 0x10) |
+		(envelopeGainType == 0 ? 0 : 0x20) | (vibrato == 0 ? 0 : 0x40) | (amplitudeModulation == 0 ? 0 : 0x80);
+	operatorDef.level = level | (keyScalingLevel << 6);
+	operatorDef.decayAttack = decay | (attack << 4);
+	operatorDef.releaseSustain = release | (sustain << 4);
+	operatorDef.waveformSelect = waveformSelect;
+}
+
+void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition& instrumentDef) {
+	instrumentDef.fourOperator = false;
+
+	operator0.toOplInstrumentOperatorDefinition(instrumentDef.operator0, waveformSelect0);
+	operator1.toOplInstrumentOperatorDefinition(instrumentDef.operator1, waveformSelect1);
+
+	instrumentDef.connectionFeedback0 = (operator0.connection == 0 ? 1 : 0) | (operator0.feedback << 1);
+	// BNK does not support 4 operator.
+	instrumentDef.connectionFeedback1 = 0;
+	
+	// TODO Figure out if this is the same as rhythmVoiceNumber
+	instrumentDef.rhythmNote = 0;
+}
+
+// These are the melodic instrument definitions used by the Win95 SB16 driver.
+OplInstrumentDefinition MidiDriver_ADLIB_Multisource::OPL_INSTRUMENT_BANK[128] = {
+// 00
+	{ false, { 0x01, 0x8F, 0xF2, 0xF4, 0x00 }, { 0x01, 0x06, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x01, 0x4B, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x01, 0x49, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x81, 0x12, 0xF2, 0xF7, 0x00 }, { 0x41, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x01, 0x57, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x01, 0x93, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x01, 0x80, 0xA1, 0xF2, 0x00 }, { 0x16, 0x0E, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x01, 0x92, 0xC2, 0xF8, 0x00 }, { 0x01, 0x00, 0xC2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+// 08
+	{ false, { 0x0C, 0x5C, 0xF6, 0xF4, 0x00 }, { 0x81, 0x00, 0xF3, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x07, 0x97, 0xF3, 0xF2, 0x00 }, { 0x11, 0x80, 0xF2, 0xF1, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x17, 0x21, 0x54, 0xF4, 0x00 }, { 0x01, 0x00, 0xF4, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x98, 0x62, 0xF3, 0xF6, 0x00 }, { 0x81, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x18, 0x23, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xE7, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x15, 0x91, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xF6, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+	{ false, { 0x45, 0x59, 0xD3, 0xF3, 0x00 }, { 0x81, 0x80, 0xA3, 0xF3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x03, 0x49, 0x75, 0xF5, 0x01 }, { 0x81, 0x80, 0xB5, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+// 10
+	{ false, { 0x71, 0x92, 0xF6, 0x14, 0x00 }, { 0x31, 0x00, 0xF1, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x72, 0x14, 0xC7, 0x58, 0x00 }, { 0x30, 0x00, 0xC7, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x70, 0x44, 0xAA, 0x18, 0x00 }, { 0xB1, 0x00, 0x8A, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+	{ false, { 0x23, 0x93, 0x97, 0x23, 0x01 }, { 0xB1, 0x00, 0x55, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+	{ false, { 0x61, 0x13, 0x97, 0x04, 0x01 }, { 0xB1, 0x80, 0x55, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x24, 0x48, 0x98, 0x2A, 0x01 }, { 0xB1, 0x00, 0x46, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x61, 0x13, 0x91, 0x06, 0x01 }, { 0x21, 0x00, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x21, 0x13, 0x71, 0x06, 0x00 }, { 0xA1, 0x89, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+// 18
+	{ false, { 0x02, 0x9C, 0xF3, 0x94, 0x01 }, { 0x41, 0x80, 0xF3, 0xC8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x03, 0x54, 0xF3, 0x9A, 0x01 }, { 0x11, 0x00, 0xF1, 0xE7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x23, 0x5F, 0xF1, 0x3A, 0x00 }, { 0x21, 0x00, 0xF2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x03, 0x87, 0xF6, 0x22, 0x01 }, { 0x21, 0x80, 0xF3, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x03, 0x47, 0xF9, 0x54, 0x00 }, { 0x21, 0x00, 0xF6, 0x3A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x23, 0x4A, 0x91, 0x41, 0x01 }, { 0x21, 0x05, 0x84, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x23, 0x4A, 0x95, 0x19, 0x01 }, { 0x21, 0x00, 0x94, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x09, 0xA1, 0x20, 0x4F, 0x00 }, { 0x84, 0x80, 0xD1, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+// 20
+	{ false, { 0x21, 0x1E, 0x94, 0x06, 0x00 }, { 0xA2, 0x00, 0xC3, 0xA6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x31, 0x8D, 0xF1, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x31, 0x5B, 0x51, 0x28, 0x00 }, { 0x32, 0x00, 0x71, 0x48, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x01, 0x8B, 0xA1, 0x9A, 0x00 }, { 0x21, 0x40, 0xF2, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x21, 0x8B, 0xA2, 0x16, 0x00 }, { 0x21, 0x08, 0xA1, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x31, 0x8B, 0xF4, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+// 28
+	{ false, { 0x31, 0x15, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x56, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x31, 0x16, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x66, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x71, 0x49, 0xD1, 0x1C, 0x01 }, { 0x31, 0x00, 0x61, 0x0C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x21, 0x4D, 0x71, 0x12, 0x01 }, { 0x23, 0x80, 0x72, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0xF1, 0x40, 0xF1, 0x21, 0x01 }, { 0xE1, 0x00, 0x6F, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x02, 0x1A, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0x85, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x02, 0x1D, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0xF3, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x10, 0x41, 0xF5, 0x05, 0x01 }, { 0x11, 0x00, 0xF2, 0xC3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+// 30
+	{ false, { 0x21, 0x9B, 0xB1, 0x25, 0x01 }, { 0xA2, 0x01, 0x72, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0xA1, 0x98, 0x7F, 0x03, 0x01 }, { 0x21, 0x00, 0x3F, 0x07, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0xA1, 0x93, 0xC1, 0x12, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x21, 0x18, 0xC1, 0x22, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x31, 0x5B, 0xF4, 0x15, 0x00 }, { 0x72, 0x83, 0x8A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0xA1, 0x90, 0x74, 0x39, 0x00 }, { 0x61, 0x00, 0x71, 0x67, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x71, 0x57, 0x54, 0x05, 0x00 }, { 0x72, 0x00, 0x7A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x90, 0x00, 0x54, 0x63, 0x00 }, { 0x41, 0x00, 0xA5, 0x45, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+// 38
+	{ false, { 0x21, 0x92, 0x85, 0x17, 0x00 }, { 0x21, 0x01, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x21, 0x94, 0x75, 0x17, 0x00 }, { 0x21, 0x05, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x21, 0x94, 0x76, 0x15, 0x00 }, { 0x61, 0x00, 0x82, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x31, 0x43, 0x9E, 0x17, 0x01 }, { 0x21, 0x00, 0x62, 0x2C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x21, 0x9B, 0x61, 0x6A, 0x00 }, { 0x21, 0x00, 0x7F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x61, 0x8A, 0x75, 0x1F, 0x00 }, { 0x22, 0x06, 0x74, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0xA1, 0x86, 0x72, 0x55, 0x01 }, { 0x21, 0x83, 0x71, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x4D, 0x54, 0x3C, 0x00 }, { 0x21, 0x00, 0xA6, 0x1C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+// 40
+	{ false, { 0x31, 0x8F, 0x93, 0x02, 0x01 }, { 0x61, 0x00, 0x72, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x31, 0x8E, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x72, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x31, 0x91, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x82, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x31, 0x8E, 0x93, 0x0F, 0x01 }, { 0x61, 0x00, 0x72, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x21, 0x4B, 0xAA, 0x16, 0x01 }, { 0x21, 0x00, 0x8F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x31, 0x90, 0x7E, 0x17, 0x01 }, { 0x21, 0x00, 0x8B, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x31, 0x81, 0x75, 0x19, 0x01 }, { 0x32, 0x00, 0x61, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x32, 0x90, 0x9B, 0x21, 0x00 }, { 0x21, 0x00, 0x72, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+// 48
+	{ false, { 0xE1, 0x1F, 0x85, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0xE1, 0x46, 0x88, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0xA1, 0x9C, 0x75, 0x1F, 0x00 }, { 0x21, 0x00, 0x75, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x31, 0x8B, 0x84, 0x58, 0x00 }, { 0x21, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0xE1, 0x4C, 0x66, 0x56, 0x00 }, { 0xA1, 0x00, 0x65, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x62, 0xCB, 0x76, 0x46, 0x00 }, { 0xA1, 0x00, 0x55, 0x36, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x62, 0x99, 0x57, 0x07, 0x00 }, { 0xA1, 0x00, 0x56, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00 },
+	{ false, { 0x62, 0x93, 0x77, 0x07, 0x00 }, { 0xA1, 0x00, 0x76, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00 },
+// 50
+	{ false, { 0x22, 0x59, 0xFF, 0x03, 0x02 }, { 0x21, 0x00, 0xFF, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x0E, 0xFF, 0x0F, 0x01 }, { 0x21, 0x00, 0xFF, 0x0F, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x22, 0x46, 0x86, 0x55, 0x00 }, { 0x21, 0x80, 0x64, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x45, 0x66, 0x12, 0x00 }, { 0xA1, 0x00, 0x96, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x8B, 0x92, 0x2A, 0x01 }, { 0x22, 0x00, 0x91, 0x2A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0xA2, 0x9E, 0xDF, 0x05, 0x00 }, { 0x61, 0x40, 0x6F, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x20, 0x1A, 0xEF, 0x01, 0x00 }, { 0x60, 0x00, 0x8F, 0x06, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x8F, 0xF1, 0x29, 0x00 }, { 0x21, 0x80, 0xF4, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+// 58
+	{ false, { 0x77, 0xA5, 0x53, 0x94, 0x00 }, { 0xA1, 0x00, 0xA0, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x61, 0x1F, 0xA8, 0x11, 0x00 }, { 0xB1, 0x80, 0x25, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x61, 0x17, 0x91, 0x34, 0x00 }, { 0x61, 0x00, 0x55, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x71, 0x5D, 0x54, 0x01, 0x00 }, { 0x72, 0x00, 0x6A, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x97, 0x21, 0x43, 0x00 }, { 0xA2, 0x00, 0x42, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0xA1, 0x1C, 0xA1, 0x77, 0x01 }, { 0x21, 0x00, 0x31, 0x47, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x21, 0x89, 0x11, 0x33, 0x00 }, { 0x61, 0x03, 0x42, 0x25, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0xA1, 0x15, 0x11, 0x47, 0x01 }, { 0x21, 0x00, 0xCF, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+// 60
+	{ false, { 0x3A, 0xCE, 0xF8, 0xF6, 0x00 }, { 0x51, 0x00, 0x86, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00 },
+	{ false, { 0x21, 0x15, 0x21, 0x23, 0x01 }, { 0x21, 0x00, 0x41, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x06, 0x5B, 0x74, 0x95, 0x00 }, { 0x01, 0x00, 0xA5, 0x72, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x22, 0x92, 0xB1, 0x81, 0x00 }, { 0x61, 0x83, 0xF2, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x41, 0x4D, 0xF1, 0x51, 0x01 }, { 0x42, 0x00, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x61, 0x94, 0x11, 0x51, 0x01 }, { 0xA3, 0x80, 0x11, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x61, 0x8C, 0x11, 0x31, 0x00 }, { 0xA1, 0x80, 0x1D, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0xA4, 0x4C, 0xF3, 0x73, 0x01 }, { 0x61, 0x00, 0x81, 0x23, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+// 68
+	{ false, { 0x02, 0x85, 0xD2, 0x53, 0x00 }, { 0x07, 0x03, 0xF2, 0xF6, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x11, 0x0C, 0xA3, 0x11, 0x01 }, { 0x13, 0x80, 0xA2, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x11, 0x06, 0xF6, 0x41, 0x01 }, { 0x11, 0x00, 0xF2, 0xE6, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+	{ false, { 0x93, 0x91, 0xD4, 0x32, 0x00 }, { 0x91, 0x00, 0xEB, 0x11, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x04, 0x4F, 0xFA, 0x56, 0x00 }, { 0x01, 0x00, 0xC2, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00 },
+	{ false, { 0x21, 0x49, 0x7C, 0x20, 0x00 }, { 0x22, 0x00, 0x6F, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x31, 0x85, 0xDD, 0x33, 0x01 }, { 0x21, 0x00, 0x56, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x20, 0x04, 0xDA, 0x05, 0x02 }, { 0x21, 0x81, 0x8F, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+// 70
+	{ false, { 0x05, 0x6A, 0xF1, 0xE5, 0x00 }, { 0x03, 0x80, 0xC3, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x07, 0x15, 0xEC, 0x26, 0x00 }, { 0x02, 0x00, 0xF8, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x05, 0x9D, 0x67, 0x35, 0x00 }, { 0x01, 0x00, 0xDF, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00 },
+	{ false, { 0x18, 0x96, 0xFA, 0x28, 0x00 }, { 0x12, 0x00, 0xF8, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x10, 0x86, 0xA8, 0x07, 0x00 }, { 0x00, 0x03, 0xFA, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00 },
+	{ false, { 0x11, 0x41, 0xF8, 0x47, 0x02 }, { 0x10, 0x03, 0xF3, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00 },
+	{ false, { 0x01, 0x8E, 0xF1, 0x06, 0x02 }, { 0x10, 0x00, 0xF3, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0x0E, 0x00, 0x1F, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0xFF, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+// 78
+	{ false, { 0x06, 0x80, 0xF8, 0x24, 0x00 }, { 0x03, 0x88, 0x56, 0x84, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0x0E, 0x00, 0xF8, 0x00, 0x00 }, { 0xD0, 0x05, 0x34, 0x04, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0xD5, 0x95, 0x37, 0xA3, 0x00 }, { 0xDA, 0x40, 0x56, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00 },
+	{ false, { 0x35, 0x5C, 0xB2, 0x61, 0x02 }, { 0x14, 0x08, 0xF4, 0x15, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00 },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x4F, 0xF5, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0x26, 0x00, 0xFF, 0x01, 0x00 }, { 0xE4, 0x00, 0x12, 0x16, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0xF3, 0xF0, 0x00 }, { 0x00, 0x00, 0xF6, 0xC9, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00 }
+};
+
+// These are the rhythm instrument definitions used by the Win95 SB16 driver.
+OplInstrumentDefinition MidiDriver_ADLIB_Multisource::OPL_RHYTHM_BANK[62] = {
+	// GS percussion start
+	// 1B
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	// 20
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 },
+
+	// GM percussion start
+	// 23
+	{ false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23 },
+	{ false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23 },
+	{ false, { 0x02, 0x07, 0xF9, 0xFF, 0x00 }, { 0x11, 0x00, 0xF8, 0xFF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x34 },
+	{ false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30 },
+	{ false, { 0x00, 0x02, 0xFF, 0x07, 0x00 }, { 0x01, 0x00, 0xFF, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x3A },
+	// 28
+	{ false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x2F },
+	{ false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x00, 0xFB, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x31 },
+	{ false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x05, 0x7B, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x33 },
+	{ false, { 0x0C, 0x00, 0xF6, 0x02, 0x00 }, { 0x12, 0x00, 0xCB, 0x43, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x36 },
+	// 30
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x39 },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x48 },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x3C },
+	{ false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C },
+	{ false, { 0x0E, 0x00, 0xF5, 0x30, 0x00 }, { 0xD0, 0x0A, 0x9F, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54 },
+	{ false, { 0x0E, 0x0A, 0xE4, 0xE4, 0x03 }, { 0x07, 0x5D, 0xF5, 0xE5, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x24 },
+	{ false, { 0x02, 0x03, 0xB4, 0x04, 0x00 }, { 0x05, 0x0A, 0x97, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C },
+	{ false, { 0x4E, 0x00, 0xF6, 0x00, 0x00 }, { 0x9E, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54 },
+	// 38
+	{ false, { 0x11, 0x45, 0xF8, 0x37, 0x02 }, { 0x10, 0x08, 0xF3, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x53 },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54 },
+	{ false, { 0x80, 0x00, 0xFF, 0x03, 0x03 }, { 0x10, 0x0D, 0xFF, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x18 },
+	{ false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4D },
+	{ false, { 0x06, 0x0B, 0xF5, 0x0C, 0x00 }, { 0x02, 0x00, 0xF5, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3C },
+	{ false, { 0x01, 0x00, 0xFA, 0xBF, 0x00 }, { 0x02, 0x00, 0xC8, 0x97, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x37, 0x00, 0x41 },
+	{ false, { 0x01, 0x51, 0xFA, 0x87, 0x00 }, { 0x01, 0x00, 0xFA, 0xB7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3B },
+	{ false, { 0x01, 0x54, 0xFA, 0x8D, 0x00 }, { 0x02, 0x00, 0xF8, 0xB8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x33 },
+	// 40
+	{ false, { 0x01, 0x59, 0xFA, 0x88, 0x00 }, { 0x02, 0x00, 0xF8, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x2D },
+	{ false, { 0x01, 0x00, 0xF9, 0x0A, 0x03 }, { 0x00, 0x00, 0xFA, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47 },
+	{ false, { 0x00, 0x80, 0xF9, 0x89, 0x03 }, { 0x00, 0x00, 0xF6, 0x6C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C },
+	{ false, { 0x03, 0x80, 0xF8, 0x88, 0x03 }, { 0x0C, 0x08, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x3A },
+	{ false, { 0x03, 0x85, 0xF8, 0x88, 0x03 }, { 0x0C, 0x00, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x35 },
+	{ false, { 0x0E, 0x40, 0x76, 0x4F, 0x00 }, { 0x00, 0x08, 0x77, 0x18, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x40 },
+	{ false, { 0x0E, 0x40, 0xC8, 0x49, 0x00 }, { 0x03, 0x00, 0x9B, 0x69, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47 },
+	{ false, { 0xD7, 0xDC, 0xAD, 0x05, 0x03 }, { 0xC7, 0x00, 0x8D, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D },
+	// 48
+	{ false, { 0xD7, 0xDC, 0xA8, 0x04, 0x03 }, { 0xC7, 0x00, 0x88, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D },
+	{ false, { 0x80, 0x00, 0xF6, 0x06, 0x03 }, { 0x11, 0x00, 0x67, 0x17, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30 },
+	{ false, { 0x80, 0x00, 0xF5, 0x05, 0x02 }, { 0x11, 0x09, 0x46, 0x16, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30 },
+	{ false, { 0x06, 0x3F, 0x00, 0xF4, 0x00 }, { 0x15, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x31, 0x00, 0x45 },
+	{ false, { 0x06, 0x3F, 0x00, 0xF4, 0x03 }, { 0x12, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x44 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3F },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4A },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3C },
+	// 50
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x50 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x40 },
+	// GM percussion end
+
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x45 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x49 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4B },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x44 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x30 },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x35 },
+	// 58
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00 }
+	// GS percussion end
+};
+
+// These are the note frequency values used by the Win95 SB16 driver.
+const uint16 MidiDriver_ADLIB_Multisource::OPL_NOTE_FREQUENCIES[12] = {
+	0x0AB7, 0x0B5A, 0x0C07, 0x0CBE, 0x0D80, 0x0E4D, 0x0F27, 0x100E, 0x1102, 0x1205, 0x1318, 0x143A
+};
+
+// These are the volume values used by the Win95 SB16 driver.
+const uint8 MidiDriver_ADLIB_Multisource::OPL_VOLUME_LOOKUP[32] = {
+	0x50, 0x3F, 0x28, 0x24, 0x20, 0x1C, 0x17, 0x15, 0x13, 0x11, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A,
+	0x09, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00
+};
+
+const float MidiDriver_ADLIB_Multisource::OPL_FREQUENCY_CONVERSION_FACTOR = pow(2, 20) / 49716.0f;
+
+MidiDriver_ADLIB_Multisource::MidiChannelControlData::MidiChannelControlData() {
+	init();
+}
+
+void MidiDriver_ADLIB_Multisource::MidiChannelControlData::init() {
+	program = 0;
+	channelPressure = 0;
+	pitchBend = MIDI_PITCH_BEND_DEFAULT;
+
+	modulation = 0;
+	volume = 0;
+	panning = MIDI_PANNING_DEFAULT;
+	expression = MIDI_EXPRESSION_DEFAULT;
+	sustain = false;
+	rpn = MIDI_RPN_NULL;
+
+	pitchBendSensitivity = GM_PITCH_BEND_SENSITIVITY_DEFAULT;
+	pitchBendSensitivityCents = 0;
+	masterTuningFine = MIDI_MASTER_TUNING_FINE_DEFAULT;
+	masterTuningCoarse = MIDI_MASTER_TUNING_COARSE_DEFAULT;
+}
+
+MidiDriver_ADLIB_Multisource::ActiveNote::ActiveNote() {
+	init();
+}
+
+void MidiDriver_ADLIB_Multisource::ActiveNote::init() {
+	noteActive = false;
+	noteSustained = false;
+
+	note = 0;
+	velocity = 0;
+	channel = 0xFF;
+	source = 0xFF;
+
+	oplNote = 0;
+	oplFrequency = 0;
+	noteCounterValue = 0;
+
+	instrumentId = 0;
+	instrumentDef = 0;
+
+	channelAllocated = false;
+}
+
+bool MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::OplType oplType) {
+	return OPL::Config::detect(oplType) >= 0;
+}
+
+MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType) :
+		_oplType(oplType),
+		_opl(0),
+		_isOpen(false),
+		_accuracyMode(ACCURACY_MODE_SB16_WIN95),
+		_allocationMode(ALLOCATION_MODE_DYNAMIC),
+		_noteSelect(NOTE_SELECT_MODE_0),
+		_modulationDepth(MODULATION_DEPTH_HIGH),
+		_vibratoDepth(VIBRATO_DEPTH_HIGH),
+		_instrumentBank(OPL_INSTRUMENT_BANK),
+		_rhythmBank(OPL_RHYTHM_BANK),
+		_rhythmBankFirstNote(GS_RHYTHM_FIRST_NOTE),
+		_rhythmBankLastNote(GS_RHYTHM_LAST_NOTE),
+		_noteCounter(1) {
+	memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
+	Common::fill(_shadowRegisters, _shadowRegisters + sizeof(_shadowRegisters), 0);
+}
+
+MidiDriver_ADLIB_Multisource::~MidiDriver_ADLIB_Multisource() {
+	close();
+}
+
+int MidiDriver_ADLIB_Multisource::open() {
+	if (_isOpen)
+		return MERR_ALREADY_OPEN;
+
+	int8 detectResult = OPL::Config::detect(_oplType);
+	
+	if (detectResult == -1 && _oplType == OPL::Config::kDualOpl2) {
+		// Try to emulate dual OPL2 on OPL3
+		// TODO Implement this in fmopl
+		detectResult = OPL::Config::detect(OPL::Config::kOpl3);
+	}
+
+	if (detectResult == -1)
+		return MERR_DEVICE_NOT_AVAILABLE;
+
+	// Create the emulator / hardware interface.
+	_opl = OPL::Config::create(_oplType);
+
+	if (!_opl)
+		return MERR_CANNOT_CONNECT;
+
+	_isOpen = true;
+
+	// Initialize emulator / hardware interface.
+	if (!_opl->init())
+		return MERR_CANNOT_CONNECT;
+
+	// Set default OPL register values.
+	initOpl();
+
+	_timerRate = getBaseTempo();
+	// Start the emulator / hardware interface. This will also start the timer
+	// callbacks.
+	_opl->start(new Common::Functor0Mem<void, MidiDriver_ADLIB_Multisource>(this, &MidiDriver_ADLIB_Multisource::onTimer));
+
+	return 0;
+}
+
+bool MidiDriver_ADLIB_Multisource::isOpen() const {
+	return _isOpen;
+}
+
+void MidiDriver_ADLIB_Multisource::close() {
+	if (!_isOpen)
+		return;
+
+	_isOpen = false;
+
+	stopAllNotes(true);
+
+	if (_opl) {
+		_opl->stop();
+		delete _opl;
+		_opl = 0;
+	}
+}
+
+uint32 MidiDriver_ADLIB_Multisource::property(int prop, uint32 param) {
+	switch (prop) {
+	case PROP_OPL_ACCURACY_MODE:
+		if (param == 0xFFFF)
+			return _accuracyMode;
+
+		switch (param) {
+		case ACCURACY_MODE_GM:
+			_accuracyMode = ACCURACY_MODE_GM;
+			break;
+		case ACCURACY_MODE_SB16_WIN95:
+		default:
+			_accuracyMode = ACCURACY_MODE_SB16_WIN95;
+		}
+
+		break;
+	case PROP_OPL_CHANNEL_ALLOCATION_MODE:
+		if (param == 0xFFFF)
+			return _allocationMode;
+
+		switch (param) {
+		case ALLOCATION_MODE_STATIC:
+			_allocationMode = ALLOCATION_MODE_STATIC;
+			break;
+		case ALLOCATION_MODE_DYNAMIC:
+		default:
+			_allocationMode = ALLOCATION_MODE_DYNAMIC;
+		}
+
+		break;
+	default:
+		return MidiDriver_Multisource::property(prop, param);
+	}
+	return 0;
+}
+
+uint32 MidiDriver_ADLIB_Multisource::getBaseTempo() {
+	return 1000000 / OPL::OPL::kDefaultCallbackFrequency;
+}
+
+MidiChannel *MidiDriver_ADLIB_Multisource::allocateChannel() {
+	// This driver does not use MidiChannel objects.
+	return 0;
+}
+
+MidiChannel *MidiDriver_ADLIB_Multisource::getPercussionChannel() {
+	// This driver does not use MidiChannel objects.
+	return 0;
+}
+
+void MidiDriver_ADLIB_Multisource::send(int8 source, uint32 b) {
+	byte command = b & 0xF0;
+
+	if (source == -1) {
+		// Source -1 is a shorthand to set controller values for all sources.
+		if (command == MIDI_COMMAND_NOTE_OFF || command == MIDI_COMMAND_NOTE_ON) {
+			// Notes should not be sent using source -1, but use source 0 in
+			// case this happens.
+			source = 0;
+		} else {
+			// Send controller event using all sources.
+			for (int i = 0; i < MAXIMUM_SOURCES; i++) {
+				send(i, b);
+			}
+			return;
+		}
+	}
+
+	// Extract the MIDI bytes.
+	byte channel = b & 0x0F;
+	byte op1 = (b >> 8) & 0xFF;
+	byte op2 = (b >> 16) & 0xFF;
+
+	switch (command) {
+	case MIDI_COMMAND_NOTE_OFF:
+		noteOff(channel, op1, op2, source);
+		break;
+	case MIDI_COMMAND_NOTE_ON:
+		noteOn(channel, op1, op2, source);
+		break;
+	case MIDI_COMMAND_POLYPHONIC_AFTERTOUCH: // Not supported by GM
+		polyAftertouch(channel, op1, op2, source);
+		break;
+	case MIDI_COMMAND_CONTROL_CHANGE:
+		controlChange(channel, op1, op2, source);
+		break;
+	case MIDI_COMMAND_PROGRAM_CHANGE:
+		programChange(channel, op1, source);
+		break;
+	case MIDI_COMMAND_CHANNEL_AFTERTOUCH:
+		channelAftertouch(channel, op1, source);
+		break;
+	case MIDI_COMMAND_PITCH_BEND:
+		pitchBend(channel, op1, op2, source);
+		break;
+	case MIDI_COMMAND_SYSTEM:
+		// The only supported system event is SysEx and that should be sent
+		// using the sysEx functions.
+		warning("MidiDriver_ADLIB_Multisource: send received system event (not processed): %x", b);
+		break;
+	default:
+		warning("MidiDriver_ADLIB_Multisource: Received unknown event %02x", command);
+		break;
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::noteOff(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
+	_activeNotesMutex.lock();
+
+	// Find the OPL channel playing this note.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive && _activeNotes[i].source == source &&
+				_activeNotes[i].channel == channel && _activeNotes[i].note == note) {
+			if (_controlData[source][channel].sustain) {
+				// Sustain controller is on. Sustain the note instead of
+				// ending it.
+				_activeNotes[i].noteSustained = true;
+			} else {
+				writeKeyOff(i);
+			}
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
+	if (velocity == 0)
+		// Note on with velocity 0 is a note off.
+		noteOff(channel, note, velocity, source);
+
+	InstrumentInfo instrument = determineInstrument(channel, source, note);
+
+	if (instrument.instrumentDef->isEmpty())
+		// Instrument definition contains no data, so the note cannot be played.
+		return;
+
+	_activeNotesMutex.lock();
+
+	// Allocate OPL channel.
+	uint8 oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
+	if (oplChannel != 0xFF) {
+		if (_activeNotes[oplChannel].noteActive)
+			// Turn off the note currently playing on this OPL channel.
+			writeKeyOff(oplChannel);
+
+		// Update the active note data.
+		_activeNotes[oplChannel].noteActive = true;
+		_activeNotes[oplChannel].noteSustained = false;
+		_activeNotes[oplChannel].note = note;
+		_activeNotes[oplChannel].velocity = velocity;
+		_activeNotes[oplChannel].channel = channel;
+		_activeNotes[oplChannel].source = source;
+
+		_activeNotes[oplChannel].oplNote = instrument.oplNote;
+		// Increase the note counter when playing a new note.
+		_activeNotes[oplChannel].noteCounterValue = _noteCounter++;
+		_activeNotes[oplChannel].instrumentId = instrument.instrumentId;
+		_activeNotes[oplChannel].instrumentDef = instrument.instrumentDef;
+
+		// Calculate operator volumes and write operator definitions to
+		// the OPL registers.
+		for (int i = 0; i < instrument.instrumentDef->getNumberOfOperators(); i++) {
+			uint16 operatorOffset = determineOperatorRegisterOffset(oplChannel, i, instrument.instrumentDef->fourOperator);
+			const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
+			writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
+			writeVolume(oplChannel, i);
+			writeRegister(OPL_REGISTER_BASE_DECAY_ATTACK + operatorOffset, operatorDef.decayAttack);
+			writeRegister(OPL_REGISTER_BASE_RELEASE_SUSTAIN + operatorOffset, operatorDef.releaseSustain);
+			writeRegister(OPL_REGISTER_BASE_WAVEFORMSELECT + operatorOffset, operatorDef.waveformSelect);
+		}
+
+		// Determine and write panning and write feedback and connection.
+		writePanning(oplChannel);
+
+		// Calculate and write frequency and block and write key on bit.
+		writeFrequency(oplChannel);
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::polyAftertouch(uint8 channel, uint8 note, uint8 pressure, uint8 source) {
+	// Because this event is not required by General MIDI and not implemented
+	// in the Win95 SB16 driver, there is no default implementation.
+}
+
+void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller, uint8 value, uint8 source) {
+	// Call the function for handling each controller.
+	switch (controller) {
+	case MIDI_CONTROLLER_MODULATION:
+		modulation(channel, value, source);
+		break;
+	case MIDI_CONTROLLER_DATA_ENTRY_MSB:
+		dataEntry(channel, value, 0xFF, source);
+		break;
+	case MIDI_CONTROLLER_VOLUME:
+		volume(channel, value, source);
+		break;
+	case MIDI_CONTROLLER_PANNING:
+		panning(channel, value, source);
+		break;
+	case MIDI_CONTROLLER_EXPRESSION:
+		expression(channel, value, source);
+		break;
+	case MIDI_CONTROLLER_DATA_ENTRY_LSB:
+		dataEntry(channel, 0xFF, value, source);
+		break;
+	case MIDI_CONTROLLER_SUSTAIN:
+		sustain(channel, value, source);
+		break;
+	case MIDI_CONTROLLER_RPN_LSB:
+		registeredParameterNumber(channel, 0xFF, value, source);
+		break;
+	case MIDI_CONTROLLER_RPN_MSB:
+		registeredParameterNumber(channel, value, 0xFF, source);
+		break;
+	case MIDI_CONTROLLER_ALL_SOUND_OFF:
+		allSoundOff(channel, source);
+		break;
+	case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
+		resetAllControllers(channel, source);
+		break;
+	case MIDI_CONTROLLER_ALL_NOTES_OFF:
+	case MIDI_CONTROLLER_OMNI_OFF:
+	case MIDI_CONTROLLER_OMNI_ON:
+	case MIDI_CONTROLLER_MONO_ON:
+	case MIDI_CONTROLLER_POLY_ON:
+		// The omni/mono/poly events also act as an all notes off.
+		allNotesOff(channel, source);
+		break;
+	default:
+		//debug("MidiDriver_ADLIB_Multisource::controlChange - Unsupported controller %X", controller);
+		break;
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) {
+	// Just set the MIDI program value; this event does not affect active notes.
+	_controlData[source][channel].program = program;
+}
+
+void MidiDriver_ADLIB_Multisource::channelAftertouch(uint8 channel, uint8 pressure, uint8 source) {
+	// Even though this event is required by General MIDI, it is not implemented
+	// in the Win95 SB16 driver, so there is no default implementation.
+}
+
+void MidiDriver_ADLIB_Multisource::pitchBend(uint8 channel, uint8 pitchBendLsb, uint8 pitchBendMsb, uint8 source) {
+	_controlData[source][channel].pitchBend = ((uint16)pitchBendMsb) << 7 | pitchBendLsb;
+
+	// Recalculate and write the frequencies of the active notes on this MIDI
+	// channel to let the new pitch bend value take effect.
+	recalculateFrequencies(channel, source);
+}
+
+void MidiDriver_ADLIB_Multisource::sysEx(const byte *msg, uint16 length) {
+	if (length >= 4 && msg[0] == 0x7E && msg[2] == 0x09 && msg[3] == 0x01) {
+		// F0 7E <device ID> 09 01 F7
+		// General MIDI System On
+		
+		// Reset the MIDI context and the OPL chip.
+
+		stopAllNotes(true);
+
+		for (int i = 0; i < MAXIMUM_SOURCES; i++) {
+			for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) {
+				_controlData[i][j].init();
+			}
+		}
+
+		for (int i = 0; i < determineNumOplChannels(); i++) {
+			_activeNotes[i].init();
+		}
+
+		memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
+		_noteCounter = 1;
+
+		initOpl();
+	} else {
+		// Ignore other SysEx messages.
+		warning("MidiDriver_ADLIB_Multisource::sysEx - Unrecognized SysEx");
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::metaEvent(int8 source, byte type, byte *data, uint16 length) {
+	if (type == MIDI_META_END_OF_TRACK && source >= 0)
+		// Stop hanging notes and release resources used by this source.
+		deinitSource(source);
+}
+
+void MidiDriver_ADLIB_Multisource::deinitSource(uint8 source) {
+	// Turn off sustained notes.
+	for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
+		sustain(i, 0, source);
+	}
+
+	// Stop fades and turn off non-sustained notes.
+	MidiDriver_Multisource::deinitSource(source);
+
+	_allocationMutex.lock();
+
+	// Deallocate channels
+	for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
+		_channelAllocations[source][i] = 0xFF;
+	}
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].channelAllocated && _activeNotes[i].source == source) {
+			_activeNotes[i].channelAllocated = false;
+		}
+	}
+
+	_allocationMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::modulation(uint8 channel, uint8 modulation, uint8 source) {
+	// Even though this controller is required by General MIDI, it is not
+	// implemented in the Win95 SB16 driver, so there is no default
+	// implementation.
+}
+
+void MidiDriver_ADLIB_Multisource::dataEntry(uint8 channel, uint8 dataMsb, uint8 dataLsb, uint8 source) {
+	// Set the data on the currently active RPN.
+	switch (_controlData[source][channel].rpn) {
+	case MIDI_RPN_PITCH_BEND_SENSITIVITY:
+		// MSB = semitones, LSB = cents.
+		if (dataMsb != 0xFF) {
+			_controlData[source][channel].pitchBendSensitivity = dataMsb;
+		}
+		if (dataLsb != 0xFF) {
+			_controlData[source][channel].pitchBendSensitivityCents = dataLsb;
+		}
+		// Apply the new pitch bend sensitivity to any active notes.
+		recalculateFrequencies(channel, source);
+	case MIDI_RPN_MASTER_TUNING_FINE:
+		// MSB and LSB are combined to a fraction of a semitone.
+		if (dataMsb != 0xFF) {
+			_controlData[source][channel].masterTuningFine &= 0x00FF;
+			_controlData[source][channel].masterTuningFine |= dataMsb << 8;
+		}
+		if (dataLsb != 0xFF) {
+			_controlData[source][channel].masterTuningFine &= 0xFF00;
+			_controlData[source][channel].masterTuningFine |= dataLsb;
+		}
+		// Apply the new master tuning to any active notes.
+		recalculateFrequencies(channel, source);
+	case MIDI_RPN_MASTER_TUNING_COARSE:
+		// MSB = semitones, LSB is ignored.
+		if (dataMsb != 0xFF) {
+			_controlData[source][channel].masterTuningCoarse = dataMsb;
+		}
+		// Apply the new master tuning to any active notes.
+		recalculateFrequencies(channel, source);
+	}
+	// Ignore data entry if null or an unknown RPN is active.
+}
+
+void MidiDriver_ADLIB_Multisource::volume(uint8 channel, uint8 volume, uint8 source) {
+	if (_controlData[source][channel].volume == volume)
+		return;
+
+	_controlData[source][channel].volume = volume;
+	// Apply the new channel volume to any active notes.
+	recalculateVolumes(channel, source);
+}
+
+void MidiDriver_ADLIB_Multisource::panning(uint8 channel, uint8 panning, uint8 source) {
+	if (_controlData[source][channel].panning == panning)
+		return;
+
+	_controlData[source][channel].panning = panning;
+
+	_activeNotesMutex.lock();
+
+	// Apply the new channel panning to any active notes.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive && _activeNotes[i].channel == channel &&
+				_activeNotes[i].source == source) {
+			writePanning(i);
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::expression(uint8 channel, uint8 expression, uint8 source) {
+	if (_controlData[source][channel].expression == expression)
+		return;
+
+	_controlData[source][channel].expression = expression;
+	// Apply the new expression value to any active notes.
+	recalculateVolumes(channel, source);
+}
+
+void MidiDriver_ADLIB_Multisource::sustain(uint8 channel, uint8 sustain, uint8 source) {
+	if (sustain >= 0x40) {
+		// Turn on sustain.
+		_controlData[source][channel].sustain = true;
+	} else if (_controlData[source][channel].sustain) {
+		// Sustain is currently on. Turn it off.
+		_controlData[source][channel].sustain = false;
+
+		_activeNotesMutex.lock();
+
+		// Turn off any sustained notes on this channel.
+		for (int i = 0; i < determineNumOplChannels(); i++) {
+			if (_activeNotes[i].noteActive && _activeNotes[i].noteSustained &&
+					_activeNotes[i].channel == channel && _activeNotes[i].source == source) {
+				writeKeyOff(i);
+			}
+		}
+
+		_activeNotesMutex.unlock();
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::registeredParameterNumber(uint8 channel, uint8 rpnMsb, uint8 rpnLsb, uint8 source) {
+	// Set the currently active RPN. MSB and LSB combined form the RPN number.
+	if (rpnMsb != 0xFF) {
+		_controlData[source][channel].rpn &= 0x00FF;
+		_controlData[source][channel].rpn |= rpnMsb << 8;
+	}
+	if (rpnLsb != 0xFF) {
+		_controlData[source][channel].rpn &= 0xFF00;
+		_controlData[source][channel].rpn |= rpnLsb;
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::allSoundOff(uint8 channel, uint8 source) {
+	// It is not possible to immediately terminate the sound on an OPL chip
+	// (skipping the "release" of the notes), so just turn the notes off.
+	stopAllNotes(source, channel);
+}
+
+void MidiDriver_ADLIB_Multisource::resetAllControllers(uint8 channel, uint8 source) {
+	modulation(channel, 0, source);
+	expression(channel, MIDI_EXPRESSION_DEFAULT, source);
+	sustain(channel, 0, source);
+	registeredParameterNumber(channel, MIDI_RPN_NULL >> 8, MIDI_RPN_NULL & 0xFF, source);
+	pitchBend(channel, MIDI_PITCH_BEND_DEFAULT & 0x7F, MIDI_PITCH_BEND_DEFAULT >> 7, source);
+	channelAftertouch(channel, 0, source);
+	// TODO Polyphonic aftertouch should also be reset; not implemented because
+	// polyphonic aftertouch is not implemented.
+}
+
+void MidiDriver_ADLIB_Multisource::allNotesOff(uint8 channel, uint8 source) {
+	_activeNotesMutex.lock();
+
+	// Execute a note off for all active notes on this MIDI channel. This will
+	// turn the notes off if sustain is off and sustain the notes if it is on.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive && !_activeNotes[i].noteSustained && 
+				_activeNotes[i].source == source && _activeNotes[i].channel == channel) {
+			noteOff(channel, _activeNotes->note, 0, source);
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::stopAllNotes(bool stopSustainedNotes) {
+	// Just write the key off bit on all OPL channels. No special handling is
+	// needed to make sure sustained notes are turned off.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		// Force the register write to prevent accidental hanging notes.
+		writeKeyOff(i, true);
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
+	_activeNotesMutex.lock();
+
+	// Write the key off bit for all active notes on this MIDI channel and
+	// source.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive && (source == 0xFF || _activeNotes[i].source == source) &&
+				(channel == 0xFF || _activeNotes[i].channel == channel)) {
+			writeKeyOff(i);
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::applySourceVolume(uint8 source) {
+	// Recalculate the volume of the active notes on all MIDI channels of this
+	// source.
+	recalculateVolumes(0xFF, source);
+}
+
+void MidiDriver_ADLIB_Multisource::initOpl() {
+	// Clear test flags and enable waveform select for OPL2 chips.
+	writeRegister(OPL_REGISTER_TEST, _oplType == OPL::Config::kOpl3 ? 0 : 0x20, true);
+	if (_oplType != OPL::Config::kOpl2) {
+		writeRegister(OPL_REGISTER_TEST | OPL_REGISTER_SET_2_OFFSET, _oplType == OPL::Config::kOpl3 ? 0 : 0x20, true);
+	}
+
+	// Stop and mask the timers and reset the interrupt.
+	writeRegister(OPL_REGISTER_TIMERCONTROL, 0x60, true);
+	writeRegister(OPL_REGISTER_TIMERCONTROL, 0x80, true);
+	if (_oplType == OPL::Config::kDualOpl2) {
+		writeRegister(OPL_REGISTER_TIMERCONTROL | OPL_REGISTER_SET_2_OFFSET, 0x60, true);
+		writeRegister(OPL_REGISTER_TIMERCONTROL | OPL_REGISTER_SET_2_OFFSET, 0x80, true);
+	}
+
+	if (_oplType == OPL::Config::kOpl3) {
+		// Turn off 4 operator mode for all channels.
+		writeRegister(OPL3_REGISTER_CONNECTIONSELECT, 0, true);
+		// Enable "new" OPL3 functionality.
+		writeRegister(OPL3_REGISTER_NEW, 1, true);
+	}
+
+	// Set note select mode and disable CSM mode for OPL2 chips.
+	writeRegister(OPL_REGISTER_NOTESELECT_CSM, _noteSelect << 6, true);
+	if (_oplType == OPL::Config::kDualOpl2) {
+		writeRegister(OPL_REGISTER_NOTESELECT_CSM | OPL_REGISTER_SET_2_OFFSET, _noteSelect << 6, true);
+	}
+
+	// Set operator registers to default values.
+	for (int i = 0; i < 5; i++) {
+		uint8 baseReg = 0;
+		uint8 value = 0;
+		switch (i) {
+		case 0:
+			baseReg = OPL_REGISTER_BASE_FREQMULT_MISC;
+			break;
+		case 1:
+			baseReg = OPL_REGISTER_BASE_LEVEL;
+			// Set default volume to 3F (maximum attenuation).
+			value = OPL_LEVEL_DEFAULT;
+			break;
+		case 2:
+			baseReg = OPL_REGISTER_BASE_DECAY_ATTACK;
+			break;
+		case 3:
+			baseReg = OPL_REGISTER_BASE_RELEASE_SUSTAIN;
+			break;
+		case 4:
+			baseReg = OPL_REGISTER_BASE_WAVEFORMSELECT;
+			break;
+		}
+
+		for (int j = 0; j < (_oplType == OPL::Config::kOpl2 ? OPL2_NUM_CHANNELS : OPL3_NUM_CHANNELS); j++) {
+			writeRegister(baseReg + determineOperatorRegisterOffset(j, 0), value, true);
+			writeRegister(baseReg + determineOperatorRegisterOffset(j, 1), value, true);
+		}
+	}
+
+	// Set channel registers to default values.
+	for (int i = 0; i < 3; i++) {
+		uint8 baseReg = 0;
+		uint8 value = 0;
+		switch (i) {
+		case 0:
+			baseReg = OPL_REGISTER_BASE_FNUMLOW;
+			break;
+		case 1:
+			baseReg = OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON;
+			break;
+		case 2:
+			baseReg = OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING;
+			if (_oplType == OPL::Config::kOpl3) {
+				// Set default panning to center.
+				value = OPL_PANNING_CENTER;
+			}
+			break;
+		}
+
+		for (int j = 0; j < determineNumOplChannels(); j++) {
+			writeRegister(baseReg + determineChannelRegisterOffset(j), value, true);
+		}
+	}
+
+	// Disable rhythm mode and set modulation and vibrato depth.
+	uint8 rhythmValue = (_vibratoDepth << 6) | (_modulationDepth << 7);
+	writeRegister(OPL_REGISTER_RHYTHM, rhythmValue, true);
+	if (_oplType == OPL::Config::kDualOpl2) {
+		writeRegister(OPL_REGISTER_RHYTHM | OPL_REGISTER_SET_2_OFFSET, rhythmValue, true);
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 source) {
+	_activeNotesMutex.lock();
+
+	// Calculate and write the frequency of all active notes on this MIDI
+	// channel and source.
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive && _activeNotes[i].channel == channel &&
+				_activeNotes[i].source == source) {
+			writeFrequency(i);
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::recalculateVolumes(uint8 channel, uint8 source) {
+	_activeNotesMutex.lock();
+
+	// Calculate and write the volume of all operators of all active notes on
+	// this MIDI channel and source. 
+	for (int i = 0; i < determineNumOplChannels(); i++) {
+		if (_activeNotes[i].noteActive &&
+				(channel == 0xFF || _activeNotes[i].channel == channel) &&
+				(source == 0xFF || _activeNotes[i].source == source)) {
+			for (int j = 0; j < _activeNotes[i].instrumentDef->getNumberOfOperators(); j++) {
+				writeVolume(i, j);
+			}
+		}
+	}
+
+	_activeNotesMutex.unlock();
+}
+
+MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::determineInstrument(uint8 channel, uint8 source, uint8 note) {
+	InstrumentInfo instrument = { 0 };
+
+	if (channel == MIDI_RHYTHM_CHANNEL) {
+		// On the rhythm channel, the note played indicates which instrument
+		// should be used.
+		if (note < _rhythmBankFirstNote || note > _rhythmBankLastNote)
+			// No rhythm instrument assigned to this note number.
+			return instrument;
+
+		// Set the high bit for rhythm instrument IDs.
+		instrument.instrumentId = 0x80 & note;
+		instrument.instrumentDef = &_rhythmBank[note - _rhythmBankFirstNote];
+		// Get the note to play from the instrument definition.
+		instrument.oplNote = instrument.instrumentDef->rhythmNote;
+	} else {
+		// On non-rhythm channels, use the active instrument (program) on the
+		// MIDI channel.
+		instrument.instrumentId = _controlData[source][channel].program;
+		instrument.instrumentDef = &_instrumentBank[instrument.instrumentId];
+		instrument.oplNote = note;
+	}
+
+	return instrument;
+}
+
+uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+
+	uint8 allocatedChannel = 0xFF;
+	if (_allocationMode == ALLOCATION_MODE_DYNAMIC) {
+		// In dynamic channel allocation mode, each note is allocated a new
+		// OPL channel. The following criterea are used, in this order:
+		// - The channel with the lowest number that has not yet been used to
+		//   play a note (note counter value is 0).
+		// - The channel with the lowest note counter value that is not
+		//   currently playing a note.
+		// - The channel with the lowest note counter value that is playing a
+		//   note using the same instrument.
+		// - The channel with the lowest note counter value (i.e. playing the
+		//   oldest note).
+		// This will always return a channel; if a note is currently playing,
+		// it will be aborted.
+
+		uint8 unusedChannel = 0xFF, inactiveChannel = 0xFF, instrumentChannel = 0xFF, lowestCounterChannel = 0xFF;
+		uint32 inactiveNoteCounter = 0xFFFF, instrumentNoteCounter = 0xFFFF, lowestNoteCounter = 0xFFFF;
+		for (int i = 0; i < determineNumOplChannels(); i++) {
+			if (_activeNotes[i].noteCounterValue == 0) {
+				// This channel is unused. No need to look any further.
+				unusedChannel = i;
+				break;
+			}
+			if (!_activeNotes[i].noteActive && _activeNotes[i].noteCounterValue < inactiveNoteCounter) {
+				// A channel not playing a note with a lower note counter value
+				// has been found.
+				inactiveNoteCounter = _activeNotes[i].noteCounterValue;
+				inactiveChannel = i;
+				continue;
+			}
+			if (_activeNotes[i].noteActive && _activeNotes[i].instrumentId == instrumentId &&
+					_activeNotes[i].noteCounterValue < instrumentNoteCounter) {
+				// A channel playing a note using the same instrument with a
+				// lower note counter value has been found.
+				instrumentNoteCounter = _activeNotes[i].noteCounterValue;
+				instrumentChannel = i;
+			}
+			if (_activeNotes[i].noteActive && _activeNotes[i].noteCounterValue < lowestNoteCounter) {
+				// A channel playing a note with a lower note counter value has
+				// been found.
+				lowestNoteCounter = _activeNotes[i].noteCounterValue;
+				lowestCounterChannel = i;
+			}
+		}
+
+		if (unusedChannel != 0xFF)
+			// An unused channel has been found. Use this.
+			allocatedChannel = unusedChannel;
+		else if (inactiveChannel != 0xFF)
+			// An inactive channel has been found. Use this.
+			allocatedChannel = inactiveChannel;
+		else if (instrumentChannel != 0xFF)
+			// An active channel using the same instrument has been found.
+			// Use this.
+			allocatedChannel = instrumentChannel;
+		else
+			// Just use the channel playing the oldest note.
+			allocatedChannel = lowestCounterChannel;
+	} else {
+		// In static allocation mode, each MIDI channel of each source is
+		// allocated a fixed OPL channel to use. All notes on that MIDI channel
+		// are played using the allocated OPL channel. If a new MIDI channel
+		// needs an OPL channel and all OPL channels have already been
+		// allocated, allocation will fail.
+
+		allocatedChannel = 0xFF;
+
+		_allocationMutex.lock();
+
+		if (_channelAllocations[source][channel] != 0xFF) {
+			// An OPL channel has already been allocated to this MIDI channel
+			// for this source. Use the previously allocated channel.
+			allocatedChannel = _channelAllocations[source][channel];
+		} else {
+			// No OPL channel has been allocated yet. Find a free OPL channel.
+			for (int i = 0; i < determineNumOplChannels(); i++) {
+				if (!_activeNotes[i].channelAllocated) {
+					// Found a free channel. Allocate this.
+					_activeNotes[i].channelAllocated = true;
+					_activeNotes[i].source = source;
+					_activeNotes[i].channel = channel;
+
+					_channelAllocations[source][channel] = i;
+
+					allocatedChannel = i;
+
+					break;
+				}
+			}
+			// If no free channel could be found, allocatedChannel will be 0xFF.
+		}
+
+		_allocationMutex.unlock();
+	}
+
+	return allocatedChannel;
+}
+
+uint16 MidiDriver_ADLIB_Multisource::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
+	// Split note into octave and octave note.
+	uint8 octaveNote = note % 12;
+	uint8 octave = note / 12;
+
+	// Calculate OPL octave (block) and frequency (F-num).
+	uint8 block;
+	uint32 oplFrequency;
+
+	if (_accuracyMode == ACCURACY_MODE_SB16_WIN95) {
+		// Frequency calculation using the algorithm of the Win95 SB16 driver.
+
+		// Look up the octave note OPL frequency. These values assume octave 5.
+		oplFrequency = OPL_NOTE_FREQUENCIES[octaveNote];
+		// Correct for octaves other than 5 by doubling or halving the OPL
+		// frequency for each octave higher or lower, respectively.
+		if (octave > 5) {
+			oplFrequency <<= (octave - 5);
+		} else {
+			oplFrequency >>= (5 - octave);
+		}
+		// The resulting value is likely larger than the 10 bit length of the
+		// F-num in the OPL registers. This is correct later by increasing the
+		// block.
+		block = 1;
+	} else {
+		// Frequency calculation using a more accurate algorithm.
+
+		// Calculate the note frequency in Hertz by relating it to a known
+		// frequency (in this case A4 (0x45) = 440 Hz). Formula is
+		// freq * 2 ^ (semitones / 12).
+		float noteFrequency = 440 * (pow(2, (note - 0x45) / 12.0f));
+		// Convert the frequency in Hz to the format used by the OPL registers.
+		// Note that the resulting value is double the actual frequency because
+		// of the use of block 0 (which halves the frequency). This allows for
+		// slightly higher precision in the pitch bend calculation.
+		oplFrequency = round(noteFrequency * OPL_FREQUENCY_CONVERSION_FACTOR);
+		block = 0;
+	}
+
+	// Calculate and apply pitch bend and tuning.
+	oplFrequency += calculatePitchBend(channel, source, oplFrequency);
+
+	// Shift the frequency down to the 10 bits used by the OPL registers.
+	// Increase the block to compensate.
+	while (oplFrequency > 0x3FF) {
+		oplFrequency >>= 1;
+		block++;
+	}
+	// Maximum supported block value is 7, so clip higher values. The highest
+	// MIDI notes exceed the maximum OPL frequency, so these will be transposed
+	// down 1 or 2 octaves.
+	block = MIN(block, (uint8)7);
+
+	// Combine the block and frequency in the OPL Ax and Bx register format.
+	return oplFrequency | (block << 10);
+}
+
+int32 MidiDriver_ADLIB_Multisource::calculatePitchBend(uint8 channel, uint8 source, uint16 oplFrequency) {
+	int32 pitchBend;
+
+	if (_accuracyMode == ACCURACY_MODE_SB16_WIN95) {
+		// Pitch bend calculation using the algorithm of the Win95 SB16 driver.
+
+		// Convert the 14 bit MIDI pitch bend value to a 16 bit signed value.
+		// WORKAROUND The conversion to signed in the Win95 SB16 driver is
+		// slightly inaccurate and causes minimum pitch bend to underflow to
+		// maximum pitch bend. This is corrected here by clipping the result to
+		// the int16 minimum value.
+		pitchBend = MAX(-0x8000, (_controlData[source][channel].pitchBend << 2) - 0x8001);
+		// Scale pitch bend by 0x1F (up) or 0x1B (down), which is a fixed
+		// distance of 2 semitones up or down (pitch bend sensitivity is not
+		// supported by this algorithm).
+		pitchBend *= (pitchBend > 0 ? 0x1F : 0x1B);
+		pitchBend >>= 8;
+		// Scale by the OPL note frequency.
+		pitchBend *= oplFrequency;
+		pitchBend >>= 0xF;
+	} else {
+		// Pitch bend calculation using a more accurate algorithm.
+
+		// Calculate the pitch bend in cents.
+		int16 signedPitchBend = _controlData[source][channel].pitchBend - 0x2000;
+		uint16 pitchBendSensitivityCents = (_controlData[source][channel].pitchBendSensitivity * 100) +
+			_controlData[source][channel].pitchBendSensitivityCents;
+		// Pitch bend upwards has 1 less resolution than downwards
+		// (0x2001-0x3FFF vs 0x0000-0x1FFF).
+		float pitchBendCents = signedPitchBend * pitchBendSensitivityCents /
+			(signedPitchBend > 0 ? 8191.0f : 8192.0f);
+		// Calculate the tuning in cents.
+		float tuningCents = ((_controlData[source][channel].masterTuningCoarse - 0x40) * 100) +
+			((_controlData[source][channel].masterTuningFine - 0x2000) * 100 / 8192.0f);
+
+		// Calculate pitch bend (formula is freq * 2 ^ (cents / 1200)).
+		// Note that if unrealistically large values for pitch bend sensitivity
+		// and/or tuning are used, the result could overflow int32. Since this is
+		// far into the ultrasonic frequencies, this should not occur in practice.
+		pitchBend = round(oplFrequency * pow(2, (pitchBendCents + tuningCents) / 1200.0f) - oplFrequency);
+	}
+
+	return pitchBend;
+}
+
+uint8 MidiDriver_ADLIB_Multisource::calculateVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition& instrumentDef, uint8 operatorNum) {
+	// Get the volume (level) for this operator from the instrument definition.
+	uint8 operatorDefVolume = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
+
+	// Determine if volume settings should be applied to this operator. Carrier
+	// operators in FM synthesis and all operators in additive synthesis need
+	// to have volume settings applied; modulator operators just use the
+	// instrument definition volume.
+	bool applyVolume = false;
+	if (instrumentDef.fourOperator) {
+		// 4 operator instruments have 4 different operator connections.
+		uint8 connection = (instrumentDef.connectionFeedback0 & 0x01) | ((instrumentDef.connectionFeedback1 & 0x01) << 1);
+		switch (connection) {
+		case 0x00:
+			// 4FM
+			// Operator 3 is a carrier.
+			applyVolume = (operatorNum == 3);
+			break;
+		case 0x01:
+			// 1ADD+3FM
+			// Operator 0 is additive and operator 3 is a carrier.
+			applyVolume = (operatorNum == 0 || operatorNum == 3);
+			break;
+		case 0x10:
+			// 2FM+2FM
+			// Operators 1 and 3 are carriers.
+			applyVolume = (operatorNum == 1 || operatorNum == 3);
+			break;
+		case 0x11:
+			// 1ADD+2FM+1ADD
+			// Operators 0 and 3 are additive and operator 2 is a carrier.
+			applyVolume = (operatorNum == 0 || operatorNum == 2 || operatorNum == 3);
+			break;
+		default:
+			// Should not happen.
+			applyVolume = false;
+		}
+	} else {
+		// 2 operator instruments have 2 different operator connections:
+		// additive (0x01) or FM (0x00) synthesis.  Carrier operators in FM
+		// synthesis and all operators in additive synthesis need to have
+		// volume settings applied; modulator operators just use the instrument
+		// definition volume. In FM synthesis connection, operator 1 is a
+		// carrier.
+		applyVolume = (instrumentDef.connectionFeedback0 & 0x01) == 0x01 || operatorNum == 1;
+	}
+	if (!applyVolume)
+		// No need to apply volume settings; just use the instrument definition
+		// operator volume.
+		return operatorDefVolume;
+
+	// Calculate the volume based on note velocity, channel volume and
+	// expression.
+	uint8 unscaledVolume = calculateUnscaledVolume(channel, source, velocity, instrumentDef, operatorNum);
+
+	uint8 invertedVolume = 0x3F - unscaledVolume;
+	// Scale by source volume.
+	invertedVolume = (invertedVolume * _sources[source].volume) / _sources[source].neutralVolume;
+	if (_userVolumeScaling) {
+		if (_userMute) {
+			invertedVolume = 0;
+		} else {
+			// Scale by user volume.
+			uint16 userVolume = (_sources[source].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume); // Treat SOURCE_TYPE_UNDEFINED as music
+			invertedVolume = (invertedVolume * userVolume) >> 8;
+		}
+	}
+	// Source volume scaling might clip volume, so reduce to maximum.
+	invertedVolume = MIN((uint8)0x3F, invertedVolume);
+	uint8 scaledVolume = 0x3F - invertedVolume;
+
+	return scaledVolume;
+}
+
+uint8 MidiDriver_ADLIB_Multisource::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
+	uint8 unscaledVolume;
+	// Get the volume (level) for this operator from the instrument definition.
+	uint8 operatorVolume = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
+
+	if (_accuracyMode == ACCURACY_MODE_SB16_WIN95) {
+		// Volume calculation using the algorithm of the Win95 SB16 driver.
+
+		// Shift velocity and channel volume to a 5 bit value and look up the OPL
+		// volume value.
+		uint8 velocityVolume = OPL_VOLUME_LOOKUP[velocity >> 2];
+		uint8 channelVolume = OPL_VOLUME_LOOKUP[_controlData[source][channel].volume >> 2];
+		// Add velocity and channel OPL volume to get the unscaled volume. The
+		// operator volume is an additional (negative) volume adjustment to balance
+		// the instruments.
+		// Note that large OPL volume values can exceed the 0x3F limit; this is
+		// handled below. (0x3F means maximum attenuation - no sound.)
+		unscaledVolume = velocityVolume + channelVolume + operatorVolume;
+	} else {
+		// Volume calculation using an algorithm more accurate to the General MIDI
+		// standard.
+
+		// Calculate the volume in dB according to the GM formula:
+		// 40 log(velocity * volume * expression / 127 ^ 3)
+		// Note that velocity is not specified in detail in the MIDI standards;
+		// we use the same volume curve as channel volume and expression.
+		float volumeDb = 40 * log10((velocity * _controlData[source][channel].volume * _controlData[source][channel].expression) / 2048383.0f);
+		// Convert to OPL volume (every unit is 0.75 dB attenuation). The
+		// operator volume is an additional (negative) volume adjustment to balance
+		// the instruments.
+		unscaledVolume = volumeDb / -0.75f + operatorVolume;
+	}
+
+	// Clip the volume to the maximum value.
+	return MIN((uint8)0x3F, unscaledVolume);
+}
+
+uint8 MidiDriver_ADLIB_Multisource::calculatePanning(uint8 channel, uint8 source) {
+	// MIDI panning is converted to OPL panning using these values:
+	// 0x00...L...0x2F 0x30...C...0x50 0x51...R...0x7F
+	if (_controlData[source][channel].panning <= OPL_MIDI_PANNING_LEFT_LIMIT) {
+		return OPL_PANNING_LEFT;
+	} else if (_controlData[source][channel].panning >= OPL_MIDI_PANNING_RIGHT_LIMIT) {
+		return OPL_PANNING_RIGHT;
+	} else {
+		return OPL_PANNING_CENTER;
+	}
+}
+
+uint8 MidiDriver_ADLIB_Multisource::determineNumOplChannels() {
+	return _oplType == OPL::Config::kOpl2 ? OPL2_NUM_CHANNELS : OPL3_NUM_CHANNELS;
+}
+
+uint16 MidiDriver_ADLIB_Multisource::determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, bool fourOperator) {
+	assert(!fourOperator || oplChannel < 6);
+	assert(fourOperator || operatorNum < 2);
+
+	uint16 offset = 0;
+	if (fourOperator) {
+		// 4 operator register offset for each channel and operator:
+		// 
+		// Channel  | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
+		// Operator | 0         | 1         | 2         | 3         |
+		// Register | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 9 | A | B | C | D |
+		//
+		// Channels 3-5 are in the second register set (add 0x100 to the register).
+		offset += (oplChannel / 3) * OPL_REGISTER_SET_2_OFFSET;
+		offset += operatorNum / 2 * 8;
+		offset += (operatorNum % 2) * 3;
+		offset += oplChannel % 3;
+	} else {
+		// 2 operator register offset for each channel and operator:
+		//
+		// Channel  | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 3 | 4 | 5 | 6 | 7 | 8 | 6 | 7 | 8 |
+		// Operator | 0         | 1         | 0         | 1         | 0         | 1         |
+		// Register | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 9 | A | B | C | D |10 |11 |12 |13 |14 |15 |
+		//
+		// Channels 9-17 are in the second register set (add 0x100 to the register).
+		offset += (oplChannel / 9) * OPL_REGISTER_SET_2_OFFSET;
+		offset += (oplChannel % 9) / 3 * 8;
+		offset += (oplChannel % 9) % 3;
+		offset += operatorNum * 3;
+	}
+
+	return offset;
+}
+
+uint16 MidiDriver_ADLIB_Multisource::determineChannelRegisterOffset(uint8 oplChannel, bool fourOperator) {
+	assert(!fourOperator || oplChannel < 6);
+
+	// In 4 operator mode, only the first three channel registers are used in
+	// each register set.
+	uint8 numChannelsPerSet = fourOperator ? 3 : 9;
+	uint16 offset = (oplChannel / numChannelsPerSet) * OPL_REGISTER_SET_2_OFFSET;
+	return offset + (oplChannel % numChannelsPerSet);
+}
+
+void MidiDriver_ADLIB_Multisource::writeKeyOff(uint8 oplChannel, bool forceWrite) {
+	_activeNotesMutex.lock();
+
+	// Rewrite the current Bx register value with the key on bit set to 0.
+	writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + determineChannelRegisterOffset(oplChannel),
+		(_activeNotes[oplChannel].oplFrequency >> 8) & OPL_MASK_FNUMHIGH_BLOCK, forceWrite);
+	// Update the active note data.
+	_activeNotes[oplChannel].noteActive = false;
+	_activeNotes[oplChannel].noteSustained = false;
+	// Register the current note counter value when turning off a note.
+	_activeNotes[oplChannel].noteCounterValue = _noteCounter;
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::writeVolume(uint8 oplChannel, uint8 operatorNum) {
+	// Calculate operator volume.
+	uint16 registerOffset = determineOperatorRegisterOffset(
+		oplChannel, operatorNum, _activeNotes[oplChannel].instrumentDef->fourOperator);
+	const OplInstrumentOperatorDefinition &operatorDef =
+		_activeNotes[oplChannel].instrumentDef->getOperatorDefinition(operatorNum);
+	uint8 level = calculateVolume(_activeNotes[oplChannel].channel,
+		_activeNotes[oplChannel].source, _activeNotes[oplChannel].velocity,
+		*_activeNotes[oplChannel].instrumentDef, operatorNum);
+
+	// Add key scaling level from the operator definition to the calculated
+	// level.
+	writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, level | (operatorDef.level & ~OPL_MASK_LEVEL));
+}
+
+void MidiDriver_ADLIB_Multisource::writePanning(uint8 oplChannel) {
+	// Calculate channel panning.
+	uint16 registerOffset = determineChannelRegisterOffset(
+		oplChannel, _activeNotes[oplChannel].instrumentDef->fourOperator);
+	uint8 panning = calculatePanning(_activeNotes[oplChannel].channel,
+		_activeNotes[oplChannel].source);
+
+	// Add connection and feedback from the instrument definition to the
+	// calculated panning.
+	writeRegister(OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING + registerOffset,
+		panning | (_activeNotes[oplChannel].instrumentDef->connectionFeedback0 & ~OPL_MASK_PANNING));
+	if (_activeNotes[oplChannel].instrumentDef->fourOperator)
+		// TODO Not sure if panning is necessary here.
+		writeRegister(OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING + registerOffset + 3,
+			panning | (_activeNotes[oplChannel].instrumentDef->connectionFeedback1 & ~OPL_MASK_PANNING));
+}
+
+void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel) {
+	_activeNotesMutex.lock();
+
+	// Calculate the frequency.
+	uint16 channelOffset = determineChannelRegisterOffset(
+		oplChannel, _activeNotes[oplChannel].instrumentDef->fourOperator);
+	uint16 frequency = calculateFrequency(_activeNotes[oplChannel].channel,
+		_activeNotes[oplChannel].source, _activeNotes[oplChannel].oplNote);
+	_activeNotes[oplChannel].oplFrequency = frequency;
+
+	// Write the low 8 frequency bits.
+	writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
+	// Write the high 2 frequency bits and block and add the key on bit.
+	writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset, (frequency >> 8) | OPL_MASK_KEYON);
+
+	_activeNotesMutex.unlock();
+}
+
+void MidiDriver_ADLIB_Multisource::writeRegister(uint16 reg, uint8 value, bool forceWrite) {
+	//debug("Writing register %X %X", reg, value);
+
+	// Write the value to the register if forceWrite is specified or if the
+	// new register value is different from the current value.
+	if (forceWrite || _shadowRegisters[reg] != value) {
+		_shadowRegisters[reg] = value;
+		_opl->writeReg(reg, value);
+	}
+}
diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h
new file mode 100644
index 0000000000..6538ed5e09
--- /dev/null
+++ b/audio/adlib_ms.h
@@ -0,0 +1,1049 @@
+/* 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_ADLIB_MS_H
+#define AUDIO_ADLIB_MS_H
+
+#include "audio/mididrv_ms.h"
+#include "audio/fmopl.h"
+
+/**
+ * Data for one operator of an OPL instrument definition.
+ */
+struct OplInstrumentOperatorDefinition {
+	/**
+	 * 2x register: frequency multiplier, key scaling rate, envelope gain type,
+	 * vibrato and modulation.
+	 */
+	uint8 freqMultMisc;
+	/**
+	 * 4x register: level and key scaling level.
+	 */
+	uint8 level;
+	/**
+	 * 6x register: decay and attack.
+	 */
+	uint8 decayAttack;
+	/**
+	 * 8x register: release and sustain.
+	 */
+	uint8 releaseSustain;
+	/**
+	 * Ex register: waveform select.
+	 */
+	uint8 waveformSelect;
+};
+
+/**
+ * Instrument definition for an OPL2 or OPL3 chip. Contains the data for all
+ * registers belonging to an OPL channel, except the Ax and Bx registers (these
+ * determine the frequency and are derived from the note played).
+ */
+struct OplInstrumentDefinition {
+	/**
+	 * Indicates if this instrument uses 2 or 4 operators.
+	 */
+	bool fourOperator;
+
+	/**
+	 * Operator data. 2 operator instruments use operators 0 and 1 only.
+	 */
+	OplInstrumentOperatorDefinition operator0;
+	OplInstrumentOperatorDefinition operator1;
+	OplInstrumentOperatorDefinition operator2;
+	OplInstrumentOperatorDefinition operator3;
+
+	/**
+	 * Cx register: connection and feedback.
+	 * Note: panning is determined by a MIDI controller and not part of the
+	 * instrument definition.
+	 */
+	uint8 connectionFeedback0;
+	/**
+	 * Second Cx register (used by 4 operator instruments).
+	 */
+	uint8 connectionFeedback1;
+
+	/**
+	 * Notes played on a MIDI rhythm channel indicate which rhythm instrument
+	 * should be played, not which note should be played. This field indicates
+	 * the pitch (MIDI note) which should be used to play this rhythm
+	 * instrument. Not used for melodic instruments.
+	 */
+	uint8 rhythmNote;
+
+	/**
+	 * Check if this instrument definition contains any data.
+	 *
+	 * @return True if this instrument is empty; false otherwise.
+	 */
+	bool isEmpty();
+	/**
+	 * Returns the number of operators used by this instrument definition.
+	 *
+	 * @return The number of operators (2 or 4).
+	 */
+	uint8 getNumberOfOperators();
+	/**
+	 * Returns the definition data for the operator with the specified number.
+	 * Specify 0 or 1 for 2 operator instruments or 0-3 for 4 operator
+	 * instruments.
+	 * 
+	 * @param operatorNum The operator for which the data should be returned.
+	 * @return Pointer to the definition data for the specified operator.
+	 */
+	OplInstrumentOperatorDefinition &getOperatorDefinition(uint8 operatorNum);
+};
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * Data for one operator of an OPL instrument definition in the AdLib BNK
+ * format.
+ */
+struct AdLibBnkInstrumentOperatorDefinition {
+	/**
+	 * Individual fields for each setting in the 2x-8x registers.
+	 * Note that waveform select is not part of the operator data in this
+	 * format; it is included in the instrument data as a separate field.
+	 */
+	uint8 keyScalingLevel;
+	uint8 frequencyMultiplier;
+	uint8 feedback; // ignored for operator 1
+	uint8 attack;
+	uint8 sustain;
+	uint8 envelopeGainType; // 0x00: not sustained, >= 0x01: sustained
+	uint8 decay;
+	uint8 release;
+	uint8 level;
+	uint8 amplitudeModulation; // 0x00: off, >= 0x01: on
+	uint8 vibrato; // 0x00: off, >= 0x01: on
+	uint8 keyScalingRate; // 0x00: low, >= 0x01: high
+	uint8 connection; // 0x00: additive, >= 0x01: FM; ignored for operator 1
+
+	/**
+	 * Copies the data in this AdLib BNK operator definition to the specified
+	 * OplInstrumentOperatorDefinition struct.
+	 * 
+	 * @param operatorDef The operator definition to which the data should be
+	 * copied.
+	 * @param waveformSelect The value of the waveform select parameter for
+	 * this operator.
+	 */
+	void toOplInstrumentOperatorDefinition(OplInstrumentOperatorDefinition &operatorDef, uint8 waveformSelect);
+} PACKED_STRUCT;
+
+/**
+ * Instrument definition for an OPL2 chip in the format used by the AdLib BNK
+ * instrument bank file format.
+ */
+struct AdLibBnkInstrumentDefinition {
+	/**
+	 * The type of instrument (0x00: melodic, 0x01: rhythm).
+	 */
+	uint8 instrumentType;
+	/**
+	 * TODO Unclear what this represents; might be the same as rhythmNote.
+	 */
+	uint8 rhythmVoiceNumber;
+
+	/**
+	 * Operator data.
+	 */
+	AdLibBnkInstrumentOperatorDefinition operator0;
+	AdLibBnkInstrumentOperatorDefinition operator1;
+
+	/**
+	 * Waveform select parameter for each operator.
+	 */
+	uint8 waveformSelect0;
+	uint8 waveformSelect1;
+
+	/**
+	 * Copies the data in this AdLib BNK instrument definition to the specified
+	 * OplInstrumentDefinition struct.
+	 * 
+	 * @param instrumentDef The instrument definition to which the data should
+	 * be copied.
+	 */
+	void toOplInstrumentDefinition(OplInstrumentDefinition &instrumentDef);
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/**
+ * MIDI driver for AdLib / OPL2 and OPL3 emulators and devices with support for
+ * multiple simultaneous sources of MIDI data.
+ *
+ * This driver converts MIDI events to OPL chip register writes. When opened it
+ * will initialize an OPL emulator or device using the specified OPL type. It
+ * tracks the MIDI state of each source separately to avoid conflicts.
+ * The default behavior of the driver plays General MIDI data with the same
+ * output as the SoudBlaster 16 Windows 95 driver. It can be subclassed and
+ * customized to match the specific behavior of a game.
+ *
+ * Customization
+ *
+ * Depending on the platform and the type of music data the game uses, you can
+ * customize the driver to match this behavior:
+ * - Windows: If the game uses the standard Windows APIs to play General MIDI
+ *   data, the default behavior of the driver should give identical output.
+ * - DOS, General MIDI: The default behavior of the driver should give you a
+ *   decent starting point, but because there is no standard way to handle GM
+ *   on OPL chips in DOS, it is probably not accurate. The instruments used by
+ *   the game can be set in the _instrumentBank and _rhythmBank fields.
+ *   You can subclass the driver to override more behavior, such as the
+ *   calculateFrequency, calculatePitchBend, calculateUnscaledVolume and
+ *   allocateOplChannel functions.
+ * - DOS, other type of MIDI: Additionally, you will need to override the
+ *   functions that handle the various MIDI events and controllers when they do
+ *   not match the General MIDI standard. You can override determineInstrument
+ *   if the game uses some other way than instrument banks to set instruments.
+ * - DOS, does not use MIDI: Write new code to access the OPL registers
+ *   directly instead of using this driver.
+ * 
+ * TODO Dual OPL2 and 4 operator instrument support is unfinished.
+ */
+class MidiDriver_ADLIB_Multisource : public MidiDriver_Multisource {
+public:
+	/**
+	 * The available accuracy modes for frequency and volume calculation.
+	 */
+	enum AccuracyMode {
+		/**
+		 * Accurate to the behavior of the Windows 95 SB16 driver.
+		 */
+		ACCURACY_MODE_SB16_WIN95,
+		/**
+		 * Accurate to the General MIDI and MIDI specifications.
+		 */
+		ACCURACY_MODE_GM
+	};
+
+	/**
+	 * The available modes for OPL channel allocation.
+	 */
+	enum ChannelAllocationMode {
+		/**
+		 * Dynamic channel allocation (new OPL channel allocated to each note
+		 * played).
+		 */
+		ALLOCATION_MODE_DYNAMIC,
+		/**
+		 * Static channel allocation (fixed OPL channel allocated to each MIDI
+		 * channel).
+		 */
+		ALLOCATION_MODE_STATIC
+	};
+
+	/**
+	 * The available modes for the OPL note select setting.
+	 */
+	enum NoteSelectMode {
+		NOTE_SELECT_MODE_0,
+		NOTE_SELECT_MODE_1
+	};
+
+	/**
+	 * The available modes for the OPL modulation depth setting.
+	 */
+	enum ModulationDepth {
+		/**
+		 * Low modulation depth (1 dB).
+		 */
+		MODULATION_DEPTH_LOW,
+		/**
+		 * High modulation depth (4.8 dB).
+		 */
+		MODULATION_DEPTH_HIGH
+	};
+
+	/**
+	 * The available modes for the OPL vibrato depth setting.
+	 */
+	enum VibratoDepth {
+		/**
+		 * Low vibrato depth (7 %).
+		 */
+		VIBRATO_DEPTH_LOW,
+		/**
+		 * High vibrato depth (14 %).
+		 */
+		VIBRATO_DEPTH_HIGH
+	};
+
+	/**
+	 * The number of available channels on each OPL chip.
+	 */
+	static const uint8 OPL2_NUM_CHANNELS = 9;
+	static const uint8 OPL3_NUM_CHANNELS = 18;
+
+	/**
+	 * OPL test and timer registers.
+	 */
+	static const uint8 OPL_REGISTER_TEST = 0x01;
+	static const uint8 OPL_REGISTER_TIMER1 = 0x02;
+	static const uint8 OPL_REGISTER_TIMER2 = 0x03;
+	static const uint8 OPL_REGISTER_TIMERCONTROL = 0x04;
+
+	/**
+	 * OPL global setting registers.
+	 */
+	static const uint8 OPL_REGISTER_NOTESELECT_CSM = 0x08;
+	static const uint8 OPL_REGISTER_RHYTHM = 0xBD;
+
+	/**
+	 * OPL operator base registers.
+	 */
+	static const uint8 OPL_REGISTER_BASE_FREQMULT_MISC = 0x20;
+	static const uint8 OPL_REGISTER_BASE_LEVEL = 0x40;
+	static const uint8 OPL_REGISTER_BASE_DECAY_ATTACK = 0x60;
+	static const uint8 OPL_REGISTER_BASE_RELEASE_SUSTAIN = 0x80;
+	static const uint8 OPL_REGISTER_BASE_WAVEFORMSELECT = 0xE0;
+
+	/**
+	 * OPL channel base registers.
+	 */
+	static const uint8 OPL_REGISTER_BASE_FNUMLOW = 0xA0;
+	static const uint8 OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON = 0xB0;
+	static const uint8 OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING = 0xC0;
+
+	/**
+	 * OPL3-specific global setting registers.
+	 */
+	static const uint16 OPL3_REGISTER_CONNECTIONSELECT = 0x104;
+	static const uint16 OPL3_REGISTER_NEW = 0x105;
+
+	/**
+	 * Offset to the second register set (for dual OPL2 and OPL3).
+	 */
+	static const uint16 OPL_REGISTER_SET_2_OFFSET = 0x100;
+
+	/**
+	 * Bitmasks for various parameters in the OPL registers.
+	 */
+	static const uint8 OPL_MASK_LEVEL = 0x3F;
+	static const uint8 OPL_MASK_FNUMHIGH_BLOCK = 0x1F;
+	static const uint8 OPL_MASK_KEYON = 0x20;
+	static const uint8 OPL_MASK_PANNING = 0x30;
+
+	/**
+	 * Settings for the panning bits in the OPL Cx registers.
+	 */
+	static const uint8 OPL_PANNING_CENTER = 0x30;
+	static const uint8 OPL_PANNING_LEFT = 0x10;
+	static const uint8 OPL_PANNING_RIGHT = 0x20;
+
+	/**
+	 * The default melodic instrument definitions.
+	 */
+	static OplInstrumentDefinition OPL_INSTRUMENT_BANK[];
+	/**
+	 * The default rhythm instrument definitions.
+	 */
+	static OplInstrumentDefinition OPL_RHYTHM_BANK[];
+
+protected:
+	/**
+	 * Default setting for OPL channel volume (level).
+	 */
+	static const uint8 OPL_LEVEL_DEFAULT = 0x3F;
+
+	/**
+	 * The lowest MIDI panning controller value interpreted as left panning.
+	 */
+	static const uint8 OPL_MIDI_PANNING_LEFT_LIMIT = 0x2F;
+	/**
+	 * The highest MIDI panning controller value interpreted as right panning.
+	 */
+	static const uint8 OPL_MIDI_PANNING_RIGHT_LIMIT = 0x51;
+
+	/**
+	 * OPL frequency (F-num) value for each octave semitone. The values assume
+	 * octave 5.
+	 */
+	static const uint16 OPL_NOTE_FREQUENCIES[];
+	/**
+	 * OPL volume lookup array for a MIDI volume value shifted from 7 to 5 bits.
+	 */
+	static const uint8 OPL_VOLUME_LOOKUP[];
+
+
+	/**
+	 * Factor to convert a frequency in Hertz to the format used by the OPL
+	 * registers (F-num).
+	 */
+	static const float OPL_FREQUENCY_CONVERSION_FACTOR;
+
+	/**
+	 * Contains the current controller settings for a MIDI channel.
+	 */
+	struct MidiChannelControlData {
+		uint8 program;
+		uint8 channelPressure;
+		uint16 pitchBend; // 14 bit value; 0x2000 is neutral
+
+		uint8 modulation;
+		uint8 volume;
+		uint8 panning; // 0x40 is center
+		uint8 expression;
+		bool sustain;
+		uint16 rpn; // Two 7 bit values stored in 8 bits each
+
+		uint8 pitchBendSensitivity; // Semitones
+		uint8 pitchBendSensitivityCents;
+		uint16 masterTuningFine; // 14 bit value; 0x2000 is neutral
+		uint8 masterTuningCoarse; // Semitones; 0x40 is neutral
+
+		MidiChannelControlData();
+
+		/**
+		 * Initializes the controller settings to default values.
+		 */
+		void init();
+	};
+
+	/**
+	 * Contains information on the currently active note on an OPL channel.
+	 */
+	struct ActiveNote {
+		/**
+		 * True if a note is currently playing (including if it is sustained,
+		 * but not if it is in the "release" phase).
+		 */
+		bool noteActive;
+		/**
+		 * True if the currently playing note is sustained, i.e. note has been
+		 * turned off but is kept active due to the sustain controller.
+		 */
+		bool noteSustained;
+
+		/**
+		 * The MIDI note value as it appeared in the note on event.
+		 */
+		uint8 note;
+		/**
+		 * The MIDI velocity value of the note on event.
+		 */
+		uint8 velocity;
+		/**
+		 * The MIDI channel that played the current/last note (0xFF if no note
+		 * has been played since initialization).
+		 */
+		uint8 channel;
+		/**
+		 * The source that played the current/last note (0xFF if no note has
+		 * been played since initialization).
+		 */
+		uint8 source;
+
+		/**
+		 * The MIDI note value that is actually played. This is the same as the
+		 * note field for melodic instruments, but on the MIDI rhythm channel
+		 * the note indicates which rhythm instrument should be played instead
+		 * of the pitch. In that case this field is different
+		 * (@see determineInstrument).
+		 */
+		uint8 oplNote;
+		/**
+		 * The OPL frequency (F-num) and octave (block) (in Ax (low byte) and
+		 * Bx (high byte) register format) that was calculated to play the MIDI
+		 * note.
+		 */
+		uint16 oplFrequency;
+		/**
+		 * The value of the note counter when a note was last turned on or off
+		 * on this OPL channel.
+		 */
+		uint32 noteCounterValue;
+
+		/**
+		 * A unique identifier of the instrument that is used to play the note.
+		 * In the default implementation this is the MIDI program number for
+		 * melodic instruments and the rhythm channel note number + 0x80 for
+		 * rhythm instruments (@see determineInstrument).
+		 */
+		uint8 instrumentId;
+		/**
+		 * Pointer to the instrument definition used to play the note.
+		 */
+		OplInstrumentDefinition *instrumentDef;
+
+		/**
+		 * True if this OPL channel has been allocated to a MIDI channel.
+		 * Note that in the default driver implementation only the static
+		 * channel allocation algorithm uses this field.
+		 */
+		bool channelAllocated;
+
+		ActiveNote();
+
+		/**
+		 * Initializes the active note data to default values.
+		 */
+		void init();
+	};
+
+	/**
+	 * OPL instrument data for playing a note.
+	 */
+	struct InstrumentInfo {
+		/**
+		 * MIDI note value to use for playing this instrument
+		 * (@see ActiveNote.oplNote).
+		 */
+		uint8 oplNote;
+		/**
+		 * Pointer to the instrument definition.
+		 */
+		OplInstrumentDefinition *instrumentDef;
+		/**
+		 * Unique identifer for this instrument (@see ActiveNote.instrumentId).
+		 */
+		uint8 instrumentId;
+	};
+
+public:
+	/**
+	 * Checks if the specified type of OPL chip is supported by the OPL
+	 * emulator or hardware that is used.
+	 * 
+	 * @param oplType The type of OPL chip that should be detected.
+	 * @return True if the specified type of OPL chip is supported by the OPL
+	 * emulator/hardware; false otherwise.
+	 */
+	static bool detectOplType(OPL::Config::OplType oplType);
+
+	/**
+	 * Constructs a new AdLib multisource MIDI driver using the specified type
+	 * of OPL chip.
+	 * 
+	 * @param oplType The type of OPL chip that should be used.
+	 */
+	MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType);
+	~MidiDriver_ADLIB_Multisource();
+
+	/**
+	 * Prepares the driver for processing MIDI data and initializes the OPL
+	 * emulator or hardware.
+	 * 
+	 * @return 0 if the driver was opened successfully; a MidiDriver error code
+	 * otherwise.
+	 */
+	int open() override;
+	bool isOpen() const override;
+	void close() override;
+	uint32 property(int prop, uint32 param) override;
+	uint32 getBaseTempo() override;
+	/**
+	 * This driver does not use MidiChannel objects, so this function returns 0.
+	 * 
+	 * @return 0
+	 */
+	MidiChannel *allocateChannel() override;
+	/**
+	 * This driver does not use MidiChannel objects, so this function returns 0.
+	 * 
+	 * @return 0
+	 */
+	MidiChannel *getPercussionChannel() override;
+
+	void send(int8 source, uint32 b) override;
+	void sysEx(const byte *msg, uint16 length) override;
+	void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
+	void stopAllNotes(bool stopSustainedNotes = false) override;
+
+	void stopAllNotes(uint8 source, uint8 channel) override;
+	void deinitSource(uint8 source) override;
+
+protected:
+	void applySourceVolume(uint8 source) override;
+
+	/**
+	 * Initializes the OPL registers to their default values.
+	 */
+	virtual void initOpl();
+
+	/**
+	 * Processes a MIDI note off event.
+	 * 
+	 * @param channel The MIDI channel on which the note is active.
+	 * @param note The MIDI note that should be turned off.
+	 * @param velocity The release velocity (not implemented).
+	 * @param source The source sending the note off event.
+	 */
+	virtual void noteOff(uint8 channel, uint8 note, uint8 velocity, uint8 source);
+	/**
+	 * Processes a MIDI note on event.
+	 * 
+	 * @param channel The MIDI channel on which the note is played.
+	 * @param note The MIDI note that should be turned on.
+	 * @param velocity The MIDI velocity of the played note.
+	 * @param source The source sending the note on event.
+	 */
+	virtual void noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source);
+	/**
+	 * Processes a MIDI polyphonic aftertouch event.
+	 * Note: this event has no default implementation because it is not
+	 * implemented in the Win95 SB16 driver.
+	 * 
+	 * @param channel The MIDI channel on which the event is sent.
+	 * @param note The MIDI note on which aftertouch should be applied.
+	 * @param pressure The aftertouch amount that should be applied.
+	 * @param source The source sending the aftertouch event.
+	 */
+	virtual void polyAftertouch(uint8 channel, uint8 note, uint8 pressure, uint8 source);
+	/**
+	 * Processes a MIDI control change event. The individual controllers are
+	 * handled by separate functions (@see modulation etc.).
+	 * 
+	 * @param channel The MIDI channel on which the event is sent.
+	 * @param controller The MIDI controller whose value should be changed.
+	 * @param value The value that should be applied to the controller.
+	 * @param source The source sending the conrol change event.
+	 */
+	virtual void controlChange(uint8 channel, uint8 controller, uint8 value, uint8 source);
+	/**
+	 * Processes a MIDI program (instrument) change event.
+	 * 
+	 * @param channel The MIDI channel on which the instrument should be set.
+	 * @param program The instrument that should be set on the channel.
+	 * @param source The source sending the program change event.
+	 */
+	virtual void programChange(uint8 channel, uint8 program, uint8 source);
+	/**
+	 * Processes a MIDI channel aftertouch event.
+	 * Note: this event has no default implementation because it is not
+	 * implemented in the Win95 SB16 driver.
+	 * 
+	 * @param channel The MIDI channel on which aftertouch should be applied.
+	 * @param pressure The aftertouch amount that should be applied.
+	 * @param source The source sending the aftertouch event.
+	 */
+	virtual void channelAftertouch(uint8 channel, uint8 pressure, uint8 source);
+	/**
+	 * Processes a MIDI pitch bend event.
+	 * Note that MIDI pitch bend is a 14 bit value sent as 2 7 bit values, with
+	 * the LSB sent first.
+	 * 
+	 * @param channel The MIDI channel on which pitch bend should be applied.
+	 * @param pitchBendLsb The LSB of the pitch bend value.
+	 * @param pitchBendMsb The MSB of the pitch bend value.
+	 * @param source The source sending the pitch bend event.
+	 */
+	virtual void pitchBend(uint8 channel, uint8 pitchBendLsb, uint8 pitchBendMsb, uint8 source);
+
+	/**
+	 * Processes a MIDI modulation control change event.
+	 * Note: this event has no default implementation because it is not
+	 * implemented in the Win95 SB16 driver.
+	 * 
+	 * @param channel The MIDI channel on which modulation should be applied.
+	 * @param modulation The modulation amount that should be applied.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void modulation(uint8 channel, uint8 modulation, uint8 source);
+	/**
+	 * Processes a MIDI data entry control change event. This sets the MSB
+	 * and/or LSB of the currently selected registered parameter number.
+	 * Note that a MIDI data entry event contains either the MSB or LSB;
+	 * specify 0xFF for the other data byte to leave it unchanged.
+	 * RPNs pitch bend sensitivity, master tuning fine and coarse are supported
+	 * in accuracy mode GM only.
+	 * 
+	 * @param channel The MIDI channel on which the RPN data byte should be set.
+	 * @param dataMsb The MSB of the RPN data value; 0xFF to not set the MSB.
+	 * @param dataLsb The LSB of the RPN data value; 0xFF to not set the LSB.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void dataEntry(uint8 channel, uint8 dataMsb, uint8 dataLsb, uint8 source);
+	/**
+	 * Process a MIDI volume control change event.
+	 * 
+	 * @param channel The MIDI channel on which volume should be set.
+	 * @param volume The volume level that should be set.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void volume(uint8 channel, uint8 volume, uint8 source);
+	/**
+	 * Process a MIDI panning control change event.
+	 * Note that panning is not supported on an OPL2 chip because it has mono
+	 * output.
+	 * 
+	 * @param channel The MIDI channel on which panning should be set.
+	 * @param panning The panning value that should be set.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void panning(uint8 channel, uint8 panning, uint8 source);
+	/**
+	 * Process a MIDI expression control change event.
+	 * 
+	 * @param channel The MIDI channel on which expression should be set.
+	 * @param expression The expression value that should be set.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void expression(uint8 channel, uint8 expression, uint8 source);
+	/**
+	 * Process a MIDI sustain control change event.
+	 * 
+	 * @param channel The MIDI channel on which sustain should be set.
+	 * @param sustain The sustain value that should be set.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void sustain(uint8 channel, uint8 sustain, uint8 source);
+	/**
+	 * Process a MIDI registered parameter number control change event. This
+	 * sets the currently active RPN; subsequent data entry control change
+	 * events will set the value for the selected RPN.
+	 * Note that a MIDI PRN event contains either the MSB or LSB; specify 0xFF
+	 * for the other rpn byte to leave it unchanged.
+	 * RPNs pitch bend sensitivity, master tuning fine and coarse are supported
+	 * in accuracy mode GM only.
+	 * 
+	 * @param channel The MIDI channel on which the active PRN should be set.
+	 * @param rpnMsb The MSB of the RPN number; 0xFF to not set the MSB.
+	 * @param rpnLsb The LSB of the RPN number; 0xFF to not set the LSB.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void registeredParameterNumber(uint8 channel, uint8 rpnMsb, uint8 rpnLsb, uint8 source);
+	/**
+	 * Process a MIDI all sound off channel mode event.
+	 * Note that this should immediately stop all sound, but it is not possible
+	 * to abort the "release" phase of a note on an OPL chip. So this will
+	 * function like an all notes off event, except it will also stop sustained
+	 * notes.
+	 * 
+	 * @param channel The MIDI channel on which the all sound off channel mode
+	 * event is sent.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void allSoundOff(uint8 channel, uint8 source);
+	/**
+	 * Process a MIDI reset all controllers channel mode event. This will reset
+	 * the following controllers to their default values:
+	 * - modulation
+	 * - expression
+	 * - sustain
+	 * - active RPN
+	 * - pitch bend
+	 * - channel aftertouch
+	 * It should also reset polyphonic aftertouch, but this is not implemented.
+	 * 
+	 * @param channel The MIDI channel on which the reset all controllers
+	 * channel mode event is sent.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void resetAllControllers(uint8 channel, uint8 source);
+	/**
+	 * Process a MIDI all notes off channel mode event. This will turn off all
+	 * non-sustained notes or sustain all notes if the sustain controller is on.
+	 * 
+	 * @param channel The MIDI channel on which the all notes off channel mode
+	 * event is sent.
+	 * @param source The source sending the control change event.
+	 */
+	virtual void allNotesOff(uint8 channel, uint8 source);
+
+	/**
+	 * Recalculates and writes the frequencies of the active notes on the
+	 * specified MIDI channel and source.
+	 * 
+	 * @param channel The MIDI channel on which the note frequencies should be
+	 * recalculated.
+	 * @param source The source for which the note frequencies should be
+	 * recalculated.
+	 */
+	virtual void recalculateFrequencies(uint8 channel, uint8 source);
+	/**
+	 * Recalculates and writes the volumes of the active notes on the specified
+	 * MIDI channel and source. 0xFF can be specified to recalculate volumes of
+	 * notes on all MIDI channels and/or sources.
+	 * 
+	 * @param channel The MIDI channel on which the note volumes should be
+	 * recalculated; 0xFF to recalculate volumes for all channels.
+	 * @param source The source for which the note volumes should be
+	 * recalculated; 0xFF to recalculate volumes for all sources.
+	 */
+	virtual void recalculateVolumes(uint8 channel, uint8 source);
+
+	/**
+	 * Determines the instrument data necessary to play the specified note on
+	 * the specified MIDI channel and source. This will determine the
+	 * instrument definition to use, the note that should be played and an
+	 * instrument ID for use by the dynamic channel allocation algorithm.
+	 * 
+	 * @param channel The MIDI channel on which the note is played.
+	 * @param source The source playing the note.
+	 * @param note The MIDI note which is played.
+	 * @return The instrument data for playing the note, or an empty struct if
+	 * the note cannot be played.
+	 */
+	virtual InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note);
+	/**
+	 * Allocates an OPL channel to play a note on the specified MIDI channel
+	 * and source with the specified instrument ID. Allocation behavior depends
+	 * on the active channel allocation mode:
+	 * - Dynamic: allocates an unused channel, a channel playing a note using
+	 *   the same instrument or the channel playing the oldest note. This will
+	 *   always allocate a channel to play the note. This is the same behavior
+	 *   as the Win95 SB16 driver.
+	 * - Static: allocates an unused OPL channel and assigns it to the MIDI
+	 *   channel playing the note. All subsequent notes on this MIDI channel
+	 *   will be played using this OPL channel. If there are no free channels, 
+	 *   it will fail to allocate a channel. The MIDI data must play one note
+	 *   at a time on each channel and not use more MIDI channels than there
+	 *   are OPL channels for this algorithm to work properly.
+	 * 
+	 * @param channel The MIDI channel on which the note is played.
+	 * @param source The source playing the note.
+	 * @param instrumentId The ID of the instrument playing the note. Not used
+	 * by the static channel allocation mode.
+	 * @return The number of the allocated OPL channel; 0xFF if allocation
+	 * failed (not possible using the dynamic channel allocation mode).
+	 */
+	virtual uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId);
+	/**
+	 * Calculates the OPL frequency (F-num) and octave (block) to play the
+	 * specified note on the specified MIDI channel and source, taking into
+	 * account the MIDI controllers pitch bend and (on accuracy mode GM) pitch
+	 * bend sensitivity and master tuning. The result is returned in the format
+	 * of the Ax (low byte) and Bx (high byte) OPL registers.
+	 * Note that the MIDI note range exceeds the frequency range of an OPL
+	 * chip, so the highest MIDI notes will be shifted down one or two octaves.
+	 * The SB16 Win95 accuracy mode calculates the same frequencies as the
+	 * Windows 95 SB16 driver. The GM accuracy mode is more accurate, but the
+	 * calculations are more CPU intensive. This mode also supports pitch bend
+	 * sensitivity (which is fixed at 2 semitones in SB16 Win95 mode) and
+	 * master tuning.
+	 * 
+	 * @param channel The MIDI channel on which the note is played.
+	 * @param source The source playing the note.
+	 * @param note The MIDI note which is played.
+	 * @return The F-num and block to play the note on the OPL chip.
+	 */
+	virtual uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note);
+	/**
+	 * Calculates the pitch bend value to apply to the specified OPL frequency
+	 * (F-num) on the specified MIDI channel and source. If the accuracy mode
+	 * is GM, pitch bend sensitivity and master tuning settings are also
+	 * applied. The result is an adjustment which can be added to the OPL
+	 * frequency to get the pitch bent note.
+	 * 
+	 * @param channel The MIDI channel for which pitch bend should be
+	 * calculated.
+	 * @param source The source for which pitch bend should be calculated.
+	 * @param oplFrequency The OPL frequency for which pitch bend should be
+	 * calculated.
+	 * @return The calculated pitch bend (OPL frequency adjustment).
+	 */
+	virtual int32 calculatePitchBend(uint8 channel, uint8 source, uint16 oplFrequency);
+	/**
+	 * Calculates the volume for the specified operator of a note on the
+	 * specified MIDI channel and source, using the specified MIDI velocity and
+	 * instrument definition.
+	 * This function will check if the operator will need to have volume
+	 * applied to it or if the operator volume from the instrument definition
+	 * should be used without adjustment (this depends on the connection type).
+	 * If volume should be applied, unscaled volume is calculated
+	 * (@see calculateUnscaledVolume) and volume is scaled to source and user
+	 * volume. The volume is returned as an OPL 2x register volume (level)
+	 * value, i.e. 0 = maximum volume, 3F = minimum volume.
+	 * 
+	 * @param channel The MIDI channel on which the note is played.
+	 * @param source The source playing the note.
+	 * @param velocity The MIDI velocity of the note for which volume should be
+	 * calculated.
+	 * @param instrumentDef The instrument definition used to play the note.
+	 * @param operatorNum The number of the operator for which volume should be
+	 * calculated; 0-1 for 2 operator instruments, 0-3 for 4 operator
+	 * instruments.
+	 * @return The calculated operator volume (level).
+	 */
+	virtual uint8 calculateVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum);
+	/**
+	 * Calculates the unscaled volume for the specified operator of a note on
+	 * the specified MIDI channel and source, using the specified MIDI velocity
+	 * and instrument definition.
+	 * The SB16 Win95 accuracy mode calculates the same values as the Windows
+	 * 95 SB16 driver. The GM accuracy mode is more accurate to the volume
+	 * curve in the General MIDI specification and supports the expression
+	 * controller, but the calculation is more CPU intensive.
+	 * The volume is returned as an OPL 2x register volume (level) value,
+	 * i.e. 0 = maximum volume, 3F = minimum volume.
+	 * 
+	 * @param channel The MIDI channel on which the note is played.
+	 * @param source The source playing the note.
+	 * @param velocity The MIDI velocity of the note for which volume should be
+	 * calculated.
+	 * @param instrumentDef The instrument definition used to play the note.
+	 * @param operatorNum The number of the operator for which volume should be
+	 * calculated; 0-1 for 2 operator instruments, 0-3 for 4 operator
+	 * instruments.
+	 * @return The calculated unscaled operator volume (level).
+	 */
+	virtual uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum);
+	/**
+	 * Determines the panning that should be applied to notes played on the
+	 * specified MIDI channel and source.
+	 * This will convert the MIDI panning controller value to simple left,
+	 * right and center panning and return the result as a Cx register panning
+	 * value (in bits 4 and 5) for an OPL3 chip.
+	 * 
+	 * @param channel The MIDI channel for which panning should be calculated.
+	 * @param source The source for which panning should be calculated.
+	 * @return The calculated panning.
+	 */
+	virtual uint8 calculatePanning(uint8 channel, uint8 source);
+
+	/**
+	 * Determines the number of channels available on the OPL chip used by the
+	 * driver.
+	 * 
+	 * @return The number of OPL channels (9 or 18).
+	 */
+	uint8 determineNumOplChannels();
+	/**
+	 * Determines the offset from a base register for the specified operator of
+	 * the specified OPL channel.
+	 * Add the offset to the base register to get the correct register for this
+	 * operator and channel.
+	 * 
+	 * @param oplChannel The OPL channel for which to determine the offset.
+	 * @param operatorNum The operator for which to determine the offset;
+	 * 0-1 for 2 operator instruments, 0-3 for 4 operator instruments.
+	 * @param fourOperator True if the instrument used is a 4 operator
+	 * instrument; false if it is a 2 operator instrument.
+	 * @return The offset to the base register for this operator.
+	 */
+	uint16 determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, bool fourOperator = false);
+	/**
+	 * Determines the offset from a base register for the specified OPL channel.
+	 * Add the offset to the base register to get the correct register for this
+	 * channel.
+	 * 
+	 * @param oplChannel The OPL channel for which to determine the offset.
+	 * @param fourOperator True if the instrument used is a 4 operator
+	 * instrument; false if it is a 2 operator instrument.
+	 * @return The offset to the base register for this channel.
+	 */
+	uint16 determineChannelRegisterOffset(uint8 oplChannel, bool fourOperator = false);
+
+	/**
+	 * Sets the key on bit to false on the specified OPL channel and updates
+	 * _activeNotes with the new status.
+	 * Specify forceWrite to force the OPL register to be written, even if the
+	 * key on bit is already false according to the shadow registers.
+	 * 
+	 * @param oplChannel The OPL channel on which the key on bit should be set
+	 * to false.
+	 * @param forceWrite True if the OPL register write should be forced; false
+	 * otherwise.
+	 */
+	void writeKeyOff(uint8 oplChannel, bool forceWrite = false);
+	/**
+	 * Calculates the volume for the specified OPL channel and operator
+	 * (@see calculateVolume) and writes the new value to the OPL registers.
+	 * 
+	 * @param oplChannel The OPL channel for which volume should be calculated
+	 * and written.
+	 * @param operatorNum The operator for which volume should be calculated
+	 * and written.
+	 */
+	void writeVolume(uint8 oplChannel, uint8 operatorNum);
+	/**
+	 * Calculates the panning for the specified OPL channel
+	 * (@see calculatePanning) and writes the new value to the OPL registers.
+	 * 
+	 * @param oplChannel The OPL channel for which panning should be calculated
+	 * and written.
+	 */
+	void writePanning(uint8 oplChannel);
+	/**
+	 * Calculates the frequency for the active note on the specified OPL
+	 * channel (@see calculateFrequency) and writes the new value to the OPL
+	 * registers.
+	 * 
+	 * @param oplChannel The OPL channel for which the frequency should be
+	 * calculated and written.
+	 */
+	void writeFrequency(uint8 oplChannel);
+
+	/**
+	 * Writes the specified value to the specified OPL register.
+	 * If the specified value is the same as the current value according to the
+	 * shadow registers, the value is not written unless forceWrite is
+	 * specified.
+	 * 
+	 * @param reg The OPL register where the value should be written
+	 * (>= 0x100 for the second register set).
+	 * @param value The value to write in the register.
+	 * @param forceWrite True if the register write should be forced; false
+	 * otherwise.
+	 */
+	void writeRegister(uint16 reg, uint8 value, bool forceWrite = false);
+
+	// The type of OPL chip to use.
+	OPL::Config::OplType _oplType;
+	// The OPL emulator / hardware interface.
+	OPL::OPL *_opl;
+
+	// True if the driver has been successfully opened.
+	bool _isOpen;
+	// Controls the behavior for calculating note frequency and volume.
+	AccuracyMode _accuracyMode;
+	// Controls the OPL channel allocation behavior.
+	ChannelAllocationMode _allocationMode;
+
+	// OPL global settings. Set these, then call oplInit or open to apply the
+	// new values.
+	NoteSelectMode _noteSelect;
+	ModulationDepth _modulationDepth;
+	VibratoDepth _vibratoDepth;
+
+	// Pointer to the melodic instrument definitions.
+	OplInstrumentDefinition *_instrumentBank;
+	// Pointer to the rhythm instrument definitions.
+	OplInstrumentDefinition *_rhythmBank;
+	// The MIDI note value of the first rhythm instrument in the bank.
+	uint8 _rhythmBankFirstNote;
+	// The MIDI note value of the last rhythm instrument in the bank.
+	uint8 _rhythmBankLastNote;
+
+	// The current MIDI controller values for each MIDI channel and source.
+	MidiChannelControlData _controlData[MAXIMUM_SOURCES][MIDI_CHANNEL_COUNT];
+	// The active note data for each OPL channel.
+	ActiveNote _activeNotes[OPL3_NUM_CHANNELS];
+	// The OPL channel allocated to each MIDI channel and source; 0xFF if a
+	// MIDI channel has no OPL channel allocated. Note that this is only used by
+	// the static channel allocation mode.
+	uint8 _channelAllocations[MAXIMUM_SOURCES][MIDI_CHANNEL_COUNT];
+	// The amount of notes played since the driver was opened / reset.
+	uint32 _noteCounter;
+
+	// The values last written to each OPL register.
+	uint8 _shadowRegisters[0x200];
+
+	Common::Mutex _allocationMutex; // For operations on channel allocations
+	Common::Mutex _activeNotesMutex; // For operations on active notes
+};
+
+#endif
diff --git a/audio/mididrv.h b/audio/mididrv.h
index 05e2f840e4..49df1e8e9e 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -124,6 +124,7 @@ public:
 	static const byte MIDI_CONTROLLER_CHORUS = 0x5D;
 	static const byte MIDI_CONTROLLER_RPN_LSB = 0x64;
 	static const byte MIDI_CONTROLLER_RPN_MSB = 0x65;
+	static const byte MIDI_CONTROLLER_ALL_SOUND_OFF = 0x78;
 	static const byte MIDI_CONTROLLER_RESET_ALL_CONTROLLERS = 0x79;
 	static const byte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
 	static const byte MIDI_CONTROLLER_OMNI_ON = 0x7C;
@@ -131,11 +132,24 @@ public:
 	static const byte MIDI_CONTROLLER_MONO_ON = 0x7E;
 	static const byte MIDI_CONTROLLER_POLY_ON = 0x7F;
 
-	static const byte MIDI_RPN_PITCH_BEND_SENSITIVITY_MSB = 0x00;
-	static const byte MIDI_RPN_PITCH_BEND_SENSITIVITY_LSB = 0x00;
-	static const byte MIDI_RPN_NULL = 0x7F;
+	static const uint16 MIDI_RPN_PITCH_BEND_SENSITIVITY = 0x0000;
+	static const uint16 MIDI_RPN_MASTER_TUNING_FINE = 0x0001;
+	static const uint16 MIDI_RPN_MASTER_TUNING_COARSE = 0x0002;
+	static const uint16 MIDI_RPN_NULL = 0x7F7F;
+
+	static const uint8 MIDI_META_END_OF_TRACK = 0x2F;
+	static const uint8 MIDI_META_SEQUENCER = 0x7F;
 
 	static const uint16 MIDI_PITCH_BEND_DEFAULT = 0x2000;
+	static const uint8 MIDI_PANNING_DEFAULT = 0x40;
+	static const uint8 MIDI_EXPRESSION_DEFAULT = 0x7F;
+	static const uint16 MIDI_MASTER_TUNING_FINE_DEFAULT = 0x2000;
+	static const uint8 MIDI_MASTER_TUNING_COARSE_DEFAULT = 0x40;
+
+	static const uint8 GM_PITCH_BEND_SENSITIVITY_DEFAULT = 0x02;
+
+	static const uint8 GS_RHYTHM_FIRST_NOTE = 0x1B;
+	static const uint8 GS_RHYTHM_LAST_NOTE = 0x58;
 
 	MidiDriver_BASE();
 
@@ -210,8 +224,8 @@ public:
 
 	/**
 	 * Stops all currently active notes. Specify stopSustainedNotes if
-	 * the MIDI data makes use of the sustain controller to also stop
-	 * sustained notes.
+	 * the MIDI data makes use of the sustain controller to make sure
+	 * sustained notes are also stopped.
 	 *
 	 * Usually, the MIDI parser tracks active notes and terminates them
 	 * when playback is stopped. This method should be used as a backup
@@ -342,7 +356,7 @@ public:
 	};
 
 	enum {
-//		PROP_TIMEDIV = 1,
+		//		PROP_TIMEDIV = 1,
 		PROP_OLD_ADLIB = 2,
 		PROP_CHANNEL_MASK = 3,
 		// HACK: Not so nice, but our SCUMM AdLib code is in audio/
@@ -351,10 +365,10 @@ public:
 		 * Set this to enable or disable scaling of the MIDI channel
 		 * volume with the user volume settings (including setting it
 		 * to 0 when Mute All is selected). This is currently
-		 * implemented in the MT-32/GM drivers (regular and Miles AIL).
+		 * implemented in the MT-32/GM drivers (regular and Miles AIL)
+		 * and the regular AdLib driver.
 		 *
-		 * Default is enabled for the regular driver, and disabled for
-		 * the Miles AIL driver.
+		 * Default is disabled.
 		 */
 		PROP_USER_VOLUME_SCALING = 5,
 		/**
@@ -375,7 +389,39 @@ public:
 		 * Set this property before opening the driver, to make sure
 		 * that the default panning is set correctly.
 		 */
-		 PROP_MIDI_DATA_REVERSE_PANNING = 6
+		PROP_MIDI_DATA_REVERSE_PANNING = 6,
+		/**
+		 * Set this property to specify the behavior of the AdLib driver
+		 * for note frequency and volume calculation.
+		 *
+		 * ACCURACY_MODE_SB16_WIN95: volume and frequency calculation is
+		 * identical to the Windows 95 SB16 driver. This is the default.
+		 * ACCURACY_MODE_GM: volume and frequency calculation is closer
+		 * to the General MIDI and MIDI specifications. Volume is more
+		 * dynamic and frequencies are closer to actual note frequencies.
+		 * Calculations are more CPU intensive in this mode.
+		 */
+		PROP_OPL_ACCURACY_MODE = 7,
+		/**
+		 * Set this property to specify the OPL channel allocation
+		 * behavior of the AdLib driver.
+		 *
+		 * ALLOCATION_MODE_DYNAMIC: behavior is identical to the Windows
+		 * 95 SB16 driver. Whenever a note is played, an OPL channel is
+		 * allocated to play this note if:
+		 * 1. the channel is not playing a note, or
+		 * 2. the channel is playing a note of the same instrument, or
+		 * 3. the channel is playing the least recently started note.
+		 * This mode is the default.
+		 * ALLOCATION_MODE_STATIC: when a note is played, an OPL channel
+		 * is exclusively allocated to the MIDI channel and source
+		 * playing the note. All notes on this MIDI channel are played
+		 * using this OPL channel. If no OPL channels are unallocated,
+		 * allocation will fail and the note will not play. This mode
+		 * requires MIDI channels to be monophonic (i.e. only play one
+		 * note at a time).
+		 */
+		PROP_OPL_CHANNEL_ALLOCATION_MODE = 8
 	};
 
 	/**
@@ -400,12 +446,12 @@ public:
 
 	// HIGH-LEVEL SEMANTIC METHODS
 	virtual void setPitchBendRange(byte channel, uint range) {
-		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY_MSB);
-		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY_LSB);
+		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8);
+		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF);
 		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_DATA_ENTRY_MSB, range); // Semi-tones
 		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0); // Cents
-		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL);
-		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL);
+		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL >> 8);
+		send(MIDI_COMMAND_CONTROL_CHANGE | channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL & 0xFF);
 	}
 
 	/**
diff --git a/audio/module.mk b/audio/module.mk
index 127bed6151..e4ef93fa2f 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -2,6 +2,7 @@ MODULE := audio
 
 MODULE_OBJS := \
 	adlib.o \
+	adlib_ms.o \
 	audiostream.o \
 	fmopl.o \
 	mididrv.o \


Commit: f660238ab5e3d0abdbcb2804a346baf22c300504
    https://github.com/scummvm/scummvm/commit/f660238ab5e3d0abdbcb2804a346baf22c300504
Author: Coen Rampen (crampen at gmail.com)
Date: 2021-07-02T14:16:50+02:00

Commit Message:
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.

Changed paths:
  A audio/mididrv_ms.cpp
  A audio/mididrv_ms.h
    audio/miles.h
    audio/miles_midi.cpp
    audio/module.mk
    audio/mt32gm.cpp
    audio/mt32gm.h


diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp
new file mode 100644
index 0000000000..aac6d41264
--- /dev/null
+++ b/audio/mididrv_ms.cpp
@@ -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);
+}
diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h
new file mode 100644
index 0000000000..f014a84026
--- /dev/null
+++ b/audio/mididrv_ms.h
@@ -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
diff --git a/audio/miles.h b/audio/miles.h
index 73b3ba81a4..e3ebeb4917 100644
--- a/audio/miles.h
+++ b/audio/miles.h
@@ -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);
diff --git a/audio/miles_midi.cpp b/audio/miles_midi.cpp
index 23740b6768..85a44e57da 100644
--- a/audio/miles_midi.cpp
+++ b/audio/miles_midi.cpp
@@ -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;
diff --git a/audio/module.mk b/audio/module.mk
index e4ef93fa2f..24212552d3 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS := \
 	audiostream.o \
 	fmopl.o \
 	mididrv.o \
+	mididrv_ms.o \
 	midiparser_qt.o \
 	midiparser_smf.o \
 	midiparser_xmidi.o \
diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
index 53eb55c769..c5df819ba1 100644
--- a/audio/mt32gm.cpp
+++ b/audio/mt32gm.cpp
@@ -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;
-	}
-
-	_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]);
-			}
-		}
+		_channelMap[source][i] = 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::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::stopAllNotes(uint8 source, uint8 channel) {
+	_activeNotesMutex.lock();
 
-void MidiDriver_MT32GM::setSourceNeutralVolume(uint16 volume) {
-	for (int i = 0; i < MAXIMUM_SOURCES; ++i) {
-		setSourceNeutralVolume(i, volume);
+	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::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();
 
diff --git a/audio/mt32gm.h b/audio/mt32gm.h
index a3bf74490e..0bb471de64 100644
--- a/audio/mt32gm.h
+++ b/audio/mt32gm.h
@@ -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




More information about the Scummvm-git-logs mailing list