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

dreammaster noreply at scummvm.org
Tue Jul 28 10:45:06 UTC 2026


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

Summary:
b9c5a9c67d MADS: Fix checkFade data references to a static 0,0 array


Commit: b9c5a9c67dfec3eb7f627f82a03d0ba49f9ed804
    https://github.com/scummvm/scummvm/commit/b9c5a9c67dfec3eb7f627f82a03d0ba49f9ed804
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-07-28T20:45:00+10:00

Commit Message:
MADS: Fix checkFade data references to a static 0,0 array

Changed paths:
    engines/mads/dragonsphere/sound/rsound.cpp
    engines/mads/dragonsphere/sound/rsound.h
    engines/mads/dragonsphere/sound/rsound_dragonsphere.cpp
    engines/mads/dragonsphere/sound/rsound_dragonsphere.h
    engines/mads/nebular/sound/rsound.cpp


diff --git a/engines/mads/dragonsphere/sound/rsound.cpp b/engines/mads/dragonsphere/sound/rsound.cpp
index 8c693cb90aa..746935fc5e8 100644
--- a/engines/mads/dragonsphere/sound/rsound.cpp
+++ b/engines/mads/dragonsphere/sound/rsound.cpp
@@ -66,7 +66,7 @@ void Channel::reset(byte *startPtr) {
 void Channel::enable(int flag) {
 	if (_activeCount) {
 		_pendingStop = flag;
-		_soundData = nullptr;
+		_soundData = RSound::_silenceStream;
 	}
 }
 
@@ -78,6 +78,8 @@ void Channel::load(byte *pData) {
 
 /*-----------------------------------------------------------------------*/
 
+byte RSound::_silenceStream[2] = { 0, 0 };
+
 RSound::RSound(Audio::Mixer *mixer, const Common::Path &filename,
 		int dataOffset, int dataSize, int sysExOffset) : SoundDriver(mixer, filename, dataOffset, dataSize) {
 	_commandParam = 0;
@@ -432,11 +434,9 @@ void RSound::Channel_checkFade(Channel *channel, int midiChannel) {
 		return;
 
 	if (channel->_volume == 0) {
-		// enable_channel_data: 2 zero bytes - a generic silence/no-op
-		// stream, not driver-specific sound data (2 bytes here, unlike
-		// Phantom's 3 - confirmed directly from this disassembly).
-		static byte silenceStream[2] = { 0, 0 };
-		channel->_pSrc = silenceStream;
+		// null_sound_data: a fixed 2-byte (0,0) silence marker - the
+		// same block Channel::enable() redirects _soundData to.
+		channel->_pSrc = _silenceStream;
 		channel->_pendingStop = 0;
 		return;
 	}
@@ -467,14 +467,14 @@ void RSound::resetChannelRange(int first, int last) {
 	}
 }
 
-void RSound::disableChannelTo(int channelIndex, byte flag, int offset) {
+void RSound::disableChannelTo(int channelIndex, byte flag) {
 	Channel &ch = _channels[channelIndex];
 	if (!ch._activeCount)
 		return;
 	if (ch._activeCount == 1)
-		ch._pSrc = loadData(offset);
+		ch._pSrc = _silenceStream;
 	ch._pendingStop = flag;
-	ch._soundData = loadData(offset);
+	ch._soundData = _silenceStream;
 }
 
 void RSound::resetHeldNotes() {
diff --git a/engines/mads/dragonsphere/sound/rsound.h b/engines/mads/dragonsphere/sound/rsound.h
index 92203fd9b03..82aff15ddbb 100644
--- a/engines/mads/dragonsphere/sound/rsound.h
+++ b/engines/mads/dragonsphere/sound/rsound.h
@@ -157,8 +157,11 @@ public:
  *    masking, same "mutate 3 bytes then sendSysEx" shape found at
  *    sub_108A1) - NOT independently confirmed by inspecting the literal
  *    bytes at rsound.dr1's offset 0x67 sysex template.
- *  - The silence/no-op stream used by Channel_checkFade is 2 zero bytes
- *    here (enable_channel_data), not Phantom's 3.
+ *  - null_sound_data (see _silenceStream) is a fixed 2-byte (0, 0)
+ *    silence marker referenced by BOTH Channel::enable() and
+ *    Channel_checkFade() - same intent as Phantom's fixed 3-byte
+ *    silence stream (2 bytes here, unlike Phantom's 3 - confirmed
+ *    directly from this disassembly).
  *
  * NOTE: The actual MIDI transmission (sendMidiByte()) currently just logs
  * via warning() - it isn't hooked up to a real ScummVM MIDI/MT-32 output
@@ -206,6 +209,24 @@ private:
 	 */
 	int _sysExOffset;
 
+	/**
+	 * "null_sound_data" - a fixed 2-byte (0,0) silence marker,
+	 * referenced by BOTH Channel::enable() (Channel_enable, written to
+	 * _soundData) and Channel_checkFade() (written to _pSrc once a
+	 * pending-stop channel's volume has fully decayed) - the same single
+	 * symbol in the disassembly, confirmed at a different address in
+	 * each driver's own resource file, but always the same (0,0)
+	 * content, same intent as Phantom's fixed 3-byte silence stream.
+	 * pollActiveChannel() processing a (note=0, duration=0) pair sets
+	 * _activeCount to 0, and its own top-of-function guard (checked
+	 * before any decrement) then short-circuits every later call before
+	 * _pSrc is ever read again - so only these 2 bytes are ever actually
+	 * consumed. A shared static array (rather than reading via
+	 * loadData()) is therefore sufficient and avoids needing a
+	 * per-driver offset for content that never varies.
+	 */
+	static byte _silenceStream[2];
+
 	/**
 	 * General-purpose script variable table (matches the equivalent
 	 * Phantom mechanism, 32 bytes).
@@ -286,14 +307,11 @@ protected:
 
 	/**
 	 * A driver-specific variant of Channel::enable() confirmed across
-	 * multiple drivers so far (RSound4's sub_1092A targeting 0x1F8B,
-	 * RSound5's sub_10854 targeting 0x20C9) - redirects _soundData (and,
-	 * if the channel is about to expire this tick, _pSrc too) to
-	 * loadData(offset) instead of nullptr. The specific offset is always
-	 * driver-specific "silence"/placeholder sound data, passed explicitly
-	 * rather than hardcoded here.
-	 */
-	void disableChannelTo(int channelIndex, byte flag, int offset);
+	 * multiple drivers so far (RSound4's sub_1092A, RSound5's sub_10854)
+	 * - redirects _soundData (and, if the channel is about to expire this
+	 * tick, _pSrc too) to _silenceStream instead of nullptr.
+	 */
+	void disableChannelTo(int channelIndex, byte flag);
 
 	/**
 	 * Resets the _heldNotes table (see its field comment). Protected so
diff --git a/engines/mads/dragonsphere/sound/rsound_dragonsphere.cpp b/engines/mads/dragonsphere/sound/rsound_dragonsphere.cpp
index c82a5b2ecbb..281512285bb 100644
--- a/engines/mads/dragonsphere/sound/rsound_dragonsphere.cpp
+++ b/engines/mads/dragonsphere/sound/rsound_dragonsphere.cpp
@@ -1264,9 +1264,9 @@ int RSound4::command1() {
 	if (isSoundActive(loadData(0x1F4F)))
 		return 0;
 	_fadeCheckPeriod = 1; // armFadeCheck
-	disableChannelTo(5, 0xFF, 0x1F8B); // channel 6 (redundant re-disable if command3's own gate already did it - harmless)
-	disableChannelTo(6, 0xFF, 0x1F8B); // channel 7
-	disableChannelTo(7, 0xFF, 0x1F8B); // channel 8
+	disableChannelTo(5, 0xFF); // channel 6 (redundant re-disable if command3's own gate already did it - harmless)
+	disableChannelTo(6, 0xFF); // channel 7
+	disableChannelTo(7, 0xFF); // channel 8
 	return 0;
 }
 
@@ -1281,15 +1281,15 @@ int RSound4::command3() {
 	// familiar "lower" group), then conditionally disables channel 6 too -
 	// only if loadData(0x1F4F) isn't already playing.
 	_fadeCheckPeriod = 1; // armFadeCheck
-	disableChannelTo(0, 0xFF, 0x1F8B); // ch1
-	disableChannelTo(1, 0xFF, 0x1F8B); // ch2
-	disableChannelTo(2, 0xFF, 0x1F8B); // ch3
-	disableChannelTo(3, 0xFF, 0x1F8B); // ch4
-	disableChannelTo(4, 0xFF, 0x1F8B); // ch5
-	disableChannelTo(8, 0xFF, 0x1F8B); // ch9
+	disableChannelTo(0, 0xFF); // ch1
+	disableChannelTo(1, 0xFF); // ch2
+	disableChannelTo(2, 0xFF); // ch3
+	disableChannelTo(3, 0xFF); // ch4
+	disableChannelTo(4, 0xFF); // ch5
+	disableChannelTo(8, 0xFF); // ch9
 	if (isSoundActive(loadData(0x1F4F)))
 		return 0;
-	disableChannelTo(5, 0xFF, 0x1F8B); // ch6 (conditional)
+	disableChannelTo(5, 0xFF); // ch6 (conditional)
 	return 0;
 }
 
@@ -1710,11 +1710,11 @@ int RSound5::command1() {
 
 int RSound5::command5() {
 	// Matches sub_10854: same disableChannelTo mechanic as RSound4's
-	// sub_1092A, but targeting loadData(0x20C9) - for channels 6,7,8.
+	// sub_1092A, for channels 6,7,8.
 	_fadeCheckPeriod = 1; // armFadeCheck
-	disableChannelTo(5, 0xFF, 0x20C9);
-	disableChannelTo(6, 0xFF, 0x20C9);
-	disableChannelTo(7, 0xFF, 0x20C9);
+	disableChannelTo(5, 0xFF);
+	disableChannelTo(6, 0xFF);
+	disableChannelTo(7, 0xFF);
 	return 0;
 }
 
diff --git a/engines/mads/dragonsphere/sound/rsound_dragonsphere.h b/engines/mads/dragonsphere/sound/rsound_dragonsphere.h
index e234c1a2e08..3b60a09e13b 100644
--- a/engines/mads/dragonsphere/sound/rsound_dragonsphere.h
+++ b/engines/mads/dragonsphere/sound/rsound_dragonsphere.h
@@ -329,7 +329,7 @@ public:
  * command1/command2/command3/command4 are all genuinely different from
  * the base class, in two ways:
  *   - They use the base class's disableChannelTo() (matching sub_1092A)
- *     instead of Channel::enable(), targeting loadData(0x1F8B).
+ *     instead of Channel::enable().
  *   - Channel 6 (the boundary between the "lower" 1-5,9 group and
  *     "upper" 6,7,8 group) is conditionally included/excluded based on
  *     isSoundActive(loadData(0x1F4F)) in command1/command3, and based on
@@ -439,9 +439,9 @@ public:
  * enable exactly).
  *
  * command5 uses the base class's disableChannelTo() (matching
- * sub_10854) instead of Channel::enable(), targeting loadData(0x20C9) -
- * for channels 6,7,8 (three channels, matching the base's default upper
- * group range, just via a different mechanic).
+ * sub_10854) instead of Channel::enable(), for channels 6,7,8 (three
+ * channels, matching the base's default upper group range, just via a
+ * different mechanic).
  *
  * command1/2/3 are not virtual in the base class, so this driver's own
  * command1() must be overridden too (calling THIS class's command5()) -
diff --git a/engines/mads/nebular/sound/rsound.cpp b/engines/mads/nebular/sound/rsound.cpp
index edc226a6eae..25779cc3537 100644
--- a/engines/mads/nebular/sound/rsound.cpp
+++ b/engines/mads/nebular/sound/rsound.cpp
@@ -343,11 +343,14 @@ void RSound::Channel_checkFade(Channel *channel) {
 		--channel->_volume;
 		sendVolume(channel->_midiChannel, channel->_volume);
 	} else {
-		// 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)
-		// FIXME This reads out of bounds
-		//channel->_pSrc = loadData(0x3246);
+		// Fully silent - recycle the channel to a fixed 2-byte (0,0)
+		// silence stream. pollActiveChannel() processing a (note=0,
+		// duration=0) pair sets _activeCount to 0, and its own
+		// top-of-function guard (checked before any decrement) then
+		// short-circuits every later call before _pSrc is ever read
+		// again - so only these first 2 bytes are ever consumed.
+		static byte silenceStream[2] = { 0, 0 };
+		channel->_pSrc = silenceStream;
 		channel->_pendingStop = 0;
 	}
 }




More information about the Scummvm-git-logs mailing list