[Scummvm-git-logs] scummvm master -> 8cb5916e895feb803bd736c388a759f4823f3368

NMIError noreply at scummvm.org
Fri Jan 9 21:49:06 UTC 2026


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

Summary:
9e9ae8f989 AUDIO/MIDI: Add HMI HMP parser
16021364a3 M4: Music work-in-progress
95af538d94 AUDIO/MIDI: Fix const
46ac37b4ef MIDI/AGOS: Refactor default MT-32 instruments
086fc1b6ae AUDIO/M4: Add HMI SOS AdLib driver
8cb5916e89 AUDIO: Comment out spammy debug message


Commit: 9e9ae8f9895248d2f58c4b33e980d79f4ffb22e3
    https://github.com/scummvm/scummvm/commit/9e9ae8f9895248d2f58c4b33e980d79f4ffb22e3
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-01-09T22:48:47+01:00

Commit Message:
AUDIO/MIDI: Add HMI HMP parser

Changed paths:
  A audio/midiparser_hmp.cpp
  A audio/midiparser_hmp.h
    audio/mididrv.h
    audio/mididrv_ms.cpp
    audio/mididrv_ms.h
    audio/midiparser.h
    audio/midiparser_smf.cpp
    audio/midiparser_smf.h
    audio/module.mk


diff --git a/audio/mididrv.h b/audio/mididrv.h
index 5cb6fe4079d..c394e84b99a 100644
--- a/audio/mididrv.h
+++ b/audio/mididrv.h
@@ -166,6 +166,8 @@ public:
 
 	static const uint8 MT32_PITCH_BEND_SENSITIVITY_DEFAULT = 0x0C;
 	static const uint8 GM_PITCH_BEND_SENSITIVITY_DEFAULT = 0x02;
+	// Default reverb value on the Roland SC-55
+	static const uint8 GM_REVERB_DEFAULT = 0x28;
 
 	static const uint8 GS_RHYTHM_FIRST_NOTE = 0x1B;
 	static const uint8 GS_RHYTHM_LAST_NOTE = 0x58;
diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp
index 778e841fd1b..248c05be88b 100644
--- a/audio/mididrv_ms.cpp
+++ b/audio/mididrv_ms.cpp
@@ -47,6 +47,8 @@ MidiDriver_Multisource::ControllerDefaults::ControllerDefaults() :
 		panning(-1),
 		expression(-1),
 		sustain(-1),
+		reverb(-1),
+		chorus(-1),
 		rpn(-1),
 		pitchBendSensitivity(-1) {
 	Common::fill(program, program + ARRAYSIZE(program), -1);
@@ -216,6 +218,9 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type) {
 	case CONTROLLER_DEFAULT_EXPRESSION:
 		setControllerDefault(type, MIDI_EXPRESSION_DEFAULT);
 		break;
+	case CONTROLLER_DEFAULT_REVERB:
+		setControllerDefault(type, GM_REVERB_DEFAULT);
+		break;
 	case CONTROLLER_DEFAULT_RPN:
 		setControllerDefault(type, MIDI_RPN_NULL);
 		break;
@@ -228,6 +233,7 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type) {
 	case CONTROLLER_DEFAULT_CHANNEL_PRESSURE:
 	case CONTROLLER_DEFAULT_MODULATION:
 	case CONTROLLER_DEFAULT_SUSTAIN:
+	case CONTROLLER_DEFAULT_CHORUS:
 	default:
 		setControllerDefault(type, 0);
 		break;
@@ -268,6 +274,12 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, in
 	case CONTROLLER_DEFAULT_SUSTAIN:
 		_controllerDefaults.sustain = value;
 		break;
+	case CONTROLLER_DEFAULT_REVERB:
+		_controllerDefaults.reverb = value;
+		break;
+	case CONTROLLER_DEFAULT_CHORUS:
+		_controllerDefaults.chorus = value;
+		break;
 	case CONTROLLER_DEFAULT_RPN:
 		_controllerDefaults.rpn = value;
 		break;
diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h
index 7aab2919b27..e2047e1e032 100644
--- a/audio/mididrv_ms.h
+++ b/audio/mididrv_ms.h
@@ -156,7 +156,9 @@ public:
 		CONTROLLER_DEFAULT_EXPRESSION,
 		CONTROLLER_DEFAULT_SUSTAIN,
 		CONTROLLER_DEFAULT_RPN,
-		CONTROLLER_DEFAULT_PITCH_BEND_SENSITIVITY
+		CONTROLLER_DEFAULT_PITCH_BEND_SENSITIVITY,
+		CONTROLLER_DEFAULT_REVERB,
+		CONTROLLER_DEFAULT_CHORUS
 	};
 
 protected:
@@ -201,6 +203,8 @@ protected:
 		int8 panning;
 		int8 expression;
 		int8 sustain;
+		int8 reverb;
+		int8 chorus;
 		int16 rpn;
 
 		int8 pitchBendSensitivity;
diff --git a/audio/midiparser.h b/audio/midiparser.h
index d045f7fc46d..d8ee41aca0c 100644
--- a/audio/midiparser.h
+++ b/audio/midiparser.h
@@ -596,7 +596,8 @@ public:
 	static MidiParser *createParser_SMF(int8 source = -1);
 	static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, int source = -1);
 	static MidiParser *createParser_QT(int8 source = -1);
-	static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
+	static MidiParser *createParser_HMP(int8 source = -1);
+	static void timerCallback(void *data) { ((MidiParser *)data)->onTimer(); }
 };
 /** @} */
 #endif
