[Scummvm-git-logs] scummvm master -> 851430aecfe2c9f37217649cc0479861bf908359

NMIError noreply at scummvm.org
Sat Feb 22 22:14:32 UTC 2025


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

Summary:
9789f6914b DARKSEED: Implement floppy version music
ed650e1cf0 AUDIO: Fix OPL channel allocation for rhythm mode
c1e19467a6 DARKSEED: Add music fade-outs
851430aecf DARKSEED: Add floppy music option


Commit: 9789f6914b7af3e384d2fd281e0818f12e5467b2
    https://github.com/scummvm/scummvm/commit/9789f6914b7af3e384d2fd281e0818f12e5467b2
Author: Coen Rampen (crampen at gmail.com)
Date: 2025-02-22T23:13:54+01:00

Commit Message:
DARKSEED: Implement floppy version music

Changed paths:
  A engines/darkseed/adlib_dsf.cpp
  A engines/darkseed/adlib_dsf.h
  A engines/darkseed/midiparser_sbr.cpp
  A engines/darkseed/midiparser_sbr.h
    audio/adlib_ms.cpp
    audio/adlib_ms.h
    engines/darkseed/adlib_worx.cpp
    engines/darkseed/module.mk
    engines/darkseed/music.cpp
    engines/darkseed/music.h
    engines/darkseed/sound.cpp
    engines/darkseed/sound.h


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index d9d60ac6197..07af79619af 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -447,12 +447,13 @@ MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType
 		_allocationMode(ALLOCATION_MODE_DYNAMIC),
 		_instrumentWriteMode(INSTRUMENT_WRITE_MODE_NOTE_ON),
 		_rhythmModeIgnoreNoteOffs(false),
-		_channel10Melodic(false),
+		_rhythmInstrumentMode(RHYTHM_INSTRUMENT_MODE_CHANNEL_10),
 		_defaultChannelVolume(0),
 		_noteSelect(NOTE_SELECT_MODE_0),
 		_modulationDepth(MODULATION_DEPTH_HIGH),
 		_vibratoDepth(VIBRATO_DEPTH_HIGH),
 		_rhythmMode(false),
+		_rhythmModeRewriteSharedRegister(false),
 		_instrumentBank(OPL_INSTRUMENT_BANK),
 		_rhythmBank(OPL_RHYTHM_BANK),
 		_rhythmBankFirstNote(GS_RHYTHM_FIRST_NOTE),
@@ -653,13 +654,21 @@ void MidiDriver_ADLIB_Multisource::send(int8 source, uint32 b) {
 void MidiDriver_ADLIB_Multisource::noteOff(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
 	_activeNotesMutex.lock();
 
-	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+	InstrumentInfo instrument = determineInstrument(channel, source, note);
+	// If rhythm mode is on and the note is on the rhythm channel or the 
+	// instrument is a rhythm instrument, this note was played using the OPL
+	// rhythm register.
+	bool rhythmNote = _rhythmMode && ((_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL) ||
+									  (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE &&
+									   instrument.instrumentDef != nullptr && instrument.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED));
+
+	if (rhythmNote) {
 		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));
+						_activeRhythmNotes[i].channel == channel && _activeRhythmNotes[i].note == note) {
+					writeKeyOff(OPL_RHYTHM_INSTRUMENT_CHANNELS[i], static_cast<OplInstrumentRhythmType>(i + 1));
 					break;
 				}
 			}
@@ -692,14 +701,17 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 	}
 
 	InstrumentInfo instrument = determineInstrument(channel, source, note);
