[Scummvm-git-logs] scummvm master -> 73dc212ba0f1bc3e79e41714fe35aa9185ccd14e

NMIError noreply at scummvm.org
Mon Jul 27 20:54:56 UTC 2026


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

Summary:
d9c3235c23 AUDIO/MIDI: Add MT-32 SysEx overload
73dc212ba0 MADS: Rex Nebular MT-32 driver WiP


Commit: d9c3235c23ffe15f3efef2b8f61f8ac87aacbae4
    https://github.com/scummvm/scummvm/commit/d9c3235c23ffe15f3efef2b8f61f8ac87aacbae4
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-07-27T22:54:49+02:00

Commit Message:
AUDIO/MIDI: Add MT-32 SysEx overload

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


diff --git a/audio/mt32gm.cpp b/audio/mt32gm.cpp
index 32b8cee4f04..8ee474f6e47 100644
--- a/audio/mt32gm.cpp
+++ b/audio/mt32gm.cpp
@@ -922,6 +922,12 @@ void MidiDriver_MT32GM::sysExQueue(const byte *msg, uint16 length, int8 source)
 	_sysExQueueMutex.unlock();
 }
 
+uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, bool queue, bool delay, int8 source) {
+	assert(length > 3);
+	uint32 targetAddress = (msg[0] << 14) | (msg[1] << 7) | msg[2];
+	return sysExMT32(msg + 3, length - 3, targetAddress, queue, delay, source);
+}
+
 uint16 MidiDriver_MT32GM::sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue, bool delay, int8 source) {
 	if (!_nativeMT32)
 		// MT-32 SysExes have no effect on GM devices.
diff --git a/audio/mt32gm.h b/audio/mt32gm.h
index ee931bae872..d5a3858e049 100644
--- a/audio/mt32gm.h
+++ b/audio/mt32gm.h
@@ -297,6 +297,7 @@ public:
 	 * not sent before this time has passed.
 	 */
 	uint16 sysExMT32(const byte *msg, uint16 length, const uint32 targetAddress, bool queue = false, bool delay = true, int8 source = -1);
+	uint16 sysExMT32(const byte *msg, uint16 length, bool queue = false, bool delay = true, int8 source = -1);
 	void metaEvent(int8 source, byte type, const byte *data, uint16 length) override;
 
 	void stopAllNotes(bool stopSustainedNotes = false) override;


Commit: 73dc212ba0f1bc3e79e41714fe35aa9185ccd14e
    https://github.com/scummvm/scummvm/commit/73dc212ba0f1bc3e79e41714fe35aa9185ccd14e
Author: Coen Rampen (crampen at gmail.com)
Date: 2026-07-27T22:54:50+02:00

Commit Message:
MADS: Rex Nebular MT-32 driver WiP

Changed paths:
    engines/mads/core/sound_manager.cpp
    engines/mads/nebular/rsound.cpp
    engines/mads/nebular/rsound.h


diff --git a/engines/mads/core/sound_manager.cpp b/engines/mads/core/sound_manager.cpp
index a819f7dcccc..3e080060506 100644
--- a/engines/mads/core/sound_manager.cpp
+++ b/engines/mads/core/sound_manager.cpp
@@ -21,6 +21,7 @@
 
 #include "audio/fmopl.h"
 #include "audio/mididrv.h"
+#include "common/config-manager.h"
 #include "common/file.h"
 #include "common/memstream.h"
 #include "mads/core/sound_manager.h"
@@ -34,6 +35,8 @@ namespace MADS {
 SoundManager::SoundManager(Audio::Mixer *mixer, bool &soundFlag) : _mixer(mixer), _soundFlag(soundFlag) {
 	MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32);
 	MusicType musicType = MidiDriver::getMusicType(dev);
+	if ((musicType == MT_GM || musicType == MT_GS) && ConfMan.getBool("native_mt32"))
+		musicType = MT_MT32;
 	switch (musicType) {
 	case MT_MT32:
 		_driverType = SOUND_MT32;
diff --git a/engines/mads/nebular/rsound.cpp b/engines/mads/nebular/rsound.cpp
index a72365e334e..6a92010d579 100644
--- a/engines/mads/nebular/rsound.cpp
+++ b/engines/mads/nebular/rsound.cpp
@@ -65,7 +65,7 @@ void Channel::load(byte *pData) {
 
 /*-----------------------------------------------------------------------*/
 
-const byte RSound::_sysExHeader[5] = { 0xF0, 0x41, 0x10, 0x16, 0x12 };
+const uint32 RSound::UPDATE_DELTA = 1000000 / 60;
 
 RSound::RSound(Audio::Mixer *mixer, const Common::Path &filename,
 		int dataOffset, int dataSize, int sysExOffset) : SoundDriver(mixer, filename, dataOffset, dataSize) {
@@ -79,19 +79,42 @@ RSound::RSound(Audio::Mixer *mixer, const Common::Path &filename,
 	_pollResult = 0;
 	_resultFlag = 0;
 	_sysExOffset = sysExOffset;
+	_updateDeltaRemainder = 0;
 
 	for (int i = 0; i < RSOUND_CHANNEL_COUNT; ++i) {
 		_channels[i]._owner = this;
 		_channels[i]._midiChannel = i + 1;
 	}
 
+	_midiDriver = new MidiDriver_MT32GM(MusicType::MT_MT32);
+	int returnCode = _midiDriver->open();
+	if (returnCode != 0)
+		error("RSound - Failed to open MIDI music driver - error code %d.", returnCode);
+
+	_driverCallbackDelta = _midiDriver->getBaseTempo();
+
 	// rsound_init calls resetAllChannels directly, then later
 	// (via initDeviceOnce, on successful hardware detection) calls
 	// rsound_command0, which resets the channels again and sends the
-	// GM-reset messages to the device. Since we don't do real hardware
+	// MIDI channel reset messages to the device. Since we don't do real hardware
 	// detection here, just go straight to command0() - matches ASound's
 	// constructor calling command0() directly.
 	command0();
+
+	_midiDriver->setTimerCallback(this, &timerCallback);
+}
+
+RSound::~RSound() {
+	_isDisabled = true;
+	if (_midiDriver != nullptr) {
+		_midiDriver->setTimerCallback(nullptr, nullptr);
+		_midiDriver->close();
+
+		Common::StackLock lock(_driverMutex);
+
+		delete _midiDriver;
+		_midiDriver = nullptr;
+	}
 }
 
 void RSound::validate() {
@@ -190,7 +213,20 @@ int RSound::getRandomNumber() {
 
 void RSound::onTimer() {
 	Common::StackLock slock(_driverMutex);
-	poll();
+
+	// The frequency of the callbacks is dependent on the underlying driver
+	// implementation and might not be 60Hz. Adjust to make sure poll() is called
+	// with the correct frequency.
+	_updateDeltaRemainder += _driverCallbackDelta;
+	while (_updateDeltaRemainder >= UPDATE_DELTA) {
+		poll();
+		_updateDeltaRemainder -= UPDATE_DELTA;
+	}
+}
+
+void RSound::timerCallback(void* data) {
+	RSound *rsound = (RSound *)data;
+	rsound->onTimer();
 }
 
 void RSound::setVolume(int volume) {
@@ -204,6 +240,7 @@ void RSound::setVolume(int volume) {
 // needs to change once the real MT-32/MIDI output interface is wired up;
 // everything else funnels through it.
 
+/*
 void RSound::sendMidiByte(byte value) {
 	warning("RSound: MIDI byte %02X", value);
 }
@@ -215,46 +252,35 @@ void RSound::sendStatus(int midiChannel, byte statusNibble) {
 		sendMidiByte(status);
 	}
 }
+*/
 
 void RSound::sendNoteOn(int midiChannel, int note, int velocity) {
-	sendStatus(midiChannel, 0x90);
-	sendMidiByte(note);
-	sendMidiByte(velocity);
+	_midiDriver->send(MidiDriver::MIDI_COMMAND_NOTE_ON | midiChannel, note, velocity);
 }
 
 void RSound::sendProgramChange(int midiChannel, int program) {
-	sendStatus(midiChannel, 0xC0);
-	sendMidiByte(program);
+	_midiDriver->send(MidiDriver::MIDI_COMMAND_PROGRAM_CHANGE | midiChannel, program, 0);
 }
 
 void RSound::sendVolume(int midiChannel, int volume) {
-	sendStatus(midiChannel, 0xB0);
-	sendMidiByte(7); // CC#7: Channel Volume
-	sendMidiByte(volume);
+	_midiDriver->send(MidiDriver::MIDI_COMMAND_CONTROL_CHANGE | midiChannel, MidiDriver::MIDI_CONTROLLER_VOLUME, volume);
 }
 
 void RSound::sendPitchBend(int midiChannel, int value) {
-	sendStatus(midiChannel, 0xE0);
-	sendMidiByte(0);     // LSB always 0 - only coarse (MSB) control is used
-	sendMidiByte(value);
+	// LSB always 0 - only coarse (MSB) control is used
+	_midiDriver->send(MidiDriver::MIDI_COMMAND_PITCH_BEND | midiChannel, 0, value);
 }
 
 void RSound::sendPan(int midiChannel, int value) {
-	sendStatus(midiChannel, 0xB0);
-	sendMidiByte(10); // CC#10: Pan
-	sendMidiByte(value);
+	_midiDriver->send(MidiDriver::MIDI_COMMAND_CONTROL_CHANGE | midiChannel, MidiDriver::MIDI_CONTROLLER_PANNING, value);
 }
 
 void RSound::muteChannel(int midiChannel) {
-	sendStatus(midiChannel, 0xB0);
-	sendMidiByte(7);
-	sendMidiByte(0);
+	sendVolume(midiChannel, 0);
 }
 
 void RSound::restoreChannelVolume(int midiChannel, int volume) {
-	sendStatus(midiChannel, 0xB0);
-	sendMidiByte(7);
-	sendMidiByte(volume);
+	sendVolume(midiChannel, volume);
 }
 
 void RSound::sendSysEx(int offset) {
@@ -267,18 +293,29 @@ void RSound::sendSysEx(int offset) {
 		return;
 	}
 
-	for (int i = 0; i < ARRAYSIZE(_sysExHeader); ++i)
-		sendMidiByte(_sysExHeader[i]);
-
-	byte checksum = 0;
+	// There is a whole block of SysEx data at this offset that has to be
+	// sent to the MT-32. Each entry is terminated by 0xFF; the block seems to
+	// be terminated by a second 0xFF following the last entry.
+	// FIXME If the data is malformed, this will read out of bounds. Not sure
+	// how the original code handles this.
 	byte *pData = loadData(offset);
-	for (int i = 0; pData[i] != 0xFF; ++i) {
-		sendMidiByte(pData[i]);
-		checksum += pData[i];
+	while (true) {
+		uint16 length = 0;
+		for (int i = 0; pData[i] != 0xFF; ++i) {
+			length++;
+		}
+		if (length == 0) {
+			// Two subsequent 0xFF bytes - end of SysEx data block.
+			break;
+		}
+		// FIXME This call adds the necessary delay for the MT-32 to process the
+		// SysEx message, which will make the engine unresponsive.
+		// This can be fixed using the SysEx queue (specify true as 3rd param).
+		// Driver status can then be checked from the main event loop using
+		// _midiDriver->isReady().
+		_midiDriver->sysExMT32(pData, length);
+		pData += length + 1;
 	}
-
-	sendMidiByte((~checksum + 1) & 0x7F);
-	sendMidiByte(0xF7);
 }
 
 /*-----------------------------------------------------------------------*/
@@ -290,9 +327,7 @@ void RSound::Channel_flushHeldNotes(Channel *channel) {
 		if (slots[i] == 0xFF)
 			break;
 
-		sendStatus(channel->_midiChannel, 0x90);
-		sendMidiByte(slots[i]);
-		sendMidiByte(0); // velocity 0 = note off
+		sendNoteOn(channel->_midiChannel, slots[i], 0); // velocity 0 = note off
 		slots[i] = 0xFF;
 	}
 }
@@ -310,7 +345,8 @@ void RSound::Channel_checkFade(Channel *channel) {
 		// Fully silent - recycle the channel to the fixed "silence" stream
 		// (unk_14566 in the disassembly, at offset 0x3246 relative to
 		// seg001's load address - matches sub_1029F exactly)
-		channel->_pSrc = loadData(0x3246);
+		// FIXME This reads out of bounds
+		//channel->_pSrc = loadData(0x3246);
 		channel->_pendingStop = 0;
 	}
 }
@@ -625,21 +661,16 @@ void RSound::resetAllChannels() {
 }
 
 /**
- * Sends the GM-reset Control Change sequence (all notes off, reset all
- * controllers, volume=100, pan=center) to MIDI channels [first, last]
+ * Resets the MIDI channel state (all notes off, reset all
+ * controllers, volume=100, pan=center) of MIDI channels [first, last]
  * (both inclusive, 1-based). Shared tail used by command0/command2/command4.
  */
-void RSound::sendGmReset(int first, int last) {
+void RSound::sendMidiChannelReset(int first, int last) {
 	for (int ch = first; ch <= last; ++ch) {
-		sendStatus(ch, 0xB0);
-		sendMidiByte(0x7B); // All Notes Off
-		sendMidiByte(0);
-		sendMidiByte(0x79); // Reset All Controllers
-		sendMidiByte(0);
-		sendMidiByte(7);    // Channel Volume
-		sendMidiByte(100);
-		sendMidiByte(10);   // Pan
-		sendMidiByte(0x40);
+		_midiDriver->send(MidiDriver::MIDI_COMMAND_CONTROL_CHANGE | ch, MidiDriver::MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+		_midiDriver->send(MidiDriver::MIDI_COMMAND_CONTROL_CHANGE | ch, MidiDriver::MIDI_CONTROLLER_RESET_ALL_CONTROLLERS, 0);
+		_midiDriver->send(MidiDriver::MIDI_COMMAND_CONTROL_CHANGE | ch, MidiDriver::MIDI_CONTROLLER_VOLUME, 100);
+		_midiDriver->send(MidiDriver::MIDI_COMMAND_CONTROL_CHANGE | ch, MidiDriver::MIDI_CONTROLLER_PANNING, 0x40);
 	}
 }
 
@@ -648,7 +679,7 @@ int RSound::command0() {
 	_isDisabled = true;
 
 	resetAllChannels();
-	sendGmReset(1, RSOUND_CHANNEL_COUNT);
+	sendMidiChannelReset(1, RSOUND_CHANNEL_COUNT);
 
 	// Matches the trailing "lea ax, unk_1138F; jmp sub_1041E" in rsound_command0.
 	// _sysExOffset is this driver's own command0_array offset, supplied
@@ -669,10 +700,10 @@ int RSound::command1() {
 
 int RSound::command2() {
 	// Channels 1-5, matching sub_101A0 (also reinitializes the held-notes
-	// table) plus the GM-reset CC sequence for those same channels.
+	// table) plus the MIDI channel reset for those same channels.
 	resetChannelRange(0, 5);
 	resetHeldNotes();
-	sendGmReset(1, 5);
+	sendMidiChannelReset(1, 5);
 	return 0;
 }
 
@@ -684,9 +715,9 @@ int RSound::command3() {
 
 int RSound::command4() {
 	// Channels 6-9, matching sub_101EA (does NOT touch the held-notes
-	// table) plus the GM-reset CC sequence for those same channels.
+	// table) plus the MIDI channel reset for those same channels.
 	resetChannelRange(5, RSOUND_CHANNEL_COUNT);
-	sendGmReset(6, RSOUND_CHANNEL_COUNT);
+	sendMidiChannelReset(6, RSOUND_CHANNEL_COUNT);
 	return 0;
 }
 
diff --git a/engines/mads/nebular/rsound.h b/engines/mads/nebular/rsound.h
index 9787d04be72..b41e3cc50a7 100644
--- a/engines/mads/nebular/rsound.h
+++ b/engines/mads/nebular/rsound.h
@@ -24,6 +24,8 @@
 
 #include "mads/core/sound_manager.h"
 
+#include "audio/mt32gm.h"
+
 namespace MADS {
 namespace RexNebular {
 
@@ -130,6 +132,9 @@ public:
 class RSound : public SoundDriver {
 	friend class Channel;
 private:
+	// Number of microseconds between driver updates (60 Hz frequency)
+	static const uint32 UPDATE_DELTA;
+
 	uint16 _randomSeed;
 	int _masterVolume;
 	byte _lastMidiStatus;             // running-status cache, avoids resending an unchanged status byte
@@ -148,12 +153,9 @@ private:
 	 */
 	int _sysExOffset;
 
-	/**
-	 * Fixed Roland SysEx header (F0 41 10 16 12) - sendSysEx_array in the
-	 * disassembly, confirmed byte-for-byte identical across multiple
-	 * drivers (checked against rsound.001/002/009).
-	 */
-	static const byte _sysExHeader[5];
+	MidiDriver_MT32GM *_midiDriver;
+	uint32 _driverCallbackDelta;
+	uint32 _updateDeltaRemainder;
 
 	void update();
 	void pollAllChannels();
@@ -240,8 +242,8 @@ protected:
 	// ---- Low-level MIDI send helpers -------------------------------
 	// All funnel through sendMidiByte(), the single hook point for
 	// wiring up real MT-32/MIDI output.
-	void sendMidiByte(byte value);
-	void sendStatus(int midiChannel, byte statusNibble);
+	//void sendMidiByte(byte value);
+	//void sendStatus(int midiChannel, byte statusNibble);
 	void sendNoteOn(int midiChannel, int note, int velocity);
 	void sendProgramChange(int midiChannel, int program);
 	void sendVolume(int midiChannel, int volume);
@@ -251,11 +253,11 @@ protected:
 	void restoreChannelVolume(int midiChannel, int volume);
 
 	/**
-	 * Sends the GM-reset Control Change sequence (all notes off, reset all
-	 * controllers, volume=100, pan=center) to MIDI channels [first, last]
+	 * Resets the MIDI channel state (all notes off, reset all
+	 * controllers, volume=100, pan=center) of MIDI channels [first, last]
 	 * (inclusive, 1-based). Shared tail used by command0/command2/command4.
 	 */
-	void sendGmReset(int first, int last);
+	void sendMidiChannelReset(int first, int last);
 
 	/**
 	 * Sends a single Roland DT1-style SysEx message: the fixed
@@ -308,8 +310,7 @@ public:
 	RSound(Audio::Mixer *mixer, const Common::Path &filename,
 		int dataOffset, int dataSize, int sysExOffset);
 
-	~RSound() override {
-	}
+	~RSound() override;
 
 	int stop() override;
 	int poll() override;
@@ -322,8 +323,8 @@ public:
 		return _frameCounter;
 	}
 
-private:
 	void onTimer();
+	static void timerCallback(void *data);
 };
 
 } // namespace RexNebular




More information about the Scummvm-git-logs mailing list