[Scummvm-git-logs] scummvm master -> 2650d9aa0f2f3deff794dba3f3f605e21a31097f
dreammaster
noreply at scummvm.org
Sun May 24 23:42:39 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
a434cc270d MADS: DRAGONSPHERE: In progress implementing sound drivers
60143443a6 MADS: DRAGONSPHERE: Add missing entries to the words.h enum
2650d9aa0f MADS: DRAGONSPHERE: Cleanups and fixes for room 104
Commit: a434cc270d15e80bc7cd0fa053fe4bc1736b42c8
https://github.com/scummvm/scummvm/commit/a434cc270d15e80bc7cd0fa053fe4bc1736b42c8
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-05-25T09:42:29+10:00
Commit Message:
MADS: DRAGONSPHERE: In progress implementing sound drivers
Changed paths:
engines/mads/madsv2/dragonsphere/asound.cpp
engines/mads/madsv2/dragonsphere/asound.h
engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
diff --git a/engines/mads/madsv2/dragonsphere/asound.cpp b/engines/mads/madsv2/dragonsphere/asound.cpp
index 35dd5af3d7e..ae1082f1973 100644
--- a/engines/mads/madsv2/dragonsphere/asound.cpp
+++ b/engines/mads/madsv2/dragonsphere/asound.cpp
@@ -19,10 +19,7 @@
*
*/
-#include "common/endian.h"
-#include "common/file.h"
-#include "common/md5.h"
-#include "common/textconsole.h"
+#include "audio/fmopl.h"
#include "mads/madsv2/dragonsphere/asound.h"
namespace MADS {
@@ -30,6 +27,1779 @@ namespace MADSV2 {
namespace Dragonsphere {
+bool AdlibChannel::_isDisabled;
+
+/*
+ * PATCH_ATTEN_TO_TL (seg001:0x0092 / offset from _asound_samples base)
+ * patchAttenuation (0-127) -> 6-bit OPL total-level.
+ * The Dragonsphere asm uses PATCH_ATTEN_TO_TL[bx] for the modulator lookup
+ * and unk_12431 - bx (i.e. PATCH_ATTEN_TO_TL[127 - patchAtt]) for the
+ * carrier lookup.
+ */
+static const uint8 PATCH_ATTEN_TO_TL[128] = {
+ 63, 54, 49, 45, 42, 40, 38, 36, 34, 33, 32, 31, 30, 29, 28, 27,
+ 26, 25, 25, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18,
+ 18, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 14, 13, 13, 13,
+ 12, 12, 12, 12, 11, 11, 11, 11, 11, 10, 10, 10, 10, 9, 9, 9,
+ 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6,
+ 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4,
+ 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
+};
+
+/*
+ * VOL_VEL_TO_ATTEN_STEP (_volVelToAttenStep)
+ * volume or velocity byte (0-127) -> attenuation step (1-32).
+ * Groups of 4 inputs map to the same step.
+ */
+static const uint8 VOL_VEL_TO_ATTEN_STEP[128] = {
+ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
+ 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
+ 9, 9, 9, 9, 10,10,10,10, 11,11,11,11, 12,12,12,12,
+ 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16,
+ 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20,
+ 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24,
+ 25,25,25,25, 26,26,26,26, 27,27,27,27, 28,28,28,28,
+ 29,29,29,29, 30,30,30,30, 31,31,31,31, 32,32,32,32
+};
+
+/*
+ * SEMITONE_FREQ_TABLE (_semitoneFreqTable)
+ * OPL F-number base for each semitone within an octave.
+ */
+static const uint16 SEMITONE_FREQ_TABLE[12] = {
+ 0x0200, 0x021E, 0x023F, 0x0261, 0x0285, 0x02AB,
+ 0x02D4, 0x02FF, 0x032D, 0x035D, 0x0390, 0x03C7
+};
+
+/*
+ * VOICE_SLOTS (byte_1239B in the binary, also used as the operator-reg
+ * index table for command6/7)
+ *
+ * Layout for each voice: { slot0 (modulator), slot1 (carrier) }
+ * The writeVolume loop uses:
+ * pass 0 -> VOICE_SLOTS[ch][0] (modulator)
+ * pass 1 -> VOICE_SLOTS[ch][1] (carrier)
+ * The alg!=0 single-op path (loc_11692) goes directly to VOICE_SLOTS[ch][1].
+ */
+static const uint8 VOICE_SLOTS[ADLIB_CHANNEL_COUNT][2] = {
+ { 0, 3 }, { 1, 4 }, { 2, 5 },
+ { 6, 9 }, { 7, 10 }, { 8, 11 },
+ { 12, 15 }, { 13, 16 }, { 14, 17 }
+};
+
+/*
+ * SLOT_TO_REG_OFFSET (_slotToRegOffset)
+ * operator-slot index (0-17) -> OPL register group offset.
+ */
+static const uint8 SLOT_TO_REG_OFFSET[18] = {
+ 0, 1, 2, 3, 4, 5,
+ 8, 9, 10, 11, 12, 13,
+ 16, 17, 18, 19, 20, 21
+};
+
+/*
+ * byte_1239B - all 22 operator TL register indices muted/restored by
+ * command6 and command7. They cover every OPL operator slot (0x40-0x55).
+ */
+static const uint8 ALL_OP_TL_REGS[22] = {
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55
+};
+
+/* Null / silence sound-data placeholder stream. */
+static uint8 ADLIB_NULLDATA[] = {
+ 0x00, 0x01, 0xF3, 0x00, 0x00, 0xEF, 0x00, 0x00,
+ 0xF5, 0x00, 0x00, 0x00, 0xF7, 0x00, 0xF8, 0x1D,
+ 0xFF, 0x00, 0xF4, 0x6E, 0x2A, 0x1C, 0xF4, 0x5F,
+ 0x2A, 0x1C, 0x2A, 0x1C, 0x2A, 0x1C, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+/* OPL version flag (_adlib_v5660 / opl_version_flag).
+ * 0x18 = OPL3 (patch-attenuation-aware writeVolume path). */
+constexpr uint16 OPL_VERSION_FLAG = 0x18;
+
+/* Rhythm-mode flags (from data segment). */
+constexpr bool RHYTHM_HI_HAT = true;
+constexpr bool RHYTHM_CYMBAL = true;
+constexpr bool RHYTHM_ENABLE = true;
+
+AdlibSample::AdlibSample(Common::SeekableReadStream &s) {
+ _attackRate = s.readByte();
+ _decayRate = s.readByte();
+ _sustainLevel = s.readByte();
+ _releaseRate = s.readByte();
+ _egTyp = s.readByte();
+ _ksr = s.readByte();
+ _totalLevel = s.readByte();
+ _scalingLevel = s.readByte();
+ _waveformSelect = s.readByte();
+ _freqMultiple = s.readByte();
+ _feedback = s.readByte();
+ _ampMod = s.readByte();
+ _vib = s.readByte();
+ _alg = s.readByte();
+ _freqSweepInit = s.readByte();
+ _reserved = s.readByte();
+ _freqMask = s.readUint16LE();
+ _freqBase = s.readUint16LE();
+ _outerLoopPtr = s.readUint16LE();
+}
+
+void AdlibChannel::reset() {
+ /* Zero the scalar "live" fields only.
+ *
+ * From AdlibChannel_reset in the binary: clears _activeCount, _pitchBend,
+ * _volumeFadeStep, _vibratoDepth, _arpCounterReload (field_13),
+ * _writeVolumePending (field_11), _transpose, _patchAttenuation,
+ * _pendingStop, _velocity, _volume, _freqSweepCounter, _noiseFreqMask,
+ * _freqAccum, _freqStep, _loopStartPtr, _pSrc.
+ * All other fields are left as-is (they are overwritten by load() or
+ * by the bytecode stream before being read).
+ */
+ _activeCount = 0;
+ _pitchBend = 0;
+ _volumeFadeStep = 0;
+ _vibratoDepth = 0;
+ _arpCounterReload = 0; /* field_13 */
+ _writeVolumePending = 0; /* field_11 */
+ _transpose = 0;
+ _patchAttenuation = 0;
+ _pendingStop = 0;
+ _velocity = 0;
+ _volume = 0;
+ _freqSweepCounter = 0;
+ _noiseFreqMask = 0;
+ _freqAccum = 0;
+ _freqStep = 0;
+ _loopStartPtr = nullptr;
+ _pSrc = nullptr;
+}
+
+void AdlibChannel::load(byte *soundData) {
+ _isDisabled = true;
+
+ /* Zero all bytes in the struct. */
+ memset(this, 0, sizeof(AdlibChannel));
+
+ /* Initialise all loop/source pointers to the supplied data block. */
+ _loopStartPtr = soundData;
+ _pSrc = soundData;
+ _innerLoopPtr = soundData;
+ _outerLoopPtr = soundData;
+ _soundData = soundData;
+
+ /* Start at maximum attenuation (silent) and mark the channel active. */
+ _patchAttenuation = 0x40;
+ _activeCount = 1;
+
+ _isDisabled = false;
+}
+
+void AdlibChannel::setPtr2(byte *ptr) {
+ /* Hard-redirect both read pointers, then arm a one-step fade. */
+ _loopStartPtr = ptr;
+ _pSrc = ptr;
+ _volumeFadeStep = 0xFF; /* -1 signed: fade down */
+ _fadePeriodReload = 1;
+ _fadePeriodCounter = 1;
+}
+
+void AdlibChannel::enable() {
+ /* AdlibChannel_enable: mark pending-stop on active channels only. */
+ if (_activeCount == 0)
+ return;
+ _pendingStop = 0xFF;
+ _soundData = ADLIB_NULLDATA;
+}
+
+void AdlibChannel::processChannelFade() {
+ if (_activeCount == 0)
+ return;
+ if (_pendingStop == 0)
+ return;
+
+ /* Channel has fully faded when both velocity and volume are zero. */
+ if (_velocity == 0 && _volume == 0) {
+ _loopStartPtr = ADLIB_NULLDATA;
+ _pSrc = ADLIB_NULLDATA;
+ _pendingStop = 0;
+ return;
+ }
+
+ /* Arm a quick fade-down (period 2, step -1). */
+ _volumeFadeStep = 0xFF;
+ _fadePeriodReload = 2;
+ if (_fadePeriodCounter == 0)
+ _fadePeriodCounter = 1;
+}
+
+ASound::ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::Path &filename,
+ int dataOffset, int dataSize)
+ : SoundDriver(mixer, opl, filename, dataOffset, dataSize) {
+ AdlibChannel::_isDisabled = false;
+
+ /* Standard OPL timer-reset sequence. */
+ write(4, 0x60);
+ write(4, 0x80);
+ write(2, 0xFF);
+ write(4, 0x21);
+ write(4, 0x60);
+ write(4, 0x80);
+
+ Common::fill(_adlibPorts, _adlibPorts + 256, 0);
+ command0();
+
+ _opl->start(new Common::Functor0Mem<void, ASound>(this, &ASound::onTimer),
+ CALLBACKS_PER_SECOND);
+}
+
+int ASound::stop() {
+ command0();
+ int result = _pollResult;
+ _pollResult = 0;
+ return result;
+}
+
+int ASound::poll() {
+ update();
+ int result = _pollResult;
+ _pollResult = 0;
+ return result;
+}
+
+void ASound::noise() {
+ Common::StackLock slock(_driverMutex);
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+ noise_inner(i);
+}
+
+int ASound::command0() {
+ /* Push _isDisabled, temporarily force to 0xFFFF while resetting. */
+ uint16 savedDisabled = _isDisabled;
+ _isDisabled = 0xFFFF;
+
+ /* 1. Reset all 9 channels. */
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+ _channels[i]->reset();
+
+ /* 2. Mute all operator TL registers (0x40 down to 0x4F, via adlib_write2
+ * with value 0x3F, stepping from 0x4F down while reg >= 0x40). */
+ for (int reg = 0x4F; reg >= 0x40; --reg)
+ write((uint8)reg, 0x3F);
+
+ /* 3. Zero registers 0xFF down to 0x60. */
+ for (int reg = 0xFF; reg >= 0x60; --reg)
+ write((uint8)reg, 0x00);
+
+ /* 4. Zero registers 0x3F down to 0x01. */
+ for (int reg = 0x3F; reg >= 0x01; --reg)
+ write((uint8)reg, 0x00);
+
+ /* 5. Waveform Select Enable. */
+ write(0x01, 0x20);
+
+ /* 6. Reset tick callback. */
+ resetCallback();
+
+ _isDisabled = savedDisabled;
+ return 0;
+}
+
+int ASound::command1() {
+ /* Fade all channels: music 0-6 (command3) then SFX 7-8 (command5). */
+ command3();
+ command5();
+ return 0;
+}
+
+int ASound::command2() {
+ /* Hard-redirect music channels 0-6 to null stream with setPtr2. */
+ _channel0.setPtr2(ADLIB_NULLDATA);
+ _channel1.setPtr2(ADLIB_NULLDATA);
+ _channel2.setPtr2(ADLIB_NULLDATA);
+ _channel3.setPtr2(ADLIB_NULLDATA);
+ _channel4.setPtr2(ADLIB_NULLDATA);
+ _channel5.setPtr2(ADLIB_NULLDATA);
+ _channel6.setPtr2(ADLIB_NULLDATA);
+ return 0;
+}
+
+int ASound::command3() {
+ /* Pending-stop music channels 0-6 (natural envelope fade via enable). */
+ _channel0.enable();
+ _channel1.enable();
+ _channel2.enable();
+ _channel3.enable();
+ _channel4.enable();
+ _channel5.enable();
+ _channel6.enable();
+ return 0;
+}
+
+int ASound::command4() {
+ /* Hard-redirect SFX channels 7-8. */
+ _channel7.setPtr2(ADLIB_NULLDATA);
+ _channel8.setPtr2(ADLIB_NULLDATA);
+ return 0;
+}
+
+int ASound::command5() {
+ /* Pending-stop SFX channels 7-8. */
+ _channel7.enable();
+ _channel8.enable();
+ return 0;
+}
+
+int ASound::command6() {
+ /* Guard: already disabled. */
+ if (_isDisabled == 0xFFFF)
+ return 0;
+ _isDisabled = 0xFFFF;
+
+ /* Save each channel's _freqSweepCounter -> _savedSweepCounter, then zero it. */
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) {
+ AdlibChannel *ch = _channels[i];
+ ch->_savedSweepCounter = ch->_freqSweepCounter;
+ ch->_freqSweepCounter = 0;
+ }
+
+ /* Mute all 22 operator TL registers. */
+ for (int i = 0; i < 22; ++i)
+ adlib_channelOff(ALL_OP_TL_REGS[i]);
+
+ return 0;
+}
+
+int ASound::command7() {
+ /* Guard: only resume from a paused state. */
+ if (_isDisabled != 0xFFFF)
+ return 0;
+
+ /* Restore all operator volumes from the _adlibPorts shadow
+ * (asound_playMusicC: just re-writes whatever was last written). */
+ for (int i = 0; i < 22; ++i)
+ adlib_channelOn(ALL_OP_TL_REGS[i]);
+
+ /* Restore each channel's sweep counter and check whether any was non-zero. */
+ uint8 anySweep = 0;
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) {
+ AdlibChannel *ch = _channels[i];
+ ch->_freqSweepCounter = ch->_savedSweepCounter;
+ anySweep |= ch->_savedSweepCounter;
+ }
+
+ if (anySweep != 0)
+ signalSoundPlaying();
+
+ _isDisabled = 0;
+ return 0;
+}
+
+int ASound::command8() {
+ /* Returns non-zero if any channel is currently active.
+ * Clears byte_12393 (music-only flag) first so all 9 channels are checked. */
+ _musicOnlyFlag = 0;
+ uint8 result = 0;
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+ result |= _channels[i]->_activeCount;
+ return result;
+}
+
+int ASound::command18() {
+ /* Re-entrant background-music launcher (asound_command18 in the binary).
+ * Fades everything, then dispatches back through the command table using
+ * _musicIndex (word_12370) as the command ID. */
+ command1();
+ return command(_musicIndex, 0);
+}
+
+void ASound::callFunction(uint16 offset) {
+ error("Unsupported call to sound driver function at offset %.4x", offset);
+}
+
+void ASound::write(uint8 reg, uint8 value) {
+ _adlibPorts[reg] = value;
+ _opl->writeReg(reg, value);
+}
+
+void ASound::onTimer() {
+ Common::StackLock slock(_driverMutex);
+ poll();
+}
+
+uint16 ASound::getRandomNumber() {
+ /* asound_getRandomNumber: ax = ror3(0x9248 + seed). */
+ uint16 ax = (uint16)(0x9248u + _randomSeed);
+ ax = (uint16)((ax >> 3) | (ax << 13));
+ _randomSeed = ax;
+ return ax;
+}
+
+void ASound::adlib_channelOff(uint8 portIndex) {
+ /* sub_1018F: OR the register with 0x3F (force max attenuation),
+ * then write back both to _adlibPorts and to the OPL chip.
+ * Note: unlike the Phantom driver, the original value is NOT preserved
+ * in _adlibPorts - the ORed value is stored back. */
+ uint8 val = _adlibPorts[portIndex] | 0x3F;
+ _adlibPorts[portIndex] = val;
+ _opl->writeReg(portIndex, val);
+}
+
+void ASound::adlib_channelOn(uint8 portIndex) {
+ /* asound_playMusicC: re-writes whatever _adlibPorts already holds.
+ * command7 simply replays the shadow without recalculating anything. */
+ _opl->writeReg(portIndex, _adlibPorts[portIndex]);
+}
+
+void ASound::signalSoundPlaying() {
+ /* asound_signalSoundPlaying: set resultFlag/pollResult to non-zero. */
+ if (_resultFlag == (int16)0xFFFF)
+ return;
+ _resultFlag = (int16)0xFFFF;
+ _pollResult = (int16)0xFFFF;
+}
+
+void ASound::clearCallback() {
+ _callbackFnPtr = nullptr;
+ _callbackCounter = 0;
+ _callbackPeriod = 0;
+}
+
+void ASound::tickCallback() {
+ if (_callbackPeriod == 0)
+ return;
+ if (--_callbackCounter != 0)
+ return;
+
+ _callbackCounter = _callbackPeriod;
+ if (_callbackFnPtr == nullptr)
+ return;
+
+ auto fn = _callbackFnPtr;
+ _callbackFnPtr = nullptr;
+ (this->*fn)();
+}
+
+void ASound::writeVolume() {
+ AdlibChannel *ch = _activeChannelPtr;
+ uint8 chanNum = _activeChannelNumber;
+
+ /* Compute combined attenuation step from volume and velocity. */
+ uint8 volIdx = ch->_volume;
+ uint8 velIdx = ch->_velocity;
+ int16 volStep = (int16)(uint16)VOL_VEL_TO_ATTEN_STEP[volIdx];
+ int16 velStep = (int16)(uint16)VOL_VEL_TO_ATTEN_STEP[velIdx];
+ int16 var4 = volStep + velStep - 1; /* var_4: combined step (shared) */
+
+ /* Check _alg of the first sample to determine loop count. */
+ AdlibSample *smpFirst = &_samples[ch->_sampleIndex * 2];
+ int passes = (smpFirst->_alg == 0) ? 2 : 1;
+
+ /* si and di track the two TL values written per pass for caching. */
+ int16 finalSi = 0, finalDi = 0;
+
+ /* var_6 = loop counter (0, then 1 for the two-pass case).
+ * For alg != 0 we jump straight into the carrier path (var_6 stays at
+ * effectively 1: VOICE_SLOTS[ch][1]). */
+ for (int var6 = (passes == 1 ? 1 : 0); var6 < 2; ++var6) {
+
+ /* Reload var_2 = var_4 at the start of each pass (loc_11642). */
+ int16 var2 = var4;
+
+ /* Select the operator slot. */
+ uint8 slot = VOICE_SLOTS[chanNum][var6];
+ uint8 regOff = SLOT_TO_REG_OFFSET[slot];
+ uint16 tlReg = (uint16)regOff + 0x40; /* var_8 */
+
+ /* KSL bits from the port shadow. */
+ int16 kslBits = (int16)((uint16)_adlibPorts[tlReg] & 0xC0); /* var_A */
+
+ int16 si, di;
+
+ if (OPL_VERSION_FLAG < 0x18) {
+ /* ---- OPL2 simple path (loc_1167C / loc_1167C equivalent) ---- */
+ int16 tl = (int16)0x3F - var2;
+ tl |= kslBits;
+ si = tl;
+ di = tl;
+ /* adlib_write2(8, tlReg, tl) */
+ write((uint8)tlReg, (uint8)tl);
+ } else {
+ /* ---- OPL3 patch-attenuation path (loc_115BE / loc_116D4) ---- */
+ uint8 pa = ch->_patchAttenuation;
+
+ /* Modulator TL (first register, offset 0): */
+ int16 tlMod = (int16)(uint16)PATCH_ATTEN_TO_TL[pa];
+ /* -(tlMod - var2) = var2 - tlMod */
+ int16 atten1 = var2 - tlMod; /* ax = si initially */
+ si = atten1;
+ if (si < 0) si = 0;
+ else if (si > 63) si = 63;
+ int16 reg0val = ((int16)0x3F - si) | kslBits;
+ si = reg0val;
+ write((uint8)tlReg, (uint8)reg0val);
+
+ /* Carrier TL (second register, offset 2):
+ * unk_12431 - bx (where bx = pa) == PATCH_ATTEN_TO_TL[127 - pa]. */
+ int16 tlCar = (int16)(uint16)PATCH_ATTEN_TO_TL[127 - pa];
+ /* di = var_2 - tlCar (var_4 for alg!=0, var_2=var_4 reload for alg==0) */
+ di = var2 - tlCar;
+ int16 diClamped = di;
+ if (diClamped < 0) diClamped = 0;
+ else if (diClamped > 0x3F) diClamped = 0x3F;
+ int16 reg2val = ((int16)0x3F - diClamped) | kslBits;
+ di = reg2val;
+ write((uint8)(tlReg + 2), (uint8)reg2val);
+ }
+
+ /* For the alg!=0 single-pass case we only run this once (var6==1).
+ * For the alg==0 two-pass case, record both sets. */
+ if (var6 == 1) {
+ finalSi = si;
+ finalDi = di;
+ } else {
+ /* var6==0: record si/di for the mod pass; di is overwritten on
+ * the carrier pass so we use si for the high byte of field_2A. */
+ finalSi = si;
+ finalDi = di;
+ }
+ }
+
+ /* Cache the written TL bytes in field_2A:
+ * dl = di & 0x3F (carrier / second register)
+ * dh = si & 0x3F (modulator / first register)
+ * Packed as a word at offset 0x2A. */
+ ch->_cachedCarrierTL = (uint8)(finalDi & 0x3F); /* dl -> field_2A low byte */
+ /* The high byte (si & 0x3F) is stored in _savedFreqSweep (offset 0x2B)
+ * by the assembly. We write it separately to avoid clobbering the real
+ * savedFreqSweep which lives there - the original asm stores both bytes
+ * as a word at [bx+2Ah], meaning field_2A=dl and field_2B=dh=si&0x3F.
+ * In our C++ layout _savedFreqSweep is at 0x2B; we shadow the dh byte
+ * into it only when writing volume, consistent with the original. */
+ ch->_savedFreqSweep = (uint8)(finalSi & 0x3F);
+}
+
+void ASound::writeFrequency() {
+ AdlibChannel *ch = _activeChannelPtr;
+ uint8 chanNum = _activeChannelNumber;
+ uint16 aReg = (uint16)chanNum + 0xA0;
+ uint16 bReg = (uint16)chanNum + 0xB0;
+
+ /* Note is 1-based; _octaveTranspose shifts by whole octaves. */
+ int note = (int)ch->_note + (int)ch->_octaveTranspose - 1;
+ int octave = note / 12;
+ int semi = note % 12;
+ if (semi < 0) {
+ semi += 12; --octave;
+ }
+
+ /* F-number from table, with optional signed transpose offset. */
+ int16 fnum = (int16)SEMITONE_FREQ_TABLE[semi] + (int16)(int8)ch->_transpose;
+
+ write((uint8)aReg, (uint8)fnum);
+
+ uint8 bVal = _adlibPorts[bReg] & 0x20; /* preserve key-on bit */
+ bVal |= (uint8)((fnum >> 8) & 0x03); /* F-num bits 9:8 */
+ bVal |= (uint8)(((uint8)octave & 0x07) << 2); /* block/octave bits 4:2 */
+ write((uint8)bReg, bVal);
+}
+
+void ASound::writePitchBend() {
+ AdlibChannel *ch = _activeChannelPtr;
+ uint8 chanNum = _activeChannelNumber;
+ uint16 aReg = (uint16)chanNum + 0xA0;
+ uint16 bReg = (uint16)chanNum + 0xB0;
+
+ /* Reconstruct the 10-bit F-number from the port shadow. */
+ uint16 fnum = (uint16)_adlibPorts[aReg]
+ | ((uint16)(_adlibPorts[bReg] & 0x1F) << 8);
+
+ int16 bent = (int16)fnum + (int16)(int8)ch->_pitchBend;
+
+ write((uint8)aReg, (uint8)bent);
+
+ /* Preserve block + key-on, update F-num[9:8]. */
+ uint8 bVal = _adlibPorts[bReg] & 0x20;
+ bVal |= (uint8)((bent >> 8) & 0x03);
+ write((uint8)bReg, bVal);
+}
+
+void ASound::writeArpeggio() {
+ AdlibChannel *ch = _activeChannelPtr;
+ uint8 chanNum = _activeChannelNumber;
+ uint16 aReg = (uint16)chanNum + 0xA0;
+ uint16 bReg = (uint16)chanNum + 0xB0;
+
+ /* dl = _note + _octaveTranspose + _writeVolumePending - 1 */
+ int note = (int)ch->_note + (int)ch->_octaveTranspose
+ + (int)ch->_writeVolumePending - 1;
+ int octave = note / 12;
+ int semi = note % 12;
+ if (semi < 0) {
+ semi += 12; --octave;
+ }
+
+ uint16 freqEntry = SEMITONE_FREQ_TABLE[semi];
+ uint8 fnHigh = (uint8)((freqEntry >> 8) & 0x03);
+ uint8 block = (uint8)(octave & 0x07);
+ uint8 bVal = (uint8)((block << 2) | fnHigh) | (_adlibPorts[bReg] & 0x20);
+
+ write((uint8)aReg, (uint8)freqEntry);
+ write((uint8)bReg, bVal);
+}
+
+void ASound::updateOctave() {
+ uint8 chanNum = _activeChannelNumber;
+ uint16 bReg = (uint16)chanNum + 0xB0;
+ write((uint8)bReg, _adlibPorts[bReg] & 0xDF);
+}
+
+void ASound::noteOn() {
+ AdlibChannel *ch = _activeChannelPtr;
+ AdlibSample *smp = &_samples[ch->_sampleIndex];
+
+ writeVolume();
+
+ ch->_freqSweepCounter = smp->_freqSweepInit;
+
+ if (ch->_freqSweepCounter != 0) {
+ /* Sweep is pending; signal that sound is active but don't key on yet. */
+ signalSoundPlaying();
+ return;
+ }
+
+ /* No sweep - write frequency and set key-on immediately. */
+ writeFrequency();
+
+ uint8 chanNum = _activeChannelNumber;
+ uint16 bReg = (uint16)chanNum + 0xB0;
+ write((uint8)bReg, _adlibPorts[bReg] | 0x20);
+}
+
+void ASound::writeSampleRegs() {
+ AdlibSample *smp = _samplePtr;
+ uint16 base = _currentOpBase;
+
+ /* 0xBD: rhythm mode flags. */
+ uint8 bdVal = _adlibPorts[0xBD] & 0x3F;
+ if (RHYTHM_HI_HAT) bdVal |= 0x80;
+ if (RHYTHM_CYMBAL) bdVal |= 0x40;
+ write(0xBD, bdVal);
+
+ /* 0x08: note-select / rhythm enable. */
+ write(0x08, RHYTHM_ENABLE ? 0x40 : 0x00);
+
+ /* 0xC0+voice: feedback and algorithm.
+ * asm: feedback << 1, then alg==1 -> bit0=0, else -> bit0=1. */
+ write((uint8)(_activeChannelReg + 0xC0),
+ (uint8)((smp->_feedback << 1) | (smp->_alg == 1 ? 0 : 1)));
+
+ /* 0x60+base: attack / decay. */
+ write((uint8)(base + 0x60),
+ (uint8)(((smp->_attackRate & 0x0F) << 4) | (smp->_decayRate & 0x0F)));
+
+ /* 0x80+base: sustain level / release rate. */
+ write((uint8)(base + 0x80),
+ (uint8)(((smp->_sustainLevel & 0x0F) << 4) | (smp->_releaseRate & 0x0F)));
+
+ /* 0x20+base: AM/VIB/EGT/KSR/mult flags. */
+ uint8 amVal = smp->_freqMultiple & 0x0F;
+ if (smp->_egTyp == 1) amVal |= 0x20;
+ if (smp->_vib == 1) amVal |= 0x40;
+ if (smp->_ksr == 1) amVal |= 0x10;
+ if (smp->_ampMod == 1) amVal |= 0x80;
+ write((uint8)(base + 0x20), amVal);
+
+ /* 0xE0+base: waveform select. */
+ write((uint8)(base + 0xE0), (uint8)(smp->_waveformSelect & 0x03));
+}
+
+void ASound::loadSample() {
+ AdlibChannel *ch = _activeChannelPtr;
+ uint8 chanNum = _activeChannelNumber;
+
+ /* Phase 1: Silence modulator operator (slot 0). */
+ uint8 mSlot = VOICE_SLOTS[chanNum][0];
+ uint8 mRegOff = SLOT_TO_REG_OFFSET[mSlot];
+ write((uint8)(mRegOff + 0x80), 0xFF);
+ write((uint8)(mRegOff + 0x40), 63);
+
+ /* Phase 2: Silence carrier operator (slot 1). */
+ uint8 cSlot = VOICE_SLOTS[chanNum][1];
+ uint8 cRegOff = SLOT_TO_REG_OFFSET[cSlot];
+ write((uint8)(cRegOff + 0x80), 0xFF);
+ write((uint8)(cRegOff + 0x40), 63);
+
+ /* Phase 3: Write first sample (modulator) registers. */
+ _activeChannelReg = (uint16)chanNum;
+ _currentOpBase = mRegOff;
+ _samplePtr = &_samples[ch->_sampleIndex * 2];
+ writeSampleRegs();
+
+ /* Phase 4: Write second sample (carrier) registers. */
+ _currentOpBase = cRegOff;
+ _samplePtr = &_samples[ch->_sampleIndex * 2 + 1];
+ writeSampleRegs();
+
+ /* Phase 5: Write carrier TL (KSL | 0x3F for full attenuation initially). */
+ write((uint8)(cRegOff + 0x40),
+ (uint8)((_samplePtr->_scalingLevel << 6) | 0x3F));
+
+ /* Phase 6: Use the first sample again for the modulator TL and for
+ * sweep/frequency initialisation. */
+ AdlibSample *smp3 = &_samples[ch->_sampleIndex * 2];
+ uint16 mTLReg = (uint16)mRegOff + 0x40;
+
+ if (smp3->_alg == 0) {
+ write((uint8)mTLReg, (uint8)((smp3->_scalingLevel << 6) | 0x3F));
+ } else {
+ /* alg != 0: negate (totalLevel - 63) to get the TL value. */
+ uint8 tl = (uint8)(-(int8)(smp3->_totalLevel - 63));
+ write((uint8)mTLReg, (uint8)((smp3->_scalingLevel << 6) | (tl & 0x3F)));
+ }
+
+ /* Copy sweep/frequency parameters from the sample into the channel. */
+ ch->_freqSweepCounter = smp3->_freqSweepInit;
+ ch->_noiseFreqMask = smp3->_freqMask;
+ ch->_freqAccum = smp3->_freqBase;
+ ch->_freqStep = smp3->_outerLoopPtr;
+}
+
+void ASound::update() {
+ if (_isDisabled)
+ return;
+
+ (void)getRandomNumber();
+ ++_frameNumber2;
+
+ pollAllChannels();
+ tickCallback(); /* asound_updateCallback */
+ updateAllChannels();
+
+ _anySweepActive = 0;
+
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+ update1(i);
+
+ /* If no sweep is active, flag the result as "done". */
+ if (!_anySweepActive) {
+ if (_resultFlag != (int16)0xFFFF) {
+ _resultFlag = (int16)0xFFFF;
+ _pollResult = (int16)0xFFFF;
+ }
+ }
+}
+
+void ASound::update1(int channelIndex) {
+ /* asound_update1: called with cx = channelIndex+1 in the original loop.
+ * Accesses channel via the adlib_channels pointer table. */
+ AdlibChannel *ch = _channels[channelIndex];
+
+ if (ch->_freqSweepCounter == 0)
+ return;
+
+ _anySweepActive = 1;
+ ch->_freqAccum += ch->_freqStep;
+ ch->_freqSweepCounter--;
+
+ if (ch->_freqSweepCounter != 0)
+ return;
+
+ /* Sweep finished: write zero frequency to silence this OPL voice. */
+ uint8 voice = (uint8)channelIndex;
+ write((uint8)(voice + 0xA0), 0x00);
+ write((uint8)(voice + 0xB0), 0x00);
+}
+
+void ASound::setFrequency(uint8 voice, uint16 freq) {
+ /* asound_setFrequency: writes A0+v and B0+v (with key-on). */
+ write((uint8)(voice + 0xA0), (uint8)(freq & 0xFF));
+ uint8 bVal = (_adlibPorts[voice + 0xB0] & 0x20)
+ | (uint8)((freq >> 8) & 0x03)
+ | 0x20;
+ write((uint8)(voice + 0xB0), bVal);
+}
+
+void ASound::noise_inner(int channelIndex) {
+ /* adlib_noise_inner */
+ AdlibChannel *ch = _channels[channelIndex];
+
+ if (ch->_freqSweepCounter == 0)
+ return;
+
+ uint16 rnd = getRandomNumber();
+ uint16 freq = (rnd & ch->_noiseFreqMask) + ch->_freqAccum;
+ setFrequency((uint8)channelIndex, freq);
+}
+
+void ASound::updateAllChannels() {
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+ _channels[i]->processChannelFade();
+}
+
+void ASound::findFreeChannel(byte *soundData) {
+ _findChannelMode = 0;
+
+ /* Scan channels 0-6 for an empty slot. */
+ for (int ch = 0; ch < 7; ++ch) {
+ if (_channels[ch]->_activeCount == 0) {
+ _channels[ch]->load(soundData);
+ return;
+ }
+ }
+
+ /* _findChannelMode==0: fall through to full search. */
+ findFreeChannelFull(soundData);
+}
+
+void ASound::findFreeChannelFull(byte *soundData) {
+ _findChannelMode = 2;
+
+ /* Scan channels 7-8 for an empty slot. */
+ if (_channel7._activeCount == 0) {
+ _channel7.load(soundData); return;
+ }
+ if (_channel8._activeCount == 0) {
+ _channel8.load(soundData); return;
+ }
+
+ /* Scan ch8 then ch7 for pending-stop (pre-emptible). */
+ if (_channel8._pendingStop == 0xFF) {
+ _channel8.load(soundData); return;
+ }
+ if (_channel7._pendingStop == 0xFF) {
+ _channel7.load(soundData); return;
+ }
+
+ /* If _findChannelMode allows it, scan ch6..ch0 for pending-stop. */
+ if (_findChannelMode == 0) {
+ if (_channel6._pendingStop == 0xFF) {
+ _channel6.load(soundData); return;
+ }
+ if (_channel5._pendingStop == 0xFF) {
+ _channel5.load(soundData); return;
+ }
+ if (_channel4._pendingStop == 0xFF) {
+ _channel4.load(soundData); return;
+ }
+ if (_channel3._pendingStop == 0xFF) {
+ _channel3.load(soundData); return;
+ }
+ if (_channel2._pendingStop == 0xFF) {
+ _channel2.load(soundData); return;
+ }
+ if (_channel1._pendingStop == 0xFF) {
+ _channel1.load(soundData); return;
+ }
+ if (_channel0._pendingStop == 0xFF) {
+ _channel0.load(soundData); return;
+ }
+ }
+}
+
+int ASound::isMusicChannelsActive() {
+ _musicOnlyFlag = 1;
+ uint8 result = 0;
+ for (int i = 0; i <= 6; ++i)
+ result |= _channels[i]->_activeCount;
+ return result;
+}
+
+int ASound::isAnyChannelActive() {
+ _musicOnlyFlag = 0;
+ uint8 result = 0;
+ for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+ result |= _channels[i]->_activeCount;
+ return result;
+}
+
+void ASound::pollAllChannels() {
+ _activeChannelNumber = 0;
+ for (int ch = 0; ch < ADLIB_CHANNEL_COUNT; ++ch) {
+ _activeChannelPtr = _channels[ch];
+ pollActiveChannel();
+ /* Note: _activeChannelNumber is incremented at the end of
+ * pollActiveChannel, not here. */
+ }
+}
+
+bool ASound::isSoundActive(byte *ptr) const {
+ for (int ch = 0; ch < ADLIB_CHANNEL_COUNT; ++ch) {
+ if (_channels[ch]->_activeCount && _channels[ch]->_soundData == ptr)
+ return true;
+ }
+ return false;
+}
+
+uint16 ASound::readWord_impl() {
+ /* asound_readWord: advance pSrc past the low byte, read lo then hi. */
+ uint16 lo = *++pSrc;
+ uint16 hi = *++pSrc;
+ return lo | (hi << 8);
+}
+
+void ASound::pollActiveChannel() {
+ AdlibChannel *ch = _activeChannelPtr;
+
+ if (ch->_activeCount == 0) {
+ ++_activeChannelNumber;
+ return;
+ }
+
+ /* byte_16A0A: volume-dirty flag. Cleared here, set by various opcodes
+ * and by the fade/vibrato sections; causes writeVolume at the end. */
+ bool volDirty = false;
+
+ /* ---- Key-on delay countdown ---- */
+ if (ch->_keyOnDelay != 0) {
+ ch->_keyOnDelay--;
+ ch = _activeChannelPtr;
+ if (ch->_keyOnDelay == 0)
+ updateOctave();
+ }
+
+ /* ---- Duration countdown ---- */
+ ch = _activeChannelPtr;
+ ch->_activeCount--;
+ ch = _activeChannelPtr;
+ if (ch->_activeCount != 0)
+ goto post_keyon;
+
+ /* ================================================================
+ * Command-dispatch loop.
+ * Re-entered (via goto dispatch) after each command that does not
+ * consume a duration tick. Falls through to the note-event path
+ * when a note byte is encountered.
+ * ================================================================ */
+dispatch:
+ {
+ ch = _activeChannelPtr;
+ pSrc = ch->_pSrc;
+
+ uint8 b = *pSrc;
+
+ if (!(b & 0x80))
+ goto note_event;
+
+ /* Decode group (si) and sub-opcode (di). */
+ uint8 si = b & 0x70;
+ uint8 di = b & 0x0F;
+
+ if (si == 0x00) {
+ /* ============================================================
+ * opcodes1 (group 0, 0x8_)
+ * Called with: pSrc pointing at the command byte.
+ * On entry, opcodes1 does: inc pSrc (skip past command byte).
+ * On exit, sets _pSrc = pSrc + 1 (def_10CDC).
+ * ============================================================ */
+ pSrc++; /* skip command byte */
+
+ switch (di) {
+
+ case 0x0: /* set sampleIndex, call loadSample */
+ ch->_sampleIndex = *pSrc;
+ loadSample();
+ volDirty = true;
+ /* def_10CDC: _pSrc = pSrc + 1 */
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x1: /* set velocity */
+ {
+ uint8 vel = *pSrc;
+ if (ch->_pendingStop != 0) {
+ /* If current velocity (signed) > new vel (signed), clamp down. */
+ if ((int16)(int8)ch->_velocity > (int16)(int8)vel)
+ ch->_velocity = vel;
+ } else {
+ ch->_velocity = vel;
+ }
+ volDirty = true;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+ }
+
+ case 0x2: /* set volume */
+ {
+ uint8 vol = *pSrc;
+ if (ch->_pendingStop != 0) {
+ /* Don't raise volume above current when fading out. */
+ uint16 curVol = (uint16)ch->_volume;
+ if (curVol <= (uint16)vol)
+ goto op2_set_vol;
+ /* else fall through and skip the set */
+ volDirty = true;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+ }
+op2_set_vol:
+ ch->_volume = vol;
+ volDirty = true;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+ }
+
+ case 0x3: /* set patchAttenuation */
+ ch->_patchAttenuation = *pSrc;
+ volDirty = true; /* opcodes1 case 3 jumps to loc_10B56 -> byte_16A0A=1 */
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x4: /* set fade: fadePeriodReload, volumeFadeStep */
+ if (ch->_pendingStop != 0) {
+ /* Skip this opcode entirely when pending-stop is set. */
+ ch->_pSrc = pSrc + 2;
+ goto dispatch;
+ }
+ ch->_fadePeriodReload = *pSrc++;
+ ch->_volumeFadeStep = *pSrc;
+ ch->_fadePeriodCounter = 1;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x5: /* set vibrato: vibPeriodReload, vibPeriodCounter=reload, vibratoDepth, vibratoMode */
+ ch->_vibPeriodReload = *pSrc++;
+ ch->_vibPeriodCounter = ch->_vibPeriodReload;
+ ch->_vibratoDepth = *pSrc++;
+ ch->_vibratoMode = *pSrc;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x6: /* set transpose */
+ ch->_transpose = *pSrc;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x7: /* set octaveTranspose */
+ ch->_octaveTranspose = *pSrc;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x8: /* set field_11 (_writeVolumePending) */
+ ch->_writeVolumePending = *pSrc;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0x9: /* set arpeggio: field_12->field_C, pitchBend, field_13 */
+ ch->_arpPeriodReload = *pSrc++;
+ ch->_arpPeriodCounter = ch->_arpPeriodReload; /* field_C = field_12 */
+ ch->_pitchBend = *pSrc++;
+ ch->_arpCounterReload = *pSrc;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0xA: /* skip forward by N+3 bytes (from the command byte) */
+ {
+ uint8 skip = *pSrc;
+ /* pSrc currently points at the byte after the command byte.
+ * Advance _pSrc by (skip + 3) from the *command* byte position,
+ * which is pSrc-1. So _pSrc += skip + 3 from original _pSrc,
+ * which was command byte. But def_10CDC does _pSrc = pSrc+1
+ * giving +2 past command. Then skip+3 total from command: */
+ /* asm: si = skip; ax = si + 3; add pSrc, ax; then def_10CDC
+ * does pSrc+1. So net advance from the command byte =
+ * skip + 3 + 1 = skip + 4. But pSrc already points one past
+ * command byte, so _pSrc = pSrc + skip + 3. */
+ ch->_pSrc = pSrc + (uint16)skip + 3;
+ goto dispatch;
+ }
+
+ case 0xB: /* set durationOverride, clear noteOffset */
+ ch->_durationOverride = *pSrc;
+ ch->_noteOffset = 0;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ case 0xC: /* set noteOffset, clear durationOverride */
+ ch->_noteOffset = *pSrc;
+ ch->_durationOverride = 0;
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+
+ default:
+ /* Unknown sub-opcode: advance past the single operand byte. */
+ ch->_pSrc = pSrc + 1;
+ goto dispatch;
+ }
+ }
+
+ if (si == 0x10) {
+ /* ============================================================
+ * opcodes2 (group 1, 0x9_)
+ * Loop control, restart, branch / call.
+ * On exit: either sets _pSrc directly or falls to def_10EAC
+ * (which just returns without touching _pSrc - the handlers
+ * themselves are responsible for updating _pSrc).
+ * ============================================================ */
+ switch (di) {
+
+ case 0x0: /* inner loop */
+ {
+ ch = _activeChannelPtr;
+ if (ch->_innerLoopCount == 0) {
+ pSrc++; /* advance to count byte */
+ uint8 cnt = *pSrc;
+ if (cnt == 0) {
+ ch->_pSrc += 2;
+ ch = _activeChannelPtr;
+ ch->_innerLoopPtr = ch->_pSrc;
+ ch->_innerLoopCount = 0;
+ goto dispatch;
+ }
+ ch->_innerLoopCount = (uint16)cnt;
+ /* Jump to innerLoopPtr (loc_10D5A). */
+ ch->_pSrc = ch->_innerLoopPtr;
+ goto dispatch;
+ }
+ /* Count is non-zero: decrement. */
+ ch->_innerLoopCount--;
+ ch = _activeChannelPtr;
+ if (ch->_innerLoopCount == 0) {
+ /* Loop done: advance past the two-byte opcode. */
+ ch->_pSrc += 2;
+ ch = _activeChannelPtr;
+ ch->_innerLoopPtr = ch->_pSrc;
+ goto dispatch;
+ }
+ /* Still looping: jump back. */
+ ch->_pSrc = ch->_innerLoopPtr;
+ goto dispatch;
+ }
+
+ case 0x1: /* outer loop */
+ {
+ ch = _activeChannelPtr;
+ if (ch->_outerLoopCount == 0) {
+ pSrc++;
+ uint8 cnt = *pSrc;
+ if (cnt == 0) {
+ ch->_pSrc += 2;
+ ch = _activeChannelPtr;
+ ch->_outerLoopPtr = ch->_pSrc;
+ ch->_innerLoopPtr = ch->_pSrc;
+ ch->_innerLoopCount = 0;
+ ch->_outerLoopCount = 0;
+ goto dispatch;
+ }
+ ch->_outerLoopCount = (uint16)cnt;
+ /* Jump to outerLoopPtr. */
+ ch->_pSrc = ch->_outerLoopPtr;
+ ch = _activeChannelPtr;
+ ch->_innerLoopPtr = ch->_outerLoopPtr;
+ goto dispatch;
+ }
+ /* Count non-zero: decrement. */
+ ch->_outerLoopCount--;
+ ch = _activeChannelPtr;
+ if (ch->_outerLoopCount == 0) {
+ ch->_pSrc += 2;
+ ch = _activeChannelPtr;
+ ch->_outerLoopPtr = ch->_pSrc;
+ ch->_innerLoopPtr = ch->_pSrc;
+ goto dispatch;
+ }
+ ch->_pSrc = ch->_outerLoopPtr;
+ ch = _activeChannelPtr;
+ ch->_innerLoopPtr = ch->_outerLoopPtr;
+ goto dispatch;
+ }
+
+ case 0x2: /* restart: reset all loop pointers to _soundData */
+ {
+ ch = _activeChannelPtr;
+ uint16 sd = (uint16)(uintptr_t)ch->_soundData;
+ ch->_loopStartPtr = ch->_soundData;
+ ch->_pSrc = ch->_soundData;
+ ch->_innerLoopPtr = ch->_soundData;
+ ch->_outerLoopPtr = ch->_soundData;
+ (void)sd;
+ goto dispatch;
+ }
+
+ case 0x3: /* set sound-data pointer (abs word offset), reset all loops */
+ {
+ byte *ptr = &_soundData[readWord_impl()];
+ ch = _activeChannelPtr;
+ ch->_loopStartPtr = ptr;
+ ch->_pSrc = ptr;
+ ch->_innerLoopPtr = ptr;
+ ch->_outerLoopPtr = ptr;
+ ch->_soundData = ptr;
+ goto dispatch;
+ }
+
+ case 0x4: /* branch unconditional */
+ {
+ byte *dest = &_soundData[readWord_impl()];
+ ch = _activeChannelPtr;
+ ch->_pSrc = dest;
+ goto dispatch;
+ }
+
+ case 0x5: /* branch with return-address save */
+ {
+ byte *dest = &_soundData[readWord_impl()];
+ ch = _activeChannelPtr;
+ ch->_branchTargetPtr = ch->_pSrc + 3;
+ ch->_pSrc = dest;
+ goto dispatch;
+ }
+
+ case 0x6: /* return from branch */
+ {
+ ch = _activeChannelPtr;
+ if (ch->_branchTargetPtr != nullptr) {
+ ch->_pSrc = ch->_branchTargetPtr;
+ ch->_branchTargetPtr = nullptr;
+ } else {
+ ch->_pSrc++;
+ }
+ goto dispatch;
+ }
+
+ default:
+ /* Unknown sub-opcode: no _pSrc advance (def_10EAC). */
+ goto dispatch;
+ }
+ }
+
+ if (si == 0x20) {
+ /* ============================================================
+ * opcodes3 (group 2, 0xA_)
+ * Tempo/callback globals, random table operations,
+ * indirect-table operations, call-by-address, nullsub.
+ *
+ * opcodes3 receives di (sub-opcode) but NOT the already-
+ * incremented pSrc; each case increments pSrc itself as needed.
+ * ============================================================ */
+ switch (di) {
+
+ case 0x0: /* random pick from packed table -> write to stream */
+ {
+ /* Format: [cmd] [tblSize] [entry0..N-1] [targetSlot]
+ * pSrc points at the command byte on entry. */
+ pSrc++;
+ uint8 tblSize = *pSrc;
+ pSrc++; /* now at entry[0] */
+ (void)getRandomNumber();
+ uint16 rnd = _randomSeed & 0x7FFF;
+ int16 idx = (int16)((int16)rnd % (int16)(uint16)tblSize);
+ uint8 chosen = *(pSrc + idx);
+ uint8 target = *(pSrc + tblSize);
+ *(pSrc + (uint16)target + 1) = chosen;
+ ch = _activeChannelPtr;
+ ch->_pSrc += (uint16)tblSize + 3;
+ goto dispatch;
+ }
+
+ case 0x1: /* random range [lo..hi] -> write to stream */
+ {
+ /* Format: [cmd] [lo] [hi] [targetSlot] ... */
+ pSrc++;
+ uint8 lo = *pSrc++;
+ uint8 hi = *pSrc++;
+ uint16 range = (uint16)(hi - lo) + 1;
+ /* pSrc now at targetSlot byte. */
+ (void)getRandomNumber();
+ uint16 rnd = _randomSeed & 0x7FFF;
+ uint8 res = (uint8)((int16)rnd % (int16)range) + lo;
+ uint8 slot = *pSrc;
+ *(pSrc + (uint16)slot + 1) = res;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 4; /* command+lo+hi+slot = 4 bytes consumed */
+ goto dispatch;
+ }
+
+ case 0x2: /* indirect table read from scriptVar -> write to stream */
+ {
+ /* Format: [cmd] [varIdx] [tableSlot] [table...] */
+ pSrc++;
+ uint8 varIdx = *pSrc++;
+ uint8 tableSlot = *pSrc++;
+ /* pSrc now points at the inline table. */
+ uint8 idxVal = _scriptVars[varIdx];
+ uint8 chosen = *(pSrc + idxVal);
+ uint8 target = *(pSrc + tableSlot);
+ *(pSrc + (uint16)target + 1) = chosen;
+ ch = _activeChannelPtr;
+ ch->_pSrc += (uint16)tableSlot + 4;
+ goto dispatch;
+ }
+
+ case 0x3: /* call function by address (near call in original) */
+ {
+ uint16 fnOffset = readWord_impl();
+ callFunction(fnOffset);
+ ch = _activeChannelPtr;
+ ch->_pSrc += 3;
+ goto dispatch;
+ }
+
+ case 0x4: /* nullsub_1 with byte arg */
+ {
+ /* "call near ptr aAsoundDriverAn+33h" - the target is a no-op. */
+ pSrc++;
+ /* (void)*pSrc; */
+ ch = _activeChannelPtr;
+ ch->_pSrc += 2;
+ goto dispatch;
+ }
+
+ case 0x5: /* advance _pSrc by 4 (from command byte) */
+ {
+ /* loc_10F55 is shared with case 1's epilogue: _pSrc += 4. */
+ ch = _activeChannelPtr;
+ ch->_pSrc += 4;
+ goto dispatch;
+ }
+
+ case 0x6: /* set word_124F2 (_tempoFineStep) */
+ {
+ pSrc++;
+ uint8 val = *pSrc;
+ _tempoFineStep = (uint16)val;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 2;
+ goto dispatch;
+ }
+
+ case 0x7: /* set word_124F0 (_tempoCoarseStep) */
+ {
+ pSrc++;
+ uint8 val = *pSrc;
+ _tempoCoarseStep = (uint16)val;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 2;
+ goto dispatch;
+ }
+
+ case 0x8: /* set word_124EE (_tempoPeriod), enable tick callback */
+ {
+ uint16 period = readWord_impl();
+ _tempoPeriod = period;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 3;
+ _tempoEnabled = 1;
+ _tempoTickCounter = 1;
+ goto dispatch;
+ }
+
+ case 0x9: /* set word_124F4 (_tempoShift) */
+ {
+ pSrc++;
+ uint8 val = *pSrc;
+ _tempoShift = (uint16)val;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 2;
+ goto dispatch;
+ }
+
+ default:
+ goto dispatch;
+ }
+ }
+
+ if (si == 0x30) {
+ /* ============================================================
+ * opcodes4 (group 3, 0xB_)
+ * Script-variable load (imm or var), store to stream, inc, dec.
+ * Each case increments pSrc itself, then sets _pSrc.
+ *
+ * Switch on di (0-4); di >= 5 falls through to return.
+ * ============================================================ */
+ switch (di) {
+
+ case 0x0: /* var[dst] = imm */
+ {
+ pSrc++;
+ uint8 dst = *pSrc++;
+ uint8 imm = *pSrc;
+ _scriptVars[dst] = imm;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 3;
+ goto dispatch;
+ }
+
+ case 0x1: /* var[dst] = var[src] */
+ {
+ pSrc++;
+ uint8 dst = *pSrc++;
+ uint8 src = *pSrc;
+ _scriptVars[dst] = _scriptVars[src];
+ ch = _activeChannelPtr;
+ ch->_pSrc += 3;
+ goto dispatch;
+ }
+
+ case 0x2: /* stream[tableSlot+1] = var[src] */
+ {
+ /* asm: [bx+di+1] = cl, where bx=pSrc, di=tableSlot. */
+ pSrc++;
+ uint8 src = *pSrc++;
+ uint8 tableSlot = *pSrc;
+ *(pSrc + (uint16)tableSlot + 1) = _scriptVars[src];
+ ch = _activeChannelPtr;
+ ch->_pSrc += 3;
+ goto dispatch;
+ }
+
+ case 0x3: /* var[dst]++ */
+ {
+ pSrc++;
+ uint8 dst = *pSrc;
+ _scriptVars[dst]++;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 2;
+ goto dispatch;
+ }
+
+ case 0x4: /* var[dst]-- */
+ {
+ pSrc++;
+ uint8 dst = *pSrc;
+ _scriptVars[dst]--;
+ ch = _activeChannelPtr;
+ ch->_pSrc += 2;
+ goto dispatch;
+ }
+
+ default:
+ goto dispatch;
+ }
+ }
+
+ if (si == 0x40) {
+ /* ============================================================
+ * opcodes5 (group 4, 0xC_)
+ * Script-variable ALU: add/sub/mul/div/rem/and/or/xor.
+ *
+ * Entry: inc pSrc, read dst (si), inc pSrc, read src (di).
+ * Then _pSrc += 3 (command + dst + src).
+ * 16 sub-opcodes (0-15).
+ *
+ * For sub-opcodes 0,2,4: src is an immediate (di from the byte).
+ * For sub-opcodes 1,3,5: src is _scriptVars[di].
+ * For 6-9: signed or unsigned division/remainder.
+ * For 10-15: bitwise AND/OR/XOR, imm or var.
+ * ============================================================ */
+ pSrc++;
+ uint8 dst = *pSrc++;
+ uint8 src = *pSrc; /* immediate value or var index */
+ ch = _activeChannelPtr;
+ ch->_pSrc += 3;
+
+ switch (di) {
+ case 0x0: _scriptVars[dst] += src; break;
+ case 0x1: _scriptVars[dst] += _scriptVars[src]; break;
+ case 0x2: _scriptVars[dst] -= src; break;
+ case 0x3: _scriptVars[dst] -= _scriptVars[src]; break;
+ case 0x4: /* mul ax=di, mul [si] -> al stored */
+ _scriptVars[dst] = (uint8)((uint16)src * (uint16)_scriptVars[dst]); break;
+ case 0x5:
+ _scriptVars[dst] = (uint8)((uint16)_scriptVars[src] * (uint16)_scriptVars[dst]); break;
+ case 0x6: /* signed idiv by imm -> quotient in al */
+ _scriptVars[dst] = (uint8)((int8)_scriptVars[dst] / (int8)src); break;
+ case 0x7: /* unsigned div by var -> quotient in al */
+ _scriptVars[dst] = _scriptVars[dst] / _scriptVars[src]; break;
+ case 0x8: /* signed idiv by imm -> remainder in dl (stored to dst) */
+ _scriptVars[dst] = (uint8)((int8)_scriptVars[dst] % (int8)src); break;
+ case 0x9: /* unsigned div by var -> remainder in ah (stored to dst) */
+ _scriptVars[dst] = _scriptVars[dst] % _scriptVars[src]; break;
+ case 0xA: _scriptVars[dst] &= src; break;
+ case 0xB: _scriptVars[dst] &= _scriptVars[src]; break;
+ case 0xC: _scriptVars[dst] |= src; break;
+ case 0xD: _scriptVars[dst] |= _scriptVars[src]; break;
+ case 0xE: _scriptVars[dst] ^= src; break;
+ case 0xF: _scriptVars[dst] ^= _scriptVars[src]; break;
+ default: break;
+ }
+ goto dispatch;
+ }
+
+ if (si == 0x50) {
+ /* ============================================================
+ * opcodes6 (group 5, 0xD_)
+ * Conditional branch: compare var[si] vs immediate (di as imm byte).
+ *
+ * Format: [cmd] [varIdx] [imm] [addrLo] [addrHi]
+ * Entry: inc pSrc, read varIdx (si reg), inc pSrc, read imm (di reg).
+ * Comparison: var[varIdx] vs imm.
+ *
+ * If condition met: call asound_readWord (reads addrLo/addrHi
+ * from current pSrc position), set _pSrc = &_soundData[addr].
+ * If not met: _pSrc += 5 (command+varIdx+imm+addr word = 5 bytes).
+ *
+ * var_2 is used as a flag: 0 = branch taken, 1 = not taken.
+ * (Confusingly, var_2 = 1 means "taken" in opcodes7 but
+ * 0 means "taken" in opcodes6 - see def_11292 / def_11360 difference.)
+ *
+ * opcodes6: var_2=1 -> taken (call readWord, set pSrc),
+ * var_2=0 -> not taken (_pSrc += 5).
+ * ============================================================ */
+ pSrc++;
+ uint8 varIdx = *pSrc++;
+ uint8 imm = *pSrc;
+ uint8 vval = _scriptVars[varIdx];
+ bool taken = false;
+
+ switch (di) {
+ case 0x0: taken = ((uint16)vval == (uint16)imm); break; /* == */
+ case 0x1: taken = ((uint16)vval != (uint16)imm); break; /* != */
+ case 0x2: taken = ((int16)(uint16)vval >= (int16)(uint16)imm); break; /* jge -> taken if >= */
+ case 0x3: taken = ((int16)(uint16)vval <= (int16)(uint16)imm); break; /* jle -> taken if <= */
+ case 0x4: taken = (_scriptVars[varIdx] != _scriptVars[imm]); break; /* jnz after cmp [di],[si] */
+ case 0x5: taken = (_scriptVars[varIdx] == _scriptVars[imm]); break; /* jz after cmp [di],[si] */
+ case 0x6: taken = (_scriptVars[imm] <= _scriptVars[varIdx]); break; /* jbe -> [di]<=[si] taken */
+ case 0x7: taken = (_scriptVars[imm] >= _scriptVars[varIdx]); break; /* jnb -> [di]>=[si] taken */
+ default: break;
+ }
+
+ ch = _activeChannelPtr;
+ if (taken) {
+ ch->_pSrc = &_soundData[readWord_impl()];
+ } else {
+ ch->_pSrc += 5;
+ }
+ goto dispatch;
+ }
+
+ if (si == 0x60) {
+ /* ============================================================
+ * opcodes7 (group 6, 0xE_)
+ * Conditional branch: compare var[si] vs var[di] (both scriptVars).
+ *
+ * Format: [cmd] [varIdxA] [varIdxB] [addrLo] [addrHi]
+ * Entry: inc pSrc, read varIdxA (si), inc pSrc, read varIdxB (di).
+ * var_2 = 0 initially.
+ *
+ * If condition met: var_2 = 1 -> branch taken:
+ * _pSrc = pSrc + 5; _branchTargetPtr = pSrc + 5;
+ * call readWord -> _pSrc = &_soundData[addr].
+ * If not met (var_2 = 0): _pSrc += 5 (skip condition + word).
+ *
+ * Note: unlike opcodes6, opcodes7 DOES save a branch-target
+ * (_branchTargetPtr = _pSrc + 5) on the taken path.
+ * ============================================================ */
+ pSrc++;
+ uint8 idxA = *pSrc++;
+ uint8 idxB = *pSrc;
+ uint8 va = _scriptVars[idxA];
+ uint8 vb = (uint8)idxB; /* di byte used as an immediate in some cases... */
+
+ /* Actually in opcodes7 the layout is var vs var:
+ * si = varIdxA, di = varIdxB (both are variable indices). */
+ vb = _scriptVars[idxB];
+
+ bool taken = false;
+ switch (di & 0x07) {
+ case 0x0: taken = ((uint16)va != (uint16)vb); break; /* jnz after cmp ax,di (case 0: jz -> NOT taken if ==; var_2 stays 0; but then def path: var_2==0 -> skip. Wait - let me re-read.) */
+ /* Re-reading loc_112F0: cmp ax,di; jz loc_11352 (-> var_2=1=taken).
+ * So case 0: taken = (va == vb). */
+ default: break;
+ }
+
+ /* Actually re-reading carefully:
+ * case 0 (loc_112F0): cmp ax,di; jz -> loc_11352 (var_2=1, taken)
+ * else -> loc_112FA (ax=0, var_2=0, not taken)
+ * case 1 (loc_11302): cmp ax,di; jz -> loc_1130A -> (jz loc_112FA, not taken)
+ * else -> loc_11284 (var_2=1, taken)
+ * -> case 1: taken = (va != vb)
+ * case 2 (loc_1130E): jge -> loc_112FA (not taken); else loc_11284 (taken)
+ * -> taken = (va < vb) (signed)
+ * case 3 (loc_1131A): jle -> not taken; else taken
+ * -> taken = (va > vb) (signed)
+ * case 4 (loc_11326): cmp [di],al; jnz -> loc_112FA (not taken); else loc_11284 (taken)
+ * -> taken = (_scriptVars[idxB] == va) (already same as case 0 with vars swapped)
+ * case 5 (loc_11332): cmp [di],al; jz->loc_1130A (not taken if ==, taken if !=)
+ * -> taken = (_scriptVars[idxB] != va)
+ * case 6 (loc_1133C): jbe -> not taken; else taken -> taken = (_scriptVars[idxB] > va)
+ * case 7 (loc_11348): jnb -> not taken; else taken -> taken = (_scriptVars[idxB] < va)
+ */
+ switch (di) {
+ case 0x0: taken = (va == vb); break;
+ case 0x1: taken = (va != vb); break;
+ case 0x2: taken = ((int8)va < (int8)vb); break;
+ case 0x3: taken = ((int8)va > (int8)vb); break;
+ case 0x4: taken = (vb == va); break;
+ case 0x5: taken = (vb != va); break;
+ case 0x6: taken = (vb > va); break;
+ case 0x7: taken = (vb < va); break;
+ default: break;
+ }
+
+ ch = _activeChannelPtr;
+ if (taken) {
+ /* Save return target (pSrc+5 from the command byte). */
+ ch->_branchTargetPtr = ch->_pSrc + 5;
+ ch->_pSrc = &_soundData[readWord_impl()];
+ } else {
+ ch->_pSrc += 5;
+ }
+ goto dispatch;
+ }
+
+ /* Unknown group: skip. */
+ goto dispatch;
+ }
+
+note_event:
+ {
+ /* ---- Note event: [note][duration] ---- */
+ ch = _activeChannelPtr;
+ pSrc = ch->_pSrc;
+ ch->_note = *pSrc;
+ pSrc++;
+ ch->_activeCount = *pSrc;
+ pSrc++;
+ ch->_pSrc += 2;
+
+ ch = _activeChannelPtr;
+ if (ch->_note == 0 || ch->_activeCount == 0) {
+ updateOctave();
+ goto post_keyon;
+ }
+
+ /* ---- Key-on ---- */
+ ch = _activeChannelPtr;
+ if (ch->_durationOverride != 0)
+ ch->_keyOnDelay = ch->_durationOverride;
+ else
+ ch->_keyOnDelay = ch->_activeCount - ch->_noteOffset;
+ noteOn();
+ }
+
+post_keyon:
+ {
+ ch = _activeChannelPtr;
+
+ /* ---- Arpeggio counter (field_13 / _arpCounterReload) ---- */
+ if (ch->_arpCounterReload != 0) {
+ /* Decrement the arpeggio period counter (field_C / _arpPeriodCounter). */
+ ch->_arpPeriodCounter--;
+ ch = _activeChannelPtr;
+ if (ch->_arpPeriodCounter == 0) {
+ /* Reload from field_12 (_arpPeriodReload). */
+ ch->_arpPeriodCounter = ch->_arpPeriodReload;
+ /* Call sub_117E8 (writeArpeggio - writes the arpeggio frequency). */
+ writeArpeggio();
+ }
+ ch = _activeChannelPtr;
+ /* Decrement the repeat counter unless it is the infinite sentinel 0xFF. */
+ if (ch->_arpCounterReload != 0xFF)
+ ch->_arpCounterReload--;
+ }
+
+ /* ---- Write-volume pending (field_11 / _writeVolumePending) ---- */
+ ch = _activeChannelPtr;
+ if (ch->_writeVolumePending != 0) {
+ /* sub_11856 was already called by writeArpeggio above (or this is
+ * a standalone field_11 set via opcode 8). Clear the flag. */
+ writeArpeggio();
+ ch = _activeChannelPtr;
+ ch->_writeVolumePending = 0;
+ }
+
+ /* ---- Fade / volume step ---- */
+ ch = _activeChannelPtr;
+ if (ch->_fadePeriodCounter != 0) {
+ ch->_fadePeriodCounter--;
+ ch = _activeChannelPtr;
+ if (ch->_fadePeriodCounter == 0) {
+ ch->_fadePeriodCounter = ch->_fadePeriodReload;
+ ch = _activeChannelPtr;
+ if (ch->_volumeFadeStep != 0) {
+ if (ch->_pendingStop != 0) {
+ /* Pending-stop fade: step velocity and volume down. */
+ if (ch->_velocity > 0)
+ ch->_velocity += ch->_volumeFadeStep;
+ ch = _activeChannelPtr;
+ if (ch->_volume != 0)
+ ch->_volume += ch->_volumeFadeStep;
+ } else {
+ /* Normal fade: step velocity, clamp to [0, 0x7F]. */
+ if ((int8)ch->_volumeFadeStep > 0) {
+ ch->_velocity += ch->_volumeFadeStep;
+ ch = _activeChannelPtr;
+ if ((uint16)ch->_velocity > 0x7F)
+ ch->_velocity = 0x7F;
+ } else {
+ ch->_velocity += ch->_volumeFadeStep;
+ ch = _activeChannelPtr;
+ if ((int8)ch->_velocity < 0)
+ ch->_velocity = 0;
+ }
+ }
+ volDirty = true; /* byte_16A0A = 1 */
+ }
+ }
+ }
+
+ /* ---- Vibrato ---- */
+ ch = _activeChannelPtr;
+ if (ch->_vibPeriodCounter != 0) {
+ ch->_vibPeriodCounter--;
+ ch = _activeChannelPtr;
+ if (ch->_vibPeriodCounter == 0) {
+ ch->_vibPeriodCounter = ch->_vibPeriodReload;
+ ch = _activeChannelPtr;
+
+ if ((int8)ch->_vibratoDepth > 0) {
+ /* Positive vibrato: ceiling at 0x7F. */
+ uint8 sum = ch->_vibratoDepth + ch->_patchAttenuation;
+ if (sum > 0x7F) {
+ if (ch->_vibratoMode == 0xFF) {
+ /* One-shot: clamp and stop. */
+ ch->_vibratoDepth = 0;
+ ch->_patchAttenuation = 0x7F;
+ } else if (sum == 0x80 && ch->_vibratoDepth != 1) {
+ /* Soft bounce at 0x80: patchAtten = 0x7F - vibratoDepth. */
+ ch->_patchAttenuation = 0x7F - ch->_vibratoDepth;
+ } else {
+ ch->_vibratoDepth = (uint8)(-(int8)ch->_vibratoDepth);
+ }
+ }
+ ch = _activeChannelPtr;
+ ch->_patchAttenuation += ch->_vibratoDepth;
+ volDirty = true;
+
+ } else if ((int8)ch->_vibratoDepth < 0) {
+ /* Negative vibrato: floor at 0. */
+ uint8 sum = ch->_vibratoDepth + ch->_patchAttenuation;
+ /* Note: sum is uint8 wrapping, but the asm uses "or al,al; jge"
+ * treating the result as signed. */
+ if ((int8)sum < 0) {
+ if (ch->_vibratoMode == 0xFF) {
+ ch->_vibratoDepth = 0;
+ ch->_patchAttenuation = 0;
+ } else if (sum == 0xFF && ch->_vibratoDepth != 0xFF) {
+ /* Soft bounce: patchAtten = neg(vibratoDepth). */
+ ch->_patchAttenuation = (uint8)(-(int8)ch->_vibratoDepth);
+ } else {
+ ch->_vibratoDepth = (uint8)(-(int8)ch->_vibratoDepth);
+ }
+ }
+ ch = _activeChannelPtr;
+ ch->_patchAttenuation += ch->_vibratoDepth;
+ volDirty = true;
+ }
+ /* vibratoDepth == 0: nothing to do. */
+ }
+ }
+
+ if (volDirty)
+ writeVolume();
+ }
+
+ ++_activeChannelNumber;
+}
+
+void ASound::playSound(int offset) {
+ findFreeChannelFull(loadData(offset));
+}
+
} // namespace Dragonsphere
} // namespace MADSV2
} // namespace MADS
diff --git a/engines/mads/madsv2/dragonsphere/asound.h b/engines/mads/madsv2/dragonsphere/asound.h
index b33f2cf3187..e1ecf5f3f50 100644
--- a/engines/mads/madsv2/dragonsphere/asound.h
+++ b/engines/mads/madsv2/dragonsphere/asound.h
@@ -22,15 +22,625 @@
#ifndef MADS_DRAGONSPHERE_ASOUND_H
#define MADS_DRAGONSPHERE_ASOUND_H
-#include "mads/madsv2/phantom/asound.h"
+#include "common/mutex.h"
+#include "common/queue.h"
+#include "common/util.h"
+#include "mads/core/sound_manager.h"
namespace MADS {
namespace MADSV2 {
namespace Dragonsphere {
-using Phantom::ASound;
-using Phantom::AdlibChannel;
-using Phantom::AdlibSample;
+#define ADLIB_CHANNEL_COUNT 9
+
+struct AdlibChannel {
+ static bool _isDisabled;
+
+ // ---- byte fields (offsets 0x00 - 0x13) --------------------------------
+
+ uint8 _activeCount = 0; // 0x00 duration tick countdown; 0 = channel idle
+ uint8 _pitchBend = 0; // 0x01 signed pitch-bend offset applied by writePitchBend
+ uint8 _volumeFadeStep = 0; // 0x02 signed per-period volume/velocity delta (0xFF = fade out)
+ uint8 _vibratoDepth = 0; // 0x03 signed LFO depth; negated each period to oscillate
+ uint8 _note = 0; // 0x04 MIDI note number for current event
+ uint8 _sampleIndex = 0; // 0x05 index into _samples[] selecting the OPL patch
+ uint8 _volume = 0; // 0x06 channel volume (0-127)
+ uint8 _noteOffset = 0; // 0x07 subtracted from _activeCount to shorten note duration
+ uint8 _durationOverride = 0; // 0x08 when non-zero, overrides the stream's duration byte
+ uint8 _keyOnDelay = 0; // 0x09 countdown before the OPL key-on bit is cleared (gate time)
+ uint8 _fadePeriodCounter = 0; // 0x0A counts down to 0 before applying _volumeFadeStep
+ uint8 _fadePeriodReload = 0; // 0x0B reload value for _fadePeriodCounter
+ uint8 _arpPeriodCounter = 0; // 0x0C arpeggio tick counter (reloaded from _arpPeriodReload)
+ uint8 _vibPeriodCounter = 0; // 0x0D LFO tick counter; when it reaches 0 a vibrato step fires
+ uint8 _vibPeriodReload = 0; // 0x0E reload value for _vibPeriodCounter
+ uint8 _patchAttenuation = 0; // 0x0F per-note attenuation offset added on top of the patch TL
+ uint8 _velocity = 0; // 0x10 note velocity (0-127); used together with _volume for TL
+ uint8 _writeVolumePending = 0; // 0x11 non-zero -> call sub_11856 (secondary freq write) this tick
+ uint8 _arpPeriodReload = 0; // 0x12 arpeggio period reload value (set by opcode 9)
+ uint8 _arpCounterReload = 0; // 0x13 arpeggio counter reload (decremented; 0xFF = infinite)
+
+ // ---- pointer fields (offsets 0x14 - 0x1F, word-sized in original) ----
+
+ byte *_loopStartPtr = nullptr; // 0x14 start of the current loop body
+ byte *_pSrc = nullptr; // 0x16 current read pointer into the sound-data stream
+ byte *_innerLoopPtr = nullptr; // 0x18 inner-loop restart address
+ byte *_outerLoopPtr = nullptr; // 0x1A outer-loop restart address
+ byte *_soundData = nullptr; // 0x1C base address of this channel's sound-data block
+ byte *_branchTargetPtr = nullptr; // 0x1E target of the most recent branch/jump opcode
+
+ // ---- word counter fields (offsets 0x20 - 0x29) ----------------------
+
+ uint16 _innerLoopCount = 0; // 0x20 remaining inner-loop iterations (0 = infinite until opcode)
+ uint16 _outerLoopCount = 0; // 0x22 remaining outer-loop iterations (0 = infinite until opcode)
+ uint16 _noiseFreqMask = 0; // 0x24 AND mask applied to the random number in noise mode
+ uint16 _freqAccum = 0; // 0x26 frequency sweep accumulator (base frequency + swept offset)
+ uint16 _freqStep = 0; // 0x28 per-tick increment added to _freqAccum during a sweep
+
+ // ---- sweep / pause state (offsets 0x2A - 0x2D) ----------------------
+
+ // 0x2A-0x2B Last computed OPL total-level bytes written by writeVolume.
+ // lo byte (0x2A) = carrier TL nibble; hi byte (0x2B) = modulator TL nibble.
+ // Written at the end of asound_writeVolume so that command7 (resume) can
+ // restore operator levels without a full recalculation.
+ uint8 _cachedCarrierTL = 0; // 0x2A cached carrier total-level (6-bit, 0 = loudest)
+ uint8 _savedFreqSweep = 0; // 0x2B copy of _freqSweepCounter saved by command6 (pause)
+ uint8 _freqSweepCounter = 0; // 0x2C countdown for frequency-sweep ticks; 0 = sweep done
+ uint8 _savedSweepCounter = 0; // 0x2D second save slot: _freqSweepCounter is mirrored here
+ // by command6/7 so command7 can restore the live value
+
+// ---- tuning / control (offsets 0x2E - 0x31) ------------------------
+
+ uint8 _transpose = 0; // 0x2E semitone offset added when looking up _semitoneFreqTable
+ uint8 _octaveTranspose = 0; // 0x2F octave shift: added to _note before octave calculation
+ uint8 _pendingStop = 0; // 0x30 0xFF = channel is fading out and will go idle when silent
+ uint8 _vibratoMode = 0; // 0x31 0xFF = one-shot vibrato (clamp & stop); else = continuous
+
+ /**
+ * Zeroes the "live" fields of one channel without touching the
+ * loop/sound-data pointers (those are handled separately by load).
+ */
+ void reset();
+
+ /**
+ * Clears the entire channel struct, then initialises all loop/source
+ * pointers to the supplied sound-data address, sets _patchAttenuation to
+ * 0x40 (maximum, silent), and marks the channel active (_activeCount = 1).
+ */
+ void load(byte *soundData);
+
+ /**
+ * Redirects _loopStartPtr and _pSrc to the null/silence stream and arms a
+ * one-step volume fade (_volumeFadeStep = 0xFF, _fadePeriodReload/Counter = 1)
+ * so the channel fades out gracefully over the next tick.
+ */
+ void setPtr2(byte *ptr);
+
+ /**
+ * Marks the channel pending-stop (_pendingStop = 0xFF) and redirects
+ * _soundData to the null/silence stream. Unlike setPtr2 this does not
+ * touch _volumeFadeStep; the channel fades naturally via processChannelFade.
+ */
+ void enable();
+
+ /**
+ * Called every frame for each channel. When _pendingStop is set, waits
+ * until both _velocity and _volume are zero before marking the channel idle
+ * (_activeCount = 0); otherwise applies _volumeFadeStep to velocity/volume
+ * each _fadePeriodReload ticks and clamps the result to [0, 0x7F].
+ */
+ void processChannelFade();
+};
+
+// ---------------------------------------------------------------------------
+// AdlibSample (unchanged from Return of the Phantom, 0x16 bytes)
+// ---------------------------------------------------------------------------
+struct AdlibSample {
+ uint8 _attackRate = 0;
+ uint8 _decayRate = 0;
+ uint8 _sustainLevel = 0;
+ uint8 _releaseRate = 0;
+ uint8 _egTyp = 0;
+ uint8 _ksr = 0;
+ uint8 _totalLevel = 0;
+ uint8 _scalingLevel = 0;
+ uint8 _waveformSelect = 0;
+ uint8 _freqMultiple = 0;
+ uint8 _feedback = 0;
+ uint8 _ampMod = 0;
+ uint8 _vib = 0;
+ uint8 _alg = 0;
+ uint8 _freqSweepInit = 0; // initial _freqSweepCounter value; 0 = immediate key-on
+ uint8 _reserved = 0;
+ uint16 _freqMask = 0; // loaded into channel _noiseFreqMask
+ uint16 _freqBase = 0; // loaded into channel _freqAccum
+ uint16 _outerLoopPtr = 0; // loaded into channel _freqStep
+
+ AdlibSample() {
+ }
+ AdlibSample(Common::SeekableReadStream &s);
+};
+
+// ---------------------------------------------------------------------------
+// ASound - Dragonsphere Adlib sound driver base class
+//
+// Command dispatch table layout (from off_11A14 / off_11A26 / off_11A2E /
+// funcs_12251 / off_11A64 in the disassembly):
+//
+// Table 1 off_11A14 commands 0- 8 (max=8, base=0, 9 entries)
+// Table 2 off_11A26 commands 16-19 (max=0x13, base=0x10, 4 entries)
+// command16 = background-music dispatcher (calls command18)
+// command17 = play specific piece (loads 7 channels direct)
+// command18 = re-entrant music launcher (reads word_12370 to pick a sub-command)
+// command19 = no-op (asound_command98)
+// Table 3 off_11A2E commands 24-32 (max=0x20, base=0x18, 9 entries)
+// Table 4 funcs_12251 commands 32-49 (max=0x31, base=0x20, 18 entries)
+// Includes asound_command32-48 (music pieces / SFX loaders)
+// and asound_command98 (no-op) in the last slot.
+// Table 5 off_11A64 commands 64-101 (max=0x65, base=0x40, 38 entries)
+// Includes asound_command64-101 (single-shot SFX loaders via
+// findFreeChannel / findFreeChannelFull) and two no-ops.
+//
+// The driver also exposes:
+// asound_command90 / 91 - two-voice SFX (findFreeChannelFull x2)
+// asound_command95 - four-voice music piece (findFreeChannel x4)
+// sub_11F98 - two-voice SFX (findFreeChannelFull x2)
+//
+// word_12370 tracks the "current music index" used by command18 to select
+// which music-piece loader to call.
+// ---------------------------------------------------------------------------
+class ASound : public SoundDriver {
+protected:
+ /** Member-function pointer type for deferred sound-loader callbacks. */
+ typedef void (ASound::*CallbackFunction)();
+
+private:
+ // ---- callback / tick state ------------------------------------------
+ uint16 _callbackCounter = 0; // per-tick countdown
+ uint16 _callbackPeriod = 0; // reload value; 0 = callback disabled
+ // Pointer to the deferred sound-loader called when the
+ // counter fires (stored as uint16 in the original; typed as a member
+ // function pointer in the C++ port).
+ CallbackFunction _callbackFnPtr = nullptr;
+
+ // ---- active-channel context (set by pollAllChannels) ----------------
+ AdlibChannel *_activeChannelPtr = nullptr;
+ uint8 _activeChannelNumber = 0;
+ uint16 _activeChannelReg = 0; // 0xA0+ch register for the active channel
+ uint16 _currentOpBase = 0; // operator register base for the active channel
+
+ // ---- per-frame working state ----------------------------------------
+ AdlibSample *_samplePtr = nullptr; // patch being loaded/written
+ byte *pSrc = nullptr; // current read pointer (mirrors channel _pSrc)
+ int16 _pollResult = 0;
+ int16 _resultFlag = 0;
+ uint16 _randomSeed = 0x4D2;
+
+ // ---- driver-wide flags ----------------------------------------------
+ uint16 _isDisabled = 0; // non-zero while the engine is paused (command6)
+ uint8 _findChannelMode = 0; // 0=full search, 1=ch0-5 only, 2=ch6-8 then pending
+
+ // ---- per-channel sweep shadows (for channel 5 special-casing) -------
+ // Not present as separate globals in Dragonsphere; the channel
+ // struct fields _savedFreqSweep / _savedSweepCounter handle this.
+
+ // ---- misc globals ---------------------------------------------------
+ uint8 _anySweepActive = 0; // set to 1 if any channel has _freqSweepCounter > 0
+ int _frameNumber2 = 0; // secondary frame counter incremented every update
+
+ // ---- script / sequencer registers -----------------------------------
+ uint8 _scriptVars[32] = {}; // byte_16A10: 32 general-purpose script registers
+
+ // ---- music-index tracker (word_12370) --------------------------------
+ // Tracks which music piece was last launched by command18.
+ uint16 _musicIndex = 0;
+
+ // ---- tempo / sequencer state (from opcodes3 group 2 handlers) --------
+ uint8 _musicOnlyFlag = 0; // byte_12393: 1=music-only check, 0=all channels
+ uint16 _tempoFineStep = 0; // word_124F2: fine tempo step (opcode A6)
+ uint16 _tempoCoarseStep = 0; // word_124F0: coarse tempo step (opcode A7)
+ uint16 _tempoPeriod = 0; // word_124EE: tempo period in ticks (opcode A8)
+ uint8 _tempoEnabled = 0; // non-zero when tempo tick is active (opcode A8)
+ uint16 _tempoTickCounter = 0; // countdown for tempo callback
+ uint16 _tempoShift = 0; // word_124F4: tempo shift (opcode A9)
+
+ // =========================================================================
+ // Private helpers
+ // =========================================================================
+
+ /** Timer callback entry point; calls update(). */
+ void onTimer();
+
+ /** Zeros _callbackFnPtr, _callbackCounter, and _callbackPeriod. */
+ void clearCallback();
+ void resetCallback() {
+ clearCallback();
+ }
+
+ /**
+ * Forces one OPL operator to maximum attenuation (total-level = 0x3F)
+ * while preserving its upper KSL bits. 'portIndex' is the register index
+ * into _adlibPorts (i.e. the 0x40-range operator register).
+ * Used by command6 to mute all operator pairs during a pause.
+ */
+ void adlib_channelOff(uint8 portIndex);
+
+ /**
+ * Computes and writes the total-level registers for the active channel
+ * (asound_writeVolume).
+ *
+ * Two top-level paths depending on the _alg field of the first sample:
+ * _alg == 0 (FM): both operators contribute; loop twice writing
+ * VOICE_SLOTS[ch][0] then VOICE_SLOTS[ch][1].
+ * _alg != 0 (additive): only the carrier (slot 1) carries volume.
+ *
+ * Within each pass, two inner paths based on OPL version:
+ * < 0x18 (OPL2): TL = clamp(0x3F - var_2, 0, 0x3F).
+ * >= 0x18 (OPL3): patch-attenuation-aware mapping using
+ * PATCH_ATTEN_TO_TL and VOL_VEL_TO_ATTEN_STEP tables;
+ * writes two registers (offset 0 and offset 2).
+ *
+ * Caches the two written TL bytes in _cachedCarrierTL / _savedFreqSweep
+ * so command7 can restore levels without a full recalculation.
+ */
+ void writeVolume();
+
+ /**
+ * Derives the OPL F-number and block (octave) from _note, _octaveTranspose,
+ * and _transpose using _semitoneFreqTable, then writes registers 0xA0+ch
+ * and 0xB0+ch (with key-on bit set).
+ */
+ void writeFrequency();
+
+ /**
+ * Applies _pitchBend as a signed offset to the already-computed frequency
+ * registers without a full note recalculation.
+ */
+ void writePitchBend();
+
+ /**
+ * Arpeggio frequency write (sub_11856).
+ * Called from pollActiveChannel when _writeVolumePending (field_11) is set.
+ * Computes a modified frequency from _note + _octaveTranspose + field_11 - 1
+ * and writes it to the OPL registers, preserving the key-on bit.
+ */
+ void writeArpeggio();
+
+ /**
+ * Clears the key-on bit (bit 5) of the 0xB0+ch register, silencing the
+ * note while preserving pitch. Called during _keyOnDelay countdown.
+ */
+ void updateOctave();
+
+ /**
+ * Arms the frequency sweep counter from the sample definition, then - if
+ * _freqSweepCounter is zero - immediately triggers the note by writing
+ * volume and frequency registers and setting the key-on bit.
+ */
+ void noteOn();
+
+ /**
+ * Writes all OPL operator registers for the patch pointed to by _samplePtr,
+ * using _currentOpBase as the operator base. Covers ADSR, KSL/TL,
+ * feedback/algorithm, AM/VIB flags, and waveform-select registers.
+ */
+ void writeSampleRegs();
+
+ /**
+ * Loads all OPL registers for the patch assigned to the active channel,
+ * covering both modulator and carrier operators, and sets up the channel's
+ * sweep/frequency state from the AdlibSample definition.
+ */
+ void loadSample();
+
+ /**
+ * Main per-frame update: increments frame counters, polls all channels,
+ * fires the tick callback, calls updateAllChannels, then runs the per-channel
+ * frequency-sweep tick for all 9 voices.
+ */
+ void update();
+
+ /**
+ * Per-channel frequency-sweep tick for channel 'channelIndex' (0-based).
+ * When _freqSweepCounter > 0: adds _freqStep to _freqAccum, decrements the
+ * counter, and - when it reaches zero - zeroes the voice's frequency registers.
+ * Sets _anySweepActive when the counter is still non-zero.
+ */
+ void update1(int channelIndex);
+
+ /**
+ * Writes a frequency value directly to the OPL registers for 'voice'.
+ * The high byte selects the block/octave; the low byte is the F-number LSB.
+ */
+ void setFrequency(uint8 voice, uint16 freq);
+
+ /**
+ * Noise-channel inner tick: picks a random frequency offset masked by
+ * _noiseFreqMask, adds _freqAccum, and writes the result to the voice
+ * frequency registers.
+ */
+ void noise_inner(int channelIndex);
+
+ /**
+ * Calls processChannelFade for all 9 channels each frame.
+ */
+ void updateAllChannels();
+
+ /**
+ * Advances pSrc by one, reads two bytes little-endian, and returns the word.
+ * Used by the bytecode opcodes that take 16-bit operands.
+ */
+ uint16 readWord_impl();
+
+ /**
+ * Deferred-callback tick: decrements _callbackCounter; when it reaches zero,
+ * reloads it from _callbackPeriod and calls _callbackFnPtr (if non-null),
+ * then clears _callbackFnPtr so it fires exactly once.
+ */
+ void tickCallback();
+
+protected:
+ // ---- nine AdlibChannel instances ------------------------------------
+ AdlibChannel _channel0, _channel1, _channel2;
+ AdlibChannel _channel3, _channel4, _channel5;
+ AdlibChannel _channel6, _channel7, _channel8;
+ AdlibChannel *_channels[ADLIB_CHANNEL_COUNT] = {
+ &_channel0, &_channel1, &_channel2,
+ &_channel3, &_channel4, &_channel5,
+ &_channel6, &_channel7, &_channel8
+ };
+
+ uint8 _adlibPorts[0x100] = { 0 };
+ Common::Array<AdlibSample> _samples;
+
+protected:
+ // =========================================================================
+ // Protected helpers (used by ASound1âASound9)
+ // =========================================================================
+
+ /**
+ * Checks whether any of channels 0-6 (or 0-8 when _musicOnlyFlag is clear)
+ * have a non-zero _activeCount. Returns non-zero if sound is playing.
+ * This is 'sub_1061A' in the disassembly.
+ */
+ int isMusicChannelsActive();
+
+ /**
+ * Like isMusicChannelsActive but scans all 9 channels unconditionally
+ * (clears the ch0-6-only flag first). This is 'sub_1064E'.
+ */
+ int isAnyChannelActive();
+
+ /**
+ * Schedule fn as the next deferred-load callback.
+ * Does NOT touch _callbackCounter or _callbackPeriod â those are preserved
+ * from the previous loader so the callback fires on the right beat.
+ * Cast the derived-class member-function pointer with reinterpret_cast.
+ */
+ void scheduleCallback(CallbackFunction fn) { _callbackFnPtr = fn; }
+
+ /**
+ * Arm the periodic timer and clear any pending callback pointer.
+ * Call at the head of every immediate-load function (symmetric counter/period).
+ */
+ void resetCallbackTimer(uint16 period) {
+ _callbackFnPtr = nullptr;
+ _callbackCounter = period;
+ _callbackPeriod = period;
+ }
+
+ /**
+ * Arm the periodic timer with separate counter and period values.
+ * Used by command44 which sets counter=0x60 but period=0xE0.
+ */
+ void resetCallbackTimerEx(uint16 counter, uint16 period) {
+ _callbackFnPtr = nullptr;
+ _callbackCounter = counter;
+ _callbackPeriod = period;
+ }
+
+ /** Set the music-piece index (word_12370) read by command18. */
+ void setMusicIndex(uint16 idx) { _musicIndex = idx; }
+ /** Read the current music-piece index. */
+ uint16 getMusicIndex() const { return _musicIndex; }
+
+ /** Write one script-variable register (byte_16A10[idx]). */
+ void setScriptVar(int idx, uint8 val) { _scriptVars[idx] = val; }
+ /**
+ * Writes (reg, value) to the OPL chip and updates the _adlibPorts shadow
+ * array so subsequent reads return the last-written value.
+ */
+ void write(uint8 reg, uint8 value);
+
+ /** Updates and returns _randomSeed using a simple linear-feedback shift. */
+ uint16 getRandomNumber();
+
+ /**
+ * Restores one OPL operator by re-writing the value already stored in the
+ * _adlibPorts shadow array. command7 uses this to resume playback after
+ * a command6 pause without recalculating any TL values.
+ * 'portIndex' is the operator register index into _adlibPorts.
+ */
+ void adlib_channelOn(uint8 portIndex);
+
+ /** Sets _pollResult and _resultFlag to indicate that sound is playing. */
+ void signalSoundPlaying();
+
+ /**
+ * Iterates over all 9 channels, sets _activeChannelPtr / _activeChannelNumber,
+ * and calls pollActiveChannel for each one.
+ */
+ void pollAllChannels();
+
+ /**
+ * Per-channel bytecode interpreter, called once per frame per active channel.
+ *
+ * Sound data is a sequence of (note, duration) byte pairs plus command bytes
+ * with the high bit set (0x80-0xFF). The upper nibble of a command byte
+ * (after masking out the high bit) selects one of seven opcode groups:
+ *
+ * 0x0_ -> opcodes1 (patch/velocity/volume/vibrato/transpose/arpeggio)
+ * 0x1_ -> opcodes2 (inner/outer loop control, restart, branch/call)
+ * 0x2_ -> opcodes3 (tempo, script-variable arithmetic, call-by-address)
+ * 0x3_ -> opcodes4 (script-variable load/store/copy/inc/dec)
+ * 0x4_ -> opcodes5 (script-variable ALU: add/sub/mul/div with imm or var)
+ * 0x5_ -> opcodes6 (extended: random-range, indexed table read/write)
+ * 0x6_ -> opcodes7 (driver-level calls: command dispatch, etc.)
+ *
+ * The lower nibble is passed as the sub-opcode to each group handler.
+ * Note bytes (high bit clear) consume one duration tick per call.
+ */
+ void pollActiveChannel();
+
+ /** Returns true if the sound data block at 'ptr' is already playing. */
+ bool isSoundActive(byte *ptr) const;
+
+ /**
+ * Scans channels 0-6 for an empty slot (_activeCount == 0) and calls
+ * load() on the first one found. Falls through to findFreeChannelFull
+ * for channels 6-8 when _findChannelMode != 1.
+ */
+ void findFreeChannel(byte *soundData);
+
+ /**
+ * Extends the search to channels 7-8, then checks for pending-stop
+ * channels (which can be pre-empted), working in reverse priority order
+ * (ch8, ch7, ch6 ... ch0).
+ */
+ void findFreeChannelFull(byte *soundData);
+
+ /** Returns a pointer to the sound data at the given offset. */
+ byte *loadData(int offset) {
+ return &_soundData[offset];
+ }
+
+protected:
+ // =========================================================================
+ // Core commands (0-8) - identical in purpose to Return of the Phantom
+ // =========================================================================
+
+ /**
+ * command0: Full hardware reset.
+ * 1. Reset all 9 channels.
+ * 2. Mute all operator TL registers (0x40-0x55) to 0x3F.
+ * 3. Zero remaining operator registers (0x60-0xFF and 0x01-0x3F).
+ * 4. Write Waveform Select Enable (register 0x01 = 0x20).
+ * 5. Reset the tick callback.
+ */
+ int command0();
+
+ /**
+ * command1: Fade out all channels.
+ * Calls command3 (fade music channels 0-6) then command5 (fade SFX 7-8).
+ */
+ int command1();
+
+ /**
+ * command2: Fade out music channels 0-6.
+ * Calls AdlibChannel::setPtr2 on each, redirecting to the null stream
+ * and arming a one-step fade.
+ */
+ int command2();
+
+ /**
+ * command3: Fade out music channels 0-6 with pending-stop.
+ * Calls AdlibChannel::enable on channels 0-6.
+ */
+ int command3();
+
+ /**
+ * command4: Fade out SFX channels 7-8.
+ * Calls AdlibChannel::setPtr2 on channels 7 and 8.
+ */
+ int command4();
+
+ /**
+ * command5: Stop SFX channels 7-8 with pending-stop flag.
+ * Calls AdlibChannel::enable on channels 7 and 8, letting each channel
+ * finish its current OPL envelope before going idle.
+ */
+ int command5();
+
+ /**
+ * command6: Pause playback.
+ * Saves each channel's _freqSweepCounter into _savedSweepCounter, zeroes
+ * _freqSweepCounter on all channels, then mutes all 22 operator TL
+ * registers (the byte_1239B table covers all operator slots 0x40-0x55).
+ * Sets _isDisabled to prevent further updates.
+ */
+ int command6();
+
+ /**
+ * command7: Resume playback.
+ * Restores operator volumes for all channels from _adlibPorts shadow,
+ * copies _savedSweepCounter back to _freqSweepCounter on all channels,
+ * signals sound playing if any channel was active, then clears _isDisabled.
+ */
+ int command7();
+
+ /**
+ * command8: Returns non-zero if any of the 9 channels has a non-zero
+ * _activeCount (i.e. sound is currently playing).
+ * Also clears the music-only flag (byte_12393 = 0) so the check covers
+ * all 9 channels.
+ */
+ int command8();
+
+ /**
+ * Calls a function at a fixed offset within the sound driver.
+ * @param offset Offset of the function
+ */
+ virtual void callFunction(uint16 offset);
+
+ // =========================================================================
+ // Music-index launcher (called via command18)
+ // =========================================================================
+
+ /**
+ * command18: Re-entrant music launcher.
+ * First calls command1 to fade current output, then branches on _musicIndex
+ * (word_12370):
+ * <= 0x12 -> calls off_11A26 table (commands 16-19)
+ * > 0x12 -> calls funcs_12251 table (commands 32-49), index = _musicIndex - 0x20
+ */
+ int command18();
+
+public:
+ /**
+ * Constructor.
+ * @param mixer Mixer instance
+ * @param opl OPL chip instance
+ * @param filename Path to the .DR1 (or equivalent) sound-driver file
+ * @param dataOffset Offset in the file of the data segment
+ * @param dataSize Size of the data segment
+ */
+ ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::Path &filename,
+ int dataOffset, int dataSize);
+
+ ~ASound() override {
+ }
+
+ /** Stop all currently playing sounds (wraps command0). */
+ virtual int stop() override;
+
+ /** Main poll method; drives the per-frame update. */
+ int poll() override;
+
+ /**
+ * Noise channel tick: for each of the 9 channels calls noise_inner,
+ * which randomises the voice frequency each frame using _noiseFreqMask.
+ */
+ void noise() override;
+
+ /**
+ * Starts playback of the sound data at the given byte offset within the
+ * driver's data segment, using findFreeChannelFull to select a channel.
+ */
+ void playSound(int offset);
+
+ void setVolume(int volume) override {
+ // TODO: implement if needed
+ }
+};
} // namespace Dragonsphere
} // namespace MADSV2
diff --git a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
index d4ddedd0b02..c6e9a011c2d 100644
--- a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
+++ b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
@@ -89,318 +89,704 @@ void DragonSoundManager::loadDriver(int sectionNumber) {
/* ASound1 (asound.dr1) *
*-----------------------------------------------------------------------*/
-const ASound1::CommandPtr ASound1::_commandList[40] = {
- &ASound1::command0, &ASound1::command1, &ASound1::command2, &ASound1::command3,
- &ASound1::command4, &ASound1::command5, &ASound1::command6, &ASound1::command7,
- &ASound1::command8, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- &ASound1::command16, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- &ASound1::command24, &ASound1::command25, &ASound1::command26, &ASound1::command27,
- nullptr, nullptr, nullptr, nullptr,
- &ASound1::command32, &ASound1::command33, &ASound1::command34, &ASound1::command35,
- &ASound1::command36, &ASound1::command37, &ASound1::command38, &ASound1::command39
+// Cast a derived-class void loader to the base CallbackFunction type.
+// Safe under single inheritance: same function address, only static type changes.
+#define MAKE_CALLBACK(cls, fn) \
+ reinterpret_cast<ASound::CallbackFunction>(&cls::fn)
+
+const ASound1::CommandPtr ASound1::_commandList[102] = {
+ // commands 0-8 (off_11A14)
+ &ASound1::command0, &ASound1::command1, &ASound1::command2, &ASound1::command3,
+ &ASound1::command4, &ASound1::command5, &ASound1::command6, &ASound1::command7,
+ &ASound1::command8,
+ // 9-15 absent
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ // commands 16-18, 19=no-op (off_11A26)
+ &ASound1::command16, &ASound1::command17, &ASound1::command18, nullptr,
+ // 20-23 absent
+ nullptr, nullptr, nullptr, nullptr,
+ // commands 24-31, then no-op for slot 32 (off_11A2E)
+ &ASound1::command24, &ASound1::command25, &ASound1::command26, &ASound1::command27,
+ &ASound1::command28, &ASound1::command29, &ASound1::command30, &ASound1::command31,
+ // commands 32-48, 49=no-op (funcs_12251)
+ &ASound1::command32, &ASound1::command33, &ASound1::command34, &ASound1::command35,
+ &ASound1::command36, &ASound1::command37, &ASound1::command38, &ASound1::command39,
+ &ASound1::command40, &ASound1::command41, &ASound1::command42, &ASound1::command43,
+ &ASound1::command44, &ASound1::command45, &ASound1::command46, &ASound1::command47,
+ &ASound1::command48, nullptr,
+ // 50-63 absent
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ // commands 64-101 (off_11A64); slot 92 and slot 98 are no-ops
+ &ASound1::command64, &ASound1::command65, &ASound1::command66, &ASound1::command67,
+ &ASound1::command68, &ASound1::command69, &ASound1::command70, &ASound1::command71,
+ &ASound1::command72, &ASound1::command73, &ASound1::command74, &ASound1::command75,
+ &ASound1::command76, &ASound1::command77, &ASound1::command78, &ASound1::command79,
+ &ASound1::command80, &ASound1::command81, &ASound1::command82, &ASound1::command83,
+ &ASound1::command84, &ASound1::command85, &ASound1::command86, &ASound1::command87,
+ &ASound1::command88, &ASound1::command89, &ASound1::command90, &ASound1::command91,
+ nullptr, &ASound1::command93, &ASound1::command94, &ASound1::command95,
+ &ASound1::command96, &ASound1::command97, nullptr, &ASound1::command99,
+ &ASound1::command100, &ASound1::command101
};
ASound1::ASound1(Audio::Mixer *mixer, OPL::OPL *opl)
: ASound(mixer, opl, "asound.dr1", 0x2520, 0x49e0) {
- // Load sound samples
auto samplesStream = getDataStream(0x1dc);
for (int i = 0; i < 182; ++i)
_samples.push_back(AdlibSample(samplesStream));
}
int ASound1::command(int commandId, int param) {
- if (commandId > 39 || !_commandList[commandId])
+ if (commandId > 101 || !_commandList[commandId])
return 0;
-
return (this->*_commandList[commandId])();
}
// commands 0-8: delegate to base ASound
-int ASound1::command0() { return ASound::command0(); }
-int ASound1::command1() { return ASound::command1(); }
-int ASound1::command2() { return ASound::command2(); }
-int ASound1::command3() { return ASound::command3(); }
-int ASound1::command4() { return ASound::command4(); }
-int ASound1::command5() { return ASound::command5(); }
-int ASound1::command6() { return ASound::command6(); }
-int ASound1::command7() { return ASound::command7(); }
-int ASound1::command8() { return ASound::command8(); }
-
-int ASound1::commandMusic0() {
- ASound::command1();
- _channels[0]->load(loadData(0x1ECA));
- _channels[1]->load(loadData(0x1FBF));
- _channels[2]->load(loadData(0x2037));
- _channels[3]->load(loadData(0x20EE));
- _channels[4]->load(loadData(0x219B));
- _channels[5]->load(loadData(0x21AF));
- return 0;
-}
+int ASound1::command0() { return ASound::command0(); }
+int ASound1::command1() { return ASound::command1(); }
+int ASound1::command2() { return ASound::command2(); }
+int ASound1::command3() { return ASound::command3(); }
+int ASound1::command4() { return ASound::command4(); }
+int ASound1::command5() { return ASound::command5(); }
+int ASound1::command6() { return ASound::command6(); }
+int ASound1::command7() { return ASound::command7(); }
+int ASound1::command8() { return ASound::command8(); }
-int ASound1::commandMusic1() {
+// ---------------------------------------------------------------------------
+// command16 - music piece A (castle interior theme)
+// isSoundActive guard + isMusicChannelsActive deferred-callback pattern.
+// counter = period = 0x90; sets musicIndex = 0x10.
+// ---------------------------------------------------------------------------
+void ASound1::loadCommand16() {
+ resetCallbackTimer(0x90);
+ setMusicIndex(0x10);
ASound::command1();
- _channels[0]->load(loadData(0x3418));
- _channels[1]->load(loadData(0x34EB));
- _channels[2]->load(loadData(0x359B));
- _channels[3]->load(loadData(0x3658));
- _channels[4]->load(loadData(0x3667));
- _channels[5]->load(loadData(0x3677));
- return 0;
+ _channels[0]->load(loadData(0x262C));
+ _channels[1]->load(loadData(0x26DA));
+ _channels[2]->load(loadData(0x2752));
+ _channels[3]->load(loadData(0x27CE));
+ _channels[4]->load(loadData(0x2826));
+ _channels[5]->load(loadData(0x284D));
+ _channels[6]->load(loadData(0x286D));
}
-int ASound1::commandMusic2() {
- ASound::command1();
- _channels[0]->load(loadData(0x3688));
- _channels[1]->load(loadData(0x387B));
- _channels[2]->load(loadData(0x3A01));
- _channels[3]->load(loadData(0x3BC6));
- _channels[4]->load(loadData(0x3D31));
- _channels[5]->load(loadData(0x3D41));
+int ASound1::command16() {
+ byte *pData = loadData(0x262C);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand16();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand16));
+ }
return 0;
}
-int ASound1::commandMusic3() {
+// ---------------------------------------------------------------------------
+// command17 - special music piece (7 channels)
+// Uses isAnyChannelActive: if any channel busy, skip entirely.
+// No isSoundActive guard; no deferred callback.
+// counter = period = 0x60; no musicIndex update.
+// ---------------------------------------------------------------------------
+int ASound1::command17() {
+ if (isAnyChannelActive())
+ return 0;
+ resetCallbackTimer(0x60);
ASound::command1();
- _channels[0]->load(loadData(0x3D52));
- _channels[1]->load(loadData(0x3FD3));
- _channels[2]->load(loadData(0x41FF));
- _channels[3]->load(loadData(0x420C));
- _channels[4]->load(loadData(0x4219));
- _channels[5]->load(loadData(0x4229));
+ _channels[0]->load(loadData(0x43CE));
+ _channels[1]->load(loadData(0x4403));
+ _channels[2]->load(loadData(0x4436));
+ _channels[3]->load(loadData(0x444F));
+ _channels[4]->load(loadData(0x4482));
+ _channels[5]->load(loadData(0x4490));
+ _channels[6]->load(loadData(0x44A8));
return 0;
}
// ---------------------------------------------------------------------------
-// command16 - random background music
-//
-// If channel 0 is active and already playing one of the five known music
-// pieces (identified by their starting offset in field_17), leave it alone.
-// Otherwise pick a piece at random: the original uses getRandomNumber() & 7,
-// discarding 0 and indexing a four-entry table repeated twice (entries 1-3
-// -> pieces 1-3, entries 4-7 -> same pieces again with entry 4 wrapping to
-// piece 0). We reproduce this with a modulo-4 on a non-zero value.
+// command18 - re-entrant music launcher (delegate to base ASound::command18)
// ---------------------------------------------------------------------------
-int ASound1::command16() {
- if (_channels[0]->_activeCount) {
- // Special offset checks
- int f = _channels[0]->_loopStartPtr - &_soundData[0];
-
- if (f == 0 || f == 0x1ECA || f == 0x21C4 ||
- f == 0x3418 || f == 0x3688 || f == 0x3D52)
- return 0;
- }
-
- int idx;
- do {
- idx = getRandomNumber() & 3;
- } while (idx == 0);
- _musicIndex = idx;
-
- typedef int (ASound1::*MusicPtr)();
- static const MusicPtr musicTable[4] = {
- &ASound1::commandMusic0,
- &ASound1::commandMusic1,
- &ASound1::commandMusic2,
- &ASound1::commandMusic3
- };
- return (this->*musicTable[idx])();
-}
+int ASound1::command18() { return ASound::command18(); }
// ---------------------------------------------------------------------------
-// commands 24-27 - upper channel pool
+// commands 24-31 - sound effects via findFreeChannelFull
// ---------------------------------------------------------------------------
-
int ASound1::command24() {
- playSound(0x173A);
- playSound(0x176D);
+ findFreeChannelFull(loadData(0x44E1));
+ findFreeChannelFull(loadData(0x4516));
return 0;
}
int ASound1::command25() {
- playSound(0x179B);
- playSound(0x17C7);
+ findFreeChannelFull(loadData(0x4546));
+ findFreeChannelFull(loadData(0x4574));
return 0;
}
-int ASound1::command26() {
- playSound(0x17F5);
- return 0;
-}
+int ASound1::command26() { findFreeChannelFull(loadData(0x45A4)); return 0; }
+int ASound1::command27() { findFreeChannelFull(loadData(0x45B0)); return 0; }
+int ASound1::command28() { findFreeChannelFull(loadData(0x46CF)); return 0; }
+int ASound1::command29() { findFreeChannelFull(loadData(0x45E4)); return 0; }
+int ASound1::command30() { findFreeChannelFull(loadData(0x461C)); return 0; }
-int ASound1::command27() {
- playSound(0x1801);
+// command31 - writes transposition byte 0x67 into the sound block before loading
+int ASound1::command31() {
+ *loadData(0x4680) = 0x67;
+ findFreeChannelFull(loadData(0x467D));
return 0;
}
// ---------------------------------------------------------------------------
-// commands 32-39
+// commands 32-48 - music piece loaders
+// All use: isSoundActive guard â isMusicChannelsActive â defer or load.
+// Exceptions: command35 uses command3 (not command1); command43/48 are
+// special (no deferred callback; modify sound data bytes directly);
+// command44 uses asymmetric counter/period; command46 uses isAnyChannelActive
+// as the outer guard instead of isSoundActive.
// ---------------------------------------------------------------------------
-// command32 - no guard, no fade, load ch0-5
+void ASound1::loadCommand32() {
+ resetCallbackTimer(0xB0);
+ ASound::command1();
+ _channels[0]->load(loadData(0x299A));
+ _channels[1]->load(loadData(0x29E3));
+ _channels[2]->load(loadData(0x2A45));
+ _channels[3]->load(loadData(0x2A6F));
+ _channels[4]->load(loadData(0x2A9F));
+ _channels[5]->load(loadData(0x2AAE));
+}
+
int ASound1::command32() {
- _channels[0]->load(loadData(0x2522));
- _channels[1]->load(loadData(0x255D));
- _channels[2]->load(loadData(0x2591));
- _channels[3]->load(loadData(0x25BB));
- _channels[4]->load(loadData(0x25E7));
- _channels[5]->load(loadData(0x2613));
+ byte *pData = loadData(0x299A);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand32();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand32));
+ }
return 0;
}
-// command33 - isSoundActive guard, command1, load ch0-5
+void ASound1::loadCommand33() {
+ resetCallbackTimer(0xB0);
+ setMusicIndex(0x21);
+ ASound::command1();
+ _channels[0]->load(loadData(0x2ABE));
+ _channels[1]->load(loadData(0x2B64));
+ _channels[2]->load(loadData(0x2BC8));
+ _channels[3]->load(loadData(0x2C03));
+ _channels[4]->load(loadData(0x2C82));
+ _channels[5]->load(loadData(0x2C9A));
+}
+
int ASound1::command33() {
- byte *pData = loadData(0x266C);
+ byte *pData = loadData(0x2ABE);
if (!isSoundActive(pData)) {
- ASound::command1();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x2929));
- _channels[2]->load(loadData(0x2B1D));
- _channels[3]->load(loadData(0x2D37));
- _channels[4]->load(loadData(0x2EC3));
- _channels[5]->load(loadData(0x3033));
+ if (!isMusicChannelsActive())
+ loadCommand33();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand33));
}
return 0;
}
-// command34 - isSoundActive guard, stop(), load ch0-5
+void ASound1::loadCommand34() {
+ resetCallbackTimer(0x50);
+ ASound::command1();
+ _channels[0]->load(loadData(0x3A74));
+ _channels[1]->load(loadData(0x3AE1));
+ _channels[2]->load(loadData(0x3AFD));
+ _channels[3]->load(loadData(0x3B19));
+ _channels[4]->load(loadData(0x3B26));
+ _channels[5]->load(loadData(0x3B33));
+}
+
int ASound1::command34() {
- byte *pData = loadData(0x1852);
+ byte *pData = loadData(0x3A74);
if (!isSoundActive(pData)) {
- stop();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x1AA9));
- _channels[2]->load(loadData(0x1BC4));
- _channels[3]->load(loadData(0x1CF1));
- _channels[4]->load(loadData(0x1DF2));
- _channels[5]->load(loadData(0x1EBE));
+ if (!isMusicChannelsActive())
+ loadCommand34();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand34));
}
return 0;
}
-// command35 - isSoundActive guard, command2 (lower-bank fade),
-// load ch0-5
+// command35 uses command3 (pending-stop fade) instead of command1
+void ASound1::loadCommand35() {
+ resetCallbackTimer(0x60);
+ ASound::command3();
+ _channels[0]->load(loadData(0x38A4));
+ _channels[1]->load(loadData(0x393C));
+ _channels[2]->load(loadData(0x39C2));
+ _channels[3]->load(loadData(0x3A0E));
+ _channels[4]->load(loadData(0x3A3E));
+ _channels[5]->load(loadData(0x3A54));
+ _channels[6]->load(loadData(0x3A63));
+}
+
int ASound1::command35() {
- byte *pData = loadData(0x0C36);
+ byte *pData = loadData(0x393C);
if (!isSoundActive(pData)) {
- ASound::command2();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x0D7F));
- _channels[2]->load(loadData(0x0E48));
- _channels[3]->load(loadData(0x0F10));
- _channels[4]->load(loadData(0x0FB2));
- _channels[5]->load(loadData(0x1096));
+ if (!isMusicChannelsActive())
+ loadCommand35();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand35));
}
return 0;
}
-// command36 - isSoundActive guard, command2 (lower-bank fade),
-// load ch0-5
+void ASound1::loadCommand36() {
+ resetCallbackTimer(0x80);
+ ASound::command1();
+ _channels[0]->load(loadData(0x3F36));
+ _channels[1]->load(loadData(0x3FFA));
+ _channels[2]->load(loadData(0x40E9));
+ _channels[3]->load(loadData(0x417D));
+ _channels[4]->load(loadData(0x41ED));
+ _channels[5]->load(loadData(0x41FC));
+}
+
int ASound1::command36() {
- byte *pData = loadData(0x1190);
+ byte *pData = loadData(0x3F36);
if (!isSoundActive(pData)) {
- ASound::command2();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x12D7));
- _channels[2]->load(loadData(0x13AA));
- _channels[3]->load(loadData(0x1476));
- _channels[4]->load(loadData(0x1528));
- _channels[5]->load(loadData(0x1614));
+ if (!isMusicChannelsActive())
+ loadCommand36();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand36));
}
return 0;
}
-// command37 - isSoundActive guard, command1, four loadAny
-// calls starting from channel 0
+void ASound1::loadCommand37() {
+ resetCallbackTimer(0xC0);
+ ASound::command1();
+ _channels[0]->load(loadData(0x4220));
+ _channels[1]->load(loadData(0x4285));
+ _channels[2]->load(loadData(0x42F0));
+ _channels[3]->load(loadData(0x42FF));
+ _channels[4]->load(loadData(0x4310));
+ _channels[5]->load(loadData(0x431C));
+}
+
int ASound1::command37() {
- byte *pData = loadData(0x3220);
+ byte *pData = loadData(0x4220);
if (!isSoundActive(pData)) {
- ASound::command1();
- findFreeChannel(pData);
- findFreeChannel(loadData(0x326A));
- findFreeChannel(loadData(0x3293));
- findFreeChannel(loadData(0x32AC));
+ if (!isMusicChannelsActive())
+ loadCommand37();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand37));
}
return 0;
}
-// command38 - alias for commandMusic0; also the direct dispatch
-// target for command 38.
+void ASound1::loadCommand38() {
+ resetCallbackTimer(0x60);
+ ASound::command1();
+ _channels[0]->load(loadData(0x12CA));
+ _channels[1]->load(loadData(0x13BF));
+ _channels[2]->load(loadData(0x14A2));
+ _channels[3]->load(loadData(0x15DB));
+ _channels[4]->load(loadData(0x16D3));
+ _channels[5]->load(loadData(0x1715));
+}
+
int ASound1::command38() {
- return commandMusic0();
+ byte *pData = loadData(0x12CA);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand38();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand38));
+ }
+ return 0;
+}
+
+void ASound1::loadCommand39() {
+ resetCallbackTimer(0xB0);
+ ASound::command1();
+ _channels[0]->load(loadData(0x179C));
+ _channels[1]->load(loadData(0x17F1));
+ _channels[2]->load(loadData(0x1857));
+ _channels[3]->load(loadData(0x18B1));
+ _channels[4]->load(loadData(0x18C0));
+ _channels[5]->load(loadData(0x18CF));
}
-// command39 - isSoundActive guard, command1, load ch0-5
int ASound1::command39() {
- byte *pData = loadData(0x423A);
+ byte *pData = loadData(0x179C);
if (!isSoundActive(pData)) {
- ASound::command1();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x43DF));
- _channels[2]->load(loadData(0x44F7));
- _channels[3]->load(loadData(0x45ED));
- _channels[4]->load(loadData(0x46F9));
- _channels[5]->load(loadData(0x48AF));
+ if (!isMusicChannelsActive())
+ loadCommand39();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand39));
+ }
+ return 0;
+}
+
+void ASound1::loadCommand40() {
+ resetCallbackTimer(0xA8);
+ ASound::command1();
+ _channels[0]->load(loadData(0x18DE));
+ _channels[1]->load(loadData(0x1A57));
+ _channels[2]->load(loadData(0x1C0D));
+ _channels[3]->load(loadData(0x1ED6));
+ _channels[4]->load(loadData(0x20FE));
+ _channels[5]->load(loadData(0x239B));
+}
+
+int ASound1::command40() {
+ byte *pData = loadData(0x18DE);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand40();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand40));
}
return 0;
}
+void ASound1::loadCommand41() {
+ resetCallbackTimer(0x90);
+ ASound::command1();
+ _channels[0]->load(loadData(0x23D2));
+ _channels[1]->load(loadData(0x2440));
+ _channels[2]->load(loadData(0x261C));
+ _channels[3]->load(loadData(0x24BA));
+ _channels[4]->load(loadData(0x259A));
+ _channels[5]->load(loadData(0x25EF));
+}
+
+int ASound1::command41() {
+ byte *pData = loadData(0x23D2);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand41();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand41));
+ }
+ return 0;
+}
+
+void ASound1::loadCommand42() {
+ resetCallbackTimer(144);
+ setMusicIndex(0x29);
+ ASound::command1();
+ _channels[0]->load(loadData(0x1192));
+ _channels[1]->load(loadData(0x11C8));
+ _channels[2]->load(loadData(0x11FB));
+ _channels[3]->load(loadData(0x123C));
+ _channels[4]->load(loadData(0x126E));
+ _channels[5]->load(loadData(0x128B));
+ _channels[6]->load(loadData(0x12A1));
+}
+
+int ASound1::command42() {
+ byte *pData = loadData(0x1192);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand42();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand42));
+ }
+ return 0;
+}
+
+// ---------------------------------------------------------------------------
+// loadMusicPitchBend - shared loader for command43 and command48
+// Resets timer to 0x54, fades current music, loads the pitch-bend piece
+// (6 channels starting at 0x287C).
+// ---------------------------------------------------------------------------
+void ASound1::loadMusicPitchBend() {
+ resetCallbackTimer(0x54);
+ ASound::command1();
+ _channels[0]->load(loadData(0x287C));
+ _channels[1]->load(loadData(0x28C8));
+ _channels[2]->load(loadData(0x290F));
+ _channels[3]->load(loadData(0x2938));
+ _channels[4]->load(loadData(0x295B));
+ _channels[5]->load(loadData(0x298C));
+}
+
+// command43 - set pitch-bend variant byte to 0x2F (bent), mark script var,
+// then start the pitch-bend piece only if it is not already playing.
+int ASound1::command43() {
+ *loadData(0x28C9) = 0x2F;
+ setScriptVar(14, 1);
+ if (!isSoundActive(loadData(0x287C))) {
+ loadMusicPitchBend();
+ setMusicIndex(0x10);
+ }
+ return 0;
+}
+
+// command44 - asymmetric timer: counter=0x60 but period=0xE0
+void ASound1::loadCommand44() {
+ resetCallbackTimerEx(0x60, 0xE0);
+ ASound::command1();
+ _channels[0]->load(loadData(0x3B40));
+ _channels[1]->load(loadData(0x3B9E));
+ _channels[2]->load(loadData(0x3F26));
+ _channels[3]->load(loadData(0x3C25));
+ _channels[4]->load(loadData(0x3DB4));
+ _channels[5]->load(loadData(0x3E8B));
+}
+
+int ASound1::command44() {
+ byte *pData = loadData(0x3B40);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand44();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand44));
+ }
+ return 0;
+}
+
+void ASound1::loadCommand45() {
+ resetCallbackTimer(0x60);
+ setMusicIndex(0x2D);
+ ASound::command1();
+ _channels[0]->load(loadData(0x32A0));
+ _channels[1]->load(loadData(0x33E1));
+ _channels[2]->load(loadData(0x34CE));
+ _channels[3]->load(loadData(0x35DB));
+ _channels[4]->load(loadData(0x36CB));
+ _channels[5]->load(loadData(0x3873));
+}
+
+int ASound1::command45() {
+ byte *pData = loadData(0x32A0);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand45();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand45));
+ }
+ return 0;
+}
+
+// command46 - uses isAnyChannelActive as the outer guard (not isSoundActive),
+// then isMusicChannelsActive for the defer/immediate decision.
+void ASound1::loadCommand46() {
+ resetCallbackTimer(0x90);
+ ASound::command1();
+ _channels[0]->load(loadData(0x432E));
+ _channels[1]->load(loadData(0x4385));
+ _channels[2]->load(loadData(0x43B8));
+ _channels[3]->load(loadData(0x43C3));
+}
+
+int ASound1::command46() {
+ if (isAnyChannelActive())
+ return 0;
+ if (!isMusicChannelsActive())
+ loadCommand46();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand46));
+ return 0;
+}
+
+void ASound1::loadCommand47() {
+ resetCallbackTimer(0x60);
+ ASound::command1();
+ _channels[0]->load(loadData(0x12C4));
+ _channels[1]->load(loadData(0x13B2));
+ _channels[2]->load(loadData(0x149C));
+ _channels[3]->load(loadData(0x15D5));
+ _channels[4]->load(loadData(0x16F4));
+ _channels[5]->load(loadData(0x1759));
+}
+
+int ASound1::command47() {
+ byte *pData = loadData(0x12C4);
+ if (!isSoundActive(pData)) {
+ if (!isMusicChannelsActive())
+ loadCommand47();
+ else
+ scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand47));
+ }
+ return 0;
+}
+
+// command48 - set pitch-bend variant byte to 0x1B (normal), then load if not active
+int ASound1::command48() {
+ *loadData(0x28C9) = 0x1B;
+ setScriptVar(14, 0);
+ if (!isSoundActive(loadData(0x287C))) {
+ loadMusicPitchBend();
+ setMusicIndex(0x28);
+ }
+ return 0;
+}
+
+// ---------------------------------------------------------------------------
+// commands 64-101 - SFX loaders via findFreeChannelFull / findFreeChannel /
+// direct channel load.
+// ---------------------------------------------------------------------------
+int ASound1::command64() { findFreeChannelFull(loadData(0x2D20)); return 0; }
+int ASound1::command65() { findFreeChannelFull(loadData(0x2D35)); return 0; }
+int ASound1::command66() { findFreeChannelFull(loadData(0x2D3F)); return 0; }
+int ASound1::command67() { findFreeChannelFull(loadData(0x2D4B)); return 0; }
+int ASound1::command68() { findFreeChannelFull(loadData(0x2D59)); return 0; }
+
+int ASound1::command69() {
+ findFreeChannelFull(loadData(0x2D78));
+ findFreeChannelFull(loadData(0x2D8E));
+ return 0;
+}
+
+int ASound1::command70() { findFreeChannelFull(loadData(0x2DA0)); return 0; }
+
+// command71 - load directly into channel 8
+int ASound1::command71() { _channels[8]->load(loadData(0x2DB9)); return 0; }
+
+int ASound1::command72() { findFreeChannelFull(loadData(0x2DC7)); return 0; }
+int ASound1::command73() { findFreeChannelFull(loadData(0x2DAC)); return 0; }
+
+int ASound1::command74() {
+ findFreeChannelFull(loadData(0x2DD5));
+ findFreeChannelFull(loadData(0x2DDE));
+ return 0;
+}
+
+int ASound1::command75() { findFreeChannelFull(loadData(0x2DF2)); return 0; }
+int ASound1::command76() { findFreeChannelFull(loadData(0x2E0D)); return 0; }
+
+int ASound1::command77() {
+ findFreeChannelFull(loadData(0x2E1C));
+ findFreeChannelFull(loadData(0x2E28));
+ return 0;
+}
+
+int ASound1::command78() { findFreeChannelFull(loadData(0x2E4C)); return 0; }
+int ASound1::command79() { findFreeChannelFull(loadData(0x2E34)); return 0; }
+int ASound1::command80() { findFreeChannelFull(loadData(0x2E5E)); return 0; }
+
+// command81 - loads the same data block twice (as in the original)
+int ASound1::command81() {
+ findFreeChannelFull(loadData(0x2EA4));
+ findFreeChannelFull(loadData(0x2EA4));
+ return 0;
+}
+
+int ASound1::command82() { findFreeChannelFull(loadData(0x2ECB)); return 0; }
+int ASound1::command83() { findFreeChannelFull(loadData(0x2EE3)); return 0; }
+int ASound1::command84() { findFreeChannelFull(loadData(0x2EF5)); return 0; }
+int ASound1::command85() { findFreeChannelFull(loadData(0x2EFF)); return 0; }
+
+// command86 - load directly into channel 7
+int ASound1::command86() { _channels[7]->load(loadData(0x2F0C)); return 0; }
+
+int ASound1::command87() {
+ findFreeChannelFull(loadData(0x2D67));
+ findFreeChannelFull(loadData(0x2D89));
+ return 0;
+}
+
+int ASound1::command88() { findFreeChannelFull(loadData(0x2F24)); return 0; }
+int ASound1::command89() { findFreeChannelFull(loadData(0x2F2E)); return 0; }
+
+int ASound1::command90() {
+ findFreeChannelFull(loadData(0x2CAA));
+ findFreeChannelFull(loadData(0x2CD4));
+ return 0;
+}
+
+int ASound1::command91() {
+ findFreeChannelFull(loadData(0x2CDF));
+ findFreeChannelFull(loadData(0x2D15));
+ return 0;
+}
+
+// command92 = nullptr (no-op, slot mapped to null in _commandList)
+
+int ASound1::command93() {
+ findFreeChannelFull(loadData(0x2F3C));
+ findFreeChannelFull(loadData(0x2F45));
+ return 0;
+}
+
+int ASound1::command94() { findFreeChannelFull(loadData(0x2F57)); return 0; }
+
+// command95 - four-voice piece using findFreeChannel (not Full)
+int ASound1::command95() {
+ findFreeChannel(loadData(0x2F6B));
+ findFreeChannel(loadData(0x2FD7));
+ findFreeChannel(loadData(0x302E));
+ findFreeChannel(loadData(0x3045));
+ return 0;
+}
+
+int ASound1::command96() { findFreeChannelFull(loadData(0x305C)); return 0; }
+
+// command97 = sub_11F98 in original
+int ASound1::command97() {
+ findFreeChannelFull(loadData(0x307E));
+ findFreeChannelFull(loadData(0x3094));
+ return 0;
+}
+
+// command98 = nullptr (no-op, slot mapped to null in _commandList)
+
+int ASound1::command99() { findFreeChannelFull(loadData(0x30B0)); return 0; }
+int ASound1::command100() { findFreeChannelFull(loadData(0x30BA)); return 0; }
+int ASound1::command101() { findFreeChannelFull(loadData(0x30C4)); return 0; }
+
/*-----------------------------------------------------------------------*/
/*-----------------------------------------------------------------------*/
/* ASound2 (asound.dr2) *
*-----------------------------------------------------------------------*/
-const ASound2::CommandPtr ASound2::_commandList[73] = {
- // commands 0-8 (asound_commands1)
+const ASound2::CommandPtr ASound2::_commandList[76] = {
+ // 0-8: delegate to base
&ASound2::command0, &ASound2::command1, &ASound2::command2, &ASound2::command3,
&ASound2::command4, &ASound2::command5, &ASound2::command6, &ASound2::command7,
&ASound2::command8,
- // 9-15 absent
+ // 9-15: nullptr
nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
- // command 16 (asound_commands2)
- &ASound2::command16,
- // 17-23 absent
- nullptr, nullptr, nullptr,
+ // 16-19: off_11A26
+ &ASound2::command16, &ASound2::command17, &ASound2::command18, nullptr,
+ // 20-23: nullptr
nullptr, nullptr, nullptr, nullptr,
- // commands 24-27 (asound_commands3)
+ // 24-31: off_11A2E
&ASound2::command24, &ASound2::command25, &ASound2::command26, &ASound2::command27,
- // 28-31 absent
- nullptr, nullptr, nullptr, nullptr,
- // commands 32-35 (asound_commands4)
+ &ASound2::command28, &ASound2::command29, &ASound2::command30, &ASound2::command31,
+ // 32-36: funcs_11C87 (slot 36 = no-op)
&ASound2::command32, &ASound2::command33, &ASound2::command34, &ASound2::command35,
- // 36-63 absent
+ nullptr,
+ // 37-63: nullptr
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- // commands 64-72 (asound_commands5)
- &ASound2::command64, &ASound2::command65, &ASound2::command66, &ASound2::command67,
- &ASound2::command68, &ASound2::command69, &ASound2::command70, &ASound2::command71,
- &ASound2::command72
+ nullptr, nullptr, nullptr,
+ // 64-75: off_11A4A (slots 73-75 = no-ops)
+ &ASound2::command64, &ASound2::command65, &ASound2::command66, &ASound2::command67,
+ &ASound2::command68, &ASound2::command69_70, &ASound2::command69_70, &ASound2::command71,
+ &ASound2::command72, nullptr, nullptr, nullptr
};
ASound2::ASound2(Audio::Mixer *mixer, OPL::OPL *opl)
: ASound(mixer, opl, "asound.dr2", 0x1fa0, 0x2950) {
- // Load sound samples
auto samplesStream = getDataStream(0x1dc);
for (int i = 0; i < 182; ++i)
_samples.push_back(AdlibSample(samplesStream));
}
int ASound2::command(int commandId, int param) {
- if (commandId > 72 || !_commandList[commandId])
+ if (commandId > 75 || !_commandList[commandId])
return 0;
-
+ if (commandId >= 32 && commandId < 64)
+ setMusicIndex(commandId);
return (this->*_commandList[commandId])();
}
-// commands 0-8: delegate to base ASound
int ASound2::command0() { return ASound::command0(); }
int ASound2::command1() { return ASound::command1(); }
int ASound2::command2() { return ASound::command2(); }
@@ -411,165 +797,228 @@ int ASound2::command6() { return ASound::command6(); }
int ASound2::command7() { return ASound::command7(); }
int ASound2::command8() { return ASound::command8(); }
-// ---------------------------------------------------------------------------
-// command16 - isSoundActive guard, command1, load ch0-5
-// ---------------------------------------------------------------------------
+void ASound2::loadCommand16() {
+ resetCallbackTimer(0x60);
+ setMusicIndex(0x10);
+ ASound::command1();
+ _channels[0]->load(loadData(0x2584));
+ _channels[1]->load(loadData(0x25E0));
+ _channels[2]->load(loadData(0x2630));
+ _channels[3]->load(loadData(0x2640));
+}
+
int ASound2::command16() {
- byte *pData = loadData(0x0C36);
+ byte *pData = loadData(0x2584);
if (!isSoundActive(pData)) {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand16));
+ else
+ loadCommand16();
+ }
+ return 0;
+}
+
+int ASound2::command17() {
+ byte *pData = loadData(0x2268);
+ if (!isSoundActive(pData)) {
+ resetCallbackTimer(0x60);
ASound::command1();
_channels[0]->load(pData);
- _channels[1]->load(loadData(0x0C8E));
- _channels[2]->load(loadData(0x0CF4));
- _channels[3]->load(loadData(0x0D4E));
- _channels[4]->load(loadData(0x0DA3));
- _channels[5]->load(loadData(0x0DB1));
+ _channels[1]->load(loadData(0x229D));
+ _channels[2]->load(loadData(0x22D0));
+ _channels[3]->load(loadData(0x22E9));
+ _channels[4]->load(loadData(0x231C));
+ _channels[5]->load(loadData(0x232A));
+ _channels[8]->load(loadData(0x2342));
}
return 0;
}
-// ---------------------------------------------------------------------------
-// commands 24-27 (asound_commands3) - upper channel pool
-// ---------------------------------------------------------------------------
+int ASound2::command18() {
+ ASound::command1();
+ if (getMusicIndex() <= 18)
+ return command16();
+ return (this->*_commandList[getMusicIndex()])();
+}
int ASound2::command24() {
- playSound(0x1A4A);
- playSound(0x1A7D);
+ playSound(0x237B);
+ playSound(0x23B0);
return 0;
}
int ASound2::command25() {
- playSound(0x1AAB);
- playSound(0x1AD7);
+ playSound(0x23E0);
+ playSound(0x240E);
return 0;
}
int ASound2::command26() {
- playSound(0x1B05);
+ playSound(0x243E);
return 0;
}
int ASound2::command27() {
- playSound(0x1B11);
+ playSound(0x244A);
return 0;
}
-// ---------------------------------------------------------------------------
-// commands 32-35 (asound_commands4)
-// ---------------------------------------------------------------------------
+int ASound2::command28() {
+ playSound(0x2569);
+ return 0;
+}
-// command32 - command1, six loadAny calls from channel 0
-int ASound2::command32() {
+int ASound2::command29() {
+ playSound(0x247E);
+ return 0;
+}
+
+int ASound2::command30() {
+ playSound(0x24B6);
+ return 0;
+}
+
+int ASound2::command31() {
+ *loadData(0x251A) = 0x67;
+ playSound(0x2517);
+ return 0;
+}
+
+void ASound2::loadCommand32() {
+ resetCallbackTimer(0x60);
ASound::command1();
- findFreeChannel(loadData(0x1BE4));
- findFreeChannel(loadData(0x1CB7));
- findFreeChannel(loadData(0x1E1E));
- findFreeChannel(loadData(0x1EC8));
- findFreeChannel(loadData(0x1ED8));
- findFreeChannel(loadData(0x1EEF));
+ _channels[0]->load(loadData(0x1192));
+ _channels[1]->load(loadData(0x1201));
+ _channels[2]->load(loadData(0x127A));
+ _channels[3]->load(loadData(0x1360));
+ _channels[4]->load(loadData(0x144A));
+ _channels[5]->load(loadData(0x1514));
+}
+
+int ASound2::command32() {
+ byte *pData = loadData(0x1192);
+ if (!isSoundActive(pData)) {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand32));
+ else
+ loadCommand32();
+ }
return 0;
}
-// command33 - isSoundActive guard, command1, load ch0-7
+void ASound2::loadCommand33() {
+ resetCallbackTimer(0x60);
+ ASound::command1();
+ _channels[0]->load(loadData(0x155E));
+ _channels[1]->load(loadData(0x162B));
+ _channels[2]->load(loadData(0x16E9));
+ _channels[3]->load(loadData(0x17B2));
+ _channels[4]->load(loadData(0x189C));
+ _channels[5]->load(loadData(0x1921));
+ _channels[8]->load(loadData(0x19DF));
+}
+
int ASound2::command33() {
- byte *pData = loadData(0x1B62);
+ byte *pData = loadData(0x155E);
if (!isSoundActive(pData)) {
- ASound::command1();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x1B97));
- _channels[2]->load(loadData(0x1BA5));
- _channels[3]->load(loadData(0x1BB3));
- _channels[4]->load(loadData(0x1BC1));
- _channels[5]->load(loadData(0x1BC5));
- _channels[6]->load(loadData(0x1BC9));
- _channels[7]->load(loadData(0x1BD5));
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand33));
+ else
+ loadCommand33();
}
return 0;
}
-// command34 - isSoundActive guard, command1, load ch0-6
+void ASound2::loadCommand34() {
+ resetCallbackTimer(0x60);
+ ASound::command1();
+ _channels[0]->load(loadData(0x1FDA));
+ _channels[1]->load(loadData(0x2071));
+ _channels[2]->load(loadData(0x20C0));
+ _channels[3]->load(loadData(0x2116));
+ _channels[4]->load(loadData(0x2126));
+ _channels[5]->load(loadData(0x2180));
+ _channels[8]->load(loadData(0x2190));
+}
+
int ASound2::command34() {
- byte *pData = loadData(0x0DC0);
+ byte *pData = loadData(0x1FDA);
if (!isSoundActive(pData)) {
- ASound::command1();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x0FAF));
- _channels[2]->load(loadData(0x1206));
- _channels[3]->load(loadData(0x139A));
- _channels[4]->load(loadData(0x1565));
- _channels[5]->load(loadData(0x1833));
- _channels[6]->load(loadData(0x18CD));
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand34));
+ else
+ loadCommand34();
}
return 0;
}
-// command35 - isSoundActive guard, command1, load ch0-6
+void ASound2::loadCommand35() {
+ resetCallbackTimerEx(0xC0, 0x50);
+ ASound::command1();
+ _channels[0]->load(loadData(0x1A2C));
+ _channels[1]->load(loadData(0x1B74));
+ _channels[2]->load(loadData(0x1C06));
+ _channels[3]->load(loadData(0x1CE3));
+ _channels[4]->load(loadData(0x1DDE));
+ _channels[5]->load(loadData(0x1E91));
+ _channels[8]->load(loadData(0x1F62));
+}
+
int ASound2::command35() {
- byte *pData = loadData(0x1F02);
+ byte *pData = loadData(0x1A2C);
if (!isSoundActive(pData)) {
- ASound::command1();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x1F66));
- _channels[2]->load(loadData(0x1F70));
- _channels[3]->load(loadData(0x1F8D));
- _channels[4]->load(loadData(0x1FCE));
- _channels[5]->load(loadData(0x1FF7));
- _channels[6]->load(loadData(0x202E));
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand35));
+ else
+ loadCommand35();
}
return 0;
}
-// ---------------------------------------------------------------------------
-// commands 64-72 (asound_commands5) - upper channel pool
-// ---------------------------------------------------------------------------
-
int ASound2::command64() {
- playSound(0x1928);
+ playSound(0x219E);
+ playSound(0x21A8);
return 0;
}
int ASound2::command65() {
- playSound(0x193C);
+ playSound(0x1F92);
+ playSound(0x1FAB);
+ playSound(0x1FBE);
+ playSound(0x1FC9);
return 0;
}
int ASound2::command66() {
- playSound(0x1946);
- playSound(0x195C);
+ playSound(0x21B2);
return 0;
}
int ASound2::command67() {
- playSound(0x196D);
+ playSound(0x21BC);
+ playSound(0x21C5);
return 0;
}
-// no-op
int ASound2::command68() {
+ playSound(0x21E7);
return 0;
}
-int ASound2::command69() {
- playSound(0x197F);
- playSound(0x19A5);
- playSound(0x19CB);
- return 0;
-}
-
-int ASound2::command70() {
- playSound(0x19E5);
- playSound(0x19F1);
+int ASound2::command69_70() {
+ playSound(0x24EF);
+ playSound(0x2503);
return 0;
}
int ASound2::command71() {
- playSound(0x19FF);
+ playSound(0x21FC);
return 0;
}
int ASound2::command72() {
- playSound(0x1A0D);
- playSound(0x1A10);
+ playSound(0x2242);
+ playSound(0x224F);
return 0;
}
@@ -1393,25 +1842,32 @@ int ASound6::command8() {
/*-----------------------------------------------------------------------*/
-const ASound9::CommandPtr ASound9::_commandList[72] = {
- &ASound9::command0, &ASound9::command1, &ASound9::command2, &ASound9::command3,
- &ASound9::command4, &ASound9::command5, &ASound9::command6, &ASound9::command7,
- &ASound9::command8, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- &ASound9::command16, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- &ASound9::command24, &ASound9::command25, &ASound9::command26, &ASound9::command27,
- nullptr, nullptr, nullptr, nullptr,
- &ASound9::command32, &ASound9::command33, &ASound9::command34, &ASound9::command35,
- &ASound9::command36, &ASound9::command37, &ASound9::command38, &ASound9::command39,
- nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr,
- &ASound9::command64, &ASound9::command65, &ASound9::command66, &ASound9::command67,
- &ASound9::command68, &ASound9::command69, &ASound9::command70, &ASound9::command71
+const ASound9::CommandPtr ASound9::_commandList[65] = {
+ // 0-8: delegate to base
+ &ASound9::command0, &ASound9::command1, &ASound9::command2, &ASound9::command3,
+ &ASound9::command4, &ASound9::command5, &ASound9::command6, &ASound9::command7,
+ &ASound9::command8,
+ // 9-15: nullptr (not in any dispatch range)
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ // 16-19: nullptr (off_11A26, all null)
+ nullptr, nullptr, nullptr, nullptr,
+ // 20-23: nullptr
+ nullptr, nullptr, nullptr, nullptr,
+ // 24-31: nullptr (off_11A2E, all null)
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ // 32-63: music/SFX (off_11A3E)
+ &ASound9::command32, &ASound9::command33_47, &ASound9::command34, &ASound9::command35,
+ &ASound9::command36, &ASound9::command37, &ASound9::command38, &ASound9::command39,
+ &ASound9::command40, &ASound9::command41, &ASound9::command42, &ASound9::command43,
+ nullptr, &ASound9::command45, &ASound9::command46, &ASound9::command33_47,
+ &ASound9::command48, &ASound9::command49, &ASound9::command50, &ASound9::command51,
+ &ASound9::command52, &ASound9::command53, &ASound9::command54, &ASound9::command55,
+ nullptr, &ASound9::command57, &ASound9::command58, &ASound9::command59,
+ nullptr, &ASound9::command61, &ASound9::command62, &ASound9::command63,
+ // 64: nullptr (off_11A80, single null entry)
+ nullptr,
};
ASound9::ASound9(Audio::Mixer *mixer, OPL::OPL *opl) :
@@ -1423,207 +1879,559 @@ ASound9::ASound9(Audio::Mixer *mixer, OPL::OPL *opl) :
}
int ASound9::command(int commandId, int param) {
- if (commandId > 71 || !_commandList[commandId])
+ if (commandId > 64 || !_commandList[commandId])
return 0;
-
return (this->*_commandList[commandId])();
}
-// commands 0-8: delegate to base ASound
-int ASound9::command0() {
- return ASound::command0();
+// Commands 0-8: delegate to base
+int ASound9::command0() { return ASound::command0(); }
+int ASound9::command1() { return ASound::command1(); }
+int ASound9::command2() { return ASound::command2(); }
+int ASound9::command3() { return ASound::command3(); }
+int ASound9::command4() { return ASound::command4(); }
+int ASound9::command5() { return ASound::command5(); }
+int ASound9::command6() { return ASound::command6(); }
+int ASound9::command7() { return ASound::command7(); }
+int ASound9::command8() { return ASound::command8(); }
+
+// ---------------------------------------------------------------------------
+// Music command 32 â deferred loader (isMusicChannelsActive pattern B)
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand32() {
+ ASound::command1();
+ resetCallbackTimerEx(0x62, 0x54);
+ _channels[0]->load(loadData(0x1192));
+ _channels[1]->load(loadData(0x11DA));
+ _channels[2]->load(loadData(0x128F));
+ _channels[3]->load(loadData(0x12C2));
+ _channels[4]->load(loadData(0x13FB));
+ _channels[5]->load(loadData(0x156B));
+ _channels[8]->load(loadData(0x14EA));
}
-int ASound9::command1() {
- return ASound::command1();
+
+int ASound9::command32() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand32));
+ else
+ loadCommand32();
+ return 0;
}
-int ASound9::command2() {
- return ASound::command2();
+
+// ---------------------------------------------------------------------------
+// Music command 33/47 â shared deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand33_47() {
+ ASound::command1();
+ resetCallbackTimerEx(0x54, 0x46);
+ _channels[0]->load(loadData(0x21A6));
+ _channels[1]->load(loadData(0x2219));
+ _channels[2]->load(loadData(0x225F));
+ _channels[4]->load(loadData(0x239A));
+ _channels[5]->load(loadData(0x23FC));
+ _channels[6]->load(loadData(0x2563));
+ _channels[3]->load(loadData(0x264A));
+ _channels[7]->load(loadData(0x2657));
+ _channels[8]->load(loadData(0x231E));
}
-int ASound9::command3() {
- return ASound::command3();
+
+int ASound9::command33_47() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand33_47));
+ else
+ loadCommand33_47();
+ return 0;
}
-int ASound9::command4() {
- return ASound::command4();
+
+// ---------------------------------------------------------------------------
+// Music command 34
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand34() {
+ ASound::command1();
+ resetCallbackTimer(0x38);
+ _channels[0]->load(loadData(0x2664));
+ _channels[1]->load(loadData(0x285C));
+ _channels[2]->load(loadData(0x2A61));
+ _channels[8]->load(loadData(0x2C64));
+ _channels[4]->load(loadData(0x2DE3));
+ _channels[5]->load(loadData(0x2EE6));
+ _channels[6]->load(loadData(0x2F31));
}
-int ASound9::command5() {
- return ASound::command5();
+
+int ASound9::command34() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand34));
+ else
+ loadCommand34();
+ return 0;
}
-int ASound9::command6() {
- return ASound::command6();
+
+// ---------------------------------------------------------------------------
+// Music command 35
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand35() {
+ ASound::command1();
+ resetCallbackTimer(0x50);
+ _channels[0]->load(loadData(0x2F7E));
+ _channels[1]->load(loadData(0x2FC7));
+ _channels[2]->load(loadData(0x300E));
+ _channels[3]->load(loadData(0x30D2));
+ _channels[8]->load(loadData(0x3148));
+ _channels[5]->load(loadData(0x31D2));
+ _channels[6]->load(loadData(0x326A));
}
-int ASound9::command7() {
- return ASound::command7();
+
+int ASound9::command35() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand35));
+ else
+ loadCommand35();
+ return 0;
}
-int ASound9::command8() {
- return ASound::command8();
+
+// ---------------------------------------------------------------------------
+// Music command 36
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand36() {
+ ASound::command1();
+ resetCallbackTimer(0x28);
+ _channels[0]->load(loadData(0x33D4));
+ _channels[1]->load(loadData(0x345F));
+ _channels[2]->load(loadData(0x34D5));
+ _channels[3]->load(loadData(0x3505));
+ _channels[4]->load(loadData(0x37D1));
+ _channels[5]->load(loadData(0x3895));
+ _channels[8]->load(loadData(0x383D));
}
-int ASound9::command24() {
- playSound(0x203E);
- playSound(0x2071);
+int ASound9::command36() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand36));
+ else
+ loadCommand36();
return 0;
}
-int ASound9::command25() {
- playSound(0x209F);
- playSound(0x20CB);
- return 0;
+// ---------------------------------------------------------------------------
+// Music command 37
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand37() {
+ ASound::command1();
+ resetCallbackTimer(0x50);
+ _channels[0]->load(loadData(0x38D8));
+ _channels[1]->load(loadData(0x393D));
+ _channels[2]->load(loadData(0x39A1));
+ _channels[3]->load(loadData(0x3A05));
+ _channels[4]->load(loadData(0x3A89));
+ _channels[8]->load(loadData(0x3A97));
+ _channels[5]->load(loadData(0x3AF3));
}
-int ASound9::command26() {
- playSound(0x20F9);
+int ASound9::command37() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand37));
+ else
+ loadCommand37();
return 0;
}
-int ASound9::command27() {
- playSound(0x2105);
+// ---------------------------------------------------------------------------
+// Music command 38
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand38() {
+ ASound::command1();
+ resetCallbackTimer(0x28);
+ _channels[0]->load(loadData(0x3C5C));
+ _channels[1]->load(loadData(0x3CE9));
+ _channels[2]->load(loadData(0x3D69));
+ _channels[3]->load(loadData(0x3505));
+ _channels[4]->load(loadData(0x3D95));
+ _channels[8]->load(loadData(0x3DF9));
+ _channels[6]->load(loadData(0x3E45));
+}
+
+int ASound9::command38() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand38));
+ else
+ loadCommand38();
return 0;
}
-int ASound9::command32() {
+// ---------------------------------------------------------------------------
+// Music command 39
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand39() {
ASound::command1();
- findFreeChannel(loadData(0x2B16));
- findFreeChannel(loadData(0x2B6C));
- findFreeChannel(loadData(0x2BB6));
- findFreeChannel(loadData(0x2E88));
- findFreeChannel(loadData(0x2E98));
- findFreeChannel(loadData(0x2EA3));
- findFreeChannel(loadData(0x2EAE));
- findFreeChannel(loadData(0x2EB7));
+ resetCallbackTimer(0x28);
+ _channels[0]->load(loadData(0x3E6E));
+ _channels[1]->load(loadData(0x3EE3));
+ _channels[2]->load(loadData(0x3F55));
+ _channels[3]->load(loadData(0x4049));
+ _channels[4]->load(loadData(0x44E3));
+ _channels[5]->load(loadData(0x459B));
+ _channels[8]->load(loadData(0x4571));
+}
+
+int ASound9::command39() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand39));
+ else
+ loadCommand39();
return 0;
}
-int ASound9::command33() {
+// ---------------------------------------------------------------------------
+// Music command 40
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand40() {
+ ASound::command1();
+ resetCallbackTimer(0x38);
+ _channels[0]->load(loadData(0x2664));
+ _channels[1]->load(loadData(0x285C));
+ _channels[2]->load(loadData(0x2A61));
+ _channels[8]->load(loadData(0x4692));
+ _channels[4]->load(loadData(0x479F));
+ _channels[5]->load(loadData(0x4841));
+ _channels[6]->load(loadData(0x4875));
+}
+
+int ASound9::command40() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand40));
+ else
+ loadCommand40();
return 0;
}
-int ASound9::command34() {
+// ---------------------------------------------------------------------------
+// Music command 41
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand41() {
ASound::command1();
- _channels[0]->load(loadData(0x31D0));
- _channels[1]->load(loadData(0x3221));
- _channels[2]->load(loadData(0x3282));
- _channels[3]->load(loadData(0x32CB));
- _channels[4]->load(loadData(0x331A));
- _channels[5]->load(loadData(0x3369));
- _channels[6]->load(loadData(0x33B0));
+ resetCallbackTimer(0x54);
+ _channels[0]->load(loadData(0x157A));
+ _channels[1]->load(loadData(0x1629));
+ _channels[2]->load(loadData(0x1A68));
+ _channels[8]->load(loadData(0x1BA2));
+ _channels[4]->load(loadData(0x1C58));
+ _channels[5]->load(loadData(0x1D38));
+ _channels[6]->load(loadData(0x1FC2));
+}
+
+int ASound9::command41() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand41));
+ else
+ loadCommand41();
return 0;
}
-int ASound9::command35() {
+// ---------------------------------------------------------------------------
+// Music command 42
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand42() {
ASound::command1();
- _channels[0]->load(loadData(0x295E));
- _channels[1]->load(loadData(0x299E));
- _channels[2]->load(loadData(0x29C3));
- _channels[3]->load(loadData(0x29E8));
- _channels[4]->load(loadData(0x2A46));
- _channels[5]->load(loadData(0x2AA5));
- _channels[6]->load(loadData(0x2AE0));
+ resetCallbackTimerEx(0xA8, 0x50);
+ _channels[0]->load(loadData(0x15B3));
+ _channels[1]->load(loadData(0x163A));
+ _channels[2]->load(loadData(0x1AE1));
+ _channels[3]->load(loadData(0x1613));
+ _channels[8]->load(loadData(0x1BE7));
+ _channels[4]->load(loadData(0x1C8F));
+ _channels[5]->load(loadData(0x1E1B));
+ _channels[6]->load(loadData(0x205F));
+}
+
+int ASound9::command42() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand42));
+ else
+ loadCommand42();
return 0;
}
-int ASound9::command36() {
+// ---------------------------------------------------------------------------
+// Music command 43 â direct load (no deferred path)
+// ---------------------------------------------------------------------------
+
+int ASound9::command43() {
ASound::command1();
- _channels[0]->load(loadData(0x30AA));
- _channels[1]->load(loadData(0x30DD));
- _channels[2]->load(loadData(0x3109));
- _channels[3]->load(loadData(0x313D));
- _channels[4]->load(loadData(0x3175));
- _channels[5]->load(loadData(0x319B));
+ resetCallbackTimer(0x60);
+ _channels[0]->load(loadData(0x4A00));
+ _channels[1]->load(loadData(0x4A5F));
+ _channels[2]->load(loadData(0x4AAB));
+ _channels[3]->load(loadData(0x4D7D));
+ _channels[4]->load(loadData(0x4D93));
+ _channels[5]->load(loadData(0x4D9E));
+ _channels[6]->load(loadData(0x4DA9));
+ _channels[7]->load(loadData(0x4DB2));
return 0;
}
-int ASound9::command37() {
+// ---------------------------------------------------------------------------
+// SFX commands 45, 46
+// ---------------------------------------------------------------------------
+
+int ASound9::command45() {
+ playSound(0x48AA);
+ playSound(0x490A);
+ return 0;
+}
+
+int ASound9::command46() {
+ playSound(0x4955);
+ playSound(0x49AA);
+ return 0;
+}
+
+// ---------------------------------------------------------------------------
+// SFX commands 48, 49, 50
+// ---------------------------------------------------------------------------
+
+int ASound9::command48() { playSound(0x54C3); return 0; }
+int ASound9::command49() { playSound(0x54CD); return 0; }
+int ASound9::command50() { playSound(0x54D7); return 0; }
+
+// ---------------------------------------------------------------------------
+// Music command 51 â deferred loader (timer set before command1)
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand51() {
+ resetCallbackTimer(0x60);
ASound::command1();
- _channels[0]->load(loadData(0x2156));
- _channels[1]->load(loadData(0x21A6));
- _channels[2]->load(loadData(0x228E));
- _channels[3]->load(loadData(0x22F7));
- _channels[4]->load(loadData(0x2351));
- _channels[5]->load(loadData(0x25A8));
- _channels[6]->load(loadData(0x28BF));
+ _channels[0]->load(loadData(0x4DBE));
+ _channels[1]->load(loadData(0x4E56));
+ _channels[2]->load(loadData(0x4EDC));
+ _channels[3]->load(loadData(0x4F32));
+ _channels[4]->load(loadData(0x4F62));
+ _channels[5]->load(loadData(0x4F75));
+ _channels[6]->load(loadData(0x4F84));
+ _channels[7]->load(loadData(0x4F95));
+}
+
+int ASound9::command51() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand51));
+ else
+ loadCommand51();
return 0;
}
-int ASound9::command38() {
- byte *pData = loadData(0x11BC);
+// ---------------------------------------------------------------------------
+// Music command 52 â isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand52() {
+ resetCallbackTimer(0x54);
+ ASound::command1();
+ _channels[0]->load(loadData(0x4FAA));
+ _channels[1]->load(loadData(0x5211));
+ _channels[2]->load(loadData(0x524A));
+ _channels[3]->load(loadData(0x526E));
+ _channels[4]->load(loadData(0x5469));
+ _channels[5]->load(loadData(0x5475));
+ _channels[6]->load(loadData(0x548E));
+}
+
+int ASound9::command52() {
+ byte *pData = loadData(0x4FAA);
if (!isSoundActive(pData)) {
- ASound::command1();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x1477));
- _channels[2]->load(loadData(0x158D));
- _channels[3]->load(loadData(0x1777));
- _channels[4]->load(loadData(0x1977));
- _channels[5]->load(loadData(0x1BC5));
- _channels[6]->load(loadData(0x1CFF));
- _channels[7]->load(loadData(0x1EAF));
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand52));
+ else
+ loadCommand52();
}
return 0;
}
-int ASound9::command39() {
- byte *pData = loadData(0x0C36);
+// ---------------------------------------------------------------------------
+// Command 53 â delayed fade: arms a one-shot timer to call command1
+// ---------------------------------------------------------------------------
+
+void ASound9::command53_loader() {
+ ASound::command1();
+}
+
+int ASound9::command53() {
+ resetCallbackTimer(0x708);
+ scheduleCallback(MAKE_CALLBACK(ASound9, command53_loader));
+ return 0;
+}
+
+// ---------------------------------------------------------------------------
+// Music command 54 â isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand54() {
+ resetCallbackTimer(0x60);
+ ASound::command1();
+ _channels[0]->load(loadData(0x5548));
+ _channels[1]->load(loadData(0x555E));
+ _channels[2]->load(loadData(0x556F));
+ _channels[3]->load(loadData(0x558F));
+}
+
+int ASound9::command54() {
+ byte *pData = loadData(0x5548);
if (!isSoundActive(pData)) {
- ASound::command0();
- _channels[0]->load(pData);
- _channels[1]->load(loadData(0x0D7D));
- _channels[2]->load(loadData(0x0E50));
- _channels[3]->load(loadData(0x0F1C));
- _channels[4]->load(loadData(0x0FCE));
- _channels[5]->load(loadData(0x10BA));
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand54));
+ else
+ loadCommand54();
}
return 0;
}
-int ASound9::command64() {
- playSound(0x2EC6);
+// ---------------------------------------------------------------------------
+// Music command 55 â isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand55() {
+ resetCallbackTimer(0x60);
+ ASound::command1();
+ _channels[0]->load(loadData(0x5712));
+ _channels[1]->load(loadData(0x57EA));
+ _channels[2]->load(loadData(0x5A1E));
+ _channels[3]->load(loadData(0x5ACA));
+ _channels[4]->load(loadData(0x5AEE));
+ _channels[5]->load(loadData(0x5B4E));
+ _channels[6]->load(loadData(0x5BBB));
+ _channels[7]->load(loadData(0x5BC8));
+ _channels[8]->load(loadData(0x5BE0));
+}
+
+int ASound9::command55() {
+ byte *pData = loadData(0x5712);
+ if (!isSoundActive(pData)) {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand55));
+ else
+ loadCommand55();
+ }
return 0;
}
-int ASound9::command65() {
- playSound(0x2EDA);
+// ---------------------------------------------------------------------------
+// Music command 57 â isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand57() {
+ resetCallbackTimer(0x30);
+ ASound::command1();
+ _channels[0]->load(loadData(0x5CD6));
+ _channels[1]->load(loadData(0x5D5E));
+ _channels[2]->load(loadData(0x5DD1));
+ _channels[3]->load(loadData(0x5E0F));
+ _channels[4]->load(loadData(0x5E54));
+ _channels[5]->load(loadData(0x5F36));
+ _channels[6]->load(loadData(0x5F97));
+ _channels[7]->load(loadData(0x5FEC));
+ _channels[8]->load(loadData(0x5ECD));
+}
+
+int ASound9::command57() {
+ byte *pData = loadData(0x5CD6);
+ if (!isSoundActive(pData)) {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand57));
+ else
+ loadCommand57();
+ }
return 0;
}
-int ASound9::command66() {
- _channels[0]->load(loadData(0x2EE4));
- _channels[1]->load(loadData(0x2F0E));
- _channels[2]->load(loadData(0x2F3E));
- _channels[3]->load(loadData(0x2F6E));
- _channels[4]->load(loadData(0x2EE4));
- _channels[5]->load(loadData(0x2F0E));
- _channels[6]->load(loadData(0x2F3E));
- _channels[7]->load(loadData(0x2F6E));
- return 0;
+// ---------------------------------------------------------------------------
+// Music command 58 â music-channel isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand58() {
+ resetCallbackTimer(0x90);
+ ASound::command1();
+ _channels[0]->load(loadData(0x5BEE));
+ _channels[1]->load(loadData(0x5C23));
+ _channels[2]->load(loadData(0x5C3E));
+ _channels[3]->load(loadData(0x5C7E));
+ _channels[4]->load(loadData(0x5C9D));
+ _channels[5]->load(loadData(0x5CA8));
+ _channels[6]->load(loadData(0x5CC8));
}
-int ASound9::command67() {
- _channels[6]->load(loadData(0x2F9E));
- _channels[7]->load(loadData(0x2FBD));
- _channels[8]->load(loadData(0x2FCC));
+int ASound9::command58() {
+ byte *pData = loadData(0x5BEE);
+ if (!isSoundActive(pData)) {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand58));
+ else
+ loadCommand58();
+ }
return 0;
}
-int ASound9::command68() {
- playSound(0x2FEB);
+// ---------------------------------------------------------------------------
+// Music command 59 â direct load (no deferred, no timer)
+// ---------------------------------------------------------------------------
+
+int ASound9::command59() {
+ ASound::command1();
+ _channels[0]->load(loadData(0x55AE));
+ _channels[1]->load(loadData(0x5637));
+ _channels[2]->load(loadData(0x5621));
+ _channels[3]->load(loadData(0x56DF));
return 0;
}
-int ASound9::command69() {
- playSound(0x2FF5);
- playSound(0x301B);
- playSound(0x3041);
+// ---------------------------------------------------------------------------
+// Command 61 â stop SFX channels then play three SFX voices
+// ---------------------------------------------------------------------------
+
+int ASound9::command61() {
+ ASound::command5();
+ playSound(0x54E1);
+ playSound(0x5507);
+ playSound(0x552D);
return 0;
}
-int ASound9::command70() {
- playSound(0x305B);
- playSound(0x3064);
+// ---------------------------------------------------------------------------
+// Music command 62 â deferred loader (channels 0-2, 5-6 only)
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand62() {
+ ASound::command1();
+ resetCallbackTimer(0x40);
+ _channels[0]->load(loadData(0x6006));
+ _channels[1]->load(loadData(0x6204));
+ _channels[2]->load(loadData(0x6409));
+ _channels[5]->load(loadData(0x660C));
+ _channels[6]->load(loadData(0x667F));
+}
+
+int ASound9::command62() {
+ if (isMusicChannelsActive())
+ scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand62));
+ else
+ loadCommand62();
return 0;
}
-int ASound9::command71() {
- playSound(0x306D);
- playSound(0x308A);
+// ---------------------------------------------------------------------------
+// SFX command 63
+// ---------------------------------------------------------------------------
+
+int ASound9::command63() {
+ playSound(0x66EC);
return 0;
}
diff --git a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
index 0476d3632be..57fba442974 100644
--- a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
+++ b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
@@ -42,37 +42,53 @@ public:
};
/**
- * ASound1 (asound.ph1, _dataOffset = 0x21e0)
+ * ASound1 (asound.dr1, _dataOffset = 0x2520, _dataSize = 0x49e0)
*
- * Dispatch table layout:
- * off_11C32: commands 0â8 (max=8, base=0)
- * off_11C44: command 16 (max=0x10, base=0x10, 1 entry)
- * off_11C46: commands 24â27 (max=0x1B, base=0x18, 4 entries)
- * off_11C4E: commands 32â39 (max=0x27, base=0x20, 8 entries)
+ * Dispatch table layout (five tables collapsed to flat [102]):
+ * off_11A14: commands 0â 8 (base=0, max=8)
+ * off_11A26: commands 16â19 (base=0x10, max=0x13; slot 19 = no-op)
+ * off_11A2E: commands 24â32 (base=0x18, max=0x20; slot 32 = no-op)
+ * funcs_12251: commands 32â49 (base=0x20, max=0x31; slot 49 = no-op)
+ * off_11A64: commands 64â101 (base=0x40, max=0x65)
+ * Slot 92 and slot 98 are no-ops (command98); slots 102â103 are
+ * nullsub_1/nullsub_4, both beyond the [102] array.
*
- * A fifth table (unk_13C3E, commands 64â76) exists but is encoded as raw
- * sound data bytes used as near-pointers â not reconstructible without the
- * binary. Those commands are silently ignored.
+ * word_12370 (_musicIndex in base): tracks the last music-piece launched
+ * via command18 for re-entry. Values <=0x12 use off_11A26; >0x12 use
+ * funcs_12251 with index = musicIndex - 0x20.
*
- * command16 (sub_11F70): random background-music selector. Checks whether
- * channel 0 is already playing one of the five known music pieces; if not,
- * randomly picks from four music loaders and plays it, storing the choice
- * in _musicIndex (mirrors word_11F5E in the original).
+ * Mutable sound-data bytes (modified before channel loads):
+ * _soundData[0x28C9] â pitch-bend variant byte (command43 / command48)
+ * _soundData[0x4680] â transposition byte for command31's block
*/
class ASound1 : public ASound {
private:
typedef int (ASound1::*CommandPtr)();
- static const CommandPtr _commandList[40];
-
- // Mirrors word_11F5E: tracks which music piece was last selected.
- int _musicIndex = 0;
-
- // Background-music loaders (targets of the CS:0x1F60 indirect table).
- int commandMusic0(); // sub_11D84 â starts at 0x1ECA
- int commandMusic1(); // sub_11EE6 â starts at 0x3418
- int commandMusic2(); // sub_11F0E â starts at 0x3688
- int commandMusic3(); // sub_11F36 â starts at 0x3D52
-
+ static const CommandPtr _commandList[102];
+
+ // --------------- deferred-load callbacks (void, no return) ------------
+ // Stored via reinterpret_cast<CallbackFunction> and fired by tickCallback.
+ void loadCommand16();
+ void loadCommand32();
+ void loadCommand33();
+ void loadCommand34();
+ void loadCommand35();
+ void loadCommand36();
+ void loadCommand37();
+ void loadCommand38();
+ void loadCommand39();
+ void loadCommand40();
+ void loadCommand41();
+ void loadCommand42();
+ void loadCommand44();
+ void loadCommand45();
+ void loadCommand46();
+ void loadCommand47();
+
+ // Shared pitch-bend loader called directly from command43 and command48.
+ void loadMusicPitchBend();
+
+ // --------------- command handlers (int, return 0) ----------------------
int command0();
int command1();
int command2();
@@ -84,11 +100,17 @@ private:
int command8();
int command16();
+ int command17();
+ int command18();
int command24();
int command25();
int command26();
int command27();
+ int command28();
+ int command29();
+ int command30();
+ int command31();
int command32();
int command33();
@@ -98,6 +120,52 @@ private:
int command37();
int command38();
int command39();
+ int command40();
+ int command41();
+ int command42();
+ int command43();
+ int command44();
+ int command45();
+ int command46();
+ int command47();
+ int command48();
+
+ 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();
+ int command79();
+ int command80();
+ int command81();
+ int command82();
+ int command83();
+ int command84();
+ int command85();
+ int command86();
+ int command87();
+ int command88();
+ int command89();
+ int command90();
+ int command91();
+ int command93();
+ int command94();
+ int command95();
+ int command96();
+ int command97();
+ int command99();
+ int command100();
+ int command101();
public:
ASound1(Audio::Mixer *mixer, OPL::OPL *opl);
@@ -106,51 +174,45 @@ public:
};
/**
- * ASound2 (asound.ph2, _dataOffset = 0x2040)
+ * ASound2 (asound.dr2, _dataOffset = 0x1FA0, _dataSize = 0x2950)
*
- * Dispatch table layout:
- * asound_commands1: commands 0â8 (max=8, base=0)
- * asound_commands2: command 16 (max=0x10, base=0x10, 1 entry)
- * asound_commands3: commands 24â27 (max=0x1B, base=0x18, 4 entries)
- * asound_commands4: commands 32â35 (max=0x23, base=0x20, 4 entries)
- * asound_commands5: commands 64â72 (max=0x48, base=0x40, 9 entries)
+ * Dispatch table layout (five tables collapsed to flat [76]):
+ * off_11A14: commands 0â8 (base=0, max=8)
+ * off_11A26: commands 16â19 (base=0x10, max=0x13; slot 19 = no-op)
+ * off_11A2E: commands 24â31 (base=0x18, max=0x1F; slot at cmd32 unreachable)
+ * funcs_11C87: commands 32â36 (base=0x20, max=0x24; slot 36 = no-op)
+ * off_11A4A: commands 64â75 (base=0x40, max=0x4B; slots 73â75 = no-ops)
+ *
+ * command16 sets _musicIndex = 0x10 for command18 re-entry.
+ * commands 32â35: _musicIndex saved by dispatcher for command18 re-entry.
*/
class ASound2 : public ASound {
private:
typedef int (ASound2::*CommandPtr)();
- static const CommandPtr _commandList[73];
+ static const CommandPtr _commandList[76];
- int command0();
- int command1();
- int command2();
- int command3();
- int command4();
- int command5();
- int command6();
- int command7();
+ // Deferred loader callbacks (void, Pattern B)
+ void loadCommand16();
+ void loadCommand32();
+ void loadCommand33();
+ void loadCommand34();
+ void loadCommand35();
+
+ int command0(); int command1(); int command2(); int command3();
+ int command4(); int command5(); int command6(); int command7();
int command8();
int command16();
+ int command17();
+ int command18();
- int command24();
- int command25();
- int command26();
- int command27();
+ int command24(); int command25(); int command26(); int command27();
+ int command28(); int command29(); int command30(); int command31();
- int command32();
- int command33();
- int command34();
- int command35();
+ 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();
+ int command64(); int command65(); int command66(); int command67();
+ int command68(); int command69_70(); int command71(); int command72();
public:
ASound2(Audio::Mixer *mixer, OPL::OPL *opl);
@@ -362,49 +424,49 @@ public:
class ASound9 : public ASound {
private:
typedef int (ASound9:: *CommandPtr)();
- int command0();
- int command1();
- int command2();
- int command3();
- int command4();
- int command5();
- int command6();
- int command7();
- int command8();
-
- int command16() {
- return command24();
- }
-
- int command24();
- int command25();
- int command26();
- int command27();
- int command32();
- int command33();
- int command34();
- int command35();
- int command36();
- int command37();
- int command38();
- int command39();
+ // Deferred loader helpers (void callbacks, Pattern B)
+ void loadCommand32();
+ void loadCommand33_47();
+ void loadCommand34();
+ void loadCommand35();
+ void loadCommand36();
+ void loadCommand37();
+ void loadCommand38();
+ void loadCommand39();
+ void loadCommand40();
+ void loadCommand41();
+ void loadCommand42();
+ void loadCommand51();
+ void loadCommand52();
+ void command53_loader();
+ void loadCommand54();
+ void loadCommand55();
+ void loadCommand57();
+ void loadCommand58();
+ void loadCommand62();
+
+ // Commands 0-8: delegate to base
+ int command0(); int command1(); int command2(); int command3();
+ int command4(); int command5(); int command6(); int command7();
+ int command8();
- int command64();
- int command65();
- int command66();
- int command67();
- int command68();
- int command69();
- int command70();
- int command71();
+ // Music commands 32-63
+ int command32(); int command33_47(); int command34(); int command35();
+ int command36(); int command37(); int command38(); int command39();
+ int command40(); int command41(); int command42(); int command43();
+ int command45(); int command46();
+ int command48(); int command49(); int command50();
+ int command51(); int command52(); int command53();
+ int command54(); int command55();
+ int command57(); int command58(); int command59();
+ int command61(); int command62(); int command63();
- static const CommandPtr _commandList[72];
+ static const CommandPtr _commandList[65];
public:
ASound9(Audio::Mixer *mixer, OPL::OPL *opl);
- ~ASound9() override {
- }
+ ~ASound9() override {}
int command(int commandId, int param) override;
};
Commit: 60143443a6daa427a2f05ab8b842fde6d96c0393
https://github.com/scummvm/scummvm/commit/60143443a6daa427a2f05ab8b842fde6d96c0393
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-05-25T09:42:29+10:00
Commit Message:
MADS: DRAGONSPHERE: Add missing entries to the words.h enum
Changed paths:
engines/mads/madsv2/dragonsphere/mads/words.h
diff --git a/engines/mads/madsv2/dragonsphere/mads/words.h b/engines/mads/madsv2/dragonsphere/mads/words.h
index 41c8190dfee..04a219259ee 100644
--- a/engines/mads/madsv2/dragonsphere/mads/words.h
+++ b/engines/mads/madsv2/dragonsphere/mads/words.h
@@ -29,99 +29,107 @@ namespace MADSV2 {
namespace Dragonsphere {
enum {
- words_look = 3,
- words_take = 4,
- words_push = 5,
- words_open = 6,
- words_put = 7,
- words_talk_to = 8,
- words_give = 9,
- words_pull = 10,
- words_close = 11,
- words_throw = 12,
- words_walk_to = 13,
- words_floor = 16,
- words_walk_across = 17,
- words_rug = 18,
- words_carpet = 19,
- words_wall = 20,
- words_bed = 21,
- words_pillow = 22,
- words_chest = 23,
- words_window = 24,
- words_nightstand = 25,
- words_tapestry = 26,
- words_dressing_screen = 27,
- words_walk_behind = 28,
- words_royal_crest = 29,
- words_look_at = 30,
- words_bedroom_wall = 31,
- words_castle_walls = 32,
- words_book = 33,
- words_fireplace = 34,
- words_door_to_queens_room = 36,
- words_fireplace_screen = 35,
- words_exit = 36,
- words_walk_through = 37,
- words_hall_to_south = 38,
- words_walk_into = 39,
- words_wall_plaque = 40,
- words_decoration = 41,
- words_swords = 42,
- words_bust = 44,
- words_arch = 45,
- words_signet_ring = 46,
- words_invoke = 47,
- words_polish = 48,
- words_bird_figurine = 50,
- words_rub = 51,
- words_birdcall = 52,
- words_make_noise = 54,
- words_shieldstone = 55,
- words_sword = 56,
- words_attack = 57,
- words_carve_up = 58,
- words_goblet = 59,
- words_fill = 60,
- words_drink_from = 61,
- words_bone = 62,
- words_gnaw = 63,
- words_fruit = 64,
- words_eat = 65,
- words_doll = 66,
- words_play_with = 67,
- words_heal = 68,
- words_heal_self = 70,
- words_polystone = 71,
- words_mimic = 72,
- words_red_powerstone = 73,
- words_yellow_powerstone = 74,
- words_blue_powerstone = 75,
- words_key_crown = 76,
- words_wear = 77,
- words_dates = 78,
- words_statue = 79,
- words_bottle_of_flies = 80,
- words_listen_to = 81,
- words_soul_egg = 82,
- words_break = 83,
- words_magic_belt = 84,
- words_adjust = 85,
- words_amulet = 86,
- words_thrust = 87,
- words_mud = 88,
- words_feel = 89,
- words_taste = 90,
- words_feathers = 91,
- words_torch = 93,
- words_wave = 94,
- words_flask = 95,
- words_flask_full_of_acid = 96,
- words_pour = 98,
- words_pour_contents_of = 99,
+ words_look = 3,
+ words_take = 4,
+ words_push = 5,
+ words_open = 6,
+ words_put = 7,
+ words_talk_to = 8,
+ words_give = 9,
+ words_pull = 10,
+ words_close = 11,
+ words_throw = 12,
+ words_walk_to = 13,
+ words_nothing = 14,
+ words_floor = 16,
+ words_walk_across = 17,
+ words_rug = 18,
+ words_carpet = 19,
+ words_wall = 20,
+ words_bed = 21,
+ words_pillow = 22,
+ words_chest = 23,
+ words_window = 24,
+ words_nightstand = 25,
+ words_tapestry = 26,
+ words_dressing_screen = 27,
+ words_walk_behind = 28,
+ words_royal_crest = 29,
+ words_look_at = 30,
+ words_bedroom_wall = 31,
+ words_castle_walls = 32,
+ words_book = 33,
+ words_fireplace = 34,
+ words_fireplace_screen = 35,
+ words_door_to_queens_room = 36,
+ words_exit = 36,
+ words_walk_through = 37,
+ words_hall_to_south = 38,
+ words_walk_into = 39,
+ words_wall_plaque = 40,
+ words_decoration = 41,
+ words_swords = 42,
+ words_wall_sconce = 43,
+ words_bust = 44,
+ words_arch = 45,
+ words_signet_ring = 46,
+ words_invoke = 47,
+ words_polish = 48,
+ words_gangbang = 49,
+ words_bird_figurine = 50,
+ words_rub = 51,
+ words_birdcall = 52,
+ words_use = 53,
+ words_make_noise = 54,
+ words_shieldstone = 55,
+ words_sword = 56,
+ words_attack = 57,
+ words_carve_up = 58,
+ words_goblet = 59,
+ words_fill = 60,
+ words_drink_from = 61,
+ words_bone = 62,
+ words_gnaw = 63,
+ words_fruit = 64,
+ words_eat = 65,
+ words_doll = 66,
+ words_play_with = 67,
+ words_heal = 68,
+ words_heal_thyself = 69,
+ words_heal_self = 70,
+ words_polystone = 71,
+ words_mimic = 72,
+ words_red_powerstone = 73,
+ words_yellow_powerstone = 74,
+ words_blue_powerstone = 75,
+ words_key_crown = 76,
+ words_wear = 77,
+ words_dates = 78,
+ words_statue = 79,
+ words_bottle_of_flies = 80,
+ words_listen_to = 81,
+ words_soul_egg = 82,
+ words_break = 83,
+ words_magic_belt = 84,
+ words_adjust = 85,
+ words_amulet = 86,
+ words_thrust = 87,
+ words_mud = 88,
+ words_feel = 89,
+ words_taste = 90,
+ words_feathers = 91,
+ words_tickle = 92,
+ words_torch = 93,
+ words_wave = 94,
+ words_flask = 95,
+ words_flask_full_of_acid = 96,
+ words_pour_contents = 97,
+ words_pour = 98,
+ words_pour_contents_of = 99,
words_drink = 100,
words_rope = 101,
words_tie = 102,
+ words_power_vacuum_stone = 103,
words_take_magic_from = 104,
words_dead_rat = 105,
words_pet = 106,
@@ -133,6 +141,7 @@ enum {
words_black_sphere = 112,
words_soptus_soporific = 113,
words_shifter_ring = 114,
+ words_shift_self = 115,
words_shift_into_bear = 116,
words_shift_into_seal = 117,
words_shift_into_snake = 118,
@@ -149,33 +158,55 @@ enum {
words_rare_coin = 129,
words_admire = 130,
words_crystal_flower = 131,
+ words_diamond_dust = 132,
+ words_ruby_ring = 133,
words_gold_nugget = 134,
+ words_magic_music_box = 135,
words_emerald = 136,
+ words_piece_of_paper = 137,
words_speak_words_on = 138,
words_vortex_stone = 139,
+ words_bust_on_wall = 142,
+ words_dragon = 144,
words_touch = 146,
words_throne_room = 147,
+ words_return_to = 148,
+ words_cave = 149,
words_passageway_to_west = 150,
words_passageway_to_east = 151,
words_cave_floor = 152,
+ words_stone_column = 153,
+ words_abyss = 154,
words_look_into = 155,
words_castle = 156,
+ words_castle_gate = 157,
words_ground = 158,
+ words_barrel = 159,
+ words_barrels = 160,
+ words_haystack = 161,
+ words_root_through = 162,
+ words_battlements = 163,
+ words_gate_to_throne_room = 164,
+ words_castle_wall = 165,
words_door = 166,
words_wall_switch = 167,
words_stairs = 168,
- words_cave = 149,
- words_abyss = 154,
words_walk_down = 169,
words_edge_of_abyss = 170,
+ words_courtyard = 171,
words_rock = 172,
words_cave_ceiling = 173,
words_cave_wall = 174,
words_brazier = 175,
words_door_to_throne_room = 176,
+ words_go_through = 177,
words_dining_table = 178,
words_activate = 179,
+ words_battlement = 180,
+ words_door_to_gaurdroom = 181,
words_dungeon_floor = 182,
+ words_dungeon_walls = 183,
+ words_dungeon_ceiling = 184,
words_bedding = 185,
words_floor_grate = 186,
words_manacles = 187,
@@ -185,18 +216,26 @@ enum {
words_guard_station = 191,
words_door_to_dungeon_cell = 192,
words_doorway_to_cell = 193,
+ words_dungeon_door = 194,
words_dungeon_wall = 195,
words_ceiling = 196,
words_door_to_hallway = 197,
words_table = 198,
words_bookshelf = 199,
+ words_trophy = 200,
+ words_reading_bench = 201,
words_chair = 202,
+ words_loveseat = 203,
words_basket = 204,
+ words_stool = 205,
words_guard_stool = 206,
words_rocks = 207,
words_dividing_wall = 208,
+ words_archway = 209,
+ words_market_grounds = 210,
words_hedge = 211,
words_sky = 212,
+ words_plains = 213,
words_fields = 214,
words_gate_to_courtyard = 215,
words_road_to_east = 216,
@@ -204,9 +243,13 @@ enum {
words_clouds = 218,
words_merchant_s_stall = 219,
words_well = 220,
+ words_down_well = 221,
+ words_go = 222,
+ words_go_down = 223,
words_crank = 224,
words_bucket = 225,
words_jump_down = 226,
+ words_walls = 227,
words_doorway_to_south = 228,
words_pedestal = 229,
words_door_to_north = 230,
@@ -219,7 +262,15 @@ enum {
words_candlestick = 237,
words_desk = 238,
words_turn = 239,
+ words_pole = 240,
+ words_the_scene = 241,
+ words_leave = 242,
+ words_end_table = 243,
+ words_battle_axes = 244,
words_door_to_king_s_room = 245,
+ words_coat_of_arms = 246,
+ words_large_window = 247,
+ words_small_window = 248,
words_door_to_meeting_room = 249,
words_door_to_ballroom = 250,
words_flowers = 251,
@@ -228,21 +279,29 @@ enum {
words_door_to_courtyard = 254,
words_platform = 255,
words_step = 256,
+ words_red_carpet = 257,
words_king_s_throne = 258,
+ words_sit_in = 259,
words_queen_s_throne = 260,
+ words_trapdoor = 261,
words_grate = 262,
words_river = 263,
words_diaries = 264,
words_swim_down = 265,
words_scullery_maid = 266,
+ words_doorway_to_dungeon = 267,
words_ward = 268,
words_darkness_beast = 269,
words_beast = 270,
words_put_magic_into = 271,
words_guard = 272,
words_crown = 273,
+ words_books = 274,
+ words_secret_door = 275,
+ words_wall_panel = 276,
words_doorway = 277,
words_faerie = 278,
+ words_soptus_ecliptus2 = 279,
words_guard_captain = 280,
words_merchant = 281,
words_shapechanger = 282,
@@ -255,54 +314,83 @@ enum {
words_soporific = 289,
words_parchment = 290,
words_king = 291,
+ words_macmorn = 292,
words_mountainside = 293,
words_path_to_south = 294,
words_rough_stone = 295,
words_climb_up = 296,
words_large_rock = 297,
words_small_rock = 298,
- words_puddle = 315,
- words_boulders = 320,
words_path_to_west = 299,
words_cave_entrance = 300,
words_pallet = 301,
words_blanket = 302,
words_firepit = 303,
words_flat_stone = 304,
+ words_move = 305,
words_spirit_bundle = 306,
+ words_trail_leading_up = 307,
words_follow = 308,
+ words_trail_leading_down = 309,
words_nest = 310,
+ words_reach_in = 311,
+ words_trail_leading_west = 312,
words_path_to_east = 313,
words_waterfall = 314,
+ words_puddle = 315,
words_edge_of_cliff = 316,
words_ledge = 317,
words_climb_down = 318,
words_landing = 319,
- words_sconce = 329,
+ words_boulders = 320,
words_rock_tumble = 321,
words_rock_tree = 322,
words_pillar = 323,
words_jump_to = 324,
+ words_cliff = 325,
+ words_pillars = 326,
+ words_special_rock = 327,
+ words_gaze_upon = 328,
+ words_sconce = 329,
+ words_ladder = 330,
words_stairway = 331,
words_mechanism = 332,
words_spearheads = 333,
words_trap_door = 334,
+ words_swim = 335,
+ words_down_river = 336,
words_swim_up = 337,
+ words_up_river = 338,
words_shore = 339,
words_swim_to = 340,
words_swim_towards = 341,
+ words_King_Callash = 342,
+ words_King_s_throne = 343,
+ words_King = 344,
words_grotto = 345,
words_climb_through = 346,
words_Queen_Mother = 347,
+ words_Macmorn = 348,
+ words_small_ledge = 349,
words_MacMorn = 350,
+ words_to_110 = 351,
+ words_to_kitty_heaven = 352,
+ words_kitty_heaven = 353,
+ words_room_110 = 354,
words_Llanie = 355,
+ words_cw = 356,
words_hermit = 357,
+ words_trou = 358,
words_shak = 359,
+ words_room_501 = 360,
words_moon = 361,
- words_eye = 368,
+ words_ufo = 362,
words_sit_on = 363,
words_stranger = 364,
words_tower_door = 365,
+ words_door_to_east = 366,
+ words_door_to_west = 367,
+ words_eye = 368,
words_doorway_to_east = 369,
words_doorway_to_west = 370,
words_skull = 371,
@@ -315,6 +403,7 @@ enum {
words_petcock = 378,
words_nozzle = 379,
words_tubing = 380,
+ words_beaker = 381,
words_flame = 382,
words_metal_plate = 383,
words_shaft_of_light = 384,
@@ -323,15 +412,18 @@ enum {
words_cage = 387,
words_freezer = 388,
words_contents_of_freezer = 389,
+ words_cage_with_rats = 390,
words_door_to_south = 391,
words_trail_of_green_slime = 392,
words_neck_lock = 393,
words_bench = 394,
words_skeleton = 395,
+ words_leg_clamps = 396,
words_leg_lock = 397,
words_waist_lock = 398,
words_Ner_Tom = 399,
words_belt = 400,
+ words_jump_into = 401,
words_closet = 402,
words_door_to_eye_chamber = 403,
words_dresser = 404,
@@ -348,6 +440,7 @@ enum {
words_large_spider_web = 415,
words_infernal_machine = 416,
words_water_source = 417,
+ words_flow_of_water = 418,
words_retort = 419,
words_doorway_to_corridor = 420,
words_telescope = 421,
@@ -357,9 +450,15 @@ enum {
words_dragon_sculpture = 425,
words_rat = 426,
words_rat_cage = 427,
+ words_down_button = 428,
+ words_door_to_machine_room = 429,
+ words_strange_portal = 430,
words_door_frame = 431,
+ words_elevator_hole = 432,
+ words_up_button = 433,
words_doorway_to_dark_room = 434,
words_big_skull = 435,
+ words_elevator_platform = 436,
words_button = 437,
words_top_button = 438,
words_bottom_button = 439,
@@ -367,18 +466,35 @@ enum {
words_eye_chamber_doorway = 441,
words_machine_room_doorway = 442,
words_glowing_floor = 443,
+ words_Brynn_Fann = 444,
+ words_Gran_Callahach = 445,
+ words_Slathan_Ni_Patan = 446,
+ words_Hightower = 447,
words_Soptus_Ecliptus = 448,
words_tower = 449,
+ words_mountain_path = 450,
words_path_behind_tower = 451,
+ words_vines = 452,
words_Slathan_ni_Patan = 453,
+ words_sick = 454,
words_path_around_tower = 455,
+ words_path_to_Hightower = 456,
words_spirit_plane = 457,
words_spirit_tree = 458,
+ words_walk = 459,
words_remains = 460,
+ words_doorway_to_elevator = 461,
words_dragon_door = 462,
words_dragon_bones = 463,
words_iron_floor = 464,
words_hole = 465,
+ words_stone = 466,
+ words_homemade_bundle = 467,
+ words_left = 468,
+ words_right = 469,
+ words_up = 470,
+ words_down = 471,
+ words_west = 472,
words_desert_to_west = 473,
words_cross = 474,
words_desert_to_east = 475,
@@ -393,9 +509,15 @@ enum {
words_tangle = 484,
words_sand = 485,
words_magic_grapes = 486,
+ words_roc_s_nest = 487,
words_grape_vine = 488,
+ words_strange_square = 489,
+ words_water_sphere = 490,
words_east_end_of_island = 491,
+ words_island = 492,
words_secret_message = 493,
+ words_find = 494,
+ words_sand_near_stones = 495,
words_desert_sky = 496,
words_pool = 497,
words_palm_tree = 498,
@@ -407,6 +529,7 @@ enum {
words_floating_disk = 504,
words_gnarled_root = 505,
words_snake_pit = 506,
+ words_marker = 507,
words_shaman = 508,
words_guardhouse = 509,
words_bone_tree = 510,
@@ -418,10 +541,19 @@ enum {
words_Roc = 516,
words_Roc_s_nest = 517,
words_select = 518,
+ words_purple_gem = 519,
words_purple_stone = 520,
words_green_stone = 521,
words_path = 522,
words_guards = 523,
+ words_lamp = 524,
+ words_scimitar = 525,
+ words_sitting_pillow = 526,
+ words_water_gourd = 527,
+ words_jar = 528,
+ words_tent_pole = 529,
+ words_paraphernalia = 531,
+ words_slathan_ni_patan2 = 532,
words_eye_rock = 533,
words_body_tree = 534,
words_clearing = 535,
@@ -432,7 +564,9 @@ enum {
words_dead_tree = 540,
words_pit = 541,
words_shifter_village = 542,
+ words_slathan_sky2 = 543,
words_shifter = 544,
+ words_walk_around = 545,
words_wrecked_bridge = 546,
words_shack = 547,
words_wrecked_shack = 548,
@@ -447,6 +581,7 @@ enum {
words_tree_stump = 557,
words_sanctuary_woods = 558,
words_toads = 559,
+ words_doofus = 560,
words_shifting_monster = 561,
words_sprite = 562,
words_maze = 563,
@@ -456,8 +591,9 @@ enum {
words_guardian = 567,
words_Butterfly_King = 568,
words_robe = 569,
+ words_cedar_chest = 570,
words_Dragonsphere = 571,
- words_path_to_Hightower = 572
+ words_Caliph = 573,
};
} // namespace Dragonsphere
Commit: 2650d9aa0f2f3deff794dba3f3f605e21a31097f
https://github.com/scummvm/scummvm/commit/2650d9aa0f2f3deff794dba3f3f605e21a31097f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-05-25T09:42:29+10:00
Commit Message:
MADS: DRAGONSPHERE: Cleanups and fixes for room 104
Changed paths:
engines/mads/madsv2/dragonsphere/mads/conv.h
engines/mads/madsv2/dragonsphere/mads/words.h
engines/mads/madsv2/dragonsphere/rooms/room103.cpp
engines/mads/madsv2/dragonsphere/rooms/room104.cpp
diff --git a/engines/mads/madsv2/dragonsphere/mads/conv.h b/engines/mads/madsv2/dragonsphere/mads/conv.h
index 018ed111345..eaad7c58914 100644
--- a/engines/mads/madsv2/dragonsphere/mads/conv.h
+++ b/engines/mads/madsv2/dragonsphere/mads/conv.h
@@ -32,32 +32,42 @@ namespace Dragonsphere {
// Each conversation gets its own enum block named after its .CON file.
enum {
- conv002_counter_only = 0,
- conv002_banter_random = 1,
- conv002_flirt_rand = 2
+ conv001_exit_b_b = 10,
+ conv001_bear_b_b = 13,
+ conv001_unbear_b_b = 16,
+ conv001_sixteen_b_b = 19,
+ conv001_pre_25_b_b = 30,
+ conv001_twentyfive = 31,
+ conv001_end_b_b = 38
};
enum {
- conv003_replies_b_b = 2,
- conv003_exit_b_b = 8,
- conv003_replies_defeat = 5
+ conv002_counter_only = 0,
+ conv002_banter_random = 1,
+ conv002_flirt_rand = 2
};
enum {
- conv004_seen_only = 0,
- conv004_resolved_only = 1,
- conv004_am_only = 2,
- conv004_wait_only = 3,
- conv004_thanks_only = 4,
- conv004_nay_only = 5,
- conv004_queen_only = 6,
- conv004_fathers_only = 7,
- conv004_last_only = 8,
- conv004_exit_b_b = 10
+ conv003_replies_b_b = 2,
+ conv003_exit_b_b = 8,
+ conv003_replies_defeat = 5
};
enum {
- conv005_exit_b_b = 16
+ conv004_seen_only = 0,
+ conv004_resolved_only = 1,
+ conv004_am_only = 2,
+ conv004_wait_only = 3,
+ conv004_thanks_only = 4,
+ conv004_nay_only = 5,
+ conv004_queen_only = 6,
+ conv004_fathers_only = 7,
+ conv004_last_only = 8,
+ conv004_exit_b_b = 10
+};
+
+enum {
+ conv005_exit_b_b = 16
};
enum {
diff --git a/engines/mads/madsv2/dragonsphere/mads/words.h b/engines/mads/madsv2/dragonsphere/mads/words.h
index 04a219259ee..a28f8c38afa 100644
--- a/engines/mads/madsv2/dragonsphere/mads/words.h
+++ b/engines/mads/madsv2/dragonsphere/mads/words.h
@@ -29,103 +29,103 @@ namespace MADSV2 {
namespace Dragonsphere {
enum {
- words_look = 3,
- words_take = 4,
- words_push = 5,
- words_open = 6,
- words_put = 7,
- words_talk_to = 8,
- words_give = 9,
- words_pull = 10,
- words_close = 11,
- words_throw = 12,
- words_walk_to = 13,
+ words_look = 3,
+ words_take = 4,
+ words_push = 5,
+ words_open = 6,
+ words_put = 7,
+ words_talk_to = 8,
+ words_give = 9,
+ words_pull = 10,
+ words_close = 11,
+ words_throw = 12,
+ words_walk_to = 13,
words_nothing = 14,
- words_floor = 16,
- words_walk_across = 17,
- words_rug = 18,
- words_carpet = 19,
- words_wall = 20,
- words_bed = 21,
- words_pillow = 22,
- words_chest = 23,
- words_window = 24,
- words_nightstand = 25,
- words_tapestry = 26,
- words_dressing_screen = 27,
- words_walk_behind = 28,
- words_royal_crest = 29,
- words_look_at = 30,
- words_bedroom_wall = 31,
- words_castle_walls = 32,
- words_book = 33,
- words_fireplace = 34,
- words_fireplace_screen = 35,
- words_door_to_queens_room = 36,
- words_exit = 36,
- words_walk_through = 37,
- words_hall_to_south = 38,
- words_walk_into = 39,
- words_wall_plaque = 40,
- words_decoration = 41,
- words_swords = 42,
- words_wall_sconce = 43,
- words_bust = 44,
- words_arch = 45,
- words_signet_ring = 46,
- words_invoke = 47,
- words_polish = 48,
- words_gangbang = 49,
- words_bird_figurine = 50,
- words_rub = 51,
- words_birdcall = 52,
- words_use = 53,
- words_make_noise = 54,
- words_shieldstone = 55,
- words_sword = 56,
- words_attack = 57,
- words_carve_up = 58,
- words_goblet = 59,
- words_fill = 60,
- words_drink_from = 61,
- words_bone = 62,
- words_gnaw = 63,
- words_fruit = 64,
- words_eat = 65,
- words_doll = 66,
- words_play_with = 67,
- words_heal = 68,
- words_heal_thyself = 69,
- words_heal_self = 70,
- words_polystone = 71,
- words_mimic = 72,
- words_red_powerstone = 73,
- words_yellow_powerstone = 74,
- words_blue_powerstone = 75,
- words_key_crown = 76,
- words_wear = 77,
- words_dates = 78,
- words_statue = 79,
- words_bottle_of_flies = 80,
- words_listen_to = 81,
- words_soul_egg = 82,
- words_break = 83,
- words_magic_belt = 84,
- words_adjust = 85,
- words_amulet = 86,
- words_thrust = 87,
- words_mud = 88,
- words_feel = 89,
- words_taste = 90,
- words_feathers = 91,
- words_tickle = 92,
- words_torch = 93,
- words_wave = 94,
- words_flask = 95,
- words_flask_full_of_acid = 96,
- words_pour_contents = 97,
- words_pour = 98,
- words_pour_contents_of = 99,
+ words_floor = 16,
+ words_walk_across = 17,
+ words_rug = 18,
+ words_carpet = 19,
+ words_wall = 20,
+ words_bed = 21,
+ words_pillow = 22,
+ words_chest = 23,
+ words_window = 24,
+ words_nightstand = 25,
+ words_tapestry = 26,
+ words_dressing_screen = 27,
+ words_walk_behind = 28,
+ words_royal_crest = 29,
+ words_look_at = 30,
+ words_bedroom_wall = 31,
+ words_castle_walls = 32,
+ words_book = 33,
+ words_fireplace = 34,
+ words_fireplace_screen = 35,
+ words_door_to_queens_room = 36,
+ words_exit = 36,
+ words_walk_through = 37,
+ words_hall_to_south = 38,
+ words_walk_into = 39,
+ words_wall_plaque = 40,
+ words_decoration = 41,
+ words_swords = 42,
+ words_wall_sconce = 43,
+ words_bust = 44,
+ words_arch = 45,
+ words_signet_ring = 46,
+ words_invoke = 47,
+ words_polish = 48,
+ words_gangbang = 49,
+ words_bird_figurine = 50,
+ words_rub = 51,
+ words_birdcall = 52,
+ words_use = 53,
+ words_make_noise = 54,
+ words_shieldstone = 55,
+ words_sword = 56,
+ words_attack = 57,
+ words_carve_up = 58,
+ words_goblet = 59,
+ words_fill = 60,
+ words_drink_from = 61,
+ words_bone = 62,
+ words_gnaw = 63,
+ words_fruit = 64,
+ words_eat = 65,
+ words_doll = 66,
+ words_play_with = 67,
+ words_heal = 68,
+ words_heal_thyself = 69,
+ words_heal_self = 70,
+ words_polystone = 71,
+ words_mimic = 72,
+ words_red_powerstone = 73,
+ words_yellow_powerstone = 74,
+ words_blue_powerstone = 75,
+ words_key_crown = 76,
+ words_wear = 77,
+ words_dates = 78,
+ words_statue = 79,
+ words_bottle_of_flies = 80,
+ words_listen_to = 81,
+ words_soul_egg = 82,
+ words_break = 83,
+ words_magic_belt = 84,
+ words_adjust = 85,
+ words_amulet = 86,
+ words_thrust = 87,
+ words_mud = 88,
+ words_feel = 89,
+ words_taste = 90,
+ words_feathers = 91,
+ words_tickle = 92,
+ words_torch = 93,
+ words_wave = 94,
+ words_flask = 95,
+ words_flask_full_of_acid = 96,
+ words_pour_contents = 97,
+ words_pour = 98,
+ words_pour_contents_of = 99,
words_drink = 100,
words_rope = 101,
words_tie = 102,
diff --git a/engines/mads/madsv2/dragonsphere/rooms/room103.cpp b/engines/mads/madsv2/dragonsphere/rooms/room103.cpp
index 3b028077b36..c3fe41fa572 100644
--- a/engines/mads/madsv2/dragonsphere/rooms/room103.cpp
+++ b/engines/mads/madsv2/dragonsphere/rooms/room103.cpp
@@ -276,7 +276,7 @@ static void room_103_parser() {
return;
}
- if (player_parse(37, 36, 0) || player_parse(6, 36, 0) || player_parse(10, 36, 0)) {
+ if (player_said_2(walk_through, door_to_queens_room) || player_said_2(open, door_to_queens_room) || player_said_2(pull, door_to_queens_room)) {
switch (kernel.trigger) {
case 0:
player.commands_allowed = false;
@@ -337,20 +337,20 @@ static void room_103_parser() {
return;
}
- if (player_parse(37, 249, 0) || player_parse(6, 249, 0) || player_parse(10, 249, 0)) {
+ if (player_said_2(walk_through, door_to_meeting_room) || player_said_2(open, door_to_meeting_room) || player_said_2(pull, door_to_meeting_room)) {
new_room = 104;
player.command_ready = false;
return;
}
- if (player_parse(37, 250, 0) || player_parse(6, 250, 0) || player_parse(10, 250, 0)) {
+ if (player_said_2(walk_through, door_to_ballroom) || player_said_2(open, door_to_ballroom) || player_said_2(pull, door_to_ballroom)) {
new_room = 105;
player.command_ready = false;
return;
}
- if (player_parse(3, 0) || player_parse(30, 0)) {
- if (player_parse(26, 0)) {
+ if (player_said_1(look) || player_said_1(look_at)) {
+ if (player_said_1(tapestry)) {
if (inter_point_x <= END_HALL_TAPESTRY_X && inter_point_y <= END_HALL_TAPESTRY_Y)
text_show(10302);
else
@@ -358,46 +358,46 @@ static void room_103_parser() {
player.command_ready = false;
return;
}
- if (player_parse(246, 0)) { text_show(10305); player.command_ready = false; return; }
- if (player_parse(36, 0)) { text_show(10307); player.command_ready = false; return; }
- if (player_parse(245, 0)) { text_show(10308); player.command_ready = false; return; }
- if (player_parse(570, 0)) { text_show(10309); player.command_ready = false; return; }
- if (player_parse(198, 0)) { text_show(10311); player.command_ready = false; return; }
- if (player_parse(248, 0)) { text_show(10312); player.command_ready = false; return; }
- if (player_parse(247, 0)) { text_show(10314); player.command_ready = false; return; }
- if (player_parse(244, 0)) { text_show(10315); player.command_ready = false; return; }
- if (player_parse(44, 0)) { text_show(10317); player.command_ready = false; return; }
- if (player_parse(41, 0)) { text_show(10320); player.command_ready = false; return; }
- if (player_parse(40, 0)) { text_show(10322); player.command_ready = false; return; }
- if (player_parse(250, 0)) { text_show(10323); player.command_ready = false; return; }
- if (player_parse(249, 0)) { text_show(10324); player.command_ready = false; return; }
+ if (player_said_1(coat_of_arms)) { text_show(10305); player.command_ready = false; return; }
+ if (player_said_1(door_to_queens_room)) { text_show(10307); player.command_ready = false; return; }
+ if (player_said_1(door_to_king_s_room)) { text_show(10308); player.command_ready = false; return; }
+ if (player_said_1(cedar_chest)) { text_show(10309); player.command_ready = false; return; }
+ if (player_said_1(table)) { text_show(10311); player.command_ready = false; return; }
+ if (player_said_1(small_window)) { text_show(10312); player.command_ready = false; return; }
+ if (player_said_1(large_window)) { text_show(10314); player.command_ready = false; return; }
+ if (player_said_1(battle_axes)) { text_show(10315); player.command_ready = false; return; }
+ if (player_said_1(bust)) { text_show(10317); player.command_ready = false; return; }
+ if (player_said_1(decoration)) { text_show(10320); player.command_ready = false; return; }
+ if (player_said_1(wall_plaque)) { text_show(10322); player.command_ready = false; return; }
+ if (player_said_1(door_to_ballroom)) { text_show(10323); player.command_ready = false; return; }
+ if (player_said_1(door_to_meeting_room)) { text_show(10324); player.command_ready = false; return; }
}
- if ((player_parse(4, 0) || player_parse(10, 0)) && player_parse(244, 0)) {
+ if ((player_said_1(take) || player_said_1(pull)) && player_said_1(battle_axes)) {
text_show(10316);
player.command_ready = false;
return;
}
- if ((player_parse(5, 0) || player_parse(10, 0)) && player_parse(26, 0)) {
+ if ((player_said_1(push) || player_said_1(pull)) && player_said_1(tapestry)) {
text_show(10304);
player.command_ready = false;
return;
}
- if (player_parse(10, 246, 0)) {
+ if (player_said_2(pull, coat_of_arms)) {
text_show(10306);
player.command_ready = false;
return;
}
- if (player_parse(6, 570, 0)) {
+ if (player_said_2(open, cedar_chest)) {
text_show(10310);
player.command_ready = false;
return;
}
- if (player_parse(6, 248, 0) || player_parse(6, 247, 0)) {
+ if (player_said_2(open, small_window) || player_said_2(open, large_window)) {
text_show(10313);
player.command_ready = false;
return;
diff --git a/engines/mads/madsv2/dragonsphere/rooms/room104.cpp b/engines/mads/madsv2/dragonsphere/rooms/room104.cpp
index 7216fe1ff65..e83a6026f70 100644
--- a/engines/mads/madsv2/dragonsphere/rooms/room104.cpp
+++ b/engines/mads/madsv2/dragonsphere/rooms/room104.cpp
@@ -190,15 +190,8 @@ static Scratch scratch;
#define LENGTH_OF_LIFE 1300
-// Vocabulary word IDs (raw integers from disassembly; VOCABH.DB/VOCAB.DB)
-// words_MacMorn=350, words_wall_panel=276, words_secret_door=275
-// words_table=198, words_books=274, words_tapestry=26 (â verified)
-// words_Queen_Mother=347, words_king=291, words_doorway=277
-// words_walk_through=37 (â), words_walk_to=13 (â)
-// ---------------------------------------------------------------------------
-
-void room_104_init() {
+static void room_104_init() {
conv_get(CONV_FINALE);
// Palette-only loads for Pid persona (prevent palette fragmentation)
@@ -264,7 +257,7 @@ void room_104_init() {
// Stamp the correct tapestry sprite based on current state
if (global[tapestry_status] == TAPESTRY_CLOSED ||
- global[tapestry_status] == TAPESTRY_CLOSED2) {
+ global[tapestry_status] == TAPESTRY_CLOSED2) {
kernel_flip_hotspot(276, false); // words_wall_panel
kernel_flip_hotspot(275, false); // words_secret_door
seq[fx_tapestry_closed] = kernel_seq_stamp(ss[fx_tapestry_closed], false, KERNEL_FIRST);
@@ -278,17 +271,17 @@ void room_104_init() {
// Adjust secret door / wall panel hotspots based on books state
if (global[books_status] == BOOKS_PRESENT ||
- global[books_status] == BOOKS_PRESENT2) {
+ global[books_status] == BOOKS_PRESENT2) {
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
+ global[tapestry_status] == TAPESTRY_OPENED2) {
kernel_flip_hotspot(275, false); // words_secret_door
}
} else if (global[books_status] == BOOKS_PULLED ||
- global[books_status] == BOOKS_PULLED2) {
+ global[books_status] == BOOKS_PULLED2) {
seq[fx_wall_open_close] = kernel_seq_stamp(ss[fx_wall_open_close], false, KERNEL_LAST);
kernel_seq_depth(seq[fx_wall_open_close], 8);
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
+ global[tapestry_status] == TAPESTRY_OPENED2) {
kernel_flip_hotspot(276, false); // words_wall_panel
kernel_flip_hotspot(275, true); // words_secret_door (true in release binary)
}
@@ -314,9 +307,9 @@ void room_104_init() {
seq[fx_open_doorway] = kernel_seq_stamp(ss[fx_open_doorway], false, KERNEL_FIRST);
kernel_seq_depth(seq[fx_open_doorway], 8);
local->doorway_id = kernel_add_dynamic(277, 37, SYNTAX_SINGULAR,
- seq[fx_open_doorway], 0, 0, 0, 0);
+ seq[fx_open_doorway], 0, 0, 0, 0);
kernel_dynamic_walk(local->doorway_id, WALK_TO_DOORWAY_X, WALK_TO_DOORWAY_Y,
- FACING_NORTHEAST);
+ FACING_NORTHEAST);
global[books_status] = BOOKS_NOT_PRESENT;
kernel_flip_hotspot_loc(26, false, TAP_HS_CLOSED_X, TAP_HS_CLOSED_Y); // words_tapestry
@@ -380,7 +373,7 @@ void room_104_init() {
{
int16 id = kernel_add_dynamic(347, 13, SYNTAX_FEM_NOT_PROPER,
- KERNEL_NONE, 0, 0, 0, 0); // words_Queen_Mother
+ KERNEL_NONE, 0, 0, 0, 0); // words_Queen_Mother
kernel_dynamic_hot[id].prep = PREP_ON;
kernel_dynamic_anim(id, aa[2], 0);
}
@@ -389,7 +382,7 @@ void room_104_init() {
{
int16 id = kernel_add_dynamic(291, 13, SYNTAX_MASC_NOT_PROPER,
- KERNEL_NONE, 0, 0, 0, 0); // words_king
+ KERNEL_NONE, 0, 0, 0, 0); // words_king
kernel_dynamic_hot[id].prep = PREP_ON;
kernel_dynamic_anim(id, aa[0], 0);
}
@@ -399,8 +392,8 @@ void room_104_init() {
seq[fx_door] = kernel_seq_stamp(ss[fx_door], false, KERNEL_LAST);
kernel_seq_depth(seq[fx_door], 14);
player_first_walk(START_X_ROOM_103, START_Y_ROOM_103, FACING_SOUTH,
- WALK_TO_X_FROM_103, WALK_TO_Y_FROM_103, FACING_SOUTH,
- false);
+ WALK_TO_X_FROM_103, WALK_TO_Y_FROM_103, FACING_SOUTH,
+ false);
player_walk_trigger(ROOM_104_DOOR_CLOSES);
} else {
@@ -436,7 +429,7 @@ void room_104_init() {
{
int16 id = kernel_add_dynamic(347, 13, SYNTAX_FEM_NOT_PROPER,
- KERNEL_NONE, 0, 0, 0, 0); // words_Queen_Mother
+ KERNEL_NONE, 0, 0, 0, 0); // words_Queen_Mother
kernel_dynamic_hot[id].prep = PREP_ON;
kernel_dynamic_anim(id, aa[2], 0);
}
@@ -445,7 +438,7 @@ void room_104_init() {
{
int16 id = kernel_add_dynamic(291, 13, SYNTAX_MASC_NOT_PROPER,
- KERNEL_NONE, 0, 0, 0, 0); // words_king
+ KERNEL_NONE, 0, 0, 0, 0); // words_king
kernel_dynamic_hot[id].prep = PREP_ON;
kernel_dynamic_anim(id, aa[0], 0);
}
@@ -490,13 +483,13 @@ static void room_104_process_conversation_finale() {
// Player-choice (b_b) node dispatch
switch (player_verb) {
- case 38: // conv001_end_b_b
+ case conv001_end_b_b:
global[end_of_game] = true;
new_room = 106;
break;
- case 30: // conv001_pre_25_b_b
- *conv_my_next_start = 31; // conv001_twentyfive
+ case conv001_pre_25_b_b:
+ *conv_my_next_start = conv001_twentyfive;
conv_abort();
if (global[llanie_status] == IS_SAVED) {
aa[3] = kernel_run_animation(kernel_name('l', 1), 0);
@@ -505,28 +498,28 @@ static void room_104_process_conversation_finale() {
}
break;
- case 10: // conv001_exit_b_b
+ case conv001_exit_b_b:
if (!kernel.trigger)
local->mac_action = MAC_TABLE;
me_trig_flag = true;
you_trig_flag = true;
break;
- case 13: // conv001_bear_b_b
+ case conv001_bear_b_b:
local->pid_action = PID_BEAR;
conv_hold();
me_trig_flag = true;
you_trig_flag = true;
break;
- case 16: // conv001_unbear_b_b
+ case conv001_unbear_b_b:
if (!kernel.trigger)
local->mac_action = MAC_UNBEAR;
me_trig_flag = true;
you_trig_flag = true;
break;
- case 19: // conv001_sixteen_b_b
+ case conv001_sixteen_b_b:
local->death_timer = 0;
local->clock = 0;
local->activate_timer = true;
@@ -537,11 +530,18 @@ static void room_104_process_conversation_finale() {
// Trigger dispatch
switch (kernel.trigger) {
- case ROOM_104_YOU_TALK: // 81
+ case ROOM_104_YOU_TALK:
// Per-node character action: which character speaks this NPC line?
switch (player_verb) {
// Mac talks (if idle, set to talking)
- case 0: case 2: case 4: case 5: case 7: case 8: case 11: case 17:
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 7:
+ case 8:
+ case 11:
+ case 17:
if (local->mac_action == MAC_SHUT_UP)
local->mac_action = MAC_TALK;
local->pid_action = PID_SHUT_UP;
@@ -608,11 +608,11 @@ static void room_104_process_conversation_finale() {
local->pid_talk_count = 0;
}
-void room_104_pre_parser() {
+static void room_104_pre_parser() {
// verb(pull/open/close/push) AND tapestry: redirect walk to tapestry area when
// the interaction point is in the right side of the room (inter_point_x > 280)
- if ((player_parse(10, 0) || player_parse(6, 0) || player_parse(11, 0) || player_parse(5, 0)) &&
- player_parse(26, 0)) { // tapestry
+ if ((player_said_1(pull) || player_said_1(open) || player_said_1(close) || player_said_1(push)) &&
+ player_said_1(tapestry)) {
if (inter_point_x > 280) {
player_walk(WALK_TO_DOORWAY_X, WALK_TO_DOORWAY_Y, FACING_NORTHEAST);
}
@@ -620,16 +620,16 @@ void room_104_pre_parser() {
// For Pid persona, cancel any walk when the player tries actions that require
// cutscene handling rather than walking
- if (player_parse(37, 176, 0) || // walk_through + door_to_throne_room
- player_parse(37, 197, 0) || // walk_through + door_to_hallway
- player_parse(37, 277, 0) || // walk_through + doorway
- ((player_parse(10, 0) || player_parse(6, 0) || player_parse(5, 0)) && player_parse(26, 0)) || // pull/open/push + tapestry
- ((player_parse(10, 0) || player_parse(11, 0) || player_parse(5, 0)) && player_parse(26, 0)) || // pull/close/push + tapestry
- ((player_parse(5, 0) || player_parse(10, 0)) && player_parse(18, 0)) || // push/pull + rug
- player_parse(35, 0) || // fireplace_screen
- player_parse(200, 0) || // trophy
- player_parse(203, 0) || // loveseat
- player_parse(6, 201, 0)) { // open + reading_bench
+ if (player_said_2(walk_through, door_to_throne_room) ||
+ player_said_2(walk_through, door_to_hallway) ||
+ player_said_2(walk_through, doorway) ||
+ ((player_said_1(pull) || player_said_1(open) || player_said_1(push)) && player_said_1(tapestry)) ||
+ ((player_said_1(pull) || player_said_1(close) || player_said_1(push)) && player_said_1(tapestry)) ||
+ ((player_said_1(push) || player_said_1(pull)) && player_said_1(rug)) ||
+ player_said_1(fireplace_screen) ||
+ player_said_1(trophy) ||
+ player_said_1(loveseat) ||
+ player_said_2(open, reading_bench)) {
if (global[player_persona] == PLAYER_IS_PID) {
player_cancel_walk();
}
@@ -649,7 +649,6 @@ static void handle_animation_king() {
king_reset_frame = -1;
switch (local->king_frame) {
-
case 22:
if (local->king_action == KING_INVISIBLE) {
king_reset_frame = 21;
@@ -724,7 +723,6 @@ static void handle_animation_king() {
}
}
-
static void handle_animation_mac() {
int mac_reset_frame;
@@ -733,7 +731,6 @@ static void handle_animation_mac() {
mac_reset_frame = -1;
switch (local->mac_frame) {
-
case 25: /* almost end of Macmorn drawing sword */
player.commands_allowed = true;
break;
@@ -785,7 +782,8 @@ static void handle_animation_mac() {
kernel_synch(KERNEL_ANIM, aa[4], KERNEL_ANIM, aa[1]);
break;
- case 112: /* end of throw table */
+ case 112:
+ /* end of throw table */
mac_reset_frame = 8; /* draw sword */
local->mac_action = MAC_SHUT_UP;
break;
@@ -857,18 +855,21 @@ static void handle_animation_mac() {
}
break;
- case 76: /* end of MacMorn laughing over Pid's death */
+ case 76:
+ /* end of MacMorn laughing over Pid's death */
conv_reset(CONV_FINALE);
text_show(10467);
global[no_load_walker] = false;
new_room = 119;
break;
- case 82: /* part way into un-bearing pid */
+ case 82:
+ /* part way into un-bearing pid */
local->pid_action = PID_UNBEAR;
break;
- case 97: /* somewhere into flipping table */
+ case 97:
+ /* somewhere into flipping table */
local->activate_timer = true;
kernel_seq_delete(seq[fx_table]);
seq[fx_table] = kernel_seq_forward(ss[fx_table], false, 10, 0, 0, 1);
@@ -886,7 +887,6 @@ static void handle_animation_mac() {
}
}
-
static void handle_animation_mac_2() {
int mac_reset_frame;
@@ -1038,7 +1038,6 @@ static void handle_animation_twinkles() {
twinkles_reset_frame = -1;
switch (local->twinkles_frame) {
-
case 1: /* keep her invisible behind wall */
if (local->twinkles_action == TWINKLES_INVISIBLE) {
twinkles_reset_frame = 0;
@@ -1068,9 +1067,7 @@ static void handle_animation_twinkles() {
case 37: /* end of coming into room and freeze */
case 38: /* end of freeze 2 */
case 47: /* end of talk */
-
switch (local->twinkles_action) {
-
case TWINKLES_TALK:
twinkles_reset_frame = 38; /* talk */
local->twinkles_action = TWINKLES_SHUT_UP;
@@ -1105,7 +1102,6 @@ static void handle_animation_twinkles() {
}
}
-
static void handle_animation_death() {
int death_reset_frame;
@@ -1114,7 +1110,6 @@ static void handle_animation_death() {
death_reset_frame = -1;
switch (local->death_frame) {
-
case 11:
global_speech_go(7); /* hurl */
break;
@@ -1291,7 +1286,7 @@ static void handle_animation_pid() {
}
}
-void room_104_daemon(void) {
+static void room_104_daemon() {
int reset_frame;
int temp; /* for synching purposes */
long dif; /* for timer stuff */
@@ -1373,7 +1368,6 @@ void room_104_daemon(void) {
}
if (kernel.trigger == ROOM_104_RUN_P2) {
-
kernel_abort_animation(aa[4]);
aa[4] = kernel_run_animation(kernel_name('p', 2), 0);
@@ -1451,7 +1445,7 @@ void room_104_daemon(void) {
}
}
-void room_104_parser() {
+static void room_104_parser() {
int16 temp; // used for kernel_synch old-sequence argument
// Active conversation: dispatch to the finale processor
@@ -1464,27 +1458,28 @@ void room_104_parser() {
// Room description on look_around
if (player.look_around) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10401); // text_104_01
+ text_show(10401);
} else {
- text_show(10437); // text_104_37
+ text_show(10437);
}
player.command_ready = false;
return;
}
// walk_through / open / pull + door_to_throne_room (176)
- if (player_parse(37, 176, 0) || player_parse(6, 176, 0) || player_parse(10, 176, 0)) {
+ if (player_said_2(walk_through, door_to_throne_room) || player_said_2(open, door_to_throne_room) ||
+ player_said_2(pull, door_to_throne_room)) {
if (global[player_persona] == PLAYER_IS_KING) {
new_room = 106;
} else {
- text_show(10434); // text_104_34
+ text_show(10434);
}
player.command_ready = false;
return;
}
// walk_through / open / pull + door_to_hallway (197)
- if (player_parse(37, 197, 0) || player_parse(6, 197, 0) || player_parse(10, 197, 0)) {
+ if (player_said_2(walk_through, door_to_hallway) || player_said_2(open, door_to_hallway) || player_said_2(pull, door_to_hallway)) {
if (global[player_persona] == PLAYER_IS_KING) {
switch (kernel.trigger) {
case 0:
@@ -1536,16 +1531,16 @@ void room_104_parser() {
break;
}
} else {
- text_show(10434); // text_104_34
+ text_show(10434);
}
player.command_ready = false;
return;
}
// pull / take / open + books (274) â open the bookcase
- if (player_parse(10, 274, 0) || player_parse(4, 274, 0) || player_parse(6, 274, 0)) {
+ if (player_said_2(pull, books) || player_said_2(take, books) || player_said_2(open, books)) {
if (global[books_status] == BOOKS_PRESENT ||
- global[books_status] == BOOKS_PRESENT2 || kernel.trigger) {
+ global[books_status] == BOOKS_PRESENT2 || kernel.trigger) {
switch (kernel.trigger) {
case 0:
player.commands_allowed = false;
@@ -1567,7 +1562,7 @@ void room_104_parser() {
kernel_synch(KERNEL_SERIES, seq[fx_wall_open_close], KERNEL_SERIES, temp);
kernel_seq_depth(seq[fx_wall_open_close], 8);
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
+ global[tapestry_status] == TAPESTRY_OPENED2) {
kernel_flip_hotspot(276, false); // words_wall_panel
kernel_flip_hotspot(275, true); // words_secret_door
}
@@ -1582,12 +1577,12 @@ void room_104_parser() {
global[player_score] += 2;
global[books_status] = BOOKS_PULLED;
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
- text_show(10428); // text_104_28
+ global[tapestry_status] == TAPESTRY_OPENED2) {
+ text_show(10428);
} else {
sound_play(N_BooksRumble);
sound_play(N_WallGrinds);
- text_show(10427); // text_104_27
+ text_show(10427);
}
} else {
sound_play(94); // N_BooksRumble
@@ -1603,7 +1598,7 @@ void room_104_parser() {
}
// push + books (274) â close the bookcase
- if (player_parse(5, 274, 0)) {
+ if (player_said_2(push, books)) {
if (global[books_status] == BOOKS_PULLED || global[books_status] == BOOKS_PULLED2) {
switch (kernel.trigger) {
case 0:
@@ -1625,7 +1620,7 @@ void room_104_parser() {
break;
case 2:
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
+ global[tapestry_status] == TAPESTRY_OPENED2) {
kernel_flip_hotspot(276, true); // words_wall_panel
kernel_flip_hotspot(275, false); // words_secret_door
}
@@ -1637,7 +1632,7 @@ void room_104_parser() {
break;
case 4:
if (global[books_status] == BOOKS_PULLED) {
- text_show(10429); // text_104_29
+ text_show(10429);
}
global[books_status] = BOOKS_PRESENT2;
player.commands_allowed = true;
@@ -1649,11 +1644,11 @@ void room_104_parser() {
}
// pull / open / push + tapestry (26) â open the tapestry
- if (player_parse(10, 26, 0) || player_parse(6, 26, 0) || player_parse(5, 26, 0)) {
+ if (player_said_2(pull, tapestry) || player_said_2(open, tapestry) || player_said_2(push, tapestry)) {
if (global[player_persona] == PLAYER_IS_KING) {
if (inter_point_x >= CORRECT_TAPESTRY_X) {
if (global[tapestry_status] == TAPESTRY_CLOSED ||
- global[tapestry_status] == TAPESTRY_CLOSED2) {
+ global[tapestry_status] == TAPESTRY_CLOSED2) {
switch (kernel.trigger) {
case 0:
kernel_seq_delete(seq[fx_tapestry_closed]);
@@ -1670,7 +1665,7 @@ void room_104_parser() {
kernel_flip_hotspot_loc(26, true, TAP_HS_OPEN_X, TAP_HS_OPEN_Y);
kernel_flip_hotspot_loc(26, false, TAP_HS_CLOSED_X, TAP_HS_CLOSED_Y);
if (global[books_status] == BOOKS_PULLED ||
- global[books_status] == BOOKS_PULLED2) {
+ global[books_status] == BOOKS_PULLED2) {
kernel_flip_hotspot(276, false); // wall_panel
kernel_flip_hotspot(275, true); // secret_door
} else {
@@ -1684,12 +1679,12 @@ void room_104_parser() {
global[tapestry_status] = TAPESTRY_OPENED;
global[player_score] += 2;
if (global[books_status] == BOOKS_NOT_PRESENT ||
- global[books_status] == BOOKS_PRESENT ||
- global[books_status] == BOOKS_PRESENT2) {
- text_show(10424); // text_104_24
+ global[books_status] == BOOKS_PRESENT ||
+ global[books_status] == BOOKS_PRESENT2) {
+ text_show(10424);
} else if (global[books_status] == BOOKS_PULLED ||
- global[books_status] == BOOKS_PULLED2) {
- text_show(10425); // text_104_25
+ global[books_status] == BOOKS_PULLED2) {
+ text_show(10425);
}
} else {
global[tapestry_status] = TAPESTRY_OPENED2;
@@ -1702,23 +1697,23 @@ void room_104_parser() {
}
// tapestry already open: fall through to close-tapestry check below
} else {
- text_show(10404); // text_104_04
+ text_show(10404);
player.command_ready = false;
return;
}
} else {
- text_show(10445); // text_104_45
+ text_show(10445);
player.command_ready = false;
return;
}
}
// pull / close / push + tapestry (26) â close the tapestry
- if (player_parse(10, 26, 0) || player_parse(11, 26, 0) || player_parse(5, 26, 0)) {
+ if (player_said_2(pull, tapestry) || player_said_2(close, tapestry) || player_said_2(push, tapestry)) {
if (global[player_persona] == PLAYER_IS_KING) {
if (inter_point_x >= CORRECT_TAPESTRY_X) {
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
+ global[tapestry_status] == TAPESTRY_OPENED2) {
switch (kernel.trigger) {
case 0:
player.commands_allowed = false;
@@ -1748,7 +1743,7 @@ void room_104_parser() {
}
}
} else {
- text_show(10445); // text_104_45
+ text_show(10445);
player.command_ready = false;
return;
}
@@ -1757,15 +1752,13 @@ void room_104_parser() {
// ---------------------------------------------------------------------------
// look / look_at block
// ---------------------------------------------------------------------------
- if (player_parse(3, 0) || player_parse(17, 0)) { // look(3) or look_at(17) â TODO: verify look_at ID
-
- if (player_parse(3, 274, 0) || player_parse(17, 274, 0) || // books
- player_parse(3, 199, 0) || player_parse(17, 199, 0)) { // bookshelf
+ if (player_said_1(look) || player_said_1(look_at)) {
+ if (player_said_1(books) || player_said_1(bookshelf)) {
if (global[player_persona] == PLAYER_IS_KING) {
if (global[books_status] == BOOKS_NOT_PRESENT) {
kernel_flip_hotspot(274, true);
global[books_status] = BOOKS_PRESENT;
- text_show(10418); // text_104_18
+ text_show(10418);
player.command_ready = false;
return;
} else if (global[books_status] == BOOKS_PRESENT) {
@@ -1773,62 +1766,62 @@ void room_104_parser() {
player.command_ready = false;
return;
} else if (global[books_status] == BOOKS_PULLED ||
- global[books_status] == BOOKS_PULLED2) {
- text_show(10419); // text_104_19
+ global[books_status] == BOOKS_PULLED2) {
+ text_show(10419);
player.command_ready = false;
return;
} else if (global[books_status] == BOOKS_PRESENT2) {
- text_show(10420); // text_104_20
+ text_show(10420);
player.command_ready = false;
return;
}
} else {
- text_show(10439); // text_104_39
+ text_show(10439);
player.command_ready = false;
return;
}
}
- if (player_parse(3, 34, 0) || player_parse(17, 34, 0)) { // fireplace (34)
+ if (player_said_1(fireplace)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10402); // text_104_02
+ text_show(10402);
} else {
- text_show(10438); // text_104_38
+ text_show(10438);
}
player.command_ready = false;
return;
}
- if (player_parse(3, 26, 0) || player_parse(17, 26, 0)) { // tapestry (26)
+ if (player_said_1(tapestry)) {
if (global[player_persona] == PLAYER_IS_KING) {
if (inter_point_x >= ARCHER_TAPESTRY_BEGIN_X && inter_point_x <= ARCHER_TAPESTRY_END_X) {
- text_show(10403); // text_104_03
+ text_show(10403);
player.command_ready = false;
return;
} else if (inter_point_x >= CASTLE_TAPESTRY_BEGIN_X && inter_point_x <= CASTLE_TAPESTRY_END_X) {
- text_show(10422); // text_104_22
+ text_show(10422);
player.command_ready = false;
return;
} else {
if (global[tapestry_status] == TAPESTRY_OPENED ||
- global[tapestry_status] == TAPESTRY_OPENED2) {
- text_show(10460); // text_104_60
+ global[tapestry_status] == TAPESTRY_OPENED2) {
+ text_show(10460);
} else {
- text_show(10423); // text_104_23
+ text_show(10423);
}
player.command_ready = false;
return;
}
} else {
- text_show(10439); // text_104_39
+ text_show(10439);
player.command_ready = false;
return;
}
}
- if (player_parse(3, 18, 0) || player_parse(17, 18, 0)) { // rug (18)
+ if (player_said_1(rug)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10405); // text_104_05
+ text_show(10405);
} else {
text_show(10439);
}
@@ -1836,9 +1829,9 @@ void room_104_parser() {
return;
}
- if (player_parse(3, 35, 0) || player_parse(17, 35, 0)) { // fireplace_screen (35)
+ if (player_said_1(fireplace_screen)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10407); // text_104_07
+ text_show(10407);
} else {
text_show(10439);
}
@@ -1846,50 +1839,49 @@ void room_104_parser() {
return;
}
- if (player_parse(3, 176, 0) || player_parse(17, 176, 0)) { // door_to_throne_room (176)
+ if (player_said_1(door_to_throne_room)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10409); // text_104_09
+ text_show(10409);
} else {
- text_show(10434); // text_104_34
+ text_show(10434);
}
player.command_ready = false;
return;
}
- if (player_parse(3, 204, 0) || player_parse(17, 204, 0)) { // sconce â TODO: verify ID=204
+ if (player_said_1(sconce)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10410); // text_104_10
+ text_show(10410);
} else {
- text_show(10440); // text_104_40
+ text_show(10440);
}
player.command_ready = false;
return;
}
- // wood_basket â TODO: unknown vocab ID; needs disassembly verification
- // if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
- // if (global[player_persona] == PLAYER_IS_KING) {
- // text_show(10411); // text_104_11
- // } else {
- // text_show(10439);
- // }
- // player.command_ready = false;
- // return;
- // }
+ if (player_said_1(basket)) {
+ if (global[player_persona] == PLAYER_IS_KING) {
+ text_show(10411);
+ } else {
+ text_show(10439);
+ }
+ player.command_ready = false;
+ return;
+ }
- if (player_parse(3, 200, 0) || player_parse(17, 200, 0)) { // trophy (200)
+ if (player_said_1(trophy)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10412); // text_104_12
+ text_show(10412);
} else {
- text_show(10441); // text_104_41
+ text_show(10441);
}
player.command_ready = false;
return;
}
- if (player_parse(3, 201, 0) || player_parse(17, 201, 0)) { // reading_bench (201)
+ if (player_said_1(reading_bench)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10414); // text_104_14
+ text_show(10414);
} else {
text_show(10439);
}
@@ -1897,9 +1889,9 @@ void room_104_parser() {
return;
}
- if (player_parse(3, 203, 0) || player_parse(17, 203, 0)) { // loveseat (203)
+ if (player_said_1(loveseat)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10416); // text_104_16
+ text_show(10416);
} else {
text_show(10439);
}
@@ -1907,9 +1899,9 @@ void room_104_parser() {
return;
}
- if (player_parse(3, 197, 0) || player_parse(17, 197, 0)) { // door_to_hallway (197)
+ if (player_said_1(door_to_hallway)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10421); // text_104_21
+ text_show(10421);
} else {
text_show(10434);
}
@@ -1917,139 +1909,132 @@ void room_104_parser() {
return;
}
- if (player_parse(3, 275, 0) || player_parse(17, 275, 0)) { // secret_door (275)
- text_show(10430); // text_104_30
+ if (player_said_1(secret_door)) {
+ text_show(10430);
player.command_ready = false;
return;
}
- if (player_parse(3, 277, 0) || player_parse(17, 277, 0) ||
- player_parse(37, 277, 0) || player_parse(6, 277, 0)) { // doorway variants
+ if (player_said_1(doorway)) {
if (global[player_persona] == PLAYER_IS_PID) {
- text_show(10432); // text_104_32
+ text_show(10432);
player.command_ready = false;
return;
}
}
- if (player_parse(3, 276, 0)) { // look + wall_panel (276) â look_at not in original
+ if (player_said_1(wall_panel)) {
if (global[books_status] == BOOKS_NOT_PRESENT ||
- global[books_status] == BOOKS_PRESENT) {
- text_show(10435); // text_104_35
+ global[books_status] == BOOKS_PRESENT) {
+ text_show(10435);
} else {
- text_show(10436); // text_104_36
+ text_show(10436);
}
player.command_ready = false;
return;
}
- if (player_parse(3, 291, 0) || player_parse(17, 291, 0)) { // king (291)
+ if (player_said_1(king)) {
if (global[player_persona] == PLAYER_IS_PID) {
- text_show(10443); // text_104_43
+ text_show(10443);
player.command_ready = false;
return;
}
}
- if (player_parse(3, 414, 0) || player_parse(17, 414, 0)) { // music_box (414)
+ if (player_said_1(music_box)) {
if (global[no_load_walker]) {
- // TODO: object_examine(magic_music_box, 843, 0);
- // magic_music_box = inventory object ID â needs verification
- // 843 = text_008_43 message ID
+ object_examine(magic_music_box, 843, 0);
player.command_ready = false;
return;
}
}
- if (player_parse(3, 350, 0) || player_parse(17, 350, 0)) { // MacMorn (350) â PID only here
+ if (player_said_1(MacMorn)) {
if (global[player_persona] == PLAYER_IS_PID) {
- text_show(10444); // text_104_44
+ text_show(10444);
player.command_ready = false;
return;
}
- // King looking at MacMorn: falls through to second MacMorn check at end of look block
}
- if (player_parse(3, 198, 0) || player_parse(17, 198, 0)) { // table (198)
+ if (player_said_1(table)) {
if (global[player_persona] == PLAYER_IS_PID) {
- text_show(10455); // text_104_55
+ text_show(10455);
} else {
if (inter_point_x < 174) {
- text_show(10451); // text_104_51
+ text_show(10451);
} else {
- text_show(10448); // text_104_48
+ text_show(10448);
}
}
player.command_ready = false;
return;
}
- if (player_parse(3, 237, 0) || player_parse(17, 237, 0)) { // decoration (237)
+ if (player_said_1(decoration)) {
if (global[player_persona] == PLAYER_IS_PID) {
text_show(10439);
} else {
- text_show(10449); // text_104_49
+ text_show(10449);
}
player.command_ready = false;
return;
}
- if (player_parse(3, 56, 0) || player_parse(17, 56, 0)) { // sword (56)
+ if (player_said_1(sword)) {
if (global[player_persona] == PLAYER_IS_PID) {
text_show(10439);
} else {
- text_show(10450); // text_104_50
+ text_show(10450);
}
player.command_ready = false;
return;
}
- // floor â TODO: unknown vocab ID; needs disassembly verification
- // if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
- // if (global[player_persona] == PLAYER_IS_PID) {
- // text_show(10439);
- // player.command_ready = false;
- // return;
- // }
- // }
-
- // wall â TODO: unknown vocab ID; needs disassembly verification
- // if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
- // if (global[player_persona] == PLAYER_IS_PID) {
- // text_show(10439);
- // player.command_ready = false;
- // return;
- // }
- // }
-
- // candlestick â TODO: unknown vocab ID; needs disassembly verification
- // if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
- // if (global[player_persona] == PLAYER_IS_PID) {
- // text_show(10439);
- // } else {
- // text_show(10461); // text_104_61
- // }
- // player.command_ready = false;
- // return;
- // }
-
- if (player_parse(3, 347, 0) || player_parse(17, 347, 0)) { // Queen_Mother (347)
- text_show(10456); // text_104_56
+ if (player_said_1(floor)) {
+ if (global[player_persona] == PLAYER_IS_PID) {
+ text_show(10439);
+ player.command_ready = false;
+ return;
+ }
+ }
+
+ if (player_said_1(wall)) {
+ if (global[player_persona] == PLAYER_IS_PID) {
+ text_show(10439);
+ player.command_ready = false;
+ return;
+ }
+ }
+
+ if (player_said_1(candlestick)) {
+ if (global[player_persona] == PLAYER_IS_PID) {
+ text_show(10439);
+ } else {
+ text_show(10461);
+ }
player.command_ready = false;
return;
}
- if (player_parse(3, 350, 0) || player_parse(17, 350, 0)) { // MacMorn (350) â King reach
- text_show(10444); // text_104_44
+ if (player_said_1(Queen_Mother)) {
+ text_show(10456);
+ player.command_ready = false;
+ return;
+ }
+
+ if (player_said_1(MacMorn)) {
+ text_show(10444);
player.command_ready = false;
return;
}
} // end look/look_at block
// push / pull + rug (18)
- if (player_parse(5, 18, 0) || player_parse(10, 18, 0)) {
+ if (player_said_2(push, rug) || player_said_2(pull, rug)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10406); // text_104_06
+ text_show(10406);
} else {
text_show(10445);
}
@@ -2058,9 +2043,9 @@ void room_104_parser() {
}
// push / pull + fireplace_screen (35)
- if (player_parse(5, 35, 0) || player_parse(10, 35, 0)) {
+ if (player_said_2(push, fireplace_screen) || player_said_2(pull, fireplace_screen)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10408); // text_104_08
+ text_show(10408);
} else {
text_show(10445);
}
@@ -2069,9 +2054,9 @@ void room_104_parser() {
}
// push / pull + trophy (200)
- if (player_parse(5, 200, 0) || player_parse(10, 200, 0)) {
+ if (player_said_2(push, trophy) || player_said_2(pull, trophy)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10413); // text_104_13
+ text_show(10413);
} else {
text_show(10445);
}
@@ -2080,9 +2065,9 @@ void room_104_parser() {
}
// open + reading_bench (201)
- if (player_parse(6, 201, 0)) {
+ if (player_said_2(open, reading_bench)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10415); // text_104_15
+ text_show(10415);
} else {
text_show(10445);
}
@@ -2091,9 +2076,9 @@ void room_104_parser() {
}
// push / pull + loveseat (203)
- if (player_parse(5, 203, 0) || player_parse(10, 203, 0)) {
+ if (player_said_2(push, loveseat) || player_said_2(pull, loveseat)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10417); // text_104_17
+ text_show(10417);
} else {
text_show(10445);
}
@@ -2102,39 +2087,38 @@ void room_104_parser() {
}
// open / push / pull + secret_door (275) or wall_panel (276)
- if (player_parse(6, 275, 0) || player_parse(6, 276, 0) ||
- player_parse(5, 275, 0) || player_parse(5, 276, 0) ||
- player_parse(10, 275, 0) || player_parse(10, 276, 0)) {
- text_show(10431); // text_104_31
+ if (player_said_2(open, secret_door) || player_said_2(open, wall_panel) ||
+ player_said_2(push, secret_door) || player_said_2(push, wall_panel) ||
+ player_said_2(pull, secret_door) || player_said_2(pull, wall_panel)) {
+ text_show(10431);
player.command_ready = false;
return;
}
- // invoke (47) + signet_ring â TODO: verify signet_ring vocab ID
- // if (player_parse(47, ???, 0)) {
- // if (global[player_persona] == PLAYER_IS_PID) {
- // text_show(10433); // text_104_33
- // player.command_ready = false;
- // return;
- // }
- // }
+ if (player_said_2(invoke, signet_ring)) {
+ if (global[player_persona] == PLAYER_IS_PID) {
+ text_show(10433);
+ player.command_ready = false;
+ return;
+ }
+ }
// put (7) + tentacle_parts (125) + wall_panel (276)
- if (player_parse(7, 125, 276, 0)) {
+ if (player_said_3(put, tentacle_parts, wall_panel)) {
if (global[player_persona] == PLAYER_IS_KING) {
- text_show(10446); // text_104_46
+ text_show(10446);
player.command_ready = false;
return;
}
}
// shift_into_bear (116) â bear transformation in finale
- if (player_parse(116, 0)) {
+ if (player_said_1(shift_into_bear)) {
if (local->anim_0_running) {
if (local->has_been_bear) {
- text_show(10457); // text_104_57
+ text_show(10457);
} else if (local->amulet_works) {
- text_show(10459); // text_104_59
+ text_show(10459);
} else {
local->has_been_bear = true;
global[player_score] += 2;
@@ -2154,8 +2138,8 @@ void room_104_parser() {
}
// sword(56)+attack(57)/carve_up(58)/thrust(87) on MacMorn(350), or take(4)+sword(56)
- if (player_parse(56, 57, 350, 0) || player_parse(56, 58, 350, 0) ||
- player_parse(56, 87, 350, 0) || player_parse(4, 56, 0)) {
+ if (player_said_3(sword, attack, MacMorn) || player_said_3(sword, carve_up, MacMorn) ||
+ player_said_3(sword, thrust, MacMorn) || player_said_2(take, sword)) {
if (local->anim_0_running) {
local->activate_timer = false;
local->pid_action = PID_DRAW_SWORD;
@@ -2182,7 +2166,7 @@ void room_104_parser() {
}
// invoke (47) + amulet (46)
- if (player_parse(47, 46, 0)) {
+ if (player_said_2(invoke, amulet)) {
if (local->anim_0_running) {
if (local->amulet_works) {
local->activate_timer = false;
@@ -2198,14 +2182,12 @@ void room_104_parser() {
kernel_synch(KERNEL_SERIES, seq[fx_e5], KERNEL_NOW, 0);
ss[fx_e3] = kernel_load_series(kernel_name('e', 3),
- PAL_MAP_ALL_TO_CLOSEST | PAL_MAP_ANY_TO_CLOSEST);
+ PAL_MAP_ALL_TO_CLOSEST | PAL_MAP_ANY_TO_CLOSEST);
aa[1] = kernel_run_animation(kernel_name('m', 2), 0);
local->anim_1_running = false;
local->anim_5_running = true;
} else {
- // TODO: object_examine(amulet_obj_id, 945, 0);
- // amulet_obj_id = inventory object ID for amulet â needs verification
- // 945 = text_009_45 message ID
+ object_examine(amulet, 945, 0);
}
player.command_ready = false;
return;
@@ -2213,61 +2195,59 @@ void room_104_parser() {
}
// sword+attack/carve_up/thrust on Queen_Mother (347)
- if (player_parse(56, 57, 347, 0) || player_parse(56, 58, 347, 0) ||
- player_parse(56, 87, 347, 0)) {
- text_show(10458); // text_104_58
+ if (player_said_3(sword, attack, Queen_Mother) || player_said_3(sword, carve_up, Queen_Mother) ||
+ player_said_3(sword, thrust, Queen_Mother)) {
+ text_show(10458);
player.command_ready = false;
return;
}
// talk_to (8) + MacMorn (350)
- if (player_parse(8, 350, 0)) {
- text_show(10464); // text_104_64
+ if (player_said_2(talk_to, MacMorn)) {
+ text_show(10464);
player.command_ready = false;
return;
}
// talk_to (8) + Queen_Mother (347)
- if (player_parse(8, 347, 0)) {
- text_show(10463); // text_104_63
+ if (player_said_2(talk_to, Queen_Mother)) {
+ text_show(10463);
player.command_ready = false;
return;
}
// talk_to (8) + king (291)
- if (player_parse(8, 291, 0)) {
- text_show(10465); // text_104_65
+ if (player_said_2(talk_to, king)) {
+ text_show(10465);
player.command_ready = false;
return;
}
- // pour_contents_of + MacMorn â TODO: verify pour_contents_of vocab ID
- // if (player_parse(???, 350, 0)) {
- // text_show(10462); // text_104_62
- // player.command_ready = false;
- // return;
- // }
+ // pour_contents_of (99) + MacMorn (350)
+ if (player_said_2(pour_contents_of, MacMorn)) {
+ text_show(10462);
+ player.command_ready = false;
+ return;
+ }
- // walk_across (41) / walk_to (13) when Queen is in room
if (local->anim_2_running) {
- if (player_parse(41, 0) || player_parse(13, 0)) {
- text_show(10445); // text_104_45
+ if (player_said_1(walk_across) || player_said_1(walk_to)) {
+ text_show(10445);
player.command_ready = false;
return;
}
}
- // take (4) + candlestick â TODO: verify candlestick vocab ID
- // if (player_parse(4, ???, 0)) {
- // text_show(10468); // text_104_68
- // player.command_ready = false;
- // return;
- // }
+ if (player_said_2(take, candlestick)) {
+ text_show(10468);
+ player.command_ready = false;
+ return;
+ }
// open (6) + music_box (414) when in finale scene
- if (player_parse(6, 414, 0)) {
+ if (player_said_2(open, music_box)) {
if (global[no_load_walker]) {
- text_show(10470); // text_104_70
+ text_show(10470);
player.command_ready = false;
return;
}
More information about the Scummvm-git-logs
mailing list