-	// 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 || instrument.instrumentDef->isEmpty() ||
-			(rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) {
+	// If rhythm mode is on and the note is on the rhythm channel or the
+	// instrument is a rhythm instrument, this note will be played using the
+	// OPL rhythm register.
+	bool rhythmNote = _rhythmMode && ((_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL) ||
+									  (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE &&
+									   instrument.instrumentDef != nullptr && instrument.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED));
+
+	if (instrument.instrumentDef == nullptr || 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.
+		// a rhythm note, so the note cannot be played.
 		return;
 	}
 
@@ -708,13 +720,14 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 	// 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)
+	oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
+	if (oplChannel != 0xFF) {
+		if (rhythmNote) {
+			activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1];
+		}
+		else {
 			activeNote = &_activeNotes[oplChannel];
+		}
 	}
 	if (activeNote != nullptr) {
 		if (activeNote->noteActive) {
@@ -737,21 +750,22 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 		activeNote->instrumentId = instrument.instrumentId;
 		activeNote->instrumentDef = instrument.instrumentDef;
 
-		if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_NOTE_ON) {
+		if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_NOTE_ON ||
+				(_instrumentWriteMode == INSTRUMENT_WRITE_MODE_FIRST_NOTE_ON &&
+					activeNote->lastWrittenInstrumentId != activeNote->instrumentId)) {
 			// Write out the instrument definition, volume and panning.
 			writeInstrument(oplChannel, instrument);
 		}
-		else if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_FIRST_NOTE_ON) {
-			if (activeNote->lastWrittenInstrumentId != activeNote->instrumentId) {
-				// Write out the instrument definition, volume and panning.
-				writeInstrument(oplChannel, instrument);
+		else {
+			if (_rhythmModeRewriteSharedRegister && rhythmNote && instrument.instrumentDef->rhythmType != RHYTHM_TYPE_BASS_DRUM) {
+				// Rewrite the shared panning / feedback / connection register.
+				writePanning(oplChannel, instrument.instrumentDef->rhythmType);
 			}
-			else {
-				// Write out volume, if applicable.
-				for (int i = 0; i < activeNote->instrumentDef->getNumberOfOperators(); i++) {
-					if (isVolumeApplicableToOperator(*activeNote->instrumentDef, i))
-						writeVolume(oplChannel, i);
-				}
+
+			// Write out volume, if applicable.
+			for (int i = 0; i < activeNote->instrumentDef->getNumberOfOperators(); i++) {
+				if (isVolumeApplicableToOperator(*activeNote->instrumentDef, i))
+					writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
 			}
 		}
 
@@ -822,14 +836,25 @@ void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller
 }
 
 void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) {
-	// Just set the MIDI program value; this event does not affect active notes.
+	// Set the MIDI program value. This event does not affect active notes...
 	_controlData[source][channel].program = program;
 
-	if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_PROGRAM_CHANGE && !(_rhythmMode && channel == MIDI_RHYTHM_CHANNEL)) {
+	// ...unless instrument write mode on program change is set.
+	if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_PROGRAM_CHANGE &&
+			!(_rhythmMode && _rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL)) {
 		InstrumentInfo instrument = determineInstrument(channel, source, 0);
 
-		if (!instrument.instrumentDef || instrument.instrumentDef->isEmpty()) {
-			// Instrument definition contains no data.
+		// If rhythm mode is on and the note is on the rhythm channel or the
+		// instrument is a rhythm instrument, notes on this channel will be
+		// played using the OPL rhythm register.
+		bool rhythmNote = _rhythmMode && ((_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL) ||
+										  (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE &&
+										   instrument.instrumentDef != nullptr && instrument.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED));
+
+		if (instrument.instrumentDef == nullptr || instrument.instrumentDef->isEmpty() ||
+				(rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) {
+			// Instrument definition contains no data or it is not suitable for
+			// a rhythm note.
 			return;
 		}
 
@@ -838,10 +863,17 @@ void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, u
 		// Determine the OPL channel to use and the active note data to update.
 		uint8 oplChannel = 0xFF;
 		ActiveNote *activeNote = nullptr;
-		// Allocate a melodic OPL channel.
 		oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
 		if (oplChannel != 0xFF) {
-			activeNote = &_activeNotes[oplChannel];
+			if (rhythmNote) {
+				activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1];
+			}
+			else {
+				activeNote = &_activeNotes[oplChannel];
+			}
+		}
+
+		if (activeNote != nullptr) {
 			if (activeNote->noteActive) {
 				// Turn off the note currently playing on this OPL channel or
 				// rhythm instrument.
@@ -930,6 +962,13 @@ void MidiDriver_ADLIB_Multisource::deinitSource(uint8 source) {
 		uint8 oplChannel = _melodicChannels[i];
 		if (_activeNotes[oplChannel].channelAllocated && _activeNotes[oplChannel].source == source) {
 			_activeNotes[oplChannel].channelAllocated = false;
+			_activeNotes[oplChannel].lastWrittenInstrumentId = -1;
+		}
+	}
+	for (int i = 0; i < 5; i++) {
+		if (_activeRhythmNotes[i].channelAllocated && _activeRhythmNotes[i].source == source) {
+			_activeRhythmNotes[i].channelAllocated = false;
+			_activeRhythmNotes[i].lastWrittenInstrumentId = -1;
 		}
 	}
 
@@ -1376,7 +1415,7 @@ void MidiDriver_ADLIB_Multisource::recalculateVolumes(uint8 channel, uint8 sourc
 MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::determineInstrument(uint8 channel, uint8 source, uint8 note) {
 	InstrumentInfo instrument = { 0, nullptr, 0 };
 
-	if (!_channel10Melodic && channel == MIDI_RHYTHM_CHANNEL) {
+	if (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL) {
 		// On the rhythm channel, the note played indicates which instrument
 		// should be used.
 		if (note < _rhythmBankFirstNote || note > _rhythmBankLastNote)
diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h
index 01796d0fb50..2a0dcc988f2 100644
--- a/audio/adlib_ms.h
+++ b/audio/adlib_ms.h
@@ -343,6 +343,22 @@ public:
 		INSTRUMENT_WRITE_MODE_FIRST_NOTE_ON
 	};
 
+	enum RhythmInstrumentMode {
+		/**
+		 * If rhythm mode is active, any note played on the MIDI rhythm channel 
+		 * 10 will be played as an OPL rhythm note. The instrument returned by
+		 * determineInstrument is expected to be a rhythm instrument.
+		 */
+		RHYTHM_INSTRUMENT_MODE_CHANNEL_10,
+		/**
+		 * If rhythm mode is active, any note for which determineInstrument
+		 * returns a rhythm instrument will be played as an OPL rhythm note.
+		 * This will disable the behavior of MIDI channel 10 as the rhythm
+		 * channel entirely and treat it as a melodic channel.
+		 */
+		RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE
+	};
+
 	/**
 	 * The available modes for the OPL note select setting.
 	 */
@@ -1207,11 +1223,15 @@ protected:
 	ChannelAllocationMode _allocationMode;
 	// Controls when the instrument definitions are written.
 	InstrumentWriteMode _instrumentWriteMode;
+	// In instrument write mode First Note On or Program Change, this flag controls if the Cx register,
+	// which is shared between rhythm mode instrument definitions (except bass drum), is rewritten
+	// before each note on.
+	bool _rhythmModeRewriteSharedRegister;
 	// Controls response to rhythm note off events when rhythm mode is active.
 	bool _rhythmModeIgnoreNoteOffs;
-	// Controls whether MIDI channel 10 is treated as the rhythm channel or as
-	// a melodic channel.
-	bool _channel10Melodic;
+	// Controls how rhythm notes are played in OPL rhythm mode and whether MIDI
+	// channel 10 is treated as the rhythm channel or as a melodic channel.
+	RhythmInstrumentMode _rhythmInstrumentMode;
 
 	// The default MIDI channel volume (set when opening the driver).
 	uint8 _defaultChannelVolume;
diff --git a/engines/darkseed/adlib_dsf.cpp b/engines/darkseed/adlib_dsf.cpp
new file mode 100644
index 00000000000..1091aa49b09
--- /dev/null
+++ b/engines/darkseed/adlib_dsf.cpp
@@ -0,0 +1,232 @@
+/* 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 "darkseed/adlib_dsf.h"
+
+namespace Darkseed {
+
+// F-num values used for the 12 octave notes.
+const uint16 MidiDriver_DarkSeedFloppy_AdLib::OPL_NOTE_FREQUENCIES[12] = {
+	0x157, 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287
+};
+
+MidiDriver_DarkSeedFloppy_AdLib::MidiDriver_DarkSeedFloppy_AdLib(OPL::Config::OplType oplType, int timerFrequency) :
+		MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(oplType, timerFrequency) {
+
+	_dsfInstrumentBank = new OplInstrumentDefinition[128];
+	_instrumentBank = _dsfInstrumentBank;
+	Common::fill(_sourcePriority, _sourcePriority + sizeof(_sourcePriority), 0);
+	
+	_defaultChannelVolume = 0x7F;
+	// Dark Seed uses rhythm instrument definitions with instrument numbers 0x0 - 0xE
+	_rhythmInstrumentMode = RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE;
+	_instrumentWriteMode = INSTRUMENT_WRITE_MODE_FIRST_NOTE_ON;
+}
+
+MidiDriver_DarkSeedFloppy_AdLib::~MidiDriver_DarkSeedFloppy_AdLib() {
+	delete[] _dsfInstrumentBank;
+}
+
+int MidiDriver_DarkSeedFloppy_AdLib::open() {
+	int result = MidiDriver_ADLIB_Multisource::open();
+	if (result == 0)
+		// Dark Seed has the OPL rhythm mode always on
+		setRhythmMode(true);
+
+	return result;
+}
+
+void MidiDriver_DarkSeedFloppy_AdLib::deinitSource(uint8 source) {
+	MidiDriver_ADLIB_Multisource::deinitSource(source);
+
+	_sourcePriority[source] = 0;
+}
+
+void MidiDriver_DarkSeedFloppy_AdLib::setSourcePriority(uint8 source, uint8 priority) {
+	assert(source < MAXIMUM_SOURCES);
+	_sourcePriority[source] = priority;
+}
+
+void MidiDriver_DarkSeedFloppy_AdLib::loadInstrumentBank(uint8 *instrumentBankData) {
+	// Dark Seed stores instruments in SIT files. Most music tracks have their
+	// own instrument bank, but there are only 2 significantly different banks.
+	// START loads the instrument bank for each of the 8 tracks it plays, but
+	// each bank is effectively the same. TOS only loads the TOS1.SIT 
+	// instrument bank; the SIT files from the music tracks it plays are
+	// ignored.
+	// 
+	// All SIT files contain 256 instruments in the 16 byte AdLib BNK format.
+	// Only the first 128 instruments are actually loaded; the rest is usually
+	// empty.
+	for (int i = 0; i < 128; i++) {
+		AdLibIbkInstrumentDefinition _ibkInstrument;
+		_ibkInstrument.o0FreqMultMisc = *instrumentBankData++;
+		_ibkInstrument.o1FreqMultMisc = *instrumentBankData++;
+		_ibkInstrument.o0Level = *instrumentBankData++;
+		_ibkInstrument.o1Level = *instrumentBankData++;
+		_ibkInstrument.o0DecayAttack = *instrumentBankData++;
+		_ibkInstrument.o1DecayAttack = *instrumentBankData++;
+		_ibkInstrument.o0ReleaseSustain = *instrumentBankData++;
+		_ibkInstrument.o1ReleaseSustain = *instrumentBankData++;
+		_ibkInstrument.o0WaveformSelect = *instrumentBankData++;
+		_ibkInstrument.o1WaveformSelect = *instrumentBankData++;
+		_ibkInstrument.connectionFeedback = *instrumentBankData++;
+
+		// The first 15 instruments are rhythm instrument definitions, meant
+		// for the 5 OPL rhythm mode instruments. 0-2 are bass drum instruments,
+		// 3-5 are snare drum instruments, etc.
+		uint8 rhythmType = 0;
+		if (i < 15) {
+			rhythmType = 6 + (i / 3);
+		}
+		_ibkInstrument.rhythmType = rhythmType;
+		_ibkInstrument.rhythmNote = 0;
+		_ibkInstrument.transpose = 0;
+
+		_ibkInstrument.toOplInstrumentDefinition(_dsfInstrumentBank[i]);
+
+		// Skip padding bytes
+		instrumentBankData += 5;
+	}
+}
+
+uint8 MidiDriver_DarkSeedFloppy_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+	uint8 allocatedChannel = 0xFF;
+
+	_allocationMutex.lock();
+	_activeNotesMutex.lock();
+
+	if (instrumentId <= 0xE) {
+		// The first 15 instruments are rhythm instruments. These get assigned
+		// to the corresponding OPL rhythm instruments.
+		// Note: original code also processes instrument 0xF, leading to
+		// undefined behavior.
+
+		// The order of the rhythm instruments is flipped compared to the order
+		// in the _activeRhythmNotes array.
+		uint8 rhythmInstType = 4 - (instrumentId / 3);
+		allocatedChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmInstType];
+		if (_activeRhythmNotes[rhythmInstType].channelAllocated && _activeRhythmNotes[rhythmInstType].source != source) {
+			// OPL rhythm instrument is already allocated
+			if (_sourcePriority[_activeRhythmNotes[rhythmInstType].source] >= _sourcePriority[source]) {
+				// Current source priority is equal to or higher than the new
+				// source priority. Do not re-allocate this rhythm instrument.
+				allocatedChannel = 0xFF;
+			} else {
+				// Current source priority is lower than the new source
+				// priority. Deallocate the channel from the current source.
+				if (_activeRhythmNotes[rhythmInstType].noteActive)
+					writeKeyOff(allocatedChannel, static_cast<OplInstrumentRhythmType>(rhythmInstType + 1));
+				_channelAllocations[_activeRhythmNotes[rhythmInstType].source][_activeRhythmNotes[rhythmInstType].channel] = 0xFF;
+			}
+		}
+
+		if (allocatedChannel != 0xFF) {
+			// Allocate the OPL channel to the source and rhythm instrument.
+			_activeRhythmNotes[rhythmInstType].channelAllocated = true;
+			_activeRhythmNotes[rhythmInstType].source = source;
+			_activeRhythmNotes[rhythmInstType].channel = channel;
+		}
+	}
+	else {
+		// For melodic instruments, the following OPL channel is allocated:
+		// - The OPL channel already allocated to this data channel.
+		// - The OPL channel with the lowest priority, if it is lower than the
+		//   priority of the new source.
+		uint8 lowestPriority = 0x64;
+		for (int i = 0; i < _numMelodicChannels; i++) {
+			uint8 oplChannel = _melodicChannels[i];
+			if (_activeNotes[oplChannel].channelAllocated && _activeNotes[oplChannel].source == source && _activeNotes[oplChannel].channel == channel) {
+				// This OPL channel is already allocated to this source and
+				// data channel. Use this OPL channel.
+				allocatedChannel = oplChannel;
+				lowestPriority = 0;
+				break;
+			}
+			// Unallocated channels are treated as having priority 0, the
+			// lowest priority.
+			uint8 currentChannelPriority = !_activeNotes[oplChannel].channelAllocated ? 0 : _sourcePriority[_activeNotes[oplChannel].source];
+			if (currentChannelPriority < lowestPriority) {
+				// Found an OPL channel with a lower priority than the
+				// previously found OPL channel.
+				allocatedChannel = i;
+				lowestPriority = currentChannelPriority;
+			}
+		}
+
+		if (_sourcePriority[source] <= lowestPriority) {
+			// New source priority is lower than the lowest found OPL channel
+			// priority. Do not re-allocate this OPL channel.
+			allocatedChannel = 0xFF;
+		}
+
+		if (allocatedChannel != 0xFF) {
+			if (_activeNotes[allocatedChannel].channelAllocated && _activeNotes[allocatedChannel].source != source) {
+				// OPL channel is already allocated. De-allocate it.
+				if (_activeNotes[allocatedChannel].noteActive)
+					writeKeyOff(allocatedChannel);
+				_channelAllocations[_activeRhythmNotes[allocatedChannel].source][_activeRhythmNotes[allocatedChannel].channel] = 0xFF;
+			}
+
+			// Allocate the OPL channel to the source and data channel.
+			_activeNotes[allocatedChannel].channelAllocated = true;
+			_activeNotes[allocatedChannel].source = source;
+			_activeNotes[allocatedChannel].channel = channel;
+		}
+	}
+
+	if (allocatedChannel != 0xFF) {
+		_channelAllocations[source][channel] = allocatedChannel;
+	}
+
+	_allocationMutex.unlock();
+	_activeNotesMutex.unlock();
+
+	return allocatedChannel;
+}
+
+uint16 MidiDriver_DarkSeedFloppy_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
+	uint8 octaveNote = ((note >= 120) ? 0x7B : (note % 12));
+	uint8 block;
+	if (note < 12) {
+		block = 0;
+	} else if (note >= 108) {
+		block = 7;
+	} else {
+		block = (note / 12) - 1;
+	}
+	uint16 fnum = OPL_NOTE_FREQUENCIES[octaveNote];
+
+	return fnum | (block << 10);
+}
+
+uint8 MidiDriver_DarkSeedFloppy_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
+	uint8 instrumentLevel = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
+	if (instrumentDef.getNumberOfOperators() >= 2 && operatorNum == 0) {
+		// For operator 0 of a 2 operator instrument, the level from the
+		// instrument definition is used without scaling, even if the
+		// connection type is additive.
+		return instrumentLevel;
+	}
+	return 0x3F - (((velocity + 0x80) * (0x3F - instrumentLevel)) >> 8);
+}
+
+} // namespace Darkseed
diff --git a/engines/darkseed/adlib_dsf.h b/engines/darkseed/adlib_dsf.h
new file mode 100644
index 00000000000..488aeff36ec
--- /dev/null
+++ b/engines/darkseed/adlib_dsf.h
@@ -0,0 +1,61 @@
+/* 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 DARKSEED_ADLIB_DSF_H
+#define DARKSEED_ADLIB_DSF_H
+
+#include "audio/adlib_ms.h"
+
+namespace Darkseed {
+
+/**
+ * Implementation of the AdLib code used by Dark Seed (floppy version).
+ * The game uses a combination of the Creative FM-Music Functions library and
+ * its own sound code.
+ */
+class MidiDriver_DarkSeedFloppy_AdLib : public MidiDriver_ADLIB_Multisource {
+protected:
+	static const uint16 OPL_NOTE_FREQUENCIES[12];
+
+public:
+	MidiDriver_DarkSeedFloppy_AdLib(OPL::Config::OplType oplType, int timerFrequency = OPL::OPL::kDefaultCallbackFrequency);
+	~MidiDriver_DarkSeedFloppy_AdLib();
+
+	int open() override;
+
+	void deinitSource(uint8 source) override;
+
+	void setSourcePriority(uint8 source, uint8 priority);
+
+	void loadInstrumentBank(uint8 *instrumentBankData);
+
+protected:
+	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, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
+
+	uint8 _sourcePriority[MAXIMUM_SOURCES];
+	OplInstrumentDefinition *_dsfInstrumentBank;
+};
+
+} // namespace Darkseed
+
+#endif // DARKSEED_ADLIB_DSF_H
diff --git a/engines/darkseed/adlib_worx.cpp b/engines/darkseed/adlib_worx.cpp
index 976abeca229..376051e3778 100644
--- a/engines/darkseed/adlib_worx.cpp
+++ b/engines/darkseed/adlib_worx.cpp
@@ -194,7 +194,7 @@ MidiDriver_Worx_AdLib::MidiDriver_Worx_AdLib(OPL::Config::OplType oplType, int t
 	_instrumentBank = instrumentBank;
 
 	_defaultChannelVolume = 0x7F;
-	_channel10Melodic = true;
+	_rhythmInstrumentMode = RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE;
 	_instrumentWriteMode = INSTRUMENT_WRITE_MODE_FIRST_NOTE_ON;
 }
 
diff --git a/engines/darkseed/midiparser_sbr.cpp b/engines/darkseed/midiparser_sbr.cpp
new file mode 100644
index 00000000000..18c83af61b5
--- /dev/null
+++ b/engines/darkseed/midiparser_sbr.cpp
@@ -0,0 +1,297 @@
+/* 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 "darkseed/midiparser_sbr.h"
+
+#include "audio/mididrv.h"
+
+namespace Darkseed {
+
+MidiParser_SBR::MidiParser_SBR(int8 source, bool sfx) : MidiParser_SMF(source), _sfx(sfx) {
+	Common::fill(_trackDeltas, _trackDeltas + ARRAYSIZE(_trackDeltas), 0);
+	Common::fill(_trackInstruments, _trackInstruments + ARRAYSIZE(_trackInstruments), 0xFF);
+	Common::fill(_trackNoteActive, _trackNoteActive + ARRAYSIZE(_trackNoteActive), 0xFF);
+	Common::fill(_trackLoopCounter, _trackLoopCounter + ARRAYSIZE(_trackLoopCounter), 0);
+
+	// SBR uses a fixed tempo.
+	_ppqn = 96;
+	setTempo(1318200);
+}
+
+void MidiParser_SBR::parseNextEvent(EventInfo &info) {
+	uint8 subtrack = info.subtrack;
+	byte *parsePos = _position._subtracks[subtrack]._playPos;
+	uint8 *start = parsePos;
+
+	/**
+	 * SBR uses 6 byte structures to represent events:
+	 * - Event type / note
+	 * - Note velocity
+	 * - (unused)
+	 * - Delta (word, little-endian)
+	 * - Instrument
+	 * Delta is ticks to the next event, not preceding this event like SMF.
+	 *
+	 * The following event types are used:
+	 * - 0x00: End of track. This is the only byte in this event.
+	 * - 0x01: Subtrack list. See loadMusic.
+	 * - 0x02: Rest. Effectively a note off and a delta to the next event.
+	 * - 0x03: Sample. Indicates the sample filename shoud be read from the 
+	 *         corresponding DIG file entry. See Sound class.
+	 * - 0x05: Restart playback from the beginning of the subtrack.
+	 * - 0x06: Loop. Velocity specifies the number of events to jump back.
+	 *         Delta specifies the number of times to repeat this section.
+	 * - 0x24+: Note on.
+	 */
+	info.start = start;
+	info.length = 0;
+	info.noop = false;
+	info.loop = false;
+	info.delta = _trackDeltas[subtrack];
+	_trackDeltas[subtrack] = 0;
+
+	// Any event will turn off the active note on this subtrack.
+	if (_trackNoteActive[subtrack] != 0xFF) {
+		info.event = 0x80 | subtrack;
+		info.basic.param1 = _trackNoteActive[subtrack];
+		info.basic.param2 = 0x00;
+		_trackNoteActive[subtrack] = 0xFF;
+		return;
+	}
+	
+	if (parsePos == nullptr || parsePos[0] == 0) {
+		// Reached the end of the track. Generate an end-of-track event.
+		info.event = 0xFF;
+		info.ext.type = MidiDriver::MIDI_META_END_OF_TRACK;
+		info.ext.data = parsePos;
+		return;
+	}
+
+	// There are more MIDI events in this track.
+	uint8 noteType = parsePos[0];
+	if (noteType == 5) {
+		// Subtrack needs to be restarted. Generate a custom meta event.
+		info.event = 0xFF;
+		info.ext.type = 5;
+		info.loop = true;
+		return;
+	}
+
+	if (noteType == 6) {
+		// Subtrack needs to be looped. Generate a custom meta event.
+		info.event = 0xFF;
+		info.ext.type = 6;
+		info.ext.data = parsePos;
+		info.loop = true;
+		// Delta needs to be one more than specified due to differences in the
+		// way this event is processed compared to the original code.
+		info.delta++;
+		return;
+	}
+
+	// Check if the instrument specified in this event is different from the 
+	// current instrument for this track. Typically, all events in a subtrack 
+	// have the same instrument.
+	uint8 instrument = parsePos[5];
+	if (_trackInstruments[subtrack] != instrument) {
+		if (_trackInstruments[subtrack] <= 0xF && (_trackInstruments[subtrack] / 3) != (instrument / 3)) {
+			// WORKAROUND The current instrument for this subtrack is a rhythm
+			// instrument, and the instrument specified in this event is for a
+			// different rhythm instrument type. This occurs a few times in
+			// the Dark Seed SBR files. The instrument for the offending events
+			// is 0, probably by mistake. The original code will allocate a
+			// subtrack to an OPL rhythm instrument based on the first event
+			// and then never change it, even when an event with an instrument
+			// with a different rhythm type is encountered. Instead, it will
+			// write the new instrument definition to the originally allocated
+			// OPL rhythm instrument, even if it has the wrong rhythm type.
+			// The ScummVM code will change the allocation to the rhythm
+			// instrument indicated by the instrument on the new event, which
+			// causes incorrect playback, because typically there already is
+			// a subtrack allocated to this instrument.
+			// To fix this, the incorrect instrument on this event is simply
+			// ignored.
+		}
+		else {
+			// The instrument on this event is different from the current
+			// instrument. Generate a program change event.
+			_trackInstruments[subtrack] = instrument;
+			info.event = 0xC0 | subtrack;
+			info.basic.param1 = instrument;
+			info.basic.param2 = 0;
+			return;
+		}
+	}
+
+	if (noteType >= 24) {
+		// Note on.
+		info.event = 0x90 | subtrack;
+		info.basic.param1 = noteType;
+		info.basic.param2 = parsePos[1];
+		_trackNoteActive[subtrack] = noteType;
+	}
+	else {
+		// For rest events, nothing needs to be done other than turning off
+		// the current note (already done above) and process the delta.
+		// Subtrack list and sample events are not processed here.
+		info.noop = true;
+	}
+	if (noteType == 2 || noteType >= 24) {
+		// Delta to the next event is only processed for
+		// note on and rest events.
+		_trackDeltas[subtrack] = READ_LE_UINT16(parsePos + 3);
+	}
+
+	// Set play position to the start of the next event.
+	_position._subtracks[subtrack]._playPos += 6;
+}
+
+bool MidiParser_SBR::processEvent(const EventInfo &info, bool fireEvents) {
+	// Handle custom meta events here.
+	if (info.event == 0xFF) {
+		uint8 subtrack = info.subtrack;
+		if (info.ext.type == 5) {
+			// Restart. Set play position to the beginning of the subtrack.
+			_position._subtracks[subtrack]._playPos = _tracks[_activeTrack][subtrack];
+			return true;
+		}
+		else if (info.ext.type == 6) {
+			// Loop.
+			bool loop = false;
+			if (_trackLoopCounter[subtrack] > 0) {
+				// A loop iteration has completed.
+				_trackLoopCounter[subtrack]--;
+				if (_trackLoopCounter[subtrack] > 0) {
+					// There are more iterations remaining.
+					loop = true;
+				}
+				else {
+					// Loop has finished. Playback will resume at the event
+					// after the loop event.
+					_position._subtracks[subtrack]._playPos += 6;
+				}
+			}
+			else {
+				// Initialize the loop. Read number of iterations from the
+				// event delta.
+				_trackLoopCounter[subtrack] = READ_LE_UINT16(info.ext.data + 3);
+				loop = true;
+			}
+			if (loop) {
+				// Set the play position back by the number of events indicated
+				// by the event velocity.
+				_position._subtracks[subtrack]._playPos -= ((info.ext.data[1]) * 6);
+			}
+			return true;
+		}
+	}
+
+	// All other events are handled like SMF events.
+	return MidiParser::processEvent(info, fireEvents);
+}
+
+void MidiParser_SBR::onTrackStart(uint8 track) {
+	Common::fill(_trackDeltas, _trackDeltas + ARRAYSIZE(_trackDeltas), 0);
+	Common::fill(_trackInstruments, _trackInstruments + ARRAYSIZE(_trackInstruments), 0xFF);
+	Common::fill(_trackNoteActive, _trackNoteActive + ARRAYSIZE(_trackNoteActive), 0xFF);
+	Common::fill(_trackLoopCounter, _trackLoopCounter + ARRAYSIZE(_trackLoopCounter), 0);
+}
+
+bool MidiParser_SBR::loadMusic(byte *data, uint32 size) {
+	assert(size > 0);
+
+	unloadMusic();
+
+	// SBR files typically contain 120 tracks; some have 100.
+	// Music files only use the first 10. SFX files use the remaining 110.
+	uint8 startTrack = _sfx ? 10 : 0;
+	uint8 endTrack = _sfx ? 120 : 10;
+
+	// Read all the tracks. These consist of 0 or more 6 byte events,
+	// terminated by a 00 byte.
+	uint16 bytesRead = 0;
+	for (int i = 0; i < endTrack; i++) {
+		byte *startOfTrack = data;
+		uint16 trackSize = 0;
+
+		bool foundEndOfTrack = false;
+		while (bytesRead < size) {
+			uint8 eventType = data[0];
+			if (eventType == 0) {
+				foundEndOfTrack = true;
+				data++;
+				trackSize++;
+				bytesRead++;
+				break;
+			}
+			else {
+				data += 6;
+				trackSize += 6;
+				bytesRead += 6;
+			}
+		}
+
+		if (!foundEndOfTrack) {
+			// Some files have less than 120 tracks
+			endTrack = i;
+			break;
+		}
+		else if (i < startTrack) {
+			_tracks[i][0] = nullptr;
+		}
+		else {
+			_tracks[i][0] = startOfTrack;
+		}
+	}
+	_numTracks = endTrack;
+
+	// Look for tracks starting with a subtrack list event (type 01).
+	// These are tracks consisting of multiple parallel subtracks.
+	// Music files typically have a subtrack list in track 0. Some SFX use
+	// multiple subtracks as well.
+	for (int i = 0; i < _numTracks; i++) {
+		if (_tracks[i][0] != nullptr && _tracks[i][0][0] == 0x01) {
+			// Read the subtrack list. This is a variable-length list of
+			// subtrack indices, terminated by a 00 byte.
+			// (The track is padded with garbage and terminated by another
+			// 00 byte to match the x * 6 byte event format used by all tracks.)
+			uint8 *tracklist = _tracks[i][0] + 1;
+			uint8 subtrackIndex = 0;
+			while (*tracklist != 0) {
+				_tracks[i][subtrackIndex++] = _tracks[*tracklist][0];
+				tracklist++;
+			}
+			_numSubtracks[i] = subtrackIndex;
+		}
+		else {
+			// This is a track containing just a single subtrack.
+			_numSubtracks[i] = 1;
+		}
+	}
+
+	_disableAutoStartPlayback = true;
+	resetTracking();
+
+	setTrack(0);
+	return true;
+}
+
+} // End of namespace Darkseed
diff --git a/engines/darkseed/midiparser_sbr.h b/engines/darkseed/midiparser_sbr.h
new file mode 100644
index 00000000000..6738680a142
--- /dev/null
+++ b/engines/darkseed/midiparser_sbr.h
@@ -0,0 +1,52 @@
+/* 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 DARKSEED_MIDIPARSER_SBR_H
+#define DARKSEED_MIDIPARSER_SBR_H
+
+#include "audio/midiparser_smf.h"
+
+namespace Darkseed {
+
+/**
+ * MIDI parser for the SBR format used by Dark Seed floppy version.
+ */
+class MidiParser_SBR : public MidiParser_SMF {
+public:
+	MidiParser_SBR(int8 source = -1, bool sfx = false);
+
+	bool loadMusic(byte *data, uint32 size) override;
+
+protected:
+	void parseNextEvent(EventInfo &info) override;
+	bool processEvent(const EventInfo &info, bool fireEvents = true) override;
+	void onTrackStart(uint8 track) override;
+
+	bool _sfx = false;
+	uint8 _trackInstruments[10];
+	uint16 _trackDeltas[10];
+	uint8 _trackNoteActive[10];
+	uint16 _trackLoopCounter[10];
+};
+
+} // End of namespace Darkseed
+
+#endif
diff --git a/engines/darkseed/module.mk b/engines/darkseed/module.mk
index e594df1b521..ea88ab8d13c 100644
--- a/engines/darkseed/module.mk
+++ b/engines/darkseed/module.mk
@@ -1,6 +1,7 @@
 MODULE := engines/darkseed
 
 MODULE_OBJS = \
+	adlib_dsf.o \
 	adlib_worx.o \
 	animation.o \
 	anm.o \
@@ -18,6 +19,7 @@ MODULE_OBJS = \
 	langtext.o \
 	menu.o \
 	metaengine.o \
+	midiparser_sbr.o \
 	morph.o \
 	music.o \
 	nsp.o \
diff --git a/engines/darkseed/music.cpp b/engines/darkseed/music.cpp
index 693896fdf69..8034ab967c1 100644
--- a/engines/darkseed/music.cpp
+++ b/engines/darkseed/music.cpp
@@ -21,32 +21,39 @@
 
 #include "darkseed/music.h"
 #include "darkseed/darkseed.h"
+#include "darkseed/midiparser_sbr.h"
 
 namespace Darkseed {
 
-MusicPlayer::MusicPlayer(DarkseedEngine* vm) :
+MusicPlayer::MusicPlayer(DarkseedEngine* vm, bool useFloppyMusic) :
 	_vm(vm),
 	_driver(nullptr),
+	_floppyAdLibDriver(nullptr),
 	_paused(false),
 	_deviceType(MT_NULL),
 	_parser(nullptr),
-	_musicData(nullptr) {
+	_musicData(nullptr),
+	_tosInstrumentBankData(nullptr),
+	_tosInstrumentBankLoaded(false),
+	_useFloppyMusic(useFloppyMusic) {
 }
 
 MusicPlayer::~MusicPlayer() {
 	stop();
-	if (_driver) {
+	if (_driver != nullptr) {
 		_driver->setTimerCallback(nullptr, nullptr);
 		_driver->close();
 	}
 
 	Common::StackLock lock(_mutex);
 
-	if (_parser)
+	if (_parser != nullptr)
 		delete _parser;
-	if (_musicData)
+	if (_musicData != nullptr)
 		delete[] _musicData;
-	if (_driver) {
+	if (_tosInstrumentBankData != nullptr)
+		delete[] _tosInstrumentBankData;
+	if (_driver != nullptr) {
 		delete _driver;
 		_driver = nullptr;
 	}
@@ -59,9 +66,20 @@ int MusicPlayer::open() {
 	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
 	_deviceType = MidiDriver::getMusicType(dev);
 
-	if (!_vm->isCdVersion()) {
-		// TODO Initialize driver and parser for floppy version
-		_driver = new MidiDriver_NULL_Multisource();
+	if (_useFloppyMusic) {
+		switch (_deviceType) {
+			case MT_ADLIB:
+				_floppyAdLibDriver = new MidiDriver_DarkSeedFloppy_AdLib(OPL::Config::kOpl2);
+				_driver = _floppyAdLibDriver;
+				break;
+			case MT_PCSPK:
+				// TODO Implement PC speaker driver
+			default:
+				_driver = new MidiDriver_NULL_Multisource();
+				break;
+		}
+
+		_parser = new MidiParser_SBR(0);
 	} else {
 		switch (_deviceType) {
 			case MT_ADLIB:
@@ -157,7 +175,7 @@ void MusicPlayer::setLoop(bool loop) {
 		_parser->property(MidiParser::mpAutoLoop, loop);
 }
 
-void MusicPlayer::load(Common::SeekableReadStream *in, int32 size) {
+void MusicPlayer::load(Common::SeekableReadStream *in, int32 size, bool sfx) {
 	Common::StackLock lock(_mutex);
 
 	if (!_parser)
@@ -189,13 +207,67 @@ void MusicPlayer::load(Common::SeekableReadStream *in, int32 size) {
 	_parser->loadMusic(_musicData, size);
 }
 
-void MusicPlayer::play(bool loop) {
+void MusicPlayer::loadTosInstrumentBankData(Common::SeekableReadStream* in, int32 size) {
+	if (size != 4096) {
+		warning("MusicPlayer::loadTosInstrumentBankData - Specified instrument bank has unexpected size %d", size);
+		return;
+	}
+
+	if (_tosInstrumentBankData != nullptr)
+		delete[] _tosInstrumentBankData;
+
+	_tosInstrumentBankData = new byte[size];
+	in->read(_tosInstrumentBankData, size);
+}
+
+void MusicPlayer::loadTosInstrumentBank() {
+	if (_floppyAdLibDriver == nullptr) {
+		warning("MusicPlayer::loadTosInstrumentBank - Driver does not support instrument banks");
+		return;
+	}
+	if (_tosInstrumentBankData == nullptr) {
+		warning("MusicPlayer::loadTosInstrumentBank - TOS instrument bank data has not been loaded");
+		return;
+	}
+
+	if (!_tosInstrumentBankLoaded) {
+		if (isPlaying())
+			stop();
+		_floppyAdLibDriver->loadInstrumentBank(_tosInstrumentBankData);
+		_tosInstrumentBankLoaded = true;
+	}
+}
+
+void MusicPlayer::loadInstrumentBank(Common::SeekableReadStream *in, int32 size) {
+	if (_floppyAdLibDriver == nullptr) {
+		warning("MusicPlayer::loadInstrumentBank - Driver does not support instrument banks");
+		return;
+	}
+	if (size != 4096) {
+		warning("MusicPlayer::loadInstrumentBank - Specified instrument bank has unexpected size %d", size);
+		return;
+	}
+
+	byte *instrumentBankData = new byte[size];
+	in->read(instrumentBankData, size);
+
+	if (isPlaying())
+		stop();
+	_floppyAdLibDriver->loadInstrumentBank(instrumentBankData);
+	_tosInstrumentBankLoaded = false;
+}
+
+void MusicPlayer::play(uint8 priority, bool loop) {
 	Common::StackLock lock(_mutex);
 
 	if (!_parser)
 		return;
 
-	_parser->property(MidiParser::mpAutoLoop, loop);
+	if (!_useFloppyMusic)
+		_parser->property(MidiParser::mpAutoLoop, loop);
+	if (_floppyAdLibDriver != nullptr)
+		_floppyAdLibDriver->setSourcePriority(0, priority);
+
 	_parser->startPlaying();
 }
 
diff --git a/engines/darkseed/music.h b/engines/darkseed/music.h
index b04184e10f0..ab7f1545c30 100644
--- a/engines/darkseed/music.h
+++ b/engines/darkseed/music.h
@@ -22,6 +22,7 @@
 #ifndef DARKSEED_MUSIC_H
 #define DARKSEED_MUSIC_H
 
+#include "darkseed/adlib_dsf.h"
 #include "darkseed/adlib_worx.h"
 
 #include "audio/mididrv_ms.h"
@@ -38,23 +39,30 @@ protected:
 
 	Common::Mutex _mutex;
 	MidiDriver_Multisource *_driver;
+	MidiDriver_DarkSeedFloppy_AdLib *_floppyAdLibDriver;
 
 	MidiParser *_parser;
 	byte *_musicData;
+	byte *_tosInstrumentBankData;
+	bool _tosInstrumentBankLoaded;
+	bool _useFloppyMusic;
 
 	bool _paused;
 
 	static void onTimer(void *data);
 
 public:
-	MusicPlayer(DarkseedEngine *vm);
+	MusicPlayer(DarkseedEngine *vm, bool useFloppyMusic);
 	~MusicPlayer();
 
 	int open();
 
-	void load(Common::SeekableReadStream *in, int32 size = -1);
+	void load(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
+	void loadTosInstrumentBankData(Common::SeekableReadStream *in, int32 size = -1);
+	void loadTosInstrumentBank();
+	void loadInstrumentBank(Common::SeekableReadStream *in, int32 size = -1);
 
-	void play(bool loop = false);
+	void play(uint8 priority = 0xFF, bool loop = false);
 	void setLoop(bool loop);
 	bool isPlaying();
 	void stop();
diff --git a/engines/darkseed/sound.cpp b/engines/darkseed/sound.cpp
index b0657a80829..dbeb561f1dd 100644
--- a/engines/darkseed/sound.cpp
+++ b/engines/darkseed/sound.cpp
@@ -133,7 +133,9 @@ static constexpr char sfxCDFilenameTbl[][14] = {
 };
 
 Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer) {
-	_musicPlayer = new MusicPlayer(g_engine);
+	// TODO Add config setting for CD version with floppy music
+	_useFloppyMusic = !g_engine->isCdVersion();
+	_musicPlayer = new MusicPlayer(g_engine, _useFloppyMusic);
 	_didSpeech.resize(978);
 	resetSpeech();
 }
@@ -143,6 +145,16 @@ Sound::~Sound() {
 }
 
 int Sound::init() {
+	Common::File file;
+	Common::Path path = Common::Path("tos1.sit");
+	if (file.open(path)) {
+		_musicPlayer->loadTosInstrumentBankData(&file, (int32)file.size());
+	}
+	else {
+		debug("Failed to load TOS instrument bank data %s", path.toString().c_str());
+	}
+	file.close();
+
 	return _musicPlayer->open();
 }
 
@@ -195,27 +207,49 @@ void Sound::playMusic(MusicId musicId, bool loop) {
 		return;
 	}
 	int filenameIdx = static_cast<uint8>(musicId) - 1;
-	playMusic(g_engine->isCdVersion()
-		? musicDosCDFilenameTbl[filenameIdx]
-		: musicDosFloppyFilenameTbl[filenameIdx],
-		loop);
+	playMusic(_useFloppyMusic ?
+			Common::String(musicDosFloppyFilenameTbl[filenameIdx]) + ".sbr" : musicDosCDFilenameTbl[filenameIdx],
+		nullptr, 6, loop);
 }
 
 void Sound::playMusic(StartMusicId musicId) {
 	int filenameIdx = static_cast<uint8>(musicId);
-	playMusic(g_engine->isCdVersion()
-		? startMusicDosCDFilenameTbl[filenameIdx]
-		: startMusicDosFloppyFilenameTbl[filenameIdx]);
+	if (_useFloppyMusic) {
+		Common::String const &filenameBase = startMusicDosFloppyFilenameTbl[filenameIdx];
+		Common::String const &filenameSbr = filenameBase + ".sbr";
+		Common::String const &filenameSit = filenameBase + ".sit";
+
+		playMusic(filenameSbr, &filenameSit, 5);
+	}
+	else {
+		playMusic(startMusicDosCDFilenameTbl[filenameIdx]);
+	}
 }
 
-void Sound::playMusic(Common::String const &filename, bool loop) {
-	debug("Loading music: %s", filename.c_str());
+void Sound::playMusic(Common::String const &musicFilename, Common::String const *instrBankFilename, uint8 priority, bool loop) {
 	Common::File file;
 	Common::Path path;
-	if (!g_engine->isCdVersion()) {
-		path = Common::Path(filename);
+	if (_useFloppyMusic) {
+		if (instrBankFilename != nullptr) {
+			debug("Loading instrument bank: %s", instrBankFilename->c_str());
+			path = Common::Path(instrBankFilename->c_str());
+			if (!file.open(path)) {
+				debug("Failed to load %s", path.toString().c_str());
+				return;
+			}
+			_musicPlayer->loadInstrumentBank(&file, (int32)file.size());
+			file.close();
+		}
+		else {
+			debug("Loading TOS instrument bank");
+			_musicPlayer->loadTosInstrumentBank();
+		}
+	}
+	debug("Loading music: %s", musicFilename.c_str());
+	if (_useFloppyMusic) {
+		path = Common::Path(musicFilename);
 	} else {
-		path = Common::Path("sound").join(filename);
+		path = Common::Path("sound").join(musicFilename);
 	}
 	if (!file.open(path)) {
 		debug("Failed to load %s", path.toString().c_str());
@@ -224,7 +258,7 @@ void Sound::playMusic(Common::String const &filename, bool loop) {
 	_musicPlayer->load(&file, (int32)file.size());
 	file.close();
 
-	_musicPlayer->play(loop);
+	_musicPlayer->play(priority, loop);
 }
 
 void Sound::stopMusic() {
diff --git a/engines/darkseed/sound.h b/engines/darkseed/sound.h
index cec4bdd3eab..8237ef34d61 100644
--- a/engines/darkseed/sound.h
+++ b/engines/darkseed/sound.h
@@ -63,6 +63,7 @@ class Sound {
 	Audio::SoundHandle _sfxHandle;
 	MusicPlayer *_musicPlayer;
 	Common::Array<uint8> _didSpeech;
+	bool _useFloppyMusic;
 
 public:
 	explicit Sound(Audio::Mixer *mixer);
@@ -81,7 +82,7 @@ public:
 	void resetSpeech();
 	void playMusic(MusicId musicId, bool loop = true);
 	void playMusic(StartMusicId musicId);
-	void playMusic(Common::String const &filename, bool loop = false);
+	void playMusic(Common::String const &filename, Common::String const *instrBankFilename = nullptr, uint8 priority = 0xFF, bool loop = false);
 	void stopMusic();
 	void playSfx(uint8 sfxId, int unk1, int unk2);
 	void stopSfx();


Commit: ed650e1cf031c954a3cefd4e7c07355543419f29
    https://github.com/scummvm/scummvm/commit/ed650e1cf031c954a3cefd4e7c07355543419f29
Author: Coen Rampen (crampen at gmail.com)
Date: 2025-02-22T23:13:54+01:00

Commit Message:
AUDIO: Fix OPL channel allocation for rhythm mode

Changed paths:
    audio/adlib_ms.cpp
    audio/adlib_ms.h
    engines/agos/drivers/accolade/adlib.cpp
    engines/agos/drivers/accolade/adlib.h
    engines/agos/drivers/simon1/adlib.cpp
    engines/agos/drivers/simon1/adlib.h
    engines/darkseed/adlib_dsf.cpp
    engines/darkseed/adlib_dsf.h
    engines/darkseed/adlib_worx.cpp
    engines/darkseed/adlib_worx.h
    engines/darkseed/midiparser_sbr.cpp
    engines/ultima/nuvie/sound/mididrv_m_adlib.cpp
    engines/ultima/nuvie/sound/mididrv_m_adlib.h


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index 07af79619af..689707dcc86 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -720,7 +720,7 @@ void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 veloc
 	// Determine the OPL channel to use and the active note data to update.
 	uint8 oplChannel = 0xFF;
 	ActiveNote *activeNote = nullptr;
-	oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
+	oplChannel = allocateOplChannel(channel, source, instrument);
 	if (oplChannel != 0xFF) {
 		if (rhythmNote) {
 			activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1];
@@ -843,27 +843,23 @@ void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, u
 	if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_PROGRAM_CHANGE &&
 			!(_rhythmMode && _rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL)) {
 		InstrumentInfo instrument = determineInstrument(channel, source, 0);
-
-		// If rhythm mode is on and the note is on the rhythm channel or the
-		// instrument is a rhythm instrument, notes on this channel will be
-		// played using the OPL rhythm register.
-		bool rhythmNote = _rhythmMode && ((_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL) ||
-										  (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE &&
-										   instrument.instrumentDef != nullptr && instrument.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED));
-
-		if (instrument.instrumentDef == nullptr || instrument.instrumentDef->isEmpty() ||
-				(rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) {
-			// Instrument definition contains no data or it is not suitable for
-			// a rhythm note.
+		if (instrument.instrumentDef == nullptr || instrument.instrumentDef->isEmpty()) {
+			// Instrument definition contains no data.
 			return;
 		}
 
+
+		// If rhythm mode is on and the instrument is a rhythm instrument,
+		// notes on this channel will be played using the OPL rhythm register.
+		bool rhythmNote = _rhythmMode && _rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE &&
+				instrument.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED;
+
 		_activeNotesMutex.lock();
 
 		// Determine the OPL channel to use and the active note data to update.
 		uint8 oplChannel = 0xFF;
 		ActiveNote *activeNote = nullptr;
-		oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
+		oplChannel = allocateOplChannel(channel, source, instrument);
 		if (oplChannel != 0xFF) {
 			if (rhythmNote) {
 				activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1];
@@ -1084,20 +1080,23 @@ void MidiDriver_ADLIB_Multisource::panning(uint8 channel, uint8 panning, uint8 s
 
 	_activeNotesMutex.lock();
 
+	bool rhythmChannel = (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL);
+
 	// Apply the new channel panning to any active notes.
-	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+	if (_rhythmMode) {
 		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
-			if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source) {
+			if (_activeRhythmNotes[i].noteActive && (rhythmChannel || _activeRhythmNotes[i].channel == channel) &&
+					_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);
-			}
+	}
+
+	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);
 		}
 	}
 
@@ -1168,21 +1167,24 @@ void MidiDriver_ADLIB_Multisource::resetAllControllers(uint8 channel, uint8 sour
 void MidiDriver_ADLIB_Multisource::allNotesOff(uint8 channel, uint8 source) {
 	_activeNotesMutex.lock();
 
+	bool rhythmChannel = (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL);
+
 	// 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.
-	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+	if (_rhythmMode) {
 		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
-			if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source) {
+			if (_activeRhythmNotes[i].noteActive && (rhythmChannel || _activeRhythmNotes[i].channel == channel) && 
+					_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);
-			}
+	}
+
+	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);
 		}
 	}
 
@@ -1207,6 +1209,8 @@ void MidiDriver_ADLIB_Multisource::stopAllNotes(bool stopSustainedNotes) {
 void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
 	_activeNotesMutex.lock();
 
+	bool rhythmChannel = (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL);
+
 	// Write the key off bit for all active notes on this MIDI channel and
 	// source.
 	for (int i = 0; i < _numMelodicChannels; i++) {
@@ -1216,10 +1220,11 @@ void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
 			writeKeyOff(oplChannel);
 		}
 	}
-	if (_rhythmMode && !_rhythmModeIgnoreNoteOffs && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
+	if (_rhythmMode && !_rhythmModeIgnoreNoteOffs) {
 		bool rhythmChanged = false;
 		for (int i = 0; i < 5; i++) {
-			if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
+			if (_activeRhythmNotes[i].noteActive && (channel == 0xFF || rhythmChannel || _activeRhythmNotes[i].channel == channel) && 
+					(source == 0xFF || _activeRhythmNotes[i].source == source)) {
 				_activeRhythmNotes[i].noteActive = false;
 				rhythmChanged = true;
 			}
@@ -1332,19 +1337,27 @@ void MidiDriver_ADLIB_Multisource::initOpl() {
 void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 source) {
 	_activeNotesMutex.lock();
 
+	bool rhythmChannel = (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL);
+
 	// Calculate and write the frequency of all active notes on this MIDI
 	// channel and source.
-	if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
+	if (_rhythmMode) {
 		// 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) {
+		if (_activeRhythmNotes[RHYTHM_TYPE_BASS_DRUM - 1].noteActive &&
+				(rhythmChannel || _activeRhythmNotes[RHYTHM_TYPE_BASS_DRUM - 1].channel == channel) && 
+				_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;
+		bool snareActive = _activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].noteActive &&
+			(rhythmChannel || _activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].channel == channel) &&
+			_activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].source == source;
+		bool hiHatActive = _activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].noteActive &&
+			(rhythmChannel || _activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].channel == channel) &&
+			_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);
@@ -1359,8 +1372,12 @@ void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 s
 		// 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;
+		bool tomTomActive = _activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].noteActive &&
+			(rhythmChannel || _activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].channel == channel) &&
+			_activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].source == source;
+		bool cymbalActive = _activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].noteActive &&
+			(rhythmChannel || _activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].channel == channel) &&
+			_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);
@@ -1371,13 +1388,13 @@ void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 s
 		}
 		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);
-			}
+	}
+
+	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);
 		}
 	}
 
@@ -1387,6 +1404,8 @@ void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 s
 void MidiDriver_ADLIB_Multisource::recalculateVolumes(uint8 channel, uint8 source) {
 	_activeNotesMutex.lock();
 
+	bool rhythmChannel = (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL);
+
 	// Calculate and write the volume of all operators of all active notes on
 	// this MIDI channel and source. 
 	for (int i = 0; i < _numMelodicChannels; i++) {
@@ -1399,9 +1418,11 @@ void MidiDriver_ADLIB_Multisource::recalculateVolumes(uint8 channel, uint8 sourc
 			}
 		}
 	}
-	if (_rhythmMode && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
+	if (_rhythmMode) {
 		for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
-			if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
+			if (_activeRhythmNotes[i].noteActive && 
+					(channel == 0xFF || rhythmChannel || _activeRhythmNotes[i].channel == channel) && 
+					(source == 0xFF || _activeRhythmNotes[i].source == source)) {
 				for (int j = 0; j < _activeRhythmNotes[i].instrumentDef->getNumberOfOperators(); j++) {
 					writeVolume(0xFF, j, static_cast<OplInstrumentRhythmType>(i + 1));
 				}
@@ -1442,7 +1463,28 @@ MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::deter
 	return instrument;
 }
 
-uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
+
+	// Allocate channel for OPL rhythm mode notes. These are just mapped to the
+	// OPL channel used to play the OPL rhythm mode instrument.
+	if (_rhythmMode) {
+		if (_rhythmInstrumentMode == RHYTHM_INSTRUMENT_MODE_CHANNEL_10 && channel == MIDI_RHYTHM_CHANNEL) {
+			// OPL rhythm notes are played using the MIDI rhythm channel,
+			// and this channel is being used to play a note.
+			// If the instrument is an OPL rhythm instrument, return the
+			// corresponding OPL channel. Otherwise the note should not be
+			// played, so do not return a channel.
+			return instrumentInfo.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED ?
+				OPL_RHYTHM_INSTRUMENT_CHANNELS[instrumentInfo.instrumentDef->rhythmType - 1] :
+				0xFF;
+		}
+		else if (instrumentInfo.instrumentDef->rhythmType != RHYTHM_TYPE_UNDEFINED) {
+			// OPL rhythm notes are played based on the instrument rhythm type.
+			// If it is a rhythm instrument, return the corresponding OPL
+			// channel. Otherwise, allocate a melodic channel.
+			return OPL_RHYTHM_INSTRUMENT_CHANNELS[instrumentInfo.instrumentDef->rhythmType - 1];
+		}
+	}
 
 	uint8 allocatedChannel = 0xFF;
 	if (_allocationMode == ALLOCATION_MODE_DYNAMIC) {
@@ -1479,7 +1521,7 @@ uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 sour
 				inactiveChannel = oplChannel;
 				continue;
 			}
-			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].instrumentId == instrumentId &&
+			if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].instrumentId == instrumentInfo.instrumentId &&
 					_activeNotes[oplChannel].noteCounterValue < instrumentNoteCounter) {
 				// A channel playing a note using the same instrument with a
 				// lower note counter value has been found.
diff --git a/audio/adlib_ms.h b/audio/adlib_ms.h
index 2a0dcc988f2..6b777e27d00 100644
--- a/audio/adlib_ms.h
+++ b/audio/adlib_ms.h
@@ -965,12 +965,12 @@ protected:
 	 * 
 	 * @param channel The MIDI channel on which the note is played.
 	 * @param source The source playing the note.
-	 * @param instrumentId The ID of the instrument playing the note. Not used
+	 * @param instrumentInfo Data of the instrument playing the note. Not used
 	 * by the static channel allocation mode.
 	 * @return The number of the allocated OPL channel; 0xFF if allocation
 	 * failed (not possible using the dynamic channel allocation mode).
 	 */
-	virtual uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId);
+	virtual uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo);
 	/**
 	 * Determines which melodic channels are available based on the OPL chip
 	 * type and rhythm mode setting and sets _melodicChannels and
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
index 3b7033237b8..c410e609ead 100644
--- a/engines/agos/drivers/accolade/adlib.cpp
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -165,7 +165,7 @@ void MidiDriver_Accolade_AdLib::deinitSource(uint8 source) {
 	MidiDriver_ADLIB_Multisource::deinitSource(source);
 }
 
-uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
 	Common::StackLock lock(_allocationMutex);
 
 	if (_sources[source].type == SOURCE_TYPE_SFX) {
@@ -177,7 +177,7 @@ uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source,
 				allocatedChannel = 6 - source;
 			} else {
 				// For OPL3, use the dynamic allocation algorithm.
-				allocatedChannel = MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentId);
+				allocatedChannel = MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
 			}
 
 			_activeNotesMutex.lock();
@@ -202,6 +202,10 @@ uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source,
 
 	// Channel allocation for music sources.
 	if (_oplType != OPL::Config::kOpl3) {
+		// Use the regular allocation algorithm for rhythm instruments.
+		if (channel == MIDI_RHYTHM_CHANNEL)
+			return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
+
 		// For OPL2, discard events for channels 6 and 7 and channels allocated
 		// for SFX.
 		if (channel >= 6 || _activeNotes[channel].channelAllocated)
@@ -211,7 +215,7 @@ uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source,
 		return channel;
 	} else {
 		// For OPL3, use the dynamic allocation algorithm.
-		return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentId);
+		return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
 	}
 }
 
@@ -232,7 +236,7 @@ void MidiDriver_Accolade_AdLib::loadSfxInstrument(uint8 source, byte *instrument
 	// Allocate a channel
 	programChange(0, 0, source);
 	InstrumentInfo instrument = determineInstrument(0, source, 0);
-	uint8 oplChannel = allocateOplChannel(0, source, instrument.instrumentId);
+	uint8 oplChannel = allocateOplChannel(0, source, instrument);
 
 	// Update the active note data.
 	ActiveNote *activeNote = &_activeNotes[oplChannel];
diff --git a/engines/agos/drivers/accolade/adlib.h b/engines/agos/drivers/accolade/adlib.h
index abbdd03d104..35ef06815f9 100644
--- a/engines/agos/drivers/accolade/adlib.h
+++ b/engines/agos/drivers/accolade/adlib.h
@@ -62,7 +62,7 @@ public:
 protected:
 	InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note) override;
 
-	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
 	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
 	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
 
diff --git a/engines/agos/drivers/simon1/adlib.cpp b/engines/agos/drivers/simon1/adlib.cpp
index 84a99b218bf..c5c63dc6864 100644
--- a/engines/agos/drivers/simon1/adlib.cpp
+++ b/engines/agos/drivers/simon1/adlib.cpp
@@ -208,7 +208,11 @@ void MidiDriver_Simon1_AdLib::disableMusicRhythmNotes() {
 	_musicRhythmNotesDisabled = true;
 }
 
-uint8 MidiDriver_Simon1_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+uint8 MidiDriver_Simon1_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
+	// Use the regular allocation algorithm for rhythm instruments.
+	if (channel == MIDI_RHYTHM_CHANNEL)
+		return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
+
 	// 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
diff --git a/engines/agos/drivers/simon1/adlib.h b/engines/agos/drivers/simon1/adlib.h
index a878e9c407c..e4f9caa8a9d 100644
--- a/engines/agos/drivers/simon1/adlib.h
+++ b/engines/agos/drivers/simon1/adlib.h
@@ -61,7 +61,7 @@ private:
 	static const RhythmMapEntry RHYTHM_MAP[];
 	static const uint16 FREQUENCY_TABLE[];
 
-	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
 	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
 	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity,
 								  const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
diff --git a/engines/darkseed/adlib_dsf.cpp b/engines/darkseed/adlib_dsf.cpp
index 1091aa49b09..9856656ccc4 100644
--- a/engines/darkseed/adlib_dsf.cpp
+++ b/engines/darkseed/adlib_dsf.cpp
@@ -108,13 +108,13 @@ void MidiDriver_DarkSeedFloppy_AdLib::loadInstrumentBank(uint8 *instrumentBankDa
 	}
 }
 
-uint8 MidiDriver_DarkSeedFloppy_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+uint8 MidiDriver_DarkSeedFloppy_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
 	uint8 allocatedChannel = 0xFF;
 
 	_allocationMutex.lock();
 	_activeNotesMutex.lock();
 
-	if (instrumentId <= 0xE) {
+	if (instrumentInfo.instrumentId <= 0xE) {
 		// The first 15 instruments are rhythm instruments. These get assigned
 		// to the corresponding OPL rhythm instruments.
 		// Note: original code also processes instrument 0xF, leading to
@@ -122,7 +122,7 @@ uint8 MidiDriver_DarkSeedFloppy_AdLib::allocateOplChannel(uint8 channel, uint8 s
 
 		// The order of the rhythm instruments is flipped compared to the order
 		// in the _activeRhythmNotes array.
-		uint8 rhythmInstType = 4 - (instrumentId / 3);
+		uint8 rhythmInstType = 4 - (instrumentInfo.instrumentId / 3);
 		allocatedChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmInstType];
 		if (_activeRhythmNotes[rhythmInstType].channelAllocated && _activeRhythmNotes[rhythmInstType].source != source) {
 			// OPL rhythm instrument is already allocated
diff --git a/engines/darkseed/adlib_dsf.h b/engines/darkseed/adlib_dsf.h
index 488aeff36ec..53358db63e3 100644
--- a/engines/darkseed/adlib_dsf.h
+++ b/engines/darkseed/adlib_dsf.h
@@ -48,7 +48,7 @@ public:
 	void loadInstrumentBank(uint8 *instrumentBankData);
 
 protected:
-	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
 	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
 	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
 
diff --git a/engines/darkseed/adlib_worx.cpp b/engines/darkseed/adlib_worx.cpp
index 376051e3778..5a6804cf01d 100644
--- a/engines/darkseed/adlib_worx.cpp
+++ b/engines/darkseed/adlib_worx.cpp
@@ -202,7 +202,7 @@ MidiDriver_Worx_AdLib::~MidiDriver_Worx_AdLib() {
 	delete[] _instrumentBank;
 }
 
-uint8 MidiDriver_Worx_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+uint8 MidiDriver_Worx_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
 	uint8 allocatedChannel = 0xFF;
 
 	_allocationMutex.lock();
diff --git a/engines/darkseed/adlib_worx.h b/engines/darkseed/adlib_worx.h
index b6eda97195a..481bf7f5e83 100644
--- a/engines/darkseed/adlib_worx.h
+++ b/engines/darkseed/adlib_worx.h
@@ -55,7 +55,7 @@ public:
 	~MidiDriver_Worx_AdLib();
 
 protected:
-	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
 	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
 	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
 };
diff --git a/engines/darkseed/midiparser_sbr.cpp b/engines/darkseed/midiparser_sbr.cpp
index 18c83af61b5..18481ab3c2c 100644
--- a/engines/darkseed/midiparser_sbr.cpp
+++ b/engines/darkseed/midiparser_sbr.cpp
@@ -33,7 +33,7 @@ MidiParser_SBR::MidiParser_SBR(int8 source, bool sfx) : MidiParser_SMF(source),
 
 	// SBR uses a fixed tempo.
 	_ppqn = 96;
-	setTempo(1318200);
+	setTempo(1318214);
 }
 
 void MidiParser_SBR::parseNextEvent(EventInfo &info) {
diff --git a/engines/ultima/nuvie/sound/mididrv_m_adlib.cpp b/engines/ultima/nuvie/sound/mididrv_m_adlib.cpp
index e52dd1f9d57..123b53dbf9e 100644
--- a/engines/ultima/nuvie/sound/mididrv_m_adlib.cpp
+++ b/engines/ultima/nuvie/sound/mididrv_m_adlib.cpp
@@ -130,7 +130,9 @@ void MidiDriver_M_AdLib::send(int8 source, uint32 b) {
 		oplChannel = 0xFF;
 		activeNote = nullptr;
 		// Allocate a melodic OPL channel.
-		oplChannel = allocateOplChannel(channel, source, 0);
+		InstrumentInfo instrumentInfo;
+		instrumentInfo = { };
+		oplChannel = allocateOplChannel(channel, source, instrumentInfo);
 		if (oplChannel != 0xFF)
 			activeNote = &_activeNotes[oplChannel];
 
@@ -293,7 +295,7 @@ void MidiDriver_M_AdLib::modulation(uint8 channel, uint8 modulation, uint8 sourc
 	writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, modulation);
 }
 
-uint8 MidiDriver_M_AdLib::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
+uint8 MidiDriver_M_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
 	// Allocation of M data channels to OPL output channels is simply 1 on 1.
 	return channel;
 }
diff --git a/engines/ultima/nuvie/sound/mididrv_m_adlib.h b/engines/ultima/nuvie/sound/mididrv_m_adlib.h
index 35e1d69fa6f..0b914f6bd55 100644
--- a/engines/ultima/nuvie/sound/mididrv_m_adlib.h
+++ b/engines/ultima/nuvie/sound/mididrv_m_adlib.h
@@ -63,7 +63,7 @@ protected:
 	void programChange(uint8 channel, uint8 program, uint8 source) override;
 	void modulation(uint8 channel, uint8 modulation, uint8 source) override;
 
-	uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
+	uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
 	uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
 	uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
 	void writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;


Commit: c1e19467a6139c0e00d3f0e6dbfa1e6c0a42f2f7
    https://github.com/scummvm/scummvm/commit/c1e19467a6139c0e00d3f0e6dbfa1e6c0a42f2f7
Author: Coen Rampen (crampen at gmail.com)
Date: 2025-02-22T23:13:54+01:00

Commit Message:
DARKSEED: Add music fade-outs

Changed paths:
    engines/darkseed/cutscene.cpp
    engines/darkseed/darkseed.cpp
    engines/darkseed/menu.cpp
    engines/darkseed/music.cpp
    engines/darkseed/music.h
    engines/darkseed/sound.cpp
    engines/darkseed/sound.h


diff --git a/engines/darkseed/cutscene.cpp b/engines/darkseed/cutscene.cpp
index d4714c22965..1f07e0c2644 100644
--- a/engines/darkseed/cutscene.cpp
+++ b/engines/darkseed/cutscene.cpp
@@ -592,11 +592,12 @@ bool Cutscene::introScene() {
 		}
 		break;
 	case 51:
-		g_engine->_sound->stopMusic();
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 52:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
@@ -734,11 +735,12 @@ bool Cutscene::embryoInsertedScene() {
 		}
 		break;
 	case 16:
-		g_engine->_sound->stopMusic();
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 17:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
@@ -828,10 +830,12 @@ bool Cutscene::shipLaunchScene() {
 		}
 		break;
 	case 13:
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 14:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
@@ -887,10 +891,12 @@ bool Cutscene::alienBornScene() {
 		}
 		break;
 	case 7:
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 8:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
@@ -975,10 +981,12 @@ bool Cutscene::babyDollScene() {
 		}
 		break;
 	case 11:
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 12:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		freeMorph();
@@ -1076,10 +1084,12 @@ bool Cutscene::bookScene() {
 		break;
 	case 14:
 		freeMorph();
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 15:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
@@ -1190,10 +1200,12 @@ bool Cutscene::nightmare2Scene() {
 		break;
 	case 16:
 		freeMorph();
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 17:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
@@ -1269,15 +1281,16 @@ bool Cutscene::nightmare3Scene() {
 		}
 		break;
 	case 11:
+		if (g_engine->_sound->isPlayingMusic())
+			g_engine->_sound->startFadeOut();
 		g_engine->fadeOut();
 		break;
 	case 12:
-		if (g_engine->fadeStep()) {
+		if (g_engine->fadeStep() || g_engine->_sound->isFading()) {
 			return true;
 		}
 		break;
 	default:
-		g_engine->_sound->stopMusic();
 		_movieStep = 9999;
 		return false;
 	}
diff --git a/engines/darkseed/darkseed.cpp b/engines/darkseed/darkseed.cpp
index 56501368f77..6d707e6b5eb 100644
--- a/engines/darkseed/darkseed.cpp
+++ b/engines/darkseed/darkseed.cpp
@@ -1076,6 +1076,15 @@ void DarkseedEngine::loadRoom(int roomNumber) {
 }
 
 void DarkseedEngine::changeToRoom(int newRoomNumber, bool placeDirectly) { // AKA LoadNewRoom
+	MusicId newMusicId = Room::getMusicIdForRoom(newRoomNumber);
+	if (g_engine->_sound->isPlayingMusic() && Room::getMusicIdForRoom(_room->_roomNumber) != newMusicId) {
+		g_engine->_sound->startFadeOut();
+		while (g_engine->_sound->isFading()) {
+			waitxticks(1);
+		}
+		g_engine->_sound->stopMusic();
+	}
+
 	_objectVar[99] = 0;
 	_objectVar[66] = 0;
 	_objectVar[67] = 0;
diff --git a/engines/darkseed/menu.cpp b/engines/darkseed/menu.cpp
index 79990953e69..d95a2eca5d2 100644
--- a/engines/darkseed/menu.cpp
+++ b/engines/darkseed/menu.cpp
@@ -99,6 +99,14 @@ Common::KeyCode Menu::getLocalisedConfirmToQuitKeycode() {
 }
 
 void Menu::loadMenu() {
+	if (g_engine->_sound->isPlayingMusic()) {
+		g_engine->_sound->startFadeOut();
+		while (g_engine->_sound->isFading()) {
+			g_engine->wait();
+		}
+		g_engine->_sound->stopMusic();
+	}
+
 	_open = true;
 	Graphics::Surface screenCopy;
 	screenCopy.copyFrom(*g_engine->_screen);
@@ -199,6 +207,8 @@ void Menu::loadMenu() {
 		g_engine->wait();
 	}
 
+	g_engine->_room->loadRoomMusic();
+
 	g_engine->removeFullscreenPic();
 	_open = false;
 }
diff --git a/engines/darkseed/music.cpp b/engines/darkseed/music.cpp
index 8034ab967c1..f3e11d3ae88 100644
--- a/engines/darkseed/music.cpp
+++ b/engines/darkseed/music.cpp
@@ -268,7 +268,19 @@ void MusicPlayer::play(uint8 priority, bool loop) {
 	if (_floppyAdLibDriver != nullptr)
 		_floppyAdLibDriver->setSourcePriority(0, priority);
 
+	if (_driver->isFading())
+		_driver->abortFade();
+	_driver->resetSourceVolume(0);
+
 	_parser->startPlaying();
 }
 
+void MusicPlayer::startFadeOut() {
+	_driver->startFade(0, 1100, 0);
+}
+
+bool MusicPlayer::isFading() {
+	return _driver->isFading();
+}
+
 } // namespace Darkseed
diff --git a/engines/darkseed/music.h b/engines/darkseed/music.h
index ab7f1545c30..8df15026388 100644
--- a/engines/darkseed/music.h
+++ b/engines/darkseed/music.h
@@ -68,6 +68,9 @@ public:
 	void stop();
 	void pause(bool pause);
 
+	void startFadeOut();
+	bool isFading();
+
 	void syncSoundSettings();
 
 private:
diff --git a/engines/darkseed/sound.cpp b/engines/darkseed/sound.cpp
index dbeb561f1dd..da0529239dc 100644
--- a/engines/darkseed/sound.cpp
+++ b/engines/darkseed/sound.cpp
@@ -364,4 +364,12 @@ void Sound::playFloppySpeech(int tosIdx) {
 	_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_speechHandle, stream);
 }
 
+void Sound::startFadeOut() {
+	_musicPlayer->startFadeOut();
+}
+
+bool Sound::isFading() {
+	return _musicPlayer->isFading();
+}
+
 } // End of namespace Darkseed
diff --git a/engines/darkseed/sound.h b/engines/darkseed/sound.h
index 8237ef34d61..c3169a16bb2 100644
--- a/engines/darkseed/sound.h
+++ b/engines/darkseed/sound.h
@@ -88,6 +88,9 @@ public:
 	void stopSfx();
 	void syncSoundSettings();
 	void killAllSound();
+	void startFadeOut();
+	bool isFading();
+
 private:
 	void playDosCDSfx(int sfxId);
 	void playFloppySpeech(int tosIdx);


Commit: 851430aecfe2c9f37217649cc0479861bf908359
    https://github.com/scummvm/scummvm/commit/851430aecfe2c9f37217649cc0479861bf908359
Author: Coen Rampen (crampen at gmail.com)
Date: 2025-02-22T23:13:54+01:00

Commit Message:
DARKSEED: Add floppy music option

Changed paths:
    engines/darkseed/darkseed.cpp
    engines/darkseed/darkseed.h
    engines/darkseed/detection.h
    engines/darkseed/detection_tables.h
    engines/darkseed/metaengine.cpp
    engines/darkseed/sound.cpp
    engines/darkseed/sound.h


diff --git a/engines/darkseed/darkseed.cpp b/engines/darkseed/darkseed.cpp
index 6d707e6b5eb..fe0fbdb53fc 100644
--- a/engines/darkseed/darkseed.cpp
+++ b/engines/darkseed/darkseed.cpp
@@ -403,6 +403,12 @@ void DarkseedEngine::syncSoundSettings() {
 	_sound->syncSoundSettings();
 }
 
+void DarkseedEngine::pauseEngineIntern(bool pause) {
+	_sound->pauseMusic(pause);
+
+	Engine::pauseEngineIntern(pause);
+}
+
 static constexpr uint8 walkToDirTbl[] = {
 	0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
 	0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
diff --git a/engines/darkseed/darkseed.h b/engines/darkseed/darkseed.h
index b99f9db836f..b958276673a 100644
--- a/engines/darkseed/darkseed.h
+++ b/engines/darkseed/darkseed.h
@@ -167,6 +167,7 @@ public:
 	void waitForSpeechOrSfx();
 
 	void syncSoundSettings() override;
+	void pauseEngineIntern(bool pause) override;
 
 	DarkseedEngine(OSystem *syst, const ADGameDescription *gameDesc);
 	~DarkseedEngine() override;
diff --git a/engines/darkseed/detection.h b/engines/darkseed/detection.h
index 324960379d5..778213a9936 100644
--- a/engines/darkseed/detection.h
+++ b/engines/darkseed/detection.h
@@ -39,6 +39,7 @@ extern const PlainGameDescriptor darkseedGames[];
 extern const ADGameDescription gameDescriptions[];
 
 #define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+#define GAMEOPTION_FLOPPY_MUSIC GUIO_GAMEOPTIONS2
 
 } // End of namespace Darkseed
 
diff --git a/engines/darkseed/detection_tables.h b/engines/darkseed/detection_tables.h
index e52aab7c64c..451ae0ee6cf 100644
--- a/engines/darkseed/detection_tables.h
+++ b/engines/darkseed/detection_tables.h
@@ -79,7 +79,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::EN_ANY,
 		Common::kPlatformDOS,
 		ADGF_TESTING | ADGF_CD,
-		GUIO1(GUIO_NONE)
+		GUIO1(GAMEOPTION_FLOPPY_MUSIC)
 	},
 	{	// 1.51 according to DS.BAT, 1.5P according to intro
 		"darkseed",
@@ -88,7 +88,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::EN_ANY,
 		Common::kPlatformDOS,
 		ADGF_TESTING | ADGF_CD,
-		GUIO1(GUIO_NONE)
+		GUIO1(GAMEOPTION_FLOPPY_MUSIC)
 	},
 	{
 		"darkseed",
@@ -97,7 +97,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::DE_DEU,
 		Common::kPlatformDOS,
 		ADGF_TESTING | ADGF_CD,
-		GUIO1(GUIO_NONE)
+		GUIO1(GAMEOPTION_FLOPPY_MUSIC)
 	},
 {
 		"darkseed",
@@ -106,7 +106,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::FR_FRA,
 		Common::kPlatformDOS,
 		ADGF_TESTING | ADGF_CD,
-		GUIO1(GUIO_NONE)
+		GUIO1(GAMEOPTION_FLOPPY_MUSIC)
 	},
 {
 		"darkseed",
@@ -115,7 +115,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::ES_ESP,
 		Common::kPlatformDOS,
 		ADGF_TESTING | ADGF_CD,
-		GUIO1(GUIO_NONE)
+		GUIO1(GAMEOPTION_FLOPPY_MUSIC)
 	},
 	{
 		"darkseed",
diff --git a/engines/darkseed/metaengine.cpp b/engines/darkseed/metaengine.cpp
index 0a1b2cf0566..0d7b6bf2170 100644
--- a/engines/darkseed/metaengine.cpp
+++ b/engines/darkseed/metaengine.cpp
@@ -41,6 +41,17 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 			0
 		}
 	},
+	{
+		GAMEOPTION_FLOPPY_MUSIC,
+		{
+			_s("Use floppy version music"),
+			_s("Use the music from the floppy version. The floppy version's music files must be copied to the SOUND directory."),
+			"use_floppy_music",
+			false,
+			0,
+			0
+		}
+	},
 	AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
 
diff --git a/engines/darkseed/sound.cpp b/engines/darkseed/sound.cpp
index da0529239dc..7feab1a0419 100644
--- a/engines/darkseed/sound.cpp
+++ b/engines/darkseed/sound.cpp
@@ -133,8 +133,8 @@ static constexpr char sfxCDFilenameTbl[][14] = {
 };
 
 Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer) {
-	// TODO Add config setting for CD version with floppy music
-	_useFloppyMusic = !g_engine->isCdVersion();
+	bool floppyMusicSetting = ConfMan.hasKey("use_floppy_music") ? ConfMan.getBool("use_floppy_music") : false;
+	_useFloppyMusic = !g_engine->isCdVersion() || floppyMusicSetting;
 	_musicPlayer = new MusicPlayer(g_engine, _useFloppyMusic);
 	_didSpeech.resize(978);
 	resetSpeech();
@@ -145,15 +145,16 @@ Sound::~Sound() {
 }
 
 int Sound::init() {
-	Common::File file;
-	Common::Path path = Common::Path("tos1.sit");
-	if (file.open(path)) {
-		_musicPlayer->loadTosInstrumentBankData(&file, (int32)file.size());
-	}
-	else {
-		debug("Failed to load TOS instrument bank data %s", path.toString().c_str());
+	if (_useFloppyMusic) {
+		Common::File file;
+		Common::Path path = g_engine->isCdVersion() ? Common::Path("sound").join("tos1.sit") : Common::Path("tos1.sit");
+		if (file.open(path)) {
+			_musicPlayer->loadTosInstrumentBankData(&file, (int32)file.size());
+		} else {
+			debug("Failed to load TOS instrument bank data %s", path.toString().c_str());
+		}
+		file.close();
 	}
-	file.close();
 
 	return _musicPlayer->open();
 }
@@ -231,8 +232,8 @@ void Sound::playMusic(Common::String const &musicFilename, Common::String const
 	Common::Path path;
 	if (_useFloppyMusic) {
 		if (instrBankFilename != nullptr) {
-			debug("Loading instrument bank: %s", instrBankFilename->c_str());
-			path = Common::Path(instrBankFilename->c_str());
+			path = g_engine->isCdVersion() ? Common::Path("sound").join(instrBankFilename->c_str()) : Common::Path(instrBankFilename->c_str());
+			debug("Loading instrument bank: %s", path.toString().c_str());
 			if (!file.open(path)) {
 				debug("Failed to load %s", path.toString().c_str());
 				return;
@@ -245,12 +246,8 @@ void Sound::playMusic(Common::String const &musicFilename, Common::String const
 			_musicPlayer->loadTosInstrumentBank();
 		}
 	}
-	debug("Loading music: %s", musicFilename.c_str());
-	if (_useFloppyMusic) {
-		path = Common::Path(musicFilename);
-	} else {
-		path = Common::Path("sound").join(musicFilename);
-	}
+	path = g_engine->isCdVersion() ? Common::Path("sound").join(musicFilename) : Common::Path(musicFilename);
+	debug("Loading music: %s", path.toString().c_str());
 	if (!file.open(path)) {
 		debug("Failed to load %s", path.toString().c_str());
 		return;
@@ -265,6 +262,10 @@ void Sound::stopMusic() {
 	_musicPlayer->stop();
 }
 
+void Sound::pauseMusic(bool pause) {
+	_musicPlayer->pause(pause);
+}
+
 void Sound::killAllSound() {
 	stopMusic();
 	stopSfx();
diff --git a/engines/darkseed/sound.h b/engines/darkseed/sound.h
index c3169a16bb2..ca6ff3d6dcb 100644
--- a/engines/darkseed/sound.h
+++ b/engines/darkseed/sound.h
@@ -84,6 +84,7 @@ public:
 	void playMusic(StartMusicId musicId);
 	void playMusic(Common::String const &filename, Common::String const *instrBankFilename = nullptr, uint8 priority = 0xFF, bool loop = false);
 	void stopMusic();
+	void pauseMusic(bool pause);
 	void playSfx(uint8 sfxId, int unk1, int unk2);
 	void stopSfx();
 	void syncSoundSettings();




More information about the Scummvm-git-logs mailing list