diff --git a/audio/midiparser_hmp.cpp b/audio/midiparser_hmp.cpp
new file mode 100644
index 00000000000..8936ae031cf
--- /dev/null
+++ b/audio/midiparser_hmp.cpp
@@ -0,0 +1,157 @@
+/* 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/midiparser_hmp.h"
+
+#include "common/algorithm.h"
+#include "common/textconsole.h"
+
+const char MidiParser_HMP::HMP_HEADER[] = "HMIMIDIP";
+const char MidiParser_HMP::HMP_HEADER_VERSION_1[] = "\x00\x00\x00\x00\x00\x00";
+const char MidiParser_HMP::HMP_HEADER_VERSION_013195[] = "013195";
+
+MidiParser_HMP::MidiParser_HMP(int8 source) : MidiParser_SMF(source) {
+}
+
+uint32 MidiParser_HMP::readDelta(byte*& data) {
+	byte str;
+	uint32 value = 0;
+
+	for (int i = 0; i < 4; ++i) {
+		str = *data++;
+		value |= ((str & 0x7F) << (7 * i));
+		if (str & 0x80)
+			break;
+	}
+	return value;
+}
+
+bool MidiParser_HMP::loadMusic(byte *data, uint32 size) {
+	unloadMusic();
+	byte *pos = data;
+
+	// Process header
+	if (memcmp(pos, HMP_HEADER, 8)) {
+		warning("Could not find HMIMIDIP header in HMP data");
+		return false;
+	}
+	pos += 8;
+	_version = determineVersion(pos);
+	// Skip version and padding bytes
+	pos += 24;
+
+	_branchOffset = READ_LE_UINT32(pos);
+	// Skip 3 reserved dwords
+	pos += 16;
+	_numTracks = 1;
+	_numSubtracks[0] = READ_LE_UINT32(pos);
+	pos += 4;
+	// Doesn't seem like this field is actually used...
+	uint32 ppqn = READ_LE_UINT32(pos);
+	_ppqn = 60;
+	pos += 4;
+	uint32 bpm = READ_LE_UINT32(pos);
+	setTempo(60000000 / bpm);
+	pos += 4;
+	_songLength = READ_LE_UINT32(pos);
+	pos += 4;
+
+	for (int i = 0; i < 16; i++) {
+		_channelPriorities[i] = READ_LE_UINT32(pos);
+		pos += 4;
+	}
+	for (int i = 0; i < 5; i++) {
+		for (int j = 0; j < 32; j++) {
+			_deviceTrackMappings[j][i] = READ_LE_UINT32(pos);
+			pos += 4;
+		}
+	}
+	if (_version == HmpVersion::VERSION_013195) {
+		Common::copy(pos, pos + 128, _restoreControllers);
+		pos += 128;
+	}
+
+	_callbackPointer = READ_LE_UINT32(pos);
+	pos += 4;
+	_callbackSegment = READ_LE_UINT32(pos);
+	pos += 4;
+
+	// Read the tracks
+	for (uint currTrack = 0; currTrack < _numSubtracks[0]; currTrack++) {
+		uint32 chunkNumber = READ_LE_UINT32(pos);
+		pos += 4;
+		uint32 chunkSize = READ_LE_UINT32(pos);
+		pos += 4;
+		uint32 trackNumber = READ_LE_UINT32(pos);
+		pos += 4;
+
+		_tracks[0][currTrack] = pos;
+		pos += chunkSize - 12;
+	}
+
+	// TODO Read branching data
+
+	return true;
+}
+
+int32 MidiParser_HMP::determineDataSize(Common::SeekableReadStream *stream) {
+	int64 startPos = stream->pos();
+
+	// Process header
+	if (strcmp(stream->readString('\x00', 8).c_str(), HMP_HEADER)) {
+		return -1;
+	}
+	byte versionBytes[6];
+	stream->readMultipleLE(*versionBytes);
+	HmpVersion version = determineVersion(versionBytes);
+	stream->skip(18);
+
+	// TODO Figure out size of branching data
+	uint32 branchOffset = stream->readUint32LE();
+	stream->skip(12);
+
+	uint32 numTracks = stream->readUint32LE();
+	stream->skip(version == HmpVersion::VERSION_013195 ? 852 : 724);
+
+	// Read tracks
+	for (uint currTrack = 0; currTrack < numTracks; currTrack++) {
+		stream->skip(4);
+		uint32 chunkSize = stream->readUint32LE();
+		stream->skip(chunkSize - 8);
+	}
+
+	// stream should now be at the end of the HMP data
+	// (other than branching data, which might be at the end)
+	return stream->pos() - startPos;
+}
+
+MidiParser_HMP::HmpVersion MidiParser_HMP::determineVersion(byte *pos) {
+	if (!memcmp(pos, HMP_HEADER_VERSION_1, 6)) {
+		return HmpVersion::VERSION_1;
+	} else if (!memcmp(pos, HMP_HEADER_VERSION_013195, 6)) {
+		return HmpVersion::VERSION_013195;
+	} else {
+		warning("Unknown HMP version '%c%c%c%c%c%c' - assuming version 1", pos[0], pos[1], pos[2], pos[3], pos[4], pos[5]);
+		return HmpVersion::VERSION_1;
+	}
+}
+
+MidiParser *MidiParser::createParser_HMP(int8 source) { return new MidiParser_HMP(source); }
diff --git a/audio/midiparser_hmp.h b/audio/midiparser_hmp.h
new file mode 100644
index 00000000000..4dd943662fc
--- /dev/null
+++ b/audio/midiparser_hmp.h
@@ -0,0 +1,62 @@
+/* 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_HMP_H
+#define AUDIO_MIDIPARSER_HMP_H
+
+#include "audio/midiparser_smf.h"
+
+class MidiParser_HMP : public MidiParser_SMF {
+protected:
+	enum class HmpVersion {
+		VERSION_1,
+		VERSION_013195
+	};
+
+	static const char HMP_HEADER[];
+	static const char HMP_HEADER_VERSION_1[];
+	static const char HMP_HEADER_VERSION_013195[];
+
+	uint32 readDelta(byte *&data) override;
+
+	HmpVersion determineVersion(byte *pos);
+
+public:
+	MidiParser_HMP(int8 source = -1);
+
+	bool loadMusic(byte *data, uint32 size) override;
+
+	int32 determineDataSize(Common::SeekableReadStream *stream) override;
+
+protected:
+	HmpVersion _version;
+	uint32 _branchOffset;
+	// Total track length in seconds
+	uint32 _songLength;
+	uint32 _channelPriorities[16];
+	uint32 _deviceTrackMappings[32][5];
+	uint8 _restoreControllers[128];
+	uint32 _callbackPointer;
+	uint32 _callbackSegment;
+};
+
+#endif
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index f7d1568710a..adb5cdfd473 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -29,11 +29,16 @@
 MidiParser_SMF::MidiParser_SMF(int8 source) : MidiParser(source) {
 }
 
+uint32 MidiParser_SMF::readDelta(byte *&data) {
+	// Default implementation: use the standard MIDI VLQ format.
+	return readVLQ(data);
+}
+
 void MidiParser_SMF::parseNextEvent(EventInfo &info) {
 	uint8 subtrack = info.subtrack;
 	const byte *playPos = _position._subtracks[subtrack]._playPos;
 	info.start = playPos;
-	info.delta = readVLQ(playPos);
+	info.delta = readDelta(playPos);
 
 	// Process the next info.
 	if ((playPos[0] & 0xF0) >= 0x80)
diff --git a/audio/midiparser_smf.h b/audio/midiparser_smf.h
index db3c8642fa6..19af97bf005 100644
--- a/audio/midiparser_smf.h
+++ b/audio/midiparser_smf.h
@@ -30,18 +30,9 @@
 class MidiParser_SMF : public MidiParser {
 
 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);
+	// Reads a delta between events.
+	virtual uint32 readDelta(byte *&data);
+
 	void parseNextEvent(EventInfo &info) override;
 
 public:
diff --git a/audio/module.mk b/audio/module.mk
index c1f488efc79..e156a38483e 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -12,6 +12,7 @@ MODULE_OBJS := \
 	mac_plugin.o \
 	mididrv.o \
 	mididrv_ms.o \
+	midiparser_hmp.o \
 	midiparser_qt.o \
 	midiparser_smf.o \
 	midiparser_xmidi.o \


Commit: 16021364a317f5a192f58be38fd892392e7e4b7d
    https://github.com/scummvm/scummvm/commit/16021364a317f5a192f58be38fd892392e7e4b7d
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-01-09T22:48:47+01:00

Commit Message:
M4: Music work-in-progress

Changed paths:
    engines/m4/adv_r/adv_game.h
    engines/m4/m4.cpp
    engines/m4/platform/sound/midi.cpp
    engines/m4/platform/sound/midi.h
    engines/m4/riddle/gui/game_menu.cpp
    engines/m4/vars.cpp


diff --git a/engines/m4/adv_r/adv_game.h b/engines/m4/adv_r/adv_game.h
index 80d7e6c2df6..ba5f991d8ac 100644
--- a/engines/m4/adv_r/adv_game.h
+++ b/engines/m4/adv_r/adv_game.h
@@ -38,7 +38,6 @@ struct GameControl {
 	int16 previous_room = 0;
 
 	int32 digi_overall_volume_percent = 100;
-	int32 midi_overall_volume_percent = 100;
 	bool camera_pan_instant = false;
 
 	/**
diff --git a/engines/m4/m4.cpp b/engines/m4/m4.cpp
index 37cb1725528..1c52698974b 100644
--- a/engines/m4/m4.cpp
+++ b/engines/m4/m4.cpp
@@ -84,12 +84,12 @@ int M4Engine::isDemo() const {
 Common::Error M4Engine::run() {
 	// Initialize 320x200 paletted graphics mode
 	initGraphics(640, 480);
-	syncSoundSettings();
 
 	_subtitles.load();
 
 	// Instantiate globals and setup
 	Vars *vars = createVars();
+	syncSoundSettings();
 
 	if (vars->init()) {
 		// Set the console
@@ -143,6 +143,8 @@ void M4Engine::syncSoundSettings() {
 
 	const int volume = ConfMan.getBool("sfx_mute") ? 0 : CLIP(ConfMan.getInt("sfx_volume"), 0, 255);
 	_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, volume);
+
+	_G(midi).syncSoundSettings();
 }
 
 bool M4Engine::canLoadGameStateCurrently(Common::U32String *msg) {
@@ -266,7 +268,9 @@ Common::Error M4Engine::syncGame(Common::Serializer &s) {
 	} else {
 		s.syncAsByte(_G(kernel).restore_game);
 		s.syncAsSint32LE(_G(game).digi_overall_volume_percent);
-		s.syncAsSint32LE(_G(game).midi_overall_volume_percent);
+		int32 midi_volume_percent = ConfMan.getInt("music_volume") * 100 / 255;
+		s.syncAsSint32LE(midi_volume_percent);
+		ConfMan.setInt("music_volume", midi_volume_percent * 255 / 100);
 		s.syncAsByte(_G(kernel).camera_pan_instant);
 	}
 
@@ -292,7 +296,7 @@ Common::Error M4Engine::syncGame(Common::Serializer &s) {
 		_G(game).previous_room = KERNEL_RESTORING_GAME;
 
 		digi_set_overall_volume(_G(game).digi_overall_volume_percent);
-		midi_set_overall_volume(_G(game).midi_overall_volume_percent);
+		syncSoundSettings();
 		interface_show();
 	}
 
diff --git a/engines/m4/platform/sound/midi.cpp b/engines/m4/platform/sound/midi.cpp
index 99ef8a66918..779507c92f7 100644
--- a/engines/m4/platform/sound/midi.cpp
+++ b/engines/m4/platform/sound/midi.cpp
@@ -19,31 +19,182 @@
  *
  */
 
