[Scummvm-git-logs] scummvm master -> b5b029943530d4136da05ba48e31c6c02a82b894
dreammaster
noreply at scummvm.org
Mon Jul 27 08:57:38 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:
b5b0299435 MADS: PHANTOM: Remainder of RSound classes
Commit: b5b029943530d4136da05ba48e31c6c02a82b894
https://github.com/scummvm/scummvm/commit/b5b029943530d4136da05ba48e31c6c02a82b894
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-07-27T18:57:31+10:00
Commit Message:
MADS: PHANTOM: Remainder of RSound classes
Assisted-by: Claude Code:claude-opus-4.8
Changed paths:
engines/mads/phantom/rsound.cpp
engines/mads/phantom/rsound.h
engines/mads/phantom/rsound_phantom.cpp
engines/mads/phantom/rsound_phantom.h
diff --git a/engines/mads/phantom/rsound.cpp b/engines/mads/phantom/rsound.cpp
index 9c3a3c60b20..c2702c6f129 100644
--- a/engines/mads/phantom/rsound.cpp
+++ b/engines/mads/phantom/rsound.cpp
@@ -179,18 +179,28 @@ Channel *RSound::playSound(int offset) {
return playSoundData(loadData(offset), 5, 7, 7);
}
-Channel *RSound::playSoundAny(int offset) {
+Channel *RSound::playSoundChannels1To5(int offset) {
// Channels 1-5 (0-based 0-4) for the free scan, but the pending-stop
// fallback only reaches down to channel 4 (0-based 3) - channel 5
// can never be pre-empted here, confirmed from the disassembly.
return playSoundData(loadData(offset), 0, 4, 3);
}
-Channel *RSound::playSoundChannels1To8(int offset) {
+Channel *RSound::playSoundAny(int offset) {
// Channels 1-8 (0-based 0-7), symmetric free/fallback scan.
return playSoundData(loadData(offset), 0, 7, 7);
}
+Channel *RSound::playSoundChannels5To8(int offset) {
+ // Channels 5-8 (0-based 4-7), symmetric free/fallback scan.
+ return playSoundData(loadData(offset), 4, 7, 7);
+}
+
+Channel *RSound::playSoundChannels1To6(int offset) {
+ // Channels 1-6 (0-based 0-5), symmetric free/fallback scan.
+ return playSoundData(loadData(offset), 0, 5, 5);
+}
+
bool RSound::isSoundActive(byte *pData) {
// Matches the disassembly exactly: only channels 1-8 are checked,
// channel 9 is never included.
@@ -413,9 +423,15 @@ void RSound::checkFadingChannels() {
/*-----------------------------------------------------------------------*/
void RSound::resetChannelRange(int first, int last) {
+ // Matches the confirmed struct-field disassembly exactly: two
+ // word-sized writes, each zeroing a pair of adjacent byte fields -
+ // [bx+_activeCount] (covers _activeCount + _pitchBendFadeStep) and
+ // [bx+_volumeFadeStep] (covers _volumeFadeStep + _panFadeStep).
for (int i = first; i <= last; ++i) {
_channels[i]._activeCount = 0;
- _channels[i]._pitchBendFadeStep = 0; // matches "mov [bx],ax; mov [bx+2],ax" zeroing the first 4 bytes
+ _channels[i]._pitchBendFadeStep = 0;
+ _channels[i]._volumeFadeStep = 0;
+ _channels[i]._panFadeStep = 0;
}
}
@@ -438,9 +454,11 @@ void RSound::resetChannels1to5() {
_isDisabled = false;
}
-void RSound::resetChannels6to9() {
+void RSound::resetChannels4to9() {
+ // CORRECTED (was wrongly named/ranged resetChannels6to9): channels
+ // 4-9, 0-based indices 3-8 - confirmed directly from disassembly.
_isDisabled = true;
- resetChannelRange(5, 8);
+ resetChannelRange(3, 8);
_isDisabled = false;
}
@@ -459,8 +477,13 @@ int RSound::command0() {
}
int RSound::command1() {
+ // IMPORTANT: falls through to the SAME tail as command5() (loc_108A9)
+ // directly and ungated - it must NOT call the virtual command5()
+ // here, since that would wrongly apply whatever driver-specific
+ // isSoundActive() gate command5() has. command1() itself is never
+ // gated in any driver confirmed so far.
command3();
- command5(); // shares command1/command5's tail (enables channels 5-8) - see command5()
+ enableUpperChannels();
return 0;
}
@@ -483,24 +506,23 @@ int RSound::command3() {
return 0;
}
-int RSound::command4() {
- // Matches rsound_command4's chunk at loc_106DB: reset channels 6-9,
- // then a full sendGmReset(9) (all 9 channels) - the "reset 6-9" and
- // "GM-reset all 9" are both really executed, matching the original
+void RSound::resetAndGmResetUpperChannels() {
+ // Matches loc_106DB (command4()'s shared tail in every driver
+ // confirmed so far): reset channels 4-9, then a full sendGmReset(9)
+ // (all 9 channels) - both really execute, matching the original
// exactly despite the apparent redundancy.
- resetChannels6to9();
+ resetChannels4to9();
sendGmReset(RSOUND_CHANNEL_COUNT);
- return 0;
}
-int RSound::command5() {
- // Matches rsound_command5/loc_108A9: enables channels 5,6,7,8.
+void RSound::enableUpperChannels() {
+ // Matches loc_108A9 (command1()'s and command5()'s shared tail in
+ // every driver confirmed so far): enables channels 5,6,7,8.
_fadeCheckPeriod = 1;
_channels[4].enable(0xFF);
_channels[5].enable(0xFF);
_channels[6].enable(0xFF);
_channels[7].enable(0xFF);
- return 0;
}
int RSound::command6() {
diff --git a/engines/mads/phantom/rsound.h b/engines/mads/phantom/rsound.h
index 7241791c36a..f069c1c9ab1 100644
--- a/engines/mads/phantom/rsound.h
+++ b/engines/mads/phantom/rsound.h
@@ -176,7 +176,6 @@ private:
* directly rather than an arbitrary function pointer.
*/
int _fadeCheckCounter;
- int _fadeCheckPeriod;
/**
* Cluster of globals written by opcodes 0xBE-0xC1 but with no
@@ -228,10 +227,15 @@ private:
*/
uint16 readScriptWord(byte *&pSrc);
- void resetChannelRange(int first, int last);
void resetAllChannels();
void resetChannels1to5();
- void resetChannels6to9();
+
+ /**
+ * CORRECTED (was wrongly named/ranged resetChannels6to9): resets
+ * channels 4-9 (0-based indices 3-8), confirmed directly from
+ * disassembly - "CODE XREF: rsound_command4".
+ */
+ void resetChannels4to9();
void checkFadingChannels();
void Channel_checkFade(Channel *channel, int midiChannel);
@@ -258,6 +262,28 @@ protected:
*/
int _randomAmbianceTriggerFlag;
+ /**
+ * Half-rate fade-check period reload value (see _fadeCheckCounter
+ * above). Protected (not private) so per-driver command1()/command3()/
+ * command5() overrides that need to arm it directly (matching an
+ * inline "mov cs:_fadeCheckPeriod, 1" in the disassembly, e.g.
+ * RSound5's driver-specific 6-channel command3() and 3-channel
+ * enableChannels678()) can do so without going through the base
+ * class's own command3()/enableUpperChannels().
+ */
+ int _fadeCheckPeriod;
+
+ /**
+ * Zeroes _activeCount/_pitchBendFadeStep/_volumeFadeStep/_panFadeStep
+ * for channels [first, last] (0-based indices). Protected (not
+ * private) so per-driver command4()/command5() overrides that need a
+ * narrower or differently-shaped reset than resetChannels4to9() /
+ * resetAndGmResetUpperChannels() below can call it directly - e.g.
+ * RSound3's command4(), confirmed to reset only channels 5-9
+ * (indices 4-8), not 4-9.
+ */
+ void resetChannelRange(int first, int last);
+
/**
* Hook called once per update() frame after the disabled check.
* Only drivers with a random-ambiance/music picker (e.g. RSound1's
@@ -285,7 +311,7 @@ protected:
* channel 5 - a genuine asymmetry preserved exactly (channel 5 can
* never be pre-empted by this call, only picked while free).
*/
- Channel *playSoundAny(int offset);
+ Channel *playSoundChannels1To5(int offset);
/**
* Plays the specified sound, using any free channel from 1 to 8
@@ -293,7 +319,22 @@ protected:
* disassembly - a third, distinct scan range from playSound() and
* playSoundAny() above.
*/
- Channel *playSoundChannels1To8(int offset);
+ Channel *playSoundAny(int offset);
+
+ /**
+ * Plays the specified sound, using any free channel from 5 to 8.
+ * Matches playChannels5to8 in the disassembly - a fourth, distinct
+ * scan range (symmetric free/fallback scan, unlike playSoundAny()'s
+ * asymmetry, and one channel narrower than playSoundAny()'s 1-8).
+ */
+ Channel *playSoundChannels5To8(int offset);
+
+ /**
+ * Plays the specified sound, using any free channel from 1 to 6.
+ * Matches playSoundChannels1to6 in the disassembly - a fifth,
+ * distinct scan range (symmetric free/fallback scan).
+ */
+ Channel *playSoundChannels1To6(int offset);
/**
* Scans [startingChannel, freeScanEnd] for a free channel; if none
@@ -382,16 +423,32 @@ protected:
int command3();
/**
- * NOTE: virtual, unlike command1-3/6-8. Confirmed (from RSound1) that
- * this driver's own command4/command5 are gated by isSoundActive() on
- * a driver-specific data offset (0x3D98 for RSound1) before doing
- * anything else - a per-driver detail that doesn't belong hardcoded
- * in the shared base. This base implementation is an UNGATED
- * fallback for drivers whose own command4/5 haven't been confirmed
- * yet; override once their disassembly is available.
+ * Shared tail of command1() (falls through into it after command3())
+ * and command5() (jumps straight into it, ungated, in every driver
+ * confirmed so far): enables channels 5,6,7,8. Matches loc_108A9.
+ */
+ void enableUpperChannels();
+
+ /**
+ * Shared tail of command4() in every driver confirmed so far:
+ * resetChannels4to9() + sendGmReset(9). Matches loc_106DB.
+ */
+ void resetAndGmResetUpperChannels();
+
+ /**
+ * PURE VIRTUAL, unlike command1-3/6-8. Confirmed (from RSound1 AND
+ * RSound2, independently) that every driver's command4/command5 are
+ * gated by isSoundActive() on a driver-specific data offset before
+ * calling resetAndGmResetUpperChannels()/enableUpperChannels() above
+ * - a per-driver detail that must not live in the shared base.
+ * Deliberately NO default implementation, so a new driver subclass
+ * can't compile without explicitly providing its own gate - silently
+ * falling back to an ungated version would be wrong (and was, in an
+ * earlier version of this port, until RSound1/RSound2 confirmed the
+ * gate is universal even though its offset isn't).
*/
- virtual int command4();
- virtual int command5();
+ virtual int command4() = 0;
+ virtual int command5() = 0;
int command6();
int command7();
int command8();
diff --git a/engines/mads/phantom/rsound_phantom.cpp b/engines/mads/phantom/rsound_phantom.cpp
index 563f4052b76..877b8c8bb00 100644
--- a/engines/mads/phantom/rsound_phantom.cpp
+++ b/engines/mads/phantom/rsound_phantom.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/util.h"
#include "mads/phantom/rsound_phantom.h"
namespace MADS {
@@ -116,17 +117,19 @@ int RSound1::command16() {
int RSound1::command4() {
// Confirmed: rsound_command4 is gated by isSoundActive(0x3D98) -
// which is also command39's own target sound - before falling
- // through to the base class's reset-channels-6-9 + full GM-reset.
+ // through to the shared reset-channels-4-9 + full GM-reset tail.
if (isSoundActive(loadData(0x3D98)))
return 0;
- return RSound::command4();
+ resetAndGmResetUpperChannels();
+ return 0;
}
int RSound1::command5() {
// Confirmed: same isSoundActive(0x3D98) gate as command4() above.
if (isSoundActive(loadData(0x3D98)))
return 0;
- return RSound::command5();
+ enableUpperChannels();
+ return 0;
}
int RSound1::command24() {
@@ -155,12 +158,12 @@ int RSound1::command32() {
byte *pData = loadData(0x2044);
if (!isSoundActive(pData)) {
command1();
- playSoundAny(0x2044);
- playSoundAny(0x206B);
- playSoundAny(0x208B);
- playSoundAny(0x20AB);
- playSoundAny(0x20C9);
- playSoundAny(0x20E3);
+ playSoundChannels1To5(0x2044);
+ playSoundChannels1To5(0x206B);
+ playSoundChannels1To5(0x208B);
+ playSoundChannels1To5(0x20AB);
+ playSoundChannels1To5(0x20C9);
+ playSoundChannels1To5(0x20E3);
}
return 0;
}
@@ -169,11 +172,11 @@ int RSound1::command33() {
byte *pData = loadData(0x530);
if (!isSoundActive(pData)) {
command1();
- playSoundAny(0x530);
- playSoundAny(0x766);
- playSoundAny(0x929);
- playSoundAny(0xB37);
- playSoundAny(0xCBA);
+ playSoundChannels1To5(0x530);
+ playSoundChannels1To5(0x766);
+ playSoundChannels1To5(0x929);
+ playSoundChannels1To5(0xB37);
+ playSoundChannels1To5(0xCBA);
_channels[5].load(loadData(0xE20));
}
return 0;
@@ -183,12 +186,12 @@ int RSound1::command34() {
byte *pData = loadData(0x1014);
if (!isSoundActive(pData)) {
command1();
- playSoundAny(0x1014);
- playSoundAny(0x126B);
- playSoundAny(0x137C);
- playSoundAny(0x149B);
- playSoundAny(0x158C);
- playSoundAny(0x164E);
+ playSoundChannels1To5(0x1014);
+ playSoundChannels1To5(0x126B);
+ playSoundChannels1To5(0x137C);
+ playSoundChannels1To5(0x149B);
+ playSoundChannels1To5(0x158C);
+ playSoundChannels1To5(0x164E);
}
return 0;
}
@@ -240,12 +243,12 @@ int RSound1::command39() {
byte *pData = loadData(0x3D98);
if (!isSoundActive(pData)) {
command1();
- playSoundChannels1To8(0x3D98);
- playSoundChannels1To8(0x3F39);
- playSoundChannels1To8(0x404D);
- playSoundChannels1To8(0x413F);
- playSoundChannels1To8(0x4247);
- playSoundChannels1To8(0x43FD);
+ playSoundAny(0x3D98);
+ playSoundAny(0x3F39);
+ playSoundAny(0x404D);
+ playSoundAny(0x413F);
+ playSoundAny(0x4247);
+ playSoundAny(0x43FD);
}
return 0;
}
@@ -399,5 +402,1071 @@ int RSound1::command76() {
return 0;
}
+/*-----------------------------------------------------------------------*/
+
+const RSound2::CommandPtr RSound2::_commandList[74] = {
+ &RSound2::command0, &RSound2::command1, &RSound2::command2, &RSound2::command3,
+ &RSound2::command4, &RSound2::command5, &RSound2::command6, &RSound2::command7,
+ &RSound2::command8, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::command16, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::command24, &RSound2::command25, &RSound2::command26, &RSound2::command27,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::command32, &RSound2::command33, &RSound2::command34, &RSound2::command35,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand, &RSound2::nullCommand,
+ &RSound2::command64, &RSound2::command65, &RSound2::command66, &RSound2::command67,
+ &RSound2::command68, &RSound2::command69, &RSound2::command70, &RSound2::command71,
+ &RSound2::command72, &RSound2::nullCommand
+};
+
+RSound2::RSound2(Audio::Mixer *mixer) : RSound(mixer, "rsound.ph2", 0x2B40, 0x1C40, 0xDC) {
+}
+
+int RSound2::command(int commandId, int param) {
+ if (commandId < 0 || commandId >= ARRAYSIZE(_commandList))
+ return 0;
+
+ _commandParam = param;
+ _frameCounter = 0;
+ return (this->*_commandList[commandId])();
+}
+
+int RSound2::command4() {
+ // Confirmed: same isSoundActive(0x3D98)-gated shape as RSound1's
+ // command4(), though this driver has no equivalent to RSound1's
+ // command39 (its own bucket structure stops at 35, then jumps to
+ // 64+), so this offset can't be "reused ambient-piece target" here -
+ // just a coincidentally identical offset NUMBER, not assumed to hold
+ // the same data.
+ if (isSoundActive(loadData(0x3D98)))
+ return 0;
+ resetAndGmResetUpperChannels();
+ return 0;
+}
+
+int RSound2::command5() {
+ // Confirmed: same isSoundActive(0x3D98) gate as command4() above.
+ if (isSoundActive(loadData(0x3D98)))
+ return 0;
+ enableUpperChannels();
+ return 0;
+}
+
+int RSound2::command16() {
+ // Unlike RSound1's command16 (a random-ambiance picker), this is a
+ // plain gated 3-channel load - confirms command16 is genuinely
+ // driver-specific, not shared behavior.
+ byte *pData = loadData(0x1188);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x11E1));
+ _channels[2].load(loadData(0x1246));
+ }
+ return 0;
+}
+
+int RSound2::command24() {
+ playSound(0x16AC);
+ playSound(0x16C0);
+ return 0;
+}
+
+int RSound2::command25() {
+ playSound(0x16D2);
+ playSound(0x16E6);
+ return 0;
+}
+
+int RSound2::command26() {
+ playSound(0x16F8);
+ return 0;
+}
+
+int RSound2::command27() {
+ playSound(0x1702);
+ return 0;
+}
+
+int RSound2::command32() {
+ // No isSoundActive gate at all - unlike every other multi-load
+ // command seen so far.
+ command1();
+ playSoundChannels1To5(0x129E);
+ playSoundChannels1To5(0x136C);
+ playSoundChannels1To5(0x14D1);
+ return 0;
+}
+
+int RSound2::command33() {
+ byte *pData = loadData(0x52E);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x52E);
+ playSoundAny(0x561);
+ playSoundAny(0x56F);
+ playSoundAny(0x57D);
+ playSoundAny(0x58B);
+ playSoundAny(0x597);
+ }
+ return 0;
+}
+
+int RSound2::command34() {
+ byte *pData = loadData(0x5A6);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x5A6);
+ playSoundAny(0x776);
+ playSoundAny(0xA3B);
+ playSoundAny(0xBC5);
+ playSoundAny(0xD87);
+ playSoundAny(0x1086);
+ playSoundAny(0x112E);
+ }
+ return 0;
+}
+
+int RSound2::command35() {
+ byte *pData = loadData(0x157C);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x157C);
+ playSoundAny(0x15EA);
+ playSoundAny(0x1615);
+ playSoundAny(0x1640);
+ }
+ return 0;
+}
+
+int RSound2::command64() {
+ _channels[8].load(loadData(0x17B6));
+ return 0;
+}
+
+int RSound2::command65() {
+ _channels[8].load(loadData(0x17AE));
+ return 0;
+}
+
+int RSound2::command66() {
+ playSound(0x17A0);
+ return 0;
+}
+
+int RSound2::command67() {
+ playSound(0x1732);
+ playSound(0x1748);
+ playSound(0x175E);
+ playSound(0x1774);
+ playSound(0x178A);
+ return 0;
+}
+
+int RSound2::command68() {
+ // Same 4 offsets called twice each - a genuine quirk, preserved
+ // exactly rather than collapsed to 4 calls.
+ playSoundAny(0x17C2);
+ playSoundAny(0x17F2);
+ playSoundAny(0x1828);
+ playSoundAny(0x185E);
+ playSoundAny(0x17C2);
+ playSoundAny(0x17F2);
+ playSoundAny(0x1828);
+ playSoundAny(0x185E);
+ return 0;
+}
+
+int RSound2::command69() {
+ _channels[8].load(loadData(0x1721));
+ return 0;
+}
+
+int RSound2::command70() {
+ playSound(0x1894);
+ playSound(0x18B1);
+ return 0;
+}
+
+int RSound2::command71() {
+ playSound(0x18CA);
+ return 0;
+}
+
+int RSound2::command72() {
+ playSound(0x168A);
+ return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+
+const RSound3::CommandPtr RSound3::_commandList[77] = {
+ &RSound3::command0, &RSound3::command1, &RSound3::command2,
+ // Index 3 is a plain "jmp rsound_command1" in the disassembly - the
+ // dispatch slot for command index 3 aliases directly to command1's
+ // FULL handler (lower channels 1-4,9 AND upper channels 5-8), not
+ // to the "inner" lower-only logic that the base class's own
+ // command3() implements. Mapping it to &RSound3::command1 (rather
+ // than &RSound3::command3) preserves that exactly.
+ &RSound3::command1,
+ &RSound3::command4, &RSound3::command5, &RSound3::command6, &RSound3::command7,
+ &RSound3::command8, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::command16, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::command24, &RSound3::command25, &RSound3::command26, &RSound3::command27,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::command32, &RSound3::command33, &RSound3::command34, &RSound3::command35,
+ &RSound3::command36, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand, &RSound3::nullCommand,
+ &RSound3::command64, &RSound3::command65, &RSound3::command66, &RSound3::command67,
+ &RSound3::command68, &RSound3::command69, &RSound3::command70, &RSound3::command71,
+ &RSound3::command72, &RSound3::command73, &RSound3::command74, &RSound3::command75,
+ &RSound3::nullCommand
+};
+
+RSound3::RSound3(Audio::Mixer *mixer) : RSound(mixer, "rsound.ph3", 0x2BA0, 0x37A0, 0xEC) {
+}
+
+int RSound3::command(int commandId, int param) {
+ if (commandId < 0 || commandId >= ARRAYSIZE(_commandList))
+ return 0;
+
+ _commandParam = param;
+ _frameCounter = 0;
+ return (this->*_commandList[commandId])();
+}
+
+int RSound3::command4() {
+ // Confirmed: TWO chained isSoundActive() gates (unlike RSound1/
+ // RSound2's single 0x3D98 gate), then a tail (sub_10832) that
+ // resets only channels 5-9 (indices 4-8) - narrower than the
+ // shared resetChannels4to9()'s 4-9 (indices 3-8) - so it can't
+ // reuse resetAndGmResetUpperChannels() and calls resetChannelRange()
+ // directly instead.
+ if (isSoundActive(loadData(0x2AA6)))
+ return 0;
+ if (isSoundActive(loadData(0x1E30)))
+ return 0;
+
+ _isDisabled = true;
+ resetChannelRange(4, 8);
+ _isDisabled = false;
+ sendGmReset(RSOUND_CHANNEL_COUNT);
+ return 0;
+}
+
+int RSound3::command5() {
+ // Same two-gate shape as command4() above, but the plain
+ // enableUpperChannels() tail (loc_10955, shared with command1()).
+ if (isSoundActive(loadData(0x2AA6)))
+ return 0;
+ if (isSoundActive(loadData(0x1E30)))
+ return 0;
+ enableUpperChannels();
+ return 0;
+}
+
+int RSound3::command16() {
+ byte *pData = loadData(0x1BFA);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x1CAF));
+ _channels[2].load(loadData(0x1D24));
+ _channels[3].load(loadData(0x1D8B));
+ }
+ return 0;
+}
+
+int RSound3::command24() {
+ playSoundChannels5To8(0x32E9);
+ playSoundChannels5To8(0x32FD);
+ return 0;
+}
+
+int RSound3::command25() {
+ playSoundChannels5To8(0x330F);
+ playSoundChannels5To8(0x3323);
+ return 0;
+}
+
+int RSound3::command26() {
+ playSoundChannels5To8(0x3335);
+ return 0;
+}
+
+int RSound3::command27() {
+ playSoundChannels5To8(0x333F);
+ return 0;
+}
+
+int RSound3::command32() {
+ byte *pData = loadData(0x14F2);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x14F2);
+ playSoundAny(0x154A);
+ playSoundAny(0x1589);
+ playSoundAny(0x15C8);
+ playSoundAny(0x1638);
+ playSoundAny(0x16BF);
+ playSoundAny(0x1704);
+ }
+ return 0;
+}
+
+int RSound3::command33() {
+ byte *pData = loadData(0x1E30);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x1E30);
+ playSoundAny(0x2094);
+ playSoundAny(0x2326);
+ playSoundAny(0x2432);
+ playSoundAny(0x2632);
+ playSoundAny(0x28FF);
+ }
+ return 0;
+}
+
+int RSound3::command34() {
+ // No isSoundActive gate at all - unconditional, unlike 32/33/35/36.
+ command1();
+ playSoundAny(0x6AE);
+ playSoundAny(0x95F);
+ playSoundAny(0xA67);
+ playSoundAny(0xC49);
+ playSoundAny(0xE43);
+ playSoundAny(0x10AA);
+ playSoundAny(0x11E0);
+ playSoundAny(0x138C);
+ return 0;
+}
+
+int RSound3::command35() {
+ byte *pData = loadData(0x2AA6);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x2AA6);
+ playSoundAny(0x2C43);
+ playSoundAny(0x2D53);
+ playSoundAny(0x2E43);
+ playSoundAny(0x2F4B);
+ playSoundAny(0x30FD);
+ }
+ return 0;
+}
+
+int RSound3::command36() {
+ byte *pData = loadData(0x1778);
+ if (!isSoundActive(pData)) {
+ command1();
+ playSoundAny(0x1778);
+ playSoundAny(0x1832);
+ playSoundAny(0x18EC);
+ playSoundAny(0x1B9D);
+ }
+ return 0;
+}
+
+int RSound3::command64() {
+ playSoundChannels5To8(0x335E);
+ return 0;
+}
+
+int RSound3::command65() {
+ playSoundChannels5To8(0x337E);
+ playSoundChannels5To8(0x3391);
+ return 0;
+}
+
+int RSound3::command66() {
+ // 0x33AE is called three times in a row - a genuine quirk, preserved
+ // exactly rather than collapsed to a single call.
+ playSoundChannels5To8(0x33A4);
+ playSoundChannels5To8(0x33AE);
+ playSoundChannels5To8(0x33AE);
+ playSoundChannels5To8(0x33AE);
+ return 0;
+}
+
+int RSound3::command67() {
+ playSoundChannels5To8(0x33BC);
+ return 0;
+}
+
+int RSound3::command68() {
+ playSoundChannels5To8(0x33C9);
+ return 0;
+}
+
+int RSound3::command69() {
+ playSoundChannels5To8(0x33DA);
+ return 0;
+}
+
+int RSound3::command70() {
+ playSoundChannels5To8(0x32CF);
+ return 0;
+}
+
+int RSound3::command71() {
+ playSoundChannels5To8(0x3294);
+ return 0;
+}
+
+int RSound3::command72() {
+ playSoundChannels5To8(0x3406);
+ return 0;
+}
+
+int RSound3::command73() {
+ playSoundChannels5To8(0x3414);
+ return 0;
+}
+
+int RSound3::command74() {
+ _channels[8].load(loadData(0x3429));
+ return 0;
+}
+
+int RSound3::command75() {
+ playSoundChannels5To8(0x33EB);
+ return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+
+const RSound4::CommandPtr RSound4::_commandList[72] = {
+ &RSound4::command0, &RSound4::command1, &RSound4::command2, &RSound4::command3,
+ &RSound4::command4, &RSound4::command5, &RSound4::command6, &RSound4::command7,
+ &RSound4::command8, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::command16, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::command24, &RSound4::command25, &RSound4::command26, &RSound4::command27,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand, &RSound4::nullCommand,
+ &RSound4::command64, &RSound4::command65, &RSound4::command66, &RSound4::command67,
+ &RSound4::command68, &RSound4::command69, &RSound4::command70, &RSound4::nullCommand
+};
+
+RSound4::RSound4(Audio::Mixer *mixer) : RSound(mixer, "rsound.ph4", 0x2A40, 0xE00, 0xEC) {
+}
+
+int RSound4::command(int commandId, int param) {
+ if (commandId < 0 || commandId >= ARRAYSIZE(_commandList))
+ return 0;
+
+ _commandParam = param;
+ _frameCounter = 0;
+ return (this->*_commandList[commandId])();
+}
+
+int RSound4::command4() {
+ // Confirmed: NO isSoundActive() gate in this driver, unlike every
+ // other driver seen so far - matches sub_107FC + sendGmReset(9)
+ // unconditionally.
+ _isDisabled = true;
+ resetChannelRange(5, 8);
+ _isDisabled = false;
+ sendGmReset(RSOUND_CHANNEL_COUNT);
+ return 0;
+}
+
+int RSound4::command5() {
+ // Confirmed: sub_10838 (_fadeCheckPeriod = 1) + channels 5-8 -
+ // identical to the shared enableUpperChannels(), no gate at all.
+ enableUpperChannels();
+ return 0;
+}
+
+int RSound4::command16() {
+ // sub_101E7 ("isSoundPlaying") confirmed functionally identical to
+ // RSound::isSoundActive() - same channel 1-8 scan, same
+ // _activeCount && _soundData==pData test, same pop-return-address
+ // early-exit trick.
+ if (!isSoundActive(loadData(0x8F0))) {
+ command1();
+ _channels[0].load(loadData(0x63A));
+ _channels[1].load(loadData(0x8F0));
+ _channels[2].load(loadData(0x916));
+ _channels[3].load(loadData(0x93C));
+ _channels[4].load(loadData(0x962));
+ }
+ return 0;
+}
+
+int RSound4::command24() {
+ playSound(0x9C6);
+ playSound(0x9D6);
+ return 0;
+}
+
+int RSound4::command25() {
+ playSound(0x9EC);
+ playSound(0xA00);
+ return 0;
+}
+
+int RSound4::command26() {
+ playSound(0xA12);
+ return 0;
+}
+
+int RSound4::command27() {
+ playSound(0xA1C);
+ return 0;
+}
+
+int RSound4::command64() {
+ playSound(0xA38);
+ return 0;
+}
+
+int RSound4::command65() {
+ playSound(0xA4E);
+ return 0;
+}
+
+int RSound4::command66() {
+ playSound(0xA60);
+ return 0;
+}
+
+int RSound4::command67() {
+ playSound(0xA76);
+ return 0;
+}
+
+int RSound4::command68() {
+ playSound(0xA80);
+ return 0;
+}
+
+int RSound4::command69() {
+ playSound(0xA8C);
+ return 0;
+}
+
+int RSound4::command70() {
+ playSound(0x9A4);
+ return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+
+const RSound5::CommandPtr RSound5::_commandList[80] = {
+ &RSound5::command0, &RSound5::command1, &RSound5::command2, &RSound5::command3,
+ &RSound5::command4, &RSound5::command5, &RSound5::command6, &RSound5::command7,
+ &RSound5::command8, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::command16, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::command24, &RSound5::command25, &RSound5::command26, &RSound5::command27,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::command33, &RSound5::command34, &RSound5::command35,
+ &RSound5::command36, &RSound5::command37, &RSound5::command38, &RSound5::command39,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand, &RSound5::nullCommand,
+ &RSound5::command64, &RSound5::command65, &RSound5::command66, &RSound5::command67,
+ &RSound5::command68, &RSound5::command69, &RSound5::command70, &RSound5::command71,
+ &RSound5::command72, &RSound5::command73, &RSound5::command74, &RSound5::command75,
+ &RSound5::command76, &RSound5::command77, &RSound5::command78, &RSound5::nullCommand
+};
+
+RSound5::RSound5(Audio::Mixer *mixer) : RSound(mixer, "rsound.ph5", 0x2BD0, 0x4870, 0xEC) {
+}
+
+int RSound5::command(int commandId, int param) {
+ if (commandId < 0 || commandId >= ARRAYSIZE(_commandList))
+ return 0;
+
+ _commandParam = param;
+ _frameCounter = 0;
+ return (this->*_commandList[commandId])();
+}
+
+int RSound5::command3() {
+ // Confirmed: enables channels 1-5 AND 9 (six channels) - channel 5
+ // has moved into the "lower" group here, unlike every prior driver's
+ // command3() (channels 1-4,9 only, matching RSound::command3()).
+ _fadeCheckPeriod = 1;
+ _channels[0].enable(0xFF);
+ _channels[1].enable(0xFF);
+ _channels[2].enable(0xFF);
+ _channels[3].enable(0xFF);
+ _channels[4].enable(0xFF);
+ _channels[8].enable(0xFF);
+ return 0;
+}
+
+void RSound5::enableChannels678() {
+ _fadeCheckPeriod = 1;
+ _channels[5].enable(0xFF);
+ _channels[6].enable(0xFF);
+ _channels[7].enable(0xFF);
+}
+
+int RSound5::command1() {
+ // Must call THIS driver's own command3() (see class comment - not
+ // virtual in the base, so RSound::command1() would otherwise call
+ // the base's command3() even with this override present).
+ command3();
+ enableChannels678();
+ return 0;
+}
+
+int RSound5::command4() {
+ // No isSoundActive() gate, matching RSound4's command4() shape.
+ _isDisabled = true;
+ resetChannelRange(5, 8);
+ _isDisabled = false;
+ sendGmReset(RSOUND_CHANNEL_COUNT);
+ return 0;
+}
+
+int RSound5::command5() {
+ enableChannels678();
+ return 0;
+}
+
+int RSound5::command16() {
+ byte *pData = loadData(0xBBE);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0xC60));
+ _channels[2].load(loadData(0xD04));
+ _channels[3].load(loadData(0xD97));
+ _channels[4].load(loadData(0xDDA));
+ }
+ return 0;
+}
+
+int RSound5::command24() {
+ playSound(0x437C);
+ playSound(0x4390);
+ return 0;
+}
+
+int RSound5::command25() {
+ playSound(0x43A2);
+ playSound(0x43B6);
+ return 0;
+}
+
+int RSound5::command26() {
+ playSound(0x43C8);
+ return 0;
+}
+
+int RSound5::command27() {
+ playSound(0x43D2);
+ return 0;
+}
+
+int RSound5::command33() {
+ byte *pData = loadData(0xE66);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x110E));
+ _channels[2].load(loadData(0x13A4));
+ _channels[3].load(loadData(0x14B4));
+ _channels[4].load(loadData(0x16E4));
+ _channels[5].load(loadData(0x19CB));
+ }
+ return 0;
+}
+
+int RSound5::command34() {
+ byte *pData = loadData(0x3190);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x3330));
+ _channels[2].load(loadData(0x34BA));
+ _channels[3].load(loadData(0x34E8));
+ _channels[4].load(loadData(0x3518));
+ _channels[5].load(loadData(0x3546));
+ _channels[6].load(loadData(0x3574));
+ _channels[7].load(loadData(0x35A6));
+ }
+ return 0;
+}
+
+int RSound5::command35() {
+ byte *pData = loadData(0x1B9A);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x218B));
+ _channels[2].load(loadData(0x271D));
+ }
+ return 0;
+}
+
+int RSound5::command36() {
+ byte *pData = loadData(0x2D04);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x2E43));
+ _channels[2].load(loadData(0x2F73));
+ }
+ return 0;
+}
+
+int RSound5::command37() {
+ byte *pData = loadData(0x35D8);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x3884));
+ _channels[2].load(loadData(0x39A6));
+ _channels[3].load(loadData(0x3BC9));
+ }
+ return 0;
+}
+
+int RSound5::command38() {
+ byte *pData = loadData(0x73A);
+ if (!isSoundActive(pData)) {
+ command1();
+ _channels[0].load(pData);
+ _channels[1].load(loadData(0x87A));
+ _channels[2].load(loadData(0x938));
+ _channels[3].load(loadData(0x9F9));
+ _channels[4].load(loadData(0xA8A));
+ }
+ return 0;
+}
+
+int RSound5::command39() {
+ // Gate leads to command3() (the lower-group enable), not command1() -
+ // see class comment. Only playSoundChannels1To5() is used here, never
+ // the upper 3 channels.
+ byte *pData = loadData(0x3C5C);
+ if (!isSoundActive(pData)) {
+ command3();
+ playSoundChannels1To5(0x3C5C);
+ playSoundChannels1To5(0x3EB3);
+ playSoundChannels1To5(0x3FC4);
+ playSoundChannels1To5(0x40E3);
+ playSoundChannels1To5(0x41D4);
+ playSoundChannels1To5(0x4296);
+ }
+ return 0;
+}
+
+int RSound5::command64() {
+ playSound(0x447B);
+ return 0;
+}
+
+int RSound5::command65() {
+ playSound(0x43EE);
+ return 0;
+}
+
+int RSound5::command66() {
+ playSound(0x43FC);
+ return 0;
+}
+
+int RSound5::command67() {
+ _channels[6].load(loadData(0x4422));
+ return 0;
+}
+
+int RSound5::command68() {
+ playSound(0x4406);
+ return 0;
+}
+
+int RSound5::command69() {
+ playSound(0x442C);
+ return 0;
+}
+
+int RSound5::command70() {
+ playSound(0x4418);
+ return 0;
+}
+
+int RSound5::command71() {
+ playSound(0x444A);
+ playSound(0x4459);
+ return 0;
+}
+
+int RSound5::command72() {
+ playSound(0x4467);
+ return 0;
+}
+
+int RSound5::command73() {
+ _channels[6].load(loadData(0x4471));
+ return 0;
+}
+
+int RSound5::command74() {
+ playSound(0x4488);
+ return 0;
+}
+
+int RSound5::command75() {
+ playSound(0x449D);
+ return 0;
+}
+
+int RSound5::command76() {
+ playSound(0x44B9);
+ return 0;
+}
+
+int RSound5::command77() {
+ playSound(0x44D1);
+ return 0;
+}
+
+int RSound5::command78() {
+ playSound(0x44EB);
+ return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+
+const RSound9::CommandPtr RSound9::_commandList[73] = {
+ &RSound9::command0, &RSound9::command1, &RSound9::command2, &RSound9::command3,
+ &RSound9::command4, &RSound9::command5, &RSound9::command6, &RSound9::command7,
+ &RSound9::command8, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::command16Or24, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::command16Or24, &RSound9::command25, &RSound9::command26, &RSound9::command27,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::command32, &RSound9::nullCommand, &RSound9::command34, &RSound9::command35,
+ &RSound9::command36, &RSound9::command37, &RSound9::command38, &RSound9::command39,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand, &RSound9::nullCommand,
+ &RSound9::command64, &RSound9::command65Or66, &RSound9::command65Or66, &RSound9::command67,
+ &RSound9::command68, &RSound9::command69, &RSound9::command70, &RSound9::command71,
+ &RSound9::nullCommand
+};
+
+RSound9::RSound9(Audio::Mixer *mixer) : RSound(mixer, "rsound.ph9", 0x2B70, 0x2D40, 0xEC) {
+}
+
+int RSound9::command(int commandId, int param) {
+ if (commandId < 0 || commandId >= ARRAYSIZE(_commandList))
+ return 0;
+
+ _commandParam = param;
+ _frameCounter = 0;
+ return (this->*_commandList[commandId])();
+}
+
+int RSound9::command4() {
+ // sub_1082B: resets channels 7,8,9 (0-based indices 6-8) - a fourth
+ // distinct range for this reset tail (RSound1/2: 4-9, RSound3: 5-9,
+ // RSound4/5: 6-9). No isSoundActive() gate, matching RSound4/5.
+ _isDisabled = true;
+ resetChannelRange(6, 8);
+ _isDisabled = false;
+ sendGmReset(RSOUND_CHANNEL_COUNT);
+ return 0;
+}
+
+int RSound9::command5() {
+ // Confirmed to match the shared enableUpperChannels() exactly
+ // (channels 5-8, armFadeCheck).
+ enableUpperChannels();
+ return 0;
+}
+
+int RSound9::command16Or24() {
+ playSoundChannels5To8(0x295A);
+ return 0;
+}
+
+int RSound9::command25() {
+ playSoundChannels5To8(0x295C);
+ return 0;
+}
+
+int RSound9::command26() {
+ playSoundChannels5To8(0x295E);
+ return 0;
+}
+
+int RSound9::command27() {
+ playSoundChannels5To8(0x2960);
+ return 0;
+}
+
+int RSound9::command32() {
+ // No isSoundActive gate at all - unconditional.
+ command1();
+ playSoundAny(0x628);
+ playSoundAny(0x682);
+ playSoundAny(0x6CA);
+ playSoundAny(0x99C);
+ return 0;
+}
+
+int RSound9::command34() {
+ // True dispatch index 34 - this is the disassembly's
+ // "rsound_command33" body (see class comment on the naming shift).
+ command1();
+ playSoundAny(0x9A8);
+ playSoundAny(0x9E4);
+ playSoundAny(0xA53);
+ playSoundAny(0xA96);
+ playSoundAny(0xAFB);
+ playSoundAny(0xB34);
+ return 0;
+}
+
+int RSound9::command35() {
+ // True dispatch index 35 - disassembly's "rsound_command34" body.
+ command1();
+ playSoundAny(0x1008);
+ playSoundAny(0x103C);
+ playSoundAny(0x105D);
+ playSoundAny(0x107E);
+ playSoundAny(0x10D6);
+ playSoundAny(0x1131);
+ playSoundAny(0x116C);
+ return 0;
+}
+
+int RSound9::command36() {
+ // True dispatch index 36 - disassembly's "rsound_command35" body.
+ command1();
+ playSoundAny(0x1198);
+ playSoundAny(0x11C9);
+ playSoundAny(0x11F5);
+ playSoundAny(0x1229);
+ playSoundAny(0x125D);
+ playSoundAny(0x1283);
+ return 0;
+}
+
+int RSound9::command37() {
+ // True dispatch index 37 - disassembly's "rsound_command36" body.
+ command1();
+ playSoundAny(0x12B8);
+ playSoundAny(0x1306);
+ playSoundAny(0x1408);
+ playSoundAny(0x1486);
+ playSoundAny(0x14E0);
+ playSoundAny(0x1733);
+ playSoundAny(0x1A44);
+ return 0;
+}
+
+int RSound9::command38() {
+ // True dispatch index 38 - disassembly's "rsound_command37" body.
+ command1();
+ playSoundAny(0x1B30);
+ playSoundAny(0x1DE1);
+ playSoundAny(0x1EE9);
+ playSoundAny(0x20CB);
+ playSoundAny(0x22C5);
+ playSoundAny(0x250F);
+ playSoundAny(0x2645);
+ playSoundAny(0x27F1);
+ return 0;
+}
+
+int RSound9::command39() {
+ // True dispatch index 39 - disassembly's "rsound_command38" body.
+ command1();
+ playSoundChannels1To6(0xB76);
+ playSoundChannels1To6(0xCB4);
+ playSoundChannels1To6(0xD7B);
+ playSoundChannels1To6(0xE3E);
+ playSoundChannels1To6(0xED2);
+ return 0;
+}
+
+int RSound9::command64() {
+ _channels[7].load(loadData(0x296A));
+ return 0;
+}
+
+int RSound9::command65Or66() {
+ _channels[8].load(loadData(0x2962));
+ return 0;
+}
+
+int RSound9::command67() {
+ // 0x298A is called three times in a row - a genuine quirk, preserved
+ // exactly rather than collapsed to a single call.
+ playSoundChannels5To8(0x2980);
+ playSoundChannels5To8(0x298A);
+ playSoundChannels5To8(0x298A);
+ playSoundChannels5To8(0x298A);
+ return 0;
+}
+
+int RSound9::command68() {
+ _channels[7].load(loadData(0x29C4));
+ return 0;
+}
+
+int RSound9::command69() {
+ _channels[8].load(loadData(0x29B3));
+ return 0;
+}
+
+int RSound9::command70() {
+ // Shares a fallthrough tail with command71 (loc_12439 in the
+ // disassembly): writes a variant byte into the sound data at 0x299B
+ // before playing 0x2998 - twice, matching the disassembly's call +
+ // tail-jmp pair exactly.
+ *loadData(0x299B) = 80;
+ playSoundChannels5To8(0x2998);
+ playSoundChannels5To8(0x2998);
+ return 0;
+}
+
+int RSound9::command71() {
+ *loadData(0x299B) = 99;
+ playSoundChannels5To8(0x2998);
+ playSoundChannels5To8(0x2998);
+ return 0;
+}
+
} // namespace Phantom
} // namespace MADS
diff --git a/engines/mads/phantom/rsound_phantom.h b/engines/mads/phantom/rsound_phantom.h
index b8df21a3980..f73a4869a82 100644
--- a/engines/mads/phantom/rsound_phantom.h
+++ b/engines/mads/phantom/rsound_phantom.h
@@ -62,7 +62,8 @@ private:
int command16();
// Overrides confirming the isSoundActive(0x3D98) gate the base
- // class's ungated command4()/command5() lack - see RSound::command4/5.
+ // class's pure-virtual command4()/command5() require - see
+ // RSound::command4/5.
int command4() override;
int command5() override;
@@ -108,6 +109,346 @@ public:
int command(int commandId, int param) override;
};
+/**
+ * RSound2 (rsound.ph2)
+ *
+ * Dispatch table layout (funcs_10964/1097A/10990/109A6/109BC in the
+ * disassembly - same 5-bucket sparse-dispatch shape as RSound1, but here
+ * represented as a single flat array, matching the Rex Nebular RSound
+ * convention, since the bucket boundaries don't otherwise affect behavior):
+ * commands 0-8 (base class, except command4/5 - see RSound::command4/5)
+ * command 16 (this class - a plain gated 3-channel load, NOT a
+ * random picker like RSound1's command16)
+ * commands 24-27 (this class)
+ * commands 32-35 (this class)
+ * commands 64-72 (this class); 73 is confirmed nullsub_1
+ * All other indices in [0, 73] are confirmed or inferred unreachable
+ * (nullCommand).
+ */
+class RSound2 : public RSound {
+private:
+ typedef int (RSound2:: *CommandPtr)();
+ static const CommandPtr _commandList[74];
+
+ // Overrides confirming the isSoundActive(0x3D98) gate the base
+ // class's pure-virtual command4()/command5() require - see
+ // RSound::command4/5. Same offset NUMBER as RSound1's, but not
+ // assumed to be the same underlying data.
+ int command4() override;
+ int command5() override;
+
+ int command16();
+ int command24();
+ int command25();
+ int command26();
+ int command27();
+ int command32();
+ int command33();
+ int command34();
+ int command35();
+ int command64();
+ int command65();
+ int command66();
+ int command67();
+ int command68();
+ int command69();
+ int command70();
+ int command71();
+ int command72();
+
+public:
+ RSound2(Audio::Mixer *mixer);
+
+ int command(int commandId, int param) override;
+};
+
+/**
+ * RSound3 (rsound.ph3)
+ *
+ * Dispatch table layout (funcs_10A0C/10A22/10A38/10A4E/10A64 in the
+ * disassembly - same 5-bucket sparse-dispatch shape as RSound1/RSound2,
+ * represented here as a single flat array):
+ * commands 0-8 (base class, except command4/5 - see RSound::command4/5;
+ * index 3 aliases directly to command1's full handler -
+ * see below)
+ * command 16 (this class - a plain gated 4-channel load)
+ * commands 24-27 (this class)
+ * commands 32-36 (this class); 37 is confirmed nullsub_1
+ * commands 64-75 (this class); 76 is confirmed nullsub_1
+ *
+ * command0/1/2/3/6/7/8 all confirmed to match the shared RSound base
+ * exactly (no overrides needed) - including a notable dispatch-table
+ * detail: command index 3 in this driver's dispatch table is a plain
+ * jmp straight into command1's full body (lower channels 1-4,9 AND
+ * upper channels 5-8), NOT the "inner" lower-only logic that the base
+ * class's own command3() implements - so the command list must map
+ * index 3 to &RSound::command1, not &RSound::command3. command0's
+ * disassembly is confirmed to genuinely omit the _isDisabled
+ * save/restore wrap that RSound::command0() has, but with no
+ * observable behavioral difference, so no override is needed there.
+ *
+ * command4()/command5() are also a genuinely different shape from
+ * RSound1/RSound2: both gate on TWO chained isSoundActive() checks
+ * (0x2AA6 then 0x1E30, each an independent early-out per the
+ * pop-return-address mechanic) rather than one, and command4()'s tail
+ * (sub_10832) resets only channels 5-9 (indices 4-8) - NOT channels
+ * 4-9 (indices 3-8) like the shared resetAndGmResetUpperChannels() -
+ * so it can't reuse that helper and calls resetChannelRange()
+ * directly instead.
+ */
+class RSound3 : public RSound {
+private:
+ typedef int (RSound3:: *CommandPtr)();
+ static const CommandPtr _commandList[77];
+
+ int command4() override;
+ int command5() override;
+
+ int command16();
+
+ int command24();
+ int command25();
+ int command26();
+ int command27();
+
+ int command32();
+ int command33();
+ int command34();
+ int command35();
+ int command36();
+
+ int command64();
+ int command65();
+ int command66();
+ int command67();
+ int command68();
+ int command69();
+ int command70();
+ int command71();
+ int command72();
+ int command73();
+ int command74();
+ int command75();
+
+public:
+ RSound3(Audio::Mixer *mixer);
+
+ int command(int commandId, int param) override;
+};
+
+/**
+ * RSound4 (rsound.ph4)
+ *
+ * Dispatch table layout (funcs_109CE/109E4/109FA/10A10/10A26 in the
+ * disassembly - same 5-bucket sparse-dispatch shape as RSound1-3,
+ * represented here as a single flat array):
+ * commands 0-8 (base class, except command4/5 - see RSound::command4/5)
+ * command 16 (this class)
+ * commands 24-27 (this class)
+ * command 32 (confirmed nullsub_1 - this driver has no command32-39
+ * at all, unlike RSound1-3, which all had a populated
+ * 32+ bucket)
+ * commands 64-70 (this class); 71 is confirmed nullsub_1
+ *
+ * command0/1/2/3/6/7/8 confirmed to match the shared RSound base exactly
+ * (no overrides needed) - including command3, which (unlike RSound3) is
+ * NOT aliased to command1's dispatch slot; this driver's dispatch table
+ * has a genuine, separate rsound_command3 entry matching the base
+ * class's command3() exactly.
+ *
+ * command4() is a genuinely new shape: unlike every driver confirmed so
+ * far, it has NO isSoundActive() gate at all - it unconditionally resets
+ * channels 6-9 (0-based indices 5-8, confirmed via explicit
+ * Channel._activeCount/_volumeFadeStep-labeled writes in sub_107FC) via
+ * resetChannelRange(), then sends a full sendGmReset(9). A third distinct
+ * channel range for this reset (RSound1/RSound2 used 4-9, RSound3 used
+ * 5-9), reinforcing that this range is always driver-specific.
+ *
+ * command1/command3 confirmed to match the shared RSound base exactly
+ * (no overrides needed) - sub_10838 (called at the top of both) is
+ * confirmed to be exactly "_fadeCheckPeriod = 1", and command5's four
+ * channel-enable targets are confirmed to be channels 5-8, matching
+ * enableUpperChannels() exactly.
+ */
+class RSound4 : public RSound {
+private:
+ typedef int (RSound4:: *CommandPtr)();
+ static const CommandPtr _commandList[72];
+
+ int command4() override;
+ int command5() override;
+
+ int command16();
+
+ int command24();
+ int command25();
+ int command26();
+ int command27();
+
+ int command64();
+ int command65();
+ int command66();
+ int command67();
+ int command68();
+ int command69();
+ int command70();
+
+public:
+ RSound4(Audio::Mixer *mixer);
+
+ int command(int commandId, int param) override;
+};
+
+/**
+ * RSound5 (rsound.ph5)
+ *
+ * Dispatch table layout (funcs_109F0/10A06/10A1C/10A32/10A48 in the
+ * disassembly - same 5-bucket sparse-dispatch shape as RSound1-4):
+ * commands 0-8 (base class, except command1/3/4/5 - see below)
+ * command 16 (this class)
+ * commands 24-27 (this class)
+ * commands 32-39 (this class); 32 is confirmed nullsub_2
+ * commands 64-78 (this class); 79 is confirmed nullsub_1
+ *
+ * command0/2/6/7/8 confirmed to match the shared RSound base exactly (no
+ * overrides needed).
+ *
+ * command1/command3 are a genuinely different shape from every prior
+ * driver: command3's "lower" enable group is channels 1-5 AND 9 (SIX
+ * channels), not the base class's 1-4,9 (five channels) - channel 5 has
+ * moved from the "upper" group into the "lower" one. Since command1/2/3
+ * are not virtual in the base class, RSound::command1() would still call
+ * the base's own command3() even with a derived override present, so
+ * command1() needs its own override here too (calling this driver's own
+ * command3()) rather than relying on the inherited one. command4/5's
+ * upper group is correspondingly narrowed to 3 channels (6,7,8) instead
+ * of the usual 4 (5,6,7,8) - both confirmed directly from disassembly.
+ *
+ * command39 is notable: its gate leads to this driver's own command3()
+ * (the 6-channel lower-group enable), NOT command1() like every other
+ * gated multi-load/multi-play command in this driver - confirmed
+ * directly from the disassembly ("call rsound_command3"), not a typo.
+ */
+class RSound5 : public RSound {
+private:
+ typedef int (RSound5:: *CommandPtr)();
+ static const CommandPtr _commandList[80];
+
+ int command1();
+ int command3();
+ int command4() override;
+ int command5() override;
+
+ /**
+ * Shared tail of command1() and command5() in this driver: enables
+ * channels 6,7,8 (three channels - this driver's narrowed "upper"
+ * group, see class comment). Matches rsound_command5's body.
+ */
+ void enableChannels678();
+
+ int command16();
+
+ int command24();
+ int command25();
+ int command26();
+ int command27();
+
+ int command33();
+ int command34();
+ int command35();
+ int command36();
+ int command37();
+ int command38();
+ int command39();
+
+ int command64();
+ int command65();
+ int command66();
+ int command67();
+ int command68();
+ int command69();
+ int command70();
+ int command71();
+ int command72();
+ int command73();
+ int command74();
+ int command75();
+ int command76();
+ int command77();
+ int command78();
+
+public:
+ RSound5(Audio::Mixer *mixer);
+
+ int command(int commandId, int param) override;
+};
+
+/**
+ * RSound9 (rsound.ph9)
+ *
+ * Dispatch table layout (funcs_109F5/10A0B/10A37/10A4D in the
+ * disassembly - same sparse-dispatch shape as RSound1-5, represented
+ * here as a single flat array):
+ * commands 0-8 (base class, except command4/5 - see RSound::command4/5)
+ * commands 16,24 (SHARED handler - both indices point to the exact
+ * same function in the dispatch table)
+ * commands 25-27 (this class)
+ * commands 32-39 (this class); 33 is confirmed nullsub_1. IMPORTANT:
+ * the disassembly's own symbol names for 34-39 are
+ * each off by one from their true dispatch index
+ * (e.g. the function IDA calls "rsound_command33" is
+ * actually invoked at index 34, "rsound_command34" at
+ * 35, etc., through "rsound_command38" at 39) -
+ * confirmed directly from the funcs_10A37 table
+ * layout, not a transcription error. The C++ method
+ * names below use the TRUE (table-position) command
+ * ID, not the stale IDA symbol suffix.
+ * commands 64-71 (this class); 65 and 66 SHARE a handler (both point
+ * to the same function); 72 is confirmed nullsub_1
+ *
+ * command0/1/2/3/6/7/8 confirmed to match the shared RSound base exactly
+ * (no overrides needed, including command1/command3, unlike RSound5).
+ * command5 also matches the base's enableUpperChannels() exactly
+ * (channels 5-8), so its override just delegates to that helper.
+ */
+class RSound9 : public RSound {
+private:
+ typedef int (RSound9:: *CommandPtr)();
+ static const CommandPtr _commandList[73];
+
+ int command4() override;
+ int command5() override;
+
+ // Shared by dispatch indices 16 AND 24 (identical handler).
+ int command16Or24();
+
+ int command25();
+ int command26();
+ int command27();
+
+ int command32();
+ int command34();
+ int command35();
+ int command36();
+ int command37();
+ int command38();
+ int command39();
+
+ int command64();
+ // Shared by dispatch indices 65 AND 66 (identical handler).
+ int command65Or66();
+ int command67();
+ int command68();
+ int command69();
+ int command70();
+ int command71();
+
+public:
+ RSound9(Audio::Mixer *mixer);
+
+ int command(int commandId, int param) override;
+};
+
} // namespace Phantom
} // namespace MADS
More information about the Scummvm-git-logs
mailing list