[Scummvm-git-logs] scummvm master -> 3dd5568cabbb789f271ae3897ac2eab5794370c0

NMIError noreply at scummvm.org
Mon May 9 16:03:07 UTC 2022


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

Summary:
734817597b AUDIO: Add rhythm mode support to AdLib MS driver
1f0e905160 AGOS: Add MIDI parsers for GMF and Windows
d37681a729 AGOS: Update Simon 1 AdLib drivers
7b3949aed6 AGOS: Improve Simon 1 MIDI code
1f19bf2d31 AGOS: Improve sound pausing and volume management
8c4c80d844 AUDIO: Add determineDataSize to XMIDI parser
f1e3c12895 AGOS: Improve Simon 2 MIDI code
b55b1da128 AUDIO: Add arbitrary MIDI instrument remapping
a72704764b AGOS: Fix Simon 2 intro first scene MT-32 music
00a907f524 MIDI: Fix parser auto loop skipping first event
6a9fc73962 AUDIO: Small MIDI driver enhancements and fixes
32ba866499 AUDIO: Move MIDI parser source handling to superclass
13da6f9cb5 AUDIO: Move null MidiDriver remove timer proc
a0ffa1e005 AGOS: Fix PC-98 MIDI driver not using Native MT-32
97745050c2 AGOS: Add option for Simon 1 DOS tempos to parsers
66bb075f1c AGOS: Add E2/WW AdLib and MT-32 SFX and enhancements
d0c56f0bbc AUDIO: Remove GMF support from SMF MidiParser
5e1905f5f6 AGOS: Waxworks OPL3 mode instrument attack fix
27e14762e7 AUDIO: Add Casio MT-540/CT-460/CSM-1 MIDI driver
5e95bdcbda AGOS: Elvira 1 - add support for Casio devices
b2cf9a580b AUDIO: Add AdLib MS driver callback frequency
3dd5568cab AGOS: Improve E2/WW AdLib SFX timing


Commit: 734817597b2244dfc142351dcbb9e67630958de2
    https://github.com/scummvm/scummvm/commit/734817597b2244dfc142351dcbb9e67630958de2
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:41+02:00

Commit Message:
AUDIO: Add rhythm mode support to AdLib MS driver

This adds support for the OPL rhythm mode to the AdLib multisource MIDI driver.
Some games (f.e. Simon 1) use this to generate the rhythm sounds of their music.

Changed paths:
    audio/adlib_ms.cpp
    audio/adlib_ms.h
    audio/mididrv.h
    engines/lure/sound.cpp


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index 02bc573b4a2..4c3002b0155 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -23,19 +23,31 @@
 
 #include "common/debug.h"
 
+bool OplInstrumentOperatorDefinition::isEmpty() {
+	return freqMultMisc == 0 && level == 0 && decayAttack == 0 &&
+		   releaseSustain == 0 && waveformSelect == 0;
+}
+
 bool OplInstrumentDefinition::isEmpty() {
-	return operator0.freqMultMisc == 0 && operator0.level == 0 && operator0.decayAttack == 0 &&
-		operator0.releaseSustain == 0 && operator0.waveformSelect == 0 &&
-		operator1.freqMultMisc == 0 && operator1.level == 0 && operator1.decayAttack == 0 &&
-		operator1.releaseSustain == 0 && operator1.waveformSelect == 0 && connectionFeedback0 == 0 &&
-		(!fourOperator || (operator2.freqMultMisc == 0 && operator2.level == 0 && operator2.decayAttack == 0 &&
-			operator2.releaseSustain == 0 && operator2.waveformSelect == 0 &&
-			operator3.freqMultMisc == 0 && operator3.level == 0 && operator3.decayAttack == 0 &&
-			operator3.releaseSustain == 0 && operator3.waveformSelect == 0 && connectionFeedback1 == 0));
+	if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
+		return operator0.isEmpty() &&
+			(rhythmType != RHYTHM_TYPE_BASS_DRUM || operator1.isEmpty());
+	} else if (!fourOperator) {
+		return operator0.isEmpty() && operator1.isEmpty();
+	} else {
+		return operator0.isEmpty() && operator1.isEmpty() &&
+			operator2.isEmpty() && operator3.isEmpty();
+	}
 }
 
 uint8 OplInstrumentDefinition::getNumberOfOperators() {
-	return fourOperator ? 4 : 2;
+	if (rhythmType == RHYTHM_TYPE_UNDEFINED) {
+		return fourOperator ? 4 : 2;
+	} else {
+		// The bass drum rhythm instrument uses 2 operators; the others use
+		// only 1.
+		return rhythmType == RHYTHM_TYPE_BASS_DRUM ? 2 : 1;
+	}
 }
 
 OplInstrumentOperatorDefinition &OplInstrumentDefinition::getOperatorDefinition(uint8 operatorNum) {
@@ -78,238 +90,250 @@ void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefini
 	
 	// TODO Figure out if this is the same as rhythmVoiceNumber
 	instrumentDef.rhythmNote = 0;
+	instrumentDef.rhythmType = RHYTHM_TYPE_UNDEFINED;
 }
 
 // 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 }
+	// 0x00
+	{ false, { 0x01, 0x8F, 0xF2, 0xF4, 0x00 }, { 0x01, 0x06, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x4B, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x49, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x81, 0x12, 0xF2, 0xF7, 0x00 }, { 0x41, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x57, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x93, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x80, 0xA1, 0xF2, 0x00 }, { 0x16, 0x0E, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x92, 0xC2, 0xF8, 0x00 }, { 0x01, 0x00, 0xC2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x08
+	{ false, { 0x0C, 0x5C, 0xF6, 0xF4, 0x00 }, { 0x81, 0x00, 0xF3, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x07, 0x97, 0xF3, 0xF2, 0x00 }, { 0x11, 0x80, 0xF2, 0xF1, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x17, 0x21, 0x54, 0xF4, 0x00 }, { 0x01, 0x00, 0xF4, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x98, 0x62, 0xF3, 0xF6, 0x00 }, { 0x81, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x18, 0x23, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xE7, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x15, 0x91, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xF6, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x45, 0x59, 0xD3, 0xF3, 0x00 }, { 0x81, 0x80, 0xA3, 0xF3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x03, 0x49, 0x75, 0xF5, 0x01 }, { 0x81, 0x80, 0xB5, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x10
+	{ false, { 0x71, 0x92, 0xF6, 0x14, 0x00 }, { 0x31, 0x00, 0xF1, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x72, 0x14, 0xC7, 0x58, 0x00 }, { 0x30, 0x00, 0xC7, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x70, 0x44, 0xAA, 0x18, 0x00 }, { 0xB1, 0x00, 0x8A, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x23, 0x93, 0x97, 0x23, 0x01 }, { 0xB1, 0x00, 0x55, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x13, 0x97, 0x04, 0x01 }, { 0xB1, 0x80, 0x55, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x24, 0x48, 0x98, 0x2A, 0x01 }, { 0xB1, 0x00, 0x46, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x13, 0x91, 0x06, 0x01 }, { 0x21, 0x00, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x13, 0x71, 0x06, 0x00 }, { 0xA1, 0x89, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x18
+	{ false, { 0x02, 0x9C, 0xF3, 0x94, 0x01 }, { 0x41, 0x80, 0xF3, 0xC8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x03, 0x54, 0xF3, 0x9A, 0x01 }, { 0x11, 0x00, 0xF1, 0xE7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x23, 0x5F, 0xF1, 0x3A, 0x00 }, { 0x21, 0x00, 0xF2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x03, 0x87, 0xF6, 0x22, 0x01 }, { 0x21, 0x80, 0xF3, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x03, 0x47, 0xF9, 0x54, 0x00 }, { 0x21, 0x00, 0xF6, 0x3A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x23, 0x4A, 0x91, 0x41, 0x01 }, { 0x21, 0x05, 0x84, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x23, 0x4A, 0x95, 0x19, 0x01 }, { 0x21, 0x00, 0x94, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x09, 0xA1, 0x20, 0x4F, 0x00 }, { 0x84, 0x80, 0xD1, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x20
+	{ false, { 0x21, 0x1E, 0x94, 0x06, 0x00 }, { 0xA2, 0x00, 0xC3, 0xA6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x8D, 0xF1, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x5B, 0x51, 0x28, 0x00 }, { 0x32, 0x00, 0x71, 0x48, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x8B, 0xA1, 0x9A, 0x00 }, { 0x21, 0x40, 0xF2, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x8B, 0xA2, 0x16, 0x00 }, { 0x21, 0x08, 0xA1, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x8B, 0xF4, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x28
+	{ false, { 0x31, 0x15, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x56, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x16, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x66, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x71, 0x49, 0xD1, 0x1C, 0x01 }, { 0x31, 0x00, 0x61, 0x0C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x4D, 0x71, 0x12, 0x01 }, { 0x23, 0x80, 0x72, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xF1, 0x40, 0xF1, 0x21, 0x01 }, { 0xE1, 0x00, 0x6F, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x02, 0x1A, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0x85, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x02, 0x1D, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0xF3, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x10, 0x41, 0xF5, 0x05, 0x01 }, { 0x11, 0x00, 0xF2, 0xC3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x30
+	{ false, { 0x21, 0x9B, 0xB1, 0x25, 0x01 }, { 0xA2, 0x01, 0x72, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x98, 0x7F, 0x03, 0x01 }, { 0x21, 0x00, 0x3F, 0x07, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x93, 0xC1, 0x12, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x18, 0xC1, 0x22, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x5B, 0xF4, 0x15, 0x00 }, { 0x72, 0x83, 0x8A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x90, 0x74, 0x39, 0x00 }, { 0x61, 0x00, 0x71, 0x67, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x71, 0x57, 0x54, 0x05, 0x00 }, { 0x72, 0x00, 0x7A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x90, 0x00, 0x54, 0x63, 0x00 }, { 0x41, 0x00, 0xA5, 0x45, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x38
+	{ false, { 0x21, 0x92, 0x85, 0x17, 0x00 }, { 0x21, 0x01, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x94, 0x75, 0x17, 0x00 }, { 0x21, 0x05, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x94, 0x76, 0x15, 0x00 }, { 0x61, 0x00, 0x82, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x43, 0x9E, 0x17, 0x01 }, { 0x21, 0x00, 0x62, 0x2C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x9B, 0x61, 0x6A, 0x00 }, { 0x21, 0x00, 0x7F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x8A, 0x75, 0x1F, 0x00 }, { 0x22, 0x06, 0x74, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x86, 0x72, 0x55, 0x01 }, { 0x21, 0x83, 0x71, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x4D, 0x54, 0x3C, 0x00 }, { 0x21, 0x00, 0xA6, 0x1C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x40
+	{ false, { 0x31, 0x8F, 0x93, 0x02, 0x01 }, { 0x61, 0x00, 0x72, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x8E, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x72, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x91, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x82, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x8E, 0x93, 0x0F, 0x01 }, { 0x61, 0x00, 0x72, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x4B, 0xAA, 0x16, 0x01 }, { 0x21, 0x00, 0x8F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x90, 0x7E, 0x17, 0x01 }, { 0x21, 0x00, 0x8B, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x81, 0x75, 0x19, 0x01 }, { 0x32, 0x00, 0x61, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x32, 0x90, 0x9B, 0x21, 0x00 }, { 0x21, 0x00, 0x72, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x48
+	{ false, { 0xE1, 0x1F, 0x85, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xE1, 0x46, 0x88, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x9C, 0x75, 0x1F, 0x00 }, { 0x21, 0x00, 0x75, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x8B, 0x84, 0x58, 0x00 }, { 0x21, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xE1, 0x4C, 0x66, 0x56, 0x00 }, { 0xA1, 0x00, 0x65, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x62, 0xCB, 0x76, 0x46, 0x00 }, { 0xA1, 0x00, 0x55, 0x36, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x62, 0x99, 0x57, 0x07, 0x00 }, { 0xA1, 0x00, 0x56, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x62, 0x93, 0x77, 0x07, 0x00 }, { 0xA1, 0x00, 0x76, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x50
+	{ false, { 0x22, 0x59, 0xFF, 0x03, 0x02 }, { 0x21, 0x00, 0xFF, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x0E, 0xFF, 0x0F, 0x01 }, { 0x21, 0x00, 0xFF, 0x0F, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x22, 0x46, 0x86, 0x55, 0x00 }, { 0x21, 0x80, 0x64, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x45, 0x66, 0x12, 0x00 }, { 0xA1, 0x00, 0x96, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x8B, 0x92, 0x2A, 0x01 }, { 0x22, 0x00, 0x91, 0x2A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA2, 0x9E, 0xDF, 0x05, 0x00 }, { 0x61, 0x40, 0x6F, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x20, 0x1A, 0xEF, 0x01, 0x00 }, { 0x60, 0x00, 0x8F, 0x06, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x8F, 0xF1, 0x29, 0x00 }, { 0x21, 0x80, 0xF4, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x58
+	{ false, { 0x77, 0xA5, 0x53, 0x94, 0x00 }, { 0xA1, 0x00, 0xA0, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x1F, 0xA8, 0x11, 0x00 }, { 0xB1, 0x80, 0x25, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x17, 0x91, 0x34, 0x00 }, { 0x61, 0x00, 0x55, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x71, 0x5D, 0x54, 0x01, 0x00 }, { 0x72, 0x00, 0x6A, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x97, 0x21, 0x43, 0x00 }, { 0xA2, 0x00, 0x42, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x1C, 0xA1, 0x77, 0x01 }, { 0x21, 0x00, 0x31, 0x47, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x89, 0x11, 0x33, 0x00 }, { 0x61, 0x03, 0x42, 0x25, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA1, 0x15, 0x11, 0x47, 0x01 }, { 0x21, 0x00, 0xCF, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x60
+	{ false, { 0x3A, 0xCE, 0xF8, 0xF6, 0x00 }, { 0x51, 0x00, 0x86, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x15, 0x21, 0x23, 0x01 }, { 0x21, 0x00, 0x41, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x06, 0x5B, 0x74, 0x95, 0x00 }, { 0x01, 0x00, 0xA5, 0x72, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x22, 0x92, 0xB1, 0x81, 0x00 }, { 0x61, 0x83, 0xF2, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x41, 0x4D, 0xF1, 0x51, 0x01 }, { 0x42, 0x00, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x94, 0x11, 0x51, 0x01 }, { 0xA3, 0x80, 0x11, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x61, 0x8C, 0x11, 0x31, 0x00 }, { 0xA1, 0x80, 0x1D, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xA4, 0x4C, 0xF3, 0x73, 0x01 }, { 0x61, 0x00, 0x81, 0x23, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x68
+	{ false, { 0x02, 0x85, 0xD2, 0x53, 0x00 }, { 0x07, 0x03, 0xF2, 0xF6, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x11, 0x0C, 0xA3, 0x11, 0x01 }, { 0x13, 0x80, 0xA2, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x11, 0x06, 0xF6, 0x41, 0x01 }, { 0x11, 0x00, 0xF2, 0xE6, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x93, 0x91, 0xD4, 0x32, 0x00 }, { 0x91, 0x00, 0xEB, 0x11, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x04, 0x4F, 0xFA, 0x56, 0x00 }, { 0x01, 0x00, 0xC2, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x21, 0x49, 0x7C, 0x20, 0x00 }, { 0x22, 0x00, 0x6F, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x31, 0x85, 0xDD, 0x33, 0x01 }, { 0x21, 0x00, 0x56, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x20, 0x04, 0xDA, 0x05, 0x02 }, { 0x21, 0x81, 0x8F, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x70
+	{ false, { 0x05, 0x6A, 0xF1, 0xE5, 0x00 }, { 0x03, 0x80, 0xC3, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x07, 0x15, 0xEC, 0x26, 0x00 }, { 0x02, 0x00, 0xF8, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x05, 0x9D, 0x67, 0x35, 0x00 }, { 0x01, 0x00, 0xDF, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x18, 0x96, 0xFA, 0x28, 0x00 }, { 0x12, 0x00, 0xF8, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x10, 0x86, 0xA8, 0x07, 0x00 }, { 0x00, 0x03, 0xFA, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x11, 0x41, 0xF8, 0x47, 0x02 }, { 0x10, 0x03, 0xF3, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x8E, 0xF1, 0x06, 0x02 }, { 0x10, 0x00, 0xF3, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0x1F, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0xFF, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x78
+	{ false, { 0x06, 0x80, 0xF8, 0x24, 0x00 }, { 0x03, 0x88, 0x56, 0x84, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0xF8, 0x00, 0x00 }, { 0xD0, 0x05, 0x34, 0x04, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xD5, 0x95, 0x37, 0xA3, 0x00 }, { 0xDA, 0x40, 0x56, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x35, 0x5C, 0xB2, 0x61, 0x02 }, { 0x14, 0x08, 0xF4, 0x15, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x4F, 0xF5, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x26, 0x00, 0xFF, 0x01, 0x00 }, { 0xE4, 0x00, 0x12, 0x16, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xF3, 0xF0, 0x00 }, { 0x00, 0x00, 0xF6, 0xC9, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }
 };
 
 // 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 },
+	// 0x1B
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	// 0x20
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
 
 	// 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 },
+	// 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, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x02, 0x07, 0xF9, 0xFF, 0x00 }, { 0x11, 0x00, 0xF8, 0xFF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x34, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x02, 0xFF, 0x07, 0x00 }, { 0x01, 0x00, 0xFF, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x3A, RHYTHM_TYPE_UNDEFINED },
+	// 0x28
+	{ false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x2F, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x00, 0xFB, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x31, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x05, 0x7B, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x33, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0C, 0x00, 0xF6, 0x02, 0x00 }, { 0x12, 0x00, 0xCB, 0x43, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x36, RHYTHM_TYPE_UNDEFINED },
+	// 0x30
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x39, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x48, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0xF5, 0x30, 0x00 }, { 0xD0, 0x0A, 0x9F, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x0A, 0xE4, 0xE4, 0x03 }, { 0x07, 0x5D, 0xF5, 0xE5, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x24, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x02, 0x03, 0xB4, 0x04, 0x00 }, { 0x05, 0x0A, 0x97, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x4E, 0x00, 0xF6, 0x00, 0x00 }, { 0x9E, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED },
+	// 0x38
+	{ false, { 0x11, 0x45, 0xF8, 0x37, 0x02 }, { 0x10, 0x08, 0xF3, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x53, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x80, 0x00, 0xFF, 0x03, 0x03 }, { 0x10, 0x0D, 0xFF, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x18, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4D, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x06, 0x0B, 0xF5, 0x0C, 0x00 }, { 0x02, 0x00, 0xF5, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x00, 0xFA, 0xBF, 0x00 }, { 0x02, 0x00, 0xC8, 0x97, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x37, 0x00, 0x41, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x51, 0xFA, 0x87, 0x00 }, { 0x01, 0x00, 0xFA, 0xB7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3B, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x54, 0xFA, 0x8D, 0x00 }, { 0x02, 0x00, 0xF8, 0xB8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x33, RHYTHM_TYPE_UNDEFINED },
+	// 0x40
+	{ false, { 0x01, 0x59, 0xFA, 0x88, 0x00 }, { 0x02, 0x00, 0xF8, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x2D, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x01, 0x00, 0xF9, 0x0A, 0x03 }, { 0x00, 0x00, 0xFA, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x80, 0xF9, 0x89, 0x03 }, { 0x00, 0x00, 0xF6, 0x6C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x03, 0x80, 0xF8, 0x88, 0x03 }, { 0x0C, 0x08, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x3A, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x03, 0x85, 0xF8, 0x88, 0x03 }, { 0x0C, 0x00, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x35, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x40, 0x76, 0x4F, 0x00 }, { 0x00, 0x08, 0x77, 0x18, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x40, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x0E, 0x40, 0xC8, 0x49, 0x00 }, { 0x03, 0x00, 0x9B, 0x69, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0xD7, 0xDC, 0xAD, 0x05, 0x03 }, { 0xC7, 0x00, 0x8D, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D, RHYTHM_TYPE_UNDEFINED },
+	// 0x48
+	{ false, { 0xD7, 0xDC, 0xA8, 0x04, 0x03 }, { 0xC7, 0x00, 0x88, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x80, 0x00, 0xF6, 0x06, 0x03 }, { 0x11, 0x00, 0x67, 0x17, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x80, 0x00, 0xF5, 0x05, 0x02 }, { 0x11, 0x09, 0x46, 0x16, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x06, 0x3F, 0x00, 0xF4, 0x00 }, { 0x15, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x31, 0x00, 0x45, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x06, 0x3F, 0x00, 0xF4, 0x03 }, { 0x12, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x44, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3F, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4A, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
+	// 0x50
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x50, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x40, RHYTHM_TYPE_UNDEFINED },
 	// 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 }
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x45, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x49, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4B, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x44, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x35, RHYTHM_TYPE_UNDEFINED },
+	// 0x58
+	{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }
 	// GS percussion end
 };
 
+// Rhythm mode uses OPL channels 6, 7 and 8. The remaining channels are
+// available for melodic instruments.
+uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL2[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
+uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL2_RHYTHM[] = { 0, 1, 2, 3, 4, 5 };
+uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL3[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL3_RHYTHM[] = { 0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+
+const uint8 MidiDriver_ADLIB_Multisource::OPL_REGISTER_RHYTHM_OFFSETS[OPL_NUM_RHYTHM_INSTRUMENTS] = { 0x11, 0x15, 0x12, 0x14, 0x10 };
+
+const uint8 MidiDriver_ADLIB_Multisource::OPL_RHYTHM_INSTRUMENT_CHANNELS[OPL_NUM_RHYTHM_INSTRUMENTS] = { 7, 8, 8, 7, 6 };
+
 // 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
@@ -376,14 +400,18 @@ MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType
 		_isOpen(false),
 		_accuracyMode(ACCURACY_MODE_SB16_WIN95),
 		_allocationMode(ALLOCATION_MODE_DYNAMIC),
+		_rhythmModeIgnoreNoteOffs(false),
 		_defaultChannelVolume(0),
 		_noteSelect(NOTE_SELECT_MODE_0),
 		_modulationDepth(MODULATION_DEPTH_HIGH),
 		_vibratoDepth(VIBRATO_DEPTH_HIGH),
+		_rhythmMode(false),
 		_instrumentBank(OPL_INSTRUMENT_BANK),
 		_rhythmBank(OPL_RHYTHM_BANK),
 		_rhythmBankFirstNote(GS_RHYTHM_FIRST_NOTE),
 		_rhythmBankLastNote(GS_RHYTHM_LAST_NOTE),
+		_melodicChannels(nullptr),
+		_numMelodicChannels(0),
 		_noteCounter(1),
 		_oplFrequencyConversionFactor(pow(2, 20) / 49716.0f) {
 	memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
@@ -421,6 +449,9 @@ int MidiDriver_ADLIB_Multisource::open() {
 	if (!_opl->init())
 		return MERR_CANNOT_CONNECT;
 
+	// Set the melodic channels applicable for the OPL chip type.
+	determineMelodicChannels();
+
 	// Set default MIDI channel volume on control data.
 	for (int i = 0; i < MAXIMUM_SOURCES; i++) {
 		for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) {
@@ -488,6 +519,12 @@ uint32 MidiDriver_ADLIB_Multisource::property(int prop, uint32 param) {
 			_allocationMode = ALLOCATION_MODE_DYNAMIC;
 		}
 
+		break;
+	case PROP_OPL_RHYTHM_MODE_IGNORE_NOTE_OFF:
+		if (param == 0xFFFF)
+			return _rhythmModeIgnoreNoteOffs;
+
+		_rhythmModeIgnoreNoteOffs = (param != 0);
 		break;
 	default:
 		return MidiDriver_Multisource::property(prop, param);
@@ -568,16 +605,30 @@ void MidiDriver_ADLIB_Multisource::send(int8 source, uint32 b) {
 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);
+	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+		if (!_rhythmModeIgnoreNoteOffs) {
+			// Find the OPL rhythm instrument playing this note.
+			for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+				if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source &&
+						_activeRhythmNotes[i].note == note) {
+					writeKeyOff(0, static_cast<OplInstrumentRhythmType>(i + 1));
+					break;
+				}
+			}
+		}
+	} else {
+		// Find the OPL channel playing this note.
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].source == source &&
+					_activeNotes[oplChannel].channel == channel && _activeNotes[oplChannel].note == note) {
+				if (_controlData[source][channel].sustain) {
+					// Sustain controller is on. Sustain the note instead of
+					// ending it.
+					_activeNotes[oplChannel].noteSustained = true;
+				} else {
+					writeKeyOff(oplChannel);
+				}
 			}
 		}
 	}
@@ -591,51 +642,71 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 		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.
+	// If rhythm mode is on and the note is on the rhythm channel, this note
+	// will be played using the OPL rhythm register.
+	bool rhythmNote = _rhythmMode && channel == MIDI_RHYTHM_CHANNEL;
+
+	if (instrument.instrumentDef->isEmpty() ||
+			(rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) {
+		// Instrument definition contains no data or it is not suitable for
+		// rhythm mode, 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);
+	// Determine the OPL channel to use and the active note data to update.
+	uint8 oplChannel = 0xFF;
+	ActiveNote *activeNote = nullptr;
+	if (rhythmNote) {
+		activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1];
+	} else {
+		// Allocate a melodic OPL channel.
+		oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
+		if (oplChannel != 0xFF)
+			activeNote = &_activeNotes[oplChannel];
+	}
+	if (activeNote != nullptr) {
+		if (activeNote->noteActive) {
+			// Turn off the note currently playing on this OPL channel or
+			// rhythm instrument.
+			writeKeyOff(oplChannel, instrument.instrumentDef->rhythmType);
+		}
 
 		// 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;
+		activeNote->noteActive = true;
+		activeNote->noteSustained = false;
+		activeNote->note = note;
+		activeNote->velocity = velocity;
+		activeNote->channel = channel;
+		activeNote->source = source;
+
+		activeNote->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;
+		activeNote->noteCounterValue = _noteCounter++;
+		activeNote->instrumentId = instrument.instrumentId;
+		activeNote->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);
+			uint16 operatorOffset = determineOperatorRegisterOffset(oplChannel, i, instrument.instrumentDef->rhythmType, instrument.instrumentDef->fourOperator);
 			const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
 			writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
-			writeVolume(oplChannel, i);
+			writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
 			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);
-
+		writePanning(oplChannel, instrument.instrumentDef->rhythmType);
 		// Calculate and write frequency and block and write key on bit.
-		writeFrequency(oplChannel);
+		writeFrequency(oplChannel, instrument.instrumentDef->rhythmType);
+
+		if (rhythmNote)
+			// Update the rhythm register.
+			writeRhythm();
 	}
 
 	_activeNotesMutex.unlock();
@@ -729,8 +800,10 @@ void MidiDriver_ADLIB_Multisource::sysEx(const byte *msg, uint16 length) {
 			}
 		}
 
-		for (int i = 0; i < determineNumOplChannels(); i++) {
-			_activeNotes[i].init();
+		setRhythmMode(false);
+
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			_activeNotes[_melodicChannels[i]].init();
 		}
 
 		memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
@@ -764,9 +837,10 @@ void MidiDriver_ADLIB_Multisource::deinitSource(uint8 source) {
 	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;
+	for (int i = 0; i < _numMelodicChannels; i++) {
+		uint8 oplChannel = _melodicChannels[i];
+		if (_activeNotes[oplChannel].channelAllocated && _activeNotes[oplChannel].source == source) {
+			_activeNotes[oplChannel].channelAllocated = false;
 		}
 	}
 
@@ -813,7 +887,7 @@ void MidiDriver_ADLIB_Multisource::applyControllerDefaults(uint8 source) {
 			}
 			// Controller defaults not supported by this driver:
 			// instrument bank, drumkit.
-			// Sustain is turned of by deinitSource.
+			// Sustain is turned off by deinitSource.
 		}
 	}
 }
@@ -883,10 +957,19 @@ void MidiDriver_ADLIB_Multisource::panning(uint8 channel, uint8 panning, uint8 s
 	_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);
+	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+			if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source) {
+				writePanning(0xFF, static_cast<OplInstrumentRhythmType>(i + 1));
+			}
+		}
+	} else {
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].channel == channel &&
+					_activeNotes[oplChannel].source == source) {
+				writePanning(oplChannel);
+			}
 		}
 	}
 
@@ -913,10 +996,11 @@ void MidiDriver_ADLIB_Multisource::sustain(uint8 channel, uint8 sustain, uint8 s
 		_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);
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].noteSustained &&
+					_activeNotes[oplChannel].channel == channel && _activeNotes[oplChannel].source == source) {
+				writeKeyOff(oplChannel);
 			}
 		}
 
@@ -958,10 +1042,19 @@ void MidiDriver_ADLIB_Multisource::allNotesOff(uint8 channel, uint8 source) {
 
 	// 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);
+	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+			if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source) {
+				noteOff(channel, _activeRhythmNotes[i].note, 0, source);
+			}
+		}
+	} else {
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].noteActive && !_activeNotes[oplChannel].noteSustained && 
+					_activeNotes[oplChannel].source == source && _activeNotes[oplChannel].channel == channel) {
+				noteOff(channel, _activeNotes[oplChannel].note, 0, source);
+			}
 		}
 	}
 
@@ -971,9 +1064,15 @@ void MidiDriver_ADLIB_Multisource::allNotesOff(uint8 channel, uint8 source) {
 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++) {
+	for (int i = 0; i < _numMelodicChannels; i++) {
 		// Force the register write to prevent accidental hanging notes.
-		writeKeyOff(i, true);
+		writeKeyOff(_melodicChannels[i], RHYTHM_TYPE_UNDEFINED, true);
+	}
+	if (_rhythmMode) {
+		for (int i = 0; i < 5; i++) {
+			_activeRhythmNotes[i].noteActive = false;
+		}
+		writeRhythm(true);
 	}
 }
 
@@ -982,13 +1081,22 @@ void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
 
 	// 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);
+	for (int i = 0; i < _numMelodicChannels; i++) {
+		uint8 oplChannel = _melodicChannels[i];
+		if (_activeNotes[oplChannel].noteActive && (source == 0xFF || _activeNotes[oplChannel].source == source) &&
+			(channel == 0xFF || _activeNotes[oplChannel].channel == channel)) {
+			writeKeyOff(oplChannel);
 		}
 	}
-
+	if (_rhythmMode && !_rhythmModeIgnoreNoteOffs && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
+		for (int i = 0; i < 5; i++) {
+			if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
+				_activeRhythmNotes[i].noteActive = false;
+			}
+		}
+		writeRhythm();
+	}
+	
 	_activeNotesMutex.unlock();
 }
 
@@ -1081,17 +1189,13 @@ void MidiDriver_ADLIB_Multisource::initOpl() {
 			break;
 		}
 
-		for (int j = 0; j < determineNumOplChannels(); j++) {
+		for (int j = 0; j < (_oplType == OPL::Config::kOpl2 ? OPL2_NUM_CHANNELS : OPL3_NUM_CHANNELS); 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);
-	}
+	// Set rhythm mode, modulation and vibrato depth.
+	writeRhythm(true);
 }
 
 void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 source) {
@@ -1099,10 +1203,50 @@ void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 s
 
 	// 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);
+	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+		// Always rewrite bass drum frequency if it is active.
+		if (_activeRhythmNotes[RHYTHM_TYPE_BASS_DRUM - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_BASS_DRUM - 1].source == source) {
+			writeFrequency(0xFF, RHYTHM_TYPE_BASS_DRUM);
+		}
+
+		// Snare drum and hi-hat share the same frequency setting. If both are
+		// active, use the most recently played instrument.
+		OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED;
+		bool snareActive = _activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].source == source;
+		bool hiHatActive = _activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].source == source;
+		if (snareActive && hiHatActive) {
+			rhythmType = (_activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].noteCounterValue >=
+				_activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].noteCounterValue ? RHYTHM_TYPE_SNARE_DRUM : RHYTHM_TYPE_HI_HAT);
+		} else if (snareActive) {
+			rhythmType = RHYTHM_TYPE_SNARE_DRUM;
+		} else if (hiHatActive) {
+			rhythmType = RHYTHM_TYPE_HI_HAT;
+		}
+		if (rhythmType != RHYTHM_TYPE_UNDEFINED)
+			writeFrequency(0xFF, rhythmType);
+
+		// Tom tom and cymbal share the same frequency setting. If both are 
+		// active, use the most recently played instrument.
+		rhythmType = RHYTHM_TYPE_UNDEFINED;
+		bool tomTomActive = _activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].source == source;
+		bool cymbalActive = _activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].source == source;
+		if (tomTomActive && cymbalActive) {
+			rhythmType = (_activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].noteCounterValue >=
+				_activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].noteCounterValue ? RHYTHM_TYPE_TOM_TOM : RHYTHM_TYPE_CYMBAL);
+		} else if (tomTomActive) {
+			rhythmType = RHYTHM_TYPE_TOM_TOM;
+		} else if (cymbalActive) {
+			rhythmType = RHYTHM_TYPE_CYMBAL;
+		}
+		if (rhythmType != RHYTHM_TYPE_UNDEFINED)
+			writeFrequency(0xFF, rhythmType);
+	} else {
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].channel == channel &&
+					_activeNotes[oplChannel].source == source) {
+				writeFrequency(oplChannel);
+			}
 		}
 	}
 
@@ -1114,12 +1258,22 @@ void MidiDriver_ADLIB_Multisource::recalculateVolumes(uint8 channel, uint8 sourc
 
 	// 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);
+	for (int i = 0; i < _numMelodicChannels; i++) {
+		uint8 oplChannel = _melodicChannels[i];
+		if (_activeNotes[oplChannel].noteActive &&
+				(channel == 0xFF || _activeNotes[oplChannel].channel == channel) &&
+				(source == 0xFF || _activeNotes[oplChannel].source == source)) {
+			for (int j = 0; j < _activeNotes[oplChannel].instrumentDef->getNumberOfOperators(); j++) {
+				writeVolume(oplChannel, j);
+			}
+		}
+	}
+	if (_rhythmMode && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
+		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+			if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
+				for (int j = 0; j < _activeRhythmNotes[i].instrumentDef->getNumberOfOperators(); j++) {
+					writeVolume(0xFF, j, static_cast<OplInstrumentRhythmType>(i + 1));
+				}
 			}
 		}
 	}
@@ -1172,31 +1326,32 @@ uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 sour
 
 		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) {
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].noteCounterValue == 0) {
 				// This channel is unused. No need to look any further.
-				unusedChannel = i;
+				unusedChannel = oplChannel;
 				break;
 			}
-			if (!_activeNotes[i].noteActive && _activeNotes[i].noteCounterValue < inactiveNoteCounter) {
+			if (!_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].noteCounterValue < inactiveNoteCounter) {
 				// A channel not playing a note with a lower note counter value
 				// has been found.
-				inactiveNoteCounter = _activeNotes[i].noteCounterValue;
-				inactiveChannel = i;
+				inactiveNoteCounter = _activeNotes[oplChannel].noteCounterValue;
+				inactiveChannel = oplChannel;
 				continue;
 			}
-			if (_activeNotes[i].noteActive && _activeNotes[i].instrumentId == instrumentId &&
-					_activeNotes[i].noteCounterValue < instrumentNoteCounter) {
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].instrumentId == instrumentId &&
+					_activeNotes[oplChannel].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;
+				instrumentNoteCounter = _activeNotes[oplChannel].noteCounterValue;
+				instrumentChannel = oplChannel;
 			}
-			if (_activeNotes[i].noteActive && _activeNotes[i].noteCounterValue < lowestNoteCounter) {
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].noteCounterValue < lowestNoteCounter) {
 				// A channel playing a note with a lower note counter value has
 				// been found.
-				lowestNoteCounter = _activeNotes[i].noteCounterValue;
-				lowestCounterChannel = i;
+				lowestNoteCounter = _activeNotes[oplChannel].noteCounterValue;
+				lowestCounterChannel = oplChannel;
 			}
 		}
 
@@ -1230,16 +1385,17 @@ uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 sour
 			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) {
+			for (int i = 0; i < _numMelodicChannels; i++) {
+				uint8 oplChannel = _melodicChannels[i];
+				if (!_activeNotes[oplChannel].channelAllocated) {
 					// Found a free channel. Allocate this.
-					_activeNotes[i].channelAllocated = true;
-					_activeNotes[i].source = source;
-					_activeNotes[i].channel = channel;
+					_activeNotes[oplChannel].channelAllocated = true;
+					_activeNotes[oplChannel].source = source;
+					_activeNotes[oplChannel].channel = channel;
 
-					_channelAllocations[source][channel] = i;
+					_channelAllocations[source][channel] = oplChannel;
 
-					allocatedChannel = i;
+					allocatedChannel = oplChannel;
 
 					break;
 				}
@@ -1253,6 +1409,28 @@ uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 sour
 	return allocatedChannel;
 }
 
+void MidiDriver_ADLIB_Multisource::determineMelodicChannels() {
+	if (_oplType == OPL::Config::kOpl2 || _oplType == OPL::Config::kDualOpl2) {
+		_numMelodicChannels = OPL2_NUM_CHANNELS;
+		if (_rhythmMode) {
+			// Rhythm mode uses 3 OPL channels for rhythm instruments.
+			_numMelodicChannels -= 3;
+			_melodicChannels = MELODIC_CHANNELS_OPL2_RHYTHM;
+		} else {
+			// Use all available OPL channels as melodic channels.
+			_melodicChannels = MELODIC_CHANNELS_OPL2;
+		}
+	} else {
+		_numMelodicChannels = OPL3_NUM_CHANNELS;
+		if (_rhythmMode) {
+			_numMelodicChannels -= 3;
+			_melodicChannels = MELODIC_CHANNELS_OPL3_RHYTHM;
+		} else {
+			_melodicChannels = MELODIC_CHANNELS_OPL3;
+		}
+	}
+}
+
 uint16 MidiDriver_ADLIB_Multisource::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
 	// Split note into octave and octave note.
 	uint8 octaveNote = note % 12;
@@ -1365,7 +1543,9 @@ uint8 MidiDriver_ADLIB_Multisource::calculateVolume(uint8 channel, uint8 source,
 	// to have volume settings applied; modulator operators just use the
 	// instrument definition volume.
 	bool applyVolume = false;
-	if (instrumentDef.fourOperator) {
+	if (instrumentDef.rhythmType != RHYTHM_TYPE_UNDEFINED) {
+		applyVolume = (instrumentDef.rhythmType != RHYTHM_TYPE_BASS_DRUM || operatorNum == 1);
+	} else if (instrumentDef.fourOperator) {
 		// 4 operator instruments have 4 different operator connections.
 		uint8 connection = (instrumentDef.connectionFeedback0 & 0x01) | ((instrumentDef.connectionFeedback1 & 0x01) << 1);
 		switch (connection) {
@@ -1468,6 +1648,9 @@ uint8 MidiDriver_ADLIB_Multisource::calculateUnscaledVolume(uint8 channel, uint8
 }
 
 uint8 MidiDriver_ADLIB_Multisource::calculatePanning(uint8 channel, uint8 source) {
+	if (_oplType != OPL::Config::kOpl3)
+		return 0;
+
 	// 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) {
@@ -1479,16 +1662,56 @@ uint8 MidiDriver_ADLIB_Multisource::calculatePanning(uint8 channel, uint8 source
 	}
 }
 
-uint8 MidiDriver_ADLIB_Multisource::determineNumOplChannels() {
-	return _oplType == OPL::Config::kOpl2 ? OPL2_NUM_CHANNELS : OPL3_NUM_CHANNELS;
+void MidiDriver_ADLIB_Multisource::setRhythmMode(bool rhythmMode) {
+	if (_rhythmMode == rhythmMode)
+		return;
+
+	_allocationMutex.lock();
+	_activeNotesMutex.lock();
+
+	if (!_rhythmMode && rhythmMode) {
+		// Rhythm mode is turned on.
+
+		// Reset the OPL channels that will be used for rhythm mode.
+		for (int i = 6; i <= 8; i++) {
+			writeKeyOff(i);
+			for (int j = 0; j < MAXIMUM_SOURCES; j++) {
+				_channelAllocations[j][i] = 0xFF;
+			}
+			_activeNotes[i].init();
+		}
+		// Initialize the rhythm note data.
+		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+			_activeRhythmNotes[i].init();
+		}
+	} else if (_rhythmMode && !rhythmMode) {
+		// Rhythm mode is turned off.
+		// Turn off any active rhythm notes.
+		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+			_activeRhythmNotes[i].noteActive = false;
+		}
+	}
+	_rhythmMode = rhythmMode;
+
+	determineMelodicChannels();
+	writeRhythm();
+
+	_activeNotesMutex.unlock();
+	_allocationMutex.unlock();
 }
 
-uint16 MidiDriver_ADLIB_Multisource::determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, bool fourOperator) {
+uint16 MidiDriver_ADLIB_Multisource::determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType, bool fourOperator) {
 	assert(!fourOperator || oplChannel < 6);
 	assert(fourOperator || operatorNum < 2);
 
 	uint16 offset = 0;
-	if (fourOperator) {
+	if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
+		// Look up the offset for rhythm instruments.
+		offset = OPL_REGISTER_RHYTHM_OFFSETS[rhythmType - 1];
+		if (rhythmType == RHYTHM_TYPE_BASS_DRUM && operatorNum == 1)
+			// Bass drum is the only rhythm instrument with 2 operators.
+			offset += 3;
+	} else if (fourOperator) {
 		// 4 operator register offset for each channel and operator:
 		// 
 		// Channel  | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
@@ -1527,67 +1750,111 @@ uint16 MidiDriver_ADLIB_Multisource::determineChannelRegisterOffset(uint8 oplCha
 	return offset + (oplChannel % numChannelsPerSet);
 }
 
-void MidiDriver_ADLIB_Multisource::writeKeyOff(uint8 oplChannel, bool forceWrite) {
+void MidiDriver_ADLIB_Multisource::writeKeyOff(uint8 oplChannel, OplInstrumentRhythmType rhythmType, 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);
+	ActiveNote *activeNote = nullptr;
+	if (rhythmType == RHYTHM_TYPE_UNDEFINED) {
+		// Melodic instrument.
+		activeNote = &_activeNotes[oplChannel];
+		// Rewrite the current Bx register value with the key on bit set to 0.
+		writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + determineChannelRegisterOffset(oplChannel),
+			(activeNote->oplFrequency >> 8) & OPL_MASK_FNUMHIGH_BLOCK, forceWrite);
+	} else {
+		// Rhythm instrument.
+		activeNote = &_activeRhythmNotes[rhythmType - 1];
+	}
+
 	// Update the active note data.
-	_activeNotes[oplChannel].noteActive = false;
-	_activeNotes[oplChannel].noteSustained = false;
+	activeNote->noteActive = false;
+	activeNote->noteSustained = false;
 	// Register the current note counter value when turning off a note.
-	_activeNotes[oplChannel].noteCounterValue = _noteCounter;
+	activeNote->noteCounterValue = _noteCounter;
+
+	if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
+		// Rhythm instrument. Rewrite the rhythm register.
+		writeRhythm();
+	}
 
 	_activeNotesMutex.unlock();
 }
 
-void MidiDriver_ADLIB_Multisource::writeVolume(uint8 oplChannel, uint8 operatorNum) {
+void MidiDriver_ADLIB_Multisource::writeRhythm(bool forceWrite) {
+	uint8 value = (_modulationDepth << 7) | (_vibratoDepth << 6) | ((_rhythmMode ? 1 : 0) << 5);
+	if (_rhythmMode) {
+		// Add the key on bits for each rhythm instrument.
+		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
+			value |= ((_activeRhythmNotes[i].noteActive ? 1 : 0) << i);
+		}
+	}
+
+	writeRegister(OPL_REGISTER_RHYTHM, value, forceWrite);
+	if (_oplType == OPL::Config::kDualOpl2) {
+		writeRegister(OPL_REGISTER_RHYTHM | OPL_REGISTER_SET_2_OFFSET, value, forceWrite);
+	}
+}
+
+void MidiDriver_ADLIB_Multisource::writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType) {
+	ActiveNote *activeNote = (rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[rhythmType - 1]);
+
 	// Calculate operator volume.
 	uint16 registerOffset = determineOperatorRegisterOffset(
-		oplChannel, operatorNum, _activeNotes[oplChannel].instrumentDef->fourOperator);
+		oplChannel, operatorNum, rhythmType, activeNote->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);
+		activeNote->instrumentDef->getOperatorDefinition(operatorNum);
+	uint8 level = calculateVolume(activeNote->channel, activeNote->source, activeNote->velocity,
+		*activeNote->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) {
+void MidiDriver_ADLIB_Multisource::writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
+	ActiveNote *activeNote;
+	if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
+		activeNote = &_activeRhythmNotes[rhythmType - 1];
+		oplChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmType - 1];
+	} else {
+		activeNote = &_activeNotes[oplChannel];
+	}
+
 	// Calculate channel panning.
 	uint16 registerOffset = determineChannelRegisterOffset(
-		oplChannel, _activeNotes[oplChannel].instrumentDef->fourOperator);
-	uint8 panning = calculatePanning(_activeNotes[oplChannel].channel,
-		_activeNotes[oplChannel].source);
+		oplChannel, activeNote->instrumentDef->fourOperator);
+	uint8 panning = calculatePanning(activeNote->channel, activeNote->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)
+		panning | (activeNote->instrumentDef->connectionFeedback0 & ~OPL_MASK_PANNING));
+	if (activeNote->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));
+			panning | (activeNote->instrumentDef->connectionFeedback1 & ~OPL_MASK_PANNING));
 }
 
-void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel) {
+void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
 	_activeNotesMutex.lock();
 
+	ActiveNote *activeNote;
+	if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
+		activeNote = &_activeRhythmNotes[rhythmType - 1];
+		oplChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmType - 1];
+	} else {
+		activeNote = &_activeNotes[oplChannel];
+	}
+
 	// 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;
+	uint16 channelOffset = determineChannelRegisterOffset(oplChannel, activeNote->instrumentDef->fourOperator);
+	uint16 frequency = calculateFrequency(activeNote->channel, activeNote->source, activeNote->oplNote);
+	activeNote->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);
+	writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
+		(frequency >> 8) | (rhythmType == RHYTHM_TYPE_UNDEFINED ? OPL_MASK_KEYON : 0));
 
 	_activeNotesMutex.unlock();
 }
diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h
index 5ffeca7014c..c002a784887 100644
--- a/audio/adlib_ms.h
+++ b/audio/adlib_ms.h
@@ -25,6 +25,18 @@
 #include "audio/mididrv_ms.h"
 #include "audio/fmopl.h"
 
+/**
+ * Rhythm instrument types used by the OPL2 and OPL3 rhythm mode.
+ */
+enum OplInstrumentRhythmType {
+	RHYTHM_TYPE_UNDEFINED,
+	RHYTHM_TYPE_HI_HAT,
+	RHYTHM_TYPE_CYMBAL,
+	RHYTHM_TYPE_TOM_TOM,
+	RHYTHM_TYPE_SNARE_DRUM,
+	RHYTHM_TYPE_BASS_DRUM
+};
+
 /**
  * Data for one operator of an OPL instrument definition.
  */
@@ -50,6 +62,13 @@ struct OplInstrumentOperatorDefinition {
 	 * Ex register: waveform select.
 	 */
 	uint8 waveformSelect;
+
+	/**
+	 * Check if this operator definition contains any data.
+	 *
+	 * @return True if this operator is empty; false otherwise.
+	 */
+	bool isEmpty();
 };
 
 /**
@@ -89,6 +108,12 @@ struct OplInstrumentDefinition {
 	 * instrument. Not used for melodic instruments.
 	 */
 	uint8 rhythmNote;
+	/**
+	 * The type of OPL rhythm instrument that this definition should be used
+	 * with. Type undefined indicates that this definition should not be used
+	 * with rhythm mode.
+	 */
+	OplInstrumentRhythmType rhythmType;
 
 	/**
 	 * Check if this instrument definition contains any data.
@@ -295,6 +320,35 @@ public:
 	 */
 	static const uint8 OPL2_NUM_CHANNELS = 9;
 	static const uint8 OPL3_NUM_CHANNELS = 18;
+	/**
+	 * The melodic channel numbers available on an OPL2 chip with rhythm mode
+	 * disabled.
+	 */
+	static uint8 MELODIC_CHANNELS_OPL2[9];
+	/**
+	 * The melodic channel numbers available on an OPL2 chip with rhythm mode
+	 * enabled.
+	 */
+	static uint8 MELODIC_CHANNELS_OPL2_RHYTHM[6];
+	/**
+	 * The melodic channel numbers available on an OPL3 chip with rhythm mode
+	 * disabled.
+	 */
+	static uint8 MELODIC_CHANNELS_OPL3[18];
+	/**
+	 * The melodic channel numbers available on an OPL3 chip with rhythm mode
+	 * enabled.
+	 */
+	static uint8 MELODIC_CHANNELS_OPL3_RHYTHM[15];
+	/**
+	 * The number of rhythm instruments available in OPL rhythm mode.
+	 */
+	static const uint8 OPL_NUM_RHYTHM_INSTRUMENTS = 5;
+	/**
+	 * The OPL channels used by the rhythm instruments, in order:
+	 * hi-hat, cymbal, tom tom, snare drum, bass drum.
+	 */
+	static const uint8 OPL_RHYTHM_INSTRUMENT_CHANNELS[OPL_NUM_RHYTHM_INSTRUMENTS];
 
 	/**
 	 * OPL test and timer registers.
@@ -337,6 +391,11 @@ public:
 	 */
 	static const uint16 OPL_REGISTER_SET_2_OFFSET = 0x100;
 
+	/**
+	 * Offsets for the rhythm mode instrument registers.
+	 */
+	static const uint8 OPL_REGISTER_RHYTHM_OFFSETS[];
+
 	/**
 	 * Bitmasks for various parameters in the OPL registers.
 	 */
@@ -825,6 +884,14 @@ protected:
 	 * failed (not possible using the dynamic channel allocation mode).
 	 */
 	virtual uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId);
+	/**
+	 * Determines which melodic channels are available based on the OPL chip
+	 * type and rhythm mode setting and sets _melodicChannels and
+	 * _numMelodicChannels to the determined values.
+	 * This is called after constructing the driver with the OPL chip type and
+	 * after calls to setRhythmMode.
+	 */
+	void determineMelodicChannels();
 	/**
 	 * Calculates the OPL frequency (F-num) and octave (block) to play the
 	 * specified note on the specified MIDI channel and source, taking into
@@ -919,26 +986,39 @@ protected:
 	virtual uint8 calculatePanning(uint8 channel, uint8 source);
 
 	/**
-	 * Determines the number of channels available on the OPL chip used by the
-	 * driver.
+	 * Activates or deactivates the rhythm mode setting of the OPL chip. This
+	 * setting uses 3 OPL channels to make 5 preset rhythm instruments
+	 * available. Rhythm mode is turned off by default.
+	 * Activating rhythm mode will deallocate and end active notes on channels
+	 * 6 to 8. Deactivating rhythm mode will end active rhythm notes.
+	 * If the specified setting is the same as the current setting, this method
+	 * does nothing.
 	 * 
-	 * @return The number of OPL channels (9 or 18).
+	 * @param rhythmMode True if rhythm mode should be turned on; false if it
+	 * should be turned off.
 	 */
-	uint8 determineNumOplChannels();
+	virtual void setRhythmMode(bool rhythmMode);
+
 	/**
 	 * Determines the offset from a base register for the specified operator of
-	 * the specified OPL channel.
+	 * the specified OPL channel or rhythm instrument.
 	 * Add the offset to the base register to get the correct register for this
-	 * operator and channel.
+	 * operator and channel or rhythm instrument.
 	 * 
 	 * @param oplChannel The OPL channel for which to determine the offset.
+	 * Ignored if a rhythm type is specified.
 	 * @param operatorNum The operator for which to determine the offset;
-	 * 0-1 for 2 operator instruments, 0-3 for 4 operator instruments.
+	 * 0-1 for 2 operator instruments, 0-3 for 4 operator instruments. Ignored
+	 * for rhythm instruments other than bass drum.
+	 * @param rhythmType The rhythm instrument for which to determine the
+	 * offset. Specify type undefined to determine the offset for a melodic
+	 * instrument on the specified channel.
 	 * @param fourOperator True if the instrument used is a 4 operator
-	 * instrument; false if it is a 2 operator instrument.
+	 * instrument; false if it is a 2 operator instrument. Ignored if a rhythm
+	 * instrument type is specified.
 	 * @return The offset to the base register for this operator.
 	 */
-	uint16 determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, bool fourOperator = false);
+	uint16 determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED, 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
@@ -952,44 +1032,63 @@ protected:
 	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.
+	 * Sets the key on bit to false for the specified OPL channel or rhythm
+	 * instrument and updates _activeNotes or _activeRhythmNotes 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.
+	 * to false. Ignored if a rhythm type is specified.
+	 * @param rhythmType The rhythm instrument for 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);
+	void writeKeyOff(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED, bool forceWrite = false);
+	/**
+	 * Determines the value for the rhythm register (0xBD) and writes the new
+	 * value to the OPL chip. This register controls rhythm mode, the rhythm
+	 * instruments and the vibrato and modulation depth settings.
+	 */
+	void writeRhythm(bool forceWrite = false);
 	/**
-	 * Calculates the volume for the specified OPL channel and operator
-	 * (@see calculateVolume) and writes the new value to the OPL registers.
+	 * Calculates the volume for the specified OPL channel or rhythm instrument
+	 * 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.
+	 * and written. Ignored if a rhythm type is specified.
 	 * @param operatorNum The operator for which volume should be calculated
 	 * and written.
+	 * @param rhythmType The rhythm instrument for which volume should be
+	 * calculated and written. Use type undefined to calculate volume for a
+	 * melodic instrument.
 	 */
-	void writeVolume(uint8 oplChannel, uint8 operatorNum);
+	void writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
 	/**
-	 * Calculates the panning for the specified OPL channel
+	 * Calculates the panning for the specified OPL channel or rhythm type
 	 * (@see calculatePanning) and writes the new value to the OPL registers.
 	 * 
 	 * @param oplChannel The OPL channel for which panning should be calculated
-	 * and written.
+	 * and written. Ignored if a rhythm type is specified.
+	 * @param rhythmType The rhythm instrument for which panning should be
+	 * calculated and written. Use type undefined to calculate panning for a
+	 * melodic instrument.
 	 */
-	void writePanning(uint8 oplChannel);
+	void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
 	/**
 	 * Calculates the frequency for the active note on the specified OPL
-	 * channel (@see calculateFrequency) and writes the new value to the OPL
-	 * registers.
+	 * channel or of the specified rhythm type (@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.
+	 * calculated and written. Ignored if a rhythm type is specified.
+	 * @param rhythmType The rhythm instrument for which the frequency should
+	 * be calculated and written. Use type undefined to calculate the frequency
+	 * for a melodic instrument.
 	 */
-	void writeFrequency(uint8 oplChannel);
+	void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
 
 	/**
 	 * Writes the specified value to the specified OPL register.
@@ -1016,6 +1115,8 @@ protected:
 	AccuracyMode _accuracyMode;
 	// Controls the OPL channel allocation behavior.
 	ChannelAllocationMode _allocationMode;
+	// Controls response to rhythm note off events when rhythm mode is active.
+	bool _rhythmModeIgnoreNoteOffs;
 
 	// The default MIDI channel volume (set when opening the driver).
 	uint8 _defaultChannelVolume;
@@ -1025,6 +1126,8 @@ protected:
 	NoteSelectMode _noteSelect;
 	ModulationDepth _modulationDepth;
 	VibratoDepth _vibratoDepth;
+	// Current OPL rhythm mode setting. Use setRhythmMode to set and activate.
+	bool _rhythmMode;
 
 	// Pointer to the melodic instrument definitions.
 	OplInstrumentDefinition *_instrumentBank;
@@ -1039,10 +1142,16 @@ protected:
 	MidiChannelControlData _controlData[MAXIMUM_SOURCES][MIDI_CHANNEL_COUNT];
 	// The active note data for each OPL channel.
 	ActiveNote _activeNotes[OPL3_NUM_CHANNELS];
+	// The active note data for the OPL rhythm instruments.
+	ActiveNote _activeRhythmNotes[5];
 	// 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];
+	// Array containing the numbers of the available melodic channels.
+	uint8 *_melodicChannels;
+	// The number of available melodic channels (length of _melodicChannels).
+	uint8 _numMelodicChannels;
 	// The amount of notes played since the driver was opened / reset.
 	uint32 _noteCounter;
 
diff --git a/audio/mididrv.h b/audio/mididrv.h
index 5381f1b637f..d232588fdd0 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -432,7 +432,21 @@ public:
 		 * MILES_VERSION_3: behavior matches Miles Sound System version 3 and
 		 * higher. GM devices are initialized according to the GM standard.
 		 */
-		PROP_MILES_VERSION = 9
+		PROP_MILES_VERSION = 9,
+		/**
+		 * Set this property to make the OPL driver ignore note off events for
+		 * rhythm instruments when rhythm mode is activated.
+		 * MIDI data should contain a note off for each note on. For rhythm
+		 * instruments, a note off typically has no effect, because the note
+		 * plays for a fixed amount of time. For the OPL rhythm instruments a
+		 * note off will cut off the note. With this option, this behavior can
+		 * be prevented.
+		 * Currently only the AdLib multisource driver supports this option.
+		 *
+		 * False: note offs for OPL rhythm mode instruments are processed.
+		 * True: note offs for OPL rhythm mode instruments are ignored.
+		 */
+		PROP_OPL_RHYTHM_MODE_IGNORE_NOTE_OFF = 10
 	};
 
 	/**
diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp
index fdff2038b2a..1e42685aa48 100644
--- a/engines/lure/sound.cpp
+++ b/engines/lure/sound.cpp
@@ -919,12 +919,13 @@ void MidiDriver_ADLIB_Lure::channelAftertouch(uint8 channel, uint8 pressure, uin
 	_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) {
+	for (int i = 0; i < _numMelodicChannels; i++) {
+		uint8 oplChannel = _melodicChannels[i];
+		if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].source == source &&
+				_activeNotes[oplChannel].channel == channel) {
 			// Set the velocity of the note and recalculate and write the
 			// volume.
-			_activeNotes[i].velocity = pressure;
+			_activeNotes[oplChannel].velocity = pressure;
 
 			recalculateVolumes(channel, source);
 


Commit: 1f0e905160b27c6b9a2415aefb8c54448504ede4
    https://github.com/scummvm/scummvm/commit/1f0e905160b27c6b9a2415aefb8c54448504ede4
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:41+02:00

Commit Message:
AGOS: Add MIDI parsers for GMF and Windows

This commit adds MIDI parser subclasses for the Simon 1 GMF format and for the
SMF variant used by the Windows versions of Simon 1 and 2.

The GMF format was handled by the SMF parser, which can now be removed from
that class. It also fixes the tempos not matching the DOS interpreter.

The Simon Windows SMF variant was handled in the AGOS midi class. Moving it to
a separate parser allows for some cleaner code in that class. It now also
corrects the tempos to match those of the DOS interpreter.

Changed paths:
  A audio/midiparser_smf.h
  A engines/agos/midiparser_gmf.cpp
  A engines/agos/midiparser_gmf.h
  A engines/agos/midiparser_simonwin.cpp
  A engines/agos/midiparser_simonwin.h
    audio/midiparser.cpp
    audio/midiparser.h
    audio/midiparser_smf.cpp
    engines/agos/module.mk


diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp
index 8f3b4b9c584..9db5d10e90d 100644
--- a/audio/midiparser.cpp
+++ b/audio/midiparser.cpp
@@ -218,27 +218,29 @@ void MidiParser::onTimer() {
 		if (eventTime > endTime)
 			break;
 
-		// Process the next info.
-		if (info.event < 0x80) {
-			warning("Bad command or running status %02X", info.event);
-			_position._playPos = nullptr;
-			return;
-		}
+		if (!info.noop) {
+			// Process the next info.
+			if (info.event < 0x80) {
+				warning("Bad command or running status %02X", info.event);
+				_position._playPos = nullptr;
+				return;
+			}
 
-		if (info.command() == 0x8) {
-			activeNote(info.channel(), info.basic.param1, false);
-		} else if (info.command() == 0x9) {
-			if (info.length > 0)
-				hangingNote(info.channel(), info.basic.param1, info.length * _psecPerTick - (endTime - eventTime));
-			else
-				activeNote(info.channel(), info.basic.param1, true);
-		}
+			if (info.command() == 0x8) {
+				activeNote(info.channel(), info.basic.param1, false);
+			} else if (info.command() == 0x9) {
+				if (info.length > 0)
+					hangingNote(info.channel(), info.basic.param1, info.length * _psecPerTick - (endTime - eventTime));
+				else
+					activeNote(info.channel(), info.basic.param1, true);
+			}
 
-		// Player::metaEvent() in SCUMM will delete the parser object,
-		// so return immediately if that might have happened.
-		bool ret = processEvent(info);
-		if (!ret)
-			return;
+			// Player::metaEvent() in SCUMM will delete the parser object,
+			// so return immediately if that might have happened.
+			bool ret = processEvent(info);
+			if (!ret)
+				return;
+		}
 
 		loopEvent |= info.loop;
 
diff --git a/audio/midiparser.h b/audio/midiparser.h
index e6aa0e02f55..fa3b117eda0 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -26,6 +26,7 @@
 
 #include "common/scummsys.h"
 #include "common/endian.h"
+#include "common/stream.h"
 
 class MidiDriver_BASE;
 
@@ -101,11 +102,13 @@ struct EventInfo {
 	               ///< will occur, and the MidiParser will have to generate one itself.
 	               ///< For all other events, this value should always be zero.
 	bool   loop;   ///< Indicates that this event loops (part of) the MIDI data.
+	bool   noop;   ///< Indicates that no action should be taken for this event
+				   ///< (only delta should be handled).
 
 	byte channel() const { return event & 0x0F; } ///< Separates the MIDI channel from the event.
 	byte command() const { return event >> 4; }   ///< Separates the command code from the event.
 
-	EventInfo() : start(0), delta(0), event(0), length(0), loop(false) { basic.param1 = 0; basic.param2 = 0; ext.type = 0; ext.data = 0; }
+	EventInfo() : start(0), delta(0), event(0), length(0), loop(false), noop(false) { basic.param1 = 0; basic.param2 = 0; ext.type = 0; ext.data = 0; }
 };
 
 /**
@@ -430,10 +433,17 @@ public:
 	virtual bool loadMusic(byte *data, uint32 size) = 0;
 	virtual void unloadMusic();
 	virtual void property(int prop, int value);
+	/**
+	 * Returns the size in bytes of the MIDI data in the specified stream, or
+	 * -1 if the size could not be determined. The MIDI data must be in the
+	 * format handled by the MidiParser subclass that this method is called on.
+	 * Not every MidiParser subclass has an implementation of this method.
+	 */
+	virtual int32 determineDataSize(Common::SeekableReadStream *stream) { return -1; };
 
 	virtual void setMidiDriver(MidiDriver_BASE *driver) { _driver = driver; }
 	void setTimerRate(uint32 rate) { _timerRate = rate; }
-	void setTempo(uint32 tempo);
+	virtual void setTempo(uint32 tempo);
 	void onTimer();
 
 	bool isPlaying() const { return (_position._playPos != 0 && _doParse); }
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index 3fcaa191763..0e60e0cec09 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -19,43 +19,13 @@
  *
  */
 
+#include "audio/midiparser_smf.h"
+
 #include "audio/mididrv.h"
 #include "audio/midiparser.h"
 #include "common/textconsole.h"
 #include "common/util.h"
 
-/**
- * The Standard MIDI File version of MidiParser.
- */
-class MidiParser_SMF : public MidiParser {
-protected:
-	byte *_buffer;
-	bool _malformedPitchBends;
-	/**
-	 * The source number to use when sending MIDI messages to the driver.
-	 * When using multiple sources, use source 0 and higher. This must be
-	 * used when source volume or channel locking is used.
-	 * By default this is -1, which means the parser is the only source
-	 * of MIDI messages and multiple source functionality is disabled.
-	 */
-	int8 _source;
-
-protected:
-	void compressToType0();
-	void parseNextEvent(EventInfo &info) override;
-
-	void sendToDriver(uint32 b) override;
-	void sendMetaEventToDriver(byte type, byte *data, uint16 length) override;
-
-public:
-	MidiParser_SMF(int8 source = -1) : _buffer(nullptr), _malformedPitchBends(false), _source(source) { }
-	~MidiParser_SMF();
-
-	bool loadMusic(byte *data, uint32 size) override;
-	void property(int property, int value) override;
-};
-
-
 static const byte commandLengths[8] = { 3, 3, 3, 3, 2, 2, 3, 0 };
 static const byte specialLengths[16] = { 0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 };
 
@@ -250,7 +220,7 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 		// Inherit the Earth MIDIs. Jamieson630 said something about a
 		// better fix, but this will have to do in the meantime.
 		_buffer = (byte *)malloc(size * 2);
-		compressToType0();
+		compressToType0(_tracks, _numTracks, _buffer, _malformedPitchBends);
 		_numTracks = 1;
 		_tracks[0] = _buffer;
 	}
@@ -264,11 +234,42 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 	return true;
 }
 
-void MidiParser_SMF::compressToType0() {
-	// We assume that _buffer has been allocated
+int32 MidiParser_SMF::determineDataSize(Common::SeekableReadStream *stream) {
+	// Determine the MIDI data size by skipping over the header and all the
+	// MIDI tracks, then comparing start and end stream positions.
+	uint32 startPos = stream->pos();
+
+	// Skip over the header.
+	byte buf[4];
+	Common::fill(buf, buf + 4, 0);
+	stream->read(buf, 4);
+	if (memcmp(buf, "MThd", 4) != 0) {
+		warning("Expected MThd but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]);
+		return -1;
+	}
+	stream->seek(stream->readUint32BE(), SEEK_CUR);
+
+	// Now skip all the MTrk blocks.
+	while (true) {
+		Common::fill(buf, buf + 4, 0);
+		int read = stream->read(buf, 4);
+		if (read < 4 || memcmp(buf, "MTrk", 4) != 0) {
+			stream->seek(-read, SEEK_CUR);
+			break;
+		}
+		stream->seek(stream->readUint32BE(), SEEK_CUR);
+	}
+
+	// The stream is now at the end of the MIDI data, so the size is the
+	// difference between the current and starting stream position.
+	return stream->pos() - startPos;
+}
+
+uint32 MidiParser_SMF::compressToType0(byte *tracks[], byte numTracks, byte *buffer, bool malformedPitchBends) {
+	// We assume that buffer has been allocated
 	// to sufficient size for this operation.
 
-	// using 0xFF since it could write trackPos[0 to _numTracks] here
+	// using 0xFF since it could write trackPos[0 to numTracks] here
 	// this would cause some illegal writes and could lead to segfaults
 	// (it crashed for some midis for me, they're not used in any game
 	// scummvm supports though). *Maybe* handle this in another way,
@@ -279,26 +280,26 @@ void MidiParser_SMF::compressToType0() {
 	uint32 delta;
 	int i;
 
-	for (i = 0; i < _numTracks; ++i) {
+	for (i = 0; i < numTracks; ++i) {
 		runningStatus[i] = 0;
-		trackPos[i] = _tracks[i];
+		trackPos[i] = tracks[i];
 		trackTimer[i] = readVLQ(trackPos[i]);
 		runningStatus[i] = 0;
 	}
 
 	int bestTrack;
 	uint32 length;
-	byte *output = _buffer;
+	byte *output = buffer;
 	byte *pos, *pos2;
 	byte event;
 	uint32 copyBytes;
 	bool write;
-	byte activeTracks = (byte)_numTracks;
+	byte activeTracks = numTracks;
 
 	while (activeTracks) {
 		write = true;
 		bestTrack = 255;
-		for (i = 0; i < _numTracks; ++i) {
+		for (i = 0; i < numTracks; ++i) {
 			if (trackPos[i] && (bestTrack == 255 || trackTimer[i] < trackTimer[bestTrack]))
 				bestTrack = i;
 		}
@@ -325,7 +326,7 @@ void MidiParser_SMF::compressToType0() {
 				event = runningStatus[bestTrack];
 				implicitEvent = true;
 			}
-		} while (_malformedPitchBends && (event & 0xF0) == 0xE0 && pos++);
+		} while (malformedPitchBends && (event & 0xF0) == 0xE0 && pos++);
 		runningStatus[bestTrack] = event;
 
 		if (commandLengths[(event >> 4) - 8] > 0) {
@@ -357,7 +358,7 @@ void MidiParser_SMF::compressToType0() {
 
 		// Update all tracks' deltas
 		if (write) {
-			for (i = 0; i < _numTracks; ++i) {
+			for (i = 0; i < numTracks; ++i) {
 				if (trackPos[i] && i != bestTrack)
 					trackTimer[i] -= trackTimer[bestTrack];
 			}
@@ -391,6 +392,8 @@ void MidiParser_SMF::compressToType0() {
 	}
 
 	*output++ = 0x00;
+
+	return output - buffer;
 }
 
 void MidiParser_SMF::sendToDriver(uint32 b) {
diff --git a/audio/midiparser_smf.h b/audio/midiparser_smf.h
new file mode 100644
index 00000000000..e9e977a7ef2
--- /dev/null
+++ b/audio/midiparser_smf.h
@@ -0,0 +1,71 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AUDIO_MIDIPARSER_SMF_H
+#define AUDIO_MIDIPARSER_SMF_H
+
+#include "audio/midiparser.h"
+
+/**
+ * The Standard MIDI File version of MidiParser.
+ */
+class MidiParser_SMF : public MidiParser {
+protected:
+	byte *_buffer;
+	bool _malformedPitchBends;
+	/**
+	 * The source number to use when sending MIDI messages to the driver.
+	 * When using multiple sources, use source 0 and higher. This must be
+	 * used when source volume or channel locking is used.
+	 * By default this is -1, which means the parser is the only source
+	 * of MIDI messages and multiple source functionality is disabled.
+	 */
+	int8 _source;
+
+protected:
+	/**
+	 * Compresses the specified type 1 MIDI tracks to a single type 0 track.
+	 * 
+	 * @param tracks Pointer to an array of type 1 tracks.
+	 * @param numTracks The number of type 1 tracks.
+	 * @param buffer Buffer which will contain the compressed type 0 track.
+	 * @param malformedPitchBends True if broken pitch bend events consisting
+	 * of just the command byte should be ignored. This is only useful for MIDI
+	 * data which has this specific problem.
+	 * @return The size of the compressed type 0 track in bytes.
+	 */
+	uint32 compressToType0(byte *tracks[], byte numTracks, byte *buffer, bool malformedPitchBends = false);
+	void parseNextEvent(EventInfo &info) override;
+
+	void sendToDriver(uint32 b) override;
+	void sendMetaEventToDriver(byte type, byte *data, uint16 length) override;
+
+public:
+	MidiParser_SMF(int8 source = -1) : _buffer(nullptr), _malformedPitchBends(false), _source(source) {}
+	~MidiParser_SMF();
+
+	bool loadMusic(byte *data, uint32 size) override;
+	void property(int property, int value) override;
+
+	int32 determineDataSize(Common::SeekableReadStream *stream) override;
+};
+
+#endif
diff --git a/engines/agos/midiparser_gmf.cpp b/engines/agos/midiparser_gmf.cpp
new file mode 100644
index 00000000000..4b683ce3b2c
--- /dev/null
+++ b/engines/agos/midiparser_gmf.cpp
@@ -0,0 +1,182 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agos/midiparser_gmf.h"
+
+#include "audio/mididrv.h"
+
+namespace AGOS {
+
+MidiParser_GMF::MidiParser_GMF(int8 source) : MidiParser_SMF(source) {
+	memset(_tracksEndPos, 0, 120);
+}
+
+void MidiParser_GMF::parseNextEvent(EventInfo &info) {
+	byte *parsePos = _position._playPos;
+	uint8 *start = parsePos;
+	uint32 delta = readVLQ(parsePos);
+
+	// GMF does not use end of track events, so we have to use the size of the
+	// MIDI data to determine if we're at the end of the track.
+
+	// Simon 1 CD version data has several zero bytes at the end.
+	// Check for these and ignore them.
+	bool containsMoreData = true;
+	if (parsePos > _tracksEndPos[_activeTrack] - 5) {
+		containsMoreData = false;
+		byte *checkPos = parsePos;
+		while (checkPos < _tracksEndPos[_activeTrack]) {
+			if (*checkPos != 0) {
+				containsMoreData = true;
+				break;
+			}
+			checkPos++;
+		}
+		// If we're already past the end of the track, the while loop will not
+		// execute and containsMoreData remains false.
+	}
+
+	if (!containsMoreData) {
+		// Reached the end of the track. Generate an end-of-track event.
+		info.start = start;
+		info.delta = delta;
+		info.event = 0xFF;
+		info.ext.type = MidiDriver::MIDI_META_END_OF_TRACK;
+		info.length = 0;
+		info.ext.data = parsePos;
+
+		_position._playPos = parsePos;
+		return;
+	}
+
+	// There are more MIDI events in this track.
+	uint8 event = *(parsePos++);
+
+	// Pitch bend events in the Simon 1 data are broken. They contain just the
+	// command byte; no data bytes follow. We generate an empty event and set
+	// the noop flag to have MidiParser process only the delta and otherwise
+	// ignore the event.
+	if ((event & 0xF0) == MidiDriver::MIDI_COMMAND_PITCH_BEND) {
+		info.start = start;
+		info.delta = delta;
+		info.event = event;
+		info.basic.param1 = 0;
+		info.basic.param2 = 0;
+		info.length = 0;
+		info.noop = true;
+
+		_position._playPos = parsePos;
+	} else {
+		// Processing of the other events is the same as the SMF format.
+		MidiParser_SMF::parseNextEvent(info);
+	}
+}
+
+bool MidiParser_GMF::loadMusic(byte *data, uint32 size) {
+	assert(size > 7);
+
+	unloadMusic();
+
+	// Determine start and end of the MIDI track(s) in the data, as well as
+	// tempo and loop flag.
+	byte midiType;
+	uint8 headerTempo;
+	bool headerLoop;
+
+	// Simon 1 uses two GMF variants: a single track music file and a multiple
+	// track SFX file. These are processed as a MIDI type 0 and type 2 track,
+	// respectively.
+	if (!memcmp(data, "GMF", 3)) {
+		// Single track file.
+		midiType = 0;
+		_numTracks = 1;
+
+		// GMF header is GMF<majorVersion><minorVersion><tempo><loop>.
+		// Version is always 1.0 for Simon 1.
+		headerTempo = data[5];
+		headerLoop = data[6] == 1;
+
+		// MIDI track data starts immediately after the GMF header.
+		_tracks[0] = data + 7;
+		_tracksEndPos[0] = data + size;
+	} else {
+		// Assume multi-track file.
+		midiType = 2;
+
+		// Each track has its own GMF header, but tempo and loop flags are all
+		// the same for Simon 1.
+		headerTempo = 2;
+		headerLoop = false;
+
+		// A multi-track file starts with a list of 2-byte offsets which
+		// identify the starting position of each track, as well as the end of
+		// the last track.
+		byte *pos = data;
+		// Read the start offset of the first track.
+		byte *trackStart = data + READ_LE_UINT16(pos);
+		pos += 2;
+		// The number of offsets before the first track indicates the number of
+		// tracks plus 1 because the end offset of the last track is included
+		// as well.
+		_numTracks = (*trackStart / 2) - 1;
+
+		if (_numTracks > ARRAYSIZE(_tracks)) {
+			warning("MidiParser_GMF::loadMusic - Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_numTracks);
+			return false;
+		}
+
+		// Read all the track start offsets.
+		int tracksRead = 0;
+		while (tracksRead < _numTracks) {
+			_tracks[tracksRead] = trackStart + 7; // Skip 7-byte GMF header
+			trackStart = data + READ_LE_UINT16(pos);
+			pos += 2;
+			// Start of the next track is the end of this track.
+			_tracksEndPos[tracksRead] = trackStart;
+
+			tracksRead++;
+		}
+	}
+
+	// Note that we assume the original data passed in
+	// will persist beyond this call, i.e. we do NOT
+	// copy the data to our own buffer. Take warning....
+	_disableAutoStartPlayback = true;
+	resetTracking();
+	_autoLoop = headerLoop;
+	_ppqn = 192;
+
+	// These translations from the GMF header tempo (2-8) to the SMF tempo have
+	// been determined by measuring the tempos generated by the DOS version of
+	// Simon 1 in DOSBox.
+	uint32 tempo;
+	if (headerTempo < 6) {
+		tempo = 330000 + ((headerTempo - 2) * 105000);
+	} else {
+		tempo = 750000 + ((headerTempo - 6) * 125000);
+	}
+	setTempo(tempo);
+
+	setTrack(0);
+	return true;
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/midiparser_gmf.h b/engines/agos/midiparser_gmf.h
new file mode 100644
index 00000000000..a4d8a28729a
--- /dev/null
+++ b/engines/agos/midiparser_gmf.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGOS_MIDIPARSER_SMF_H
+#define AGOS_MIDIPARSER_SMF_H
+
+#include "audio/midiparser_smf.h"
+
+namespace AGOS {
+
+/**
+ * MIDI parser for the GMF format used by the DOS (and Acorn CD) versions of
+ * Simon The Sorcerer. GMF is basically SMF but with the following differences:
+ * - Different header
+ * - Tempo is determined by a header field
+ * - Just a single track with no track header
+ * - No end of track or other meta events
+ */
+class MidiParser_GMF : public MidiParser_SMF {
+public:
+	MidiParser_GMF(int8 source = -1);
+
+	bool loadMusic(byte *data, uint32 size) override;
+
+protected:
+	void parseNextEvent(EventInfo &info) override;
+
+	// The end position of each track, exclusive
+	// (i.e. 1 byte past the end of the data).
+	byte *_tracksEndPos[MAXIMUM_TRACKS];
+};
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/midiparser_simonwin.cpp b/engines/agos/midiparser_simonwin.cpp
new file mode 100644
index 00000000000..c094959c23c
--- /dev/null
+++ b/engines/agos/midiparser_simonwin.cpp
@@ -0,0 +1,202 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agos/midiparser_simonwin.h"
+
+#include "audio/mididrv.h"
+
+namespace AGOS {
+
+MidiParser_SimonWin::MidiParser_SimonWin(int8 source, bool correctSimon1Tempo) :
+	MidiParser_SMF(source), _trackData(), _correctSimon1Tempo(correctSimon1Tempo) { }
+
+MidiParser_SimonWin::~MidiParser_SimonWin() {
+	// Call unloadMusic to make sure any _trackData contents are deallocated.
+	unloadMusic();
+}
+
+void MidiParser_SimonWin::parseNextEvent(EventInfo &info) {
+	byte *parsePos = _position._playPos;
+	uint8 *start = parsePos;
+	uint32 delta = readVLQ(parsePos);
+	uint8 event = *(parsePos++);
+
+	// Pitch bend events in the Simon 1 data are broken. They contain just the
+	// command byte; no data bytes follow. We generate an empty event and set
+	// the noop flag to have MidiParser process only the delta and otherwise
+	// ignore the event.
+	if ((event & 0xF0) == MidiDriver::MIDI_COMMAND_PITCH_BEND) {
+		info.start = start;
+		info.delta = delta;
+		info.event = event;
+		info.basic.param1 = 0;
+		info.basic.param2 = 0;
+		info.length = 0;
+		info.noop = true;
+
+		_position._playPos = parsePos;
+	} else {
+		// Processing of the other events is the same as the SMF format.
+		MidiParser_SMF::parseNextEvent(info);
+	}
+}
+
+void MidiParser_SimonWin::setTempo(uint32 tempo) {
+	uint32 newTempo = tempo;
+	if (_correctSimon1Tempo && tempo < 750000) {
+		// WORKAROUND The tempos set in the SMF data of Simon 1 Windows are
+		// faster than the DOS version for the faster tempos. These are
+		// corrected here to match the DOS version. The correct tempos have
+		// been determined by measuring the tempos generated by the DOS version
+		// running in DOSBox.
+		newTempo = 330000 + (((tempo / 125000) - 2) * 105000);
+	}
+	MidiParser::setTempo(newTempo);
+}
+
+int32 MidiParser_SimonWin::determineDataSize(Common::SeekableReadStream *stream) {
+	int64 startPos = stream->pos();
+
+	// Read the number of tracks.
+	byte numSongs = stream->readByte();
+	if (numSongs > MAXIMUM_TRACKS) {
+		warning("MidiParser_SimonWin::determineDataSize - Can only handle %d tracks but was handed %d", MAXIMUM_TRACKS, numSongs);
+		return -1;
+	}
+
+	// Add up the sizes of the individual SMF tracks.
+	int32 totalSize = 1;
+	for (int i = 0; i < numSongs; ++i) {
+		int size = MidiParser_SMF::determineDataSize(stream);
+		if (size < 0)
+			return -1;
+
+		totalSize += size;
+		stream->seek(startPos + totalSize, SEEK_SET);
+	}
+
+	return totalSize;
+}
+
+bool MidiParser_SimonWin::loadMusic(byte *data, uint32 size) {
+	assert(size > 7);
+
+	unloadMusic();
+
+	// The first byte indicates the number of tracks in the MIDI data.
+	byte *pos = data;
+	_numTracks = *(pos++);
+	if (_numTracks > 16) {
+		warning("MidiParser_SimonWin::loadMusic - Can only handle %d tracks but was handed %d", MAXIMUM_TRACKS, _numTracks);
+		return false;
+	}
+	
+	// Read the tracks.
+	byte *trackDataStart;
+	for (int i = 0; i < _numTracks; ++i) {
+		trackDataStart = pos;
+
+		// Read the track header.
+
+		// Make sure there's a MThd.
+		if (memcmp(pos, "MThd", 4) != 0) {
+			warning("MidiParser_SimonWin::loadMusic - Expected MThd but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]);
+			return false;
+		}
+		pos += 4;
+
+		// Verify correct header length.
+		uint32 len = read4high(pos);
+		if (len != 6) {
+			warning("MidiParser_SimonWin::loadMusic - MThd length 6 expected but found %d", len);
+			return false;
+		}
+
+		// Verify that this MIDI is type 0 or 1 (it is expected to be type 1).
+		uint16 numSubtracks = pos[2] << 8 | pos[3];
+		assert(numSubtracks >= 1 && numSubtracks <= 20);
+		uint8 subtrackMidiType = pos[1];
+		if (subtrackMidiType >= 2) {
+			warning("MidiParser_SimonWin::loadMusic - MIDI track contained a type %d subtrack", subtrackMidiType);
+			return false;
+		}
+
+		// Each track could potentially have a different PPQN, but for Simon 1
+		// and 2 all tracks have PPQN 192, so this is not a problem.
+		_ppqn = pos[4] << 8 | pos[5];
+		pos += len;
+
+		// Now determine all the MTrk (sub)track start offsets.
+		byte *subtrackStarts[20];
+		for (int j = 0; j < numSubtracks; j++) {
+			if (memcmp(pos, "MTrk", 4) != 0) {
+				warning("MidiParser_SimonWin::loadMusic - Could not find subtrack header at expected location");
+				return false;
+			}
+			pos += 4;
+			uint32 subtrackLength = READ_BE_UINT32(pos);
+			pos += 4;
+			subtrackStarts[j] = pos;
+			pos += subtrackLength;
+		}
+
+		// We are now at the end of the track, so we can determine the total
+		// track length.
+		uint32 trackDataLength = pos - trackDataStart;
+
+		if (subtrackMidiType == 1) {
+			// Compress the type 1 data to type 0.
+			byte *buffer = new byte[trackDataLength * 2];
+			uint32 compressedDataLength = compressToType0(subtrackStarts, numSubtracks, buffer, true);
+			// Copy the compressed data to the _trackData array.
+			_trackData[i] = new byte[compressedDataLength];
+			memcpy(_trackData[i], buffer, compressedDataLength);
+			delete[] buffer;
+
+			_tracks[i] = _trackData[i];
+		} else {
+			// Note that we assume the original data passed in
+			// will persist beyond this call, i.e. we do NOT
+			// copy the data to our own buffer. Take warning....
+			_tracks[i] = subtrackStarts[0];
+		}
+	}
+
+	_disableAutoStartPlayback = true;
+	resetTracking();
+	setTempo(500000);
+	setTrack(0);
+	return true;
+}
+
+void MidiParser_SimonWin::unloadMusic() {
+	MidiParser_SMF::unloadMusic();
+
+	// Deallocate the compressed type 0 track data.
+	for (int i = 0; i < MAXIMUM_TRACKS; i++) {
+		if (_trackData[i]) {
+			delete[] _trackData[i];
+			_trackData[i] = nullptr;
+		}
+	}
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/midiparser_simonwin.h b/engines/agos/midiparser_simonwin.h
new file mode 100644
index 00000000000..2b3e95fc1fd
--- /dev/null
+++ b/engines/agos/midiparser_simonwin.h
@@ -0,0 +1,63 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGOS_MIDIPARSER_SIMONWIN_H
+#define AGOS_MIDIPARSER_SIMONWIN_H
+
+#include "audio/midiparser_smf.h"
+
+#include "common/stream.h"
+
+namespace AGOS {
+
+/**
+ * Parser for the MIDI data of the Windows versions of Simon The Sorcerer 1
+ * and 2. This consists of 1 or more type 1 SMF files, preceded by a byte
+ * indicating the number of files. The MIDI data for Simon 1 also has broken
+ * pitch bend events, as well as incorrect tempos, which are corrected by this
+ * parser.
+ */
+class MidiParser_SimonWin : public MidiParser_SMF {
+protected:
+	static const uint8 MAXIMUM_TRACKS = 16;
+
+public:
+	int32 determineDataSize(Common::SeekableReadStream *stream) override;
+
+	MidiParser_SimonWin(int8 source = -1, bool correctSimon1Tempo = false);
+	~MidiParser_SimonWin();
+
+	void setTempo(uint32 tempo) override;
+
+	bool loadMusic(byte *data, uint32 size) override;
+	void unloadMusic() override;
+
+protected:
+	void parseNextEvent(EventInfo &info) override;
+
+	byte *_trackData[MAXIMUM_TRACKS];
+
+	bool _correctSimon1Tempo;
+};
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index f128e7917be..dbf03668161 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -23,7 +23,9 @@ MODULE_OBJS := \
 	menus.o \
 	metaengine.o \
 	midi.o \
+	midiparser_gmf.o \
 	midiparser_s1d.o \
+	midiparser_simonwin.o \
 	pn.o \
 	res.o \
 	res_ami.o \


Commit: d37681a729c6876d410e071c8c54be1069062808
    https://github.com/scummvm/scummvm/commit/d37681a729c6876d410e071c8c54be1069062808
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:42+02:00

Commit Message:
AGOS: Update Simon 1 AdLib drivers

This updates the Simon 1 DOS AdLib driver and adds an AdLib driver for Windows.

The DOS driver now has OPL3 support, which eliminates the note cut off that
frequently occurs on an OPL2. It also allows for some limited use of stereo.
The driver now plays the floppy SFX accurately compared to the original
interpreter. It also implements multisource functionality, allowing for
separate volume control for music and SFX for the floppy version.

The Windows driver just maps the MT-32 instruments of the Simon 1 MIDI data to
the equivalent GM instruments.

Changed paths:
  A engines/agos/drivers/simon1/adlib_win.cpp
  A engines/agos/drivers/simon1/adlib_win.h
    engines/agos/drivers/simon1/adlib.cpp
    engines/agos/drivers/simon1/adlib.h
    engines/agos/module.mk


diff --git a/engines/agos/drivers/simon1/adlib.cpp b/engines/agos/drivers/simon1/adlib.cpp
index af0eec62d4c..cb035906ea4 100644
--- a/engines/agos/drivers/simon1/adlib.cpp
+++ b/engines/agos/drivers/simon1/adlib.cpp
@@ -21,477 +21,285 @@
 
 #include "agos/drivers/simon1/adlib.h"
 
-#include "common/textconsole.h"
-#include "common/util.h"
 #include "common/file.h"
 
 namespace AGOS {
 
-enum {
-	kChannelUnused       = 0xFF,
-	kChannelOrphanedFlag = 0x80,
+// Rhythm map hardcoded in the Simon 1 executable.
+const MidiDriver_Simon1_AdLib::RhythmMapEntry MidiDriver_Simon1_AdLib::RHYTHM_MAP[39] = {
+	{ 11, 123,  40 },
+	{ 12, 127,  50 },
+	{ 12, 124,   1 },
+	{ 12, 124,  90 },
+	{ 13, 125,  50 },
+	{ 13, 125,  25 },
+	{ 15, 127,  80 },
+	{ 13, 125,  25 },
+	{ 15, 127,  40 },
+	{ 13, 125,  35 },
+	{ 15, 127,  90 },
+	{ 13, 125,  35 },
+	{ 13, 125,  45 },
+	{ 14, 126,  90 },
+	{ 13, 125,  45 },
+	{ 15, 127,  90 },
+	{  0,   0,   0 },
+	{ 15, 127,  60 },
+	{  0,   0,   0 },
+	{ 13, 125,  60 },
+	{  0,   0,   0 },
+	{  0,   0,   0 },
+	{  0,   0,   0 },
+	{ 13, 125,  45 },
+	{ 13, 125,  40 },
+	{ 13, 125,  35 },
+	{ 13, 125,  30 },
+	{ 13, 125,  25 },
+	{ 13, 125,  80 },
+	{ 13, 125,  40 },
+	{ 13, 125,  80 },
+	{ 13, 125,  40 },
+	{ 14, 126,  40 },
+	{ 15, 127,  60 },
+	{  0,   0,   0 },
+	{  0,   0,   0 },
+	{ 14, 126,  80 },
+	{  0,   0,   0 },
+	{ 13, 125, 100 }
+};
 
-	kOPLVoicesCount = 9
+// Frequency table hardcoded in the Simon 1 executable.
+// Note that only the first 12 entries are used.
+const uint16 MidiDriver_Simon1_AdLib::FREQUENCY_TABLE[16] = {
+	0x0157, 0x016B, 0x0181, 0x0198, 0x01B0, 0x01CA, 0x01E5, 0x0202,
+	0x0220, 0x0241, 0x0263, 0x0287, 0x2100, 0xD121, 0xA307, 0x46A4
 };
 
-MidiDriver_Simon1_AdLib::Voice::Voice()
-	: channel(kChannelUnused), note(0), instrTotalLevel(0), instrScalingLevel(0), frequency(0) {
-}
+MidiDriver_Simon1_AdLib::MidiDriver_Simon1_AdLib(OPL::Config::OplType oplType, const byte *instrumentData) : MidiDriver_ADLIB_Multisource(oplType), _musicRhythmNotesDisabled(false) {
+	// The Simon 1 MIDI data is written for MT-32 and rhythm notes are played
+	// by quickly turning a note on and off. The note off has no effect on an
+	// MT-32, but it will cut off the rhythm note on OPL. To prevent this, note
+	// off events for rhythm notes are ignored and the rhythm note is turned
+	// off right before this rhythm instrument is played again. The original
+	// interpreter does this as well.
+	_rhythmModeIgnoreNoteOffs = true;
 
-MidiDriver_Simon1_AdLib::MidiDriver_Simon1_AdLib(const byte *instrumentData)
-	: _isOpen(false), _opl(nullptr), _timerProc(nullptr), _timerParam(nullptr),
-	  _melodyVoices(0), _amvdrBits(0), _rhythmEnabled(false), _voices(), _midiPrograms(),
-	  _instruments(instrumentData) {
+	parseInstrumentData(instrumentData);
 }
 
 MidiDriver_Simon1_AdLib::~MidiDriver_Simon1_AdLib() {
-	close();
-	delete[] _instruments;
+	delete[] _instrumentBank;
+	delete[] _rhythmBank;
 }
 
 int MidiDriver_Simon1_AdLib::open() {
-	if (_isOpen) {
-		return MERR_ALREADY_OPEN;
-	}
-
-	_opl = OPL::Config::create();
-	if (!_opl) {
-		return MERR_DEVICE_NOT_AVAILABLE;
-	}
+	int result = MidiDriver_ADLIB_Multisource::open();
+	if (result >= 0)
+		// Simon 1 has the OPL rhythm mode permanently enabled.
+		setRhythmMode(true);
 
-	if (!_opl->init()) {
-		delete _opl;
-		_opl = nullptr;
-
-		return MERR_CANNOT_CONNECT;
-	}
-
-	_opl->start(new Common::Functor0Mem<void, MidiDriver_Simon1_AdLib>(this, &MidiDriver_Simon1_AdLib::onTimer));
-
-	_opl->writeReg(0x01, 0x20);
-	_opl->writeReg(0x08, 0x40);
-	_opl->writeReg(0xBD, 0xC0);
-	reset();
-
-	_isOpen = true;
-	return 0;
-}
-
-bool MidiDriver_Simon1_AdLib::isOpen() const {
-	return _isOpen;
+	return result;
 }
 
-void MidiDriver_Simon1_AdLib::close() {
-	setTimerCallback(nullptr, nullptr);
-
-	if (_isOpen) {
-		_opl->stop();
-		delete _opl;
-		_opl = nullptr;
-
-		_isOpen = false;
+void MidiDriver_Simon1_AdLib::parseInstrumentData(const byte *instrumentData) {
+	const byte *dataPtr = instrumentData;
+
+	// The instrument data consists of 128 16-byte entries.
+	_instrumentBank = new OplInstrumentDefinition[128];
+
+	for (int i = 0; i < 128; i++) {
+		_instrumentBank[i].fourOperator = false;
+
+		_instrumentBank[i].operator0.freqMultMisc = *dataPtr++;
+		_instrumentBank[i].operator1.freqMultMisc = *dataPtr++;
+		_instrumentBank[i].operator0.level = *dataPtr++;
+		_instrumentBank[i].operator1.level = *dataPtr++;
+		_instrumentBank[i].operator0.decayAttack = *dataPtr++;
+		_instrumentBank[i].operator1.decayAttack = *dataPtr++;
+		_instrumentBank[i].operator0.releaseSustain = *dataPtr++;
+		_instrumentBank[i].operator1.releaseSustain = *dataPtr++;
+		_instrumentBank[i].operator0.waveformSelect = *dataPtr++;
+		_instrumentBank[i].operator1.waveformSelect = *dataPtr++;
+
+		_instrumentBank[i].connectionFeedback0 = *dataPtr++;
+		_instrumentBank[i].connectionFeedback1 = 0;
+		_instrumentBank[i].rhythmNote = 0;
+		_instrumentBank[i].rhythmType = RHYTHM_TYPE_UNDEFINED;
+
+		// Remaining bytes seem to be unused.
+		dataPtr += 5;
 	}
-}
-
-void MidiDriver_Simon1_AdLib::send(uint32 b) {
-	int channel = b & 0x0F;
-	int command = b & 0xF0;
-	int param1  = (b >>  8) & 0xFF;
-	int param2  = (b >> 16) & 0xFF;
-
-	// The percussion channel is handled specially. The AdLib output uses
-	// channels 11 to 15 for percussions. For this, the original converted
-	// note on on the percussion channel to note on channels 11 to 15 before
-	// giving it to the AdLib output. We do this in here for simplicity.
-	if (command == 0x90 && channel == 9) {
-		param1 -= 36;
-		if (param1 < 0 || param1 >= ARRAYSIZE(_rhythmMap)) {
-			return;
-		}
 
-		channel = _rhythmMap[param1].channel;
-		MidiDriver::send(0xC0 | channel, _rhythmMap[param1].program, 0);
+	// Construct a rhythm bank from the original rhythm map data.
+	_rhythmBank = new OplInstrumentDefinition[39];
+	// MIDI note range 36-74.
+	_rhythmBankFirstNote = 36;
+	_rhythmBankLastNote = 36 + 39 - 1;
 
-		param1 = _rhythmMap[param1].note;
-		MidiDriver::send(0x80 | channel, param1, param2);
-
-		param2 >>= 1;
-	}
-
-	switch (command) {
-	case 0x80: // note OFF
-		noteOff(channel, param1);
-		break;
-
-	case 0x90: // note ON
-		if (param2 == 0) {
-			noteOff(channel, param1);
+	for (int i = 0; i < 39; i++) {
+		if (RHYTHM_MAP[i].channel == 0) {
+			// Some notes in the range have no definition.
+			_rhythmBank[i].rhythmType = RHYTHM_TYPE_UNDEFINED;
 		} else {
-			noteOn(channel, param1, param2);
+			// The rhythm bank makes use of instruments defined in the main instrument bank.
+			_rhythmBank[i] = _instrumentBank[RHYTHM_MAP[i].program];
+			// The MIDI channels used in the rhythm map correspond to OPL rhythm instrument types:
+			// 11 - bass drum
+			// 12 - snare drum
+			// 13 - tom tom
+			// 14 - cymbal
+			// 15 - hi-hat
+			_rhythmBank[i].rhythmType = static_cast<OplInstrumentRhythmType>(6 - (RHYTHM_MAP[i].channel - 10));
+			_rhythmBank[i].rhythmNote = RHYTHM_MAP[i].note;
 		}
-		break;
-
-	case 0xB0: // control change
-		controlChange(channel, param1, param2);
-		break;
-
-	case 0xC0: // program change
-		programChange(channel, param1);
-		break;
-
-	default:
-		break;
-	}
-}
-
-void MidiDriver_Simon1_AdLib::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
-	_timerParam = timer_param;
-	_timerProc = timer_proc;
-}
-
-uint32 MidiDriver_Simon1_AdLib::getBaseTempo() {
-	return 1000000 / OPL::OPL::kDefaultCallbackFrequency;
-}
-
-void MidiDriver_Simon1_AdLib::onTimer() {
-	if (_timerProc) {
-		(*_timerProc)(_timerParam);
-	}
-}
-
-void MidiDriver_Simon1_AdLib::reset() {
-	resetOPLVoices();
-	resetRhythm();
-	for (int i = 0; i < kNumberOfVoices; ++i) {
-		_voices[i].channel = kChannelUnused;
 	}
-	resetVoices();
 }
 
-void MidiDriver_Simon1_AdLib::resetOPLVoices() {
-	_amvdrBits &= 0xE0;
-	_opl->writeReg(0xBD, _amvdrBits);
-	for (int i = 8; i >= 0; --i) {
-		_opl->writeReg(0xB0 + i, 0);
-	}
-}
+void MidiDriver_Simon1_AdLib::noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
+	if (_musicRhythmNotesDisabled && _sources[source].type != SOURCE_TYPE_SFX && channel == MIDI_RHYTHM_CHANNEL)
+		// A music source played a rhythm note while these are disabled.
+		// Ignore this event.
+		return;
+	if (_sources[source].type == SOURCE_TYPE_SFX)
+		// The original interpreter uses max velocity for all SFX notes.
+		velocity = 0x7F;
 
-void MidiDriver_Simon1_AdLib::resetRhythm() {
-	_melodyVoices = 9;
-	_amvdrBits = 0xC0;
-	_opl->writeReg(0xBD, _amvdrBits);
+	MidiDriver_ADLIB_Multisource::noteOn(channel, note, velocity, source);
 }
 
-void MidiDriver_Simon1_AdLib::resetVoices() {
-	memset(_midiPrograms, 0, sizeof(_midiPrograms));
-	for (int i = 0; i < kNumberOfVoices; ++i) {
-		_voices[i].channel = kChannelUnused;
-	}
-
-	for (int i = 0; i < kOPLVoicesCount; ++i) {
-		resetRhythm();
-		_opl->writeReg(0x08, 0x00);
+void MidiDriver_Simon1_AdLib::programChange(uint8 channel, uint8 program, uint8 source) {
+	MidiDriver_ADLIB_Multisource::programChange(channel, program, source);
 
-		int oplRegister = _operatorMap[i];
-		for (int j = 0; j < 4; ++j) {
-			oplRegister += 0x20;
+	_activeNotesMutex.lock();
 
-			_opl->writeReg(oplRegister + 0, _operatorDefaults[2 * j + 0]);
-			_opl->writeReg(oplRegister + 3, _operatorDefaults[2 * j + 1]);
+	// Deallocate all inactive OPL channels for this MIDI channel and source.
+	for (int i = 0; i < _numMelodicChannels; i++) {
+		uint8 oplChannel = _melodicChannels[i];
+		if (_activeNotes[oplChannel].channelAllocated && !_activeNotes[oplChannel].noteActive &&
+			_activeNotes[oplChannel].channel == channel && _activeNotes[oplChannel].source == source) {
+			_activeNotes[oplChannel].channelAllocated = false;
 		}
-
-		_opl->writeReg(oplRegister + 0x60, 0x00);
-		_opl->writeReg(oplRegister + 0x63, 0x00);
-
-		// This seems to be serious bug but the original does it the same way.
-		_opl->writeReg(_operatorMap[i] + i, 0x08);
 	}
-}
 
-int MidiDriver_Simon1_AdLib::allocateVoice(uint channel) {
-	for (int i = 0; i < _melodyVoices; ++i) {
-		if (_voices[i].channel == (channel | kChannelOrphanedFlag)) {
-			return i;
-		}
-	}
+	_activeNotesMutex.unlock();
 
-	for (int i = 0; i < _melodyVoices; ++i) {
-		if (_voices[i].channel == kChannelUnused) {
-			return i;
-		}
-	}
-
-	for (int i = 0; i < _melodyVoices; ++i) {
-		if (_voices[i].channel > 0x7F) {
-			return i;
-		}
-	}
-
-	// The original had some logic for a priority based reuse of channels.
-	// However, the priority value is always 0, which causes the first channel
-	// to be picked all the time.
-	const int voice = 0;
-	_opl->writeReg(0xA0 + voice, (_voices[voice].frequency     ) & 0xFF);
-	_opl->writeReg(0xB0 + voice, (_voices[voice].frequency >> 8) & 0xFF);
-	return voice;
+	// Note: the original also sets up the new instrument on active OPL
+	// channels, i.e. channels which are currently playing a note. This is
+	// against the MIDI spec, which states that program changes should not
+	// affect active notes, and against the behavior of the MT-32, for which
+	// the music is composed. So instead, the new instrument is set up when a
+	// new note is played on these OPL channels.
 }
 
-void MidiDriver_Simon1_AdLib::noteOff(uint channel, uint note) {
-	if (_melodyVoices <= 6 && channel >= 11) {
-		_amvdrBits &= ~(_rhythmInstrumentMask[channel - 11]);
-		_opl->writeReg(0xBD, _amvdrBits);
-	} else {
-		for (int i = 0; i < _melodyVoices; ++i) {
-			if (_voices[i].note == note && _voices[i].channel == channel) {
-				_voices[i].channel |= kChannelOrphanedFlag;
-				_opl->writeReg(0xA0 + i, (_voices[i].frequency     ) & 0xFF);
-				_opl->writeReg(0xB0 + i, (_voices[i].frequency >> 8) & 0xFF);
-				return;
-			}
-		}
-	}
-}
-
-void MidiDriver_Simon1_AdLib::noteOn(uint channel, uint note, uint velocity) {
-	if (_rhythmEnabled && channel >= 11) {
-		noteOnRhythm(channel, note, velocity);
-		return;
-	}
-
-	const int voiceNum = allocateVoice(channel);
-	Voice &voice = _voices[voiceNum];
-
-	if ((voice.channel & 0x7F) != channel) {
-		setupInstrument(voiceNum, _midiPrograms[channel]);
-	}
-	voice.channel = channel;
-
-	_opl->writeReg(0x43 + _operatorMap[voiceNum], (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel);
-
-	voice.note = note;
-	if (note >= 0x80) {
-		note = 0;
-	}
-
-	const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note];
-	const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F];
+void MidiDriver_Simon1_AdLib::deinitSource(uint8 source) {
+	if (_sources[source].type != SOURCE_TYPE_MUSIC)
+		// When a sound effect has finished playing, re-enable music rhythm
+		// notes.
+		_musicRhythmNotesDisabled = false;
 
-	uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2);
-	uint lowByte  = frequency & 0x00FF;
-	voice.frequency = (highByte << 8) | lowByte;
-
-	_opl->writeReg(0xA0 + voiceNum, lowByte);
-	_opl->writeReg(0xB0 + voiceNum, highByte | 0x20);
+	MidiDriver_ADLIB_Multisource::deinitSource(source);
 }
 
-void MidiDriver_Simon1_AdLib::noteOnRhythm(uint channel, uint note, uint velocity) {
-	const uint voiceNum = channel - 5;
-	Voice &voice = _voices[voiceNum];
-
-	_amvdrBits |= _rhythmInstrumentMask[voiceNum - 6];
-
-	const uint level = (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel;
-	if (voiceNum == 6) {
-		_opl->writeReg(0x43 + _rhythmOperatorMap[voiceNum - 6], level);
-	} else {
-		_opl->writeReg(0x40 + _rhythmOperatorMap[voiceNum - 6], level);
-	}
-
-	voice.note = note;
-	if (note >= 0x80) {
-		note = 0;
-	}
-
-	const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note];
-	const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F];
-
-	uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2);
-	uint lowByte  = frequency & 0x00FF;
-	voice.frequency = (highByte << 8) | lowByte;
-
-	const uint oplOperator = _rhythmVoiceMap[voiceNum - 6];
-	_opl->writeReg(0xA0 + oplOperator, lowByte);
-	_opl->writeReg(0xB0 + oplOperator, highByte);
-
-	_opl->writeReg(0xBD, _amvdrBits);
+void MidiDriver_Simon1_AdLib::disableMusicRhythmNotes() {
+	_musicRhythmNotesDisabled = true;
 }
 
-void MidiDriver_Simon1_AdLib::controlChange(uint channel, uint controller, uint value) {
-	// Enable/Disable Rhythm Section
-	if (controller == 0x67) {
-		resetVoices();
-		_rhythmEnabled = (value != 0);
-
-		if (_rhythmEnabled) {
-			_melodyVoices = 6;
-			_amvdrBits = 0xE0;
-		} else {
-			_melodyVoices = 9;
-			_amvdrBits = 0xC0;
+uint8 MidiDriver_Simon1_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+	// When allocating an OPL channel for playback of a note, the algorithm
+	// looks for the following types of channels:
+	// - An OPL channel already allocated to this source and MIDI channel that
+	//   is not playing a note.
+	// - An unallocated OPL channel.
+	// - An OPL channel allocated to a different source and/or MIDI channel
+	//   that is not playing a note.
+	//
+	// If no free OPL channel could be found, an active channel is "stolen" and
+	// the note it is currently playing is cut off. This channel is always
+	// channel 0.
+	uint8 allocatedChannel = 0xFF;
+
+	uint8 unallocatedChannel = 0xFF;
+	uint8 inactiveChannel = 0xFF;
+	for (int i = 0; i < _numMelodicChannels; i++) {
+		uint8 oplChannel = _melodicChannels[i];
+		if (_activeNotes[oplChannel].channelAllocated && _activeNotes[oplChannel].channel == channel &&
+			_activeNotes[oplChannel].source == source && !_activeNotes[oplChannel].noteActive) {
+			// Found an OPL channel already allocated to this source and MIDI
+			// channel that is not playing a note.
+			allocatedChannel = oplChannel;
+			// Always use the first available channel of this type.
+			break;
 		}
 
-		_voices[6].channel = kChannelUnused;
-		_voices[7].channel = kChannelUnused;
-		_voices[8].channel = kChannelUnused;
+		if (!_activeNotes[oplChannel].channelAllocated && unallocatedChannel == 0xFF)
+			// Found an unallocated OPL channel.
+			unallocatedChannel = oplChannel;
 
-		_opl->writeReg(0xBD, _amvdrBits);
+		if (!_activeNotes[oplChannel].noteActive && inactiveChannel == 0xFF)
+			// Found an OPL channel allocated to a different source and/or MIDI
+			// channel that is not playing a note.
+			inactiveChannel = oplChannel;
 	}
-}
-
-void MidiDriver_Simon1_AdLib::programChange(uint channel, uint program) {
-	_midiPrograms[channel] = program;
-
-	if (_rhythmEnabled && channel >= 11) {
-		setupInstrument(channel - 5, program);
-	} else {
-		// Fully unallocate all previously allocated but now unused voices for
-		// this MIDI channel.
-		for (uint i = 0; i < kOPLVoicesCount; ++i) {
-			if (_voices[i].channel == (channel | kChannelOrphanedFlag)) {
-				_voices[i].channel = kChannelUnused;
-			}
-		}
+	if (allocatedChannel == 0xFF) {
+		// No allocated channel found.
+		if (unallocatedChannel != 0xFF) {
+			// Found an unallocated channel - use this.
+			allocatedChannel = unallocatedChannel;
+		} else if (inactiveChannel != 0xFF) {
+			// Found an inactive channel - use this.
+			allocatedChannel = inactiveChannel;
+		} else {
+			// An channel already playing a note must be "stolen".
 
-		// Set the program for all voices allocted for this MIDI channel.
-		for (uint i = 0; i < kOPLVoicesCount; ++i) {
-			if (_voices[i].channel == channel) {
-				setupInstrument(i, program);
-			}
+			// The original had some logic for a priority based reuse of
+			// channels. However, the priority value is always 0, which causes
+			// the first channel to be picked all the time.
+			allocatedChannel = 0;
 		}
 	}
-}
-
-void MidiDriver_Simon1_AdLib::setupInstrument(uint voice, uint instrument) {
-	const byte *instrumentData = _instruments + instrument * 16;
-
-	int scaling = instrumentData[3];
-	if (_rhythmEnabled && voice >= 7) {
-		scaling = instrumentData[2];
-	}
 
-	const int scalingLevel = scaling & 0xC0;
-	const int totalLevel   = scaling & 0x3F;
+	if (_activeNotes[allocatedChannel].noteActive)
+		// Turn off the current note if the channel was "stolen".
+		writeKeyOff(allocatedChannel);
+	_activeNotes[allocatedChannel].channelAllocated = true;
+	_activeNotes[allocatedChannel].source = source;
+	_activeNotes[allocatedChannel].channel = channel;
 
-	_voices[voice].instrScalingLevel = scalingLevel;
-	_voices[voice].instrTotalLevel   = (-(totalLevel - 0x3F)) & 0xFF;
-
-	if (!_rhythmEnabled || voice <= 6) {
-		int oplRegister = _operatorMap[voice];
-		for (int j = 0; j < 4; ++j) {
-			oplRegister += 0x20;
-			_opl->writeReg(oplRegister + 0, *instrumentData++);
-			_opl->writeReg(oplRegister + 3, *instrumentData++);
-		}
-		oplRegister += 0x60;
-		_opl->writeReg(oplRegister + 0, *instrumentData++);
-		_opl->writeReg(oplRegister + 3, *instrumentData++);
-
-		_opl->writeReg(0xC0 + voice, *instrumentData++);
-	} else {
-		voice -= 7;
-
-		int oplRegister = _rhythmOperatorMap[voice + 1];
-		for (int j = 0; j < 4; ++j) {
-			oplRegister += 0x20;
-			_opl->writeReg(oplRegister + 0, *instrumentData++);
-			++instrumentData;
-		}
-		oplRegister += 0x60;
-		_opl->writeReg(oplRegister + 0, *instrumentData++);
-		++instrumentData;
-
-		_opl->writeReg(0xC0 + _rhythmVoiceMap[voice + 1], *instrumentData++);
-	}
+	return allocatedChannel;
 }
 
-const int MidiDriver_Simon1_AdLib::_operatorMap[9] = {
-	0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11,
-	0x12
-};
-
-const int MidiDriver_Simon1_AdLib::_operatorDefaults[8] = {
-	0x01, 0x11, 0x4F, 0x00, 0xF1, 0xF2, 0x53, 0x74
-};
+uint16 MidiDriver_Simon1_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
+	// Determine the octave note. Notes 120-127 are clipped to octave note 12.
+	uint8 octaveNote = note >= 120 ? 12 : note % 12;
+	// Determine the octave / block. Notes 12-96 are in octaves 0-7, with lower
+	// and higher notes clipped to octave 0 and 7, respectively.
+	uint8 octave = CLIP((note / 12) - 1, 0, 7);
 
-const int MidiDriver_Simon1_AdLib::_rhythmOperatorMap[5] = {
-	0x10, 0x14, 0x12, 0x15, 0x11
-};
+	// Look up the OPL frequency / F-num.
+	uint16 octaveNoteFrequency = FREQUENCY_TABLE[octaveNote];
 
-const uint MidiDriver_Simon1_AdLib::_rhythmInstrumentMask[5] = {
-	0x10, 0x08, 0x04, 0x02, 0x01
-};
+	// Combine block and F-num in the format used by the OPL Ax and Bx
+	// registers.
+	return (octave << 10) | octaveNoteFrequency;
+}
 
-const int MidiDriver_Simon1_AdLib::_rhythmVoiceMap[5] = {
-	6, 7, 8, 8, 7
-};
+uint8 MidiDriver_Simon1_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
+	if (channel == MIDI_RHYTHM_CHANNEL && _sources[source].type != SOURCE_TYPE_SFX)
+		// The original interpreter halves the velocity for music rhythm notes.
+		// Note that SFX notes always use max velocity.
+		velocity >>= 1;
 
-const int MidiDriver_Simon1_AdLib::_frequencyIndexAndOctaveTable[128] = {
-	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-	0x08, 0x09, 0x0A, 0x0B, 0x00, 0x01, 0x02, 0x03,
-	0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
-	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-	0x18, 0x19, 0x1A, 0x1B, 0x20, 0x21, 0x22, 0x23,
-	0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B,
-	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
-	0x38, 0x39, 0x3A, 0x3B, 0x40, 0x41, 0x42, 0x43,
-	0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B,
-	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
-	0x58, 0x59, 0x5A, 0x5B, 0x60, 0x61, 0x62, 0x63,
-	0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
-	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
-	0x78, 0x79, 0x7A, 0x7B, 0x70, 0x71, 0x72, 0x73,
-	0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B,
-	0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B
-};
+	// Invert the instrument definition attenuation.
+	uint8 instDefVolume = 0x3F - (instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F);
+	// Calculate the note volume using velocity and instrument definition
+	// volume.
+	uint8 calculatedVolume = ((velocity | 0x80) * instDefVolume) >> 8;
 
-const int MidiDriver_Simon1_AdLib::_frequencyTable[16] = {
-	0x0157, 0x016B, 0x0181, 0x0198, 0x01B0, 0x01CA, 0x01E5, 0x0202,
-	0x0220, 0x0241, 0x0263, 0x0287, 0x2100, 0xD121, 0xA307, 0x46A4
-};
-
-const MidiDriver_Simon1_AdLib::RhythmMap MidiDriver_Simon1_AdLib::_rhythmMap[39] = {
-	{ 11, 123,  40 },
-	{ 12, 127,  50 },
-	{ 12, 124,   1 },
-	{ 12, 124,  90 },
-	{ 13, 125,  50 },
-	{ 13, 125,  25 },
-	{ 15, 127,  80 },
-	{ 13, 125,  25 },
-	{ 15, 127,  40 },
-	{ 13, 125,  35 },
-	{ 15, 127,  90 },
-	{ 13, 125,  35 },
-	{ 13, 125,  45 },
-	{ 14, 126,  90 },
-	{ 13, 125,  45 },
-	{ 15, 127,  90 },
-	{  0,   0,   0 },
-	{ 15, 127,  60 },
-	{  0,   0,   0 },
-	{ 13, 125,  60 },
-	{  0,   0,   0 },
-	{  0,   0,   0 },
-	{  0,   0,   0 },
-	{ 13, 125,  45 },
-	{ 13, 125,  40 },
-	{ 13, 125,  35 },
-	{ 13, 125,  30 },
-	{ 13, 125,  25 },
-	{ 13, 125,  80 },
-	{ 13, 125,  40 },
-	{ 13, 125,  80 },
-	{ 13, 125,  40 },
-	{ 14, 126,  40 },
-	{ 15, 127,  60 },
-	{  0,   0,   0 },
-	{  0,   0,   0 },
-	{ 14, 126,  80 },
-	{  0,   0,   0 },
-	{ 13, 125, 100 }
-};
+	// Invert the calculated volume to an attenuation.
+	return 0x3F - calculatedVolume;
+}
 
-MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename) {
+MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename) {
 	// Load instrument data.
 	Common::File ibk;
 
@@ -499,17 +307,22 @@ MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename) {
 		return nullptr;
 	}
 
+	// Check for the expected FourCC (IBK\x1A)
 	if (ibk.readUint32BE() != 0x49424b1a) {
 		return nullptr;
 	}
 
 	byte *instrumentData = new byte[128 * 16];
 	if (ibk.read(instrumentData, 128 * 16) != 128 * 16) {
+		// Failed to read the expected amount of data.
 		delete[] instrumentData;
 		return nullptr;
 	}
 
-	return new MidiDriver_Simon1_AdLib(instrumentData);
+	MidiDriver_Simon1_AdLib *driver = new MidiDriver_Simon1_AdLib(OPL::Config::kOpl3, instrumentData);
+	delete[] instrumentData;
+
+	return driver;
 }
 
 } // End of namespace AGOS
diff --git a/engines/agos/drivers/simon1/adlib.h b/engines/agos/drivers/simon1/adlib.h
index cc243c50be0..f84d7a9aeb3 100644
--- a/engines/agos/drivers/simon1/adlib.h
+++ b/engines/agos/drivers/simon1/adlib.h
@@ -22,95 +22,56 @@
 #ifndef AGOS_SIMON1_ADLIB_H
 #define AGOS_SIMON1_ADLIB_H
 
-#include "audio/mididrv.h"
-#include "audio/fmopl.h"
+#include "audio/adlib_ms.h"
 
 namespace AGOS {
 
-class MidiDriver_Simon1_AdLib : public MidiDriver {
-public:
-	MidiDriver_Simon1_AdLib(const byte *instrumentData);
-	~MidiDriver_Simon1_AdLib() override;
-
-	// MidiDriver API
-	int open() override;
-	bool isOpen() const override;
-	void close() override;
-
-	void send(uint32 b) override;
-
-	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
-	uint32 getBaseTempo() override;
-
-	MidiChannel *allocateChannel() override { return 0; }
-	MidiChannel *getPercussionChannel() override { return 0; }
+class MidiDriver_Simon1_AdLib : public MidiDriver_ADLIB_Multisource {
 private:
-	bool _isOpen;
-
-	OPL::OPL *_opl;
-
-	Common::TimerManager::TimerProc _timerProc;
-	void *_timerParam;
-	void onTimer();
-
-	void reset();
-	void resetOPLVoices();
-
-	void resetRhythm();
-	int _melodyVoices;
-	uint8 _amvdrBits;
-	bool _rhythmEnabled;
-
-	enum {
-		kNumberOfVoices = 11,
-		kNumberOfMidiChannels = 16
+	struct RhythmMapEntry {
+		// The OPL rhythm instrument to use.
+		// The original interpreter would move a note played on the MIDI rhythm
+		// channel to one of MIDI channels 11-15, each corresponding to an OPL
+		// rhythm instrument.
+		uint8 channel;
+		// The instrument bank entry used to play the rhythm note.
+		uint8 program;
+		// The MIDI note number that is actually played.
+		uint8 note;
 	};
 
-	struct Voice {
-		Voice();
-
-		uint channel;
-		uint note;
-		uint instrTotalLevel;
-		uint instrScalingLevel;
-		uint frequency;
-	};
-
-	void resetVoices();
-	int allocateVoice(uint channel);
-
-	Voice _voices[kNumberOfVoices];
-	uint _midiPrograms[kNumberOfMidiChannels];
+public:
+	MidiDriver_Simon1_AdLib(OPL::Config::OplType oplType, const byte *instrumentData);
+	~MidiDriver_Simon1_AdLib();
 
-	void noteOff(uint channel, uint note);
-	void noteOn(uint channel, uint note, uint velocity);
-	void noteOnRhythm(uint channel, uint note, uint velocity);
-	void controlChange(uint channel, uint controller, uint value);
-	void programChange(uint channel, uint program);
+	int open() override;
 
-	void setupInstrument(uint voice, uint instrument);
-	const byte *_instruments;
+	void noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) override;
+	void programChange(uint8 channel, uint8 program, uint8 source) override;
 
-	static const int _operatorMap[9];
-	static const int _operatorDefaults[8];
+	void deinitSource(uint8 source) override;
 
-	static const int _rhythmOperatorMap[5];
-	static const uint _rhythmInstrumentMask[5];
-	static const int _rhythmVoiceMap[5];
+	// Turns off rhythm notes for sources with type MUSIC (typically source 0).
+	// This should be called when a SFX source that uses rhythm notes starts
+	// playing to prevent conflicts on the rhythm channels. Deinitializing a
+	// SFX source will turn rhythm notes back on.
+	void disableMusicRhythmNotes();
 
-	static const int _frequencyIndexAndOctaveTable[128];
-	static const int _frequencyTable[16];
+private:
+	static const RhythmMapEntry RHYTHM_MAP[];
+	static const uint16 FREQUENCY_TABLE[];
 
-	struct RhythmMap {
-		int channel;
-		int program;
-		int note;
-	};
+	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
+	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity,
+								  OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
+	void parseInstrumentData(const byte *instrumentData);
 
-	static const RhythmMap _rhythmMap[39];
+	// True if rhythm notes for sources with type MUSIC should not be played.
+	bool _musicRhythmNotesDisabled;
 };
 
-MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename);
+MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename);
 
 } // End of namespace AGOS
 
diff --git a/engines/agos/drivers/simon1/adlib_win.cpp b/engines/agos/drivers/simon1/adlib_win.cpp
new file mode 100644
index 00000000000..739426e0bc1
--- /dev/null
+++ b/engines/agos/drivers/simon1/adlib_win.cpp
@@ -0,0 +1,38 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agos/drivers/simon1/adlib_win.h"
+
+namespace AGOS {
+
+MidiDriver_Simon1_AdLib_Windows::MidiDriver_Simon1_AdLib_Windows() : MidiDriver_ADLIB_Multisource(OPL::Config::kOpl3) { }
+
+void MidiDriver_Simon1_AdLib_Windows::programChange(uint8 channel, uint8 program, uint8 source) {
+	// WORKAROUND The Windows version of Simon The Sorcerer uses the MT-32 MIDI
+	// data of the DOS versions, but plays this using Windows' General MIDI
+	// system. As a result, the music is played using different instruments
+	// than intended. This is fixed here by mapping the MT-32 instruments to
+	// GM instruments using MidiDriver's standard mapping.
+	uint8 gmInstrument = _mt32ToGm[program];
+	MidiDriver_ADLIB_Multisource::programChange(channel, gmInstrument, source);
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/simon1/adlib_win.h b/engines/agos/drivers/simon1/adlib_win.h
new file mode 100644
index 00000000000..9ca679d411e
--- /dev/null
+++ b/engines/agos/drivers/simon1/adlib_win.h
@@ -0,0 +1,44 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGOS_SIMON1_ADLIB_WIN_H
+#define AGOS_SIMON1_ADLIB_WIN_H
+
+#include "audio/adlib_ms.h"
+
+namespace AGOS {
+
+/**
+ * AdLib MIDI driver for the Windows version of Simon The Sorcerer.
+ * This driver contains a workaround for converting the MT-32 instruments to
+ * the General MIDI instruments used by this driver.
+ */
+class MidiDriver_Simon1_AdLib_Windows : public MidiDriver_ADLIB_Multisource {
+public:
+	MidiDriver_Simon1_AdLib_Windows();
+
+protected:
+	void programChange(uint8 channel, uint8 program, uint8 source) override;
+};
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index dbf03668161..9e6f7546068 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS := \
 	drivers/accolade/pc98.o \
 	drivers/accolade/mt32.o \
 	drivers/simon1/adlib.o \
+	drivers/simon1/adlib_win.o \
 	agos.o \
 	charset.o \
 	charset-fontdata.o \


Commit: 7b3949aed664fb99bc7d95e5e6fbd2a89264671b
    https://github.com/scummvm/scummvm/commit/7b3949aed664fb99bc7d95e5e6fbd2a89264671b
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:42+02:00

Commit Message:
AGOS: Improve Simon 1 MIDI code

The MIDI code now supports mixed MIDI mode for the DOS floppy version of
Simon 1. The code for loading the various MIDI formats has been simplified.

Changed paths:
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/res_snd.cpp


diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 40b91e9fc73..925a66f2b3d 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -27,10 +27,14 @@
 #include "agos/agos.h"
 #include "agos/midi.h"
 
+#include "agos/midiparser_gmf.h"
+#include "agos/midiparser_simonwin.h"
+
 #include "agos/drivers/accolade/mididriver.h"
 #include "agos/drivers/accolade/adlib.h"
 #include "agos/drivers/accolade/mt32.h"
 #include "agos/drivers/simon1/adlib.h"
+#include "agos/drivers/simon1/adlib_win.h"
 // Miles Audio for Simon 2
 #include "audio/miles.h"
 
@@ -39,6 +43,7 @@
 #include "common/translation.h"
 
 #include "gui/message.h"
+#include <engines/agos/intern_detection.h>
 
 namespace AGOS {
 
@@ -48,15 +53,16 @@ namespace AGOS {
 // and just provide a factory function.
 extern MidiParser *MidiParser_createS1D();
 
-MidiPlayer::MidiPlayer() {
+MidiPlayer::MidiPlayer(AGOSEngine *vm) {
 	// Since initialize() is called every time the music changes,
 	// this is where we'll initialize stuff that must persist
 	// between songs.
+	_vm = vm;
 	_driver = nullptr;
+	_driverMsMusic = nullptr;
+	_driverMsSfx = nullptr;
 	_map_mt32_to_gm = false;
 
-	_adLibMusic = false;
-	_enable_sfx = true;
 	_current = nullptr;
 
 	_musicVolume = 255;
@@ -71,19 +77,47 @@ MidiPlayer::MidiPlayer() {
 	_loopQueuedTrack = 0;
 
 	_musicMode = kMusicModeDisabled;
+
+	_parserMusic = nullptr;
+	_parserSfx = nullptr;
+	_musicData = nullptr;
+	_sfxData = nullptr;
 }
 
 MidiPlayer::~MidiPlayer() {
 	stop();
+	stopSfx();
 
-	if (_driver) {
+	if (_driverMsSfx && _driverMsSfx != _driverMsMusic) {
+		_driverMsSfx->setTimerCallback(nullptr, nullptr);
+		_driverMsSfx->close();
+		delete _driverMsSfx;
+		_driverMsSfx = nullptr;
+	}
+	if (_driverMsMusic) {
+		_driverMsMusic->setTimerCallback(nullptr, nullptr);
+		_driverMsMusic->close();
+		delete _driverMsMusic;
+		_driverMsMusic = nullptr;
+	} else if (_driver) {
 		_driver->setTimerCallback(nullptr, nullptr);
 		_driver->close();
 		delete _driver;
+		_driver = nullptr;
 	}
-	_driver = nullptr;
 
 	Common::StackLock lock(_mutex);
+
+	if (_parserMusic)
+		delete _parserMusic;
+	if (_parserSfx)
+		delete _parserSfx;
+
+	if (_musicData)
+		delete[] _musicData;
+	if (_sfxData)
+		delete[] _sfxData;
+
 	clearConstructs();
 }
 
@@ -92,9 +126,168 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	assert(!_driver);
 
 	Common::String accoladeDriverFilename;
-	musicType = MT_INVALID;
 	int devFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
 
+	if (_vm->getGameType() == GType_SIMON1) {
+		// Check the type of device that the user has configured.
+		MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
+		musicType = MidiDriver::getMusicType(dev);
+		if (musicType == MT_GM && ConfMan.getBool("native_mt32"))
+			musicType = MT_MT32;
+
+		// All versions of Simon 1 that use MIDI have data that targets the
+		// MT-32 (even the Windows and Acorn versions).
+		MusicType dataType = MT_MT32;
+
+		if (dataType == MT_MT32 && musicType == MT_GM) {
+			// Not a real MT32 / no MUNT
+			::GUI::MessageDialog dialog(_(
+				"You appear to be using a General MIDI device,\n"
+				"but your game only supports Roland MT32 MIDI.\n"
+				"We try to map the Roland MT32 instruments to\n"
+				"General MIDI ones. It is still possible that\n"
+				"some tracks sound incorrect."));
+			dialog.runModal();
+		}
+
+		if (_vm->getPlatform() == Common::kPlatformDOS &&
+				(_vm->getFeatures() & GF_DEMO) && !(_vm->getFeatures() & GF_TALKIE)) {
+			// DOS floppy demo uses the older Accolade music drivers.
+			_musicMode = kMusicModeAccolade;
+			accoladeDriverFilename = "MUSIC.DRV";
+
+			switch (musicType) {
+			case MT_ADLIB:
+				_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
+				break;
+			case MT_MT32:
+				_driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
+				break;
+			default:
+				_driver = new MidiDriver_NULL_Multisource();
+				break;
+			}
+
+			// Create the MIDI parser for the MUS format used by this version.
+			_parserMusic = MidiParser_createS1D();
+
+			// Open the MIDI driver.
+			int returnCode = _driver->open();
+			if (returnCode != 0)
+				error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
+
+			// Connect the driver and the parser.
+			_parserMusic->setMidiDriver(_driver);
+			_parserMusic->setTimerRate(_driver->getBaseTempo());
+
+			_driver->setTimerCallback(_parserMusic, &_parserMusic->timerCallback);
+
+			return 0;
+		} else {
+			// Create the correct driver(s) for the device type and platform.
+			switch (musicType) {
+			case MT_ADLIB:
+				if (_vm->getPlatform() == Common::kPlatformDOS) {
+					// DOS versions use a specific AdLib driver implementation
+					// that needs an instrument bank file.
+					_driverMsMusic = createMidiDriverSimon1AdLib("MT_FM.IBK");
+					if (!(_vm->getFeatures() & GF_TALKIE)) {
+						// DOS floppy version has MIDI SFX for AdLib.
+						_driverMsSfx = _driverMsMusic;
+					}
+				} else {
+					// Windows and Acorn CD
+					// TODO Acorn does not use an OPL chip, but we don't have
+					// an implementation for the Acorn audio. It has the same
+					// music data has the DOS CD version, but it does not have
+					// the MT_FM.IBK instrument bank, so we use the standard
+					// OPL MIDI driver.
+					_driverMsMusic = new MidiDriver_Simon1_AdLib_Windows();
+				}
+				break;
+			case MT_MT32:
+			case MT_GM:
+				_driverMsMusic = new MidiDriver_MT32GM(dataType);
+				if (_vm->getPlatform() == Common::kPlatformDOS && !(_vm->getFeatures() & GF_TALKIE) &&
+						ConfMan.getBool("multi_midi")) {
+					// DOS floppy version uses AdLib MIDI SFX if mixed MIDI
+					// mode is active.
+					_driverMsSfx = createMidiDriverSimon1AdLib("MT_FM.IBK");
+				}
+				break;
+			default:
+				_driverMsMusic = new MidiDriver_NULL_Multisource();
+				break;
+			}
+
+			// Create the MIDI parser(s) for the format used by the platform.
+			if (_vm->getPlatform() == Common::kPlatformWindows) {
+				// Windows version uses a specific SMF variant.
+				_parserMusic = new MidiParser_SimonWin(0);
+			} else {
+				// DOS floppy & CD and Acorn CD use GMF (also an SMF variant).
+				_parserMusic = new MidiParser_GMF(0);
+
+				if (_driverMsSfx) {
+					// DOS floppy needs a second parser for AdLib SFX.
+					_parserSfx = new MidiParser_GMF(1);
+					_parserMusic->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
+					_parserSfx->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
+				}
+			}
+			_driver = _driverMsMusic;
+
+			// WORKAROUND The Simon 1 MIDI data does not always set a value for
+			// program, volume or panning before it starts playing notes on a
+			// MIDI channel. This can cause settings for these parameters from
+			// a previous track to be used unintentionally. To correct this,
+			// default values for these parameters are set when a new track is
+			// started.
+			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM);
+			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_VOLUME);
+			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PANNING);
+			_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+
+			// Open the MIDI driver(s).
+			int returnCode = _driverMsMusic->open();
+			if (returnCode != 0)
+				error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
+			_driverMsMusic->syncSoundSettings();
+			if (_driverMsSfx && _driverMsMusic != _driverMsSfx) {
+				_driverMsSfx->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+				returnCode = _driverMsSfx->open();
+				if (returnCode != 0)
+					error("MidiPlayer::open - Failed to open MIDI SFX driver - error code %d.", returnCode);
+				_driverMsSfx->syncSoundSettings();
+			}
+
+			// Connect the driver(s) and parser(s).
+			_parserMusic->setMidiDriver(_driverMsMusic);
+			_parserMusic->setTimerRate(_driverMsMusic->getBaseTempo());
+			if (_parserSfx) {
+				_parserSfx->setMidiDriver(_driverMsSfx);
+				_parserSfx->setTimerRate(_driverMsSfx->getBaseTempo());
+			}
+
+			if (_driverMsSfx == _driverMsMusic) {
+				// Use MidiPlayer::onTimer to trigger both parsers from the
+				// single driver (it can only have one timer callback).
+				_driverMsMusic->setTimerCallback(this, &onTimer);
+			} else {
+				// Trigger each parser callback from the corresponding driver.
+				_driverMsMusic->setTimerCallback(_parserMusic, &_parserMusic->timerCallback);
+				if (_driverMsSfx) {
+					_driverMsSfx->setTimerCallback(_parserSfx, &_parserSfx->timerCallback);
+				}
+			}
+
+			return 0;
+		}
+	}
+
+	// TODO Old code below is no longer used for Simon 1.
+	musicType = MT_INVALID;
+
 	switch (gameType) {
 	case GType_ELVIRA1:
 		if (platform == Common::kPlatformPC98) {
@@ -251,7 +444,6 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	case kMusicModeSimon1: {
 		// This only handles the original AdLib driver of Simon1.
 		if (musicType == MT_ADLIB) {
-			_adLibMusic = true;
 			_map_mt32_to_gm = false;
 			_nativeMT32 = false;
 
@@ -275,7 +467,6 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	}
 
 	dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
-	_adLibMusic = (MidiDriver::getMusicType(dev) == MT_ADLIB);
 	_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
 
 	_driver = MidiDriver::createMidi(dev);
@@ -420,6 +611,11 @@ void MidiPlayer::onTimer(void *data) {
 	MidiPlayer *p = (MidiPlayer *)data;
 	Common::StackLock lock(p->_mutex);
 
+	if (p->_parserMusic)
+		p->_parserMusic->onTimer();
+	if (p->_parserSfx)
+		p->_parserSfx->onTimer();
+
 	if (!p->_paused) {
 		if (p->_music.parser && p->_currentTrack != 255) {
 			p->_current = &p->_music;
@@ -476,6 +672,12 @@ void MidiPlayer::startTrack(int track) {
 void MidiPlayer::stop() {
 	Common::StackLock lock(_mutex);
 
+	if (_parserMusic) {
+		_parserMusic->stopPlaying();
+		if (_driverMsMusic)
+			_driverMsMusic->deinitSource(0);
+	}
+
 	if (_music.parser) {
 		_current = &_music;
 		_music.parser->jumpToTick(0);
@@ -484,12 +686,35 @@ void MidiPlayer::stop() {
 	_currentTrack = 255;
 }
 
+void MidiPlayer::stopSfx() {
+	Common::StackLock lock(_mutex);
+
+	if (_parserSfx) {
+		_parserSfx->stopPlaying();
+		if (_driverMsSfx)
+			_driverMsSfx->deinitSource(1);
+	}
+}
+
 void MidiPlayer::pause(bool b) {
 	if (_paused == b || !_driver)
 		return;
 	_paused = b;
 
 	Common::StackLock lock(_mutex);
+
+	if (_paused) {
+		if (_parserMusic)
+			_parserMusic->pausePlaying();
+		if (_parserSfx)
+			_parserSfx->pausePlaying();
+	} else {
+		if (_parserMusic)
+			_parserMusic->resumePlaying();
+		if (_parserSfx)
+			_parserSfx->resumePlaying();
+	}
+
 	// if using the driver Accolade_AdLib call setVolume() to turn off\on the volume on all channels
 	if (musicType == MT_ADLIB && _musicMode == kMusicModeAccolade) {
 		static_cast <MidiDriver_Accolade_AdLib*> (_driver)->setVolume(_paused ? 0 : ConfMan.getInt("music_volume"));
@@ -534,10 +759,32 @@ void MidiPlayer::setVolume(int musicVol, int sfxVol) {
 	}
 }
 
+void MidiPlayer::syncSoundSettings() {
+	if (_driverMsMusic)
+		_driverMsMusic->syncSoundSettings();
+	if (_driverMsSfx)
+		_driverMsSfx->syncSoundSettings();
+
+	// Sync code for non-multisource drivers.
+	// TODO Remove when no longer necessary
+	bool mute = false;
+	if (ConfMan.hasKey("mute"))
+		mute = ConfMan.getBool("mute");
+
+	// Sync the engine with the config manager
+	int soundVolumeMusic = ConfMan.getInt("music_volume");
+	int soundVolumeSFX = ConfMan.getInt("sfx_volume");
+
+	setVolume(mute ? 0 : soundVolumeMusic, mute ? 0 : soundVolumeSFX);
+}
+
 void MidiPlayer::setLoop(bool loop) {
 	Common::StackLock lock(_mutex);
 
 	_loopTrack = loop;
+
+	if (_parserMusic)
+		_parserMusic->property(MidiParser::mpAutoLoop, loop);
 }
 
 void MidiPlayer::queueTrack(int track, bool loop) {
@@ -592,6 +839,63 @@ void MidiPlayer::resetVolumeTable() {
 	}
 }
 
+void MidiPlayer::loadMusic(Common::SeekableReadStream *in, int32 size, bool sfx) {
+	Common::StackLock lock(_mutex);
+
+	MidiParser *parser = sfx ? _parserSfx : _parserMusic;
+
+	if (size < 0) {
+		// Use the parser to determine the size of the MIDI data.
+		int64 startPos = in->pos();
+		size = parser->determineDataSize(in);
+		if (size < 0) {
+			warning("MidiPlayer::loadMusic - Could not determine size of music data");
+			return;
+		}
+		// determineDataSize might move the stream position, so return it to
+		// the original position.
+		in->seek(startPos, SEEK_SET);
+	}
+
+	parser->unloadMusic();
+
+	// Copy the data into _musicData or _sfxData.
+	byte **dataPtr = sfx ? &_sfxData : &_musicData;
+	if (*dataPtr) {
+		delete[] *dataPtr;
+	}
+
+	*dataPtr = new byte[size];
+	in->read(*dataPtr, size);
+
+	// Finally, load the data into the parser.
+	parser->loadMusic(*dataPtr, size);
+}
+
+void MidiPlayer::play(int track, bool sfx, bool sfxUsesRhythm) {
+	MidiParser *parser = sfx ? _parserSfx : _parserMusic;
+
+	if (parser->setTrack(track)) {
+		if (sfx && sfxUsesRhythm) {
+			// This sound effect uses OPL rhythm instruments. Disable music
+			// rhythm notes while this sound effect is playing to prevent
+			// conflicts.
+			// Note that if AdLib music is used, _driverMsMusic and
+			// _driverMsSfx point to the same object. If AdLib music is not
+			// used, the disableMusicRhythmNotes call will do nothing.
+			MidiDriver_Simon1_AdLib *adLibSfxDriver = dynamic_cast<MidiDriver_Simon1_AdLib *>(_driverMsSfx);
+			if (adLibSfxDriver)
+				adLibSfxDriver->disableMusicRhythmNotes();
+		}
+
+		parser->startPlaying();
+	} else {
+		warning("MidiPlayer::play - Could not play %s track %i", sfx ? "SFX" : "music", track);
+	}
+}
+
+// TODO Remove dead code
+
 static const int simon1_gmf_size[] = {
 	8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138,
 	6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717,
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index 57e11953b8d..c947a93e46d 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -23,6 +23,7 @@
 #define AGOS_MIDI_H
 
 #include "audio/mididrv.h"
+#include "audio/mididrv_ms.h"
 #include "audio/midiparser.h"
 #include "common/mutex.h"
 
@@ -61,8 +62,19 @@ struct MusicInfo {
 
 class MidiPlayer : public MidiDriver_BASE {
 protected:
+	AGOSEngine *_vm;
+
 	Common::Mutex _mutex;
 	MidiDriver *_driver;
+	// Multisource driver used for music. Provides access to multisource
+	// methods without casting. If this is not nullptr, it points to the same
+	// object as _driver.
+	MidiDriver_Multisource *_driverMsMusic;
+	// Multisource driver used for sound effects. Only used for Simon The
+	// Sorcerer DOS floppy AdLib sound effects.
+	// If AdLib is also used for music, this points to the same object as
+	// _driverMsMusic and _driver.
+	MidiDriver_Multisource *_driverMsSfx;
 	bool _map_mt32_to_gm;
 	bool _nativeMT32;
 
@@ -70,6 +82,11 @@ protected:
 	MusicInfo _sfx;
 	MusicInfo *_current; // Allows us to establish current context for operations.
 
+	MidiParser *_parserMusic;
+	byte *_musicData;
+	MidiParser *_parserSfx;
+	byte *_sfxData;
+
 	// These are maintained for both music and SFX
 	byte _masterVolume;    // 0-255
 	byte _musicVolume;
@@ -89,30 +106,41 @@ protected:
 	void resetVolumeTable();
 
 public:
-	bool _adLibMusic;
-	bool _enable_sfx;
-
-public:
-	MidiPlayer();
+	MidiPlayer(AGOSEngine *vm);
 	~MidiPlayer() override;
 
+	// Loads music data supported by the MidiParser used for the detected
+	// version of the game. Specify sfx to indicate that this is a MIDI sound
+	// effect.
+	void loadMusic(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
+	// Plays the currently loaded music data. If the loaded MIDI data has
+	// multiple tracks, specify track to select the track to play. Specify sfx
+	// to indicate that the loaded sound effect data should be used. Specify
+	// sfxUsesRhythm to inidicate that the sound effect uses OPL rhythm
+	// instruments; this will disable music rhythm notes while the sound effect
+	// is playing.
+	void play(int track = 0, bool sfx = false, bool sfxUsesRhythm = false);
+
 	void loadSMF(Common::SeekableReadStream *in, int song, bool sfx = false);
 	void loadMultipleSMF(Common::SeekableReadStream *in, bool sfx = false);
 	void loadXMIDI(Common::SeekableReadStream *in, bool sfx = false);
 	void loadS1D(Common::SeekableReadStream *in, bool sfx = false);
 
 	bool hasNativeMT32() const { return _nativeMT32; }
+	bool hasAdLibSfx() const { return _parserSfx != nullptr; }
 	void setLoop(bool loop);
 	void startTrack(int track);
 	void queueTrack(int track, bool loop);
 	bool isPlaying(bool check_queued = false) { return (_currentTrack != 255 && (_queuedTrack != 255 || !check_queued)); }
 
 	void stop();
+	void stopSfx();
 	void pause(bool b);
 
 	int  getMusicVolume() const { return _musicVolume; }
 	int  getSFXVolume() const { return _sfxVolume; }
 	void setVolume(int musicVol, int sfxVol);
+	void syncSoundSettings();
 
 public:
 	int open(int gameType, Common::Platform platform, bool isDemo);
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index eeeb101dcd5..a190a00d381 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -37,6 +37,22 @@
 
 namespace AGOS {
 
+// This data is hardcoded in the executable.
+const int AGOSEngine_Simon1::SIMON1_GMF_SIZE[] = {
+	8900, 12166,  2848,  3442,  4034,  4508,  7064,  9730,  6014,  4742,
+	3138,  6570,  5384,  8909,  6457, 16321,  2742,  8968,  4804,  8442,
+	7717,  9444,  5800,  1381,  5660,  6684,  2456,  4744,  2455,  1177,
+	1232, 17256,  5103,  8794,  4884,    16
+};
+
+// This data is hardcoded in the executable.
+// High nibble is the file ID (STINGSx.MUS), low nibble is the SFX number
+// in the file (0 based).
+const byte AGOSEngine::SIMON1_RHYTHM_SFX[] = {
+	0x15, 0x16, 0x2C, 0x31, 0x37, 0x3A, 0x42, 0x43, 0x44,
+	0x51, 0x55, 0x61, 0x68, 0x74, 0x78, 0x83, 0x89, 0x90
+};
+
 void AGOSEngine_Simon1::playSpeech(uint16 speech_id, uint16 vgaSpriteId) {
 	if (speech_id == 9999) {
 		if (_subtitles)
@@ -220,12 +236,18 @@ void AGOSEngine::playModule(uint16 music) {
 	}
 
 	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream);
-	_mixer->pauseHandle(_modHandle, _musicPaused);
 }
 
 void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 	stopMusic();
 
+	if (getPlatform() != Common::kPlatformAmiga && (getFeatures() & GF_TALKIE) && music == 35) {
+		// WORKAROUND: For a script bug in the CD versions
+		// We skip this music resource, as it was replaced by
+		// a sound effect, and the script was never updated.
+		return;
+	}
+
 	// Support for compressed music from the ScummVM Music Enhancement Project
 	_system->getAudioCDManager()->stop();
 	_system->getAudioCDManager()->play(music + 1, -1, 0, 0, true);
@@ -234,32 +256,22 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 
 	if (getPlatform() == Common::kPlatformAmiga) {
 		playModule(music);
-	} else if (getFeatures() & GF_TALKIE) {
-		char buf[4];
-
-		// WORKAROUND: For a script bug in the CD versions
-		// We skip this music resource, as it was replaced by
-		// a sound effect, and the script was never updated.
-		if (music == 35)
-			return;
+	} else if ((getPlatform() == Common::kPlatformDOS || getPlatform() == Common::kPlatformAcorn) &&
+			getFeatures() & GF_TALKIE) {
+		// DOS CD and Acorn CD use the same music data.
 
-		_midi->setLoop(true); // Must do this BEFORE loading music. (GMF may have its own override.)
+		// Data is stored in one large data file and the GMF format does not
+		// have an indication of size or end of data, so the data size has to
+		// be supplied from a hardcoded list.
+		int size = SIMON1_GMF_SIZE[music];
 
 		_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
-		_gameFile->read(buf, 4);
-		if (!memcmp(buf, "GMF\x1", 4)) {
-			_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
-			_midi->loadSMF(_gameFile, music);
-		} else {
-			_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
-			_midi->loadMultipleSMF(_gameFile);
-		}
+		_midi->loadMusic(_gameFile, size);
+		_midi->play();
+	} else if (getPlatform() == Common::kPlatformDOS) {
+		// DOS floppy version.
 
-		_midi->startTrack(0);
-		_midi->startTrack(track);
-	} else if (getPlatform() == Common::kPlatformAcorn) {
-		// TODO: Add support for Desktop Tracker format in Acorn disk version
-	} else {
+		// GMF music data is in separate MODxx.MUS files.
 		char filename[15];
 		Common::File f;
 		sprintf(filename, "MOD%d.MUS", music);
@@ -267,15 +279,26 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 		if (f.isOpen() == false)
 			error("playMusic: Can't load music from '%s'", filename);
 
-		_midi->setLoop(true); // Must do this BEFORE loading music. (GMF may have its own override.)
+		_midi->loadMusic(&f, f.size());
+		if (getFeatures() & GF_DEMO) {
+			// Full version music data has a loop flag in the file header, but
+			// the demo needs to have this set manually.
+			_midi->setLoop(true);
+		}
 
-		if (getFeatures() & GF_DEMO)
-			_midi->loadS1D(&f);
-		else
-			_midi->loadSMF(&f, music);
+		_midi->play();
+	} else if (getPlatform() == Common::kPlatformWindows) {
+		// Windows version uses SMF data in one large data file.
+		_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
 
-		_midi->startTrack(0);
-		_midi->startTrack(track);
+		_midi->loadMusic(_gameFile);
+		_midi->setLoop(true);
+
+		_midi->play();
+	} else if (getPlatform() == Common::kPlatformAcorn) {
+		// Acorn floppy version.
+		
+		// TODO: Add support for Desktop Tracker format in Acorn disk version
 	}
 }
 
@@ -318,27 +341,42 @@ void AGOSEngine::stopMusic() {
 void AGOSEngine::playSting(uint16 soundId) {
 	// The sound effects in floppy disk version of
 	// Simon the Sorcerer 1 are only meant for AdLib
-	if (!_midi->_adLibMusic || !_midi->_enable_sfx)
+	if (!_midi->hasAdLibSfx())
 		return;
 
+	// AdLib SFX use GMF data bundled in 9 STINGSx.MUS files.
 	char filename[16];
-
 	Common::File mus_file;
-	uint16 mus_offset;
 
 	sprintf(filename, "STINGS%i.MUS", _soundFileId);
 	mus_file.open(filename);
 	if (!mus_file.isOpen())
 		error("playSting: Can't load sound effect from '%s'", filename);
 
-	mus_file.seek(soundId * 2, SEEK_SET);
-	mus_offset = mus_file.readUint16LE();
-	if (mus_file.err())
-		error("playSting: Can't read sting %d offset", soundId);
+	// WORKAROUND Some Simon 1 DOS floppy SFX use the OPL rhythm instruments.
+	// This can conflict with the music using the rhythm instruments, so the
+	// original interpreter disables the music rhythm notes while a sound
+	// effect is playing. However, only some sound effects use rhythm notes, so
+	// in many cases this is not needed and leads to the music drums needlessly
+	// being disabled.
+	// To improve this, the sound effect number is checked against a list of
+	// SFX using rhythm notes, and only if it is in the list the music drums
+	// will be disabled while it plays.
+	bool rhythmSfx = false;
+	// Search for the file ID / SFX ID combination in the list of SFX that use
+	// rhythm notes.
+	byte sfxId = (_soundFileId << 4) | soundId;
+	for (int i = 0; i < ARRAYSIZE(SIMON1_RHYTHM_SFX); i++) {
+		if (SIMON1_RHYTHM_SFX[i] == sfxId) {
+			rhythmSfx = true;
+			break;
+		}
+	}
+
+	_midi->stopSfx();
 
-	mus_file.seek(mus_offset, SEEK_SET);
-	_midi->loadSMF(&mus_file, soundId, true);
-	_midi->startTrack(0);
+	_midi->loadMusic(&mus_file, mus_file.size(), true);
+	_midi->play(soundId, true, rhythmSfx);
 }
 
 static const byte elvira1_soundTable[100] = {


Commit: 1f19bf2d31e4949586d8d1c4fb46beec62bb810f
    https://github.com/scummvm/scummvm/commit/1f19bf2d31e4949586d8d1c4fb46beec62bb810f
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:42+02:00

Commit Message:
AGOS: Improve sound pausing and volume management

MIDI music is now paused by pausing the parser(s) instead of relying on the
mixer streams to stop providing callbacks. This gives more consistent behavior
as external MIDI devices or softsynths now also pause playback.

Muting sound now sets the volume to 0 instead of pausing playback.

Changed paths:
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/input.cpp
    engines/agos/sound.cpp
    engines/agos/sound.h


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 4bc3b1efab9..6fb5f4b16d4 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -518,9 +518,13 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 
 	_sound = nullptr;
 
-	_effectsPaused = false;
-	_ambientPaused = false;
-	_musicPaused = false;
+	_effectsMuted = false;
+	_ambientMuted = false;
+	_musicMuted = false;
+	// Initialize at default ScummVM volumes; these will be overwritten by
+	// syncSoundSettings.
+	_musicVolume = 192;
+	_effectsVolume = 192;
 
 	_saveLoadType = 0;
 	_saveLoadSlot = 0;
@@ -600,7 +604,7 @@ Common::Error AGOSEngine::init() {
 
 	initGraphics(_internalWidth, _internalHeight);
 
-	_midi = new MidiPlayer();
+	_midi = new MidiPlayer(this);
 
 	if ((getGameType() == GType_SIMON2 && getPlatform() == Common::kPlatformWindows) ||
 		(getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformWindows) ||
@@ -611,14 +615,9 @@ Common::Error AGOSEngine::init() {
 		if (ret)
 			warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));
 
-		_midi->setVolume(ConfMan.getInt("music_volume"), ConfMan.getInt("sfx_volume"));
-
 		_midiEnabled = true;
 	}
 
-	// Setup mixer
-	syncSoundSettings();
-
 	// allocate buffers
 	_backGroundBuf = new Graphics::Surface();
 	_backGroundBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
@@ -655,23 +654,6 @@ Common::Error AGOSEngine::init() {
 	setDebugger(new Debugger(this));
 	_sound = new Sound(this, gss, _mixer);
 
-	if (ConfMan.hasKey("music_mute") && ConfMan.getBool("music_mute") == 1) {
-		_musicPaused = true;
-		if (_midiEnabled) {
-			_midi->pause(_musicPaused);
-		}
-		_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, 0);
-	}
-
-	if (ConfMan.hasKey("sfx_mute") && ConfMan.getBool("sfx_mute") == 1) {
-		if (getGameId() == GID_SIMON1DOS)
-			_midi->_enable_sfx = !_midi->_enable_sfx;
-		else {
-			_effectsPaused = !_effectsPaused;
-			_sound->effectsPause(_effectsPaused);
-		}
-	}
-
 	_copyProtection = ConfMan.getBool("copy_protection");
 	_language = Common::parseLanguage(ConfMan.get("language"));
 
@@ -699,6 +681,9 @@ Common::Error AGOSEngine::init() {
 		_subtitles = true;
 	}
 
+	// Setup mixer
+	syncSoundSettings();
+
 	return Common::kNoError;
 }
 
@@ -1008,7 +993,7 @@ void AGOSEngine::pauseEngineIntern(bool pauseIt) {
 	} else {
 		_pause = false;
 
-		_midi->pause(_musicPaused);
+		_midi->pause(false);
 		_mixer->pauseAll(false);
 	}
 }
@@ -1087,16 +1072,63 @@ uint32 AGOSEngine::getTime() const {
 void AGOSEngine::syncSoundSettings() {
 	Engine::syncSoundSettings();
 
-	bool mute = false;
-	if (ConfMan.hasKey("mute"))
-		mute = ConfMan.getBool("mute");
+	int newMusicVolume = ConfMan.getInt("music_volume");
+	int newEffectsVolume = ConfMan.getInt("sfx_volume");
 
-	// Sync the engine with the config manager
-	int soundVolumeMusic = ConfMan.getInt("music_volume");
-	int soundVolumeSFX = ConfMan.getInt("sfx_volume");
+	_musicMuted = newMusicVolume == 0;
+	if (newMusicVolume != 0)
+		_musicVolume = newMusicVolume;
+	if (getGameType() == GType_SIMON2) {
+		// Simon 2 has regular and ambient SFX, which can be toggled on and off
+		// separately.
+		if (newEffectsVolume == 0) {
+			// Global SFX volume 0 mutes both regular and ambient SFX.
+			_effectsMuted = _ambientMuted = true;
+		} else {
+			// If global SFX volume is > 0 and both regular and ambient SFX are
+			// muted, unmute them. If only one of them is muted, the volume
+			// change will only affect that type of SFX. If both are not muted,
+			// it will affect both types.
+			if (_effectsMuted && _ambientMuted)
+				_effectsMuted = _ambientMuted = false;
+			_effectsVolume = newEffectsVolume;
+		}
+		// Engine::syncSoundSettings applies SFX volume to all SFX handles,
+		// so manage the regular and ambient handles separately here.
+		_sound->effectsMute(_effectsMuted, _effectsVolume);
+		_sound->ambientMute(_ambientMuted, _effectsVolume);
+	} else {
+		// Other games only have one SFX setting.
+		_effectsMuted = newEffectsVolume == 0;
+		if (newEffectsVolume != 0)
+			_effectsVolume = newEffectsVolume;
+	}
+	_speech = !ConfMan.getBool("speech_mute");
+
+	if (_midiEnabled)
+		_midi->syncSoundSettings();
+}
+
+void AGOSEngine::syncSoundSettingsIntern() {
+	ConfMan.setBool("speech_mute", !_speech);
+	ConfMan.setInt("music_volume", _musicMuted ? 0 : _musicVolume);
+	bool sfxMute = getGameType() == GType_SIMON2 ?
+		_effectsMuted && _ambientMuted : _effectsMuted;
+	ConfMan.setInt("sfx_volume", sfxMute ? 0 : _effectsVolume);
+
+	Engine::syncSoundSettings();
+
+	if (getGameType() == GType_SIMON2) {
+		// Simon 2 has ambient sound effects, which can be toggled on and off
+		// separately from the other SFX.
+		// Engine::syncSoundSettings applies SFX volume to all SFX handles,
+		// so manage the regular and ambient handles separately here.
+		_sound->effectsMute(_effectsMuted, _effectsVolume);
+		_sound->ambientMute(_ambientMuted, _effectsVolume);
+	}
 
 	if (_midiEnabled)
-		_midi->setVolume((mute ? 0 : soundVolumeMusic), (mute ? 0 : soundVolumeSFX));
+		_midi->syncSoundSettings();
 }
 
 } // End of namespace AGOS
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 49011301944..e9e5fa40ce7 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -198,6 +198,10 @@ class Debugger;
 #endif
 
 class AGOSEngine : public Engine {
+protected:
+	// List of Simon 1 DOS floppy SFX which use rhythm notes.
+	static const byte SIMON1_RHYTHM_SFX[];
+
 protected:
 	friend class Debugger;
 
@@ -214,6 +218,9 @@ protected:
 
 	bool hasFeature(EngineFeature f) const override;
 	void syncSoundSettings() override;
+	// Applies AGOS engine internal sound settings to ConfigManager, digital
+	// sound channels and MIDI.
+	void syncSoundSettingsIntern();
 	void pauseEngineIntern(bool pause) override;
 
 	virtual void setupOpcodes();
@@ -578,9 +585,15 @@ protected:
 
 	Sound *_sound;
 
-	bool _effectsPaused;
-	bool _ambientPaused;
-	bool _musicPaused;
+	bool _effectsMuted;
+	bool _ambientMuted;
+	bool _musicMuted;
+	// The current music volume, or the last used music volume if music is
+	// currently muted.
+	uint16 _musicVolume;
+	// The current SFX and ambient volume, or the last used volume if SFX
+	// and/or ambient sounds are currently muted.
+	uint16 _effectsVolume;
 
 	uint8 _saveGameNameLen;
 	uint16 _saveLoadRowCurPos;
@@ -1811,6 +1824,10 @@ protected:
 };
 
 class AGOSEngine_Simon1 : public AGOSEngine_Waxworks {
+private:
+	// Simon 1 DOS CD and Acorn CD GMF data sizes.
+	static const int SIMON1_GMF_SIZE[];
+
 public:
 	AGOSEngine_Simon1(OSystem *system, const AGOSGameDescription *gd);
 	//~AGOSEngine_Simon1();
diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp
index 6f3efab5164..0d8b45f370a 100644
--- a/engines/agos/input.cpp
+++ b/engines/agos/input.cpp
@@ -685,44 +685,54 @@ bool AGOSEngine::processSpecialKeys() {
 		break;
 	case 'v':
 		if (getGameType() == GType_FF || (getGameType() == GType_SIMON2 && (getFeatures() & GF_TALKIE))) {
-			if (_subtitles)
+			if (_subtitles) {
 				_speech = !_speech;
+				syncSoundSettingsIntern();
+			}
 		}
 		break;
 	case '+':
-		if (_midiEnabled) {
-			_midi->setVolume(_midi->getMusicVolume() + 16, _midi->getSFXVolume() + 16);
+		if (_musicMuted) {
+			_musicMuted = false;
+			_musicVolume = 16;
+		} else {
+			_musicVolume = CLIP(_musicVolume + 16, 0, 256);
 		}
-		ConfMan.setInt("music_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) + 16);
-		syncSoundSettings();
+		syncSoundSettingsIntern();
 		break;
 	case '-':
-		if (_midiEnabled) {
-			_midi->setVolume(_midi->getMusicVolume() - 16, _midi->getSFXVolume() - 16);
+		if (!_musicMuted) {
+			_musicVolume = CLIP(_musicVolume - 16, 0, 256);
+			if (_musicVolume == 0) {
+				_musicMuted = true;
+			}
+			syncSoundSettingsIntern();
 		}
-		ConfMan.setInt("music_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) - 16);
-		syncSoundSettings();
 		break;
 	case 'm':
-		_musicPaused = !_musicPaused;
-		if (_midiEnabled) {
-			_midi->pause(_musicPaused);
-		}
-		_mixer->pauseHandle(_modHandle, _musicPaused);
-		syncSoundSettings();
+		_musicMuted = !_musicMuted;
+		if (!_musicMuted && _musicVolume == 0)
+			// If last used music volume is 0 when unmuting, use ScummVM
+			// default volume.
+			_musicVolume = 192;
+		syncSoundSettingsIntern();
 		break;
 	case 's':
-		if (getGameId() == GID_SIMON1DOS) {
-			_midi->_enable_sfx = !_midi->_enable_sfx;
-		} else {
-			_effectsPaused = !_effectsPaused;
-			_sound->effectsPause(_effectsPaused);
-		}
+		_effectsMuted = !_effectsMuted;
+		if (!_effectsMuted && _effectsVolume == 0)
+			// If last used SFX volume is 0 when unmuting, use ScummVM
+			// default volume.
+			_effectsVolume = 192;
+		syncSoundSettingsIntern();
 		break;
 	case 'b':
 		if (getGameType() == GType_SIMON2) {
-			_ambientPaused = !_ambientPaused;
-			_sound->ambientPause(_ambientPaused);
+			_ambientMuted = !_ambientMuted;
+			if (!_ambientMuted && _effectsVolume == 0)
+				// If last used SFX volume is 0 when unmuting, use ScummVM
+				// default volume.
+				_effectsVolume = 192;
+			syncSoundSettingsIntern();
 		}
 		break;
 	default:
diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp
index 360ff7192fb..641890936be 100644
--- a/engines/agos/sound.cpp
+++ b/engines/agos/sound.cpp
@@ -396,10 +396,6 @@ Sound::Sound(AGOSEngine *vm, const GameSpecificSettings *gss, Audio::Mixer *mixe
 	_voice = nullptr;
 	_effects = nullptr;
 
-	_effectsPaused = false;
-	_ambientPaused = false;
-	_sfx5Paused = false;
-
 	_filenums = nullptr;
 	_lastVoiceFile = 0;
 	_offsets = nullptr;
@@ -571,9 +567,6 @@ void Sound::playEffects(uint sound) {
 	if (!_effects)
 		return;
 
-	if (_effectsPaused)
-		return;
-
 	if (_vm->getGameType() == GType_SIMON1)
 		_mixer->stopHandle(_effectsHandle);
 	_effects->playSound(sound, Audio::Mixer::kSFXSoundType, &_effectsHandle, false);
@@ -588,9 +581,6 @@ void Sound::playAmbient(uint sound) {
 
 	_ambientPlaying = sound;
 
-	if (_ambientPaused)
-		return;
-
 	_mixer->stopHandle(_ambientHandle);
 	_effects->playSound(sound, Audio::Mixer::kSFXSoundType, &_ambientHandle, true);
 }
@@ -627,21 +617,13 @@ void Sound::stopAll() {
 	_ambientPlaying = 0;
 }
 
-void Sound::effectsPause(bool b) {
-	_effectsPaused = b;
-	_sfx5Paused = b;
+void Sound::effectsMute(bool mute, uint16 effectsVolume) {
+	_mixer->setChannelVolume(_effectsHandle, mute ? 0 : effectsVolume);
+	_mixer->setChannelVolume(_sfx5Handle, mute ? 0 : effectsVolume);
 }
 
-void Sound::ambientPause(bool b) {
-	_ambientPaused = b;
-
-	if (_ambientPaused && _ambientPlaying) {
-		_mixer->stopHandle(_ambientHandle);
-	} else if (_ambientPlaying) {
-		uint tmp = _ambientPlaying;
-		_ambientPlaying = 0;
-		playAmbient(tmp);
-	}
+void Sound::ambientMute(bool mute, uint16 effectsVolume) {
+	_mixer->setChannelVolume(_ambientHandle, mute ? 0 : effectsVolume);
 }
 
 // Personal Nightmare specific
@@ -664,9 +646,6 @@ void Sound::handleSoundQueue() {
 }
 
 void Sound::queueSound(byte *ptr, uint16 sound, uint32 size, uint16 freq) {
-	if (_effectsPaused)
-		return;
-
 	// Only a single sound can be queued
 	_soundQueuePtr = ptr;
 	_soundQueueNum = sound;
@@ -676,9 +655,6 @@ void Sound::queueSound(byte *ptr, uint16 sound, uint32 size, uint16 freq) {
 
 // Elvira 1/2 and Waxworks specific
 void Sound::playRawData(byte *soundData, uint sound, uint size, uint freq) {
-	if (_effectsPaused)
-		return;
-
 	byte *buffer = (byte *)malloc(size);
 	memcpy(buffer, soundData, size);
 
@@ -697,24 +673,15 @@ void Sound::playAmbientData(byte *soundData, uint sound, uint pan, uint vol) {
 
 	_ambientPlaying = sound;
 
-	if (_ambientPaused)
-		return;
-
 	_mixer->stopHandle(_ambientHandle);
 	playSoundData(&_ambientHandle, soundData, sound, pan, vol, true);
 }
 
 void Sound::playSfxData(byte *soundData, uint sound, uint pan, uint vol) {
-	if (_effectsPaused)
-		return;
-
 	playSoundData(&_effectsHandle, soundData, sound, pan, vol, false);
 }
 
 void Sound::playSfx5Data(byte *soundData, uint sound, uint pan, uint vol) {
-	if (_sfx5Paused)
-		return;
-
 	_mixer->stopHandle(_sfx5Handle);
 	playSoundData(&_sfx5Handle, soundData, sound, pan, vol, true);
 }
diff --git a/engines/agos/sound.h b/engines/agos/sound.h
index 5b38f14aaef..8334384a734 100644
--- a/engines/agos/sound.h
+++ b/engines/agos/sound.h
@@ -46,10 +46,6 @@ private:
 	BaseSound *_voice;
 	BaseSound *_effects;
 
-	bool _effectsPaused;
-	bool _ambientPaused;
-	bool _sfx5Paused;
-
 	uint16 *_filenums;
 	uint32 *_offsets;
 	uint16 _lastVoiceFile;
@@ -114,8 +110,8 @@ public:
 	void stopSfx5();
 	void stopVoice();
 	void stopAll();
-	void effectsPause(bool b);
-	void ambientPause(bool b);
+	void effectsMute(bool mute, uint16 effectsVolume);
+	void ambientMute(bool mute, uint16 effectsVolume);
 };
 
 } // End of namespace AGOS


Commit: 8c4c80d844df0811919690db2afb012133fed5bc
    https://github.com/scummvm/scummvm/commit/8c4c80d844df0811919690db2afb012133fed5bc
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:42+02:00

Commit Message:
AUDIO: Add determineDataSize to XMIDI parser

This is needed for Simon 2.

Changed paths:
    audio/midiparser_xmidi.cpp


diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp
index d1fecd81a88..bd7cfc70aa0 100644
--- a/audio/midiparser_xmidi.cpp
+++ b/audio/midiparser_xmidi.cpp
@@ -108,6 +108,7 @@ public:
 	bool loadMusic(byte *data, uint32 size) override;
 	bool hasJumpIndex(uint8 index) override;
 	bool jumpToIndex(uint8 index, bool stopNotes) override;
+	int32 determineDataSize(Common::SeekableReadStream *stream) override;
 };
 
 // This is a special XMIDI variable length quantity
@@ -526,6 +527,45 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
 	return false;
 }
 
+int32 MidiParser_XMIDI::determineDataSize(Common::SeekableReadStream *stream) {
+	int32 length = 0;
+
+	byte buf[4];
+	Common::fill(buf, buf + 4, 0);
+	// Read FourCC.
+	stream->read(buf, 4);
+
+	if (!memcmp(buf, "FORM", 4)) {
+		// Optional XDIR header.
+
+		// Skip over the header.
+		uint32 headerLength = stream->readUint32BE();
+		stream->seek(headerLength, SEEK_CUR);
+
+		// Read next FourCC.
+		Common::fill(buf, buf + 4, 0);
+		stream->read(buf, 4);
+
+		// Add header length to total length.
+		length += 8;
+		length += headerLength;
+	}
+
+	if (!memcmp(buf, "CAT ", 4)) {
+		// CAT chunk.
+		uint32 catLength = stream->readUint32BE();
+		// Add catalog chunk length to total length.
+		length += 8;
+		length += catLength;
+	} else {
+		// XMIDI files must have a CAT chunk.
+		warning("Expected FORM or CAT  but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]);
+		return -1;
+	}
+
+	return length;
+}
+
 void MidiParser_XMIDI::onTrackStart(uint8 track) {
 	// Load custom timbres
 	if (_newTimbreListDriver && _tracksTimbreListSize[track] > 0)


Commit: f1e3c12895b85c936542327108a3acbe2801ead4
    https://github.com/scummvm/scummvm/commit/f1e3c12895b85c936542327108a3acbe2801ead4
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:42+02:00

Commit Message:
AGOS: Improve Simon 2 MIDI code

The DOS versions of Simon the Sorcerer 2 now use the Miles drivers. AdLib now
sounds like the original does. ScummVM would use the second set of XMIDI data,
written for the MT-32, for GM with instrument remapping. Now the first set is
used, which uses GM instruments and has extra instruments on several pieces.
For all versions music fade-outs have been added during screen fades.

Changed paths:
    engines/agos/agos.cpp
    engines/agos/debugger.cpp
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/res_snd.cpp
    engines/agos/script_s2.cpp
    engines/agos/vga_s2.cpp
    engines/agos/vga_ww.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 6fb5f4b16d4..446c749e75c 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -753,7 +753,7 @@ void AGOSEngine_Simon2::setupGame() {
 	_itemMemSize = 20000;
 	_tableMemSize = 100000;
 	// Check whether to use MT-32 MIDI tracks in Simon the Sorcerer 2
-	if (getGameType() == GType_SIMON2 && _midi->hasNativeMT32())
+	if (getGameType() == GType_SIMON2 && getPlatform() == Common::kPlatformDOS && _midi->usesMT32Data())
 		_musicIndexBase = (1128 + 612) / 4;
 	else
 		_musicIndexBase = 1128 / 4;
diff --git a/engines/agos/debugger.cpp b/engines/agos/debugger.cpp
index b8e8c890e17..290d995ca07 100644
--- a/engines/agos/debugger.cpp
+++ b/engines/agos/debugger.cpp
@@ -55,7 +55,7 @@ bool Debugger::Cmd_PlayMusic(int argc, const char **argv) {
 				// TODO
 			} else if (_vm->getGameType() == GType_SIMON2) {
 				_vm->loadMusic(music);
-				_vm->_midi->startTrack(0);
+				_vm->_midi->play();
 			} else {
 				_vm->playMusic(music, 0);
 			}
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 925a66f2b3d..7c92d683d8c 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -37,6 +37,7 @@
 #include "agos/drivers/simon1/adlib_win.h"
 // Miles Audio for Simon 2
 #include "audio/miles.h"
+#include "audio/midiparser.h"
 
 // PKWARE data compression library decompressor required for Simon 2
 #include "common/dcl.h"
@@ -77,6 +78,8 @@ MidiPlayer::MidiPlayer(AGOSEngine *vm) {
 	_loopQueuedTrack = 0;
 
 	_musicMode = kMusicModeDisabled;
+	_deviceType = MT_NULL;
+	_dataType = MT_NULL;
 
 	_parserMusic = nullptr;
 	_parserSfx = nullptr;
@@ -88,6 +91,8 @@ MidiPlayer::~MidiPlayer() {
 	stop();
 	stopSfx();
 
+	Common::StackLock lock(_mutex);
+
 	if (_driverMsSfx && _driverMsSfx != _driverMsMusic) {
 		_driverMsSfx->setTimerCallback(nullptr, nullptr);
 		_driverMsSfx->close();
@@ -106,8 +111,6 @@ MidiPlayer::~MidiPlayer() {
 		_driver = nullptr;
 	}
 
-	Common::StackLock lock(_mutex);
-
 	if (_parserMusic)
 		delete _parserMusic;
 	if (_parserSfx)
@@ -126,20 +129,23 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	assert(!_driver);
 
 	Common::String accoladeDriverFilename;
-	int devFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
+	// All games have MT-32 data, except Simon 2, which has both GM and MT-32
+	// data for DOS, or only GM for Windows. Some of the GM tracks have extra
+	// instruments compared to the MT-32 tracks, so we prefer GM.
+	int devFlags = MDT_MIDI | MDT_ADLIB | (_vm->getGameType() == GType_SIMON2 ? MDT_PREFER_GM : MDT_PREFER_MT32);
 
 	if (_vm->getGameType() == GType_SIMON1) {
 		// Check the type of device that the user has configured.
 		MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
-		musicType = MidiDriver::getMusicType(dev);
-		if (musicType == MT_GM && ConfMan.getBool("native_mt32"))
-			musicType = MT_MT32;
+		_deviceType = MidiDriver::getMusicType(dev);
+		if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
+			_deviceType = MT_MT32;
 
 		// All versions of Simon 1 that use MIDI have data that targets the
 		// MT-32 (even the Windows and Acorn versions).
-		MusicType dataType = MT_MT32;
+		_dataType = MT_MT32;
 
-		if (dataType == MT_MT32 && musicType == MT_GM) {
+		if (_dataType == MT_MT32 && _deviceType == MT_GM) {
 			// Not a real MT32 / no MUNT
 			::GUI::MessageDialog dialog(_(
 				"You appear to be using a General MIDI device,\n"
@@ -156,7 +162,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 			_musicMode = kMusicModeAccolade;
 			accoladeDriverFilename = "MUSIC.DRV";
 
-			switch (musicType) {
+			switch (_deviceType) {
 			case MT_ADLIB:
 				_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
 				break;
@@ -170,6 +176,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 
 			// Create the MIDI parser for the MUS format used by this version.
 			_parserMusic = MidiParser_createS1D();
+			_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
 
 			// Open the MIDI driver.
 			int returnCode = _driver->open();
@@ -185,7 +192,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 			return 0;
 		} else {
 			// Create the correct driver(s) for the device type and platform.
-			switch (musicType) {
+			switch (_deviceType) {
 			case MT_ADLIB:
 				if (_vm->getPlatform() == Common::kPlatformDOS) {
 					// DOS versions use a specific AdLib driver implementation
@@ -207,7 +214,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 				break;
 			case MT_MT32:
 			case MT_GM:
-				_driverMsMusic = new MidiDriver_MT32GM(dataType);
+				_driverMsMusic = new MidiDriver_MT32GM(_dataType);
 				if (_vm->getPlatform() == Common::kPlatformDOS && !(_vm->getFeatures() & GF_TALKIE) &&
 						ConfMan.getBool("multi_midi")) {
 					// DOS floppy version uses AdLib MIDI SFX if mixed MIDI
@@ -219,11 +226,23 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 				_driverMsMusic = new MidiDriver_NULL_Multisource();
 				break;
 			}
+			_driver = _driverMsMusic;
+
+			// WORKAROUND The Simon 1 MIDI data does not always set a value for
+			// program, volume or panning before it starts playing notes on a
+			// MIDI channel. This can cause settings for these parameters from
+			// a previous track to be used unintentionally. To correct this,
+			// default values for these parameters are set when a new track is
+			// started.
+			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM);
+			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_VOLUME);
+			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PANNING);
+			_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
 
 			// Create the MIDI parser(s) for the format used by the platform.
 			if (_vm->getPlatform() == Common::kPlatformWindows) {
 				// Windows version uses a specific SMF variant.
-				_parserMusic = new MidiParser_SimonWin(0);
+				_parserMusic = new MidiParser_SimonWin(0, true);
 			} else {
 				// DOS floppy & CD and Acorn CD use GMF (also an SMF variant).
 				_parserMusic = new MidiParser_GMF(0);
@@ -233,20 +252,10 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 					_parserSfx = new MidiParser_GMF(1);
 					_parserMusic->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
 					_parserSfx->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
+					_parserSfx->property(MidiParser::mpDisableAutoStartPlayback, true);
 				}
 			}
-			_driver = _driverMsMusic;
-
-			// WORKAROUND The Simon 1 MIDI data does not always set a value for
-			// program, volume or panning before it starts playing notes on a
-			// MIDI channel. This can cause settings for these parameters from
-			// a previous track to be used unintentionally. To correct this,
-			// default values for these parameters are set when a new track is
-			// started.
-			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM);
-			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_VOLUME);
-			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PANNING);
-			_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+			_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
 
 			// Open the MIDI driver(s).
 			int returnCode = _driverMsMusic->open();
@@ -283,10 +292,82 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 
 			return 0;
 		}
+	} else if (_vm->getGameType() == GType_SIMON2) {
+		// Check the type of device that the user has configured.
+		MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
+		_deviceType = MidiDriver::getMusicType(dev);
+		if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
+			_deviceType = MT_MT32;
+
+		// DOS version has both MT-32 and GM tracks; Windows is GM only.
+		_dataType = (_vm->getPlatform() == Common::kPlatformDOS && _deviceType == MT_MT32) ? MT_MT32 : MT_GM;
+
+		switch (_deviceType) {
+		case MT_ADLIB:
+			if (_vm->getPlatform() == Common::kPlatformDOS) {
+				if (Common::File::exists("MIDPAK.AD")) {
+					// if there is a file called MIDPAK.AD, use it directly
+					warning("MidiPlayer::open - SIMON 2: using MIDPAK.AD");
+					_driverMsMusic = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", "");
+				} else {
+					// if there is no file called MIDPAK.AD, try to extract it from the file SETUP.SHR
+					// if we didn't do this, the user would be forced to "install" the game instead of simply
+					// copying all files from CD-ROM.
+					Common::SeekableReadStream *midpakAdLibStream = simon2SetupExtractFile("MIDPAK.AD");
+					if (!midpakAdLibStream)
+						error("MidiPlayer::open - Could not extract MIDPAK.AD from SETUP.SHR");
+
+					// Pass this extracted data to the driver
+					warning("MidiPlayer::open - SIMON 2: using MIDPAK.AD extracted from SETUP.SHR");
+					_driverMsMusic = Audio::MidiDriver_Miles_AdLib_create("", "", midpakAdLibStream);
+					delete midpakAdLibStream;
+				}
+			} else {
+				// Windows
+				_driverMsMusic = new MidiDriver_ADLIB_Multisource(OPL::Config::kOpl3);
+			}
+			break;
+		case MT_MT32:
+		case MT_GM:
+			if (_vm->getPlatform() == Common::kPlatformDOS) {
+				_driverMsMusic = Audio::MidiDriver_Miles_MIDI_create(_dataType, "");
+			} else {
+				// Windows
+				_driverMsMusic = new MidiDriver_MT32GM(_dataType);
+			}
+			break;
+		default:
+			_driverMsMusic = new MidiDriver_NULL_Multisource();
+			break;
+		}
+		_driver = _driverMsMusic;
+		_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+
+		// Create the MIDI parser(s) for the format used by the platform.
+		if (_vm->getPlatform() == Common::kPlatformDOS) {
+			_parserMusic = MidiParser::createParser_XMIDI(MidiParser::defaultXMidiCallback, 0, 0);
+		} else {
+			// Windows version uses a specific SMF variant.
+			_parserMusic = new MidiParser_SimonWin(0);
+		}
+		_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
+
+		// Open the MIDI driver.
+		int returnCode = _driverMsMusic->open();
+		if (returnCode != 0)
+			error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
+		_driverMsMusic->syncSoundSettings();
+
+		// Connect the driver and parser.
+		_parserMusic->setMidiDriver(_driverMsMusic);
+		_parserMusic->setTimerRate(_driverMsMusic->getBaseTempo());
+		_driverMsMusic->setTimerCallback(this, &onTimer);
+
+		return 0;
 	}
 
-	// TODO Old code below is no longer used for Simon 1.
-	musicType = MT_INVALID;
+	// TODO Old code below is no longer used for Simon 1 and 2.
+	_deviceType = MT_INVALID;
 
 	switch (gameType) {
 	case GType_ELVIRA1:
@@ -337,9 +418,9 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 		}
 	} else if (_musicMode != kMusicModeDisabled) {
 		dev = MidiDriver::detectDevice(devFlags);
-		musicType = MidiDriver::getMusicType(dev);
+		_deviceType = MidiDriver::getMusicType(dev);
 
-		switch (musicType) {
+		switch (_deviceType) {
 		case MT_ADLIB:
 		case MT_MT32:
 			break;
@@ -355,7 +436,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 				dialog.runModal();
 			}
 			// Switch to MT32 driver in any case
-			musicType = MT_MT32;
+			_deviceType = MT_MT32;
 			break;
 		default:
 			_musicMode = kMusicModeDisabled;
@@ -366,7 +447,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	switch (_musicMode) {
 	case kMusicModeAccolade: {
 		// Setup midi driver
-		switch (musicType) {
+		switch (_deviceType) {
 		case MT_ADLIB:
 			_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
 			break;
@@ -391,7 +472,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	}
 
 	case kMusicModeMilesAudio: {
-		switch (musicType) {
+		switch (_deviceType) {
 		case MT_ADLIB: {
 			Common::File instrumentDataFile;
 			if (instrumentDataFile.exists("MIDPAK.AD")) {
@@ -443,7 +524,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 
 	case kMusicModeSimon1: {
 		// This only handles the original AdLib driver of Simon1.
-		if (musicType == MT_ADLIB) {
+		if (_deviceType == MT_ADLIB) {
 			_map_mt32_to_gm = false;
 			_nativeMT32 = false;
 
@@ -611,8 +692,17 @@ void MidiPlayer::onTimer(void *data) {
 	MidiPlayer *p = (MidiPlayer *)data;
 	Common::StackLock lock(p->_mutex);
 
-	if (p->_parserMusic)
+	if (p->_parserMusic) {
 		p->_parserMusic->onTimer();
+		if (!p->_parserMusic->isPlaying() && p->_queuedTrack != 255) {
+			// Music is no longer playing and there is a track queued up.
+			// Play the queued track.
+			p->setLoop(p->_loopQueuedTrack);
+			p->play(p->_queuedTrack, false, false, true);
+			p->_queuedTrack = 255;
+			p->_loopQueuedTrack = false;
+		}
+	}
 	if (p->_parserSfx)
 		p->_parserSfx->onTimer();
 
@@ -629,6 +719,20 @@ void MidiPlayer::onTimer(void *data) {
 	p->_current = nullptr;
 }
 
+bool MidiPlayer::usesMT32Data() const {
+	return _dataType == MT_MT32;
+}
+
+bool MidiPlayer::hasAdLibSfx() const {
+	return _parserSfx != nullptr;
+}
+
+bool MidiPlayer::isPlaying(bool checkQueued) {
+	Common::StackLock lock(_mutex);
+
+	return _parserMusic->isPlaying() && (!checkQueued || _queuedTrack != 255);
+}
+
 void MidiPlayer::startTrack(int track) {
 	Common::StackLock lock(_mutex);
 
@@ -672,6 +776,10 @@ void MidiPlayer::startTrack(int track) {
 void MidiPlayer::stop() {
 	Common::StackLock lock(_mutex);
 
+	// Clear the queued track to prevent it from starting when the current
+	// track is stopped.
+	_queuedTrack = 255;
+
 	if (_parserMusic) {
 		_parserMusic->stopPlaying();
 		if (_driverMsMusic)
@@ -716,7 +824,7 @@ void MidiPlayer::pause(bool b) {
 	}
 
 	// if using the driver Accolade_AdLib call setVolume() to turn off\on the volume on all channels
-	if (musicType == MT_ADLIB && _musicMode == kMusicModeAccolade) {
+	if (_deviceType == MT_ADLIB && _musicMode == kMusicModeAccolade) {
 		static_cast <MidiDriver_Accolade_AdLib*> (_driver)->setVolume(_paused ? 0 : ConfMan.getInt("music_volume"));
 	} else if (_musicMode == kMusicModePC98) {
 		_driver->property(0x30, _paused ? 1 : 0);
@@ -730,6 +838,16 @@ void MidiPlayer::pause(bool b) {
 	}
 }
 
+void MidiPlayer::fadeOut() {
+	Common::StackLock lock(_mutex);
+
+	if (!_parserMusic->isPlaying())
+		return;
+
+	// 1 second fade-out to silence.
+	_driverMsMusic->startFade(0, 1000, 0);
+}
+
 void MidiPlayer::setVolume(int musicVol, int sfxVol) {
 	musicVol = CLIP(musicVol, 0, 255);
 	sfxVol   = CLIP(sfxVol,   0, 255);
@@ -743,7 +861,7 @@ void MidiPlayer::setVolume(int musicVol, int sfxVol) {
 	if (_musicMode == kMusicModePC98) {
 		_driver->property(0x10, _musicVolume);
 		_driver->property(0x20, _sfxVolume);
-	} else if (_musicMode == kMusicModeAccolade && musicType == MT_ADLIB) {
+	} else if (_musicMode == kMusicModeAccolade && _deviceType == MT_ADLIB) {
 		static_cast <MidiDriver_Accolade_AdLib*> (_driver)->setVolume(_musicVolume);
 	}
 
@@ -788,15 +906,16 @@ void MidiPlayer::setLoop(bool loop) {
 }
 
 void MidiPlayer::queueTrack(int track, bool loop) {
-	_mutex.lock();
-	if (_currentTrack == 255) {
-		_mutex.unlock();
+	Common::StackLock lock(_mutex);
+
+	if (!_parserMusic->isPlaying()) {
+		// There is no music playing right now, so immediately play the track.
 		setLoop(loop);
-		startTrack(track);
+		play(track);
 	} else {
+		// Queue up the track for playback at the end of the current track.
 		_queuedTrack = track;
 		_loopQueuedTrack = loop;
-		_mutex.unlock();
 	}
 }
 
@@ -872,7 +991,9 @@ void MidiPlayer::loadMusic(Common::SeekableReadStream *in, int32 size, bool sfx)
 	parser->loadMusic(*dataPtr, size);
 }
 
-void MidiPlayer::play(int track, bool sfx, bool sfxUsesRhythm) {
+void MidiPlayer::play(int track, bool sfx, bool sfxUsesRhythm, bool queued) {
+	Common::StackLock lock(_mutex);
+
 	MidiParser *parser = sfx ? _parserSfx : _parserMusic;
 
 	if (parser->setTrack(track)) {
@@ -888,8 +1009,15 @@ void MidiPlayer::play(int track, bool sfx, bool sfxUsesRhythm) {
 				adLibSfxDriver->disableMusicRhythmNotes();
 		}
 
+		if (!sfx && !queued && _driverMsMusic)
+			// Reset the volume to neutral (in case the previous track was
+			// faded out).
+			_driverMsMusic->resetSourceVolume(0);
 		parser->startPlaying();
 	} else {
+		// Original interpreter stops playing when an invalid track is
+		// requested (f.e. Simon 2 MT-32 intro).
+		parser->stopPlaying();
 		warning("MidiPlayer::play - Could not play %s track %i", sfx ? "SFX" : "music", track);
 	}
 }
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index c947a93e46d..96452cffcb8 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -113,32 +113,40 @@ public:
 	// version of the game. Specify sfx to indicate that this is a MIDI sound
 	// effect.
 	void loadMusic(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
-	// Plays the currently loaded music data. If the loaded MIDI data has
-	// multiple tracks, specify track to select the track to play. Specify sfx
-	// to indicate that the loaded sound effect data should be used. Specify
-	// sfxUsesRhythm to inidicate that the sound effect uses OPL rhythm
-	// instruments; this will disable music rhythm notes while the sound effect
-	// is playing.
-	void play(int track = 0, bool sfx = false, bool sfxUsesRhythm = false);
+
+	/**
+	 * Plays the currently loaded music data. If the loaded MIDI data has
+	 * multiple tracks, specify track to select the track to play.
+	 * 
+	 * @param track The track to play. Default 0.
+	 * @param sfx True if the SFX MIDI data should be played, otherwise the
+	 * loaded music data will be played. Default false.
+	 * @param sfxUsesRhythm True if the sound effect uses OPL rhythm
+	 * instruments. Default false.
+	 * @param queued True if this track was queued; false if it was played
+	 * directly. Default false.
+	 */
+	void play(int track = 0, bool sfx = false, bool sfxUsesRhythm = false, bool queued = false);
 
 	void loadSMF(Common::SeekableReadStream *in, int song, bool sfx = false);
 	void loadMultipleSMF(Common::SeekableReadStream *in, bool sfx = false);
 	void loadXMIDI(Common::SeekableReadStream *in, bool sfx = false);
 	void loadS1D(Common::SeekableReadStream *in, bool sfx = false);
 
-	bool hasNativeMT32() const { return _nativeMT32; }
-	bool hasAdLibSfx() const { return _parserSfx != nullptr; }
+	// Returns true if the playback device uses MT-32 MIDI data; false it it
+	// uses a different data type.
+	bool usesMT32Data() const;
+	bool hasAdLibSfx() const;
 	void setLoop(bool loop);
 	void startTrack(int track);
 	void queueTrack(int track, bool loop);
-	bool isPlaying(bool check_queued = false) { return (_currentTrack != 255 && (_queuedTrack != 255 || !check_queued)); }
+	bool isPlaying(bool checkQueued = false);
 
 	void stop();
 	void stopSfx();
 	void pause(bool b);
+	void fadeOut();
 
-	int  getMusicVolume() const { return _musicVolume; }
-	int  getSFXVolume() const { return _sfxVolume; }
 	void setVolume(int musicVol, int sfxVol);
 	void syncSoundSettings();
 
@@ -151,7 +159,10 @@ public:
 
 private:
 	kMusicMode _musicMode;
-	MusicType musicType;
+	// The type of the music device selected for playback.
+	MusicType _deviceType;
+	// The type of the MIDI data of the game (MT-32 or GM).
+	MusicType _dataType;
 
 private:
 	Common::SeekableReadStream *simon2SetupExtractFile(const Common::String &requestedFileName);
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index a190a00d381..69e080bdb02 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -45,7 +45,6 @@ const int AGOSEngine_Simon1::SIMON1_GMF_SIZE[] = {
 	1232, 17256,  5103,  8794,  4884,    16
 };
 
-// This data is hardcoded in the executable.
 // High nibble is the file ID (STINGSx.MUS), low nibble is the SFX number
 // in the file (0 based).
 const byte AGOSEngine::SIMON1_RHYTHM_SFX[] = {
@@ -133,19 +132,10 @@ void AGOSEngine::skipSpeech() {
 }
 
 void AGOSEngine::loadMusic(uint16 music) {
-	char buf[4];
-
 	stopMusic();
 
 	_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
-	_gameFile->read(buf, 4);
-	if (!memcmp(buf, "FORM", 4)) {
-		_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
-		_midi->loadXMIDI(_gameFile);
-	} else {
-		_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
-		_midi->loadMultipleSMF(_gameFile);
-	}
+	_midi->loadMusic(_gameFile);
 
 	_lastMusicPlayed = music;
 	_nextMusicToPlay = -1;
diff --git a/engines/agos/script_s2.cpp b/engines/agos/script_s2.cpp
index 6eb2cee04e3..143f9bc2a0c 100644
--- a/engines/agos/script_s2.cpp
+++ b/engines/agos/script_s2.cpp
@@ -347,7 +347,7 @@ void AGOSEngine_Simon2::os2_playTune() {
 	if (_lastMusicPlayed != music)
 		_nextMusicToPlay = music;
 	else
-		_midi->startTrack(track);
+		_midi->play(track);
 }
 
 void AGOSEngine_Simon2::os2_screenTextPObj() {
diff --git a/engines/agos/vga_s2.cpp b/engines/agos/vga_s2.cpp
index 4f536c5d3fd..78849a9776c 100644
--- a/engines/agos/vga_s2.cpp
+++ b/engines/agos/vga_s2.cpp
@@ -146,7 +146,7 @@ void AGOSEngine::vc69_playSeq() {
 	// as a means of stopping what music is currently
 	// playing.
 	_midi->setLoop(loop != 0);
-	_midi->startTrack(track);
+	_midi->play(track);
 }
 
 void AGOSEngine::vc70_joinSeq() {
@@ -195,7 +195,7 @@ void AGOSEngine::vc72_segue() {
 		stopMusic();
 	} else {
 		_midi->setLoop(loop != 0);
-		_midi->startTrack(track);
+		_midi->play(track);
 	}
 }
 
diff --git a/engines/agos/vga_ww.cpp b/engines/agos/vga_ww.cpp
index 8d0a5de6eac..4decf54fd5f 100644
--- a/engines/agos/vga_ww.cpp
+++ b/engines/agos/vga_ww.cpp
@@ -24,6 +24,7 @@
 
 #include "agos/agos.h"
 #include "agos/intern.h"
+#include "agos/midi.h"
 
 #include "common/system.h"
 
@@ -196,7 +197,7 @@ void AGOSEngine::vc61() {
 
 void AGOSEngine::vc62_fastFadeOut() {
 	vc29_stopAllSounds();
-
+	
 	if (!_fastFadeOutFlag) {
 		uint i, fadeSize, fadeCount;
 
@@ -219,6 +220,11 @@ void AGOSEngine::vc62_fastFadeOut() {
 			fadeSize = 4;
 		}
 
+		if (getGameType() == GType_SIMON2 && _nextMusicToPlay != -1)
+			// Music will be stopped after the screen fade, so fade out the
+			// music during the screen fade.
+			_midi->fadeOut();
+
 		for (i = fadeCount; i != 0; --i) {
 			paletteFadeOut(_currentPalette, _fastFadeCount, fadeSize);
 			_system->getPaletteManager()->setPalette(_currentPalette, 0, _fastFadeCount);


Commit: b55b1da128f0ab2e020b5804be55bb5d3ec010f6
    https://github.com/scummvm/scummvm/commit/b55b1da128f0ab2e020b5804be55bb5d3ec010f6
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:42+02:00

Commit Message:
AUDIO: Add arbitrary MIDI instrument remapping

This commit adds a function to the multisource MIDI drivers for specifying an
arbitrary instrument map for remapping the instruments in the MIDI data.

This is used for remapping instruments from two Simon 2 GM tracks to MT-32
because they are missing from the MT-32 MIDI data set.

Changed paths:
    audio/adlib_ms.cpp
    audio/mididrv_ms.cpp
    audio/mididrv_ms.h
    audio/miles_adlib.cpp
    audio/mt32gm.cpp


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index 4c3002b0155..99a345df9c8 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -768,6 +768,10 @@ void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller
 }
 
 void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) {
+	if (_instrumentRemapping && channel != MIDI_RHYTHM_CHANNEL)
+		// Apply instrument remapping (if specified) to instrument channels.
+		program = _instrumentRemapping[program];
+
 	// Just set the MIDI program value; this event does not affect active notes.
 	_controlData[source][channel].program = program;
 }
diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp
index 82cbf64c119..e774c0f9c4c 100644
--- a/audio/mididrv_ms.cpp
+++ b/audio/mididrv_ms.cpp
@@ -52,6 +52,7 @@ MidiDriver_Multisource::ControllerDefaults::ControllerDefaults() :
 	pitchBendSensitivity(-1) { }
 
 MidiDriver_Multisource::MidiDriver_Multisource() :
+		_instrumentRemapping(nullptr),
 		_userVolumeScaling(false),
 		_userMusicVolume(192),
 		_userSfxVolume(192),
@@ -346,6 +347,10 @@ void MidiDriver_Multisource::setSourceNeutralVolume(uint8 source, uint16 volume)
 	_sources[source].neutralVolume = volume;
 }
 
+void MidiDriver_Multisource::setInstrumentRemapping(byte *instrumentRemapping) {
+	_instrumentRemapping = instrumentRemapping;
+}
+
 void MidiDriver_Multisource::syncSoundSettings() {
 	// Get user volume settings.
 	_userMusicVolume = MIN(256, ConfMan.getInt("music_volume"));
diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h
index 9525522079f..3a1c70cdf76 100644
--- a/audio/mididrv_ms.h
+++ b/audio/mididrv_ms.h
@@ -360,6 +360,21 @@ public:
 	 */
 	void clearControllerDefault(ControllerDefaultType type);
 
+	/**
+	 * Sets an instrument map for arbitrarily remapping instruments in the MIDI
+	 * data. The map should consist of 128 bytes, with the index representing
+	 * the instrument number in the MIDI data, and the value being the
+	 * instrument which should be substituted.
+	 * This instrument mapping is applied before MT-32 to GM or GM to MT-32
+	 * instrument mapping.
+	 * Call this method with nullptr as parameter to clear a previously set
+	 * instrument remapping.
+	 * 
+	 * @param instrumentRemapping The instrument map that should be used for
+	 * remapping, or nullptr to disable remapping.
+	 */
+	void setInstrumentRemapping(byte *instrumentRemapping);
+
 	/**
 	 * Applies the user volume settings to the MIDI driver. MIDI channel
 	 * volumes will be scaled using the user volume.
@@ -423,6 +438,9 @@ protected:
 	// Default values for each controller
 	ControllerDefaults _controllerDefaults;
 
+	// Map for arbitrary instrument remapping.
+	byte *_instrumentRemapping;
+
 	// True if the driver should scale MIDI channel volume to the user
 	// specified volume settings.
 	bool _userVolumeScaling;
diff --git a/audio/miles_adlib.cpp b/audio/miles_adlib.cpp
index e43742a860c..8be15e794fe 100644
--- a/audio/miles_adlib.cpp
+++ b/audio/miles_adlib.cpp
@@ -1145,6 +1145,10 @@ void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumb
 }
 
 void MidiDriver_Miles_AdLib::programChange(byte midiChannel, byte patchId) {
+	if (_instrumentRemapping && midiChannel != MIDI_RHYTHM_CHANNEL)
+		// Apply instrument remapping (if specified) to instrument channels.
+		patchId = _instrumentRemapping[patchId];
+
 	const InstrumentEntry *instrumentPtr = nullptr;
 	byte patchBank = _midiChannels[midiChannel].currentPatchBank;
 
diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
index ce86a2dfc1f..4e6f1fa9354 100644
--- a/audio/mt32gm.cpp
+++ b/audio/mt32gm.cpp
@@ -712,6 +712,10 @@ void MidiDriver_MT32GM::removeActiveNotes(uint8 outputChannel, bool sustainedNot
 }
 
 void MidiDriver_MT32GM::programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
+	if (_instrumentRemapping && outputChannel != MIDI_RHYTHM_CHANNEL)
+		// Apply instrument remapping (if specified) to instrument channels.
+		patchId = _instrumentRemapping[patchId];
+
 	// remember patch id for the current MIDI-channel
 	controlData.program = patchId;
 


Commit: a72704764bd4c95564a0cad8b4980d4398a92ec8
    https://github.com/scummvm/scummvm/commit/a72704764bd4c95564a0cad8b4980d4398a92ec8
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
AGOS: Fix Simon 2 intro first scene MT-32 music

The first scene of the Simon The Sorcerer 2 intro uses 3 music tracks. The last
2 are missing from the MT-32 MIDI data set. The original interpreter just stops
playing music after the first track and does not restart until the next scene.

This commit fixes this problem by using the GM versions of these 2 tracks
instead and mapping the GM instruments to MT-32 ones.

Changed paths:
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/res_snd.cpp
    engines/agos/script_s2.cpp
    engines/agos/vga_s2.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 446c749e75c..91060bfcd60 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -754,9 +754,9 @@ void AGOSEngine_Simon2::setupGame() {
 	_tableMemSize = 100000;
 	// Check whether to use MT-32 MIDI tracks in Simon the Sorcerer 2
 	if (getGameType() == GType_SIMON2 && getPlatform() == Common::kPlatformDOS && _midi->usesMT32Data())
-		_musicIndexBase = (1128 + 612) / 4;
+		_musicIndexBase = MUSIC_INDEX_BASE_SIMON2_MT32;
 	else
-		_musicIndexBase = 1128 / 4;
+		_musicIndexBase = MUSIC_INDEX_BASE_SIMON2_GM;
 	_soundIndexBase = 1660 / 4;
 	_frameCount = 1;
 	_vgaBaseDelay = 1;
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index e9e5fa40ce7..94beead2735 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -202,6 +202,11 @@ protected:
 	// List of Simon 1 DOS floppy SFX which use rhythm notes.
 	static const byte SIMON1_RHYTHM_SFX[];
 
+	// Music index base for Simon 2 GM data.
+	static const uint16 MUSIC_INDEX_BASE_SIMON2_GM = 1128 / 4;
+	// Music index base for Simon 2 MT-32 data.
+	static const uint16 MUSIC_INDEX_BASE_SIMON2_MT32 = (1128 + 612) / 4;
+
 protected:
 	friend class Debugger;
 
@@ -1289,7 +1294,11 @@ protected:
 	void windowScroll(WindowBlock *window);
 	virtual void windowDrawChar(WindowBlock *window, uint x, uint y, byte chr);
 
-	void loadMusic(uint16 track);
+	// Loads the MIDI data for the specified track. The forceSimon2Gm parameter
+	// forces loading the MIDI data from the GM data set and activates GM to
+	// MT-32 instrument remapping. This is useful only for a specific
+	// workaround (see AGOSEngine_Simon2::playMusic for more details).
+	void loadMusic(uint16 track, bool forceSimon2Gm = false);
 	void playModule(uint16 music);
 	virtual void playMusic(uint16 music, uint16 track);
 	void stopMusic();
@@ -1943,6 +1952,10 @@ protected:
 	void clearVideoWindow(uint16 windowNum, uint16 color) override;
 
 	void playSpeech(uint16 speechId, uint16 vgaSpriteId) override;
+	// This overload plays the music track specified in the second parameter.
+	// The first parameter is ignored; music data must be loaded using the
+	// loadMusic method before calling this method.
+	void playMusic(uint16 music, uint16 track) override;
 
 	Common::String genSaveName(int slot) const override;
 };
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 7c92d683d8c..9d0f1f19289 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -54,6 +54,20 @@ namespace AGOS {
 // and just provide a factory function.
 extern MidiParser *MidiParser_createS1D();
 
+// This instrument remapping has been constructed by checking how the GM
+// instruments correspond to MT-32 instruments in other tracks (f.e. track 10-3
+// is similar to track 11).
+byte MidiPlayer::SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[] {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x1D, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x56, 0x53, 0x4B, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
 MidiPlayer::MidiPlayer(AGOSEngine *vm) {
 	// Since initialize() is called every time the music changes,
 	// this is where we'll initialize stuff that must persist
@@ -905,6 +919,11 @@ void MidiPlayer::setLoop(bool loop) {
 		_parserMusic->property(MidiParser::mpAutoLoop, loop);
 }
 
+void MidiPlayer::setSimon2Remapping(bool remap) {
+	if (_driverMsMusic)
+		_driverMsMusic->setInstrumentRemapping(remap ? SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING : nullptr);
+}
+
 void MidiPlayer::queueTrack(int track, bool loop) {
 	Common::StackLock lock(_mutex);
 
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index 96452cffcb8..2b4f3b9f69b 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -62,6 +62,10 @@ struct MusicInfo {
 
 class MidiPlayer : public MidiDriver_BASE {
 protected:
+	// Instrument map specifically for remapping the instruments of the GM
+	// version of Simon 2 track 10 subtracks 2 and 3 to MT-32.
+	static byte SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[];
+
 	AGOSEngine *_vm;
 
 	Common::Mutex _mutex;
@@ -138,6 +142,9 @@ public:
 	bool usesMT32Data() const;
 	bool hasAdLibSfx() const;
 	void setLoop(bool loop);
+	// Activates or deactivates remapping GM to MT-32 instruments for
+	// Simon 2 track 10.
+	void setSimon2Remapping(bool remap);
 	void startTrack(int track);
 	void queueTrack(int track, bool loop);
 	bool isPlaying(bool checkQueued = false);
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index 69e080bdb02..7c424ae77c0 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -131,12 +131,18 @@ void AGOSEngine::skipSpeech() {
 	}
 }
 
-void AGOSEngine::loadMusic(uint16 music) {
+void AGOSEngine::loadMusic(uint16 music, bool forceSimon2Gm) {
 	stopMusic();
 
-	_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
+	uint16 indexBase = forceSimon2Gm ? MUSIC_INDEX_BASE_SIMON2_GM : _musicIndexBase;
+
+	_gameFile->seek(_gameOffsetsPtr[indexBase + music - 1], SEEK_SET);
 	_midi->loadMusic(_gameFile);
 
+	// Activate Simon 2 GM to MT-32 remapping if we force GM, otherwise
+	// deactivate it (in case it was previously activated).
+	_midi->setSimon2Remapping(forceSimon2Gm);
+
 	_lastMusicPlayed = music;
 	_nextMusicToPlay = -1;
 }
@@ -228,6 +234,23 @@ void AGOSEngine::playModule(uint16 music) {
 	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream);
 }
 
+void AGOSEngine_Simon2::playMusic(uint16 music, uint16 track) {
+	if (_lastMusicPlayed == 10 && getPlatform() == Common::kPlatformDOS && _midi->usesMT32Data()) {
+		// WORKAROUND Simon 2 track 10 (played during the first intro scene)
+		// consist of 3 subtracks. Subtracks 2 and 3 are missing from the MT-32
+		// MIDI data. The original interpreter just stops playing after track 1
+		// and does not restart until the next scene.
+		// We fix this by loading the GM version of track 10 and remapping the
+		// instruments to MT-32.
+
+		// Reload track 10 and force GM for all subtracks but the first (this
+		// also activates the instrument remapping).
+		loadMusic(10, track > 0);
+	}
+
+	_midi->play(track);
+}
+
 void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 	stopMusic();
 
diff --git a/engines/agos/script_s2.cpp b/engines/agos/script_s2.cpp
index 143f9bc2a0c..4e2104978b8 100644
--- a/engines/agos/script_s2.cpp
+++ b/engines/agos/script_s2.cpp
@@ -347,7 +347,7 @@ void AGOSEngine_Simon2::os2_playTune() {
 	if (_lastMusicPlayed != music)
 		_nextMusicToPlay = music;
 	else
-		_midi->play(track);
+		playMusic(0, track);
 }
 
 void AGOSEngine_Simon2::os2_screenTextPObj() {
diff --git a/engines/agos/vga_s2.cpp b/engines/agos/vga_s2.cpp
index 78849a9776c..94f3040ad98 100644
--- a/engines/agos/vga_s2.cpp
+++ b/engines/agos/vga_s2.cpp
@@ -146,7 +146,7 @@ void AGOSEngine::vc69_playSeq() {
 	// as a means of stopping what music is currently
 	// playing.
 	_midi->setLoop(loop != 0);
-	_midi->play(track);
+	playMusic(0, track);
 }
 
 void AGOSEngine::vc70_joinSeq() {
@@ -195,7 +195,7 @@ void AGOSEngine::vc72_segue() {
 		stopMusic();
 	} else {
 		_midi->setLoop(loop != 0);
-		_midi->play(track);
+		playMusic(0, track);
 	}
 }
 


Commit: 00a907f524a9067db9fd90adc06c5fcb44aac9cd
    https://github.com/scummvm/scummvm/commit/00a907f524a9067db9fd90adc06c5fcb44aac9cd
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
MIDI: Fix parser auto loop skipping first event

The auto loop functionality of the MIDI parser would call jumpToTick to jump
back to the start of the MIDI stream, then call parseNextEvent to read the
first MIDI event. However, jumpToTick already makes sure the first MIDI event
after the jump point is parsed. Calling parseNextEvent again would discard the
first and parse the second MIDI event.
Fixed this by removing the call to parseNextEvent after jumping to the start.

Changed paths:
    audio/midiparser.cpp


diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp
index 9db5d10e90d..a4ba38924c1 100644
--- a/audio/midiparser.cpp
+++ b/audio/midiparser.cpp
@@ -293,7 +293,6 @@ bool MidiParser::processEvent(const EventInfo &info, bool fireEvents) {
 			// as well as sending it to the output device.
 			if (_autoLoop) {
 				jumpToTick(0);
-				parseNextEvent(_nextEvent);
 			} else {
 				stopPlaying();
 				if (fireEvents)


Commit: 6a9fc7396290885e98adcb0c3bade352e90642c7
    https://github.com/scummvm/scummvm/commit/6a9fc7396290885e98adcb0c3bade352e90642c7
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
AUDIO: Small MIDI driver enhancements and fixes

This commit adds the following functionality to the MIDI drivers:

- Add checking if a driver is ready to process MIDI events for a specific
source (rather than any source). To facilitate this, SysExes can now be sent
using a source number. This is stored with the SysEx data in the SysEx queue
and can be checked when isReady is called with a source number.
- Allow specifying controller default values per MIDI channel. Currently this
is only implemented for program.
- The OPL dynamic channel allocation algorithm will now respect statically
allocated channels, in case a subclass uses combined static and dynamic channel
allocation.

It also fixes the following bugs:

- Instrument remapping can now be specified using const arrays.
- OPL instrument writing code is refactored to a separate function.
- OPL note on with velocity 0 would be handled as a note off, and then
continued to be processed as a note on.
- OPL writeFrequency would always write key on bit, even if the note is not
active.
- MT-32 default channel volume was incorrect.

Changed paths:
    audio/adlib_ms.cpp
    audio/adlib_ms.h
    audio/mididrv.h
    audio/mididrv_ms.cpp
    audio/mididrv_ms.h
    audio/miles_adlib.cpp
    audio/mt32gm.cpp
    audio/mt32gm.h
    engines/groovie/music.cpp
    engines/groovie/music.h


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index 99a345df9c8..2da9051bfab 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -78,7 +78,7 @@ void AdLibBnkInstrumentOperatorDefinition::toOplInstrumentOperatorDefinition(Opl
 	operatorDef.waveformSelect = waveformSelect;
 }
 
-void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition& instrumentDef) {
+void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition &instrumentDef) {
 	instrumentDef.fourOperator = false;
 
 	operator0.toOplInstrumentOperatorDefinition(instrumentDef.operator0, waveformSelect0);
@@ -637,9 +637,11 @@ void MidiDriver_ADLIB_Multisource::noteOff(uint8 channel, uint8 note, uint8 velo
 }
 
 void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
-	if (velocity == 0)
+	if (velocity == 0) {
 		// Note on with velocity 0 is a note off.
 		noteOff(channel, note, velocity, source);
+		return;
+	}
 
 	InstrumentInfo instrument = determineInstrument(channel, source, note);
 	// If rhythm mode is on and the note is on the rhythm channel, this note
@@ -687,20 +689,9 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 		activeNote->instrumentId = instrument.instrumentId;
 		activeNote->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->rhythmType, instrument.instrumentDef->fourOperator);
-			const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
-			writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
-			writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
-			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);
-		}
+		// Write out the instrument definition, volume and panning.
+		writeInstrument(oplChannel, instrument);
 
-		// Determine and write panning and write feedback and connection.
-		writePanning(oplChannel, instrument.instrumentDef->rhythmType);
 		// Calculate and write frequency and block and write key on bit.
 		writeFrequency(oplChannel, instrument.instrumentDef->rhythmType);
 
@@ -768,10 +759,6 @@ void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller
 }
 
 void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) {
-	if (_instrumentRemapping && channel != MIDI_RHYTHM_CHANNEL)
-		// Apply instrument remapping (if specified) to instrument channels.
-		program = _instrumentRemapping[program];
-
 	// Just set the MIDI program value; this event does not affect active notes.
 	_controlData[source][channel].program = program;
 }
@@ -861,8 +848,8 @@ void MidiDriver_ADLIB_Multisource::applyControllerDefaults(uint8 source) {
 		}
 	} else {
 		for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
-			if (_controllerDefaults.program >= 0) {
-				_controlData[source][i].program = _controllerDefaults.program;
+			if (_controllerDefaults.program[i] >= 0) {
+				_controlData[source][i].program = _controllerDefaults.program[i];
 			}
 			if (_controllerDefaults.channelPressure >= 0) {
 				_controlData[source][i].channelPressure = _controllerDefaults.channelPressure;
@@ -1093,12 +1080,15 @@ void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
 		}
 	}
 	if (_rhythmMode && !_rhythmModeIgnoreNoteOffs && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
+		bool rhythmChanged = false;
 		for (int i = 0; i < 5; i++) {
 			if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
 				_activeRhythmNotes[i].noteActive = false;
+				rhythmChanged = true;
 			}
 		}
-		writeRhythm();
+		if (rhythmChanged)
+			writeRhythm();
 	}
 	
 	_activeNotesMutex.unlock();
@@ -1303,7 +1293,11 @@ MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::deter
 	} else {
 		// On non-rhythm channels, use the active instrument (program) on the
 		// MIDI channel.
-		instrument.instrumentId = _controlData[source][channel].program;
+		byte program = _controlData[source][channel].program;
+		if (_instrumentRemapping)
+			// Apply instrument remapping (if specified).
+			program = _instrumentRemapping[program];
+		instrument.instrumentId = program;
 		instrument.instrumentDef = &_instrumentBank[instrument.instrumentId];
 		instrument.oplNote = note;
 	}
@@ -1332,6 +1326,10 @@ uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 sour
 		uint32 inactiveNoteCounter = 0xFFFF, instrumentNoteCounter = 0xFFFF, lowestNoteCounter = 0xFFFF;
 		for (int i = 0; i < _numMelodicChannels; i++) {
 			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].channelAllocated)
+				// Channel has been statically allocated. Try the next channel.
+				continue;
+
 			if (_activeNotes[oplChannel].noteCounterValue == 0) {
 				// This channel is unused. No need to look any further.
 				unusedChannel = oplChannel;
@@ -1538,7 +1536,7 @@ int32 MidiDriver_ADLIB_Multisource::calculatePitchBend(uint8 channel, uint8 sour
 	return pitchBend;
 }
 
-uint8 MidiDriver_ADLIB_Multisource::calculateVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition& instrumentDef, uint8 operatorNum) {
+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;
 
@@ -1754,6 +1752,26 @@ uint16 MidiDriver_ADLIB_Multisource::determineChannelRegisterOffset(uint8 oplCha
 	return offset + (oplChannel % numChannelsPerSet);
 }
 
+void MidiDriver_ADLIB_Multisource::writeInstrument(uint8 oplChannel, InstrumentInfo instrument) {
+	ActiveNote *activeNote = (instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1]);
+	activeNote->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->rhythmType, instrument.instrumentDef->fourOperator);
+		const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
+		writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
+		writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
+		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, instrument.instrumentDef->rhythmType);
+}
+
 void MidiDriver_ADLIB_Multisource::writeKeyOff(uint8 oplChannel, OplInstrumentRhythmType rhythmType, bool forceWrite) {
 	_activeNotesMutex.lock();
 
@@ -1858,7 +1876,7 @@ void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel, OplInstrumen
 	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) | (rhythmType == RHYTHM_TYPE_UNDEFINED ? OPL_MASK_KEYON : 0));
+		(frequency >> 8) | (rhythmType == RHYTHM_TYPE_UNDEFINED && activeNote->noteActive ? OPL_MASK_KEYON : 0));
 
 	_activeNotesMutex.unlock();
 }
@@ -1866,9 +1884,11 @@ void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel, OplInstrumen
 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) {
+	// Write the value to the register if it is a timer register, if forceWrite
+	// is specified or if the new register value is different from the current
+	// value.
+	if ((reg >= 1 && reg <= 3) || (_oplType == OPL::Config::kDualOpl2 && reg >= 0x101 && reg <= 0x103) || 
+			forceWrite || _shadowRegisters[reg] != value) {
 		_shadowRegisters[reg] = value;
 		_opl->writeReg(reg, value);
 	}
diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h
index c002a784887..235ba8e3aa6 100644
--- a/audio/adlib_ms.h
+++ b/audio/adlib_ms.h
@@ -606,15 +606,15 @@ public:
 	uint32 property(int prop, uint32 param) override;
 	uint32 getBaseTempo() override;
 	/**
-	 * This driver does not use MidiChannel objects, so this function returns 0.
+	 * This driver does not use MidiChannel objects, so this function returns nullptr.
 	 * 
-	 * @return 0
+	 * @return nullptr
 	 */
 	MidiChannel *allocateChannel() override;
 	/**
-	 * This driver does not use MidiChannel objects, so this function returns 0.
+	 * This driver does not use MidiChannel objects, so this function returns nullptr.
 	 * 
-	 * @return 0
+	 * @return nullptr
 	 */
 	MidiChannel *getPercussionChannel() override;
 
@@ -1030,7 +1030,14 @@ protected:
 	 * @return The offset to the base register for this channel.
 	 */
 	uint16 determineChannelRegisterOffset(uint8 oplChannel, bool fourOperator = false);
-
+	/**
+	 * Writes the specified instrument definition to the specified OPL channel.
+	 * It will calculate volume and panning if necessary.
+	 * 
+	 * @param oplChannel The OPL channel on which to write the instrument.
+	 * @param instrument The data of the instrument to write.
+	 */
+	void writeInstrument(uint8 oplChannel, InstrumentInfo instrument);
 	/**
 	 * Sets the key on bit to false for the specified OPL channel or rhythm
 	 * instrument and updates _activeNotes or _activeRhythmNotes with the new
@@ -1076,7 +1083,7 @@ protected:
 	 * calculated and written. Use type undefined to calculate panning for a
 	 * melodic instrument.
 	 */
-	void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
+	virtual void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
 	/**
 	 * Calculates the frequency for the active note on the specified OPL
 	 * channel or of the specified rhythm type (@see calculateFrequency) and
@@ -1088,7 +1095,7 @@ protected:
 	 * be calculated and written. Use type undefined to calculate the frequency
 	 * for a melodic instrument.
 	 */
-	void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
+	virtual void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED);
 
 	/**
 	 * Writes the specified value to the specified OPL register.
diff --git a/audio/mididrv.h b/audio/mididrv.h
index d232588fdd0..275dd7b1235 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -244,8 +244,11 @@ public:
 	 * A driver implementation might need time to prepare playback of
 	 * a track. Use this function to check if the driver is ready to
 	 * receive MIDI events.
+	 *
+	 * @param source Check if the driver is ready to receive events from this
+	 * specific source. Specify -1 to check readiness regardless of source.
 	 */
-	virtual bool isReady() { return true; }
+	virtual bool isReady(int8 source = -1) { return true; }
 
 protected:
 
diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp
index e774c0f9c4c..f662f88260e 100644
--- a/audio/mididrv_ms.cpp
+++ b/audio/mididrv_ms.cpp
@@ -37,19 +37,20 @@ MidiDriver_Multisource::MidiSource::MidiSource() :
 	fadeDuration(0) { }
 
 MidiDriver_Multisource::ControllerDefaults::ControllerDefaults() :
-	// The -1 value indicates no default value should be set on the controller.
-	program(-1),
-	instrumentBank(-1),
-	drumkit(-1),
-	channelPressure(-1),
-	pitchBend(-1),
-	modulation(-1),
-	volume(-1),
-	panning(-1),
-	expression(-1),
-	sustain(-1),
-	rpn(-1),
-	pitchBendSensitivity(-1) { }
+		// The -1 value indicates no default value should be set on the controller.
+		instrumentBank(-1),
+		drumkit(-1),
+		channelPressure(-1),
+		pitchBend(-1),
+		modulation(-1),
+		volume(-1),
+		panning(-1),
+		expression(-1),
+		sustain(-1),
+		rpn(-1),
+		pitchBendSensitivity(-1) {
+	Common::fill(program, program + ARRAYSIZE(program), -1);
+}
 
 MidiDriver_Multisource::MidiDriver_Multisource() :
 		_instrumentRemapping(nullptr),
@@ -238,7 +239,7 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, in
 	// corresponding to the specified controller.
 	switch (type) {
 	case CONTROLLER_DEFAULT_PROGRAM:
-		_controllerDefaults.program = value;
+		Common::fill(_controllerDefaults.program, _controllerDefaults.program + ARRAYSIZE(_controllerDefaults.program), value);
 		break;
 	case CONTROLLER_DEFAULT_INSTRUMENT_BANK:
 		_controllerDefaults.instrumentBank = value;
@@ -279,6 +280,19 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, in
 	}
 }
 
+void MidiDriver_Multisource::setControllerDefaults(ControllerDefaultType type, int16 *value) {
+	// Set the specified default values on _controllerDefaults on the field
+	// corresponding to the specified controller.
+	switch (type) {
+	case CONTROLLER_DEFAULT_PROGRAM:
+		Common::copy(value, value + ARRAYSIZE(_controllerDefaults.program), _controllerDefaults.program);
+		break;
+	default:
+		warning("MidiDriver_Multisource::setControllerDefaults - Unsupported controller default type %i", type);
+		break;
+	}
+}
+
 void MidiDriver_Multisource::clearControllerDefault(ControllerDefaultType type) {
 	// Reset the default value for this controller to -1.
 	setControllerDefault(type, -1);
@@ -347,7 +361,7 @@ void MidiDriver_Multisource::setSourceNeutralVolume(uint8 source, uint16 volume)
 	_sources[source].neutralVolume = volume;
 }
 
-void MidiDriver_Multisource::setInstrumentRemapping(byte *instrumentRemapping) {
+void MidiDriver_Multisource::setInstrumentRemapping(const byte *instrumentRemapping) {
 	_instrumentRemapping = instrumentRemapping;
 }
 
diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h
index 3a1c70cdf76..945dc899715 100644
--- a/audio/mididrv_ms.h
+++ b/audio/mididrv_ms.h
@@ -189,7 +189,7 @@ protected:
 	// Stores the default values that should be set for each controller.
 	// -1 means no explicit default should be set for that controller.
 	struct ControllerDefaults {
-		int8 program;
+		int8 program[16];
 		int8 instrumentBank;
 		int8 drumkit;
 
@@ -353,6 +353,20 @@ public:
 	 * @param value The default value which should be set.
 	 */
 	void setControllerDefault(ControllerDefaultType type, int16 value);
+	/**
+	 * Specify a default value for a controller which should be set when a new
+	 * track is started. This expects an array of values, each of which will
+	 * be used as the default for the corresponding MIDI channel.
+	 *
+	 * This is currently only supported for program.
+	 *
+	 * See setControllerDefault for more details.
+	 *
+	 * @param type The controller which should be reset.
+	 * @param values The default values which should be set. Must be a 16 value
+	 * array.
+	 */
+	void setControllerDefaults(ControllerDefaultType type, int16 *values);
 	/**
 	 * Clears a previously set default value for the specified controller.
 	 * 
@@ -373,7 +387,7 @@ public:
 	 * @param instrumentRemapping The instrument map that should be used for
 	 * remapping, or nullptr to disable remapping.
 	 */
-	void setInstrumentRemapping(byte *instrumentRemapping);
+	void setInstrumentRemapping(const byte *instrumentRemapping);
 
 	/**
 	 * Applies the user volume settings to the MIDI driver. MIDI channel
@@ -439,7 +453,7 @@ protected:
 	ControllerDefaults _controllerDefaults;
 
 	// Map for arbitrary instrument remapping.
-	byte *_instrumentRemapping;
+	const byte *_instrumentRemapping;
 
 	// True if the driver should scale MIDI channel volume to the user
 	// specified volume settings.
diff --git a/audio/miles_adlib.cpp b/audio/miles_adlib.cpp
index 8be15e794fe..376a10e2941 100644
--- a/audio/miles_adlib.cpp
+++ b/audio/miles_adlib.cpp
@@ -1224,8 +1224,8 @@ void MidiDriver_Miles_AdLib::applyControllerDefaults(uint8 source) {
 		return;
 
 	for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
-		if (_controllerDefaults.program >= 0) {
-			_midiChannels[i].currentProgram = _controllerDefaults.program;
+		if (_controllerDefaults.program[i] >= 0) {
+			_midiChannels[i].currentProgram = _controllerDefaults.program[i];
 		}
 		if (_controllerDefaults.pitchBend >= 0) {
 			_midiChannels[i].currentPitchBender = _controllerDefaults.pitchBend;
diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
index 4e6f1fa9354..77d83772949 100644
--- a/audio/mt32gm.cpp
+++ b/audio/mt32gm.cpp
@@ -378,6 +378,24 @@ void MidiDriver_MT32GM::close() {
 	}
 }
 
+bool MidiDriver_MT32GM::isReady(int8 source) {
+	Common::StackLock lock(_sysExQueueMutex);
+
+	// For an unspecified source, just return if the queue is empty or not.
+	if (source < 0)
+		return _sysExQueue.empty();
+
+	// For a specific source, check if there is a SysEx for that source in the
+	// queue.
+	for (Common::ListInternal::Iterator<SysExData> it = _sysExQueue.begin();
+			it != _sysExQueue.end(); it++) {
+		if (it->source == source)
+			return false;
+	}
+
+	return true;
+}
+
 uint32 MidiDriver_MT32GM::property(int prop, uint32 param) {
 	switch (prop) {
 	case PROP_MIDI_DATA_REVERSE_PANNING:
@@ -458,36 +476,36 @@ void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControl
 	if (outputChannel != MIDI_RHYTHM_CHANNEL) {
 		// Apply default bank and program only to melodic channels.
 		if (_controllerDefaults.instrumentBank >= 0 && controlData.instrumentBank != _controllerDefaults.instrumentBank) {
-			send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, _controllerDefaults.instrumentBank);
-			send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
+			controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_MSB, _controllerDefaults.instrumentBank, source, controlData);
+			controlChange(outputChannel, MIDI_CONTROLLER_BANK_SELECT_LSB, 0, source, controlData);
 		}
-		if (_controllerDefaults.program >= 0 && controlData.program != _controllerDefaults.program) {
-			send(source, MIDI_COMMAND_PROGRAM_CHANGE | outputChannel, _controllerDefaults.program, 0);
+		if (_controllerDefaults.program[outputChannel] >= 0 && controlData.program != _controllerDefaults.program[outputChannel]) {
+			programChange(outputChannel, _controllerDefaults.program[outputChannel], source, controlData);
 		}
 	} else {
 		// Apply default drumkit only to the rhythm channel.
 		if (_controllerDefaults.drumkit >= 0 && controlData.program != _controllerDefaults.drumkit) {
-			send(source, MIDI_COMMAND_PROGRAM_CHANGE | outputChannel, _controllerDefaults.drumkit, 0);
+			programChange(outputChannel, _controllerDefaults.drumkit, source, controlData);
 		}
 	}
 	if (_controllerDefaults.channelPressure >= 0 && controlData.channelPressure != _controllerDefaults.channelPressure) {
-		send(source, MIDI_COMMAND_CHANNEL_AFTERTOUCH | outputChannel, _controllerDefaults.channelPressure, 0);
+		channelAftertouch(outputChannel, _controllerDefaults.channelPressure, source, controlData);
 	}
 	if (_controllerDefaults.pitchBend >= 0 && controlData.pitchWheel != _controllerDefaults.pitchBend) {
-		send(source, MIDI_COMMAND_PITCH_BEND | outputChannel, _controllerDefaults.pitchBend & 0x7F, _controllerDefaults.pitchBend >> 7);
+		pitchBend(outputChannel, _controllerDefaults.pitchBend & 0x7F, _controllerDefaults.pitchBend >> 7, source, controlData);
 	}
 
 	if (_controllerDefaults.modulation >= 0 && controlData.modulation != _controllerDefaults.modulation) {
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_MODULATION, _controllerDefaults.modulation);
+		controlChange(outputChannel, MIDI_CONTROLLER_MODULATION, _controllerDefaults.modulation, source, controlData);
 	}
 	if (_controllerDefaults.volume >= 0 && controlData.volume != _controllerDefaults.volume) {
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_VOLUME, _controllerDefaults.volume);
+		controlChange(outputChannel, MIDI_CONTROLLER_VOLUME, _controllerDefaults.volume, source, controlData);
 	}
 	if (_controllerDefaults.panning >= 0 && controlData.panPosition != _controllerDefaults.panning) {
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_PANNING, _controllerDefaults.panning);
+		controlChange(outputChannel, MIDI_CONTROLLER_PANNING, _controllerDefaults.panning, source, controlData);
 	}
 	if (_controllerDefaults.expression >= 0 && controlData.expression != _controllerDefaults.expression) {
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_EXPRESSION, _controllerDefaults.expression);
+		controlChange(outputChannel, MIDI_CONTROLLER_EXPRESSION, _controllerDefaults.expression, source, controlData);
 	}
 
 	// RPN will be changed by setting pitch bend sensitivity, so store the
@@ -495,10 +513,10 @@ void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControl
 	uint16 rpn = controlData.rpn;
 	bool setRpn = false;
 	if (_controllerDefaults.pitchBendSensitivity >= 0 && controlData.pitchBendSensitivity != _controllerDefaults.pitchBendSensitivity) {
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8);
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF);
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_DATA_ENTRY_MSB, _controllerDefaults.pitchBendSensitivity);
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
+		controlChange(outputChannel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_PITCH_BEND_SENSITIVITY >> 8, source, controlData);
+		controlChange(outputChannel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENSITIVITY & 0xFF, source, controlData);
+		controlChange(outputChannel, MIDI_CONTROLLER_DATA_ENTRY_MSB, _controllerDefaults.pitchBendSensitivity, source, controlData);
+		controlChange(outputChannel, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0, source, controlData);
 		if (rpn != controlData.rpn)
 			// Active RPN was changed; reset it to previous value (or default).
 			setRpn = true;
@@ -510,8 +528,8 @@ void MidiDriver_MT32GM::applyControllerDefaults(uint8 source, MidiChannelControl
 	}
 
 	if (setRpn) {
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_MSB, rpn >> 8);
-		send(source, MIDI_COMMAND_CONTROL_CHANGE | outputChannel, MIDI_CONTROLLER_RPN_LSB, rpn & 0xFF);
+		controlChange(outputChannel, MIDI_CONTROLLER_RPN_MSB, rpn >> 8, source, controlData);
+		controlChange(outputChannel, MIDI_CONTROLLER_RPN_LSB, rpn & 0xFF, source, controlData);
 	}
 }
 
@@ -712,16 +730,16 @@ void MidiDriver_MT32GM::removeActiveNotes(uint8 outputChannel, bool sustainedNot
 }
 
 void MidiDriver_MT32GM::programChange(byte outputChannel, byte patchId, int8 source, MidiChannelControlData &controlData, bool channelLockedByOtherSource) {
-	if (_instrumentRemapping && outputChannel != MIDI_RHYTHM_CHANNEL)
-		// Apply instrument remapping (if specified) to instrument channels.
-		patchId = _instrumentRemapping[patchId];
-
 	// remember patch id for the current MIDI-channel
 	controlData.program = patchId;
 
 	if (channelLockedByOtherSource)
 		return;
 
+	if (_instrumentRemapping && outputChannel != MIDI_RHYTHM_CHANNEL)
+		// Apply instrument remapping (if specified) to instrument channels.
+		patchId = _instrumentRemapping[patchId];
+
 	if (_midiType == MT_MT32) {
 		if (outputChannel == MIDI_RHYTHM_CHANNEL &&
 				!(!_nativeMT32 && _enableGS && patchId == 0x7F)) {
@@ -881,17 +899,18 @@ uint16 MidiDriver_MT32GM::sysExNoDelay(const byte *msg, uint16 length) {
 	return delay;
 }
 
-void MidiDriver_MT32GM::sysExQueue(const byte *msg, uint16 length) {
+void MidiDriver_MT32GM::sysExQueue(const byte *msg, uint16 length, int8 source) {
 	SysExData sysEx;
 	memcpy(sysEx.data, msg, length);
 	sysEx.length = length;
+	sysEx.source = source;
 
 	_sysExQueueMutex.lock();
-	_sysExQueue.push(sysEx);
+	_sysExQueue.push_back(sysEx);
 	_sysExQueueMutex.unlock();
 }
 
-uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay) {
+uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay, int8 source) {
 	if (!_nativeMT32)
 		// MT-32 SysExes have no effect on GM devices.
 		return 0;
@@ -934,7 +953,7 @@ uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32
 	sysExMessage[sysExPos++] = sysExChecksum & 0x7F;
 
 	if (queue) {
-		sysExQueue(sysExMessage, sysExPos);
+		sysExQueue(sysExMessage, sysExPos, source);
 	} else if (!delay) {
 		return sysExNoDelay(sysExMessage, sysExPos);
 	} else {
@@ -1071,6 +1090,20 @@ int8 MidiDriver_MT32GM::mapSourceChannel(uint8 source, uint8 dataChannel) {
 void MidiDriver_MT32GM::deinitSource(uint8 source) {
 	assert(source < MAXIMUM_SOURCES);
 
+	_sysExQueueMutex.lock();
+
+	// Remove any pending SysExes for this source from the queue.
+	Common::ListInternal::Iterator<SysExData> it = _sysExQueue.begin();
+	while (it != _sysExQueue.end()) {
+		if (it->source == source) {
+			it = _sysExQueue.erase(it);
+		} else {
+			it++;
+		}
+	}
+
+	_sysExQueueMutex.unlock();
+
 	MidiDriver_Multisource::deinitSource(source);
 
 	// Free channels which were used by this source.
@@ -1082,7 +1115,7 @@ void MidiDriver_MT32GM::deinitSource(uint8 source) {
 			// Set the sustain default value if it is specified (typically
 			// sustain would be turned off).
 			if (_controllerDefaults.sustain >= 0 && _controlData[i]->sustain != (_controllerDefaults.sustain >= 0x40)) {
-				send(-1, MIDI_COMMAND_CONTROL_CHANGE | i, MIDI_CONTROLLER_SUSTAIN, _controllerDefaults.sustain);
+				controlChange(i, MIDI_CONTROLLER_SUSTAIN, _controllerDefaults.sustain, _controlData[i]->source, *_controlData[i]);
 			}
 
 			_controlData[i]->source = -1;
@@ -1133,8 +1166,9 @@ void MidiDriver_MT32GM::onTimer() {
 
 	if (!_sysExQueue.empty() && _sysExDelay == 0) {
 		// Ready to send next SysEx message to the MIDI device
-		SysExData sysEx = _sysExQueue.pop();
+		SysExData sysEx = _sysExQueue.front();
 		_sysExDelay = sysExNoDelay(sysEx.data, sysEx.length) * 1000;
+		_sysExQueue.pop_front();
 	}
 
 	_sysExQueueMutex.unlock();
diff --git a/audio/mt32gm.h b/audio/mt32gm.h
index 4802e310aba..450fae6d338 100644
--- a/audio/mt32gm.h
+++ b/audio/mt32gm.h
@@ -24,8 +24,8 @@
 
 #include "audio/mididrv.h"
 #include "audio/mididrv_ms.h"
+#include "common/list.h"
 #include "common/mutex.h"
-#include "common/queue.h"
 
 /**
  * @defgroup audio_mt32_gm MIDI driver for MT-32 and GM
@@ -112,7 +112,7 @@ class MidiDriver_MT32GM : public MidiDriver_Multisource {
 public:
 	static const byte MT32_DEFAULT_INSTRUMENTS[8];
 	static const byte MT32_DEFAULT_PANNING[8];
-	static const uint8 MT32_DEFAULT_CHANNEL_VOLUME = 98;
+	static const uint8 MT32_DEFAULT_CHANNEL_VOLUME = 102;
 	static const uint8 GM_DEFAULT_CHANNEL_VOLUME = 100;
 	// Map for correcting Roland GS drumkit numbers.
 	static const uint8 GS_DRUMKIT_FALLBACK_MAP[128];
@@ -239,7 +239,8 @@ protected:
 	struct SysExData {
 		byte data[270];
 		uint16 length;
-		SysExData() : length(0) {
+		int8 source;
+		SysExData() : length(0), source(-1) {
 			memset(data, 0, sizeof(data));
 		}
 	};
@@ -259,10 +260,7 @@ public:
 	virtual int open(MidiDriver *driver, bool nativeMT32);
 	void close() override;
 	bool isOpen() const override { return _isOpen; }
-	bool isReady() override {
-		Common::StackLock lock(_sysExQueueMutex);
-		return _sysExQueue.empty();
-	}
+	bool isReady(int8 source = -1) override;
 	uint32 property(int prop, uint32 param) override;
 
 	using MidiDriver_BASE::send;
@@ -277,7 +275,7 @@ public:
 	 * MIDI messages (not using the queue) should not be sent until the queue
 	 * is empty.
 	 */
-	void sysExQueue(const byte *msg, uint16 length);
+	void sysExQueue(const byte *msg, uint16 length, int8 source = -1);
 	/**
 	 * Write data to an MT-32 memory location using a SysEx message.
 	 * This function will add the necessary header and checksum bytes.
@@ -297,7 +295,7 @@ public:
 	 * it is the caller's responsibility to make sure that the next SysEx is
 	 * not sent before this time has passed.
 	 */
-	uint16 sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue = false, bool delay = true);
+	uint16 sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue = false, bool delay = true, int8 source = -1);
 	void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
 
 	void stopAllNotes(bool stopSustainedNotes = false) override;
@@ -584,7 +582,7 @@ protected:
 	// SysEx message can be sent.
 	uint32 _sysExDelay;
 	// Queue of SysEx messages to be sent to the MIDI device.
-	Common::Queue<SysExData> _sysExQueue;
+	Common::List<SysExData> _sysExQueue;
 	// Mutex for write access to the SysEx queue.
 	Common::Mutex _sysExQueueMutex;
 };
diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp
index 81cd0b5e3d5..abb72bc3797 100644
--- a/engines/groovie/music.cpp
+++ b/engines/groovie/music.cpp
@@ -512,8 +512,8 @@ void MusicPlayerXMI::stopAllNotes(bool stopSustainedNotes) {
 		_driver->stopAllNotes(stopSustainedNotes);
 }
 
-bool MusicPlayerXMI::isReady() {
-	return _driver ? _driver->isReady() : false;
+bool MusicPlayerXMI::isReady(int8 source) {
+	return _driver ? _driver->isReady(source) : false;
 }
 
 void MusicPlayerXMI::updateVolume() {
diff --git a/engines/groovie/music.h b/engines/groovie/music.h
index 87ab4e21048..1cad84e194e 100644
--- a/engines/groovie/music.h
+++ b/engines/groovie/music.h
@@ -155,7 +155,7 @@ public:
 		if (_milesXmidiTimbres)
 			_milesXmidiTimbres->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
 	};
-	bool isReady() override;
+	bool isReady(int8 source = -1) override;
 
 	void setUserVolume(uint16 volume) override;
 


Commit: 32ba86649920fa4fc0ca9f6f6ccf51b2a62dc16b
    https://github.com/scummvm/scummvm/commit/32ba86649920fa4fc0ca9f6f6ccf51b2a62dc16b
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
AUDIO: Move MIDI parser source handling to superclass

This commit moves the source handling from the SMF and XMIDI parsers to the
MIDI parser superclass. This reduces code duplication.

Changed paths:
    audio/midiparser.cpp
    audio/midiparser.h
    audio/midiparser_smf.cpp
    audio/midiparser_smf.h
    audio/midiparser_xmidi.cpp


diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp
index a4ba38924c1..ddaa74a896e 100644
--- a/audio/midiparser.cpp
+++ b/audio/midiparser.cpp
@@ -30,7 +30,8 @@
 //
 //////////////////////////////////////////////////
 
-MidiParser::MidiParser() :
+MidiParser::MidiParser(int8 source) :
+_source(source),
 _hangingNotesCount(0),
 _driver(nullptr),
 _timerRate(0x4A0000),
@@ -84,11 +85,19 @@ void MidiParser::property(int prop, int value) {
 }
 
 void MidiParser::sendToDriver(uint32 b) {
-	_driver->send(b);
+	if (_source < 0) {
+		_driver->send(b);
+	} else {
+		_driver->send(_source, b);
+	}
 }
 
 void MidiParser::sendMetaEventToDriver(byte type, byte *data, uint16 length) {
-	_driver->metaEvent(type, data, length);
+	if (_source < 0) {
+		_driver->metaEvent(type, data, length);
+	} else {
+		_driver->metaEvent(_source, type, data, length);
+	}
 }
 
 void MidiParser::setTempo(uint32 tempo) {
@@ -186,7 +195,7 @@ void MidiParser::onTimer() {
 	// even if the parser does not parse events.
 	_sysExDelay -= (_sysExDelay > _timerRate) ? _timerRate : _sysExDelay;
 
-	if (!_position._playPos || !_driver || !_doParse || _pause || !_driver->isReady())
+	if (!_position._playPos || !_driver || !_doParse || _pause || !_driver->isReady(_source))
 		return;
 
 	_abortParse = false;
diff --git a/audio/midiparser.h b/audio/midiparser.h
index fa3b117eda0..5e2d1498d3d 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -320,6 +320,15 @@ protected:
 	bool   _doParse;       ///< True if the parser should be parsing; false if it should not be active
 	bool   _pause;		   ///< True if the parser has paused parsing
 
+	/**
+	 * The source number to use when sending MIDI messages to the driver.
+	 * When using multiple sources, use source 0 and higher. This must be
+	 * used when source volume or channel locking is used.
+	 * By default this is -1, which means the parser is the only source
+	 * of MIDI messages and multiple source functionality is disabled.
+	 */
+	int8   _source;
+
 protected:
 	static uint32 readVLQ(byte * &data);
 	virtual void resetTracking();
@@ -427,7 +436,7 @@ public:
 public:
 	typedef void (*XMidiCallbackProc)(byte eventData, void *refCon);
 
-	MidiParser();
+	MidiParser(int8 source = -1);
 	virtual ~MidiParser() { stopPlaying(); }
 
 	virtual bool loadMusic(byte *data, uint32 size) = 0;
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index 0e60e0cec09..5b2be3ce17e 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -396,20 +396,4 @@ uint32 MidiParser_SMF::compressToType0(byte *tracks[], byte numTracks, byte *buf
 	return output - buffer;
 }
 
-void MidiParser_SMF::sendToDriver(uint32 b) {
-	if (_source < 0) {
-		MidiParser::sendToDriver(b);
-	} else {
-		_driver->send(_source, b);
-	}
-}
-
-void MidiParser_SMF::sendMetaEventToDriver(byte type, byte *data, uint16 length) {
-	if (_source < 0) {
-		MidiParser::sendMetaEventToDriver(type, data, length);
-	} else {
-		_driver->metaEvent(_source, type, data, length);
-	}
-}
-
 MidiParser *MidiParser::createParser_SMF(int8 source) { return new MidiParser_SMF(source); }
diff --git a/audio/midiparser_smf.h b/audio/midiparser_smf.h
index e9e977a7ef2..d7eb03354ac 100644
--- a/audio/midiparser_smf.h
+++ b/audio/midiparser_smf.h
@@ -31,14 +31,6 @@ class MidiParser_SMF : public MidiParser {
 protected:
 	byte *_buffer;
 	bool _malformedPitchBends;
-	/**
-	 * The source number to use when sending MIDI messages to the driver.
-	 * When using multiple sources, use source 0 and higher. This must be
-	 * used when source volume or channel locking is used.
-	 * By default this is -1, which means the parser is the only source
-	 * of MIDI messages and multiple source functionality is disabled.
-	 */
-	int8 _source;
 
 protected:
 	/**
@@ -55,11 +47,8 @@ protected:
 	uint32 compressToType0(byte *tracks[], byte numTracks, byte *buffer, bool malformedPitchBends = false);
 	void parseNextEvent(EventInfo &info) override;
 
-	void sendToDriver(uint32 b) override;
-	void sendMetaEventToDriver(byte type, byte *data, uint16 length) override;
-
 public:
-	MidiParser_SMF(int8 source = -1) : _buffer(nullptr), _malformedPitchBends(false), _source(source) {}
+	MidiParser_SMF(int8 source = -1) : MidiParser(source), _buffer(nullptr), _malformedPitchBends(false) {}
 	~MidiParser_SMF();
 
 	bool loadMusic(byte *data, uint32 size) override;
diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp
index bd7cfc70aa0..97941906dc3 100644
--- a/audio/midiparser_xmidi.cpp
+++ b/audio/midiparser_xmidi.cpp
@@ -43,14 +43,6 @@ protected:
 	Loop _loop[4];
 	int _loopCount;
 
-	/**
-	 * The source number to use when sending MIDI messages to the driver.
-	 * When using multiple sources, use source 0 and higher. This must be
-	 * used when source volume or channel locking is used.
-	 * By default this is -1, which means the parser is the only source
-	 * of MIDI messages and multiple source functionality is disabled.
-	 */
-	int8 _source;
 	/**
 	 * The sequence branches defined for each track. These point to
 	 * positions in the MIDI data.
@@ -87,15 +79,12 @@ protected:
 		_loopCount = -1;
 	}
 	void onTrackStart(uint8 track) override;
-
-	void sendToDriver(uint32 b) override;
-	void sendMetaEventToDriver(byte type, byte *data, uint16 length) override;
 public:
 	MidiParser_XMIDI(XMidiCallbackProc proc, void *data, int8 source = -1) :
+			MidiParser(source),
 			_callbackProc(proc),
 			_callbackData(data),
 			_newTimbreListDriver(nullptr),
-			_source(source),
 			_loopCount(-1) {
 		memset(_loop, 0, sizeof(_loop));
 		memset(_trackBranches, 0, sizeof(_trackBranches));
@@ -572,22 +561,6 @@ void MidiParser_XMIDI::onTrackStart(uint8 track) {
 		_newTimbreListDriver->processXMIDITimbreChunk(_tracksTimbreList[track], _tracksTimbreListSize[track]);
 }
 
-void MidiParser_XMIDI::sendToDriver(uint32 b) {
-	if (_source < 0) {
-		MidiParser::sendToDriver(b);
-	} else {
-		_driver->send(_source, b);
-	}
-}
-
-void MidiParser_XMIDI::sendMetaEventToDriver(byte type, byte *data, uint16 length) {
-	if (_source < 0) {
-		MidiParser::sendMetaEventToDriver(type, data, length);
-	} else {
-		_driver->metaEvent(_source, type, data, length);
-	}
-}
-
 void MidiParser::defaultXMidiCallback(byte eventData, void *data) {
 	warning("MidiParser: defaultXMidiCallback(%d)", eventData);
 }


Commit: 13da6f9cb5e6963e2a72ebde6c93a224b8dcc9cb
    https://github.com/scummvm/scummvm/commit/13da6f9cb5e6963e2a72ebde6c93a224b8dcc9cb
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
AUDIO: Move null MidiDriver remove timer proc

Changed paths:
    audio/mididrv_ms.cpp
    audio/mididrv_ms.h


diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp
index f662f88260e..778e841fd1b 100644
--- a/audio/mididrv_ms.cpp
+++ b/audio/mididrv_ms.cpp
@@ -382,10 +382,6 @@ void MidiDriver_Multisource::onTimer() {
 		_timer_proc(_timer_param);
 }
 
-MidiDriver_NULL_Multisource::~MidiDriver_NULL_Multisource() {
-	g_system->getTimerManager()->removeTimerProc(timerCallback);
-}
-
 int MidiDriver_NULL_Multisource::open() {
 	// Setup a timer callback so "fades" will end after the specified time
 	// (effectively becoming timers, because there is no audio).
@@ -394,6 +390,10 @@ int MidiDriver_NULL_Multisource::open() {
 	return 0;
 }
 
+void MidiDriver_NULL_Multisource::close() {
+	g_system->getTimerManager()->removeTimerProc(timerCallback);
+}
+
 void MidiDriver_NULL_Multisource::timerCallback(void *data) {
 	MidiDriver_NULL_Multisource *driver = (MidiDriver_NULL_Multisource *)data;
 	driver->onTimer();
diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h
index 945dc899715..7aab2919b27 100644
--- a/audio/mididrv_ms.h
+++ b/audio/mididrv_ms.h
@@ -479,11 +479,9 @@ protected:
 
 class MidiDriver_NULL_Multisource : public MidiDriver_Multisource {
 public:
-	~MidiDriver_NULL_Multisource();
-
 	int open() override;
 	bool isOpen() const override { return true; }
-	void close() override { }
+	void close() override;
 	uint32 getBaseTempo() override { return 10000; }
 	MidiChannel *allocateChannel() override { return 0; }
 	MidiChannel *getPercussionChannel() override { return 0; }


Commit: a0ffa1e0053c66d546bacf5c6413500722be5ce3
    https://github.com/scummvm/scummvm/commit/a0ffa1e0053c66d546bacf5c6413500722be5ce3
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
AGOS: Fix PC-98 MIDI driver not using Native MT-32

The AGOS PC-98 MIDI driver did not check the native_mt32 flag when determining
the type of MIDI device. This would cause a hardware MT-32 or external
softsynth to be treated as a GM device.
This commit fixes this by adding the missing check.

Changed paths:
    engines/agos/drivers/accolade/pc98.cpp


diff --git a/engines/agos/drivers/accolade/pc98.cpp b/engines/agos/drivers/accolade/pc98.cpp
index 694762707cb..317c6cd83d0 100644
--- a/engines/agos/drivers/accolade/pc98.cpp
+++ b/engines/agos/drivers/accolade/pc98.cpp
@@ -23,6 +23,7 @@
 #include "audio/mididrv.h"
 #include "audio/mixer.h"
 #include "engines/engine.h"
+#include "common/config-manager.h"
 #include "common/func.h"
 
 namespace AGOS {
@@ -154,6 +155,7 @@ private:
 
 	MidiDriver *_drv;
 	DeviceHandle _dev;
+	MusicType _devType;
 
 	uint8 _volSysex[9];
 	uint8 _partAssignSysexGS[9];
@@ -530,7 +532,8 @@ const uint16 PC98FMDriver::_frequency[12] = {
 #define MIDIMSG32(s, p1, p2) (p2 << 16 | p1 << 8 | s)
 
 PC98MidiDriver::PC98MidiDriver(MidiDriver::DeviceHandle dev) : _dev(dev), _drv(nullptr) {
-	_instrumentsRemap = (getMusicType(dev) == MT_MT32) ? _instrumentsRemapMT32 : (getMusicType(dev) == MT_GM ? _instrumentsRemapGM : nullptr);
+	_devType = (getMusicType(dev) == MT_MT32 || ConfMan.getBool("native_mt32")) ? MT_MT32 : MT_GM;
+	_instrumentsRemap = (_devType == MT_MT32) ? _instrumentsRemapMT32 : (_devType == MT_GM ? _instrumentsRemapGM : nullptr);
 	int8 *tbl2 = new int8[128]();
 	_instrumentLevelAdjust = tbl2;
 	_partsRemap = _partsRemapMidi;
@@ -565,7 +568,7 @@ int PC98MidiDriver::open() {
 
 		property(kPropMusicVolume, Audio::Mixer::kMaxChannelVolume);
 
-		if (getMusicType(_dev) == MT_MT32) {
+		if (_devType == MT_MT32) {
 			_partAssignSysexGS[7] = 0x10;
 			for (uint8 i = 0x10; i < 0x20; ++i) {
 				_partAssignSysexGS[5] = i;
@@ -578,7 +581,7 @@ int PC98MidiDriver::open() {
 				sendSysexWithCheckSum(_partAssignSysexMT32);
 			}
 
-		} else if (getMusicType(_dev) == MT_GM) {
+		} else if (_devType == MT_GM) {
 			_partAssignSysexGS[5] = 0x10;
 			_partAssignSysexGS[7] = 9;
 			sendSysexWithCheckSum(_partAssignSysexGS);
@@ -647,7 +650,7 @@ void PC98MidiDriver::setVolume(int musicVolume, int sfxVolume) {
 	if (!_isOpen)
 		return;
 
-	if (getMusicType(_dev) == MT_MT32) {
+	if (_devType == MT_MT32) {
 		_volSysex[7] = musicVolume * 100 / Audio::Mixer::kMaxChannelVolume;
 		sendSysexWithCheckSum(_volSysex);
 	} else {


Commit: 97745050c2b24145ca95079078b231a216630b28
    https://github.com/scummvm/scummvm/commit/97745050c2b24145ca95079078b231a216630b28
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:43+02:00

Commit Message:
AGOS: Add option for Simon 1 DOS tempos to parsers

The DOS version of Simon 1 has different music tempos compared to the Windows
version. This commit adds the option to both the DOS and Windows MIDI parsers
to use either the DOS or Windows tempos.

Changed paths:
    engines/agos/midiparser_gmf.cpp
    engines/agos/midiparser_gmf.h
    engines/agos/midiparser_simonwin.cpp
    engines/agos/midiparser_simonwin.h


diff --git a/engines/agos/midiparser_gmf.cpp b/engines/agos/midiparser_gmf.cpp
index 4b683ce3b2c..0877ceb52dd 100644
--- a/engines/agos/midiparser_gmf.cpp
+++ b/engines/agos/midiparser_gmf.cpp
@@ -25,7 +25,7 @@
 
 namespace AGOS {
 
-MidiParser_GMF::MidiParser_GMF(int8 source) : MidiParser_SMF(source) {
+MidiParser_GMF::MidiParser_GMF(int8 source, bool useDosTempos) : MidiParser_SMF(source), _useDosTempos(useDosTempos) {
 	memset(_tracksEndPos, 0, 120);
 }
 
@@ -164,14 +164,19 @@ bool MidiParser_GMF::loadMusic(byte *data, uint32 size) {
 	_autoLoop = headerLoop;
 	_ppqn = 192;
 
-	// These translations from the GMF header tempo (2-8) to the SMF tempo have
-	// been determined by measuring the tempos generated by the DOS version of
-	// Simon 1 in DOSBox.
 	uint32 tempo;
-	if (headerTempo < 6) {
-		tempo = 330000 + ((headerTempo - 2) * 105000);
+	if (_useDosTempos) {
+		// These translations from the GMF header tempo (2-8) to the SMF tempo have
+		// been determined by measuring the tempos generated by the DOS version of
+		// Simon 1 in DOSBox.
+		if (headerTempo < 6) {
+			tempo = 330000 + ((headerTempo - 2) * 105000);
+		} else {
+			tempo = 750000 + ((headerTempo - 6) * 125000);
+		}
 	} else {
-		tempo = 750000 + ((headerTempo - 6) * 125000);
+		// These are the tempos as specified in the Windows version SMF data.
+		tempo = headerTempo * 125000;
 	}
 	setTempo(tempo);
 
diff --git a/engines/agos/midiparser_gmf.h b/engines/agos/midiparser_gmf.h
index a4d8a28729a..b0a4d20c042 100644
--- a/engines/agos/midiparser_gmf.h
+++ b/engines/agos/midiparser_gmf.h
@@ -36,7 +36,7 @@ namespace AGOS {
  */
 class MidiParser_GMF : public MidiParser_SMF {
 public:
-	MidiParser_GMF(int8 source = -1);
+	MidiParser_GMF(int8 source = -1, bool useDosTempos = false);
 
 	bool loadMusic(byte *data, uint32 size) override;
 
@@ -46,6 +46,10 @@ protected:
 	// The end position of each track, exclusive
 	// (i.e. 1 byte past the end of the data).
 	byte *_tracksEndPos[MAXIMUM_TRACKS];
+
+	// True if the music tempos from the DOS version should be used; false if
+	// the tempos from the Windows version should be used.
+	bool _useDosTempos;
 };
 
 } // End of namespace AGOS
diff --git a/engines/agos/midiparser_simonwin.cpp b/engines/agos/midiparser_simonwin.cpp
index c094959c23c..fc15267b7c0 100644
--- a/engines/agos/midiparser_simonwin.cpp
+++ b/engines/agos/midiparser_simonwin.cpp
@@ -25,8 +25,8 @@
 
 namespace AGOS {
 
-MidiParser_SimonWin::MidiParser_SimonWin(int8 source, bool correctSimon1Tempo) :
-	MidiParser_SMF(source), _trackData(), _correctSimon1Tempo(correctSimon1Tempo) { }
+MidiParser_SimonWin::MidiParser_SimonWin(int8 source, bool useDosTempos) :
+	MidiParser_SMF(source), _trackData(), _useDosTempos(useDosTempos) { }
 
 MidiParser_SimonWin::~MidiParser_SimonWin() {
 	// Call unloadMusic to make sure any _trackData contents are deallocated.
@@ -61,7 +61,7 @@ void MidiParser_SimonWin::parseNextEvent(EventInfo &info) {
 
 void MidiParser_SimonWin::setTempo(uint32 tempo) {
 	uint32 newTempo = tempo;
-	if (_correctSimon1Tempo && tempo < 750000) {
+	if (_useDosTempos && tempo < 750000) {
 		// WORKAROUND The tempos set in the SMF data of Simon 1 Windows are
 		// faster than the DOS version for the faster tempos. These are
 		// corrected here to match the DOS version. The correct tempos have
diff --git a/engines/agos/midiparser_simonwin.h b/engines/agos/midiparser_simonwin.h
index 2b3e95fc1fd..3adb9916418 100644
--- a/engines/agos/midiparser_simonwin.h
+++ b/engines/agos/midiparser_simonwin.h
@@ -42,7 +42,7 @@ protected:
 public:
 	int32 determineDataSize(Common::SeekableReadStream *stream) override;
 
-	MidiParser_SimonWin(int8 source = -1, bool correctSimon1Tempo = false);
+	MidiParser_SimonWin(int8 source = -1, bool useDosTempos = false);
 	~MidiParser_SimonWin();
 
 	void setTempo(uint32 tempo) override;
@@ -55,7 +55,7 @@ protected:
 
 	byte *_trackData[MAXIMUM_TRACKS];
 
-	bool _correctSimon1Tempo;
+	bool _useDosTempos;
 };
 
 } // End of namespace AGOS


Commit: 66bb075f1c107e1c42d1ac6601e20a9388d15d6a
    https://github.com/scummvm/scummvm/commit/66bb075f1c107e1c42d1ac6601e20a9388d15d6a
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:44+02:00

Commit Message:
AGOS: Add E2/WW AdLib and MT-32 SFX and enhancements

This adds support for the AdLib and MT-32 sound effects in Elvira 2 and
Waxworks. It adds an option to the UI to toggle between sampled and
synthesized SFX. It also adds the following enhancements:
- AdLib OPL3 mode for Elvira 2, Waxworks and Simon 1 floppy demo. This can be
selected using a new UI option.
- Mixed AdLib/MIDI mode for Elvira 2 and Waxworks.
- Implemented "monophonic chords", a feature of the original MIDI code which
would play only the highest note of a chord on AdLib. Most noticable in the
Waxworks music.
- Added UI option to select Simon 1 DOS music tempos.
- Rewrite of the AdLib and MT-32 drivers to remove duplication and make use of
features of the standard multisource drivers.
- Refactored MidiPlayer to standardize interface and remove code moved to the
drivers and parsers.

Changed paths:
  A engines/agos/sfxparser_accolade.cpp
  A engines/agos/sfxparser_accolade.h
  R engines/agos/drivers/simon1/adlib_win.cpp
  R engines/agos/drivers/simon1/adlib_win.h
    engines/agos/POTFILES
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/detection.cpp
    engines/agos/drivers/accolade/adlib.cpp
    engines/agos/drivers/accolade/adlib.h
    engines/agos/drivers/accolade/mididriver.h
    engines/agos/drivers/accolade/mt32.cpp
    engines/agos/drivers/accolade/mt32.h
    engines/agos/drivers/simon1/adlib.cpp
    engines/agos/drivers/simon1/adlib.h
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/midiparser_s1d.cpp
    engines/agos/module.mk
    engines/agos/res_snd.cpp
    engines/agos/script_e2.cpp
    engines/agos/script_s1.cpp
    engines/agos/vga.cpp
    engines/agos/vga_e2.cpp


diff --git a/engines/agos/POTFILES b/engines/agos/POTFILES
index 95027f8fe42..ac001c84591 100644
--- a/engines/agos/POTFILES
+++ b/engines/agos/POTFILES
@@ -2,3 +2,4 @@ engines/agos/saveload.cpp
 engines/agos/animation.cpp
 engines/agos/metaengine.cpp
 engines/agos/midi.cpp
+engines/agos/detection.cpp
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 91060bfcd60..9e5e6a3d917 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -525,6 +525,7 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 	// syncSoundSettings.
 	_musicVolume = 192;
 	_effectsVolume = 192;
+	_useDigitalSfx = true;
 
 	_saveLoadType = 0;
 	_saveLoadSlot = 0;
@@ -611,12 +612,29 @@ Common::Error AGOSEngine::init() {
 		((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) ||
 		(getPlatform() == Common::kPlatformDOS || getPlatform() == Common::kPlatformPC98)) {
 
-		int ret = _midi->open(getGameType(), getPlatform(), (getFeatures() & GF_DEMO));
+		int ret = _midi->open();
 		if (ret)
 			warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));
 
 		_midiEnabled = true;
 	}
+	// Digital SFX are used if MIDI SFX are not available or if the "prefer
+	// digital SFX" setting is set to true or is not present at all.
+	// Two exceptions to this are:
+	// - Elvira 2 DOS needs an optional file to enable digital SFX. If it is
+	//   not present, MIDI SFX are used.
+	// - Simon 1 DOS floppy has only MIDI SFX.
+	// Note that MIDI SFX can be safely used if the MidiPlayer failed to
+	// initialize; they just will not play.
+	_useDigitalSfx = !_midiEnabled || !_midi->hasMidiSfx() || !ConfMan.hasKey("prefer_digitalsfx") || ConfMan.getBool("prefer_digitalsfx");
+	if ((getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformDOS && !SearchMan.hasFile("013.VGA")) ||
+			(getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformDOS && !(getFeatures() & GF_TALKIE))) {
+		_useDigitalSfx = false;
+	}
+	if (!_useDigitalSfx && (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) && getPlatform() == Common::kPlatformDOS) {
+		// Load the MIDI SFX data file for Elvira 2 and Waxworks DOS.
+		loadMidiSfx();
+	}
 
 	// allocate buffers
 	_backGroundBuf = new Graphics::Surface();
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 94beead2735..0a2e16f6ccc 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -253,8 +253,6 @@ public:
 	const char *getFileName(int type) const;
 
 protected:
-	void playSting(uint16 a);
-
 	const byte *_vcPtr;								/* video code ptr */
 	uint16 _vcGetOutOfCode;
 
@@ -599,6 +597,7 @@ protected:
 	// The current SFX and ambient volume, or the last used volume if SFX
 	// and/or ambient sounds are currently muted.
 	uint16 _effectsVolume;
+	bool _useDigitalSfx;
 
 	uint8 _saveGameNameLen;
 	uint16 _saveLoadRowCurPos;
@@ -653,8 +652,12 @@ protected:
 	void decompressPN(Common::Stack<uint32> &dataList, uint8 *&dataOut, int &dataOutSize);
 	void loadOffsets(const char *filename, int number, uint32 &file, uint32 &offset, uint32 &compressedSize, uint32 &size);
 	void loadSound(uint16 sound, int16 pan, int16 vol, uint16 type);
+	void playSfx(uint16 sound, uint16 freq, uint16 flags, bool canUseMidiSfx);
 	void loadSound(uint16 sound, uint16 freq, uint16 flags);
+	void loadMidiSfx();
+	virtual void playMidiSfx(uint16 sound);
 	void loadVoice(uint speechId);
+	void stopAllSfx();
 
 	void loadSoundFile(const char *filename);
 
@@ -1904,6 +1907,7 @@ protected:
 	int userGameGetKey(bool *b, uint maxChar) override;
 
 	void playMusic(uint16 music, uint16 track) override;
+	void playMidiSfx(uint16 sound) override;
 
 	void vcStopAnimation(uint16 zone, uint16 sprite) override;
 
diff --git a/engines/agos/detection.cpp b/engines/agos/detection.cpp
index ce14c9410fb..b279d7e8593 100644
--- a/engines/agos/detection.cpp
+++ b/engines/agos/detection.cpp
@@ -24,9 +24,11 @@
 #include "engines/advancedDetector.h"
 #include "engines/obsolete.h"
 
+#include "common/config-manager.h"
 #include "common/system.h"
 #include "common/textconsole.h"
 #include "common/installshield_cab.h"
+#include "common/translation.h"
 
 #include "agos/detection.h"
 #include "agos/intern_detection.h"
@@ -71,6 +73,31 @@ static const char *const directoryGlobs[] = {
 
 using namespace AGOS;
 
+static const ExtraGuiOption opl3Mode = {
+	_s("AdLib OPL3 mode"),
+	_s("When AdLib is selected, OPL3 features will be used. Depending on the \
+	game, this will prevent cut-off notes, add extra notes or instruments \
+	and/or add stereo."),
+	"opl3_mode",
+	false
+};
+
+static const ExtraGuiOption useDosTempos = {
+	_s("Use DOS version music tempos"),
+	_s("Selecting this option will play the music using the tempos used by \
+	the DOS version of the game. Otherwise, the faster tempos of the Windows \
+	version will be used."),
+	"dos_music_tempos",
+	false
+};
+
+static const ExtraGuiOption preferDigitalSfx = {
+	_s("Prefer digital sound effects"),
+	_s("Prefer digital sound effects instead of synthesized ones"),
+	"prefer_digitalsfx",
+	true
+};
+
 class AgosMetaEngineDetection : public AdvancedMetaEngineDetection {
 public:
 	AgosMetaEngineDetection() : AdvancedMetaEngineDetection(AGOS::gameDescriptions, sizeof(AGOS::AGOSGameDescription), agosGames) {
@@ -98,6 +125,35 @@ public:
 	const DebugChannelDef *getDebugChannels() const override {
 		return debugFlagList;
 	}
+
+	const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const override {
+		const Common::String gameid = ConfMan.get("gameid", target);
+		const Common::String platform = ConfMan.get("platform", target);
+		const Common::String extra = ConfMan.get("extra", target);
+
+		ExtraGuiOptions options;
+		if (target.empty() || ((gameid == "elvira2" || gameid == "waxworks" || gameid == "simon1") && platform == "pc")) {
+			// DOS versions of Elvira 2, Waxworks and Simon 1 can optionally
+			// make use of AdLib OPL3 features.
+			options.push_back(opl3Mode);
+		}
+		if (target.empty() || (gameid == "simon1" && ((platform == "pc" && extra != "Floppy Demo") || platform == "windows" ||
+				(platform == "acorn" && extra.contains("CD"))))) {
+			// Simon 1 DOS (except the floppy demo), Windows and Acorn CD can
+			// choose between the DOS or Windows music tempos.
+			ExtraGuiOption dosTemposOption = useDosTempos;
+			// DOS tempos are default for the DOS versions; other versions use
+			// the Windows tempos by default.
+			dosTemposOption.defaultState = platform == "pc";
+			options.push_back(dosTemposOption);
+		}
+		if (target.empty() || ((gameid == "elvira2" || gameid == "waxworks") && platform == "pc")) {
+			// DOS versions of Elvira 2 and Waxworks can use either Adlib or
+			// digital SFX.
+			options.push_back(preferDigitalSfx);
+		}
+		return options;
+	}
 };
 
 REGISTER_PLUGIN_STATIC(AGOS_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, AgosMetaEngineDetection);
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
index f14e544effc..7b16af317c5 100644
--- a/engines/agos/drivers/accolade/adlib.cpp
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -19,44 +19,19 @@
  *
  */
 
-#include "agos/drivers/accolade/mididriver.h"
 #include "agos/drivers/accolade/adlib.h"
 
-#include "audio/fmopl.h"
-#include "audio/mididrv.h"
+#include "agos/drivers/accolade/mididriver.h"
 
 namespace AGOS {
 
-#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6
-#define AGOS_ADLIB_VOICES_PERCUSSION_START 6
-#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5
-#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9
-
-// 5 instruments on top of the regular MIDI ones
-// used by the MUSIC.DRV variant for percussion instruments
-#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5
-
-const byte operator1Register[AGOS_ADLIB_VOICES_COUNT] = {
-	0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11
-};
-
-const byte operator2Register[AGOS_ADLIB_VOICES_COUNT] = {
-	0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF
-};
-
-// percussion:
-//  voice  6 - base drum - also uses operator 13h
-//  voice  7 - snare drum
-//  voice  8 - tom tom
-//  voice  9 - cymbal
-//  voice 10 - hi hat
-const byte percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = {
-	0x10, 0x08, 0x04, 0x02, 0x01
-};
-
 // hardcoded, dumped from Accolade music system
 // same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently
-const byte percussionKeyNoteChannelTable[] = {
+// Numbers 6-A correspond to MIDI channels in the original driver, but here they
+// are directly mapped to rhythm instrument types (bass drum, snare drum,
+// tom tom, cymbal and hi-hat respectively). F means there is no instrument
+// defined for the rhythm note.
+const byte MidiDriver_Accolade_AdLib::RHYTHM_NOTE_INSTRUMENT_TYPES[] = {
 	0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08,
 	0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F,
 	0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
@@ -64,26 +39,25 @@ const byte percussionKeyNoteChannelTable[] = {
 };
 
 // hardcoded, dumped from Accolade music system (INSTR.DAT variant)
-const uint16 frequencyLookUpTable[12] = {
-	0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF,
-	0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B
+const uint16 MidiDriver_Accolade_AdLib::OPL_NOTE_FREQUENCIES_INSTR_DAT[] = {
+	0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399,
+	0x03CF, 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B
 };
 
 // hardcoded, dumped from Accolade music system (MUSIC.DRV variant)
-const uint16 frequencyLookUpTableMusicDrv[12] = {
-	0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB,
-	0x0306, 0x0334, 0x0365, 0x0399, 0x03CF
+const uint16 MidiDriver_Accolade_AdLib::OPL_NOTE_FREQUENCIES_MUSIC_DRV[] = {
+	0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2,
+	0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF
 };
 
-//
 // Accolade adlib music driver
 //
 // Remarks:
 //
 // There are at least 2 variants of this sound system.
-// One for the games Elvira 1 + Elvira 2
+// One for the game Elvira 1
 // It seems it was also used for the game "Altered Destiny"
-// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo
+// Another one for the games Elvira 2 + Waxworks + Simon, the Sorcerer 1 Demo
 //
 // First one uses the file INSTR.DAT for instrument data, channel mapping etc.
 // Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc.
@@ -92,697 +66,437 @@ const uint16 frequencyLookUpTableMusicDrv[12] = {
 // feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too.
 //
 // I have currently not implemented dynamic channel allocation.
-
-MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib()
-		: _masterVolume(143), _opl(nullptr),
-		  _adlibTimerProc(nullptr), _adlibTimerParam(nullptr), _isOpen(false) {
-	memset(_channelMapping, 0, sizeof(_channelMapping));
-	memset(_instrumentMapping, 0, sizeof(_instrumentMapping));
-	memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust));
-	memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping));
-
-	_instrumentTable = nullptr;
-	_instrumentCount = 0;
-	_musicDrvMode = false;
-	_percussionReg = 0x20;
+MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion) : MidiDriver_ADLIB_Multisource(oplType) {
+	_instrumentBank = nullptr;
+	_rhythmBank = nullptr;
+	_newVersion = newVersion;
+	_oplNoteFrequencies = _newVersion ? OPL_NOTE_FREQUENCIES_MUSIC_DRV : OPL_NOTE_FREQUENCIES_INSTR_DAT;
+
+	Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
+	Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
+	Common::fill(_volumeAdjustments, _volumeAdjustments + ARRAYSIZE(_volumeAdjustments), 0);
+	Common::fill(_sfxNoteFractions, _sfxNoteFractions + ARRAYSIZE(_sfxNoteFractions), 0);
+	memset(_sfxInstruments, 0, sizeof(_sfxInstruments));
 }
 
 MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() {
-	if (_instrumentTable) {
-		delete[] _instrumentTable;
-		_instrumentCount = 0;
-	}
+	if (_instrumentBank)
+		delete[] _instrumentBank;
+	if (_rhythmBank)
+		delete[] _rhythmBank;
 }
 
 int MidiDriver_Accolade_AdLib::open() {
-//	debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");
+	_modulationDepth = MODULATION_DEPTH_LOW;
+	_vibratoDepth = VIBRATO_DEPTH_LOW;
 
-	_opl = OPL::Config::create(OPL::Config::kOpl2);
+	int result = MidiDriver_ADLIB_Multisource::open();
 
-	if (!_opl)
-		return -1;
+	if (result == 0) {
+		// Rhythm mode is always on.
+		setRhythmMode(true);
 
-	_opl->init();
+		// The original driver writes out default instruments to all channels
+		// here. This implementation writes instruments before note on, so this
+		// is not necessary.
 
-	_isOpen = true;
-
-	_opl->start(new Common::Functor0Mem<void, MidiDriver_Accolade_AdLib>(this, &MidiDriver_Accolade_AdLib::onTimer));
-
-	resetAdLib();
-
-	// Finally set up default instruments
-	for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) {
-		if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) {
-			// Regular FM voices with instrument 0
-			programChangeSetInstrument(FMvoiceNr, 0, 0);
-		} else {
-			byte percussionInstrument;
-			if (!_musicDrvMode) {
-				// INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5
-				percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1;
-			} else {
-				// MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84
-				percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80;
-			}
-			programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument);
-		}
+		// driver initialization does this here:
+		// INSTR.DAT
+		// noteOn(9, 0x29, 0);
+		// noteOff(9, 0x26, false);
+		// MUSIC.DRV
+		// noteOn(9, 0x26, 0);
+		// noteOff(9, 0x26, false);
 	}
 
-	// driver initialization does this here:
-	// INSTR.DAT
-	// noteOn(9, 0x29, 0);
-	// noteOff(9, 0x26, false);
-	// MUSIC.DRV
-	// noteOn(9, 0x26, 0);
-	// noteOff(9, 0x26, false);
-
-	return 0;
+	return result;
 }
 
-void MidiDriver_Accolade_AdLib::close() {
-	delete _opl;
-	_isOpen = false;
-}
-
-void MidiDriver_Accolade_AdLib::setVolume(byte volume) {
-	_masterVolume = volume;
-	for (int i = 0; i < AGOS_ADLIB_VOICES_COUNT; i++) {
-		// Re-set registers
-		noteOnSetVolume(i, 1, _channels[i].velocity);
-		if (i <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
-			// Set second operator for FM voices + first percussion
-			noteOnSetVolume(i, 2, _channels[i].velocity);
-		}
+void MidiDriver_Accolade_AdLib::send(int8 source, uint32 b) {
+	// Remap the MIDI channel according to the channel map.
+	// (Seems to be 1 on 1 for AdLib...)
+	byte channel = b & 0xF;
+	channel = _channelRemapping[channel];
+	b &= 0xFFFFFFF0;
+	b |= channel;
+	byte command = b & 0xF0;
+
+	if (_oplType != OPL::Config::kOpl3 && _sources[source].type != SOURCE_TYPE_SFX && command != MIDI_COMMAND_PROGRAM_CHANGE) {
+		// Filter out events for channels used by SFX.
+		// Program change events are always accepted; they just set the program
+		// for the music source and do not affect the SFX notes.
+		if (_activeNotes[channel].channelAllocated)
+			return;
 	}
-}
-
-void MidiDriver_Accolade_AdLib::onTimer() {
-	if (_adlibTimerProc)
-		(*_adlibTimerProc)(_adlibTimerParam);
-}
-
-void MidiDriver_Accolade_AdLib::resetAdLib() {
-	// The original driver sent 0x00 to register 0x00 up to 0xF5
-	setRegister(0xBD, 0x00); // Disable rhythm
 
-	// reset FM voice instrument data
-	resetAdLibOperatorRegisters(0x20, 0);
-	resetAdLibOperatorRegisters(0x60, 0);
-	resetAdLibOperatorRegisters(0x80, 0);
-	resetAdLibFMVoiceChannelRegisters(0xA0, 0);
-	resetAdLibFMVoiceChannelRegisters(0xB0, 0);
-	resetAdLibFMVoiceChannelRegisters(0xC0, 0);
-	resetAdLibOperatorRegisters(0xE0, 0);
-	resetAdLibOperatorRegisters(0x40, 0x3F); // original driver sent 0x00
-
-	setRegister(0x01, 0x20); // enable waveform control on both operators
-	setRegister(0x04, 0x60); // Timer control
-
-	setRegister(0x08, 0);    // select FM music mode
-	setRegister(0xBD, 0x20); // Enable rhythm
-
-	// reset our percussion register
-	_percussionReg = 0x20;
+	MidiDriver_ADLIB_Multisource::send(source, b);
 }
 
-void MidiDriver_Accolade_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
-	byte operatorIndex;
-
-	for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) {
-		switch (operatorIndex) {
-		case 0x06:
-		case 0x07:
-		case 0x0E:
-		case 0x0F:
-			break;
-		default:
-			setRegister(baseRegister + operatorIndex, value);
+void MidiDriver_Accolade_AdLib::deinitSource(uint8 source) {
+	if (_sources[source].type == SOURCE_TYPE_SFX) {
+		// When a sound effect ends, the original driver will immediately
+		// rewrite the music instrument for the channel used by this sound
+		// effect. This has the effect of stopping the release phase of the
+		// sound effect. This is reproduced here to make sure the sound effects
+		// sound the same.
+		byte channel = _channelAllocations[source][0];
+		// OPL3 mode has no fixed instrument assignment to the OPL channel, so
+		// just use instrument 0.
+		byte program = 0;
+		if (_oplType != OPL::Config::kOpl3) {
+			// For OPL2, get the current music instrument for this OPL channel.
+			program = _controlData[0][channel].program;
+			if (_instrumentRemapping)
+				// Apply instrument remapping (if specified) to instrument channels.
+				program = _instrumentRemapping[program];
 		}
-	}
-}
-
-void MidiDriver_Accolade_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
-	byte FMvoiceChannel;
-
-	for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
-		setRegister(baseRegister + FMvoiceChannel, value);
-	}
-}
 
-// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
-void MidiDriver_Accolade_AdLib::send(uint32 b) {
-	byte command = b & 0xf0;
-	byte channel = b & 0xf;
-	byte op1 = (b >> 8) & 0xff;
-	byte op2 = (b >> 16) & 0xff;
+		InstrumentInfo instrument { };
+		instrument.instrumentId = program;
+		instrument.instrumentDef = &_instrumentBank[program];
+		instrument.oplNote = 0;
 
-	byte mappedChannel    = _channelMapping[channel];
-	byte mappedInstrument = 0;
+		writeInstrument(channel, instrument);
 
-	// Ignore everything that is outside of our channel range
-	if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT)
-		return;
-
-	switch (command) {
-	case 0x80:
-		noteOff(mappedChannel, op1, false);
-		break;
-	case 0x90:
-		// Convert noteOn with velocity 0 to a noteOff
-		if (op2 == 0)
-			return noteOff(mappedChannel, op1, false);
-
-		noteOn(mappedChannel, op1, op2);
-		break;
-	case 0xb0: // Control change
-		// Doesn't seem to be implemented
-		break;
-	case 0xc0: // Program Change
-		mappedInstrument = _instrumentMapping[op1];
-		programChange(mappedChannel, mappedInstrument, op1);
-		break;
-	case 0xa0: // Polyphonic key pressure (aftertouch)
-	case 0xd0: // Channel pressure (aftertouch)
-		// Aftertouch doesn't seem to be implemented
-		break;
-	case 0xe0:
-		// No pitch bend change
-		break;
-	case 0xf0: // SysEx
-		warning("ADLIB: SysEx: %x", b);
-		break;
-	default:
-		warning("ADLIB: Unknown event %02x", command);
+		// Clear other SFX data.
+		_sfxNoteFractions[source - 1] = 0;
 	}
-}
 
-void MidiDriver_Accolade_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
-	_adlibTimerProc = timerProc;
-	_adlibTimerParam = timerParam;
+	MidiDriver_ADLIB_Multisource::deinitSource(source);
 }
 
-void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) {
-	byte adjustedNote     = note;
-	byte regValueA0h      = 0;
-	byte regValueB0h      = 0;
-
-	// adjust velocity
-	byte adjustedVelocity = velocity + _channels[FMvoiceChannel].volumeAdjust;
+uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+	Common::StackLock lock(_allocationMutex);
 
-	if (!_musicDrvMode) {
-		// INSTR.DAT
-		// force note-off
-		noteOff(FMvoiceChannel, note, true);
-
-	} else {
-		// MUSIC.DRV
-		if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
-			// force note-off, but only for actual FM voice channels
-			noteOff(FMvoiceChannel, note, true);
-		}
-	}
+	if (_sources[source].type == SOURCE_TYPE_SFX) {
+		if (_channelAllocations[source][0] == 0xFF) {
+			// Allocate a channel for this SFX source.
+			byte allocatedChannel;
+			if (_oplType != OPL::Config::kOpl3) {
+				// For OPL2, use channels 5 and 4.
+				allocatedChannel = 6 - source;
+			} else {
+				// For OPL3, use the dynamic allocation algorithm.
+				allocatedChannel = MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentId);
+			}
 
-	if (FMvoiceChannel != 9) {
-		// regular FM voice
+			_activeNotesMutex.lock();
 
-		if (!_musicDrvMode) {
-			// INSTR.DAT: adjust key note
-			while (adjustedNote < 24)
-				adjustedNote += 12;
-			adjustedNote -= 12;
-		}
+			ActiveNote *activeNote = &_activeNotes[allocatedChannel];
+			if (activeNote->noteActive) {
+				// Turn off the note currently playing on this OPL channel.
+				writeKeyOff(allocatedChannel, activeNote->instrumentDef->rhythmType);
+			}
+			_channelAllocations[source][0] = allocatedChannel;
+			activeNote->channelAllocated = true;
+			activeNote->source = source;
+			activeNote->channel = channel;
+			activeNote->oplNote = 0;
 
-	} else {
-		// percussion channel
-		// MUSIC.DRV variant didn't do this adjustment, it directly used a pointer
-		adjustedNote -= 36;
-		if (adjustedNote > 40) { // Security check
-			warning("ADLIB: bad percussion channel note");
-			return;
+			_activeNotesMutex.unlock();
 		}
 
-		byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote];
-		if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT)
-			return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug
-
-		// Map the keynote accordingly
-		adjustedNote = _percussionKeyNoteMapping[adjustedNote];
-		// Now overwrite the FM voice channel
-		FMvoiceChannel = percussionChannel;
+		// Return the allocated channel.
+		return _channelAllocations[source][0];
 	}
 
-	if (!_musicDrvMode) {
-		// INSTR.DAT
-
-		// Save this key note
-		_channels[FMvoiceChannel].currentNote = adjustedNote;
-
-		adjustedVelocity += 24;
-		if (adjustedVelocity > 120)
-			adjustedVelocity = 120;
-		adjustedVelocity = adjustedVelocity >> 1; // divide by 2
+	// Channel allocation for music sources.
+	if (_oplType != OPL::Config::kOpl3) {
+		// For OPL2, discard events for channels 6 and 7 and channels allocated
+		// for SFX.
+		if (channel >= 6 || _activeNotes[channel].channelAllocated)
+			return 0xFF;
 
+		// Then just map MIDI channels 0-5 to OPL channels 0-5.
+		return channel;
 	} else {
-		// MUSIC.DRV
-		adjustedVelocity = adjustedVelocity >> 1; // divide by 2
+		// For OPL3, use the dynamic allocation algorithm.
+		return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentId);
 	}
+}
 
-	// Save velocity in the case volume will need to be changed
-	_channels[FMvoiceChannel].velocity = adjustedVelocity;
-	// Set volume of voice channel
-	noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity);
-	if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
-		// Set second operator for FM voices + first percussion
-		noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity);
-	}
-
-	if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) {
-		// Percussion
-		byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START;
-
-		// Enable bit of the requested percussion type
-		assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT);
-		_percussionReg |= percussionBits[percussionIdx];
-		setRegister(0xBD, _percussionReg);
-	}
+byte MidiDriver_Accolade_AdLib::getNumberOfSfxSources() {
+	// With OPL3 more channels are available for SFX.
+	return _oplType == OPL::Config::kOpl3 ? 4 : 2;
+}
 
-	if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) {
-		// FM voice, Base Drum, Snare Drum + Tom Tom
-		byte adlibNote = adjustedNote;
-		byte adlibOctave = 0;
-		byte adlibFrequencyIdx = 0;
-		uint16 adlibFrequency = 0;
-
-		if (!_musicDrvMode) {
-			// INSTR.DAT
-			if (adlibNote >= 0x60)
-				adlibNote = 0x5F;
-
-			adlibOctave = (adlibNote / 12) - 1;
-			adlibFrequencyIdx = adlibNote % 12;
-			adlibFrequency = frequencyLookUpTable[adlibFrequencyIdx];
-
-			if (adlibFrequency & 0x8000)
-				adlibOctave++;
-			if (adlibOctave & 0x80) {
-				adlibOctave++;
-				adlibFrequency = adlibFrequency >> 1;
-			}
+void MidiDriver_Accolade_AdLib::loadSfxInstrument(uint8 source, byte *instrumentData) {
+	if (source > (_oplType == OPL::Config::kOpl3 ? 4 : 2))
+		return;
 
-		} else {
-			// MUSIC.DRV variant
-			if (adlibNote >= 19)
-				adlibNote -= 19;
+	// Copy instrument data into SFX instruments bank.
+	loadInstrumentData(_sfxInstruments[source - 1], instrumentData, RHYTHM_TYPE_UNDEFINED, 0, _newVersion);
 
-			adlibOctave = (adlibNote / 12);
-			adlibFrequencyIdx = adlibNote % 12;
-			// additional code, that will lookup octave and do a multiplication with it
-			// noteOn however calls the frequency calculation in a way that it multiplies with 0
-			adlibFrequency = frequencyLookUpTableMusicDrv[adlibFrequencyIdx];
-		}
+	_activeNotesMutex.lock();
 
-		regValueA0h = adlibFrequency & 0xFF;
-		regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2);
-		if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
-			// set Key-On flag for regular FM voices, but not for percussion
-			regValueB0h |= 0x20;
-		}
+	// Allocate a channel
+	programChange(0, 0, source);
+	InstrumentInfo instrument = determineInstrument(0, source, 0);
+	uint8 oplChannel = allocateOplChannel(0, source, instrument.instrumentId);
 
-		setRegister(0xA0 + FMvoiceChannel, regValueA0h);
-		setRegister(0xB0 + FMvoiceChannel, regValueB0h);
-		_channels[FMvoiceChannel].currentA0hReg = regValueA0h;
-		_channels[FMvoiceChannel].currentB0hReg = regValueB0h;
+	// Update the active note data.
+	ActiveNote *activeNote = &_activeNotes[oplChannel];
+	activeNote->instrumentId = instrument.instrumentId;
+	activeNote->instrumentDef = instrument.instrumentDef;
 
-		if (_musicDrvMode) {
-			// MUSIC.DRV
-			if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) {
-				_channels[FMvoiceChannel].currentNote = adjustedNote;
-			}
-		}
-	}
+	_activeNotesMutex.unlock();
 }
 
-// 100% the same for INSTR.DAT and MUSIC.DRV variants
-// except for a bug, that was introduced for MUSIC.DRV
-void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte velocity) {
-	byte operatorReg = 0;
-	byte regValue40h = 0;
-	const InstrumentEntry *curInstrument = nullptr;
-
-	// Adjust velocity with the master volume
-	uint16 adjustedVelocity = CLIP<uint16>((velocity * _masterVolume) / 255, 0, 0x3F);
-
-	regValue40h = (63 - adjustedVelocity) & 0x3F;
-
-	if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) {
-		// first operator of FM voice channels or first percussion channel
-		curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr;
-		if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound
-			// only one does, instrument wants fixed volume
-			if (operatorNr == 1) {
-				regValue40h = curInstrument->reg40op1;
-			} else {
-				regValue40h = curInstrument->reg40op2;
-			}
+void MidiDriver_Accolade_AdLib::setSfxNoteFraction(uint8 source, uint16 noteFraction) {
+	// Note is in the upper byte.
+	_activeNotes[_channelAllocations[source][0]].oplNote = noteFraction >> 8;
+	// Note fraction is in the lower byte.
+	_sfxNoteFractions[source - 1] = noteFraction & 0xFF;
+}
 
-			// not sure, if we are supposed to implement these bugs, or not
-#if 0
-			if (!_musicDrvMode) {
-				// Table is 16 bytes instead of 18 bytes
-				if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) {
-					regValue40h = 0;
-					warning("volume set bug (original)");
-				}
-			}
-			if (_musicDrvMode) {
-				// MUSIC.DRV variant has a bug, which will overwrite these registers
-				// for all operators above 11 / 0Bh, which means percussion will always
-				// get a value of 0 (the table holding those bytes was 12 bytes instead of 18
-				if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) {
-					regValue40h = 0;
-					warning("volume set bug (original)");
-				}
-			}
-#endif
-		}
-	}
+void MidiDriver_Accolade_AdLib::updateSfxNote(uint8 source) {
+	writeFrequency(_channelAllocations[source][0]);
+}
 
-	if (operatorNr == 1) {
-		operatorReg = operator1Register[FMvoiceChannel];
+MidiDriver_Accolade_AdLib::InstrumentInfo MidiDriver_Accolade_AdLib::determineInstrument(uint8 channel, uint8 source, uint8 note) {
+	if (_sources[source].type == SOURCE_TYPE_SFX) {
+		// For SFX sources, return an instrument from the SFX bank.
+		InstrumentInfo instrument { };
+		instrument.instrumentId = 0xFFFF - source;
+		instrument.instrumentDef = &_sfxInstruments[source - 1];
+		instrument.oplNote = note;
+		return instrument;
 	} else {
-		operatorReg = operator2Register[FMvoiceChannel];
+		return MidiDriver_ADLIB_Multisource::determineInstrument(channel, source, note);
 	}
-	assert(operatorReg != 0xFF); // Security check
-	setRegister(0x40 + operatorReg, regValue40h);
 }
 
-void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) {
-	byte adjustedNote = note;
-	byte regValueB0h = 0;
-
-	if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
-		// regular FM voice
-
-		if (!_musicDrvMode) {
-			// INSTR.DAT: adjust key note
-			while (adjustedNote < 24)
-				adjustedNote += 12;
-			adjustedNote -= 12;
-		}
-
-		if (!dontCheckNote) {
-			// check, if current note is also the current actually playing channel note
-			if (_channels[FMvoiceChannel].currentNote != adjustedNote)
-				return; // not the same -> ignore this note off command
+uint16 MidiDriver_Accolade_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
+	if (!_newVersion) {
+		// Elvira 1 version.
+		if (channel != MIDI_RHYTHM_CHANNEL) {
+			// All melodic notes are lowered by 1 octave, except the lowest notes.
+			while (note < 0x18)
+				note += 0xC;
+			note -= 0xC;
 		}
-
-		regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit
-		setRegister(0xB0 + FMvoiceChannel, regValueB0h);
-
+		// Highest 32 notes are clipped.
+		if (note > 0x5F)
+			note = 0x5F;
 	} else {
-		// percussion
-		adjustedNote -= 36;
-		if (adjustedNote > 40) { // Security check
-			warning("ADLIB: bad percussion channel note");
-			return;
-		}
-
-		byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote];
-		if (percussionChannel > AGOS_ADLIB_VOICES_COUNT)
-			return;
-
-		byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START;
-
-		// Disable bit of the requested percussion type
-		assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT);
-		_percussionReg &= ~percussionBits[percussionIdx];
-		setRegister(0xBD, _percussionReg);
+		// Elvira 2 / Waxworks version.
+		// Notes 19 and higher are transposed down 19 semitones.
+		// Note that this is about 1.5 octave, which implies that notes 0-18 are
+		// not played accurately by this driver.
+		if (note >= 0x13)
+			note -= 0x13;
 	}
-}
 
-void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
-	if (mappedInstrumentNr >= _instrumentCount) {
-		warning("ADLIB: tried to set non-existent instrument");
-		return; // out of range
-	}
-
-	// setup instrument
-	//warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);
-
-	if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
-		// Regular FM voice
-		programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr);
-
-	} else {
-		// Percussion
-		// set default instrument (again)
-		byte percussionInstrumentNr = 0;
-		const InstrumentEntry *instrumentPtr;
-
-		if (!_musicDrvMode) {
-			// INSTR.DAT: percussion default instruments start at instrument 1
-			percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1;
-		} else {
-			// MUSIC.DRV: percussion default instruments start at instrument 0x80
-			percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80;
+	// Determine octave and note within octave, and look up the matching OPL
+	// frequency.
+	int8 block = note / 12;
+	if (!_newVersion)
+		// Elvira 1 version lowers the octave by 1 (note that melodic notes
+		// were lowered 1 octave earlier).
+		block--;
+	uint8 octaveNote = note % 12;
+
+	// Look up the note frequency.
+	uint16 baseFrequency = _oplNoteFrequencies[octaveNote];
+	uint16 frequency;
+	if (!_newVersion) {
+		// Elvira 1 version has a negative frequency lookup value for notes
+		// which are in a higher octave than the others.
+		if (baseFrequency & 0x8000)
+			block++;
+		// Clear the high bits of the negative lookup values.
+		frequency = baseFrequency & 0x3FF;
+		if (block < 0) {
+			// If octave is now negative, halve the frequency and increase
+			// octave.
+			frequency >>= 1;
+			block++;
 		}
-		if (percussionInstrumentNr >= _instrumentCount) {
-			warning("ADLIB: tried to set non-existent instrument");
-			return;
+	} else {
+		// Elvira 2 / Waxworks version adds the note fraction for SFX.
+		uint16 fractionFrequency = 0;
+		if (_sources[source].type == SOURCE_TYPE_SFX) {
+			// Because the frequency differences between notes are not constant
+			// the fraction is multiplied by a factor depending on the note.
+			fractionFrequency = (((octaveNote + 1) / 6) + 2) * (_sfxNoteFractions[source - 1] >> 4);
 		}
-		instrumentPtr = &_instrumentTable[percussionInstrumentNr];
-		_channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
-		_channels[FMvoiceChannel].volumeAdjust         = _instrumentVolumeAdjust[percussionInstrumentNr];
+		frequency = baseFrequency + fractionFrequency;
 	}
-}
+	// Note that when processing sound effects, the note can be higher than the
+	// MIDI maximum value of 0x7F. The original interpreter depends on this for
+	// correct playback of the sound effect. However, this can cause block to
+	// overflow the 3 bit range available to it in the OPL registers.
+	block &= 0x7;
 
-void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) {
-	const InstrumentEntry *instrumentPtr;
-	byte op1Reg = 0;
-	byte op2Reg = 0;
-
-	if (mappedInstrumentNr >= _instrumentCount) {
-		warning("ADLIB: tried to set non-existent instrument");
-		return; // out of range
-	}
+	return block << 10 | frequency;
+}
 
-	// setup instrument
-	instrumentPtr = &_instrumentTable[mappedInstrumentNr];
-	//warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr);
-
-	op1Reg = operator1Register[FMvoiceChannel];
-	op2Reg = operator2Register[FMvoiceChannel];
-
-	setRegister(0x20 + op1Reg, instrumentPtr->reg20op1);
-	setRegister(0x40 + op1Reg, instrumentPtr->reg40op1);
-	setRegister(0x60 + op1Reg, instrumentPtr->reg60op1);
-	setRegister(0x80 + op1Reg, instrumentPtr->reg80op1);
-
-	if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) {
-		// set 2nd operator as well for FM voices and first percussion voice
-		setRegister(0x20 + op2Reg, instrumentPtr->reg20op2);
-		setRegister(0x40 + op2Reg, instrumentPtr->reg40op2);
-		setRegister(0x60 + op2Reg, instrumentPtr->reg60op2);
-		setRegister(0x80 + op2Reg, instrumentPtr->reg80op2);
-
-		if (!_musicDrvMode) {
-			// set Feedback / Algorithm as well
-			setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
-		} else {
-			if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) {
-				// set Feedback / Algorithm as well for regular FM voices only
-				setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
-			}
+uint8 MidiDriver_Accolade_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
+	// A volume adjustment is applied to the velocity of melodic notes.
+	int8 volumeAdjustment = 0;
+	if (_sources[source].type != SOURCE_TYPE_SFX) {
+		if (instrumentDef.rhythmType == RHYTHM_TYPE_UNDEFINED) {
+			byte program = _controlData[source][channel].program;
+			volumeAdjustment = _volumeAdjustments[program];
+		} else if (!_newVersion) {
+			// For rhythm notes, the Elvira 1 version of the driver checks the
+			// current "instrument" of channel 9. In this driver channel 9
+			// corresponds to the cymbal rhythm instrument, which is set to
+			// instrument definition 4. It then reads the volume adjustment
+			// for instrument 4 and applies this to all rhythm notes. This
+			// seems quite dubious and might be a bug, but it is reproduced
+			// here so rhythm volume is the same as the original interpreter.
+			// The Elvira 2 / Waxworks driver skips volume adjustment
+			// completely for rhythm notes.
+			volumeAdjustment = _volumeAdjustments[4];
 		}
 	}
+	// Note velocity and the volume adjustment are added, clipped to normal
+	// velocity range and divided by 2 to get an OPL volume value.
+	uint8 volume = CLIP(velocity + volumeAdjustment, 0, 0x7F);
+
+	if (!_newVersion) {
+		// The Elvira 1 version raises the volume a bit and clips the highest
+		// values.
+		volume += 0x18;
+		if (volume > 0x78)
+			volume = 0x78;
+	}
 
-	// Remember instrument
-	_channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
-	_channels[FMvoiceChannel].volumeAdjust         = _instrumentVolumeAdjust[MIDIinstrumentNr];
+	// Invert the volume.
+	return 0x3F - (volume >> 1);
 }
 
-void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) {
-	_opl->writeReg(reg, value);
-	//warning("OPL %x %x (%d)", reg, value, value);
+void MidiDriver_Accolade_AdLib::writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
+	// The Elvira 1 driver does not write the Cx register for rhythm
+	// instruments except the bass drum; the Elvira 2 / Waxworks driver does
+	// not write it for the bass drum either.
+	if (rhythmType == RHYTHM_TYPE_UNDEFINED || (rhythmType == RHYTHM_TYPE_BASS_DRUM && !_newVersion))
+		MidiDriver_ADLIB_Multisource::writePanning(oplChannel, rhythmType);
 }
 
-uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) {
-	return 0;
+void MidiDriver_Accolade_AdLib::writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
+	// The original driver does not write the frequency for the cymbal and
+	// hi-hat instruments.
+	if (rhythmType != RHYTHM_TYPE_HI_HAT && rhythmType != RHYTHM_TYPE_CYMBAL)
+		MidiDriver_ADLIB_Multisource::writeFrequency(oplChannel, rhythmType);
 }
 
-// Called right at the start, we get an INSTR.DAT entry
-bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
-	uint16 channelMappingOffset         = 0;
-	uint16 channelMappingSize           = 0;
-	uint16 instrumentMappingOffset      = 0;
-	uint16 instrumentMappingSize        = 0;
-	uint16 instrumentVolumeAdjustOffset = 0;
-	uint16 instrumentVolumeAdjustSize   = 0;
-	uint16 keyNoteMappingOffset         = 0;
-	uint16 keyNoteMappingSize           = 0;
-	uint16 instrumentCount              = 0;
-	uint16 instrumentDataOffset         = 0;
-	uint16 instrumentDataSize           = 0;
-	uint16 instrumentEntrySize          = 0;
-
-	if (!useMusicDrvFile) {
-		// INSTR.DAT: we expect at least 354 bytes
-		if (driverDataSize < 354)
-			return false;
-
-		// Data is like this:
-		// 128 bytes  instrument mapping
-		// 128 bytes  instrument volume adjust (signed!)
-		//  16 bytes  unknown
-		//  16 bytes  channel mapping
-		//  64 bytes  key note mapping (not used for MT32)
-		//   1 byte   instrument count
-		//   1 byte   bytes per instrument
-		//   x bytes  no instruments used for MT32
-
-		channelMappingOffset         = 256 + 16;
-		channelMappingSize           = 16;
-		instrumentMappingOffset      = 0;
-		instrumentMappingSize        = 128;
-		instrumentVolumeAdjustOffset = 128;
-		instrumentVolumeAdjustSize   = 128;
-		keyNoteMappingOffset         = 256 + 16 + 16;
-		keyNoteMappingSize           = 64;
-
-		byte instrDatInstrumentCount    = driverData[256 + 16 + 16 + 64];
-		byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1];
-
-		// We expect 9 bytes per instrument
-		if (instrDatBytesPerInstrument != 9)
-			return false;
-		// And we also expect at least one adlib instrument
-		if (!instrDatInstrumentCount)
-			return false;
-
-		instrumentCount      = instrDatInstrumentCount;
-		instrumentDataOffset = 256 + 16 + 16 + 64 + 2;
-		instrumentDataSize   = instrDatBytesPerInstrument * instrDatInstrumentCount;
-		instrumentEntrySize  = instrDatBytesPerInstrument;
-
-	} else {
-		// MUSIC.DRV: we expect at least 468 bytes
-		if (driverDataSize < 468)
-			return false;
-
-		// music.drv is basically a driver, but with a few fixed locations for certain data
-
-		channelMappingOffset         = 396;
-		channelMappingSize           = 16;
-		instrumentMappingOffset      = 140;
-		instrumentMappingSize        = 128;
-		instrumentVolumeAdjustOffset = 140 + 128;
-		instrumentVolumeAdjustSize   = 128;
-		keyNoteMappingOffset         = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn)
-		keyNoteMappingSize           = 64;
-
-		// seems to have used 128 + 5 instruments
-		// 128 regular ones and an additional 5 for percussion
-		instrumentCount         = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT;
-		instrumentDataOffset    = 722;
-		instrumentEntrySize     = 9;
-		instrumentDataSize      = instrumentCount * instrumentEntrySize;
-	}
-
-	// Channel mapping
-	if (channelMappingSize) {
-		// Get these 16 bytes for MIDI channel mapping
-		if (channelMappingSize != sizeof(_channelMapping))
-			return false;
-
-		memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping));
-	} else {
-		// Set up straight mapping
-		for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) {
-			_channelMapping[channelNr] = channelNr;
-		}
-	}
-
-	if (instrumentMappingSize) {
-		// And these for instrument mapping
-		if (instrumentMappingSize > sizeof(_instrumentMapping))
-			return false;
-
-		memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
-	}
-	// Set up straight mapping for the remaining data
-	for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) {
-		_instrumentMapping[instrumentNr] = instrumentNr;
+void MidiDriver_Accolade_AdLib::loadInstrumentData(OplInstrumentDefinition &definition, byte *instrumentData,
+		OplInstrumentRhythmType rhythmType, byte rhythmNote, bool newVersion) {
+	definition.fourOperator = false;
+
+	definition.connectionFeedback0 = instrumentData[8];
+	definition.operator0.freqMultMisc = instrumentData[0];
+	// The original driver does not add the KSL bits to the calculated
+	// volume when writing the level registers. To replicate this, the KSL
+	// bits are set to 0 for operators affected by volume.
+	// Note that the Elvira 2 / Waxworks driver has a bug which will cause
+	// the operator 0 level register of the bass drum instrument to be
+	// overwritten by the connection bit (usually 0) of another instrument
+	// if the bass drum connection is FM (and it is). This is fixed here by
+	// setting the correct value. The Elvira 1 version does not have this
+	// bug.
+	definition.operator0.level = (definition.connectionFeedback0 & 1) ? 0 : instrumentData[1];
+	definition.operator0.decayAttack = instrumentData[2];
+	definition.operator0.releaseSustain = instrumentData[3];
+	// The original driver only writes 0 to the waveform select registers
+	// during initialization, so only sine waveform is used.
+	definition.operator0.waveformSelect = 0;
+	definition.operator1.freqMultMisc = instrumentData[4];
+	definition.operator1.level = 0;
+	definition.operator1.decayAttack = instrumentData[6];
+	definition.operator1.releaseSustain = instrumentData[7];
+	definition.operator1.waveformSelect = 0;
+	if (newVersion) {
+		// The Elvira 2 / Waxworks driver always sets the last two bits of
+		// the sustain value.
+		// This was done during "programChange" in the original driver
+		definition.operator0.releaseSustain |= 3;
+		definition.operator1.releaseSustain |= 3;
 	}
 
-	if (instrumentVolumeAdjustSize) {
-		if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust))
-			return false;
+	definition.rhythmType = rhythmType;
+	definition.rhythmNote = rhythmNote;
+}
 
-		memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize);
-	}
+void MidiDriver_Accolade_AdLib::readDriverData(byte *driverData, uint16 driverDataSize, bool newVersion) {
+	uint16 minDataSize = newVersion ? 468 : 354;
+	if (driverDataSize < minDataSize)
+		error("ACCOLADE-ADLIB: Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
 
-	// Get key note mapping, if available
-	if (keyNoteMappingSize) {
-		if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping))
-			return false;
+	// INSTR.DAT Data is like this:
+	// 128 bytes  instrument mapping
+	// 128 bytes  instrument volume adjust (signed!)
+	//  16 bytes  unknown
+	//  16 bytes  channel mapping
+	//  64 bytes  key note mapping (not used for MT32)
+	//   1 byte   instrument count
+	//   1 byte   bytes per instrument
+	//   x bytes  no instruments used for MT32
 
-		memcpy(_percussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize);
-	}
+	// music.drv is basically a driver, but with a few fixed locations for certain data
 
-	// Check, if there are enough bytes left to hold all instrument data
-	if (driverDataSize < (instrumentDataOffset + instrumentDataSize))
-		return false;
+	uint16 channelMappingOffset = newVersion ? 396 : 256 + 16;
+	Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
 
-	// We release previous instrument data, just in case
-	if (_instrumentTable)
-		delete[] _instrumentTable;
+	uint16 instrumentMappingOffset = newVersion ? 140 : 0;
+	Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemapping), _instrumentRemapping);
+	setInstrumentRemapping(_instrumentRemapping);
 
-	_instrumentTable = new InstrumentEntry[instrumentCount];
-	_instrumentCount = instrumentCount;
+	uint16 volumeAdjustmentsOffset = newVersion ? 140 + 128 : 128;
+	int8 *volumeAdjustmentsData = (int8 *)driverData + volumeAdjustmentsOffset;
+	Common::copy(volumeAdjustmentsData, volumeAdjustmentsData + ARRAYSIZE(_volumeAdjustments), _volumeAdjustments);
 
-	byte            *instrDATReadPtr    = driverData + instrumentDataOffset;
-	InstrumentEntry *instrumentWritePtr = _instrumentTable;
+	if (!newVersion) {
+		byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1];
 
-	for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
-		memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(InstrumentEntry));
-		instrDATReadPtr += instrumentEntrySize;
-		instrumentWritePtr++;
+		// We expect 9 bytes per instrument
+		if (instrDatBytesPerInstrument != 9)
+			error("ACCOLADE-ADLIB: Expected instrument definitions of length 9 - got length %d", instrDatBytesPerInstrument);
 	}
 
-	// Enable MUSIC.DRV-Mode (slightly different behaviour)
-	if (useMusicDrvFile)
-		_musicDrvMode = true;
+	byte instrumentDefinitionCount = newVersion ? 128 : driverData[256 + 16 + 16 + 64];
+	uint16 rhythmNoteOffset = newVersion ? 376 + 36 : 256 + 16 + 16;
+	uint16 instrumentDataOffset = newVersion ? 722 : 256 + 16 + 16 + 64 + 2;
 
-	if (_musicDrvMode) {
-		// Extra code for MUSIC.DRV
+	_instrumentBank = new OplInstrumentDefinition[instrumentDefinitionCount];
+	for (int i = 0; i < instrumentDefinitionCount; i++) {
+		byte *instrumentData = driverData + instrumentDataOffset + (i * 9);
+		loadInstrumentData(_instrumentBank[i], instrumentData, RHYTHM_TYPE_UNDEFINED, 0, newVersion);
+	}
 
-		// This was done during "programChange" in the original driver
-		instrumentWritePtr = _instrumentTable;
-		for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) {
-			instrumentWritePtr->reg80op1 |= 0x03; // set release rate
-			instrumentWritePtr->reg80op2 |= 0x03;
-			instrumentWritePtr++;
-		}
+	_rhythmBank = new OplInstrumentDefinition[40];
+	_rhythmBankFirstNote = 36;
+	_rhythmBankLastNote = 75;
+	// Elvira 1 version uses instruments 1-5 for rhythm, Elvira 2 / Waxworks
+	// version uses 0x80-0x84.
+	byte *rhythmInstrumentDefinitions = driverData + instrumentDataOffset + ((newVersion ? 0x80 : 1) * 9);
+	byte *rhythmNotes = driverData + rhythmNoteOffset;
+	for (int i = 0; i < 40; i++) {
+		byte instrumentDefNumber = RHYTHM_NOTE_INSTRUMENT_TYPES[i] > 0xA ? 0 : RHYTHM_NOTE_INSTRUMENT_TYPES[i] - 6;
+		OplInstrumentRhythmType rhythmType = RHYTHM_NOTE_INSTRUMENT_TYPES[i] > 0xA ? RHYTHM_TYPE_UNDEFINED :
+			static_cast<OplInstrumentRhythmType>(11 - RHYTHM_NOTE_INSTRUMENT_TYPES[i]);
+		byte *instrumentData = rhythmInstrumentDefinitions + (instrumentDefNumber * 9);
+
+		loadInstrumentData(_rhythmBank[i], instrumentData, rhythmType, rhythmNotes[i], newVersion);
 	}
-	return true;
 }
 
-MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename) {
-	byte  *driverData = nullptr;
+MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType) {
+	byte *driverData = nullptr;
 	uint16 driverDataSize = 0;
-	bool   isMusicDrvFile = false;
+	bool newVersion = false;
 
-	MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, isMusicDrvFile);
+	MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, newVersion);
 	if (!driverData)
 		error("ACCOLADE-ADLIB: error during readDriver()");
 
-	MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib();
-	if (driver) {
-		if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) {
-			delete driver;
-			driver = nullptr;
-		}
-	}
+	MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(oplType, newVersion);
+	if (!driver)
+		error("ACCOLADE-ADLIB: could not create driver");
+
+	driver->readDriverData(driverData, driverDataSize, newVersion);
 
 	delete[] driverData;
 	return driver;
diff --git a/engines/agos/drivers/accolade/adlib.h b/engines/agos/drivers/accolade/adlib.h
index fa2741ec75c..595b2d751e3 100644
--- a/engines/agos/drivers/accolade/adlib.h
+++ b/engines/agos/drivers/accolade/adlib.h
@@ -19,102 +19,78 @@
  *
  */
 
-#include "agos/drivers/accolade/mididriver.h"
+#ifndef AGOS_DRIVERS_ACCOLADE_ADLIB_H
+#define AGOS_DRIVERS_ACCOLADE_ADLIB_H
 
-#include "audio/fmopl.h"
-#include "audio/mididrv.h"
+#include "audio/adlib_ms.h"
 
 namespace AGOS {
 
-#define AGOS_ADLIB_VOICES_COUNT 11
-
-struct InstrumentEntry {
-	byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
-	byte reg40op1; // Level Key Scaling / Total Level
-	byte reg60op1; // Attack Rate / Decay Rate
-	byte reg80op1; // Sustain Level / Release Rate
-	byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
-	byte reg40op2; // Level Key Scaling / Total Level
-	byte reg60op2; // Attack Rate / Decay Rate
-	byte reg80op2; // Sustain Level / Release Rate
-	byte regC0;    // Feedback / Algorithm, bit 0 - set -> both operators in use
-};
+class MidiDriver_Accolade_AdLib : public MidiDriver_ADLIB_Multisource {
+protected:
+	static const byte RHYTHM_NOTE_INSTRUMENT_TYPES[40];
+	static const uint16 OPL_NOTE_FREQUENCIES_INSTR_DAT[12];
+	static const uint16 OPL_NOTE_FREQUENCIES_MUSIC_DRV[12];
 
-class MidiDriver_Accolade_AdLib : public MidiDriver {
 public:
-	MidiDriver_Accolade_AdLib();
+	MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion = false);
 	~MidiDriver_Accolade_AdLib() override;
 
-	// MidiDriver
 	int open() override;
-	void close() override;
-	void send(uint32 b) override;
-	MidiChannel *allocateChannel() override { return NULL; }
-	MidiChannel *getPercussionChannel() override { return NULL; }
-
-	bool isOpen() const override { return _isOpen; }
-	uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
-
-	void setVolume(byte volume);
-	uint32 property(int prop, uint32 param) override;
-
-	bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
-
-	void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
+	using MidiDriver_ADLIB_Multisource::send;
+	void send(int8 source, uint32 b) override;
+	void deinitSource(uint8 source) override;
+
+	// Read the specified data from INSTR.DAT or MUSIC.DRV.
+	void readDriverData(byte *driverData, uint16 driverDataSize, bool isMusicDrv);
+
+	// Returns the number of simultaneous SFX sources supported by the current
+	// driver configuration.
+	byte getNumberOfSfxSources();
+	// Loads the specified instrument for the specified instrument source.
+	void loadSfxInstrument(uint8 source, byte *instrumentData);
+	// Sets the note (upper byte) and note fraction (lower byte; 1/256th notes)
+	// for the specified SFX source.
+	void setSfxNoteFraction(uint8 source, uint16 noteFraction);
+	// Writes out the current frequency for the specified SFX source.
+	void updateSfxNote(uint8 source);
+
+protected:
+	InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note) override;
+
+	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
+	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
+
+	void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
+	void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
+
+	// Copies the specified instrument data (in INSTR.DAT/MUSIC.DRV format)
+	// into the specified instrument definition.
+	void loadInstrumentData(OplInstrumentDefinition &definition, byte *instrumentData,
+		OplInstrumentRhythmType rhythmType, byte rhythmNote, bool newVersion);
+
+	// False if the driver should have the behavior of the Elvira 1 driver;
+	// true if it should have the behavior of the Elvira 2 / Waxworks version.
+	bool _newVersion;
 
-private:
-	bool _musicDrvMode;
-
-	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel
-	byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
-	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments
-	byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
 	// from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument
-	signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT];
-	// simple mapping between MIDI key notes and MT32 key notes
-	byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT];
-
-	// from INSTR.DAT/MUSIC.DRV - adlib instrument data
-	InstrumentEntry *_instrumentTable;
-	byte            _instrumentCount;
-
-	struct ChannelEntry {
-		const  InstrumentEntry *currentInstrumentPtr;
-		byte   currentNote;
-		byte   currentA0hReg;
-		byte   currentB0hReg;
-		int16  volumeAdjust;
-		byte   velocity;
-
-		ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0),
-						currentA0hReg(0), currentB0hReg(0), volumeAdjust(0), velocity(0) { }
-	};
-
-	byte _percussionReg;
-
-	OPL::OPL *_opl;
-	int _masterVolume;
-
-	Common::TimerManager::TimerProc _adlibTimerProc;
-	void *_adlibTimerParam;
-
-	bool _isOpen;
-
-	// stores information about all FM voice channels
-	ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT];
-
-	void onTimer();
-
-	void resetAdLib();
-	void resetAdLibOperatorRegisters(byte baseRegister, byte value);
-	void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
-
-	void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
-	void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
-	void setRegister(int reg, int value);
-	void noteOn(byte FMvoiceChannel, byte note, byte velocity);
-	void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte velocity);
-	void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote);
+	int8 _volumeAdjustments[128];
+	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and AdLib channel
+	byte _channelRemapping[16];
+	// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and AdLib instruments
+	byte _instrumentRemapping[128];
+	// Points to one of the OPL_NOTE_FREQUENCIES arrays, depending on the driver version
+	const uint16 *_oplNoteFrequencies;
+
+	// Data used by AdLib SFX (Elvira 2 / Waxworks)
+
+	// Instrument definition for each SFX source
+	OplInstrumentDefinition _sfxInstruments[4];
+	// Current MIDI note fraction (1/256th notes) for each SFX source
+	byte _sfxNoteFractions[4];
 };
 
 } // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h
index b78fb029c39..3b6df3f0702 100644
--- a/engines/agos/drivers/accolade/mididriver.h
+++ b/engines/agos/drivers/accolade/mididriver.h
@@ -23,7 +23,11 @@
 #define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
 
 #include "agos/agos.h"
+
+#include "audio/fmopl.h"
 #include "audio/mididrv.h"
+#include "audio/mididrv_ms.h"
+
 #include "common/error.h"
 
 namespace AGOS {
@@ -35,8 +39,8 @@ namespace AGOS {
 
 extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile);
 
-extern MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename);
-extern MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
+extern MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType);
+extern MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
 extern MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev);
 
 } // End of namespace AGOS
diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp
index b95d1f83321..c84448f3214 100644
--- a/engines/agos/drivers/accolade/mt32.cpp
+++ b/engines/agos/drivers/accolade/mt32.cpp
@@ -19,199 +19,154 @@
  *
  */
 
-#include "agos/drivers/accolade/mididriver.h"
 #include "agos/drivers/accolade/mt32.h"
 
-#include "audio/mididrv.h"
-
-#include "common/config-manager.h"
+#include "agos/drivers/accolade/mididriver.h"
+#include "agos/sfxparser_accolade.h"
 
 namespace AGOS {
 
-MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() {
-	_driver = nullptr;
-	_isOpen = false;
-	_nativeMT32 = false;
-	_baseFreq = 250;
+const uint8 MidiDriver_Accolade_MT32::SYSEX_INSTRUMENT_ASSIGNMENT[7] = { 0x02, 0x00, 0x18, 0x32, 0x01, 0x00, 0x01 };
 
-	memset(_channelMapping, 0, sizeof(_channelMapping));
-	memset(_instrumentMapping, 0, sizeof(_instrumentMapping));
+MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() : MidiDriver_MT32GM(MT_MT32) {
+	Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
+	Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
+	Common::fill(_channelLocks, _channelLocks + ARRAYSIZE(_channelLocks), false);
 }
 
-MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() {
-	Common::StackLock lock(_mutex);
-	if (_driver) {
-		_driver->setTimerCallback(nullptr, nullptr);
-		_driver->close();
-		delete _driver;
-	}
-	_driver = nullptr;
+int MidiDriver_Accolade_MT32::open(MidiDriver *driver, bool nativeMT32) {
+	int result = MidiDriver_MT32GM::open(driver, nativeMT32);
+
+	setInstrumentRemapping(_instrumentRemapping);
+
+	return result;
 }
 
-int MidiDriver_Accolade_MT32::open() {
-	assert(!_driver);
+void MidiDriver_Accolade_MT32::send(int8 source, uint32 b) {
+	byte dataChannel = b & 0xf;
+	int8 outputChannel = mapSourceChannel(source, dataChannel);
 
-//	debugC(kDebugLevelMT32Driver, "MT32: starting driver");
+	MidiChannelControlData &controlData = *_controlData[outputChannel];
 
-	// Setup midi driver
-	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
-	MusicType musicType = MidiDriver::getMusicType(dev);
+	// Check if this event is sent by a music source and the channel is locked
+	// by an SFX source.
+	bool channelLockedByOtherSource = _sources[source].type != SOURCE_TYPE_SFX && _channelLocks[outputChannel];
 
-	// check, if we got a real MT32 (or MUNT, or MUNT over MIDI)
-	switch (musicType) {
-	case MT_MT32:
-		_nativeMT32 = true;
-		break;
-	case MT_GM:
-		if (ConfMan.getBool("native_mt32")) {
-			_nativeMT32 = true;
-		}
-		break;
-	default:
-		break;
-	}
+	processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource);
+}
 
-	_driver = MidiDriver::createMidi(dev);
-	if (!_driver)
-		return 255;
+int8 MidiDriver_Accolade_MT32::mapSourceChannel(uint8 source, uint8 dataChannel) {
+	if (!_isOpen)
+		// Use 1 on 1 mapping during device initialization.
+		return dataChannel;
 
-	int ret = _driver->open();
-	if (ret)
-		return ret;
+	if (_sources[source].type == SOURCE_TYPE_SFX) {
+		// Use channels 7 and 8 for SFX (sources 1 and 2).
+		uint8 sfxChannel =  9 - source;
 
-	if (_nativeMT32)
-		_driver->sendMT32Reset();
-	else
-		_driver->sendGMReset();
+		_allocationMutex.lock();
 
-	return 0;
-}
+		if (!_channelLocks[sfxChannel]) {
+			// Lock channel
+			stopAllNotes(0xFF, sfxChannel);
+			_channelLocks[sfxChannel] = true;
+		}
 
-void MidiDriver_Accolade_MT32::close() {
-	if (_driver) {
-		_driver->close();
+		_allocationMutex.unlock();
+
+		return sfxChannel;
+	} else {
+		return _channelRemapping[dataChannel];
 	}
 }
 
-// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
-void MidiDriver_Accolade_MT32::send(uint32 b) {
-	byte command = b & 0xf0;
-	byte channel = b & 0xf;
+void MidiDriver_Accolade_MT32::deinitSource(uint8 source) {
+	_allocationMutex.lock();
 
-	if (command == 0xF0) {
-		if (_driver) {
-			_driver->send(b);
+	if (_sources[source].type == SOURCE_TYPE_SFX) {
+		for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
+			if (_controlData[i]->source == source) {
+				// Restore the music instrument.
+				programChange(i, _controlData[i]->program, 0, *_controlData[i], false);
+				// Unlock the channel.
+				_channelLocks[i] = false;
+			}
 		}
-		return;
 	}
 
-	byte mappedChannel = _channelMapping[channel];
+	_allocationMutex.unlock();
 
-	if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) {
-		// channel mapped to an actual MIDI channel, so use that one
-		b = (b & 0xFFFFFFF0) | mappedChannel;
-		if (command == 0xC0) {
-			// Program change
-			// Figure out the requested instrument
-			byte midiInstrument = (b >> 8) & 0xFF;
-			byte mappedInstrument = _instrumentMapping[midiInstrument];
-
-			// If there is no actual MT32 (or MUNT), we make a second mapping to General MIDI instruments
-			if (!_nativeMT32) {
-				mappedInstrument = (MidiDriver::_mt32ToGm[mappedInstrument]);
-			}
-			// And replace it
-			b = (b & 0xFFFF00FF) | (mappedInstrument << 8);
-		}
+	MidiDriver_MT32GM::deinitSource(source);
+}
 
-		if (_driver) {
-			_driver->send(b);
-		}
+void MidiDriver_Accolade_MT32::loadSfxInstrument(uint8 source, byte *instrumentData) {
+	if (!(source == 1 || source == 2)) {
+		warning("MidiDriver_Accolade_MT32::loadSfxInstrument - unexpected source %d", source);
+		return;
 	}
+
+	// Send the instrument data to the timbre memory (patch 1 or 2).
+	uint32 address = (0x08 << 14) | (((source - 1) * 2) << 7);
+	sysExMT32(instrumentData + 3, SfxParser_Accolade::INSTRUMENT_SIZE_MT32 - 3, address, true, true, source);
+
+	// Allocate the new patch to instrument number 0x75 or 0x76.
+	byte instrNum = SFX_PROGRAM_BASE + source - 1;
+	address = (0x05 << 14) | instrNum << 3;
+	byte instrAssignData[7];
+	Common::copy(SYSEX_INSTRUMENT_ASSIGNMENT, SYSEX_INSTRUMENT_ASSIGNMENT + ARRAYSIZE(instrAssignData), instrAssignData);
+	instrAssignData[1] = source - 1;
+	sysExMT32(instrAssignData, 7, address, true, true, source);
 }
 
-// Called right at the start, we get an INSTR.DAT entry
-bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
-	uint16 channelMappingOffset = 0;
-	uint16 channelMappingSize = 0;
-	uint16 instrumentMappingOffset = 0;
-	uint16 instrumentMappingSize = 0;
-
-	if (!useMusicDrvFile) {
-		// INSTR.DAT: we expect at least 354 bytes
-		if (driverDataSize < 354)
-			return false;
-
-		// Data is like this:
-		// 128 bytes  instrument mapping
-		// 128 bytes  instrument volume adjust (signed!) (not used for MT32)
-		//  16 bytes  unknown
-		//  16 bytes  channel mapping
-		//  64 bytes  key note mapping (not really used for MT32)
-		//   1 byte   instrument count
-		//   1 byte   bytes per instrument
-		//   x bytes  no instruments used for MT32
-
-		channelMappingOffset    = 256 + 16;
-		channelMappingSize      = 16;
-		instrumentMappingOffset = 0;
-		instrumentMappingSize   = 128;
+void MidiDriver_Accolade_MT32::changeSfxInstrument(uint8 source) {
+	// Change to the newly loaded instrument.
+	byte channel = mapSourceChannel(source, 0);
+	MidiChannelControlData &controlData = *_controlData[channel];
+	byte originalInstrument = controlData.program;
+	programChange(channel, SFX_PROGRAM_BASE + source - 1, source, controlData);
+	// Store the original instrument so it can be used when deinitializing
+	// the source.
+	controlData.program = originalInstrument;
+}
 
-	} else {
-		// MUSIC.DRV: we expect at least 468 bytes
-		if (driverDataSize < 468)
-			return false;
-
-		channelMappingOffset    = 396;
-		channelMappingSize      = 16;
-		instrumentMappingOffset = 140;
-		instrumentMappingSize   = 128;
-	}
+void MidiDriver_Accolade_MT32::readDriverData(byte *driverData, uint16 driverDataSize, bool newVersion) {
+	uint16 minDataSize = newVersion ? 468 : 354;
+	if (driverDataSize < minDataSize)
+		error("ACCOLADE-ADLIB: Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
 
-	// Channel mapping
-	if (channelMappingSize) {
-		// Get these 16 bytes for MIDI channel mapping
-		if (channelMappingSize != sizeof(_channelMapping))
-			return false;
+	// INSTR.DAT Data is like this:
+	// 128 bytes  instrument mapping
+	// 128 bytes  instrument volume adjust (signed!) (not used for MT32)
+	//  16 bytes  unknown
+	//  16 bytes  channel mapping
+	//  64 bytes  key note mapping (not really used for MT32)
+	//   1 byte   instrument count
+	//   1 byte   bytes per instrument
+	//   x bytes  no instruments used for MT32
 
-		memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping));
-	} else {
-		// Set up straight mapping
-		for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) {
-			_channelMapping[channelNr] = channelNr;
-		}
-	}
+	// music.drv is basically a driver, but with a few fixed locations for certain data
 
-	if (instrumentMappingSize) {
-		// And these for instrument mapping
-		if (instrumentMappingSize > sizeof(_instrumentMapping))
-			return false;
+	uint16 channelMappingOffset = newVersion ? 396 : 256 + 16;
+	Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
 
-		memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
-	}
-	// Set up straight mapping for the remaining data
-	for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) {
-		_instrumentMapping[instrumentNr] = instrumentNr;
-	}
-	return true;
+	uint16 instrumentMappingOffset = newVersion ? 140 : 0;
+	Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemapping), _instrumentRemapping);
 }
 
-MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename) {
-	byte  *driverData = nullptr;
+MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename) {
+	byte *driverData = nullptr;
 	uint16 driverDataSize = 0;
-	bool   isMusicDrvFile = false;
+	bool newVersion = false;
 
-	MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, isMusicDrvFile);
+	MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, newVersion);
 	if (!driverData)
-		error("ACCOLADE-ADLIB: error during readDriver()");
+		error("ACCOLADE-MT32: error during readDriver()");
 
 	MidiDriver_Accolade_MT32 *driver = new MidiDriver_Accolade_MT32();
-	if (driver) {
-		if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) {
-			delete driver;
-			driver = nullptr;
-		}
-	}
+	if (!driver)
+		error("ACCOLADE-MT32: could not create driver");
+
+	driver->readDriverData(driverData, driverDataSize, newVersion);
 
 	delete[] driverData;
 	return driver;
diff --git a/engines/agos/drivers/accolade/mt32.h b/engines/agos/drivers/accolade/mt32.h
index 3ab34168b1c..9fdb85bfca0 100644
--- a/engines/agos/drivers/accolade/mt32.h
+++ b/engines/agos/drivers/accolade/mt32.h
@@ -19,65 +19,48 @@
  *
  */
 
-#include "agos/drivers/accolade/mididriver.h"
+#ifndef AGOS_DRIVERS_ACCOLADE_MT32_H
+#define AGOS_DRIVERS_ACCOLADE_MT32_H
 
-#include "audio/mididrv.h"
-
-#include "common/mutex.h"
+#include "audio/mt32gm.h"
 
 namespace AGOS {
 
-class MidiDriver_Accolade_MT32 : public MidiDriver {
+class MidiDriver_Accolade_MT32 : public MidiDriver_MT32GM {
+protected:
+	static const uint8 SFX_PROGRAM_BASE = 0x75;
+	static const uint8 SYSEX_INSTRUMENT_ASSIGNMENT[7];
+
 public:
 	MidiDriver_Accolade_MT32();
-	~MidiDriver_Accolade_MT32() override;
-
-	// MidiDriver
-	int open() override;
-	void close() override;
-	bool isOpen() const override { return _isOpen; }
 
-	void send(uint32 b) override;
+	int open(MidiDriver *driver, bool nativeMT32) override;
+	using MidiDriver_MT32GM::send;
+	void send(int8 source, uint32 b) override;
 
-	MidiChannel *allocateChannel() override {
-		if (_driver)
-			return _driver->allocateChannel();
-		return NULL;
-	}
-	MidiChannel *getPercussionChannel() override {
-		if (_driver)
-			return _driver->getPercussionChannel();
-		return NULL;
-	}
+	int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
+	void deinitSource(uint8 source) override;
 
-	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
-		if (_driver)
-			_driver->setTimerCallback(timer_param, timer_proc);
-	}
+	// Read the specified data from INSTR.DAT or MUSIC.DRV.
+	void readDriverData(byte *driverData, uint16 driverDataSize, bool isMusicDrv);
 
-	uint32 getBaseTempo() override {
-		if (_driver) {
-			return _driver->getBaseTempo();
-		}
-		return 1000000 / _baseFreq;
-	}
+	// Loads the specified instrument for the specified instrument source.
+	void loadSfxInstrument(uint8 source, byte *instrumentData);
+	// Changes the channel assigned to the specified SFX source to the SFX
+	// program number.
+	void changeSfxInstrument(uint8 source);
 
 protected:
-	Common::Mutex _mutex;
-	MidiDriver *_driver;
-	bool _nativeMT32; // native MT32, may also be our MUNT, or MUNT over MIDI
-
-	bool _isOpen;
-	int _baseFreq;
-
-private:
 	// simple mapping between MIDI channel and MT32 channel
-	byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
+	byte _channelRemapping[16];
 	// simple mapping between MIDI instruments and MT32 instruments
-	byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
+	byte _instrumentRemapping[128];
 
-public:
-	bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
+	// Indicates if a MIDI channel is locked by an SFX source and unavailable
+	// for music.
+	bool _channelLocks[MIDI_CHANNEL_COUNT];
 };
 
 } // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/drivers/simon1/adlib.cpp b/engines/agos/drivers/simon1/adlib.cpp
index cb035906ea4..1f88cd4be8a 100644
--- a/engines/agos/drivers/simon1/adlib.cpp
+++ b/engines/agos/drivers/simon1/adlib.cpp
@@ -299,7 +299,7 @@ uint8 MidiDriver_Simon1_AdLib::calculateUnscaledVolume(uint8 channel, uint8 sour
 	return 0x3F - calculatedVolume;
 }
 
-MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename) {
+MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename, OPL::Config::OplType oplType) {
 	// Load instrument data.
 	Common::File ibk;
 
@@ -319,7 +319,7 @@ MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilena
 		return nullptr;
 	}
 
-	MidiDriver_Simon1_AdLib *driver = new MidiDriver_Simon1_AdLib(OPL::Config::kOpl3, instrumentData);
+	MidiDriver_Simon1_AdLib *driver = new MidiDriver_Simon1_AdLib(oplType, instrumentData);
 	delete[] instrumentData;
 
 	return driver;
diff --git a/engines/agos/drivers/simon1/adlib.h b/engines/agos/drivers/simon1/adlib.h
index f84d7a9aeb3..7d4a5fee3c8 100644
--- a/engines/agos/drivers/simon1/adlib.h
+++ b/engines/agos/drivers/simon1/adlib.h
@@ -71,7 +71,7 @@ private:
 	bool _musicRhythmNotesDisabled;
 };
 
-MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename);
+MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename, OPL::Config::OplType);
 
 } // End of namespace AGOS
 
diff --git a/engines/agos/drivers/simon1/adlib_win.cpp b/engines/agos/drivers/simon1/adlib_win.cpp
deleted file mode 100644
index 739426e0bc1..00000000000
--- a/engines/agos/drivers/simon1/adlib_win.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "agos/drivers/simon1/adlib_win.h"
-
-namespace AGOS {
-
-MidiDriver_Simon1_AdLib_Windows::MidiDriver_Simon1_AdLib_Windows() : MidiDriver_ADLIB_Multisource(OPL::Config::kOpl3) { }
-
-void MidiDriver_Simon1_AdLib_Windows::programChange(uint8 channel, uint8 program, uint8 source) {
-	// WORKAROUND The Windows version of Simon The Sorcerer uses the MT-32 MIDI
-	// data of the DOS versions, but plays this using Windows' General MIDI
-	// system. As a result, the music is played using different instruments
-	// than intended. This is fixed here by mapping the MT-32 instruments to
-	// GM instruments using MidiDriver's standard mapping.
-	uint8 gmInstrument = _mt32ToGm[program];
-	MidiDriver_ADLIB_Multisource::programChange(channel, gmInstrument, source);
-}
-
-} // End of namespace AGOS
diff --git a/engines/agos/drivers/simon1/adlib_win.h b/engines/agos/drivers/simon1/adlib_win.h
deleted file mode 100644
index 9ca679d411e..00000000000
--- a/engines/agos/drivers/simon1/adlib_win.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#ifndef AGOS_SIMON1_ADLIB_WIN_H
-#define AGOS_SIMON1_ADLIB_WIN_H
-
-#include "audio/adlib_ms.h"
-
-namespace AGOS {
-
-/**
- * AdLib MIDI driver for the Windows version of Simon The Sorcerer.
- * This driver contains a workaround for converting the MT-32 instruments to
- * the General MIDI instruments used by this driver.
- */
-class MidiDriver_Simon1_AdLib_Windows : public MidiDriver_ADLIB_Multisource {
-public:
-	MidiDriver_Simon1_AdLib_Windows();
-
-protected:
-	void programChange(uint8 channel, uint8 program, uint8 source) override;
-};
-
-} // End of namespace AGOS
-
-#endif
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 9d0f1f19289..65069f865bf 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -34,7 +34,6 @@
 #include "agos/drivers/accolade/adlib.h"
 #include "agos/drivers/accolade/mt32.h"
 #include "agos/drivers/simon1/adlib.h"
-#include "agos/drivers/simon1/adlib_win.h"
 // Miles Audio for Simon 2
 #include "audio/miles.h"
 #include "audio/midiparser.h"
@@ -48,16 +47,15 @@
 
 namespace AGOS {
 
-
 // MidiParser_S1D is not considered part of the standard
 // MidiParser suite, but we still try to mask its details
 // and just provide a factory function.
-extern MidiParser *MidiParser_createS1D();
+extern MidiParser *MidiParser_createS1D(uint8 source = 0, bool monophonicChords = false);
 
 // This instrument remapping has been constructed by checking how the GM
 // instruments correspond to MT-32 instruments in other tracks (f.e. track 10-3
 // is similar to track 11).
-byte MidiPlayer::SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[] {
+const byte MidiPlayer::SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[] {
 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 
@@ -69,29 +67,17 @@ byte MidiPlayer::SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[] {
 };
 
 MidiPlayer::MidiPlayer(AGOSEngine *vm) {
-	// Since initialize() is called every time the music changes,
-	// this is where we'll initialize stuff that must persist
-	// between songs.
 	_vm = vm;
 	_driver = nullptr;
 	_driverMsMusic = nullptr;
 	_driverMsSfx = nullptr;
-	_map_mt32_to_gm = false;
-
-	_current = nullptr;
-
-	_musicVolume = 255;
-	_sfxVolume = 255;
 
-	resetVolumeTable();
 	_paused = false;
 
-	_currentTrack = 255;
-	_loopTrack = 0;
 	_queuedTrack = 255;
 	_loopQueuedTrack = 0;
 
-	_musicMode = kMusicModeDisabled;
+	_pc98 = false;
 	_deviceType = MT_NULL;
 	_dataType = MT_NULL;
 
@@ -99,226 +85,272 @@ MidiPlayer::MidiPlayer(AGOSEngine *vm) {
 	_parserSfx = nullptr;
 	_musicData = nullptr;
 	_sfxData = nullptr;
+	_parserSfxAccolade = nullptr;
 }
 
 MidiPlayer::~MidiPlayer() {
 	stop();
-	stopSfx();
-
-	Common::StackLock lock(_mutex);
+	stop(true);
 
+	// Close the drivers first before locking the mutex. Otherwise a deadlock
+	// can occur where the timer thread has locked the timer mutex while
+	// waiting for the MidiPlayer mutex in the callback, while the main thread
+	// has locked the MidiPlayer mutex and waits for the timer mutex to remove
+	// a driver callback.
 	if (_driverMsSfx && _driverMsSfx != _driverMsMusic) {
 		_driverMsSfx->setTimerCallback(nullptr, nullptr);
 		_driverMsSfx->close();
-		delete _driverMsSfx;
-		_driverMsSfx = nullptr;
 	}
 	if (_driverMsMusic) {
 		_driverMsMusic->setTimerCallback(nullptr, nullptr);
 		_driverMsMusic->close();
-		delete _driverMsMusic;
-		_driverMsMusic = nullptr;
 	} else if (_driver) {
 		_driver->setTimerCallback(nullptr, nullptr);
 		_driver->close();
-		delete _driver;
-		_driver = nullptr;
 	}
 
+	Common::StackLock lock(_mutex);
+
 	if (_parserMusic)
 		delete _parserMusic;
 	if (_parserSfx)
 		delete _parserSfx;
+	if (_parserSfxAccolade)
+		delete _parserSfxAccolade;
 
 	if (_musicData)
 		delete[] _musicData;
 	if (_sfxData)
 		delete[] _sfxData;
 
-	clearConstructs();
+	if (_driverMsSfx && _driverMsSfx != _driverMsMusic) {
+		delete _driverMsSfx;
+		_driverMsSfx = nullptr;
+	}
+	if (_driverMsMusic) {
+		delete _driverMsMusic;
+		_driverMsMusic = nullptr;
+	} else if (_driver) {
+		delete _driver;
+		_driver = nullptr;
+	}
 }
 
-int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
+int MidiPlayer::open() {
 	// Don't call open() twice!
 	assert(!_driver);
 
-	Common::String accoladeDriverFilename;
+	_pc98 = _vm->getGameType() == GType_ELVIRA1 && _vm->getPlatform() == Common::kPlatformPC98;
 	// All games have MT-32 data, except Simon 2, which has both GM and MT-32
 	// data for DOS, or only GM for Windows. Some of the GM tracks have extra
-	// instruments compared to the MT-32 tracks, so we prefer GM.
-	int devFlags = MDT_MIDI | MDT_ADLIB | (_vm->getGameType() == GType_SIMON2 ? MDT_PREFER_GM : MDT_PREFER_MT32);
-
-	if (_vm->getGameType() == GType_SIMON1) {
-		// Check the type of device that the user has configured.
-		MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
-		_deviceType = MidiDriver::getMusicType(dev);
-		if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
-			_deviceType = MT_MT32;
-
-		// All versions of Simon 1 that use MIDI have data that targets the
-		// MT-32 (even the Windows and Acorn versions).
+	// instruments compared to the MT-32 tracks, so GM is preferred.
+	int devFlags = MDT_MIDI | (_pc98 ? MDT_PC98 : MDT_ADLIB) |
+		(_vm->getGameType() == GType_SIMON2 ? MDT_PREFER_GM : MDT_PREFER_MT32);
+
+	// Check the type of device that the user has configured.
+	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
+	_deviceType = MidiDriver::getMusicType(dev);
+	if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
+		_deviceType = MT_MT32;
+
+	if (_vm->getGameType() == GType_SIMON2) {
+		// Simon 2 DOS version has both MT-32 and GM tracks; Windows is GM only.
+		_dataType = (_vm->getPlatform() == Common::kPlatformDOS && _deviceType == MT_MT32) ? MT_MT32 : MT_GM;
+	} else {
+		// All the other games' MIDI data targets MT-32 (even Simon 1 Windows
+		// and Acorn).
 		_dataType = MT_MT32;
+	}
 
-		if (_dataType == MT_MT32 && _deviceType == MT_GM) {
-			// Not a real MT32 / no MUNT
-			::GUI::MessageDialog dialog(_(
-				"You appear to be using a General MIDI device,\n"
-				"but your game only supports Roland MT32 MIDI.\n"
-				"We try to map the Roland MT32 instruments to\n"
-				"General MIDI ones. It is still possible that\n"
-				"some tracks sound incorrect."));
-			dialog.runModal();
-		}
-
-		if (_vm->getPlatform() == Common::kPlatformDOS &&
-				(_vm->getFeatures() & GF_DEMO) && !(_vm->getFeatures() & GF_TALKIE)) {
-			// DOS floppy demo uses the older Accolade music drivers.
-			_musicMode = kMusicModeAccolade;
-			accoladeDriverFilename = "MUSIC.DRV";
-
-			switch (_deviceType) {
-			case MT_ADLIB:
-				_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
-				break;
-			case MT_MT32:
-				_driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
-				break;
-			default:
-				_driver = new MidiDriver_NULL_Multisource();
-				break;
-			}
+	if (_dataType == MT_MT32 && _deviceType == MT_GM) {
+		// Not a real MT32 / no MUNT
+		::GUI::MessageDialog dialog(_(
+			"You appear to be using a General MIDI device,\n"
+			"but your game only supports Roland MT32 MIDI.\n"
+			"We try to map the Roland MT32 instruments to\n"
+			"General MIDI ones. It is still possible that\n"
+			"some tracks sound incorrect."));
+		dialog.runModal();
+	}
 
-			// Create the MIDI parser for the MUS format used by this version.
-			_parserMusic = MidiParser_createS1D();
-			_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
+	// Elvira 2, Waxworks and Simon 1 DOS floppy have MIDI SFX.
+	bool usesMidiSfx = _vm->getGameType() == GType_WW || _vm->getGameType() == GType_ELVIRA2 ||
+		(_vm->getGameType() == GType_SIMON1 && _vm->getPlatform() == Common::kPlatformDOS &&
+			!(_vm->getFeatures() & GF_DEMO) && !(_vm->getFeatures() & GF_TALKIE));
 
-			// Open the MIDI driver.
-			int returnCode = _driver->open();
-			if (returnCode != 0)
-				error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
+	// OPL3 is used for Windows and Acorn versions, all Simon 2 versions and
+	// if the user has set the OPL3 mode option. Otherwise OPL2 is used.
+	OPL::Config::OplType oplType = (_vm->getPlatform() != Common::kPlatformDOS ||
+		_vm->getGameType() == GType_SIMON2 || ConfMan.getBool("opl3_mode")) ? OPL::Config::kOpl3 : OPL::Config::kOpl2;
 
-			// Connect the driver and the parser.
-			_parserMusic->setMidiDriver(_driver);
-			_parserMusic->setTimerRate(_driver->getBaseTempo());
+	// Create drivers and parsers for the different versions of the games.
+	if ((_vm->getGameType() == GType_ELVIRA1 && _vm->getPlatform() == Common::kPlatformDOS) ||
+			_vm->getGameType() == GType_ELVIRA2 || _vm->getGameType() == GType_WW ||
+			(_vm->getGameType() == GType_SIMON1 && _vm->getPlatform() == Common::kPlatformDOS &&
+				 (_vm->getFeatures() & GF_DEMO) && !(_vm->getFeatures() & GF_TALKIE))) {
+		// Elvira 1 DOS, Elvira 2, Waxworks and Simon 1 DOS floppy demo
 
-			_driver->setTimerCallback(_parserMusic, &_parserMusic->timerCallback);
+		// These games use drivers used by several Accolade games.
+		// Elvira 1 DOS uses an older version of the Accolade drivers which
+		// uses different files.
+		Common::String accoladeDriverFilename = _vm->getGameType() == GType_ELVIRA1 ? "INSTR.DAT" : "MUSIC.DRV";
 
-			return 0;
-		} else {
-			// Create the correct driver(s) for the device type and platform.
-			switch (_deviceType) {
-			case MT_ADLIB:
-				if (_vm->getPlatform() == Common::kPlatformDOS) {
-					// DOS versions use a specific AdLib driver implementation
-					// that needs an instrument bank file.
-					_driverMsMusic = createMidiDriverSimon1AdLib("MT_FM.IBK");
-					if (!(_vm->getFeatures() & GF_TALKIE)) {
-						// DOS floppy version has MIDI SFX for AdLib.
-						_driverMsSfx = _driverMsMusic;
-					}
+		switch (_deviceType) {
+		case MT_ADLIB:
+			_driverMsMusic = _driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
+			if (_vm->getGameType() == GType_WW)
+				// WORKAROUND Some Waxworks tracks do not set an instrument on
+				// a MIDI channel. This will cause that channel to play with
+				// whatever instrument was set there by a previous track.
+				// This is fixed by setting default instruments on all channels
+				// before starting a track.
+				_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM);
+			if (usesMidiSfx)
+				// Elvira 2 and Waxworks have AdLib SFX.
+				_parserSfxAccolade = new SfxParser_Accolade_AdLib();
+			break;
+		case MT_MT32:
+		case MT_GM:
+			_driverMsMusic = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
+			if (_vm->getGameType() == GType_WW) {
+				// WORKAROUND See above.
+				int16 defaultInstruments[16];
+				Common::fill(defaultInstruments, defaultInstruments + ARRAYSIZE(defaultInstruments), -1);
+				Common::copy(MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS, MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS + ARRAYSIZE(MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS), defaultInstruments + 1);
+				_driverMsMusic->setControllerDefaults(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM, defaultInstruments);
+			}
+			if (usesMidiSfx) {
+				if (ConfMan.getBool("multi_midi")) {
+					// Use AdLib SFX with MT-32 music.
+					_driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
+					_parserSfxAccolade = new SfxParser_Accolade_AdLib();
 				} else {
-					// Windows and Acorn CD
-					// TODO Acorn does not use an OPL chip, but we don't have
-					// an implementation for the Acorn audio. It has the same
-					// music data has the DOS CD version, but it does not have
-					// the MT_FM.IBK instrument bank, so we use the standard
-					// OPL MIDI driver.
-					_driverMsMusic = new MidiDriver_Simon1_AdLib_Windows();
+					// Use MT-32 SFX.
+					_driverMsSfx = _driverMsMusic;
+					_parserSfxAccolade = new SfxParser_Accolade_MT32();
 				}
-				break;
-			case MT_MT32:
-			case MT_GM:
-				_driverMsMusic = new MidiDriver_MT32GM(_dataType);
-				if (_vm->getPlatform() == Common::kPlatformDOS && !(_vm->getFeatures() & GF_TALKIE) &&
-						ConfMan.getBool("multi_midi")) {
-					// DOS floppy version uses AdLib MIDI SFX if mixed MIDI
-					// mode is active.
-					_driverMsSfx = createMidiDriverSimon1AdLib("MT_FM.IBK");
+			}
+			break;
+		default:
+			_driverMsMusic = new MidiDriver_NULL_Multisource();
+			break;
+		}
+		_driver = _driverMsMusic;
+
+		// These games use the MUS MIDI format used by several Accolade games.
+		// The Elvira 2 / Waxworks version of the AdLib driver has a mechanism
+		// that will only play the highest note of a chord; the Elvira 1
+		// version does not have this.
+		_parserMusic = MidiParser_createS1D(0, _vm->getGameType() != GType_ELVIRA1 && _deviceType == MT_ADLIB);
+	} else if (_vm->getGameType() == GType_ELVIRA1 && _vm->getPlatform() == Common::kPlatformPC98) {
+		// Elvira 1 PC-98
+
+		// This version has its own drivers. It uses the same MUS format as the
+		// DOS version.
+		_driver = MidiDriverPC98_create(dev);
+		_parserMusic = MidiParser_createS1D(0);
+	} else if (_vm->getGameType() == GType_SIMON1) {
+		// Simon 1 (except the DOS floppy demo)
+
+		// The DOS versions have their own drivers. The Windows version uses
+		// the standard Windows drivers.
+		switch (_deviceType) {
+		case MT_ADLIB:
+			if (_vm->getPlatform() == Common::kPlatformDOS) {
+				// The DOS version AdLib driver uses an instrument bank file.
+				_driverMsMusic = createMidiDriverSimon1AdLib("MT_FM.IBK", oplType);
+				if (!(_vm->getFeatures() & GF_TALKIE)) {
+					// The DOS floppy version has AdLib MIDI SFX.
+					_driverMsSfx = _driverMsMusic;
 				}
-				break;
-			default:
-				_driverMsMusic = new MidiDriver_NULL_Multisource();
-				break;
+			} else {
+				// Windows and Acorn CD
+				// TODO Acorn does not use an OPL chip, but ScummVM doesn't
+				// have an implementation of the Acorn audio. It has the same
+				// music data as the DOS CD version, but it does not have
+				// the MT_FM.IBK instrument bank, so the standard AdLib MIDI
+				// driver is used.
+				_driverMsMusic = new MidiDriver_ADLIB_Multisource(oplType);
+				// WORKAROUND These versions have the same music data as the
+				// DOS versions, which target MT-32, despite Windows using GM
+				// as its MIDI format. To fix this, the MT-32 instruments are
+				// remapped to corresponding GM instruments.
+				_driverMsMusic->setInstrumentRemapping(MidiDriver::_mt32ToGm);
 			}
-			_driver = _driverMsMusic;
 
 			// WORKAROUND The Simon 1 MIDI data does not always set a value for
-			// program, volume or panning before it starts playing notes on a
-			// MIDI channel. This can cause settings for these parameters from
-			// a previous track to be used unintentionally. To correct this,
-			// default values for these parameters are set when a new track is
+			// program before it starts playing notes on a MIDI channel. This
+			// can cause instruments from a previous track to be used
+			// unintentionally.
+			// To correct this, default instruments are set when a new track is
 			// started.
 			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM);
-			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_VOLUME);
-			_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PANNING);
-			_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
-
-			// Create the MIDI parser(s) for the format used by the platform.
-			if (_vm->getPlatform() == Common::kPlatformWindows) {
-				// Windows version uses a specific SMF variant.
-				_parserMusic = new MidiParser_SimonWin(0, true);
-			} else {
-				// DOS floppy & CD and Acorn CD use GMF (also an SMF variant).
-				_parserMusic = new MidiParser_GMF(0);
-
-				if (_driverMsSfx) {
-					// DOS floppy needs a second parser for AdLib SFX.
-					_parserSfx = new MidiParser_GMF(1);
-					_parserMusic->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
-					_parserSfx->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
-					_parserSfx->property(MidiParser::mpDisableAutoStartPlayback, true);
-				}
-			}
-			_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
-
-			// Open the MIDI driver(s).
-			int returnCode = _driverMsMusic->open();
-			if (returnCode != 0)
-				error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
-			_driverMsMusic->syncSoundSettings();
-			if (_driverMsSfx && _driverMsMusic != _driverMsSfx) {
-				_driverMsSfx->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
-				returnCode = _driverMsSfx->open();
-				if (returnCode != 0)
-					error("MidiPlayer::open - Failed to open MIDI SFX driver - error code %d.", returnCode);
-				_driverMsSfx->syncSoundSettings();
-			}
 
-			// Connect the driver(s) and parser(s).
-			_parserMusic->setMidiDriver(_driverMsMusic);
-			_parserMusic->setTimerRate(_driverMsMusic->getBaseTempo());
-			if (_parserSfx) {
-				_parserSfx->setMidiDriver(_driverMsSfx);
-				_parserSfx->setTimerRate(_driverMsSfx->getBaseTempo());
+			break;
+		case MT_MT32:
+		case MT_GM:
+			_driverMsMusic = new MidiDriver_MT32GM(_dataType);
+
+			// WORKAROUND See above.
+			int16 defaultInstruments[16];
+			Common::fill(defaultInstruments, defaultInstruments + ARRAYSIZE(defaultInstruments), -1);
+			Common::copy(MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS, MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS + ARRAYSIZE(MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS), defaultInstruments + 1);
+			_driverMsMusic->setControllerDefaults(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM, defaultInstruments);
+
+			if (_vm->getPlatform() == Common::kPlatformDOS && !(_vm->getFeatures() & GF_TALKIE) &&
+					ConfMan.getBool("multi_midi")) {
+				// The DOS floppy version can use AdLib MIDI SFX with MT-32
+				// music.
+				_driverMsSfx = createMidiDriverSimon1AdLib("MT_FM.IBK", oplType);
 			}
 
-			if (_driverMsSfx == _driverMsMusic) {
-				// Use MidiPlayer::onTimer to trigger both parsers from the
-				// single driver (it can only have one timer callback).
-				_driverMsMusic->setTimerCallback(this, &onTimer);
-			} else {
-				// Trigger each parser callback from the corresponding driver.
-				_driverMsMusic->setTimerCallback(_parserMusic, &_parserMusic->timerCallback);
-				if (_driverMsSfx) {
-					_driverMsSfx->setTimerCallback(_parserSfx, &_parserSfx->timerCallback);
-				}
-			}
+			break;
+		default:
+			_driverMsMusic = new MidiDriver_NULL_Multisource();
+			break;
+		}
+		_driver = _driverMsMusic;
 
-			return 0;
+		// WORKAROUND The Simon 1 MIDI data does not always set a value for
+		// volume or panning before it starts playing notes on a MIDI channel.
+		// This can cause settings for these parameters from a previous track
+		// to be used unintentionally. To correct this, default values for
+		// these parameters are set when a new track is started.
+		_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_VOLUME);
+		_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PANNING);
+
+		// The Windows version uses music tempos which are noticably faster
+		// than those used by the DOS versions. The MIDI parsers can be
+		// configured to use one of both tempos. The DOS tempos will be used
+		// for the DOS versions or if the user has selected the "Use DOS music
+		// tempos" option. Otherwise the Windows tempos will be used.
+		bool useDosTempos = ConfMan.hasKey("dos_music_tempos") ? ConfMan.getBool("dos_music_tempos") : _vm->getPlatform() == Common::kPlatformDOS;
+
+		// Create the MIDI parser(s) for the format used by the platform.
+		if (_vm->getPlatform() == Common::kPlatformWindows) {
+			// The Windows version uses a slightly different SMF variant.
+			_parserMusic = new MidiParser_SimonWin(0, useDosTempos);
+		} else {
+			// DOS floppy & CD and Acorn CD use GMF (also an SMF variant).
+			_parserMusic = new MidiParser_GMF(0, useDosTempos);
+
+			if (_driverMsSfx) {
+				// DOS floppy needs a second GMF parser for AdLib SFX.
+				_parserSfx = new MidiParser_GMF(1, true);
+				_parserMusic->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
+				_parserSfx->property(MidiParser::mpDisableAllNotesOffMidiEvents, true);
+				_parserSfx->property(MidiParser::mpDisableAutoStartPlayback, true);
+			}
 		}
 	} else if (_vm->getGameType() == GType_SIMON2) {
-		// Check the type of device that the user has configured.
-		MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
-		_deviceType = MidiDriver::getMusicType(dev);
-		if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
-			_deviceType = MT_MT32;
-
-		// DOS version has both MT-32 and GM tracks; Windows is GM only.
-		_dataType = (_vm->getPlatform() == Common::kPlatformDOS && _deviceType == MT_MT32) ? MT_MT32 : MT_GM;
+		// Simon 2
 
+		// The DOS version uses MIDPAK, which is similar to Miles AIL v2.
+		// The Windows version uses the standard Windows drivers.
 		switch (_deviceType) {
 		case MT_ADLIB:
 			if (_vm->getPlatform() == Common::kPlatformDOS) {
+				// DOS
 				if (Common::File::exists("MIDPAK.AD")) {
 					// if there is a file called MIDPAK.AD, use it directly
 					warning("MidiPlayer::open - SIMON 2: using MIDPAK.AD");
@@ -344,6 +376,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 		case MT_MT32:
 		case MT_GM:
 			if (_vm->getPlatform() == Common::kPlatformDOS) {
+				// DOS
 				_driverMsMusic = Audio::MidiDriver_Miles_MIDI_create(_dataType, "");
 			} else {
 				// Windows
@@ -355,353 +388,70 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 			break;
 		}
 		_driver = _driverMsMusic;
-		_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
 
 		// Create the MIDI parser(s) for the format used by the platform.
 		if (_vm->getPlatform() == Common::kPlatformDOS) {
+			// DOS uses Miles XMIDI.
 			_parserMusic = MidiParser::createParser_XMIDI(MidiParser::defaultXMidiCallback, 0, 0);
 		} else {
-			// Windows version uses a specific SMF variant.
+			// Windows version uses an SMF variant (same as Simon 1 Windows).
 			_parserMusic = new MidiParser_SimonWin(0);
 		}
-		_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
-
-		// Open the MIDI driver.
-		int returnCode = _driverMsMusic->open();
-		if (returnCode != 0)
-			error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
-		_driverMsMusic->syncSoundSettings();
-
-		// Connect the driver and parser.
-		_parserMusic->setMidiDriver(_driverMsMusic);
-		_parserMusic->setTimerRate(_driverMsMusic->getBaseTempo());
-		_driverMsMusic->setTimerCallback(this, &onTimer);
-
-		return 0;
 	}
 
-	// TODO Old code below is no longer used for Simon 1 and 2.
-	_deviceType = MT_INVALID;
-
-	switch (gameType) {
-	case GType_ELVIRA1:
-		if (platform == Common::kPlatformPC98) {
-			_musicMode = kMusicModePC98;
-			devFlags = (devFlags & ~MDT_ADLIB) | MDT_PC98;
-		} else {
-			_musicMode = kMusicModeAccolade;
-			accoladeDriverFilename = "INSTR.DAT";
-		}
-		break;
-	case GType_ELVIRA2:
-	case GType_WW:
-		// Attention: Elvira 2 shipped with INSTR.DAT and MUSIC.DRV
-		// MUSIC.DRV is the correct one. INSTR.DAT seems to be a left-over
-		_musicMode = kMusicModeAccolade;
-		accoladeDriverFilename = "MUSIC.DRV";
-		break;
-	case GType_SIMON1:
-		if (isDemo) {
-			_musicMode = kMusicModeAccolade;
-			accoladeDriverFilename = "MUSIC.DRV";
-		} else if (Common::File::exists("MT_FM.IBK")) {
-			_musicMode = kMusicModeSimon1;
-		}
-		break;
-	case GType_SIMON2:
-		//_musicMode = kMusicModeMilesAudio;
-		// currently disabled, because there are a few issues
-		// MT32 seems to work fine now, AdLib seems to use bad instruments and is also outputting music on
-		// the right speaker only. The original driver did initialize the panning to 0 and the Simon2 XMIDI
-		// tracks don't set panning at all. We can reset panning to be centered, which would solve this
-		// issue, but we still don't know who's setting it in the original interpreter.
-		break;
-	default:
-		break;
-	}
+	// Set common properties for the drivers and music parser.
+	if (_driverMsMusic)
+		_driverMsMusic->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+	if (_driverMsSfx && _driverMsSfx != _driverMsMusic)
+		_driverMsSfx->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
 
-	MidiDriver::DeviceHandle dev;
-	int ret = 0;
+	_parserMusic->property(MidiParser::mpDisableAutoStartPlayback, true);
 
-	if (_musicMode == kMusicModePC98) {
-		dev = MidiDriver::detectDevice(devFlags);
-		_driver = MidiDriverPC98_create(dev);
-		if (_driver && !_driver->open()) {
-			_driver->setTimerCallback(this, &onTimer);
-			return 0;
-		}
-	} else if (_musicMode != kMusicModeDisabled) {
-		dev = MidiDriver::detectDevice(devFlags);
-		_deviceType = MidiDriver::getMusicType(dev);
-
-		switch (_deviceType) {
-		case MT_ADLIB:
-		case MT_MT32:
-			break;
-		case MT_GM:
-			if (!ConfMan.getBool("native_mt32")) {
-				// Not a real MT32 / no MUNT
-				::GUI::MessageDialog dialog(_(
-											"You appear to be using a General MIDI device,\n"
-											"but your game only supports Roland MT32 MIDI.\n"
-											"We try to map the Roland MT32 instruments to\n"
-											"General MIDI ones. It is still possible that\n"
-											"some tracks sound incorrect."));
-				dialog.runModal();
-			}
-			// Switch to MT32 driver in any case
-			_deviceType = MT_MT32;
-			break;
-		default:
-			_musicMode = kMusicModeDisabled;
-			break;
-		}
+	// Open the MIDI driver(s).
+	int returnCode = _driver->open();
+	if (returnCode != 0)
+		error("MidiPlayer::open - Failed to open MIDI music driver - error code %d.", returnCode);
+	if (_driverMsSfx && _driverMsSfx != _driverMsMusic) {
+		returnCode = _driverMsSfx->open();
+		if (returnCode != 0)
+			error("MidiPlayer::open - Failed to open MIDI SFX driver - error code %d.", returnCode);
 	}
 
-	switch (_musicMode) {
-	case kMusicModeAccolade: {
-		// Setup midi driver
-		switch (_deviceType) {
-		case MT_ADLIB:
-			_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
-			break;
-		case MT_MT32:
-			_driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
-			break;
-		default:
-			assert(0);
-			break;
-		}
-		if (!_driver)
-			return 255;
-
-		ret = _driver->open();
-		if (ret == 0) {
-			// Reset is done inside our MIDI driver
-			_driver->setTimerCallback(this, &onTimer);
-		}
+	syncSoundSettings();
 
-		//setTimerRate(_driver->getBaseTempo());
-		return 0;
+	// Connect the driver(s) and the parser(s).
+	_parserMusic->setMidiDriver(_driver);
+	_parserMusic->setTimerRate(_driver->getBaseTempo());
+	if (_parserSfx) {
+		_parserSfx->setMidiDriver(_driverMsSfx);
+	} else if (_parserSfxAccolade) {
+		_parserSfxAccolade->setMidiDriver(_driverMsSfx);
 	}
 
-	case kMusicModeMilesAudio: {
-		switch (_deviceType) {
-		case MT_ADLIB: {
-			Common::File instrumentDataFile;
-			if (instrumentDataFile.exists("MIDPAK.AD")) {
-				// if there is a file called MIDPAK.AD, use it directly
-				warning("SIMON 2: using MIDPAK.AD");
-				_driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", "MIDPAK.AD");
-			} else {
-				// if there is no file called MIDPAK.AD, try to extract it from the file SETUP.SHR
-				// if we didn't do this, the user would be forced to "install" the game instead of simply
-				// copying all files from CD-ROM.
-				Common::SeekableReadStream *midpakAdLibStream;
-				midpakAdLibStream = simon2SetupExtractFile("MIDPAK.AD");
-				if (!midpakAdLibStream)
-					error("MidiPlayer: could not extract MIDPAK.AD from SETUP.SHR");
-
-				// Pass this extracted data to the driver
-				warning("SIMON 2: using MIDPAK.AD extracted from SETUP.SHR");
-				_driver = Audio::MidiDriver_Miles_AdLib_create("", "", midpakAdLibStream);
-				delete midpakAdLibStream;
-			}
-			// TODO: not sure what's going wrong with AdLib
-			// it doesn't seem to matter if we use the regular XMIDI tracks or the 2nd set meant for MT32
-			break;
+	if ((_parserSfx || _parserSfxAccolade) && _driverMsSfx == _driver) {
+		// Use MidiPlayer::onTimer to trigger both parsers from the
+		// single driver (it can only have one timer callback).
+		_driver->setTimerCallback(this, &onTimer);
+		if (_parserSfx) {
+			_parserSfx->setTimerRate(_driver->getBaseTempo());
+		} else if (_parserSfxAccolade) {
+			_parserSfxAccolade->setTimerRate(_driver->getBaseTempo());
 		}
-		case MT_MT32:
-			_driver = Audio::MidiDriver_Miles_MT32_create("");
-			_nativeMT32 = true; // use 2nd set of XMIDI tracks
-			break;
-		case MT_GM:
-			if (ConfMan.getBool("native_mt32")) {
-				_driver = Audio::MidiDriver_Miles_MT32_create("");
-				_nativeMT32 = true; // use 2nd set of XMIDI tracks
-			}
-			break;
-
-		default:
-			break;
-		}
-		if (!_driver)
-			return 255;
-
-		ret = _driver->open();
-		if (ret == 0) {
-			// Reset is done inside our MIDI driver
-			_driver->setTimerCallback(this, &onTimer);
-		}
-		return 0;
-	}
-
-	case kMusicModeSimon1: {
-		// This only handles the original AdLib driver of Simon1.
-		if (_deviceType == MT_ADLIB) {
-			_map_mt32_to_gm = false;
-			_nativeMT32 = false;
-
-			_driver = createMidiDriverSimon1AdLib("MT_FM.IBK");
-			if (_driver && _driver->open() == 0) {
-				_driver->setTimerCallback(this, &onTimer);
-				// Like the original, we enable the rhythm support by default.
-				_driver->send(0xB0, 0x67, 0x01);
-				return 0;
-			}
-
-			delete _driver;
-			_driver = nullptr;
+	} else {
+		// Connect each parser to its own driver.
+		_driver->setTimerCallback(_parserMusic, &_parserMusic->timerCallback);
+		if (_parserSfx) {
+			_driverMsSfx->setTimerCallback(_parserSfx, &_parserSfx->timerCallback);
+			_parserSfx->setTimerRate(_driverMsSfx->getBaseTempo());
+		} else if (_parserSfxAccolade) {
+			_driverMsSfx->setTimerCallback(_parserSfxAccolade, &_parserSfxAccolade->timerCallback);
+			_parserSfxAccolade->setTimerRate(_driverMsSfx->getBaseTempo());
 		}
-
-		_musicMode = kMusicModeDisabled;
 	}
 
-	default:
-		break;
-	}
-
-	dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
-	_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
-
-	_driver = MidiDriver::createMidi(dev);
-	if (!_driver)
-		return 255;
-
-	if (_nativeMT32)
-		_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
-
-	_map_mt32_to_gm = (gameType != GType_SIMON2 && !_nativeMT32);
-
-	ret = _driver->open();
-	if (ret)
-		return ret;
-	_driver->setTimerCallback(this, &onTimer);
-
-	if (_nativeMT32)
-		_driver->sendMT32Reset();
-	else
-		_driver->sendGMReset();
-
 	return 0;
 }
 
-void MidiPlayer::send(uint32 b) {
-	if (!_current)
-		return;
-
-	if (_musicMode != kMusicModeDisabled) {
-		// Handle volume control for Simon1 output.
-		if (_musicMode == kMusicModeSimon1) {
-			// The driver does not support any volume control, thus we simply
-			// scale the velocities on note on for now.
-			// TODO: We should probably handle this at output level at some
-			// point. Then we can allow volume changes to affect already
-			// playing notes too. For now this simple change allows us to
-			// have some simple volume control though.
-			if ((b & 0xF0) == 0x90) {
-				byte volume = (b >> 16) & 0x7F;
-
-				if (_current == &_sfx) {
-					volume = volume * _sfxVolume / 255;
-				} else if (_current == &_music) {
-					volume = volume * _musicVolume / 255;
-				}
-
-				b = (b & 0xFF00FFFF) | (volume << 16);
-			}
-		}
-
-		// Send directly to Accolade/Miles/Simon1 Audio driver
-		_driver->send(b);
-		return;
-	}
-
-	byte channel = (byte)(b & 0x0F);
-	if ((b & 0xFFF0) == 0x07B0) {
-		// Adjust volume changes by master music and master sfx volume.
-		byte volume = (byte)((b >> 16) & 0x7F);
-		_current->volume[channel] = volume;
-		if (_current == &_sfx)
-			volume = volume * _sfxVolume / 255;
-		else if (_current == &_music)
-			volume = volume * _musicVolume / 255;
-		b = (b & 0xFF00FFFF) | (volume << 16);
-	} else if ((b & 0xF0) == 0xC0 && _map_mt32_to_gm) {
-		b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8);
-	} else if ((b & 0xFFF0) == 0x007BB0) {
-		// Only respond to an All Notes Off if this channel
-		// has already been allocated.
-		if (!_current->channel[b & 0x0F])
-			return;
-	} else if ((b & 0xFFF0) == 0x79B0) {
-		// "Reset All Controllers". There seems to be some confusion
-		// about what this message should do to the volume controller.
-		// See http://www.midi.org/about-midi/rp15.shtml for more
-		// information.
-		//
-		// If I understand it correctly, the current standard indicates
-		// that the volume should be reset, but the next revision will
-		// exclude it. On my system, both ALSA and FluidSynth seem to
-		// reset it, while AdLib does not. Let's follow the majority.
-
-		_current->volume[channel] = 127;
-	}
-
-	// Allocate channels if needed
-	if (!_current->channel[channel])
-		_current->channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
-
-	if (_current->channel[channel]) {
-		if (channel == 9) {
-			if (_current == &_sfx)
-				_current->channel[9]->volume(_current->volume[9] * _sfxVolume / 255);
-			else if (_current == &_music)
-				_current->channel[9]->volume(_current->volume[9] * _musicVolume / 255);
-		}
-		_current->channel[channel]->send(b);
-		if ((b & 0xFFF0) == 0x79B0) {
-			// We have received a "Reset All Controllers" message
-			// and passed it on to the MIDI driver. This may or may
-			// not have affected the volume controller. To ensure
-			// consistent behavior, explicitly set the volume to
-			// what we think it should be.
-
-			if (_current == &_sfx)
-				_current->channel[channel]->volume(_current->volume[channel] * _sfxVolume / 255);
-			else if (_current == &_music)
-				_current->channel[channel]->volume(_current->volume[channel] * _musicVolume / 255);
-		}
-	}
-}
-
-void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) {
-	// Only thing we care about is End of Track.
-	if (!_current || type != 0x2F) {
-		return;
-	} else if (_current == &_sfx) {
-		clearConstructs(_sfx);
-	} else if (_loopTrack) {
-		_current->parser->jumpToTick(0);
-	} else if (_queuedTrack != 255) {
-		_currentTrack = 255;
-		byte destination = _queuedTrack;
-		_queuedTrack = 255;
-		_loopTrack = _loopQueuedTrack;
-		_loopQueuedTrack = false;
-
-		// Remember, we're still inside the locked mutex.
-		// Have to unlock it before calling jump()
-		// (which locks it itself), and then relock it
-		// upon returning.
-		_mutex.unlock();
-		startTrack(destination);
-		_mutex.lock();
-	} else {
-		stop();
-	}
-}
-
 void MidiPlayer::onTimer(void *data) {
 	MidiPlayer *p = (MidiPlayer *)data;
 	Common::StackLock lock(p->_mutex);
@@ -720,25 +470,16 @@ void MidiPlayer::onTimer(void *data) {
 	if (p->_parserSfx)
 		p->_parserSfx->onTimer();
 
-	if (!p->_paused) {
-		if (p->_music.parser && p->_currentTrack != 255) {
-			p->_current = &p->_music;
-			p->_music.parser->onTimer();
-		}
-	}
-	if (p->_sfx.parser) {
-		p->_current = &p->_sfx;
-		p->_sfx.parser->onTimer();
-	}
-	p->_current = nullptr;
+	if (p->_parserSfxAccolade)
+		p->_parserSfxAccolade->onTimer();
 }
 
 bool MidiPlayer::usesMT32Data() const {
 	return _dataType == MT_MT32;
 }
 
-bool MidiPlayer::hasAdLibSfx() const {
-	return _parserSfx != nullptr;
+bool MidiPlayer::hasMidiSfx() const {
+	return _parserSfx != nullptr || _parserSfxAccolade != nullptr;
 }
 
 bool MidiPlayer::isPlaying(bool checkQueued) {
@@ -747,80 +488,35 @@ bool MidiPlayer::isPlaying(bool checkQueued) {
 	return _parserMusic->isPlaying() && (!checkQueued || _queuedTrack != 255);
 }
 
-void MidiPlayer::startTrack(int track) {
+void MidiPlayer::stop(bool sfx) {
 	Common::StackLock lock(_mutex);
 
-	if (track == _currentTrack)
-		return;
-
-	if (_music.num_songs > 0) {
-		if (track >= _music.num_songs)
-			return;
+if (!sfx) {
+		// Clear the queued track to prevent it from starting when the current
+		// track is stopped.
+		_queuedTrack = 255;
 
-		if (_music.parser) {
-			_current = &_music;
-			delete _music.parser;
-			_current = nullptr;
-			_music.parser = nullptr;
+		if (_parserMusic) {
+			_parserMusic->stopPlaying();
+			if (_driverMsMusic)
+				_driverMsMusic->deinitSource(0);
 		}
-
-		MidiParser *parser = MidiParser::createParser_SMF();
-		parser->property (MidiParser::mpMalformedPitchBends, 1);
-		parser->setMidiDriver(this);
-		parser->setTimerRate(_driver->getBaseTempo());
-		if (!parser->loadMusic(_music.songs[track], _music.song_sizes[track])) {
-			warning("Error reading track %d", track);
-			delete parser;
-			parser = nullptr;
+	} else {
+		if (_parserSfx) {
+			_parserSfx->stopPlaying();
+			if (_driverMsSfx)
+				_driverMsSfx->deinitSource(1);
 		}
-
-		_currentTrack = (byte)track;
-		_music.parser = parser; // That plugs the power cord into the wall
-	} else if (_music.parser) {
-		if (!_music.parser->setTrack(track)) {
-			return;
+		if (_parserSfxAccolade) {
+			_parserSfxAccolade->stopAll();
 		}
-		_currentTrack = (byte)track;
-		_current = &_music;
-		_music.parser->jumpToTick(0);
-		_current = nullptr;
-	}
-}
-
-void MidiPlayer::stop() {
-	Common::StackLock lock(_mutex);
-
-	// Clear the queued track to prevent it from starting when the current
-	// track is stopped.
-	_queuedTrack = 255;
-
-	if (_parserMusic) {
-		_parserMusic->stopPlaying();
-		if (_driverMsMusic)
-			_driverMsMusic->deinitSource(0);
-	}
-
-	if (_music.parser) {
-		_current = &_music;
-		_music.parser->jumpToTick(0);
-	}
-	_current = nullptr;
-	_currentTrack = 255;
-}
-
-void MidiPlayer::stopSfx() {
-	Common::StackLock lock(_mutex);
-
-	if (_parserSfx) {
-		_parserSfx->stopPlaying();
-		if (_driverMsSfx)
-			_driverMsSfx->deinitSource(1);
 	}
 }
 
 void MidiPlayer::pause(bool b) {
 	if (_paused == b || !_driver)
 		return;
+
 	_paused = b;
 
 	Common::StackLock lock(_mutex);
@@ -836,20 +532,8 @@ void MidiPlayer::pause(bool b) {
 		if (_parserSfx)
 			_parserSfx->resumePlaying();
 	}
-
-	// if using the driver Accolade_AdLib call setVolume() to turn off\on the volume on all channels
-	if (_deviceType == MT_ADLIB && _musicMode == kMusicModeAccolade) {
-		static_cast <MidiDriver_Accolade_AdLib*> (_driver)->setVolume(_paused ? 0 : ConfMan.getInt("music_volume"));
-	} else if (_musicMode == kMusicModePC98) {
-		_driver->property(0x30, _paused ? 1 : 0);
-	}
-
-	for (int i = 0; i < 16; ++i) {
-		if (_music.channel[i])
-			_music.channel[i]->volume(_paused ? 0 : (_music.volume[i] * _musicVolume / 255));
-		if (_sfx.channel[i])
-			_sfx.channel[i]->volume(_paused ? 0 : (_sfx.volume[i] * _sfxVolume / 255));
-	}
+	if (_parserSfxAccolade)
+		_parserSfxAccolade->pauseAll(_paused);
 }
 
 void MidiPlayer::fadeOut() {
@@ -862,59 +546,30 @@ void MidiPlayer::fadeOut() {
 	_driverMsMusic->startFade(0, 1000, 0);
 }
 
-void MidiPlayer::setVolume(int musicVol, int sfxVol) {
-	musicVol = CLIP(musicVol, 0, 255);
-	sfxVol   = CLIP(sfxVol,   0, 255);
-
-	if (_musicVolume == musicVol && _sfxVolume == sfxVol)
-		return;
-
-	_musicVolume = musicVol;
-	_sfxVolume   = sfxVol;
-
-	if (_musicMode == kMusicModePC98) {
-		_driver->property(0x10, _musicVolume);
-		_driver->property(0x20, _sfxVolume);
-	} else if (_musicMode == kMusicModeAccolade && _deviceType == MT_ADLIB) {
-		static_cast <MidiDriver_Accolade_AdLib*> (_driver)->setVolume(_musicVolume);
-	}
-
-	// Now tell all the channels this.
-	Common::StackLock lock(_mutex);
-	if (_driver && !_paused) {
-		for (int i = 0; i < 16; ++i) {
-			if (_music.channel[i])
-				_music.channel[i]->volume(_music.volume[i] * _musicVolume / 255);
-			if (_sfx.channel[i])
-				_sfx.channel[i]->volume(_sfx.volume[i] * _sfxVolume / 255);
-		}
-	}
-}
-
 void MidiPlayer::syncSoundSettings() {
 	if (_driverMsMusic)
 		_driverMsMusic->syncSoundSettings();
 	if (_driverMsSfx)
 		_driverMsSfx->syncSoundSettings();
 
-	// Sync code for non-multisource drivers.
-	// TODO Remove when no longer necessary
-	bool mute = false;
-	if (ConfMan.hasKey("mute"))
-		mute = ConfMan.getBool("mute");
+	if (_pc98) {
+		// Sync code for non-multisource drivers.
+		bool mute = false;
+		if (ConfMan.hasKey("mute"))
+			mute = ConfMan.getBool("mute");
 
-	// Sync the engine with the config manager
-	int soundVolumeMusic = ConfMan.getInt("music_volume");
-	int soundVolumeSFX = ConfMan.getInt("sfx_volume");
+		// Sync the engine with the config manager
+		int soundVolumeMusic = ConfMan.getInt("music_volume");
+		int soundVolumeSFX = ConfMan.getInt("sfx_volume");
 
-	setVolume(mute ? 0 : soundVolumeMusic, mute ? 0 : soundVolumeSFX);
+		_driver->property(0x10, mute ? 0 : soundVolumeMusic);
+		_driver->property(0x20, mute ? 0 : soundVolumeSFX);
+	}
 }
 
 void MidiPlayer::setLoop(bool loop) {
 	Common::StackLock lock(_mutex);
 
-	_loopTrack = loop;
-
 	if (_parserMusic)
 		_parserMusic->property(MidiParser::mpAutoLoop, loop);
 }
@@ -938,61 +593,29 @@ void MidiPlayer::queueTrack(int track, bool loop) {
 	}
 }
 
-void MidiPlayer::clearConstructs() {
-	clearConstructs(_music);
-	clearConstructs(_sfx);
-}
-
-void MidiPlayer::clearConstructs(MusicInfo &info) {
-	int i;
-	if (info.num_songs > 0) {
-		for (i = 0; i < info.num_songs; ++i)
-			free(info.songs[i]);
-		info.num_songs = 0;
-	}
-
-	free(info.data);
-	info.data = nullptr;
-
-	delete info.parser;
-	info.parser = nullptr;
+void MidiPlayer::load(Common::SeekableReadStream *in, int32 size, bool sfx) {
+	Common::StackLock lock(_mutex);
 
-	if (_driver) {
-		for (i = 0; i < 16; ++i) {
-			if (info.channel[i]) {
-				info.channel[i]->allNotesOff();
-				info.channel[i]->release();
-			}
-		}
+	if (sfx && _parserSfxAccolade) {
+		_parserSfxAccolade->load(in, size);
+		return;
 	}
-	info.clear();
-}
-
-void MidiPlayer::resetVolumeTable() {
-	int i;
-	for (i = 0; i < 16; ++i) {
-		_music.volume[i] = _sfx.volume[i] = 127;
-		if (_driver)
-			_driver->send(((_musicVolume >> 1) << 16) | 0x7B0 | i);
-	}
-}
-
-void MidiPlayer::loadMusic(Common::SeekableReadStream *in, int32 size, bool sfx) {
-	Common::StackLock lock(_mutex);
 
 	MidiParser *parser = sfx ? _parserSfx : _parserMusic;
+	if (!parser)
+		return;
 
 	if (size < 0) {
 		// Use the parser to determine the size of the MIDI data.
 		int64 startPos = in->pos();
 		size = parser->determineDataSize(in);
 		if (size < 0) {
-			warning("MidiPlayer::loadMusic - Could not determine size of music data");
+			warning("MidiPlayer::load - Could not determine size of music data");
 			return;
 		}
 		// determineDataSize might move the stream position, so return it to
 		// the original position.
-		in->seek(startPos, SEEK_SET);
+		in->seek(startPos);
 	}
 
 	parser->unloadMusic();
@@ -1013,7 +636,14 @@ void MidiPlayer::loadMusic(Common::SeekableReadStream *in, int32 size, bool sfx)
 void MidiPlayer::play(int track, bool sfx, bool sfxUsesRhythm, bool queued) {
 	Common::StackLock lock(_mutex);
 
+	if (sfx && _parserSfxAccolade) {
+		_parserSfxAccolade->play(track);
+		return;
+	}
+	
 	MidiParser *parser = sfx ? _parserSfx : _parserMusic;
+	if (!parser)
+		return;
 
 	if (parser->setTrack(track)) {
 		if (sfx && sfxUsesRhythm) {
@@ -1041,232 +671,6 @@ void MidiPlayer::play(int track, bool sfx, bool sfxUsesRhythm, bool queued) {
 	}
 }
 
-// TODO Remove dead code
-
-static const int simon1_gmf_size[] = {
-	8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138,
-	6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717,
-	9444, 5800, 1381, 5660, 6684, 2456, 4744, 2455, 1177, 1232,
-	17256, 5103, 8794, 4884, 16
-};
-
-void MidiPlayer::loadSMF(Common::SeekableReadStream *in, int song, bool sfx) {
-	Common::StackLock lock(_mutex);
-
-	MusicInfo *p = sfx ? &_sfx : &_music;
-	clearConstructs(*p);
-
-	uint32 startpos = in->pos();
-	byte header[4];
-	in->read(header, 4);
-	bool isGMF = !memcmp(header, "GMF\x1", 4);
-	in->seek(startpos, SEEK_SET);
-
-	uint32 size = in->size() - in->pos();
-	if (isGMF) {
-		if (sfx) {
-			// Multiple GMF resources are stored in the SFX files,
-			// but each one is referenced by a pointer at the
-			// beginning of the file. Those pointers can be used
-			// to determine file size.
-			in->seek(0, SEEK_SET);
-			uint16 value = in->readUint16LE() >> 2; // Number of resources
-			if (song != value - 1) {
-				in->seek(song * 2 + 2, SEEK_SET);
-				value = in->readUint16LE();
-				size = value - startpos;
-			}
-			in->seek(startpos, SEEK_SET);
-		} else if (size >= 64000) {
-			// For GMF resources not in separate
-			// files, we're going to have to use
-			// hardcoded size tables.
-			size = simon1_gmf_size[song];
-		}
-	}
-
-	// When allocating space, add 4 bytes in case
-	// this is a GMF and we have to tack on our own
-	// End of Track event.
-	p->data = (byte *)calloc(size + 4, 1);
-	in->read(p->data, size);
-
-	uint32 timerRate = _driver->getBaseTempo();
-
-	if (isGMF) {
-		// The GMF header
-		// 3 BYTES: 'GMF'
-		// 1 BYTE : Major version
-		// 1 BYTE : Minor version
-		// 1 BYTE : Ticks (Ranges from 2 - 8, always 2 for SFX)
-		// 1 BYTE : Loop control. 0 = no loop, 1 = loop (Music only)
-		if (!sfx) {
-			// In the original, the ticks value indicated how many
-			// times the music timer was called before it actually
-			// did something. The larger the value the slower the
-			// music.
-			//
-			// We, on the other hand, have a timer rate which is
-			// used to control by how much the music advances on
-			// each onTimer() call. The larger the value, the
-			// faster the music.
-			//
-			// It seems that 4 corresponds to our base tempo, so
-			// this should be the right way to calculate it.
-			timerRate = (4 * _driver->getBaseTempo()) / p->data[5];
-
-			// According to bug #1706 calling setLoop() from
-			// within a lock causes a lockup, though I have no
-			// idea when this actually happens.
-			_loopTrack = (p->data[6] != 0);
-		}
-	}
-
-	MidiParser *parser = MidiParser::createParser_SMF();
-	parser->property(MidiParser::mpMalformedPitchBends, 1);
-	parser->setMidiDriver(this);
-	parser->setTimerRate(timerRate);
-	if (!parser->loadMusic(p->data, size)) {
-		warning("Error reading track");
-		delete parser;
-		parser = nullptr;
-	}
-
-	if (!sfx) {
-		_currentTrack = 255;
-		resetVolumeTable();
-	}
-	p->parser = parser; // That plugs the power cord into the wall
-}
-
-void MidiPlayer::loadMultipleSMF(Common::SeekableReadStream *in, bool sfx) {
-	// This is a special case for Simon 2 Windows.
-	// Instead of having multiple sequences as
-	// separate tracks in a Type 2 file, simon2win
-	// has multiple songs, each of which is a Type 1
-	// file. Thus, preceding the songs is a single
-	// byte specifying how many songs are coming.
-	// We need to load ALL the songs and then
-	// treat them as separate tracks -- for the
-	// purpose of jumps, anyway.
-	Common::StackLock lock(_mutex);
-
-	MusicInfo *p = sfx ? &_sfx : &_music;
-	clearConstructs(*p);
-
-	p->num_songs = in->readByte();
-	if (p->num_songs > 16) {
-		warning("playMultipleSMF: %d is too many songs to keep track of", (int)p->num_songs);
-		return;
-	}
-
-	byte i;
-	for (i = 0; i < p->num_songs; ++i) {
-		byte buf[5];
-		uint32 pos = in->pos();
-
-		// Make sure there's a MThd
-		in->read(buf, 4);
-		if (memcmp(buf, "MThd", 4) != 0) {
-			warning("Expected MThd but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]);
-			return;
-		}
-		in->seek(in->readUint32BE(), SEEK_CUR);
-
-		// Now skip all the MTrk blocks
-		while (true) {
-			in->read(buf, 4);
-			if (memcmp(buf, "MTrk", 4) != 0)
-				break;
-			in->seek(in->readUint32BE(), SEEK_CUR);
-		}
-
-		uint32 pos2 = in->pos() - 4;
-		uint32 size = pos2 - pos;
-		p->songs[i] = (byte *)calloc(size, 1);
-		in->seek(pos, SEEK_SET);
-		in->read(p->songs[i], size);
-		p->song_sizes[i] = size;
-	}
-
-	if (!sfx) {
-		_currentTrack = 255;
-		resetVolumeTable();
-	}
-}
-
-void MidiPlayer::loadXMIDI(Common::SeekableReadStream *in, bool sfx) {
-	Common::StackLock lock(_mutex);
-	MusicInfo *p = sfx ? &_sfx : &_music;
-	clearConstructs(*p);
-
-	char buf[4];
-	uint32 pos = in->pos();
-	uint32 size = 4;
-	in->read(buf, 4);
-	if (!memcmp(buf, "FORM", 4)) {
-		int i;
-		for (i = 0; i < 16; ++i) {
-			if (!memcmp(buf, "CAT ", 4))
-				break;
-			size += 2;
-			memcpy(buf, &buf[2], 2);
-			in->read(&buf[2], 2);
-		}
-		if (memcmp(buf, "CAT ", 4) != 0) {
-			error("Could not find 'CAT ' tag to determine resource size");
-		}
-		size += 4 + in->readUint32BE();
-		in->seek(pos, 0);
-		p->data = (byte *)calloc(size, 1);
-		in->read(p->data, size);
-	} else {
-		error("Expected 'FORM' tag but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]);
-	}
-
-	// In the DOS version of Simon the Sorcerer 2, the music contains lots
-	// of XMIDI callback controller events. As far as we know, they aren't
-	// actually used, so we disable the callback handler explicitly.
-
-	MidiParser *parser = MidiParser::createParser_XMIDI(nullptr);
-	parser->setMidiDriver(this);
-	parser->setTimerRate(_driver->getBaseTempo());
-	if (!parser->loadMusic(p->data, size))
-		error("Error reading track");
-
-	if (!sfx) {
-		_currentTrack = 255;
-		resetVolumeTable();
-	}
-	p->parser = parser; // That plugs the power cord into the wall
-}
-
-void MidiPlayer::loadS1D(Common::SeekableReadStream *in, bool sfx) {
-	Common::StackLock lock(_mutex);
-	MusicInfo *p = sfx ? &_sfx : &_music;
-	clearConstructs(*p);
-
-	uint16 size = in->readUint16LE();
-	if (size != in->size() - 2) {
-		error("Size mismatch in MUS file (%ld versus reported %d)", (long)in->size() - 2, (int)size);
-	}
-
-	p->data = (byte *)calloc(size, 1);
-	in->read(p->data, size);
-
-	MidiParser *parser = MidiParser_createS1D();
-	parser->setMidiDriver(this);
-	parser->setTimerRate(_driver->getBaseTempo());
-	if (!parser->loadMusic(p->data, size))
-		error("Error reading track");
-
-	if (!sfx) {
-		_currentTrack = 255;
-		resetVolumeTable();
-	}
-	p->parser = parser; // That plugs the power cord into the wall
-}
-
 #define MIDI_SETUP_BUNDLE_HEADER_SIZE 56
 #define MIDI_SETUP_BUNDLE_FILEHEADER_SIZE 48
 #define MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE 12
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index 2b4f3b9f69b..a4f4f837327 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -22,6 +22,8 @@
 #ifndef AGOS_MIDI_H
 #define AGOS_MIDI_H
 
+#include "agos/sfxparser_accolade.h"
+
 #include "audio/mididrv.h"
 #include "audio/mididrv_ms.h"
 #include "audio/midiparser.h"
@@ -33,139 +35,95 @@ class File;
 
 namespace AGOS {
 
-enum kMusicMode {
-	kMusicModeDisabled = 0,
-	kMusicModeAccolade = 1,
-	kMusicModeMilesAudio = 2,
-	kMusicModeSimon1 = 3,
-	kMusicModePC98 = 4
-};
-
-struct MusicInfo {
-	MidiParser *parser;
-	byte *data;
-	byte num_songs;           // For Type 1 SMF resources
-	byte *songs[16];          // For Type 1 SMF resources
-	uint32 song_sizes[16];    // For Type 1 SMF resources
-
-	MidiChannel *channel[16]; // Dynamic remapping of channels to resolve conflicts
-	byte volume[16];          // Current channel volume
-
-	MusicInfo() { clear(); }
-	void clear() {
-		parser = 0; data = 0; num_songs = 0;
-		memset(songs, 0, sizeof(songs));
-		memset(song_sizes, 0, sizeof(song_sizes));
-		memset(channel, 0, sizeof(channel));
-	}
-};
-
-class MidiPlayer : public MidiDriver_BASE {
+class MidiPlayer {
 protected:
 	// Instrument map specifically for remapping the instruments of the GM
 	// version of Simon 2 track 10 subtracks 2 and 3 to MT-32.
-	static byte SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[];
+	static const byte SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[];
 
 	AGOSEngine *_vm;
 
 	Common::Mutex _mutex;
+	// Driver used for music. This points to the same object as _driverMsMusic,
+	// except if a PC-98 driver is used (these do not implement the Multisource
+	// interface).
 	MidiDriver *_driver;
 	// Multisource driver used for music. Provides access to multisource
 	// methods without casting. If this is not nullptr, it points to the same
 	// object as _driver.
 	MidiDriver_Multisource *_driverMsMusic;
-	// Multisource driver used for sound effects. Only used for Simon The
-	// Sorcerer DOS floppy AdLib sound effects.
+	// Multisource driver used for sound effects. Only used for Elvira 2,
+	// Waxworks and Simon The Sorcerer DOS floppy AdLib sound effects.
 	// If AdLib is also used for music, this points to the same object as
 	// _driverMsMusic and _driver.
 	MidiDriver_Multisource *_driverMsSfx;
-	bool _map_mt32_to_gm;
-	bool _nativeMT32;
-
-	MusicInfo _music;
-	MusicInfo _sfx;
-	MusicInfo *_current; // Allows us to establish current context for operations.
 
+	// MIDI parser and data used for music.
 	MidiParser *_parserMusic;
 	byte *_musicData;
+	// MIDI parser and data used for SFX (Simon 1 DOS floppy).
 	MidiParser *_parserSfx;
 	byte *_sfxData;
+	// Parser used for SFX (Elvira 2 and Waxworks DOS).
+	SfxParser_Accolade *_parserSfxAccolade;
 
-	// These are maintained for both music and SFX
-	byte _masterVolume;    // 0-255
-	byte _musicVolume;
-	byte _sfxVolume;
 	bool _paused;
 
-	// These are only used for music.
-	byte _currentTrack;
-	bool _loopTrack;
+	// Queued music track data (Simon 2).
 	byte _queuedTrack;
 	bool _loopQueuedTrack;
 
 protected:
 	static void onTimer(void *data);
-	void clearConstructs();
-	void clearConstructs(MusicInfo &info);
-	void resetVolumeTable();
 
 public:
 	MidiPlayer(AGOSEngine *vm);
-	~MidiPlayer() override;
+	~MidiPlayer();
 
-	// Loads music data supported by the MidiParser used for the detected
-	// version of the game. Specify sfx to indicate that this is a MIDI sound
-	// effect.
-	void loadMusic(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
+	// Creates and opens the relevant parsers and drivers for the game version
+	// and selected sound device.
+	int open();
+
+	// Loads music or SFX data supported by the MidiParser or SfxParser used
+	// for the detected version of the game. Specify sfx to indicate that this
+	// is a synthesized sound effect.
+	void load(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
 
 	/**
-	 * Plays the currently loaded music data. If the loaded MIDI data has
+	 * Plays the currently loaded music or SFX data. If the loaded data has
 	 * multiple tracks, specify track to select the track to play.
 	 * 
 	 * @param track The track to play. Default 0.
-	 * @param sfx True if the SFX MIDI data should be played, otherwise the
-	 * loaded music data will be played. Default false.
+	 * @param sfx True if the SFX data should be played, otherwise the loaded
+	 * music data will be played. Default false.
 	 * @param sfxUsesRhythm True if the sound effect uses OPL rhythm
 	 * instruments. Default false.
 	 * @param queued True if this track was queued; false if it was played
 	 * directly. Default false.
 	 */
 	void play(int track = 0, bool sfx = false, bool sfxUsesRhythm = false, bool queued = false);
-
-	void loadSMF(Common::SeekableReadStream *in, int song, bool sfx = false);
-	void loadMultipleSMF(Common::SeekableReadStream *in, bool sfx = false);
-	void loadXMIDI(Common::SeekableReadStream *in, bool sfx = false);
-	void loadS1D(Common::SeekableReadStream *in, bool sfx = false);
-
+	
 	// Returns true if the playback device uses MT-32 MIDI data; false it it
 	// uses a different data type.
 	bool usesMT32Data() const;
-	bool hasAdLibSfx() const;
+	// Returns true if the game version and selected sound device can use MIDI
+	// (or synthesized) sound effects.
+	bool hasMidiSfx() const;
 	void setLoop(bool loop);
 	// Activates or deactivates remapping GM to MT-32 instruments for
 	// Simon 2 track 10.
 	void setSimon2Remapping(bool remap);
-	void startTrack(int track);
 	void queueTrack(int track, bool loop);
 	bool isPlaying(bool checkQueued = false);
 
-	void stop();
-	void stopSfx();
+	void stop(bool sfx = false);
 	void pause(bool b);
 	void fadeOut();
 
-	void setVolume(int musicVol, int sfxVol);
 	void syncSoundSettings();
 
-public:
-	int open(int gameType, Common::Platform platform, bool isDemo);
-
-	// MidiDriver_BASE interface implementation
-	void send(uint32 b) override;
-	void metaEvent(byte type, byte *data, uint16 length) override;
-
 private:
-	kMusicMode _musicMode;
+	bool _pc98;
 	// The type of the music device selected for playback.
 	MusicType _deviceType;
 	// The type of the MIDI data of the game (MT-32 or GM).
diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp
index 19fbfae0219..ede991aa038 100644
--- a/engines/agos/midiparser_s1d.cpp
+++ b/engines/agos/midiparser_s1d.cpp
@@ -41,16 +41,37 @@ private:
 		byte *start, *end;
 	} _loops[16];
 
+	// Data for monophonic chords mode.
+	// If this is activated, when multiple notes are played at the same time on
+	// a melodic channel (0-5), only the highest note will be played.
+	// This functionality is used by Elvira 2 (although there are no chords in
+	// the MIDI data), Waxworks and the Simon 1 floppy demo.
+
+	// The highest note played at _lastPlayedNoteTime for each channel.
+	byte _highestNote[6];
+	// The timestamp at which the last note was played at each channel.
+	uint32 _lastPlayedNoteTime[6];
+
+	// True if, for notes played at the same time, only the highest note should
+	// be played. If false, all notes of a chord will be sent to the driver.
+	bool _monophonicChords;
+
 	uint32 readVLQ2(byte *&data);
-	void chainEvent(EventInfo &info);
 protected:
 	void parseNextEvent(EventInfo &info) override;
+	bool processEvent(const EventInfo &info, bool fireEvents = true) override;
 	void resetTracking() override;
 
-public:
-	MidiParser_S1D() : _data(nullptr), _noDelta(false) {}
+	public:
+	MidiParser_S1D(uint8 source = 0, bool monophonicChords = false) : MidiParser(source),
+			_monophonicChords(monophonicChords), _data(nullptr), _noDelta(false) {
+		Common::fill(_loops, _loops + ARRAYSIZE(_loops), Loop { 0, 0, 0 });
+		Common::fill(_highestNote, _highestNote + ARRAYSIZE(_highestNote), 0);
+		Common::fill(_lastPlayedNoteTime, _lastPlayedNoteTime + ARRAYSIZE(_lastPlayedNoteTime), 0);
+	}
 
 	bool loadMusic(byte *data, uint32 size) override;
+	int32 determineDataSize(Common::SeekableReadStream *stream) override;
 };
 
 uint32 MidiParser_S1D::readVLQ2(byte *&data) {
@@ -66,17 +87,11 @@ uint32 MidiParser_S1D::readVLQ2(byte *&data) {
 	return delta;
 }
 
-void MidiParser_S1D::chainEvent(EventInfo &info) {
-	// When we chain an event, we add up the old delta.
-	uint32 delta = info.delta;
-	parseNextEvent(info);
-	info.delta += delta;
-}
-
 void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 	info.start = _position._playPos;
 	info.length = 0;
 	info.delta = _noDelta ? 0 : readVLQ2(_position._playPos);
+	info.noop = false;
 	_noDelta = false;
 
 	info.event = *_position._playPos++;
@@ -126,26 +141,26 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 
 						// Go to the start of the loop
 						_position._playPos = _loops[info.channel()].start;
+						info.loop = true;
 					}
 				} else {
-					if (_loops[info.channel()].timer)
+					if (_loops[info.channel()].timer) {
 						_position._playPos = _loops[info.channel()].start;
+						info.loop = true;
+					}
 					--_loops[info.channel()].timer;
 				}
 			}
-
-			// We need to read the next midi event here. Since we can not
-			// safely pass this event to the MIDI event processing.
-			chainEvent(info);
+			// Event has been fully processed here.
+			info.noop = true;
 			} break;
 
 		case 0xB: // auto stop marker(?)
 			// In case the stop mode(?) is set to 0x80 this will stop the
 			// track.
 
-			// We need to read the next midi event here. Since we can not
-			// safely pass this event to the MIDI event processing.
-			chainEvent(info);
+			// Event has been fully processed here.
+			info.noop = true;
 			break;
 
 		case 0xC: // program change
@@ -157,9 +172,8 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 			if (_loops[info.channel()].end)
 				_position._playPos = _loops[info.channel()].end;
 
-			// We need to read the next midi event here. Since we can not
-			// safely pass this event to the MIDI event processing.
-			chainEvent(info);
+			// Event has been fully processed here.
+			info.noop = true;
 			break;
 
 		default:
@@ -167,14 +181,38 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
 			// not to be MIDI related.
 			warning("MidiParser_S1D: default case %d", info.channel());
 
-			// We need to read the next midi event here. Since we can not
-			// safely pass this event to the MIDI event processing.
-			chainEvent(info);
+			// Event has been fully processed here.
+			info.noop = true;
 			break;
 		}
 	}
 }
 
+bool MidiParser_S1D::processEvent(const EventInfo &info, bool fireEvents) {
+	byte channel = info.channel();
+	if (_monophonicChords && channel < 6 && info.command() == 0x9 && info.basic.param2 > 0) {
+		// In monophonic chords mode, when multiple notes are played at the
+		// same time on a melodic channel (0-5), only the highest note should
+		// be played.
+		if (_lastPlayedNoteTime[channel] == _position._playTick && _highestNote[channel] > info.basic.param1) {
+			// This note is lower than a previously played note on the same
+			// channel and with the same timestamp. Ignore it.
+			return true;
+		} else {
+			// This note either has a different timestamp (i.e. it is not
+			// played at the same time), or it is higher than the previously
+			// played note.
+			// Update the timestamp and note registry and play this note
+			// (because the channel is monophonic, a previously played lower
+			// note will be cut off).
+			_lastPlayedNoteTime[channel] = _position._playTick;
+			_highestNote[channel] = info.basic.param1;
+		}
+	}
+
+	return MidiParser::processEvent(info, fireEvents);
+}
+
 bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
 	unloadMusic();
 
@@ -182,7 +220,7 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
 		return false;
 
 	// The original actually just ignores the first two bytes.
-	byte *pos = data;
+	byte *pos = data + 2;
 	if (*pos == 0xFC) {
 		// SysEx found right at the start
 		// this seems to happen since Elvira 2, we ignore it
@@ -230,6 +268,11 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
 	return true;
 }
 
+int32 MidiParser_S1D::determineDataSize(Common::SeekableReadStream* stream) {
+	// Data size is stored in the first two bytes.
+	return stream->readUint16LE() + 2;
+}
+
 void MidiParser_S1D::resetTracking() {
 	MidiParser::resetTracking();
 	// The first event never contains any delta.
@@ -237,6 +280,6 @@ void MidiParser_S1D::resetTracking() {
 	memset(_loops, 0, sizeof(_loops));
 }
 
-MidiParser *MidiParser_createS1D() { return new MidiParser_S1D; }
+MidiParser *MidiParser_createS1D(uint8 source, bool monophonicChords) { return new MidiParser_S1D(source, monophonicChords); }
 
 } // End of namespace AGOS
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index 9e6f7546068..99c2468c856 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -6,7 +6,6 @@ MODULE_OBJS := \
 	drivers/accolade/pc98.o \
 	drivers/accolade/mt32.o \
 	drivers/simon1/adlib.o \
-	drivers/simon1/adlib_win.o \
 	agos.o \
 	charset.o \
 	charset-fontdata.o \
@@ -40,6 +39,7 @@ MODULE_OBJS := \
 	script_ww.o \
 	script_s1.o \
 	script_s2.o \
+	sfxparser_accolade.o \
 	sound.o \
 	string.o \
 	string_pn.o \
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index 7c424ae77c0..097e9413044 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -137,7 +137,7 @@ void AGOSEngine::loadMusic(uint16 music, bool forceSimon2Gm) {
 	uint16 indexBase = forceSimon2Gm ? MUSIC_INDEX_BASE_SIMON2_GM : _musicIndexBase;
 
 	_gameFile->seek(_gameOffsetsPtr[indexBase + music - 1], SEEK_SET);
-	_midi->loadMusic(_gameFile);
+	_midi->load(_gameFile);
 
 	// Activate Simon 2 GM to MT-32 remapping if we force GM, otherwise
 	// deactivate it (in case it was previously activated).
@@ -279,7 +279,7 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 		int size = SIMON1_GMF_SIZE[music];
 
 		_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
-		_midi->loadMusic(_gameFile, size);
+		_midi->load(_gameFile, size);
 		_midi->play();
 	} else if (getPlatform() == Common::kPlatformDOS) {
 		// DOS floppy version.
@@ -292,7 +292,7 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 		if (f.isOpen() == false)
 			error("playMusic: Can't load music from '%s'", filename);
 
-		_midi->loadMusic(&f, f.size());
+		_midi->load(&f, f.size());
 		if (getFeatures() & GF_DEMO) {
 			// Full version music data has a loop flag in the file header, but
 			// the demo needs to have this set manually.
@@ -304,7 +304,7 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 		// Windows version uses SMF data in one large data file.
 		_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
 
-		_midi->loadMusic(_gameFile);
+		_midi->load(_gameFile);
 		_midi->setLoop(true);
 
 		_midi->play();
@@ -315,46 +315,10 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 	}
 }
 
-void AGOSEngine::playMusic(uint16 music, uint16 track) {
-	stopMusic();
-
-	if (getPlatform() == Common::kPlatformAmiga) {
-		playModule(music);
-	} else if (getPlatform() == Common::kPlatformAtariST) {
-		// TODO: Add support for music formats used
-	} else {
-		_midi->setLoop(true); // Must do this BEFORE loading music.
-
-		Common::SeekableReadStream *str = nullptr;
-		if (getPlatform() == Common::kPlatformPC98) {
-			str = createPak98FileStream(Common::String::format("MOD%d.PAK", music).c_str());
-			if (!str)
-				error("playMusic: Can't load music from 'MOD%d.PAK'", music);
-		} else {
-			Common::File *file = new Common::File();
-			if (!file->open(Common::String::format("MOD%d.MUS", music)))
-				error("playMusic: Can't load music from 'MOD%d.MUS'", music);
-			str = file;
-		}
-
-		_midi->loadS1D(str);
-		_midi->startTrack(0);
-		_midi->startTrack(track);
-		delete str;
-	}
-}
-
-void AGOSEngine::stopMusic() {
-	if (_midiEnabled) {
-		_midi->stop();
-	}
-	_mixer->stopHandle(_modHandle);
-}
-
-void AGOSEngine::playSting(uint16 soundId) {
+void AGOSEngine_Simon1::playMidiSfx(uint16 sound) {
 	// The sound effects in floppy disk version of
 	// Simon the Sorcerer 1 are only meant for AdLib
-	if (!_midi->hasAdLibSfx())
+	if (!_midi->hasMidiSfx())
 		return;
 
 	// AdLib SFX use GMF data bundled in 9 STINGSx.MUS files.
@@ -378,7 +342,7 @@ void AGOSEngine::playSting(uint16 soundId) {
 	bool rhythmSfx = false;
 	// Search for the file ID / SFX ID combination in the list of SFX that use
 	// rhythm notes.
-	byte sfxId = (_soundFileId << 4) | soundId;
+	byte sfxId = (_soundFileId << 4) | sound;
 	for (int i = 0; i < ARRAYSIZE(SIMON1_RHYTHM_SFX); i++) {
 		if (SIMON1_RHYTHM_SFX[i] == sfxId) {
 			rhythmSfx = true;
@@ -386,10 +350,46 @@ void AGOSEngine::playSting(uint16 soundId) {
 		}
 	}
 
-	_midi->stopSfx();
+	_midi->stop(true);
+
+	_midi->load(&mus_file, mus_file.size(), true);
+	_midi->play(sound, true, rhythmSfx);
+}
+
+void AGOSEngine::playMusic(uint16 music, uint16 track) {
+	stopMusic();
+
+	if (getPlatform() == Common::kPlatformAmiga) {
+		playModule(music);
+	} else if (getPlatform() == Common::kPlatformAtariST) {
+		// TODO: Add support for music formats used
+	} else {
+		_midi->setLoop(true); // Must do this BEFORE loading music.
+
+		Common::SeekableReadStream *str = nullptr;
+		if (getPlatform() == Common::kPlatformPC98) {
+			str = createPak98FileStream(Common::String::format("MOD%d.PAK", music).c_str());
+			if (!str)
+				error("playMusic: Can't load music from 'MOD%d.PAK'", music);
+		} else {
+			Common::File *file = new Common::File();
+			if (!file->open(Common::String::format("MOD%d.MUS", music)))
+				error("playMusic: Can't load music from 'MOD%d.MUS'", music);
+			str = file;
+		}
+
+		//warning("Playing track %d", music);
+		_midi->load(str);
+		_midi->play();
+		delete str;
+	}
+}
 
-	_midi->loadMusic(&mus_file, mus_file.size(), true);
-	_midi->play(soundId, true, rhythmSfx);
+void AGOSEngine::stopMusic() {
+	if (_midiEnabled) {
+		_midi->stop();
+	}
+	_mixer->stopHandle(_modHandle);
 }
 
 static const byte elvira1_soundTable[100] = {
@@ -571,6 +571,14 @@ void AGOSEngine::loadSound(uint16 sound, int16 pan, int16 vol, uint16 type) {
 		_sound->playSfx5Data(dst, sound, pan, vol);
 }
 
+void AGOSEngine::playSfx(uint16 sound, uint16 freq, uint16 flags, bool canUseMidiSfx) {
+	if (_useDigitalSfx) {
+		loadSound(sound, freq, flags);
+	} else if (canUseMidiSfx) {
+		playMidiSfx(sound);
+	}
+}
+
 void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
 	byte *dst;
 	uint32 offs, size = 0;
@@ -611,7 +619,6 @@ void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
 
 			if (size > _curSfxFileSize)
 				error("loadSound: Reading beyond EOF (%d, %d)", size, _curSfxFileSize);
-
 		}
 
 		size = READ_BE_UINT16(dst + 2);
@@ -638,6 +645,29 @@ void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
 	}
 }
 
+void AGOSEngine::loadMidiSfx() {
+	if (!_midi->hasMidiSfx())
+		return;
+
+	Common::File fxb_file;
+
+	Common::String filename = getGameType() == GType_ELVIRA2 ? "MYLIB.FXB" : "WAX.FXB";
+	fxb_file.open(filename);
+	if (!fxb_file.isOpen())
+		error("loadMidiSfx: Can't open sound effect bank '%s'", filename.c_str());
+
+	_midi->load(&fxb_file, fxb_file.size(), true);
+
+	fxb_file.close();
+}
+
+void AGOSEngine::playMidiSfx(uint16 sound) {
+	if (!_midi->hasMidiSfx())
+		return;
+
+	_midi->play(sound, true);
+}
+
 void AGOSEngine::loadVoice(uint speechId) {
 	if (getGameType() == GType_PP && speechId == 99) {
 		_sound->stopVoice();
@@ -673,4 +703,10 @@ void AGOSEngine::loadVoice(uint speechId) {
 	}
 }
 
+void AGOSEngine::stopAllSfx() {
+	_sound->stopAllSfx();
+	if (_midi->hasMidiSfx())
+		_midi->stop(true);
+}
+
 } // End of namespace AGOS
diff --git a/engines/agos/script_e2.cpp b/engines/agos/script_e2.cpp
index 73b4af19853..eb5183aa215 100644
--- a/engines/agos/script_e2.cpp
+++ b/engines/agos/script_e2.cpp
@@ -581,7 +581,7 @@ void AGOSEngine_Elvira2::oe2_ifExitLocked() {
 void AGOSEngine_Elvira2::oe2_playEffect() {
 	// 174: play sound
 	uint soundId = getVarOrWord();
-	loadSound(soundId, 0, 0);
+	playSfx(soundId, 0, 0, true);
 }
 
 void AGOSEngine_Elvira2::oe2_getDollar2() {
diff --git a/engines/agos/script_s1.cpp b/engines/agos/script_s1.cpp
index 31750926014..875353165a1 100644
--- a/engines/agos/script_s1.cpp
+++ b/engines/agos/script_s1.cpp
@@ -378,7 +378,7 @@ void AGOSEngine_Simon1::os1_playEffect() {
 	uint16 soundId = getVarOrWord();
 
 	if (getGameId() == GID_SIMON1DOS)
-		playSting(soundId);
+		playSfx(soundId, 0, 0, true);
 	else
 		_sound->playEffects(soundId);
 }
diff --git a/engines/agos/sfxparser_accolade.cpp b/engines/agos/sfxparser_accolade.cpp
new file mode 100644
index 00000000000..a83baf604fb
--- /dev/null
+++ b/engines/agos/sfxparser_accolade.cpp
@@ -0,0 +1,508 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agos/sfxparser_accolade.h"
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+namespace AGOS {
+
+SfxParser_Accolade::SfxSlot::SfxSlot() {
+	clear();
+}
+
+void SfxParser_Accolade::SfxSlot::reset() {
+	noteFractionDelta = 0;
+	vibratoTime = 0;
+	vibratoCounter = 0;
+	vibratoDelta = 0;
+	waitCounter = 0;
+	loopStart = 0;
+	loopCounter = 0;
+}
+
+void SfxParser_Accolade::SfxSlot::clear() {
+	allocated = false;
+	active = false;
+	source = -1;
+	scriptPos = 0;
+	playTime = 0;
+	lastEventTime = 0;
+	lastPlayedNote = -1;
+	currentNoteFraction = 0;
+	programChanged = false;
+	reset();
+}
+
+bool SfxParser_Accolade::SfxSlot::atEndOfScript() {
+	return scriptPos >= sfxData->scriptSize;
+}
+
+int16 SfxParser_Accolade::SfxSlot::readScript(bool opCode) {
+	if (atEndOfScript())
+		error("SfxParser_Accolade::SfxData::readScript - attempt to read past the end of the script");
+
+	int16 data = sfxData->scriptData[scriptPos];
+	scriptPos++;
+
+	if (opCode && (data <= 0 || data > 0xC)) {
+		// Any opcode outside the range 1-B will cause the script to stop.
+		data = 0xC;
+	}
+
+	return data;
+}
+
+const byte SfxParser_Accolade::INSTRUMENT_SIZE_MT32;
+
+SfxParser_Accolade::SfxParser_Accolade() : _driver(nullptr), _timerRate(0), _sfxData(),
+	_numSfx(0), _sourceAllocations { -1, -1, -1, -1 }, _paused(false) { }
+
+SfxParser_Accolade::~SfxParser_Accolade() {
+	stopAll();
+
+	if (_sfxData) {
+		delete[] _sfxData;
+		_sfxData = nullptr;
+	}
+}
+
+void SfxParser_Accolade::load(Common::SeekableReadStream *in, int32 size) {
+	// First word is the total data size.
+	uint16 dataSize = in->readUint16LE();
+	if (dataSize > size)
+		error("SfxParser_Accolade::load - Sound effect bank lists size %d but has file size %d", dataSize, size);
+
+	// Next word is the number of SFX definitions.
+	_numSfx = in->readUint16LE();
+	_sfxData = new SfxData[_numSfx];
+
+	// Next is a list of start offsets for each SFX definition. Combined with
+	// the total size the size of each SFX definition can be determined.
+	int64 indexStartPos = in->pos();
+	for (int i = 0; i < _numSfx; i++) {
+		in->seek(indexStartPos + (i * 2));
+		uint16 sfxDataOffset = in->readUint16LE();
+		uint16 sfxDataEndOffset = i < _numSfx - 1 ? in->readUint16LE() : dataSize - 4;
+		in->seek(indexStartPos + sfxDataOffset);
+		uint16 sfxDataSize = sfxDataEndOffset - sfxDataOffset;
+
+		// Read instrument definition.
+		readInstrument(&_sfxData[i], in);
+
+		// Instrument data size is fixed; the reset of the SFX definition is
+		// the script.
+		int scriptSize = sfxDataSize - INSTRUMENT_SIZE_MT32 - INSTRUMENT_SIZE_ADLIB;
+		if (scriptSize < 2)
+			error("SfxParser_Accolade::load - Unexpected script size %d", scriptSize);
+		if (scriptSize % 2 != 0)
+			warning("SfxParser_Accolade::load - Script has odd number of bytes %d", scriptSize);
+		scriptSize >>= 1;
+		// Script size is stored in words.
+		_sfxData[i].scriptSize = scriptSize;
+
+		// Read each word into the script data.
+		for (int j = 0; j < scriptSize; j++) {
+			_sfxData[i].scriptData[j] = in->readSint16LE();
+		}
+	}
+}
+
+void SfxParser_Accolade::setTimerRate(uint32 rate) {
+	_timerRate = rate;
+}
+
+void SfxParser_Accolade::play(uint8 sfxNumber) {
+	Common::StackLock lock(_mutex);
+
+	if (sfxNumber >= _numSfx) {
+		warning("SfxParser_Accolade::play - Sound effect %d requested but bank has only %d sound effects", sfxNumber, _numSfx);
+		return;
+	}
+
+	// Find an unallocated slot.
+	SfxSlot *sfxSlot = nullptr;
+	int sfxSlotNum = -1;
+	for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
+		if (!_sfxSlots[i].allocated) {
+			_sfxSlots[i].allocated = true;
+			sfxSlot = &_sfxSlots[i];
+			sfxSlotNum = i;
+			break;
+		}
+	}
+
+	// Note that the original interpreter would only output MIDI data from 2
+	// slots simultaneously, but potentially *all* SFX could be active at the
+	// same time (but the same sound effect could not play more than once at
+	// the same time).
+	// This implementation only allows 4 SFX active at the same time, which
+	// seems to be more than enough for Elvira 2 and Waxworks.
+	if (!sfxSlot)
+		return;
+
+	// Allocate a source.
+	for (int i = 0; i < getNumberOfSfxSources(); i++) {
+		if (_sourceAllocations[i] == -1) {
+			_sourceAllocations[i] = sfxSlotNum;
+			sfxSlot->source = i + 1;
+			break;
+		}
+	}
+
+	// Set the SFX data and load the instrument into the allocated channel.
+	sfxSlot->sfxData = &_sfxData[sfxNumber];
+	sfxSlot->programChanged = loadInstrument(sfxSlot);
+
+	// Activate the slot to start script execution.
+	sfxSlot->active = true;
+}
+
+void SfxParser_Accolade::stopAll() {
+	Common::StackLock lock(_mutex);
+
+	for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
+		if (_sfxSlots[i].active)
+			stop(&_sfxSlots[i]);
+	}
+}
+
+void SfxParser_Accolade::pauseAll(bool paused) {
+	Common::StackLock lock(_mutex);
+
+	if (_paused == paused)
+		return;
+
+	_paused = paused;
+
+	if (_paused) {
+		// Stop the current note for all active SFX.
+		for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
+			if (_sfxSlots[i].active)
+				noteOff(&_sfxSlots[i]);
+		}
+	}
+}
+
+void SfxParser_Accolade::stop(SfxSlot *sfxSlot) {
+	noteOff(sfxSlot);
+
+	// Deallocate the source.
+	if (sfxSlot->source >= 0) {
+		_driver->deinitSource(sfxSlot->source);
+		_sourceAllocations[sfxSlot->source - 1] = -1;
+	}
+
+	// The original interpreter would try to re-assign the source to an active
+	// sound effect without a source. This is not implemented here because
+	// Elvira 2 and Waxworks use SFX very sparingly, so it seems very unlikely
+	// that more than 2 SFX would be active at the same time.
+
+	sfxSlot->clear();
+}
+
+void SfxParser_Accolade::processOpCode(SfxSlot *sfxSlot, byte opCode) {
+	switch (opCode) {
+	case 0x1:
+		// Set note and note fraction delta.
+		sfxSlot->noteFractionDelta = sfxSlot->readScript(false);
+		break;
+	case 0x2:
+		// Clear note and note fraction delta.
+		sfxSlot->noteFractionDelta = 0;
+		break;
+	case 0x3:
+		// Set vibrato.
+		int16 vibratoTime;
+		vibratoTime = sfxSlot->readScript(false);
+		assert(vibratoTime >= 0);
+		sfxSlot->vibratoTime = vibratoTime;
+		// The counter starts at half the vibrato time, which causes the note
+		// frequency to move above and below the note fraction (like a sine
+		// wave).
+		sfxSlot->vibratoCounter = (vibratoTime >> 1) | 1;
+		sfxSlot->vibratoDelta = sfxSlot->readScript(false);
+		break;
+	case 0x4:
+		// Clear vibrato.
+		sfxSlot->vibratoTime = 0;
+		sfxSlot->vibratoDelta = 0;
+		break;
+	case 0x5:
+		// Wait.
+		sfxSlot->waitCounter = sfxSlot->readScript(false);
+		assert(sfxSlot->waitCounter >= 0);
+		break;
+	case 0x6:
+		// Play note.
+		noteOff(sfxSlot);
+		int8 note;
+		note = sfxSlot->readScript(false) & 0xFF;
+		assert(note >= 0 && note <= 0x7F);
+		sfxSlot->currentNoteFraction = note << 8;
+		noteOn(sfxSlot);
+		break;
+	case 0x7:
+		// Loop start.
+
+		// Just register the loop start position.
+		sfxSlot->loopStart = sfxSlot->scriptPos;
+		break;
+	case 0x8:
+		// Loop next.
+		int16 loopParam;
+		loopParam = sfxSlot->readScript(false);
+		assert(loopParam >= 0);
+		if (sfxSlot->loopCounter == 0) {
+			// Loop counter has not been set yet, so do this now.
+			if (loopParam == 0)
+				// Loop infinitely.
+				loopParam = -1;
+			sfxSlot->loopCounter = loopParam;
+			// Go back to loop start.
+			sfxSlot->scriptPos = sfxSlot->loopStart;
+		} else {
+			// Decrease loop counter, unless the loop is infinite.
+			if (sfxSlot->loopCounter != -1)
+				sfxSlot->loopCounter--;
+			if (sfxSlot->loopCounter != 0)
+				// Go back to loop start.
+				sfxSlot->scriptPos = sfxSlot->loopStart;
+			// Else continue the script.
+		}
+		break;
+	case 0x9:
+		// Stop the current note.
+		noteOff(sfxSlot);
+		break;
+	case 0xA:
+		// Reset sound effect data.
+		sfxSlot->reset();
+		// The original interpreter does this; not sure why...
+		sfxSlot->vibratoCounter = 1;
+		break;
+	case 0xB:
+		// Noop. Consumes 1 script parameter.
+		sfxSlot->scriptPos++;
+		break;
+	case 0xC:
+	default:
+		// Stop the sound effect.
+		stop(sfxSlot);
+		break;
+	}
+}
+
+void SfxParser_Accolade::noteOn(SfxSlot *sfxSlot) {
+	byte note = sfxSlot->currentNoteFraction >> 8;
+	if (sfxSlot->source >= 0)
+		// Send a note on with maximum velocity.
+		_driver->send(sfxSlot->source, 0x90 | (note << 8) | (0x7F << 16));
+	sfxSlot->lastPlayedNote = note;
+}
+
+void SfxParser_Accolade::noteOff(SfxSlot *sfxSlot) {
+	if (sfxSlot->lastPlayedNote < 0)
+		return;
+
+	if (sfxSlot->source >= 0)
+		// Send a note off.
+		_driver->send(sfxSlot->source, 0x80 | (sfxSlot->lastPlayedNote << 8));
+	sfxSlot->lastPlayedNote = -1;
+}
+
+void SfxParser_Accolade::onTimer() {
+	Common::StackLock lock(_mutex);
+
+	if (_paused)
+		return;
+
+	for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
+		if (!_sfxSlots[i].active)
+			continue;
+
+		if (!_sfxSlots[i].programChanged) {
+			// If the SFX instrument has not yet been set on the allocated
+			// channel, wait until the driver is ready.
+			if (!_driver->isReady(_sfxSlots[i].source))
+				continue;
+
+			// Then change to the SFX instrument.
+			changeInstrument(&_sfxSlots[i]);
+			_sfxSlots[i].programChanged = true;
+		}
+
+		// Note that ScummVM timer callback frequency is limited by what SDL
+		// can provide, which is every 10ms minimum. The original interpreter
+		// processes a script tick about every 3.25ms. So every onTimer call
+		// multiple script ticks are processed at the same time. Because of
+		// this, the sound effect audio is not entirely accurate.
+
+		// Determine the end time of the timer callback period that will be
+		// processed.
+		uint32 endTime = _sfxSlots[i].playTime + _timerRate;
+		// Process script ticks while the sound effect remains active.
+		while (_sfxSlots[i].active) {
+			// Determine the end time of the script tick that will be
+			// processed.
+			uint32 eventTime = _sfxSlots[i].lastEventTime + SCRIPT_TIMER_RATE;
+			if (eventTime > endTime)
+				// Script tick end time is after this timer callback period, so
+				// leave it to the next callback invocation.
+				break;
+
+			// Process this script tick.
+			_sfxSlots[i].lastEventTime = eventTime;
+
+			// Process vibrato counter.
+			if (_sfxSlots[i].vibratoCounter > 0) {
+				// Count down the vibrato counter.
+				_sfxSlots[i].vibratoCounter--;
+			} else {
+				// Vibrato counter has reached zero. The vibrato note fraction
+				// delta is negated, so that it will now be subtracted from the
+				// note fraction instead of added, or the other way around.
+				_sfxSlots[i].vibratoDelta = -_sfxSlots[i].vibratoDelta;
+				// Reset the vibrato counter so it counts down to the next
+				// delta negation.
+				_sfxSlots[i].vibratoCounter = _sfxSlots[i].vibratoTime;
+			}
+
+			// Calculate the new note and note fraction by adding the deltas.
+			uint16 newNoteFraction = _sfxSlots[i].currentNoteFraction;
+			newNoteFraction += _sfxSlots[i].noteFractionDelta;
+			newNoteFraction += _sfxSlots[i].vibratoDelta;
+
+			if (newNoteFraction != _sfxSlots[i].currentNoteFraction) {
+				// Update the note fraction.
+				_sfxSlots[i].currentNoteFraction = newNoteFraction;
+				updateNote(&_sfxSlots[i]);
+			}
+
+			// Process the script.
+			if (_sfxSlots[i].waitCounter > 0) {
+				// Count down the wait counter. No script opcode will be
+				// processsed.
+				_sfxSlots[i].waitCounter--;
+			} else if (_sfxSlots[i].atEndOfScript()) {
+				// The end of the script has been reached, so stop the sound
+				// effect.
+				// Note that the original interpreter did not do any bounds
+				// checking. Some scripts are not terminated properly and would
+				// cause the original interpreter to overread into the
+				// instrument data of the next sound effect.
+				stop(&_sfxSlots[i]);
+			} else {
+				// Process the next script opcode.
+				byte opCode = _sfxSlots[i].readScript(true) & 0xFF;
+				processOpCode(&_sfxSlots[i], opCode);
+			}
+		}
+
+		// If the sound effect is still active, update the play timestamp.
+		if (_sfxSlots[i].active)
+			_sfxSlots[i].playTime = endTime;
+	}
+}
+
+void SfxParser_Accolade::timerCallback(void *data) {
+	((SfxParser_Accolade *)data)->onTimer();
+}
+
+void SfxParser_Accolade_AdLib::setMidiDriver(MidiDriver_Multisource *driver) {
+	_driver = driver;
+	_adLibDriver = dynamic_cast<MidiDriver_Accolade_AdLib *>(driver);
+	assert(_adLibDriver);
+}
+
+byte SfxParser_Accolade_AdLib::getNumberOfSfxSources() {
+	// The number of available sources depends on the OPL chip emulation used.
+	return _adLibDriver->getNumberOfSfxSources();
+}
+
+void SfxParser_Accolade_AdLib::readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) {
+	in->skip(INSTRUMENT_SIZE_MT32);
+	in->read(sfxData->instrumentDefinition, INSTRUMENT_SIZE_ADLIB);
+}
+
+bool SfxParser_Accolade_AdLib::loadInstrument(SfxSlot *sfxSlot) {
+	if (sfxSlot->source < 0)
+		return true;
+
+	_adLibDriver->loadSfxInstrument(sfxSlot->source, sfxSlot->sfxData->instrumentDefinition);
+	// No separate instrument change is necessary for AdLib, so true is
+	// returned to indicate the instrument is already changed.
+	return true;
+}
+
+void SfxParser_Accolade_AdLib::noteOn(SfxSlot *sfxSlot) {
+	if (sfxSlot->source >= 0)
+		// Set the current note fraction first.
+		_adLibDriver->setSfxNoteFraction(sfxSlot->source, sfxSlot->currentNoteFraction);
+	// Then start the note.
+	SfxParser_Accolade::noteOn(sfxSlot);
+}
+
+void SfxParser_Accolade_AdLib::updateNote(SfxSlot *sfxSlot) {
+	if (sfxSlot->source < 0)
+		return;
+
+	// Set the current note fraction first.
+	_adLibDriver->setSfxNoteFraction(sfxSlot->source, sfxSlot->currentNoteFraction);
+	// Then update the note.
+	_adLibDriver->updateSfxNote(sfxSlot->source);
+}
+
+void SfxParser_Accolade_MT32::setMidiDriver(MidiDriver_Multisource *driver) {
+	_driver = driver;
+	_mt32Driver = dynamic_cast<MidiDriver_Accolade_MT32 *>(driver);
+	assert(_mt32Driver);
+}
+
+byte SfxParser_Accolade_MT32::getNumberOfSfxSources() {
+	// MT-32 always uses 2 SFX sources.
+	return 2;
+}
+
+void SfxParser_Accolade_MT32::readInstrument(SfxData *sfxSlot, Common::SeekableReadStream *in) {
+	in->read(sfxSlot->instrumentDefinition, INSTRUMENT_SIZE_MT32);
+	in->skip(INSTRUMENT_SIZE_ADLIB);
+}
+
+bool SfxParser_Accolade_MT32::loadInstrument(SfxSlot *sfxSlot) {
+	if (sfxSlot->source < 0)
+		return true;
+
+	_mt32Driver->loadSfxInstrument(sfxSlot->source, sfxSlot->sfxData->instrumentDefinition);
+	// MT-32 requires a program change after the SysExes to load the instrument
+	// have been processed. Return false to indicate this.
+	return false;
+}
+
+void SfxParser_Accolade_MT32::changeInstrument(SfxSlot *sfxData) {
+	if (sfxData->source < 0)
+		return;
+
+	_mt32Driver->changeSfxInstrument(sfxData->source);
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/sfxparser_accolade.h b/engines/agos/sfxparser_accolade.h
new file mode 100644
index 00000000000..d99040223b1
--- /dev/null
+++ b/engines/agos/sfxparser_accolade.h
@@ -0,0 +1,220 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGOS_SFXPARSER_ACCOLADE_H
+#define AGOS_SFXPARSER_ACCOLADE_H
+
+#include "agos/drivers/accolade/adlib.h"
+#include "agos/drivers/accolade/mt32.h"
+
+#include "common/mutex.h"
+#include "common/stream.h"
+
+namespace AGOS {
+
+class SfxParser_Accolade {
+public:
+	// Size in bytes of MT-32 instrument data in the SFX data.
+	static const byte INSTRUMENT_SIZE_MT32 = 0xF9;
+
+protected:
+	// Number of microseconds per script tick.
+	static const uint16 SCRIPT_TIMER_RATE = 3250;
+	// Size in bytes of AdLib instrument data in the SFX data.
+	static const byte INSTRUMENT_SIZE_ADLIB = 0x09;
+	// Maximum number of words in an SFX script.
+	static const byte MAX_SCRIPT_SIZE = 0x30;
+	// Maximum number of simultaneous sources for OPL2.
+	static const byte OPL2_NUM_SOURCES = 2;
+	// Maximum number of simultaneous sources for OPL3.
+	static const byte OPL3_NUM_SOURCES = 4;
+
+	// Data for a single SFX. Taken from the game's SFX bank.
+	struct SfxData {
+		// The instrument data for the used sound device (OPL or MT-32).
+		byte instrumentDefinition[INSTRUMENT_SIZE_MT32];
+		// The SFX script.
+		int16 scriptData[MAX_SCRIPT_SIZE];
+		// The size in words of the SFX script.
+		int scriptSize;
+	};
+
+	// State data a SFX playback slot.
+	struct SfxSlot {
+		SfxSlot();
+
+		// The data of the SFX currently played by this slot.
+		SfxData *sfxData;
+
+		// True if this slot has been allocated to playing a SFX.
+		bool allocated;
+		// True if SFX playback is active.
+		bool active;
+		// The source used to send data to the MIDI driver.
+		int8 source;
+		// Current position in the SFX script.
+		byte scriptPos;
+
+		// Current playback time in microseconds.
+		uint32 playTime;
+		// The timestamp of the last processed script tick.
+		uint32 lastEventTime;
+		// The last MIDI note that was sent as a note on event.
+		int16 lastPlayedNote;
+		// The current MIDI note (upper byte) and note fraction (1/256th notes;
+		// lower byte) value.
+		uint16 currentNoteFraction;
+		// True if the allocated channel on the MIDI device has been changed to
+		// the SFX instrument.
+		bool programChanged;
+
+		// Delta to the note fraction. This is added to/subtracted from the
+		// note fraction every script tick.
+		int16 noteFractionDelta;
+		// The vibrato time. The number of script ticks it takes for the note
+		// difference to go from lowest to highest (or the other way around).
+		int16 vibratoTime;
+		// The number of script ticks that have passed since the vibrato has
+		// started.
+		int16 vibratoCounter;
+		// Vibrato delta to the note fraction. This is added to/subtracted
+		// from the note fraction every script tick.
+		int16 vibratoDelta;
+		// The number of ticks that remain before the next script event is
+		// processed.
+		int16 waitCounter;
+		// The script position at which the current loop has started.
+		byte loopStart;
+		// The number of times the looped section will be repeated (-1 for
+		// infinite loop).
+		int16 loopCounter;
+
+		// Completely clears the SFX slot data.
+		void clear();
+		// Resets the SFX slot data as needed by the reset opcode.
+		void reset();
+		// True if the current position is at the end of the script.
+		bool atEndOfScript();
+		// Reads the next script word. Specify the opCode flag to return a
+		// valid opcode.
+		int16 readScript(bool opCode);
+	};
+
+public:
+	SfxParser_Accolade();
+	virtual ~SfxParser_Accolade();
+
+	// Loads the specified sound effects bank (FXB file).
+	void load(Common::SeekableReadStream *in, int32 size);
+
+	// Sets the MIDI driver that should be used to output the SFX.
+	virtual void setMidiDriver(MidiDriver_Multisource *driver) = 0;
+	// Sets the number of microseconds between timer callbacks.
+	void setTimerRate(uint32 rate);
+
+	// Starts playback of the specified sound effect.
+	void play(uint8 sfxNumber);
+	// Stops all active SFX.
+	void stopAll();
+	// Pauses or unpauses all active SFX.
+	void pauseAll(bool paused);
+
+	void onTimer();
+	static void timerCallback(void *data);
+
+protected:
+	// Stops the sound effect playing in the specified slot.
+	void stop(SfxSlot *sfxSlot);
+	// Processes the specified opcode for the specified slot.
+	void processOpCode(SfxSlot *sfxSlot, byte opCode);
+
+	// Returns the number of sources available for SFX playback.
+	virtual byte getNumberOfSfxSources() = 0;
+	// Reads the SFX instrument data into the specified SfxData from the
+	// specified SFX bank data. This is positioned at the start of the data of
+	// a sound effect in the bank; when the function returns is should be
+	// positioned right after all instrument data for the sound effect.
+	virtual void readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) = 0;
+
+	// Loads the SFX instrument for the specified slot into the channel
+	// allocated to the sound effect. Returns true if the channel needs to be
+	// changed to the new instrument when the driver is ready.
+	virtual bool loadInstrument(SfxSlot *sfxSlot) = 0;
+	// Changes the channel allocated to the sound effect to the SFX instrument.
+	virtual void changeInstrument(SfxSlot *sfxSlot) { };
+	// Starts a note at the current note / note fraction for the slot.
+	virtual void noteOn(SfxSlot *sfxSlot);
+	// Stops the current note for the slot.
+	virtual void noteOff(SfxSlot *sfxSlot);
+	// Updates the note / note fraction for the slot.
+	virtual void updateNote(SfxSlot *sfxSlot) { };
+
+	Common::Mutex _mutex;
+
+	MidiDriver_Multisource *_driver;
+	uint32 _timerRate;
+
+	// Array of SFX data loaded from the SFX bank.
+	SfxData *_sfxData;
+	// The number of SFX data loaded.
+	uint16 _numSfx;
+	// The slots available for SFX playback.
+	SfxSlot _sfxSlots[4];
+	// The slot numbers allocated to the available SFX sources. -1 if no slot
+	// is using the source.
+	int8 _sourceAllocations[4];
+
+	// True if SFX playback is paused.
+	bool _paused;
+};
+
+class SfxParser_Accolade_AdLib : public SfxParser_Accolade {
+public:
+	SfxParser_Accolade_AdLib() : _adLibDriver(nullptr) { }
+
+protected:
+	void setMidiDriver(MidiDriver_Multisource *driver) override;
+	byte getNumberOfSfxSources() override;
+	void readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) override;
+	bool loadInstrument(SfxSlot *sfxSlot) override;
+	void noteOn(SfxSlot *sfxSlot) override;
+	void updateNote(SfxSlot *sfxSlot) override;
+
+	MidiDriver_Accolade_AdLib *_adLibDriver;
+};
+
+class SfxParser_Accolade_MT32 : public SfxParser_Accolade {
+public:
+	SfxParser_Accolade_MT32() : _mt32Driver(nullptr) { }
+
+protected:
+	void setMidiDriver(MidiDriver_Multisource *driver) override;
+	byte getNumberOfSfxSources() override;
+	void readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) override;
+	bool loadInstrument(SfxSlot *sfxSlot) override;
+	void changeInstrument(SfxSlot *sfxSlot) override;
+
+	MidiDriver_Accolade_MT32 *_mt32Driver;
+};
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/vga.cpp b/engines/agos/vga.cpp
index a8a2046b5dc..4e969f94aab 100644
--- a/engines/agos/vga.cpp
+++ b/engines/agos/vga.cpp
@@ -1161,14 +1161,17 @@ void AGOSEngine::vc28_playSFX() {
 	uint16 flags = vcReadNextWord();
 	debug(0, "vc28_playSFX: (sound %d, channels %d, frequency %d, flags %d)", sound, chans, freq, flags);
 
-	loadSound(sound, freq, flags);
+	// Waxworks DOS uses 2 opcodes to play SFX: this one for digital SFX and
+	// vc52 for MIDI SFX. If MIDI SFX are used, this opcode should be ignored;
+	// vc52 will play the corresponding MIDI SFX (if available).
+	playSfx(sound, freq, flags, !(getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS));
 }
 
 void AGOSEngine::vc29_stopAllSounds() {
 	if (getGameType() != GType_PP)
 		_sound->stopVoice();
 
-	_sound->stopAllSfx();
+	stopAllSfx();
 }
 
 void AGOSEngine::vc30_setFrameRate() {
diff --git a/engines/agos/vga_e2.cpp b/engines/agos/vga_e2.cpp
index 24c86a86174..836a1d3145b 100644
--- a/engines/agos/vga_e2.cpp
+++ b/engines/agos/vga_e2.cpp
@@ -191,12 +191,8 @@ void AGOSEngine::vc52_playSound() {
 			_sound->playEffects(sound);
 	} else if (getFeatures() & GF_TALKIE) {
 		_sound->playEffects(sound);
-	} else if (getGameId() == GID_SIMON1DOS) {
-		playSting(sound);
-	} else if (getGameType() == GType_WW) {
-		// TODO: Sound effects in PC version only
 	} else {
-		loadSound(sound, 0, 0);
+		playSfx(sound, 0, 0, true);
 	}
 }
 


Commit: d0c56f0bbc3bf932debd24628775e3460e42a9af
    https://github.com/scummvm/scummvm/commit/d0c56f0bbc3bf932debd24628775e3460e42a9af
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:44+02:00

Commit Message:
AUDIO: Remove GMF support from SMF MidiParser

This removes the code for the GMF MIDI format used by Simon The Sorcerer from
the SMF MIDI parser. The GMF format now has its own parser.

Changed paths:
    audio/midiparser.h
    audio/midiparser_smf.cpp
    audio/midiparser_smf.h


diff --git a/audio/midiparser.h b/audio/midiparser.h
index 5e2d1498d3d..4a2ee4eb4dc 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -384,13 +384,6 @@ public:
 	 * behavior.
 	 */
 	enum {
-		/**
-		 * Events containing a pitch bend command should be treated as
-		 * single-byte padding before the  real event. This allows the
-		 * MidiParser to work with some malformed SMF files from Simon 1/2.
-		 */
-		mpMalformedPitchBends = 1,
-
 		/**
 		 * Sets auto-looping, which can be used by lightweight clients
 		 * that don't provide their own flow control.
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index 5b2be3ce17e..16ed203addf 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -33,32 +33,15 @@ MidiParser_SMF::~MidiParser_SMF() {
 	free(_buffer);
 }
 
-void MidiParser_SMF::property(int prop, int value) {
-	switch (prop) {
-	case mpMalformedPitchBends:
-		_malformedPitchBends = (value > 0);
-		break;
-	default:
-		MidiParser::property(prop, value);
-		break;
-	}
-}
-
 void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 	info.start = _position._playPos;
 	info.delta = readVLQ(_position._playPos);
 
-	// Process the next info. If mpMalformedPitchBends
-	// was set, we must skip over any pitch bend events
-	// because they are from Simon games and are not
-	// real pitch bend events, they're just two-byte
-	// prefixes before the real info.
-	do {
-		if ((_position._playPos[0] & 0xF0) >= 0x80)
-			info.event = *(_position._playPos++);
-		else
-			info.event = _position._runningStatus;
-	} while (_malformedPitchBends && (info.event & 0xF0) == 0xE0 && _position._playPos++);
+	// Process the next info.
+	if ((_position._playPos[0] & 0xF0) >= 0x80)
+		info.event = *(_position._playPos++);
+	else
+		info.event = _position._runningStatus;
 	if (info.event < 0x80)
 		return;
 
@@ -135,11 +118,9 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 	uint32 len;
 	byte midiType;
-	bool isGMF;
 
 	unloadMusic();
 	byte *pos = data;
-	isGMF = false;
 
 	if (!memcmp(pos, "RIFF", 4)) {
 		// Skip the outer RIFF header.
@@ -166,16 +147,8 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 		}
 		_ppqn = pos[4] << 8 | pos[5];
 		pos += len;
-	} else if (!memcmp(pos, "GMF\x1", 4)) {
-		// Older GMD/MUS file with no header info.
-		// Assume 1 track, 192 PPQN, and no MTrk headers.
-		isGMF = true;
-		midiType = 0;
-		_numTracks = 1;
-		_ppqn = 192;
-		pos += 7; // 'GMD\x1' + 3 bytes of useless (translate: unknown) information
 	} else {
-		warning("Expected MThd or GMD header but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]);
+		warning("Expected MThd header but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]);
 		return false;
 	}
 
@@ -187,26 +160,17 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 
 	int tracksRead = 0;
 	while (tracksRead < _numTracks) {
-		if (memcmp(pos, "MTrk", 4) && !isGMF) {
+		if (memcmp(pos, "MTrk", 4)) {
 			warning("Position: %p ('%c')", (void *)pos, *pos);
 			warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
 			return false;
 		}
 
-		// If needed, skip the MTrk and length bytes
-		_tracks[tracksRead] = pos + (isGMF ? 0 : 8);
-		if (!isGMF) {
-			pos += 4;
-			len = read4high(pos);
-			pos += len;
-		} else {
-			// An SMF End of Track meta event must be placed
-			// at the end of the stream.
-			data[size++] = 0xFF;
-			data[size++] = 0x2F;
-			data[size++] = 0x00;
-			data[size++] = 0x00;
-		}
+		// Skip the MTrk and length bytes
+		_tracks[tracksRead] = pos + 8;
+		pos += 4;
+		len = read4high(pos);
+		pos += len;
 		++tracksRead;
 	}
 
@@ -220,7 +184,7 @@ bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
 		// Inherit the Earth MIDIs. Jamieson630 said something about a
 		// better fix, but this will have to do in the meantime.
 		_buffer = (byte *)malloc(size * 2);
-		compressToType0(_tracks, _numTracks, _buffer, _malformedPitchBends);
+		compressToType0(_tracks, _numTracks, _buffer, false);
 		_numTracks = 1;
 		_tracks[0] = _buffer;
 	}
diff --git a/audio/midiparser_smf.h b/audio/midiparser_smf.h
index d7eb03354ac..d7503116fce 100644
--- a/audio/midiparser_smf.h
+++ b/audio/midiparser_smf.h
@@ -30,7 +30,6 @@
 class MidiParser_SMF : public MidiParser {
 protected:
 	byte *_buffer;
-	bool _malformedPitchBends;
 
 protected:
 	/**
@@ -48,11 +47,10 @@ protected:
 	void parseNextEvent(EventInfo &info) override;
 
 public:
-	MidiParser_SMF(int8 source = -1) : MidiParser(source), _buffer(nullptr), _malformedPitchBends(false) {}
+	MidiParser_SMF(int8 source = -1) : MidiParser(source), _buffer(nullptr) {}
 	~MidiParser_SMF();
 
 	bool loadMusic(byte *data, uint32 size) override;
-	void property(int property, int value) override;
 
 	int32 determineDataSize(Common::SeekableReadStream *stream) override;
 };


Commit: 5e1905f5f68309ba90af8ee05c2dcaa8c1047a50
    https://github.com/scummvm/scummvm/commit/5e1905f5f68309ba90af8ee05c2dcaa8c1047a50
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:44+02:00

Commit Message:
AGOS: Waxworks OPL3 mode instrument attack fix

This fixes the slow attack for several instruments, which causes some barely
audible notes in OPL3 mode.

Changed paths:
    audio/adlib_ms.cpp
    engines/agos/drivers/accolade/adlib.cpp
    engines/agos/drivers/accolade/adlib.h
    engines/agos/midi.cpp


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index 2da9051bfab..ae8c8546a08 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -648,7 +648,7 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 	// will be played using the OPL rhythm register.
 	bool rhythmNote = _rhythmMode && channel == MIDI_RHYTHM_CHANNEL;
 
-	if (instrument.instrumentDef->isEmpty() ||
+	if (!instrument.instrumentDef || instrument.instrumentDef->isEmpty() ||
 			(rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) {
 		// Instrument definition contains no data or it is not suitable for
 		// rhythm mode, so the note cannot be played.
@@ -1286,7 +1286,7 @@ MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::deter
 			return instrument;
 
 		// Set the high bit for rhythm instrument IDs.
-		instrument.instrumentId = 0x80 & note;
+		instrument.instrumentId = 0x80 | note;
 		instrument.instrumentDef = &_rhythmBank[note - _rhythmBankFirstNote];
 		// Get the note to play from the instrument definition.
 		instrument.oplNote = instrument.instrumentDef->rhythmNote;
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
index 7b16af317c5..fbbcc3e818d 100644
--- a/engines/agos/drivers/accolade/adlib.cpp
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -253,6 +253,40 @@ void MidiDriver_Accolade_AdLib::updateSfxNote(uint8 source) {
 	writeFrequency(_channelAllocations[source][0]);
 }
 
+void MidiDriver_Accolade_AdLib::patchWwInstruments() {
+	// WORKAROUND Several instruments in Waxworks have a very slow attack (it
+	// takes a long time for a note to reach maximum volume). When a note
+	// played by this instrument is very short, only a small part of the attack
+	// phase is played and the note is barely audible. Example: the rapid notes
+	// in track 10 (played at the start of the London scenario).
+	// This problem only occurs in OPL3 mode. In OPL2 mode, these notes are all
+	// played on the same OPL channel. This means that each successive note
+	// builds on the volume reached by the previous note and apart from the
+	// first couple of notes they can be heard clearly. In OPL3 mode, each note
+	// is played on its own channel, so each note starts from 0 volume.
+	// This is fixed here by patching the attack value of this instrument to be
+	// 1/4th of the original length (from 3 to 5). The notes do not sound
+	// exactly as on OPL2, but they are clearly audible.
+
+	if (_oplType != OPL::Config::kOpl3)
+		// This workaround is only needed for OPL3 mode.
+		return;
+
+	// Patch the attack of instrument 0x22.
+	_instrumentBank[0x22].operator1.decayAttack &= 0x0F;
+	_instrumentBank[0x22].operator1.decayAttack |= 0x50;
+
+	// Patch the attack of instrument 0x25.
+	_instrumentBank[0x25].operator1.decayAttack &= 0x0F;
+	_instrumentBank[0x25].operator1.decayAttack |= 0x60;
+
+	// Patch the attack of instrument 0x7F.
+	_instrumentBank[0x7F].operator0.decayAttack &= 0x0F;
+	_instrumentBank[0x7F].operator0.decayAttack |= 0x60;
+	_instrumentBank[0x7F].operator1.decayAttack &= 0x0F;
+	_instrumentBank[0x7F].operator1.decayAttack |= 0x90;
+}
+
 MidiDriver_Accolade_AdLib::InstrumentInfo MidiDriver_Accolade_AdLib::determineInstrument(uint8 channel, uint8 source, uint8 note) {
 	if (_sources[source].type == SOURCE_TYPE_SFX) {
 		// For SFX sources, return an instrument from the SFX bank.
diff --git a/engines/agos/drivers/accolade/adlib.h b/engines/agos/drivers/accolade/adlib.h
index 595b2d751e3..5300886e670 100644
--- a/engines/agos/drivers/accolade/adlib.h
+++ b/engines/agos/drivers/accolade/adlib.h
@@ -54,6 +54,8 @@ public:
 	void setSfxNoteFraction(uint8 source, uint16 noteFraction);
 	// Writes out the current frequency for the specified SFX source.
 	void updateSfxNote(uint8 source);
+	// Applies a workaround for a Waxworks OPL3 instrument issue.
+	void patchWwInstruments();
 
 protected:
 	InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note) override;
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 65069f865bf..767fd6e7dd2 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -198,13 +198,18 @@ int MidiPlayer::open() {
 		switch (_deviceType) {
 		case MT_ADLIB:
 			_driverMsMusic = _driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
-			if (_vm->getGameType() == GType_WW)
+			if (_vm->getGameType() == GType_WW) {
 				// WORKAROUND Some Waxworks tracks do not set an instrument on
 				// a MIDI channel. This will cause that channel to play with
 				// whatever instrument was set there by a previous track.
 				// This is fixed by setting default instruments on all channels
 				// before starting a track.
 				_driverMsMusic->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM);
+				if (oplType == OPL::Config::kOpl3)
+					// WORKAROUND Some Waxworks OPL instruments do not work
+					// well in OPL3 mode and need some adjustments.
+					static_cast<MidiDriver_Accolade_AdLib *>(_driverMsMusic)->patchWwInstruments();
+			}
 			if (usesMidiSfx)
 				// Elvira 2 and Waxworks have AdLib SFX.
 				_parserSfxAccolade = new SfxParser_Accolade_AdLib();


Commit: 27e14762e7986648d3c19a9af4bd9893593efc0c
    https://github.com/scummvm/scummvm/commit/27e14762e7986648d3c19a9af4bd9893593efc0c
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:44+02:00

Commit Message:
AUDIO: Add Casio MT-540/CT-460/CSM-1 MIDI driver

This adds a MIDI driver for the Casio MT-540, CT-460 and CSM-1 devices. These
are supported by Elvira 1 and several Sierra SCI0 games as well as Altered
Destiny and Les Manley 1.

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


diff --git a/audio/casio.cpp b/audio/casio.cpp
new file mode 100644
index 00000000000..b084d2c5195
--- /dev/null
+++ b/audio/casio.cpp
@@ -0,0 +1,362 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "audio/casio.h"
+
+#include "common/config-manager.h"
+
+MidiDriver_Casio::ActiveNote::ActiveNote() {
+	clear();
+}
+
+void MidiDriver_Casio::ActiveNote::clear() {
+	source = 0x7F;
+	channel = 0xFF;
+	note = 0xFF;
+}
+
+const uint8 MidiDriver_Casio::INSTRUMENT_REMAPPING_CT460_TO_MT540[30] {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0E, 0x0A, 0x07,
+	0x09, 0x1B, 0x0F, 0x10, 0x11, 0x14, 0x08, 0x15, 0x0B, 0x0C,
+	0x0D, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1C, 0x1D, 0x12, 0x13
+};
+
+const uint8 MidiDriver_Casio::INSTRUMENT_REMAPPING_MT540_TO_CT460[30] {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x10, 0x0A,
+	0x08, 0x12, 0x13, 0x14, 0x07, 0x0C, 0x0D, 0x0E, 0x1C, 0x1D,
+	0x0F, 0x11, 0x15, 0x16, 0x17, 0x18, 0x19, 0x0B, 0x1A, 0x1B
+};
+
+const uint8 MidiDriver_Casio::RHYTHM_INSTRUMENT_MT540 = 0x10;
+const uint8 MidiDriver_Casio::RHYTHM_INSTRUMENT_CT460 = 0x0D;
+
+MidiDriver_Casio::MidiDriver_Casio(MusicType midiType) : _midiType(midiType), _driver(nullptr),
+		_deviceType(MT_CT460), _isOpen(false), _rhythmNoteRemapping(nullptr) {
+	if (!(_midiType == MT_MT540 || _midiType == MT_CT460)) {
+		error("MidiDriver_Casio - Unsupported music data type %i", midiType);
+	}
+	Common::fill(_instruments, _instruments + ARRAYSIZE(_instruments), 0);
+}
+
+MidiDriver_Casio::~MidiDriver_Casio() {
+	close();
+
+	if (_driver) {
+		_driver->setTimerCallback(nullptr, nullptr);
+		delete _driver;
+		_driver = nullptr;
+	}
+}
+
+int MidiDriver_Casio::open() {
+	assert(!_driver);
+
+	// Detect the output device.
+	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_GM);
+	MusicType deviceMusicType = MidiDriver::getMusicType(dev);
+	// System MIDI ports have type GM. This driver supports no other type.
+	if (deviceMusicType != MT_GM)
+		error("MidiDriver_Casio::open - Detected device has unsupported type %i", deviceMusicType);
+
+	// Create the driver.
+	MidiDriver *driver = MidiDriver::createMidi(dev);
+
+	// Check the MIDI mode setting to determine if the device connected to the
+	// system MIDI port is an MT-540 or a CT-460/CSM-1.
+	int midiMode = ConfMan.getInt("midi_mode");
+	MusicType deviceType;
+	if (midiMode == 3) {
+		deviceType = MT_MT540;
+	} else if (midiMode == 4) {
+		deviceType = MT_CT460;
+	} else {
+		error("MidiDriver_Casio::open - Unsupported MIDI mode %i", midiMode);
+	}
+
+	return open(driver, deviceType);
+}
+
+int MidiDriver_Casio::open(MidiDriver* driver, MusicType deviceType) {
+	assert(!_driver);
+
+	_driver = driver;
+	_deviceType = deviceType;
+	if (!(_deviceType == MT_MT540 || _deviceType == MT_CT460)) {
+		error("MidiDriver_Casio::open - Unsupported device type %i", _deviceType);
+	}
+
+	if (!_driver)
+		return 255;
+
+	int result = _driver->open();
+	if (result != MidiDriver::MERR_ALREADY_OPEN && result != 0) {
+		return result;
+	}
+
+	// Initialize the device by setting the instrument to 0 on all channels.
+	for (int i = 0; i < 4; i++) {
+		programChange(i, 0, -1);
+	}
+
+	_timerRate = _driver->getBaseTempo();
+	_driver->setTimerCallback(this, timerCallback);
+
+	_isOpen = true;
+
+	return 0;
+}
+
+void MidiDriver_Casio::close() {
+	if (_driver && _isOpen) {
+		stopAllNotes();
+
+		_driver->close();
+		_isOpen = false;
+	}
+}
+
+bool MidiDriver_Casio::isOpen() const {
+	return _isOpen;
+}
+
+void MidiDriver_Casio::send(int8 source, uint32 b) {
+	byte dataChannel = b & 0xf;
+	int8 outputChannel = source < 0 ? dataChannel : mapSourceChannel(source, dataChannel);
+	if (outputChannel < 0 || outputChannel >= 4)
+		// Only process events for channels 0-3.
+		return;
+
+	processEvent(source, b, outputChannel);
+}
+
+void MidiDriver_Casio::metaEvent(int8 source, byte type, byte *data, uint16 length) {
+	assert(source < MAXIMUM_SOURCES);
+
+	if (type == 0x2F && source >= 0) // End of Track
+		deinitSource(source);
+
+	_driver->metaEvent(type, data, length);
+}
+
+int8 MidiDriver_Casio::mapSourceChannel(uint8 source, uint8 dataChannel) {
+	// TODO Multisource functionality has not been fully implemented. Current
+	// implementation assumes 1 source with access to all channels.
+	return dataChannel;
+}
+
+void MidiDriver_Casio::processEvent(int8 source, uint32 b, uint8 outputChannel) {
+	assert(source < MAXIMUM_SOURCES);
+
+	byte command = b & 0xF0;
+	byte op1 = (b >> 8) & 0xFF;
+	byte op2 = (b >> 16) & 0xFF;
+
+	// The only commands supported by the Casio devices are note on, note off
+	// and program change.
+	switch (command) {
+	case MIDI_COMMAND_NOTE_OFF:
+		noteOff(outputChannel, command, op1, op2, source);
+		break;
+	case MIDI_COMMAND_NOTE_ON:
+		noteOn(outputChannel, op1, op2, source);
+		break;
+	case MIDI_COMMAND_PROGRAM_CHANGE:
+		programChange(outputChannel, op1, source);
+		break;
+	default:
+		warning("MidiDriver_Casio::processEvent - Received unsupported event %02x", command);
+		break;
+	}
+}
+
+void MidiDriver_Casio::noteOff(byte outputChannel, byte command, byte note, byte velocity, int8 source) {
+	// Apply rhythm note mapping.
+	int8 mappedNote = mapNote(outputChannel, note);
+	if (mappedNote < 0)
+		// Rhythm note with no Casio equivalent.
+		return;
+
+	_mutex.lock();
+
+	// Remove this note from the active note registry.
+	for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
+		if (_activeNotes[i].channel == outputChannel && _activeNotes[i].note == mappedNote &&
+				_activeNotes[i].source == source) {
+			_activeNotes[i].clear();
+			break;
+		}
+	}
+
+	_mutex.unlock();
+
+	_driver->send(command | outputChannel, mappedNote, velocity);
+}
+
+void MidiDriver_Casio::noteOn(byte outputChannel, byte note, byte velocity, int8 source) {
+	if (velocity == 0) {
+		// Note on with velocity 0 is a note off.
+		noteOff(outputChannel, MIDI_COMMAND_NOTE_ON, note, velocity, source);
+		return;
+	}
+
+	// Apply rhythm note mapping.
+	int8 mappedNote = mapNote(outputChannel, note);
+	if (mappedNote < 0)
+		// Rhythm note with no Casio equivalent.
+		return;
+
+	_mutex.lock();
+
+	// Add this note to the active note registry.
+	for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
+		if (_activeNotes[i].note == 0xFF) {
+			_activeNotes[i].channel = outputChannel;
+			_activeNotes[i].note = mappedNote;
+			_activeNotes[i].source = source;
+			break;
+		}
+	}
+
+	_mutex.unlock();
+
+	byte calculatedVelocity = calculateVelocity(source, velocity);
+
+	_driver->send(MIDI_COMMAND_NOTE_ON | outputChannel, mappedNote, calculatedVelocity);
+}
+
+int8 MidiDriver_Casio::mapNote(byte outputChannel, byte note) {
+	int8 mappedNote = note;
+
+	if (_rhythmNoteRemapping && isRhythmChannel(outputChannel)) {
+		mappedNote = _rhythmNoteRemapping[note];
+		if (mappedNote == 0)
+			mappedNote = -1;
+	}
+
+	return mappedNote;
+}
+
+byte MidiDriver_Casio::calculateVelocity(int8 source, byte velocity) {
+	byte calculatedVelocity = velocity;
+	// Apply volume settings to velocity.
+	if (source >= 0) {
+		// Scale to source volume.
+		calculatedVelocity = (velocity * _sources[source].volume) / _sources[source].neutralVolume;
+	}
+	if (_userVolumeScaling) {
+		if (_userMute) {
+			calculatedVelocity = 0;
+		} else {
+			// Scale to user volume.
+			uint16 userVolume = _sources[source].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume;
+			calculatedVelocity = (calculatedVelocity * userVolume) >> 8;
+		}
+	}
+	// Source volume scaling might clip volume, so reduce to maximum,
+	return MIN(calculatedVelocity, static_cast<byte>(0x7F));
+}
+
+void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source) {
+	if (outputChannel < 4)
+		// Register the new instrument.
+		_instruments[outputChannel] = patchId;
+
+	// Apply instrument mapping.
+	byte mappedInstrument = mapInstrument(patchId);
+
+	_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (mappedInstrument << 8));
+}
+
+byte MidiDriver_Casio::mapInstrument(byte program) {
+	byte mappedInstrument = program;
+
+	if (_instrumentRemapping)
+		// Apply custom instrument mapping.
+		mappedInstrument = _instrumentRemapping[program];
+
+	// Apply MT-540 <> CT-460 instrument mapping if necessary.
+	if (mappedInstrument < ARRAYSIZE(INSTRUMENT_REMAPPING_MT540_TO_CT460)) {
+		if (_midiType == MT_MT540 && _deviceType == MT_CT460) {
+			mappedInstrument = INSTRUMENT_REMAPPING_MT540_TO_CT460[mappedInstrument];
+		} else if (_midiType == MT_CT460 && _deviceType == MT_MT540) {
+			mappedInstrument = INSTRUMENT_REMAPPING_CT460_TO_MT540[mappedInstrument];
+		}
+	}
+
+	return mappedInstrument;
+}
+
+bool MidiDriver_Casio::isRhythmChannel(uint8 outputChannel) {
+	if (outputChannel >= 4)
+		return false;
+
+	// Check if the current instrument on the channel is the rhythm instrument.
+	byte currentInstrument = mapInstrument(_instruments[outputChannel]);
+	byte rhythmInstrument = _deviceType == MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460;
+
+	return currentInstrument == rhythmInstrument;
+}
+
+void MidiDriver_Casio::stopAllNotes(bool stopSustainedNotes) {
+	stopAllNotes(0xFF, 0xFF);
+}
+
+void MidiDriver_Casio::stopAllNotes(uint8 source, uint8 channel) {
+	_mutex.lock();
+
+	// Send note off events for all notes in the active note registry for this
+	// source and channel.
+	for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
+		if (_activeNotes[i].note != 0xFF && (source == 0xFF || _activeNotes[i].source == source) &&
+				(channel == 0xFF || _activeNotes[i].channel == channel)) {
+			noteOff(_activeNotes[i].channel, MIDI_COMMAND_NOTE_OFF, _activeNotes[i].note, 0, _activeNotes[i].source);
+		}
+	}
+
+	_mutex.unlock();
+}
+
+void MidiDriver_Casio::applySourceVolume(uint8 source) {
+	// Because the Casio devices do not support the volume controller, source
+	// volume is applied to note velocity and it cannot be applied directly.
+}
+
+MidiChannel *MidiDriver_Casio::allocateChannel() {
+	// MidiChannel objects are not supported by this driver.
+	return nullptr;
+}
+
+MidiChannel *MidiDriver_Casio::getPercussionChannel() {
+	// MidiChannel objects are not supported by this driver.
+	return nullptr;
+}
+
+uint32 MidiDriver_Casio::getBaseTempo() {
+	if (_driver) {
+		return _driver->getBaseTempo();
+	}
+	return 0;
+}
+
+void MidiDriver_Casio::timerCallback(void *data) {
+	MidiDriver_Casio *driver = static_cast<MidiDriver_Casio *>(data);
+	driver->onTimer();
+}
diff --git a/audio/casio.h b/audio/casio.h
new file mode 100644
index 00000000000..81edee81888
--- /dev/null
+++ b/audio/casio.h
@@ -0,0 +1,243 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AUDIO_CASIO_H
+#define AUDIO_CASIO_H
+
+#include "audio/mididrv.h"
+#include "audio/mididrv_ms.h"
+
+/**
+ * MIDI driver implementation for the Casio MT-540, CT-640 and CSM-1 devices.
+ *
+ * This driver provides source volume and user volume scaling, as well as
+ * fades (due to device limitations these are applied to note velocity instead
+ * of the volume controller). It also provides instrument mapping between the
+ * MT-540 and CT-640/CSM-1 instrument map and can map rhythm notes from the 
+ * input MIDI data by specifying a remapping.
+ *
+ * TODO This driver does not provide a full multisource functionality
+ * implementation because this was not needed for the game for which it was
+ * added (Elvira). It assumes only 1 source sends MIDI data at the same time
+ * and this source has access to all MIDI channels.
+ *
+ * Some details of these Casio devices:
+ * - They seem to support only note on, note off and program change MIDI
+ *   events. Because they do not support the volume controller, volume is
+ *   applied to note velocity (and I'm not sure if they support even that...).
+ *   All Notes Off is performed by keeping track of active notes and sending
+ *   note off events for all active notes.
+ * - They only use MIDI channels 0-3. The MT-32 and GM devices have channel 9
+ *   as the fixed rhythm channel. The Casio devices can switch any used channel
+ *   to a rhythm channel by setting a specific instrument.
+ * - They have only 30 instruments, 3 of which are rhythm or SFX banks.
+ * - All devices seem to have the same capabilities, but the instrument
+ *   numbering is different between the MT-540 on the one hand and the CT-640
+ *   and CSM-1 on the other.
+ */
+class MidiDriver_Casio : public MidiDriver_Multisource {
+protected:
+	// Tracks a note currently playing on the device.
+	struct ActiveNote {
+		// The source that played the note (0x7F if no note is tracked).
+		int8 source;
+		// The output MIDI channel on which the note is playing
+		// (0xFF if no note is tracked).
+		uint8 channel;
+		// The MIDI note number of the playing note
+		// (0xFF if no note is tracked).
+		uint8 note;
+
+		ActiveNote();
+
+		// Sets the struct to values indicating no note is currently tracked.
+		void clear();
+	};
+
+public:
+	// Array for remapping instrument numbers from CT-460/CSM-1 to MT-540.
+	static const uint8 INSTRUMENT_REMAPPING_CT460_TO_MT540[30];
+	// Array for remapping instrument numbers from MT-540 to CT-460/CSM-1.
+	static const uint8 INSTRUMENT_REMAPPING_MT540_TO_CT460[30];
+
+	// The instrument number used for rhythm sounds on the MT-540.
+	static const uint8 RHYTHM_INSTRUMENT_MT540;
+	// The instrument number used for rhythm sounds on the CT-460 and CSM-1.
+	static const uint8 RHYTHM_INSTRUMENT_CT460;
+
+	/**
+	 * Constructs a new Casio MidiDriver instance.
+	 * 
+	 * @param midiType The type of MIDI data that will be sent to the driver
+	 * (MT-540 or CT-460/CSM-1).
+	 */
+	MidiDriver_Casio(MusicType midiType);
+	~MidiDriver_Casio();
+
+	int open() override;
+	/**
+	 * Opens the driver wrapping the specified MidiDriver instance.
+	 * 
+	 * @param driver The driver that will receive MIDI events from this driver.
+	 * @param deviceType The type of MIDI device that will receive MIDI events
+	 * from this driver (MT-540 or CT-460/CSM-1).
+	 * @return 0 if the driver was opened successfully; >0 if an error occured.
+	 */
+	virtual int open(MidiDriver *driver, MusicType deviceType);
+	void close() override;
+	bool isOpen() const override;
+
+	using MidiDriver_BASE::send;
+	void send(int8 source, uint32 b) override;
+	void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
+
+	void stopAllNotes(bool stopSustainedNotes = false) override;
+	MidiChannel *allocateChannel() override;
+	MidiChannel *getPercussionChannel() override;
+	uint32 getBaseTempo() override;
+
+protected:
+	/**
+	 * Maps a data MIDI channel to an output MIDI channel for the specified
+	 * source.
+	 * TODO This driver has no default implementation for a channel allocation
+	 * scheme. It assumes only one source is active at a time and has access to
+	 * all output channels. The default implementation for this method just
+	 * returns the data channel.
+	 * 
+	 * @param source The source for which the MIDI channel should be mapped.
+	 * @param dataChannel The data channel that should be mapped.
+	 * @return The output MIDI channel.
+	 */
+	virtual int8 mapSourceChannel(uint8 source, uint8 dataChannel);
+	/**
+	 * Processes a MIDI event.
+	 * 
+	 * @param source The source sending the MIDI event.
+	 * @param b The MIDI event data.
+	 * @param outputChannel The MIDI channel on which the event should be sent.
+	 */
+	virtual void processEvent(int8 source, uint32 b, uint8 outputChannel);
+	/**
+	 * Processes a MIDI note off event.
+	 * 
+	 * @param outputChannel The MIDI channel on which the event should be sent.
+	 * @param command The MIDI command that triggered the note off event (other
+	 * than note off (0x80) this can also be note on (0x90) with velocity 0).
+	 * @param note The MIDI note that should be turned off.
+	 * @param velocity The note off velocity.
+	 * @param source The source sending the MIDI event.
+	 */
+	virtual void noteOff(byte outputChannel, byte command, byte note, byte velocity, int8 source);
+	/**
+	 * Processes a MIDI note on event.
+	 * 
+	 * @param outputChannel The MIDI channel on which the event should be sent.
+	 * @param note The MIDI note that should be turned on.
+	 * @param velocity The note velocity,
+	 * @param source The source sending the MIDI event.
+	 */
+	virtual void noteOn(byte outputChannel, byte note, byte velocity, int8 source);
+	/**
+	 * Processes a MIDI program change event.
+	 * 
+	 * @param outputChannel The MIDI channel on which the event should be sent.
+	 * @param patchId The instrument that should be set.
+	 * @param source The source sending the MIDI event.
+	 */
+	virtual void programChange(byte outputChannel, byte patchId, int8 source);
+
+	/**
+	 * Maps the specified note to a different note according to the rhythm note
+	 * mapping. This mapping is only applied if the note is played on a rhythm
+	 * channel.
+	 * 
+	 * @param outputChannel The MIDI channel on which the note is/will be
+	 * active.
+	 * @param note The note that should be mapped.
+	 * @return The mapped note, or the specified note if it was not mapped.
+	 */
+	virtual int8 mapNote(byte outputChannel, byte note);
+	/**
+	 * Calculates the velocity for a note on event. This applies source volume
+	 * and user volume settings to the specified velocity value.
+	 * 
+	 * @param source The source that sent the note on event.
+	 * @param velocity The velocity specified in the note on event.
+	 * @return The calculated velocity.
+	 */
+	virtual byte calculateVelocity(int8 source, byte velocity);
+	/**
+	 * Maps the specified instrument to the instrument value that should be
+	 * sent to the MIDI device. This applies the current instrument remapping
+	 * (if present) and maps MT-540 instruments to CT-460/CSM-1 instruments
+	 * (or the other way around) if necessary.
+	 * 
+	 * @param program The instrument that should be mapped.
+	 * @return The mapped instrument, or the specified instrument if no mapping
+	 * was necessary.
+	 */
+	virtual byte mapInstrument(byte program);
+	/**
+	 * Returns whether the specified MIDI channel is a rhythm channel. On the
+	 * Casio devices, the rhythm channel is not fixed but is created by setting
+	 * a channel to a specific instrument (see the rhythm instrument constants).
+	 * 
+	 * @param outputChannel The channel that should be checked.
+	 * @return True if the specified channel is a rhythm channel, false
+	 * otherwise.
+	 */
+	bool isRhythmChannel(uint8 outputChannel);
+	// This implementation does nothing, because source volume is applied to
+	// note velocity and cannot be applied immediately.
+	void applySourceVolume(uint8 source) override;
+	void stopAllNotes(uint8 source, uint8 channel) override;
+
+	// The wrapped MIDI driver.
+	MidiDriver *_driver;
+	// The type of Casio device accessed by the wrapped driver: MT-540 or
+	// CT-460/CSM-1.
+	MusicType _deviceType;
+	// The type of MIDI data supplied to the driver: MT-540 or CT-460/CSM-1.
+	MusicType _midiType;
+	// True if this MIDI driver has been opened.
+	bool _isOpen;
+
+	// The current instrument on each MIDI output channel. This is the
+	// instrument number as specified in the program change events (before
+	// remapping is applied).
+	byte _instruments[4];
+	// Tracks the notes currently active on the MIDI device.
+	ActiveNote _activeNotes[32];
+
+	// Optional remapping for rhythm notes. Should point to a 128 byte array
+	// which maps the rhythm note numbers in the input MIDI data to the rhythm
+	// note numbers used by the output device.
+	byte *_rhythmNoteRemapping;
+
+	// Mutex for operations on active notes.
+	Common::Mutex _mutex;
+
+public:
+	static void timerCallback(void *data);
+};
+
+#endif
diff --git a/audio/mididrv.h b/audio/mididrv.h
index 275dd7b1235..243483fad84 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -57,7 +57,9 @@ enum MusicType {
 	MT_SEGACD,			// SegaCD
 	MT_GM,				// General MIDI
 	MT_MT32,			// MT-32
-	MT_GS				// Roland GS
+	MT_GS,				// Roland GS
+	MT_MT540,			// Casio MT-540
+	MT_CT460			// Casio CT-460 / CSM-1
 };
 
 /**
diff --git a/audio/module.mk b/audio/module.mk
index b2a413f6e44..8519db4438c 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
 	adlib.o \
 	adlib_ms.o \
 	audiostream.o \
+	casio.o \
 	fmopl.o \
 	mididrv.o \
 	mididrv_ms.o \


Commit: 5e95bdcbda9b5da59dace2ca7804dafc97b15778
    https://github.com/scummvm/scummvm/commit/5e95bdcbda9b5da59dace2ca7804dafc97b15778
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:44+02:00

Commit Message:
AGOS: Elvira 1 - add support for Casio devices

This adds support for the Casio MT-540, CT-460 and CSM-1 for Elvira 1.

Changed paths:
  A engines/agos/drivers/accolade/casio.cpp
  A engines/agos/drivers/accolade/casio.h
    engines/agos/drivers/accolade/driverfile.cpp
    engines/agos/drivers/accolade/mididriver.h
    engines/agos/midi.cpp
    engines/agos/module.mk


diff --git a/engines/agos/drivers/accolade/casio.cpp b/engines/agos/drivers/accolade/casio.cpp
new file mode 100644
index 00000000000..a15f68666d9
--- /dev/null
+++ b/engines/agos/drivers/accolade/casio.cpp
@@ -0,0 +1,110 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agos/drivers/accolade/casio.h"
+
+#include "agos/drivers/accolade/mididriver.h"
+
+namespace AGOS {
+
+MidiDriver_Accolade_Casio::MidiDriver_Accolade_Casio() : MidiDriver_Casio(MT_CT460) {
+	Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
+	Common::fill(_instrumentRemappingData, _instrumentRemappingData + ARRAYSIZE(_instrumentRemappingData), 0);
+	Common::fill(_rhythmNoteRemappingData, _rhythmNoteRemappingData + ARRAYSIZE(_rhythmNoteRemappingData), 0);
+}
+
+int MidiDriver_Accolade_Casio::open() {
+	int result = MidiDriver_Casio::open();
+
+	// Apply instrument and rhythm note remapping after device initialization.
+	_instrumentRemapping = _instrumentRemappingData;
+	// WORKAROUND The MT-32 and Casio devices use a different mapping of notes
+	// to instruments on the rhythm channel. The INSTR.DAT file of Elvira 1
+	// contains a remapping of the rhythm notes in the game's MT-32 MIDI data
+	// to the Casio's rhythm notes, but this does not seem to be used - the
+	// game outputs the MT-32 rhythm notes when CT-460 is selected. As a result
+	// the wrong rhythm instruments are played. This is fixed here by using the
+	// game's remapping data to properly remap the rhythm notes to the notes
+	// that the Casio devices use.
+	_rhythmNoteRemapping = _rhythmNoteRemappingData;
+
+	return result;
+}
+
+int8 MidiDriver_Accolade_Casio::mapSourceChannel(uint8 source, uint8 dataChannel) {
+	if (!_isOpen)
+		// Use 1 on 1 mapping during device initialization.
+		return dataChannel;
+
+	return _channelRemapping[dataChannel];
+}
+
+void MidiDriver_Accolade_Casio::readDriverData(byte *driverData, uint16 driverDataSize) {
+	uint16 minDataSize = 354;
+	if (driverDataSize < minDataSize)
+		error("MidiDriver_Accolade_Casio::readDriverData - Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
+
+	// INSTR.DAT Data is like this:
+	// 128 bytes  instrument mapping
+	// 128 bytes  instrument volume adjust (signed!) (not used for Casio)
+	//  16 bytes  unknown
+	//  16 bytes  channel mapping
+	//  64 bytes  key note mapping
+	//   1 byte   instrument count
+	//   1 byte   bytes per instrument
+	//   x bytes  no instruments used for Casio
+
+	uint16 channelMappingOffset = 256 + 16;
+	Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
+
+	uint16 instrumentMappingOffset = 0;
+	Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemappingData), _instrumentRemappingData);
+
+	uint16 rhythmNoteMappingOffset = 256 + 32;
+	// 64 bytes are reserved for the rhythm note mapping, but only 40 seem to
+	// be used. The first mapping is for note 0x24.
+	Common::copy(driverData + rhythmNoteMappingOffset, driverData + rhythmNoteMappingOffset + 40, _rhythmNoteRemappingData + 0x24);
+}
+
+MidiDriver_Multisource *MidiDriver_Accolade_Casio_create(Common::String driverFilename) {
+	byte *driverData = nullptr;
+	uint16 driverDataSize = 0;
+	bool newVersion = false;
+
+	MidiDriver_Accolade_readDriver(driverFilename, MT_CT460, driverData, driverDataSize, newVersion);
+	if (!driverData)
+		error("MidiDriver_Accolade_Casio_create - Error during readDriver()");
+
+	if (newVersion)
+		// Only Elvira 1 has support for Casio and this uses the old drivers.
+		error("MidiDriver_Accolade_Casio_create - Driver not supported for Elvira 2 / Waxworks / Simon 1 demo");
+
+	MidiDriver_Accolade_Casio *driver = new MidiDriver_Accolade_Casio();
+	if (!driver)
+		error("MidiDriver_Accolade_Casio_create - Could not create driver");
+
+	driver->readDriverData(driverData, driverDataSize);
+
+	delete[] driverData;
+	return driver;
+}
+
+} // End of namespace AGOS
diff --git a/engines/agos/drivers/accolade/casio.h b/engines/agos/drivers/accolade/casio.h
new file mode 100644
index 00000000000..4d1bbafa574
--- /dev/null
+++ b/engines/agos/drivers/accolade/casio.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGOS_DRIVERS_ACCOLADE_CASIO_H
+#define AGOS_DRIVERS_ACCOLADE_CASIO_H
+
+#include "audio/casio.h"
+
+namespace AGOS {
+
+class MidiDriver_Accolade_Casio : public MidiDriver_Casio {
+public:
+	MidiDriver_Accolade_Casio();
+
+	int open();
+	int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
+
+	void readDriverData(byte *driverData, uint16 driverDataSize);
+
+protected:
+	// Mapping between MT-32 data MIDI channels and Casio channels.
+	byte _channelRemapping[16];
+	// Mapping between MT-32 data instruments and Casio instruments.
+	byte _instrumentRemappingData[128];
+	// Mapping between MT-32 data rhythm notes and Casio rhythm notes.
+	byte _rhythmNoteRemappingData[128];
+};
+
+} // End of namespace AGOS
+
+#endif
diff --git a/engines/agos/drivers/accolade/driverfile.cpp b/engines/agos/drivers/accolade/driverfile.cpp
index 153761addbf..12c65debb84 100644
--- a/engines/agos/drivers/accolade/driverfile.cpp
+++ b/engines/agos/drivers/accolade/driverfile.cpp
@@ -51,6 +51,9 @@ void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requested
 		case MT_MT32:
 			skipChunks = 1; // Skip one entry for MT32
 			break;
+		case MT_CT460:
+			skipChunks = 2; // CT-460 data is the third entry
+			break;
 		default:
 			assert(0);
 			break;
diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h
index 3b6df3f0702..2ffcdf34413 100644
--- a/engines/agos/drivers/accolade/mididriver.h
+++ b/engines/agos/drivers/accolade/mididriver.h
@@ -41,6 +41,7 @@ extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType re
 
 extern MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType);
 extern MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
+extern MidiDriver_Multisource *MidiDriver_Accolade_Casio_create(Common::String driverFilename);
 extern MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev);
 
 } // End of namespace AGOS
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 767fd6e7dd2..2cbb88b5efd 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -150,6 +150,18 @@ int MidiPlayer::open() {
 	// Check the type of device that the user has configured.
 	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
 	_deviceType = MidiDriver::getMusicType(dev);
+
+	// Check if a Casio device has been configured for Elvira 1 DOS.
+	if (_vm->getGameType() == GType_ELVIRA1 && _vm->getPlatform() == Common::kPlatformDOS &&
+			_deviceType == MT_GM && ConfMan.hasKey("midi_mode")) {
+		int midiMode = ConfMan.getInt("midi_mode");
+		if (midiMode == 3) {
+			_deviceType = MT_MT540;
+		} else if (midiMode == 4) {
+			_deviceType = MT_CT460;
+		}
+	}
+
 	if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
 		_deviceType = MT_MT32;
 
@@ -236,6 +248,11 @@ int MidiPlayer::open() {
 				}
 			}
 			break;
+		case MT_MT540:
+		case MT_CT460:
+			// Casio devices are supported by Elvira 1 DOS only.
+			_driverMsMusic = MidiDriver_Accolade_Casio_create(accoladeDriverFilename);
+			break;
 		default:
 			_driverMsMusic = new MidiDriver_NULL_Multisource();
 			break;
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index 99c2468c856..fc68522b55d 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -2,6 +2,7 @@ MODULE := engines/agos
 
 MODULE_OBJS := \
 	drivers/accolade/adlib.o \
+	drivers/accolade/casio.o \
 	drivers/accolade/driverfile.o \
 	drivers/accolade/pc98.o \
 	drivers/accolade/mt32.o \


Commit: b2cf9a580bf9a417d955480a9c207225afc9bb75
    https://github.com/scummvm/scummvm/commit/b2cf9a580bf9a417d955480a9c207225afc9bb75
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:44+02:00

Commit Message:
AUDIO: Add AdLib MS driver callback frequency

This change allows a consumer to specify the desired timer callback frequency
for the AdLib multisource MIDI driver.

Changed paths:
    audio/adlib_ms.cpp
    audio/adlib_ms.h


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index ae8c8546a08..56942d62c8c 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -394,7 +394,7 @@ 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) :
+MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType, int timerFrequency) :
 		_oplType(oplType),
 		_opl(nullptr),
 		_isOpen(false),
@@ -413,9 +413,11 @@ MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType
 		_melodicChannels(nullptr),
 		_numMelodicChannels(0),
 		_noteCounter(1),
-		_oplFrequencyConversionFactor(pow(2, 20) / 49716.0f) {
+		_oplFrequencyConversionFactor(pow(2, 20) / 49716.0f),
+		_timerFrequency(timerFrequency) {
 	memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
 	Common::fill(_shadowRegisters, _shadowRegisters + sizeof(_shadowRegisters), 0);
+	_timerRate = 1000000 / _timerFrequency;
 }
 
 MidiDriver_ADLIB_Multisource::~MidiDriver_ADLIB_Multisource() {
@@ -463,10 +465,9 @@ int MidiDriver_ADLIB_Multisource::open() {
 	// 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));
+	_opl->start(new Common::Functor0Mem<void, MidiDriver_ADLIB_Multisource>(this, &MidiDriver_ADLIB_Multisource::onTimer), _timerFrequency);
 
 	return 0;
 }
@@ -533,7 +534,7 @@ uint32 MidiDriver_ADLIB_Multisource::property(int prop, uint32 param) {
 }
 
 uint32 MidiDriver_ADLIB_Multisource::getBaseTempo() {
-	return 1000000 / OPL::OPL::kDefaultCallbackFrequency;
+	return _timerRate;
 }
 
 MidiChannel *MidiDriver_ADLIB_Multisource::allocateChannel() {
diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h
index 235ba8e3aa6..53231123c0a 100644
--- a/audio/adlib_ms.h
+++ b/audio/adlib_ms.h
@@ -589,8 +589,10 @@ public:
 	 * of OPL chip.
 	 * 
 	 * @param oplType The type of OPL chip that should be used.
+	 * @param timerFrequency The number of timer callbacks per second that
+	 * should be generated.
 	 */
-	MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType);
+	MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType, int timerFrequency = OPL::OPL::kDefaultCallbackFrequency);
 	~MidiDriver_ADLIB_Multisource();
 
 	/**
@@ -1118,6 +1120,8 @@ protected:
 
 	// True if the driver has been successfully opened.
 	bool _isOpen;
+	// The number of timer callbacks per second.
+	int _timerFrequency;
 	// Controls the behavior for calculating note frequency and volume.
 	AccuracyMode _accuracyMode;
 	// Controls the OPL channel allocation behavior.


Commit: 3dd5568cabbb789f271ae3897ac2eab5794370c0
    https://github.com/scummvm/scummvm/commit/3dd5568cabbb789f271ae3897ac2eab5794370c0
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-05-09T17:19:45+02:00

Commit Message:
AGOS: Improve E2/WW AdLib SFX timing

This change improves the accuracy of the timing of the AdLib SFX of Elvira 2
and Waxworks. The frequency of the SFX script timer is now synchronized with
the callback timer of the AdLib driver, which makes the timing of the OPL
register writes match that of the original interpreter.

Changed paths:
    engines/agos/drivers/accolade/adlib.cpp
    engines/agos/drivers/accolade/adlib.h
    engines/agos/drivers/accolade/mididriver.h
    engines/agos/midi.cpp
    engines/agos/sfxparser_accolade.cpp
    engines/agos/sfxparser_accolade.h


diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
index fbbcc3e818d..6f6d703a25e 100644
--- a/engines/agos/drivers/accolade/adlib.cpp
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -66,7 +66,8 @@ const uint16 MidiDriver_Accolade_AdLib::OPL_NOTE_FREQUENCIES_MUSIC_DRV[] = {
 // feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too.
 //
 // I have currently not implemented dynamic channel allocation.
-MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion) : MidiDriver_ADLIB_Multisource(oplType) {
+MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion, int timerFrequency) :
+		MidiDriver_ADLIB_Multisource(oplType, timerFrequency) {
 	_instrumentBank = nullptr;
 	_rhythmBank = nullptr;
 	_newVersion = newVersion;
@@ -517,7 +518,7 @@ void MidiDriver_Accolade_AdLib::readDriverData(byte *driverData, uint16 driverDa
 	}
 }
 
-MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType) {
+MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType, int timerFrequency) {
 	byte *driverData = nullptr;
 	uint16 driverDataSize = 0;
 	bool newVersion = false;
@@ -526,7 +527,7 @@ MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFi
 	if (!driverData)
 		error("ACCOLADE-ADLIB: error during readDriver()");
 
-	MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(oplType, newVersion);
+	MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(oplType, newVersion, timerFrequency);
 	if (!driver)
 		error("ACCOLADE-ADLIB: could not create driver");
 
diff --git a/engines/agos/drivers/accolade/adlib.h b/engines/agos/drivers/accolade/adlib.h
index 5300886e670..1e223651e75 100644
--- a/engines/agos/drivers/accolade/adlib.h
+++ b/engines/agos/drivers/accolade/adlib.h
@@ -33,7 +33,7 @@ protected:
 	static const uint16 OPL_NOTE_FREQUENCIES_MUSIC_DRV[12];
 
 public:
-	MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion = false);
+	MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion, int timerFrequency);
 	~MidiDriver_Accolade_AdLib() override;
 
 	int open() override;
diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h
index 2ffcdf34413..a3c6aa50559 100644
--- a/engines/agos/drivers/accolade/mididriver.h
+++ b/engines/agos/drivers/accolade/mididriver.h
@@ -39,7 +39,7 @@ namespace AGOS {
 
 extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile);
 
-extern MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType);
+extern MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType, int callbackFrequency = OPL::OPL::kDefaultCallbackFrequency);
 extern MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
 extern MidiDriver_Multisource *MidiDriver_Accolade_Casio_create(Common::String driverFilename);
 extern MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev);
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 2cbb88b5efd..acc3ddcf718 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -209,7 +209,14 @@ int MidiPlayer::open() {
 
 		switch (_deviceType) {
 		case MT_ADLIB:
-			_driverMsMusic = _driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
+			if (usesMidiSfx) {
+				// Elvira 2 and Waxworks have AdLib SFX.
+				_parserSfxAccolade = new SfxParser_Accolade_AdLib();
+				// Sync the driver to the AdLib SFX script timer.
+				_driverMsMusic = _driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType, SfxParser_Accolade::SCRIPT_TIMER_FREQUENCY);
+			} else {
+				_driverMsMusic = _driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
+			}
 			if (_vm->getGameType() == GType_WW) {
 				// WORKAROUND Some Waxworks tracks do not set an instrument on
 				// a MIDI channel. This will cause that channel to play with
@@ -222,9 +229,6 @@ int MidiPlayer::open() {
 					// well in OPL3 mode and need some adjustments.
 					static_cast<MidiDriver_Accolade_AdLib *>(_driverMsMusic)->patchWwInstruments();
 			}
-			if (usesMidiSfx)
-				// Elvira 2 and Waxworks have AdLib SFX.
-				_parserSfxAccolade = new SfxParser_Accolade_AdLib();
 			break;
 		case MT_MT32:
 		case MT_GM:
@@ -239,7 +243,7 @@ int MidiPlayer::open() {
 			if (usesMidiSfx) {
 				if (ConfMan.getBool("multi_midi")) {
 					// Use AdLib SFX with MT-32 music.
-					_driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType);
+					_driverMsSfx = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename, oplType, SfxParser_Accolade::SCRIPT_TIMER_FREQUENCY);
 					_parserSfxAccolade = new SfxParser_Accolade_AdLib();
 				} else {
 					// Use MT-32 SFX.
diff --git a/engines/agos/sfxparser_accolade.cpp b/engines/agos/sfxparser_accolade.cpp
index a83baf604fb..1eb54862668 100644
--- a/engines/agos/sfxparser_accolade.cpp
+++ b/engines/agos/sfxparser_accolade.cpp
@@ -73,6 +73,8 @@ int16 SfxParser_Accolade::SfxSlot::readScript(bool opCode) {
 }
 
 const byte SfxParser_Accolade::INSTRUMENT_SIZE_MT32;
+const uint16 SfxParser_Accolade::SCRIPT_TIMER_FREQUENCY;
+const uint16 SfxParser_Accolade::SCRIPT_TIMER_RATE;
 
 SfxParser_Accolade::SfxParser_Accolade() : _driver(nullptr), _timerRate(0), _sfxData(),
 	_numSfx(0), _sourceAllocations { -1, -1, -1, -1 }, _paused(false) { }
@@ -351,12 +353,6 @@ void SfxParser_Accolade::onTimer() {
 			_sfxSlots[i].programChanged = true;
 		}
 
-		// Note that ScummVM timer callback frequency is limited by what SDL
-		// can provide, which is every 10ms minimum. The original interpreter
-		// processes a script tick about every 3.25ms. So every onTimer call
-		// multiple script ticks are processed at the same time. Because of
-		// this, the sound effect audio is not entirely accurate.
-
 		// Determine the end time of the timer callback period that will be
 		// processed.
 		uint32 endTime = _sfxSlots[i].playTime + _timerRate;
@@ -417,7 +413,7 @@ void SfxParser_Accolade::onTimer() {
 				processOpCode(&_sfxSlots[i], opCode);
 			}
 		}
-
+		
 		// If the sound effect is still active, update the play timestamp.
 		if (_sfxSlots[i].active)
 			_sfxSlots[i].playTime = endTime;
diff --git a/engines/agos/sfxparser_accolade.h b/engines/agos/sfxparser_accolade.h
index d99040223b1..5df443bbb6a 100644
--- a/engines/agos/sfxparser_accolade.h
+++ b/engines/agos/sfxparser_accolade.h
@@ -34,10 +34,12 @@ class SfxParser_Accolade {
 public:
 	// Size in bytes of MT-32 instrument data in the SFX data.
 	static const byte INSTRUMENT_SIZE_MT32 = 0xF9;
+	// Number of script ticks per second.
+	static const uint16 SCRIPT_TIMER_FREQUENCY = 292;
+	// Number of microseconds per script tick.
+	static const uint16 SCRIPT_TIMER_RATE = 1000000 / SCRIPT_TIMER_FREQUENCY;
 
 protected:
-	// Number of microseconds per script tick.
-	static const uint16 SCRIPT_TIMER_RATE = 3250;
 	// Size in bytes of AdLib instrument data in the SFX data.
 	static const byte INSTRUMENT_SIZE_ADLIB = 0x09;
 	// Maximum number of words in an SFX script.




More information about the Scummvm-git-logs mailing list