-#include "audio/midiparser.h"
 #include "m4/platform/sound/midi.h"
 #include "m4/adv_r/adv_file.h"
 #include "m4/vars.h"
 
+#include "common/config-manager.h"
+#include "audio/adlib_ms.h"
+#include "audio/fmopl.h"
+#include "audio/midiparser.h"
+#include "audio/midiparser_hmp.h"
+#include "audio/mt32gm.h"
+
 namespace M4 {
 namespace Sound {
 
 int Midi::_midiEndTrigger;
 
-Midi::Midi(Audio::Mixer *mixer) : _mixer(mixer) {
-	Midi::createDriver();
+Midi::Midi() {
+	_driver = nullptr;
+	_paused = false;
+	_deviceType = MT_NULL;
+	_midiParser = nullptr;
+	_midiData = nullptr;
+}
+
+Midi::~Midi() {
+	stop();
+
+	if (_driver != nullptr) {
+		_driver->setTimerCallback(nullptr, nullptr);
+		_driver->close();
+	}
 
-	int ret = _driver->open();
-	if (ret == 0) {
-		if (_nativeMT32)
-			_driver->sendMT32Reset();
-		else
-			_driver->sendGMReset();
+	Common::StackLock lock(_mutex);
 
-		_driver->setTimerCallback(this, &timerCallback);
+	if (_midiParser != nullptr)
+		delete _midiParser;
+	if (_midiData != nullptr)
+		delete[] _midiData;
+	if (_driver != nullptr) {
+		delete _driver;
+		_driver = nullptr;
 	}
 }
 
+int Midi::open() {
+	assert(_driver == nullptr);
+
+	// Check the type of device that the user has configured.
+	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+	_deviceType = MidiDriver::getMusicType(dev);
+	if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
+		_deviceType = MT_MT32;
+
+	OPL::Config::OplType oplType;
+	switch (_deviceType) {
+	case MT_ADLIB:
+		oplType = MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::kOpl3) ? OPL::Config::kOpl3 : OPL::Config::kOpl2;
+		_driver = new MidiDriver_ADLIB_Multisource(oplType);
+		break;
+	case MT_GM:
+	case MT_MT32:
+		_driver = new MidiDriver_MT32GM(MusicType::MT_GM);
+		break;
+	default:
+		_driver = new MidiDriver_NULL_Multisource();
+		break;
+	}
+
+	_midiParser = new MidiParser_HMP(0);
+
+	_driver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
+	// Riddle's MIDI data does not consistently set values for every controller
+	// at the start of every track
+	_driver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PITCH_BEND);
+	_driver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_MODULATION);
+	_driver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_PANNING);
+	_driver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_REVERB);
+	_driver->setControllerDefault(MidiDriver_Multisource::CONTROLLER_DEFAULT_CHORUS);
+
+	_midiParser->property(MidiParser::mpDisableAutoStartPlayback, true);
+	// Riddle's MIDI data uses sustain
+	_midiParser->property(MidiParser::mpSendSustainOffOnNotesOff, true);
+
+	// Open the MIDI driver.
+	int returnCode = _driver->open();
+	if (returnCode != 0)
+		error("Midi::open - Failed to open MIDI music driver - error code %d.", returnCode);
+
+	syncSoundSettings();
+
+	// Connect the driver and the parser.
+	_midiParser->setMidiDriver(_driver);
+	_midiParser->setTimerRate(_driver->getBaseTempo());
+	_driver->setTimerCallback(this, &onTimer);
+
+	return 0;
+}
+
+void Midi::load(byte* in, int32 size) {
+	Common::StackLock lock(_mutex);
+
+	if (_midiParser == nullptr)
+		return;
+
+	_midiParser->unloadMusic();
+
+	if (_midiData != nullptr)
+		delete[] _midiData;
+	_midiData = new byte[size];
+
+	Common::copy(in, in + size, _midiData);
+
+	_midiParser->loadMusic(_midiData, size);
+}
+
+void Midi::play() {
+	Common::StackLock lock(_mutex);
+
+	if (_midiParser == nullptr || _driver == nullptr)
+		return;
+
+	_midiParser->startPlaying();
+}
+
+void Midi::pause(bool pause) {
+	if (_paused == pause || _driver == nullptr)
+		return;
+
+	_paused = pause;
+
+	if (_midiParser != nullptr) {
+		Common::StackLock lock(_mutex);
+		if (_paused) {
+			_midiParser->pausePlaying();
+		} else {
+			_midiParser->resumePlaying();
+		}
+	}
+}
+
+void Midi::stop() {
+	Common::StackLock lock(_mutex);
+
+	if (_midiParser != nullptr) {
+		_midiParser->stopPlaying();
+		if (_driver != nullptr)
+			_driver->deinitSource(0);
+	}
+}
+
+bool Midi::isPlaying() {
+	Common::StackLock lock(_mutex);
+
+	return _midiParser->isPlaying();
+}
+
+void Midi::startFade(uint16 duration, uint16 targetVolume) {
+	if (_driver == nullptr || _midiParser == nullptr || !_midiParser->isPlaying())
+		return;
+
+	_driver->startFade(0, duration, targetVolume);
+}
+
+bool Midi::isFading() {
+	return _driver->isFading(0);
+}
+
+void Midi::syncSoundSettings() {
+	if (_driver != nullptr)
+		_driver->syncSoundSettings();
+}
+
 void Midi::midi_play(const char *name, int volume, bool loop, int trigger, int roomNum) {
+	if (_driver == nullptr || _midiParser == nullptr)
+		return;
+
 	_midiEndTrigger = trigger;
 
 	// Load in the resource
@@ -54,14 +205,22 @@ void Midi::midi_play(const char *name, int volume, bool loop, int trigger, int r
 		error("Could not find music - %s", fileName.c_str());
 
 	HLock(workHandle);
-#if 0
-// Dump the midi file for analysis purposes
+	/*
 	Common::DumpFile dump;
 	dump.open(fileName.c_str());
 	dump.write(*workHandle, assetSize);
 	dump.close();
-#endif
+	*/
+
+	load((byte *)*workHandle, assetSize);
+	_midiParser->setTrack(0);
+	_midiParser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
+	// TODO Some calls use volume 0? What is that supposed to do?
+	_driver->setSourceVolume(0, volume);
 
+	play();
+
+	/*
 #ifdef TODO
 	byte *pSrc = (byte *)*workHandle;
 
@@ -84,6 +243,7 @@ void Midi::midi_play(const char *name, int volume, bool loop, int trigger, int r
 	if (trigger != -1)
 		kernel_timing_trigger(10, trigger);
 #endif
+	*/
 
 	HUnLock(workHandle);
 	rtoss(fileName);
@@ -97,12 +257,25 @@ void Midi::loop() {
 	// No implementation
 }
 
-void Midi::set_overall_volume(int vol) {
-	_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, vol * 255 / 100);
+void Midi::midi_fade_volume(int targetVolume, int duration) {
+	uint16 durationMsec = duration * 1000 / 30;
+	startFade(durationMsec, targetVolume);
+	// TODO Should this stop playback when fade is completed?
+	// Should this call return after the fade has completed?
 }
 
-int Midi::get_overall_volume() const {
-	return _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) * 100 / 255;
+void Midi::onTimer(void* data) {
+	Midi *m = (Midi *)data;
+	Common::StackLock lock(m->_mutex);
+
+	if (m->_midiParser != nullptr) {
+		m->_midiParser->onTimer();
+		if (!m->_midiParser->isPlaying() && _midiEndTrigger >= 0) {
+			// FIXME Can this trigger a deadlock on the mutex?
+			kernel_timing_trigger(10, _midiEndTrigger);
+			_midiEndTrigger = -1;
+		}
+	}
 }
 
 } // namespace Sound
@@ -119,16 +292,8 @@ void midi_stop() {
 	_G(midi).stop();
 }
 
-void midi_set_overall_volume(int vol) {
-	_G(midi).set_overall_volume(vol);
-}
-
-int midi_get_overall_volume() {
-	return _G(midi).get_overall_volume();
-}
-
-void midi_fade_volume(int val1, int val2) {
-	warning("TODO: midi_fade_volume");
+void midi_fade_volume(int targetVolume, int duration) {
+	_G(midi).midi_fade_volume(targetVolume, duration);
 }
 
 } // namespace M4
diff --git a/engines/m4/platform/sound/midi.h b/engines/m4/platform/sound/midi.h
index 04f6ee93f09..f5f8b220c0f 100644
--- a/engines/m4/platform/sound/midi.h
+++ b/engines/m4/platform/sound/midi.h
@@ -23,24 +23,51 @@
 #ifndef M4_SOUND_PLATFORM_MIDI_H
 #define M4_SOUND_PLATFORM_MIDI_H
 
-#include "audio/midiplayer.h"
 #include "m4/m4_types.h"
 
+#include "audio/mididrv_ms.h"
+#include "audio/midiparser.h"
+
 namespace M4 {
 namespace Sound {
 
-class Midi : public Audio::MidiPlayer {
+class Midi {
 private:
 	static int _midiEndTrigger;
-	Audio::Mixer *_mixer;
+
+	Common::Mutex _mutex;
+
+	MusicType _deviceType;
+
+	MidiDriver_Multisource *_driver;
+	MidiParser *_midiParser;
+	byte *_midiData;
+
+	bool _paused;
+
+protected:
+	static void onTimer(void *data);
+
 public:
-	Midi(Audio::Mixer *mixer);
+	Midi();
+	~Midi();
+
+	int open();
+
+	void load(byte *in, int32 size);
+	void play();
+	void pause(bool pause);
+	void stop();
+	bool isPlaying();
+	void startFade(uint16 duration, uint16 targetVolume);
+	bool isFading();
+
+	void syncSoundSettings();
 
 	void midi_play(const char *name, int volume, bool loop, int trigger, int roomNum);
 	void task();
 	void loop();
-	void set_overall_volume(int vol);
-	int get_overall_volume() const;
+	void midi_fade_volume(int targetVolume, int duration);
 };
 
 } // namespace Sound
@@ -48,9 +75,7 @@ public:
 void midi_play(const char *name, int volume, bool loop, int trigger, int roomNum);
 void midi_loop();
 void midi_stop();
-void midi_set_overall_volume(int vol);
-int midi_get_overall_volume();
-void midi_fade_volume(int val1, int val2);
+void midi_fade_volume(int targetVolume, int duration);
 
 } // namespace M4
 
diff --git a/engines/m4/riddle/gui/game_menu.cpp b/engines/m4/riddle/gui/game_menu.cpp
index 0d1183ec0b5..f58722279e0 100644
--- a/engines/m4/riddle/gui/game_menu.cpp
+++ b/engines/m4/riddle/gui/game_menu.cpp
@@ -32,6 +32,7 @@
 #include "m4/mem/mem.h"
 #include "m4/platform/keys.h"
 #include "m4/m4.h"
+#include "common/config-manager.h"
 
 #include "m4/burger/gui/game_menu.h"
 
@@ -243,7 +244,7 @@ void OptionsMenu::show() {
 	assert(_GM(opMenu));
 
 	const int digiPercent = digi_get_overall_volume();
-	const int midiPercent = midi_get_overall_volume();
+	const int midiPercent = ConfMan.getInt("music_volume") * 100 / 255;
 
 	menuItemButton::add(_GM(opMenu), OM_TAG_GAMEMENU,
 		OM_GAMEMENU_X, OM_GAMEMENU_Y, OM_GAMEMENU_W, OM_GAMEMENU_H,
@@ -291,7 +292,7 @@ void OptionsMenu::cbSetDigi(M4::GUI::menuItemHSlider *myItem, M4::GUI::guiMenu *
 }
 
 void OptionsMenu::cbSetMidi(M4::GUI::menuItemHSlider *myItem, M4::GUI::guiMenu *) {
-	midi_set_overall_volume(myItem->percent);
+	ConfMan.setInt("music_volume", myItem->percent * 255 / 100);
 }
 
 /*------------------- SAVE/LOAD METHODS ------------------*/
diff --git a/engines/m4/vars.cpp b/engines/m4/vars.cpp
index f316478839a..ebfb4c8903a 100644
--- a/engines/m4/vars.cpp
+++ b/engines/m4/vars.cpp
@@ -41,7 +41,7 @@ namespace M4 {
 
 Vars *g_vars;
 
-Vars::Vars() : _digi(g_engine->_mixer), _midi(g_engine->_mixer) {
+Vars::Vars() : _digi(g_engine->_mixer), _midi() {
 	g_vars = this;
 
 	Common::fill(_sizeMem, _sizeMem + _MEMTYPE_LIMIT, 0);
@@ -134,7 +134,9 @@ void Vars::game_systems_initialize(byte flags) {
 	fire_up_gui();
 
 	if (flags & INSTALL_SOUND_DRIVERS) {
-		// No implementation
+		int result = _midi.open();
+		if (result > 0)
+			warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(result));
 	} else {
 		term_message("Sound driver installation skipped");
 	}


Commit: 95af538d94301932529a74757e3ef1fdaa76e51f
    https://github.com/scummvm/scummvm/commit/95af538d94301932529a74757e3ef1fdaa76e51f
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-01-09T22:48:47+01:00

Commit Message:
AUDIO/MIDI: Fix const

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


diff --git a/audio/midiparser_hmp.cpp b/audio/midiparser_hmp.cpp
index 8936ae031cf..69b51057837 100644
--- a/audio/midiparser_hmp.cpp
+++ b/audio/midiparser_hmp.cpp
@@ -31,7 +31,7 @@ const char MidiParser_HMP::HMP_HEADER_VERSION_013195[] = "013195";
 MidiParser_HMP::MidiParser_HMP(int8 source) : MidiParser_SMF(source) {
 }
 
-uint32 MidiParser_HMP::readDelta(byte*& data) {
+uint32 MidiParser_HMP::readDelta(const byte*& data) {
 	byte str;
 	uint32 value = 0;
 
@@ -44,9 +44,9 @@ uint32 MidiParser_HMP::readDelta(byte*& data) {
 	return value;
 }
 
-bool MidiParser_HMP::loadMusic(byte *data, uint32 size) {
+bool MidiParser_HMP::loadMusic(const byte *data, uint32 size) {
 	unloadMusic();
-	byte *pos = data;
+	const byte *pos = data;
 
 	// Process header
 	if (memcmp(pos, HMP_HEADER, 8)) {
@@ -143,7 +143,7 @@ int32 MidiParser_HMP::determineDataSize(Common::SeekableReadStream *stream) {
 	return stream->pos() - startPos;
 }
 
-MidiParser_HMP::HmpVersion MidiParser_HMP::determineVersion(byte *pos) {
+MidiParser_HMP::HmpVersion MidiParser_HMP::determineVersion(const byte *pos) {
 	if (!memcmp(pos, HMP_HEADER_VERSION_1, 6)) {
 		return HmpVersion::VERSION_1;
 	} else if (!memcmp(pos, HMP_HEADER_VERSION_013195, 6)) {
diff --git a/audio/midiparser_hmp.h b/audio/midiparser_hmp.h
index 4dd943662fc..12dbe2027cb 100644
--- a/audio/midiparser_hmp.h
+++ b/audio/midiparser_hmp.h
@@ -36,14 +36,14 @@ protected:
 	static const char HMP_HEADER_VERSION_1[];
 	static const char HMP_HEADER_VERSION_013195[];
 
-	uint32 readDelta(byte *&data) override;
+	uint32 readDelta(const byte *&data) override;
 
-	HmpVersion determineVersion(byte *pos);
+	HmpVersion determineVersion(const byte *pos);
 
 public:
 	MidiParser_HMP(int8 source = -1);
 
-	bool loadMusic(byte *data, uint32 size) override;
+	bool loadMusic(const byte *data, uint32 size) override;
 
 	int32 determineDataSize(Common::SeekableReadStream *stream) override;
 
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
index adb5cdfd473..2bf7e1f0932 100644
--- a/audio/midiparser_smf.cpp
+++ b/audio/midiparser_smf.cpp
@@ -29,7 +29,7 @@
 MidiParser_SMF::MidiParser_SMF(int8 source) : MidiParser(source) {
 }
 
-uint32 MidiParser_SMF::readDelta(byte *&data) {
+uint32 MidiParser_SMF::readDelta(const byte *&data) {
 	// Default implementation: use the standard MIDI VLQ format.
 	return readVLQ(data);
 }
diff --git a/audio/midiparser_smf.h b/audio/midiparser_smf.h
index 19af97bf005..f2da401096b 100644
--- a/audio/midiparser_smf.h
+++ b/audio/midiparser_smf.h
@@ -31,7 +31,7 @@ class MidiParser_SMF : public MidiParser {
 
 protected:
 	// Reads a delta between events.
-	virtual uint32 readDelta(byte *&data);
+	virtual uint32 readDelta(const byte *&data);
 
 	void parseNextEvent(EventInfo &info) override;
 


Commit: 46ac37b4efe4f794f1c386b1344a90af854e7a67
    https://github.com/scummvm/scummvm/commit/46ac37b4efe4f794f1c386b1344a90af854e7a67
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-01-09T22:48:47+01:00

Commit Message:
MIDI/AGOS: Refactor default MT-32 instruments

Changed paths:
    audio/adlib_ms.cpp
    audio/mididrv_ms.cpp
    audio/mididrv_ms.h
    audio/mt32gm.cpp
    audio/mt32gm.h
    engines/agos/midi.cpp


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index d13015b0762..dc79e224f7e 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -2089,7 +2089,7 @@ 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);
+	debug("Writing register %X %X", 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
diff --git a/audio/mididrv_ms.cpp b/audio/mididrv_ms.cpp
index 248c05be88b..a214c007883 100644
--- a/audio/mididrv_ms.cpp
+++ b/audio/mididrv_ms.cpp
@@ -292,7 +292,7 @@ void MidiDriver_Multisource::setControllerDefault(ControllerDefaultType type, in
 	}
 }
 
-void MidiDriver_Multisource::setControllerDefaults(ControllerDefaultType type, int16 *value) {
+void MidiDriver_Multisource::setControllerDefaults(ControllerDefaultType type, const int16 *value) {
 	// Set the specified default values on _controllerDefaults on the field
 	// corresponding to the specified controller.
 	switch (type) {
diff --git a/audio/mididrv_ms.h b/audio/mididrv_ms.h
index e2047e1e032..07b665df992 100644
--- a/audio/mididrv_ms.h
+++ b/audio/mididrv_ms.h
@@ -370,7 +370,7 @@ public:
 	 * @param values The default values which should be set. Must be a 16 value
 	 * array.
 	 */
-	void setControllerDefaults(ControllerDefaultType type, int16 *values);
+	void setControllerDefaults(ControllerDefaultType type, const int16 *values);
 	/**
 	 * Clears a previously set default value for the specified controller.
 	 * 
diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
index 441acf2f3f3..4cc5457666f 100644
--- a/audio/mt32gm.cpp
+++ b/audio/mt32gm.cpp
@@ -41,6 +41,11 @@ const byte MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS[8] = {
 	0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A
 };
 
+// Same as above, now numbered by MIDI channel. For use with setControllerDefaults.
+const int16 MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS_CONTROLLER_DEFAULTS[16] = {
+	-1, 0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A, -1, -1, -1, -1, -1, -1, -1
+};
+
 // These are the power-on default panning settings for channels 2-9 of the Roland MT-32 family.
 // Internally, the MT-32 has 15 panning positions (0-E with 7 being center).
 // This has been translated to the equivalent MIDI panning values (0-127).
diff --git a/audio/mt32gm.h b/audio/mt32gm.h
index 43de8d15a9a..ee931bae872 100644
--- a/audio/mt32gm.h
+++ b/audio/mt32gm.h
@@ -111,6 +111,7 @@
 class MidiDriver_MT32GM : public MidiDriver_Multisource {
 public:
 	static const byte MT32_DEFAULT_INSTRUMENTS[8];
+	static const int16 MT32_DEFAULT_INSTRUMENTS_CONTROLLER_DEFAULTS[16];
 	static const byte MT32_DEFAULT_PANNING[8];
 	static const uint8 MT32_DEFAULT_CHANNEL_VOLUME = 102;
 	static const uint8 GM_DEFAULT_CHANNEL_VOLUME = 100;
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index a8f6cb1ef54..6c50733400e 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -250,10 +250,7 @@ int MidiPlayer::open() {
 			_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);
+				_driverMsMusic->setControllerDefaults(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM, MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS_CONTROLLER_DEFAULTS);
 			}
 			if (usesMidiSfx) {
 				if (ConfMan.getBool("multi_midi")) {
@@ -355,10 +352,7 @@ int MidiPlayer::open() {
 			_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);
+			_driverMsMusic->setControllerDefaults(MidiDriver_Multisource::CONTROLLER_DEFAULT_PROGRAM, MidiDriver_MT32GM::MT32_DEFAULT_INSTRUMENTS_CONTROLLER_DEFAULTS);
 
 			if (_vm->getPlatform() == Common::kPlatformDOS && !(_vm->getFeatures() & GF_TALKIE) &&
 					ConfMan.getBool("multi_midi")) {


Commit: 086fc1b6aec49703d05f8ffce5a7fd7b243b52ad
    https://github.com/scummvm/scummvm/commit/086fc1b6aec49703d05f8ffce5a7fd7b243b52ad
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-01-09T22:48:47+01:00

Commit Message:
AUDIO/M4: Add HMI SOS AdLib driver

Changed paths:
  A audio/adlib_hmisos.cpp
  A audio/adlib_hmisos.h
    audio/midiparser_hmp.cpp
    audio/midiparser_hmp.h
    audio/module.mk
    engines/m4/platform/sound/midi.cpp


diff --git a/audio/adlib_hmisos.cpp b/audio/adlib_hmisos.cpp
new file mode 100644
index 00000000000..58a7626b17b
--- /dev/null
+++ b/audio/adlib_hmisos.cpp
@@ -0,0 +1,130 @@
+/* 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/adlib_hmisos.h"
+
+const byte MidiDriver_ADLIB_HMISOS::INSTRUMENT_BANK_SIGNATURE[8] = { 0x00, 0x00, 'A', 'D', 'L', 'I', 'B', '-' };
+
+MidiDriver_ADLIB_HMISOS::MidiDriver_ADLIB_HMISOS(OPL::Config::OplType oplType, int timerFrequency) :
+		MidiDriver_ADLIB_Multisource(oplType, timerFrequency) {
+	memset(_sosInstrumentBank, 0, sizeof(_sosInstrumentBank));
+	memset(_sosRhythmBank, 0, sizeof(_sosRhythmBank));
+
+	_instrumentWriteMode = INSTRUMENT_WRITE_MODE_NOTE_ON;
+	_modulationDepth = MODULATION_DEPTH_LOW;
+	_vibratoDepth = VIBRATO_DEPTH_LOW;
+	_rhythmModeRewriteSharedRegister = true;
+}
+
+int MidiDriver_ADLIB_HMISOS::open() {
+
+	return MidiDriver_ADLIB_Multisource::open();
+}
+
+bool MidiDriver_ADLIB_HMISOS::loadInstrumentBanks(Common::SeekableReadStream *instrumentBankStream, Common::SeekableReadStream *rhythmBankStream) {
+	int numInstruments = loadInstrumentBank(instrumentBankStream, false);
+	if (numInstruments < 0)
+		return false;
+	_instrumentBank = _sosInstrumentBank;
+
+	if (rhythmBankStream != nullptr) {
+		numInstruments = loadInstrumentBank(rhythmBankStream, true);
+		if (numInstruments < 0)
+			return false;
+		_rhythmBank = _sosRhythmBank;
+		_rhythmBankFirstNote = 0;
+		_rhythmBankLastNote = numInstruments;
+	}
+
+	return true;
+}
+
+int MidiDriver_ADLIB_HMISOS::loadInstrumentBank(Common::SeekableReadStream *stream, bool rhythmBank) {
+	OplInstrumentDefinition *instrumentBank = rhythmBank ? _sosRhythmBank : _sosInstrumentBank;
+
+	byte signature[8];
+	stream->read(signature, 8);
+
+	if (memcmp(INSTRUMENT_BANK_SIGNATURE, signature, 8)) {
+		warning("Invalid HMI SOS AdLib instrument bank signature");
+		return -1;
+	}
+
+	uint16 instrumentsUsed = stream->readUint16LE();
+	if (instrumentsUsed > 128)
+		instrumentsUsed = 128;
+	//uint16 instrumentsTotal = stream->readUint16LE();
+	stream->skip(2);
+	uint32 namesOffset = stream->readUint32LE();
+	uint32 instDataOffset = stream->readUint32LE();
+
+	stream->seek(namesOffset, SEEK_SET);
+	byte rhythmNotes[128];
+	for (uint16 i = 0; i < instrumentsUsed; i++) {
+		stream->skip(2); // Instrument data offset
+		// The flags byte is used to specify the rhythm note
+		rhythmNotes[i] = stream->readByte();
+		stream->skip(9); // Instrument name
+	}
+
+	stream->seek(instDataOffset, SEEK_SET);
+	AdLibBnkInstrumentDefinition bnkInstDef;
+	for (uint16 i = 0; i < instrumentsUsed; i++) {
+		stream->skip(2); // Instrument type and voice number
+
+		bnkInstDef.operator0.keyScalingLevel = stream->readByte();
+		bnkInstDef.operator0.frequencyMultiplier = stream->readByte();
+		bnkInstDef.operator0.feedback = stream->readByte();
+		bnkInstDef.operator0.attack = stream->readByte();
+		bnkInstDef.operator0.sustain = stream->readByte();
+		bnkInstDef.operator0.envelopeGainType = stream->readByte();
+		bnkInstDef.operator0.decay = stream->readByte();
+		bnkInstDef.operator0.release = stream->readByte();
+		bnkInstDef.operator0.level = stream->readByte();
+		bnkInstDef.operator0.amplitudeModulation = stream->readByte();
+		bnkInstDef.operator0.vibrato = stream->readByte();
+		bnkInstDef.operator0.keyScalingRate = stream->readByte();
+		// Connection bit in SOS BNK is not flipped
+		bnkInstDef.operator0.connection = stream->readByte() == 0 ? 1 : 0;
+
+		bnkInstDef.operator1.keyScalingLevel = stream->readByte();
+		bnkInstDef.operator1.frequencyMultiplier = stream->readByte();
+		bnkInstDef.operator1.feedback = stream->readByte();
+		bnkInstDef.operator1.attack = stream->readByte();
+		bnkInstDef.operator1.sustain = stream->readByte();
+		bnkInstDef.operator1.envelopeGainType = stream->readByte();
+		bnkInstDef.operator1.decay = stream->readByte();
+		bnkInstDef.operator1.release = stream->readByte();
+		bnkInstDef.operator1.level = stream->readByte();
+		bnkInstDef.operator1.amplitudeModulation = stream->readByte();
+		bnkInstDef.operator1.vibrato = stream->readByte();
+		bnkInstDef.operator1.keyScalingRate = stream->readByte();
+		bnkInstDef.operator1.connection = stream->readByte() == 0 ? 1 : 0;
+
+		bnkInstDef.waveformSelect0 = stream->readByte();
+		bnkInstDef.waveformSelect1 = stream->readByte();
+
+		bnkInstDef.toOplInstrumentDefinition(instrumentBank[i]);
+		instrumentBank[i].rhythmNote = rhythmNotes[i];
+	}
+
+	return instrumentsUsed;
+}
diff --git a/audio/adlib_hmisos.h b/audio/adlib_hmisos.h
new file mode 100644
index 00000000000..f959673d3c2
--- /dev/null
+++ b/audio/adlib_hmisos.h
@@ -0,0 +1,57 @@
+/* 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_ADLIB_HMISOS_H
+#define AUDIO_ADLIB_HMISOS_H
+
+#include "audio/adlib_ms.h"
+
+#include "common/stream.h"
+
+/**
+ * General MIDI AdLib driver based on HMI Sound Operating System version dated 1994-11-27
+ * (as used by The Riddle Of Master Lu).
+ *
+ * Currently, only instrument bank loading is implemented.
+ * TODO:
+ * - Frequency calculation
+ * - Level calculation
+ * - Channel allocation
+ * - Controller 0x66 (pitch bend range)
+ */
+class MidiDriver_ADLIB_HMISOS : public MidiDriver_ADLIB_Multisource {
+public:
+	static const byte INSTRUMENT_BANK_SIGNATURE[8];
+
+	MidiDriver_ADLIB_HMISOS(OPL::Config::OplType oplType, int timerFrequency = OPL::OPL::kDefaultCallbackFrequency);
+	~MidiDriver_ADLIB_HMISOS() {};
+
+	int open() override;
+	bool loadInstrumentBanks(Common::SeekableReadStream *instrumentBankStream, Common::SeekableReadStream *rhythmBankStream = nullptr);
+
+protected:
+	int loadInstrumentBank(Common::SeekableReadStream *instrumentBankStream, bool rhythmBank);
+
+	OplInstrumentDefinition _sosInstrumentBank[128];
+	OplInstrumentDefinition _sosRhythmBank[128];
+};
+
+#endif
diff --git a/audio/midiparser_hmp.cpp b/audio/midiparser_hmp.cpp
index 69b51057837..deea89ae38d 100644
--- a/audio/midiparser_hmp.cpp
+++ b/audio/midiparser_hmp.cpp
@@ -29,6 +29,14 @@ const char MidiParser_HMP::HMP_HEADER_VERSION_1[] = "\x00\x00\x00\x00\x00\x00";
 const char MidiParser_HMP::HMP_HEADER_VERSION_013195[] = "013195";
 
 MidiParser_HMP::MidiParser_HMP(int8 source) : MidiParser_SMF(source) {
+	_version = HmpVersion::VERSION_1;
+	_branchOffset = 0;
+	_songLength = 0;
+	memset(_channelPriorities, 0, sizeof(_channelPriorities));
+	memset(_deviceTrackMappings, 0, sizeof(_deviceTrackMappings));
+	memset(_restoreControllers, 0, sizeof(_restoreControllers));
+	_callbackPointer = 0;
+	_callbackSegment = 0;
 }
 
 uint32 MidiParser_HMP::readDelta(const byte*& data) {
@@ -65,7 +73,7 @@ bool MidiParser_HMP::loadMusic(const byte *data, uint32 size) {
 	_numSubtracks[0] = READ_LE_UINT32(pos);
 	pos += 4;
 	// Doesn't seem like this field is actually used...
-	uint32 ppqn = READ_LE_UINT32(pos);
+	//uint32 ppqn = READ_LE_UINT32(pos);
 	_ppqn = 60;
 	pos += 4;
 	uint32 bpm = READ_LE_UINT32(pos);
@@ -96,11 +104,11 @@ bool MidiParser_HMP::loadMusic(const byte *data, uint32 size) {
 
 	// Read the tracks
 	for (uint currTrack = 0; currTrack < _numSubtracks[0]; currTrack++) {
-		uint32 chunkNumber = READ_LE_UINT32(pos);
+		//uint32 chunkNumber = READ_LE_UINT32(pos);
 		pos += 4;
 		uint32 chunkSize = READ_LE_UINT32(pos);
 		pos += 4;
-		uint32 trackNumber = READ_LE_UINT32(pos);
+		//uint32 trackNumber = READ_LE_UINT32(pos);
 		pos += 4;
 
 		_tracks[0][currTrack] = pos;
@@ -125,7 +133,8 @@ int32 MidiParser_HMP::determineDataSize(Common::SeekableReadStream *stream) {
 	stream->skip(18);
 
 	// TODO Figure out size of branching data
-	uint32 branchOffset = stream->readUint32LE();
+	//uint32 branchOffset = stream->readUint32LE();
+	stream->skip(4);
 	stream->skip(12);
 
 	uint32 numTracks = stream->readUint32LE();
diff --git a/audio/midiparser_hmp.h b/audio/midiparser_hmp.h
index 12dbe2027cb..bc2e863d166 100644
--- a/audio/midiparser_hmp.h
+++ b/audio/midiparser_hmp.h
@@ -19,12 +19,17 @@
  *
  */
 
-
 #ifndef AUDIO_MIDIPARSER_HMP_H
 #define AUDIO_MIDIPARSER_HMP_H
 
 #include "audio/midiparser_smf.h"
 
+/**
+ * MIDI parser for the HMI SOS format HMP.
+ * 
+ * Implementation is incomplete. Currently only the track chunks are read and
+ * played back. Not implented yet: device track mapping, branching, callbacks, etc.
+ */
 class MidiParser_HMP : public MidiParser_SMF {
 protected:
 	enum class HmpVersion {
diff --git a/audio/module.mk b/audio/module.mk
index e156a38483e..14fc47c208e 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -3,6 +3,7 @@ MODULE := audio
 MODULE_OBJS := \
 	adlib.o \
 	adlib_ctmidi.o \
+	adlib_hmisos.o \
 	adlib_ms.o \
 	audiostream.o \
 	casio.o \
diff --git a/engines/m4/platform/sound/midi.cpp b/engines/m4/platform/sound/midi.cpp
index 779507c92f7..cede69aaec4 100644
--- a/engines/m4/platform/sound/midi.cpp
+++ b/engines/m4/platform/sound/midi.cpp
@@ -24,6 +24,7 @@
 #include "m4/vars.h"
 
 #include "common/config-manager.h"
+#include "audio/adlib_hmisos.h"
 #include "audio/adlib_ms.h"
 #include "audio/fmopl.h"
 #include "audio/midiparser.h"
@@ -75,8 +76,18 @@ int Midi::open() {
 	OPL::Config::OplType oplType;
 	switch (_deviceType) {
 	case MT_ADLIB:
-		oplType = MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::kOpl3) ? OPL::Config::kOpl3 : OPL::Config::kOpl2;
-		_driver = new MidiDriver_ADLIB_Multisource(oplType);
+		oplType = MidiDriver_ADLIB_HMISOS::detectOplType(OPL::Config::kOpl3) ? OPL::Config::kOpl3 : OPL::Config::kOpl2;
+		MidiDriver_ADLIB_HMISOS *adLibDriver;
+		adLibDriver = new MidiDriver_ADLIB_HMISOS(oplType);
+		_driver = adLibDriver;
+
+		Common::SeekableReadStream *instrumentBankStream;
+		instrumentBankStream = SearchMan.createReadStreamForMember(Common::Path("MELODIC.BNK"));
+		Common::SeekableReadStream *rhythmBankStream;
+		rhythmBankStream = SearchMan.createReadStreamForMember(Common::Path("DRUM.BNK"));
+
+		adLibDriver->loadInstrumentBanks(instrumentBankStream, rhythmBankStream);
+
 		break;
 	case MT_GM:
 	case MT_MT32:


Commit: 8cb5916e895feb803bd736c388a759f4823f3368
    https://github.com/scummvm/scummvm/commit/8cb5916e895feb803bd736c388a759f4823f3368
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-01-09T22:48:47+01:00

Commit Message:
AUDIO: Comment out spammy debug message

Changed paths:
    audio/adlib_ms.cpp


diff --git a/audio/adlib_ms.cpp b/audio/adlib_ms.cpp
index dc79e224f7e..d13015b0762 100644
--- a/audio/adlib_ms.cpp
+++ b/audio/adlib_ms.cpp
@@ -2089,7 +2089,7 @@ 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);
+	//debug("Writing register %X %X", 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




More information about the Scummvm-git-logs mailing list