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

dreammaster noreply at scummvm.org
Sat Jun 6 11:38:01 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:
601fc79921 MM: MM1: Add classic PC speaker sounds
30eb8f2e15 MM: MM1: Update classic PC speaker playback
f6c38ae6fb MM: MM1: Add classic sound test command


Commit: 601fc79921b3a4a6c34af80bdc561dd9c6f618ea
    https://github.com/scummvm/scummvm/commit/601fc79921b3a4a6c34af80bdc561dd9c6f618ea
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-06T21:37:57+10:00

Commit Message:
MM: MM1: Add classic PC speaker sounds

Changed paths:
  A engines/mm/shared/classic/pc_speaker.cpp
  A engines/mm/shared/classic/pc_speaker.h
    engines/mm/mm1/game/combat.cpp
    engines/mm/mm1/game/encounter.cpp
    engines/mm/mm1/sound.cpp
    engines/mm/mm1/sound.h
    engines/mm/mm1/views/combat.cpp
    engines/mm/mm1/views/interactions/hacker.cpp
    engines/mm/mm1/views/interactions/inspectron.cpp
    engines/mm/mm1/views/interactions/lord_ironfist.cpp
    engines/mm/mm1/views/interactions/prisoners.cpp
    engines/mm/mm1/views/interactions/prisoners.h
    engines/mm/mm1/views/search.cpp
    engines/mm/mm1/views/title.cpp
    engines/mm/module.mk


diff --git a/engines/mm/mm1/game/combat.cpp b/engines/mm/mm1/game/combat.cpp
index 3da28dc6cfa..b12442f46d6 100644
--- a/engines/mm/mm1/game/combat.cpp
+++ b/engines/mm/mm1/game/combat.cpp
@@ -900,6 +900,7 @@ void Combat::updateMonsterStatus() {
 	if (val <= 0) {
 		_monsterP->_hp = 0;
 		_monsterP->_status = MONFLAG_DEAD;
+		Sound::sound2(SOUND_9);
 
 	} else {
 		_monsterP->_hp = val;
@@ -1107,6 +1108,7 @@ void Combat::resetDestMonster() {
 
 void Combat::spellFailed() {
 	g_globals->_combatParty[_currentChar]->_checked = true;
+	Sound::sound(SOUND_2);
 
 	SoundMessage msg(10, 2, SpellCasting::spellResultMessage(STRING["spells.failed"]));
 	msg._delaySeconds = 3;
diff --git a/engines/mm/mm1/game/encounter.cpp b/engines/mm/mm1/game/encounter.cpp
index e6bdc19aaba..91520da80c0 100644
--- a/engines/mm/mm1/game/encounter.cpp
+++ b/engines/mm/mm1/game/encounter.cpp
@@ -25,6 +25,7 @@
 #include "mm/mm1/events.h"
 #include "mm/mm1/globals.h"
 #include "mm/mm1/mm1.h"
+#include "mm/mm1/sound.h"
 
 namespace MM {
 namespace MM1 {
@@ -34,6 +35,8 @@ void Encounter::execute() {
 	if (!g_globals->_encountersOn)
 		return;
 
+	Sound::sound2(SOUND_1);
+
 	Maps::Map &map = *g_maps->_currentMap;
 	int comp, maxRand, maxVal;
 	const Monster *monsterP;
diff --git a/engines/mm/mm1/sound.cpp b/engines/mm/mm1/sound.cpp
index 4a747e2cd54..a846c71e382 100644
--- a/engines/mm/mm1/sound.cpp
+++ b/engines/mm/mm1/sound.cpp
@@ -19,13 +19,117 @@
  *
  */
 
-#include "common/textconsole.h"
+#include "common/system.h"
 #include "mm/mm1/sound.h"
 #include "mm/mm1/mm1.h"
 
 namespace MM {
 namespace MM1 {
 
+static const uint32 kBiosTimerTickMicros = 54925;
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound0[] = {
+	{ 1, 0x0a98 }, { 1, 0x0970 }, { 1, 0x08e8 }, { 1, 0x07ef },
+	{ 14, 0x0712 }, { 2, 0x0000 }, { 6, 0x0712 }, { 2, 0x0000 },
+	{ 6, 0x0712 }, { 1, 0x0000 }, { 1, 0x0e24 }, { 1, 0x0a98 },
+	{ 6, 0x08e8 }, { 2, 0x0000 }, { 4, 0x0a98 }, { 4, 0x0970 },
+	{ 4, 0x08e8 }, { 3, 0x07ef }, { 1, 0x0000 }, { 3, 0x0712 },
+	{ 1, 0x0000 }, { 2, 0x06ac }, { 1, 0x08e8 }, { 1, 0x0712 },
+	{ 8, 0x05f2 }, { 4, 0x06ac }, { 4, 0x0712 }, { 8, 0x06ac },
+	{ 4, 0x0712 }, { 2, 0x07ef }, { 1, 0x12e0 }, { 1, 0x0b3a },
+	{ 12, 0x0712 }, { 4, 0x06ac }, { 4, 0x0712 }, { 3, 0x07ef },
+	{ 1, 0x0000 }, { 3, 0x08e8 }, { 1, 0x0000 }, { 3, 0x0970 },
+	{ 1, 0x0000 }, { 14, 0x0a98 }, { 2, 0x0000 }, { 6, 0x0e24 },
+	{ 2, 0x0000 }, { 5, 0x0a98 }, { 1, 0x0000 }, { 1, 0x0e24 },
+	{ 1, 0x0be4 }, { 3, 0x08e8 }, { 1, 0x0000 }, { 3, 0x0be4 },
+	{ 1, 0x0000 }, { 2, 0x08e8 }, { 2, 0x0970 }, { 2, 0x08e8 },
+	{ 2, 0x07ef }, { 14, 0x0712 }, { 2, 0x0000 }, { 2, 0x06ac },
+	{ 2, 0x0712 }, { 2, 0x07ef }, { 2, 0x08e8 }, { 2, 0x0712 },
+	{ 2, 0x07ef }, { 2, 0x08e8 }, { 2, 0x0970 }, { 2, 0x07ef },
+	{ 2, 0x08e8 }, { 2, 0x0970 }, { 2, 0x0a98 }, { 2, 0x08e8 },
+	{ 2, 0x0970 }, { 2, 0x0a98 }, { 2, 0x0b3a }, { 4, 0x06ac },
+	{ 5, 0x0712 }, { 6, 0x07ef }, { 7, 0x08e8 }, { 2, 0x0e24 },
+	{ 3, 0x0a98 }, { 32, 0x0868 }, { 4, 0x0000 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound1[] = {
+	{ 2, 0x0be4 }, { 4, 0x0000 }, { 1, 0x08e8 }, { 1, 0x0000 },
+	{ 8, 0x05f2 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound2[] = {
+	{ 2, 0x0be4 }, { 4, 0x0000 }, { 1, 0x0be4 }, { 1, 0x0000 },
+	{ 1, 0x0be4 }, { 1, 0x0000 }, { 1, 0x0be4 }, { 1, 0x0000 },
+	{ 12, 0x07ef }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound3[] = {
+	{ 1, 0x11d1 }, { 1, 0x0be4 }, { 3, 0x0712 }, { 6, 0x0000 },
+	{ 2, 0x0712 }, { 1, 0x0000 }, { 12, 0x0712 }, { 6, 0x0000 },
+	{ 3, 0x0712 }, { 3, 0x0000 }, { 2, 0x06ac }, { 2, 0x0000 },
+	{ 2, 0x0be4 }, { 2, 0x0000 }, { 2, 0x06ac }, { 1, 0x11d1 },
+	{ 1, 0x0be4 }, { 3, 0x0712 }, { 6, 0x0000 }, { 2, 0x0712 },
+	{ 1, 0x0000 }, { 12, 0x0712 }, { 6, 0x0000 }, { 3, 0x0712 },
+	{ 2, 0x0000 }, { 1, 0x17c8 }, { 1, 0x0be4 }, { 2, 0x07ef },
+	{ 2, 0x0000 }, { 2, 0x08e8 }, { 2, 0x0000 }, { 2, 0x07ef },
+	{ 2, 0x0000 }, { 1, 0x0e24 }, { 1, 0x0be4 }, { 3, 0x08e8 },
+	{ 6, 0x0000 }, { 2, 0x0be4 }, { 1, 0x0000 }, { 1, 0x8e84 },
+	{ 1, 0x5f1e }, { 1, 0x4742 }, { 1, 0x2f8f }, { 1, 0x23a2 },
+	{ 1, 0x17c8 }, { 1, 0x11d1 }, { 1, 0x0e24 }, { 1, 0x0be4 },
+	{ 12, 0x08e8 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound4[] = {
+	{ 16, 0x06ac }, { 3, 0x0000 }, { 4, 0x0868 }, { 4, 0x08e8 },
+	{ 4, 0x0970 }, { 16, 0x08e8 }, { 3, 0x0000 }, { 4, 0x0b3a },
+	{ 4, 0x0be4 }, { 4, 0x0b3a }, { 24, 0x0d59 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound5[] = {
+	{ 2, 0x8e84 }, { 2, 0x5f1e }, { 2, 0x4742 }, { 2, 0x2f8f },
+	{ 2, 0x23a2 }, { 2, 0x17c8 }, { 2, 0x11d1 }, { 2, 0x0be4 },
+	{ 2, 0x08e8 }, { 2, 0x05f2 }, { 2, 0x0474 }, { 2, 0x0389 },
+	{ 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound6[] = {
+	{ 6, 0x03e8 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound7[] = {
+	{ 8, 0x4e20 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound8[] = {
+	{ 8, 0x0b3a }, { 8, 0x0be4 }, { 8, 0x0c98 }, { 8, 0x0be4 },
+	{ 2, 0x0000 }, { 16, 0x0d59 }, { 0, 0x0000 }
+};
+
+static const Shared::Classic::PitSequenceEntry kMM1Sound9[] = {
+	{ 5, 0x08e8 }, { 1, 0x0000 }, { 1, 0x07ef }, { 1, 0x0000 },
+	{ 1, 0x0be4 }, { 1, 0x0000 }, { 1, 0x07ef }, { 1, 0x0000 },
+	{ 12, 0x0712 }, { 0, 0x0000 }
+};
+
+Sound::Sound(Audio::Mixer *mixer) : Shared::Xeen::Sound(mixer) {
+	_speaker.init();
+}
+
+void Sound::playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool append, bool restart) {
+	if (!g_engine->_sound || !g_engine->_sound->_fxOn)
+		return;
+	if (g_engine->isEnhanced())
+		return;
+
+	if (restart)
+		g_engine->_sound->_speaker.stop();
+
+	if (!append && !restart && g_engine->_sound->_speaker.isPlaying())
+		return;
+
+	g_engine->_sound->_speaker.playPitSequence(sequence, kBiosTimerTickMicros, append);
+}
+
 void Sound::sound(SoundId soundNum) {
 	if (g_engine->isEnhanced()) {
 		if (soundNum == SOUND_1) {
@@ -34,15 +138,60 @@ void Sound::sound(SoundId soundNum) {
 		}
 	}
 
-	warning("TODO: sound %d", (int)soundNum);
+	if (!g_engine->_sound || !g_engine->_sound->_fxOn)
+		return;
+
+	switch (soundNum) {
+	case SOUND_1:
+		playSequence(kMM1Sound7, false, true);
+		break;
+	case SOUND_2:
+		playSequence(kMM1Sound6, false, true);
+		break;
+	case SOUND_3:
+		playSequence(kMM1Sound6, false, true);
+		playSequence(kMM1Sound6, true);
+		playSequence(kMM1Sound6, true);
+		break;
+	default:
+		break;
+	}
 }
 
 void Sound::sound2(SoundId soundNum) {
-	warning("TODO: sound2 %d", (int)soundNum);
+	switch (soundNum) {
+	case SOUND_TITLE:
+		playSequence(kMM1Sound0, true);
+		break;
+	case SOUND_1:
+		playSequence(kMM1Sound1, true);
+		break;
+	case SOUND_2:
+		playSequence(kMM1Sound2, true);
+		break;
+	case SOUND_3:
+		playSequence(kMM1Sound3, true);
+		break;
+	case SOUND_4:
+		playSequence(kMM1Sound4, true);
+		break;
+	case SOUND_5:
+		playSequence(kMM1Sound5, true);
+		break;
+	case SOUND_8:
+		playSequence(kMM1Sound8, true);
+		break;
+	case SOUND_9:
+		playSequence(kMM1Sound9);
+		break;
+	default:
+		break;
+	}
 }
 
 void Sound::stopSound() {
-	warning("TODO: stopSound");
+	if (g_engine->_sound)
+		g_engine->_sound->_speaker.stop();
 }
 
 } // namespace MM1
diff --git a/engines/mm/mm1/sound.h b/engines/mm/mm1/sound.h
index fb4c306085a..59e924ae728 100644
--- a/engines/mm/mm1/sound.h
+++ b/engines/mm/mm1/sound.h
@@ -23,18 +23,26 @@
 #define MM_MM1_SOUND_H
 
 #include "mm/shared/xeen/sound.h"
+#include "mm/shared/classic/pc_speaker.h"
 
 namespace MM {
 namespace MM1 {
 
 enum SoundId {
+	SOUND_TITLE = 0,
 	SOUND_1 = 1, SOUND_2 = 2, SOUND_3 = 3, SOUND_4 = 4,
 	SOUND_5 = 5, SOUND_8 = 8, SOUND_9 = 9
 };
 
 class Sound : public Shared::Xeen::Sound {
+private:
+	Shared::Classic::PcSpeaker _speaker;
+
+	static void playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool append = false, bool restart = false);
+
 public:
-	Sound(Audio::Mixer *mixer) : Shared::Xeen::Sound(mixer) {}
+	Sound(Audio::Mixer *mixer);
+	~Sound() override {}
 
 	static void sound(SoundId soundNum);
 	static void sound2(SoundId soundNum);
diff --git a/engines/mm/mm1/views/combat.cpp b/engines/mm/mm1/views/combat.cpp
index b753a6968d2..9f1f5388897 100644
--- a/engines/mm/mm1/views/combat.cpp
+++ b/engines/mm/mm1/views/combat.cpp
@@ -91,6 +91,7 @@ bool Combat::msgGame(const GameMessage &msg) {
 		_spellResult._lines.clear();
 		_spellResult._lines.push_back(Line(msg._value, 1, msg._stringValue));
 		_spellResult._delaySeconds = 3;
+		Sound::sound(SOUND_2);
 
 		setMode(SPELL_RESULT);
 		return true;
diff --git a/engines/mm/mm1/views/interactions/hacker.cpp b/engines/mm/mm1/views/interactions/hacker.cpp
index a8fe1310947..5cf257dd9b0 100644
--- a/engines/mm/mm1/views/interactions/hacker.cpp
+++ b/engines/mm/mm1/views/interactions/hacker.cpp
@@ -43,6 +43,7 @@ bool Hacker::msgGame(const GameMessage &msg) {
 	if (_canAccept) {
 		// Show the view
 		Sound::sound(SOUND_2);
+		Sound::sound2(SOUND_2);
 		addView();
 
 	} else {
@@ -58,6 +59,7 @@ bool Hacker::msgGame(const GameMessage &msg) {
 		g_maps->_mapPos.x--;
 		map.redrawGame();
 
+		Sound::sound2(SOUND_2);
 		send(SoundMessage(
 			0, 1, STRING["maps.map36.hacker1"],
 			0, 2, line
diff --git a/engines/mm/mm1/views/interactions/inspectron.cpp b/engines/mm/mm1/views/interactions/inspectron.cpp
index 4abe339748e..d18251f51e3 100644
--- a/engines/mm/mm1/views/interactions/inspectron.cpp
+++ b/engines/mm/mm1/views/interactions/inspectron.cpp
@@ -46,6 +46,7 @@ bool Inspectron::msgGame(const GameMessage &msg) {
 	if (_canAccept) {
 		// Open the view for display
 		Sound::sound(SOUND_2);
+		Sound::sound2(SOUND_2);
 		addView();
 
 	} else {
@@ -63,6 +64,7 @@ bool Inspectron::msgGame(const GameMessage &msg) {
 		g_maps->_mapPos.y++;
 		map.redrawGame();
 
+		Sound::sound2(SOUND_2);
 		send(SoundMessage(
 			0, 1, STRING["maps.map35.inspectron1"],
 			0, 2, line
diff --git a/engines/mm/mm1/views/interactions/lord_ironfist.cpp b/engines/mm/mm1/views/interactions/lord_ironfist.cpp
index 987da0f36ed..427f07cb957 100644
--- a/engines/mm/mm1/views/interactions/lord_ironfist.cpp
+++ b/engines/mm/mm1/views/interactions/lord_ironfist.cpp
@@ -37,6 +37,7 @@ bool LordIronfist::msgFocus(const FocusMessage &msg) {
 	g_globals->_currCharacter = &g_globals->_party[0];
 	_canAccept = !g_globals->_currCharacter->_quest;
 	Sound::sound(SOUND_2);
+	Sound::sound2(SOUND_2);
 
 	return TextView::msgFocus(msg);
 }
@@ -46,7 +47,6 @@ void LordIronfist::draw() {
 	clearSurface();
 
 	if (_canAccept) {
-		Sound::sound2(SOUND_2);
 		writeString(0, 1, STRING["maps.map43.ironfist1"]);
 		writeString(0, 2, STRING["maps.map43.ironfist2"]);
 
diff --git a/engines/mm/mm1/views/interactions/prisoners.cpp b/engines/mm/mm1/views/interactions/prisoners.cpp
index b2e079b7e5b..3d7b30eed17 100644
--- a/engines/mm/mm1/views/interactions/prisoners.cpp
+++ b/engines/mm/mm1/views/interactions/prisoners.cpp
@@ -46,6 +46,11 @@ void Prisoner::draw() {
 	writeString(STRING["maps.prisoners.options3"]);
 }
 
+bool Prisoner::msgFocus(const FocusMessage &msg) {
+	Sound::sound2(SOUND_2);
+	return TextView::msgFocus(msg);
+}
+
 bool Prisoner::msgKeypress(const KeypressMessage &msg) {
 	if (endDelay())
 		return true;
diff --git a/engines/mm/mm1/views/interactions/prisoners.h b/engines/mm/mm1/views/interactions/prisoners.h
index 63fbe26abf1..b037fff3eea 100644
--- a/engines/mm/mm1/views/interactions/prisoners.h
+++ b/engines/mm/mm1/views/interactions/prisoners.h
@@ -44,6 +44,7 @@ public:
 	virtual ~Prisoner() {}
 
 	void draw() override;
+	bool msgFocus(const FocusMessage &msg) override;
 	bool msgKeypress(const KeypressMessage &msg) override;
 	void timeout() override;
 };
diff --git a/engines/mm/mm1/views/search.cpp b/engines/mm/mm1/views/search.cpp
index 19946c44922..21ada2243e5 100644
--- a/engines/mm/mm1/views/search.cpp
+++ b/engines/mm/mm1/views/search.cpp
@@ -421,6 +421,7 @@ void Search::drawItem() {
 				c._name,
 				item->_name.c_str()
 			));
+			Sound::sound2(SOUND_5);
 
 			if (treasure.hasItems()) {
 				delaySeconds(2);
diff --git a/engines/mm/mm1/views/title.cpp b/engines/mm/mm1/views/title.cpp
index 39211cef572..dd9be716574 100644
--- a/engines/mm/mm1/views/title.cpp
+++ b/engines/mm/mm1/views/title.cpp
@@ -68,10 +68,16 @@ bool Title::msgFocus(const FocusMessage &msg) {
 	_screenNum = -1;
 	_fadeIndex = 0;
 
+	if (!g_engine->isEnhanced())
+		Sound::sound2(SOUND_TITLE);
+
 	return true;
 }
 
 bool Title::msgUnfocus(const UnfocusMessage & msg) {
+	if (!g_engine->isEnhanced())
+		Sound::stopSound();
+
 	for (int i = 0; i < SCREENS_COUNT; ++i)
 		_screens[i].clear();
 
diff --git a/engines/mm/module.mk b/engines/mm/module.mk
index 5f69cbaaaf4..cc6c4b28fd4 100644
--- a/engines/mm/module.mk
+++ b/engines/mm/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
 	metaengine.o \
 	mm.o \
 	shared/utils/bitmap_font.o \
+	shared/classic/pc_speaker.o \
 	shared/utils/strings.o \
 	shared/utils/strings_data.o \
 	shared/utils/xeen_font.o \
diff --git a/engines/mm/shared/classic/pc_speaker.cpp b/engines/mm/shared/classic/pc_speaker.cpp
new file mode 100644
index 00000000000..1834be138ec
--- /dev/null
+++ b/engines/mm/shared/classic/pc_speaker.cpp
@@ -0,0 +1,101 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "audio/softsynth/pcspk.h"
+#include "mm/shared/classic/pc_speaker.h"
+
+namespace MM {
+namespace Shared {
+namespace Classic {
+
+static const float kPitBaseFrequency = 1193182.0f;
+
+PcSpeaker::PcSpeaker() {
+	_speaker = new Audio::PCSpeaker();
+}
+
+PcSpeaker::~PcSpeaker() {
+	delete _speaker;
+}
+
+bool PcSpeaker::init() {
+	_ready = _speaker->init();
+	return _ready;
+}
+
+void PcSpeaker::stop() {
+	if (_ready)
+		_speaker->stop();
+}
+
+bool PcSpeaker::isPlaying() const {
+	return _ready && _speaker->isPlaying();
+}
+
+void PcSpeaker::playTone(int frequency, int32 lengthMs) {
+	if (_ready)
+		_speaker->play(Audio::PCSpeaker::kWaveFormSquare, frequency, lengthMs);
+}
+
+void PcSpeaker::queueTone(float frequency, uint32 lengthUs) {
+	if (_ready)
+		_speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, frequency, lengthUs);
+}
+
+void PcSpeaker::queueSilence(uint32 lengthUs) {
+	if (_ready)
+		_speaker->playQueue(Audio::PCSpeaker::kWaveFormSilence, 0.0f, lengthUs);
+}
+
+void PcSpeaker::playPitSequence(const PitSequenceEntry *sequence, uint32 tickMicros, bool append) {
+	if (!_ready || !sequence)
+		return;
+
+	if (!append)
+		stop();
+
+	for (const PitSequenceEntry *entry = sequence; entry->durationTicks != 0; ++entry) {
+		const uint32 lengthUs = entry->durationTicks * tickMicros;
+		if (entry->pitDivisor == 0) {
+			queueSilence(lengthUs);
+		} else {
+			queueTone(kPitBaseFrequency / entry->pitDivisor, lengthUs);
+		}
+	}
+}
+
+void PcSpeaker::playFrequencySequence(const FrequencySequenceEntry *sequence) {
+	if (!_ready || !sequence)
+		return;
+
+	stop();
+
+	for (const FrequencySequenceEntry *entry = sequence; entry->lengthUs != 0; ++entry) {
+		if (entry->frequency == 0.0f)
+			queueSilence(entry->lengthUs);
+		else
+			queueTone(entry->frequency, entry->lengthUs);
+	}
+}
+
+} // namespace Classic
+} // namespace Shared
+} // namespace MM
diff --git a/engines/mm/shared/classic/pc_speaker.h b/engines/mm/shared/classic/pc_speaker.h
new file mode 100644
index 00000000000..6f7f89f20f1
--- /dev/null
+++ b/engines/mm/shared/classic/pc_speaker.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MM_SHARED_CLASSIC_PC_SPEAKER_H
+#define MM_SHARED_CLASSIC_PC_SPEAKER_H
+
+#include "common/scummsys.h"
+
+namespace Audio {
+class PCSpeaker;
+}
+
+namespace MM {
+namespace Shared {
+namespace Classic {
+
+struct PitSequenceEntry {
+	uint16 durationTicks;
+	uint16 pitDivisor;
+};
+
+struct FrequencySequenceEntry {
+	uint32 lengthUs;
+	float frequency;
+};
+
+class PcSpeaker {
+private:
+	Audio::PCSpeaker *_speaker = nullptr;
+	bool _ready = false;
+
+public:
+	PcSpeaker();
+	~PcSpeaker();
+
+	bool init();
+	void stop();
+	bool isPlaying() const;
+
+	void playTone(int frequency, int32 lengthMs);
+	void queueTone(float frequency, uint32 lengthUs);
+	void queueSilence(uint32 lengthUs);
+	void playPitSequence(const PitSequenceEntry *sequence, uint32 tickMicros, bool append = false);
+	void playFrequencySequence(const FrequencySequenceEntry *sequence);
+};
+
+} // namespace Classic
+} // namespace Shared
+} // namespace MM
+
+#endif


Commit: 30eb8f2e15b229fb21fe8056748ac59ce7a8cb98
    https://github.com/scummvm/scummvm/commit/30eb8f2e15b229fb21fe8056748ac59ce7a8cb98
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-06T21:37:57+10:00

Commit Message:
MM: MM1: Update classic PC speaker playback

Changed paths:
    engines/mm/mm1/events.cpp
    engines/mm/mm1/sound.cpp
    engines/mm/mm1/sound.h
    engines/mm/shared/classic/pc_speaker.cpp
    engines/mm/shared/classic/pc_speaker.h


diff --git a/engines/mm/mm1/events.cpp b/engines/mm/mm1/events.cpp
index 197e6823d54..91ffb1a673f 100644
--- a/engines/mm/mm1/events.cpp
+++ b/engines/mm/mm1/events.cpp
@@ -24,6 +24,7 @@
 #include "mm/mm1/events.h"
 #include "mm/mm1/mm1.h"
 #include "mm/mm1/gfx/gfx.h"
+#include "mm/mm1/sound.h"
 #include "mm/mm1/views/dialogs.h"
 #include "mm/mm1/views_enh/dialogs.h"
 
@@ -74,6 +75,7 @@ void Events::runGame() {
 		if ((currTime = g_system->getMillis()) >= nextFrameTime) {
 			nextFrameTime = currTime + FRAME_DELAY;
 			tick();
+			Sound::update();
 			drawElements();
 			_screen->update();
 		}
diff --git a/engines/mm/mm1/sound.cpp b/engines/mm/mm1/sound.cpp
index a846c71e382..1e50695144f 100644
--- a/engines/mm/mm1/sound.cpp
+++ b/engines/mm/mm1/sound.cpp
@@ -115,7 +115,7 @@ Sound::Sound(Audio::Mixer *mixer) : Shared::Xeen::Sound(mixer) {
 	_speaker.init();
 }
 
-void Sound::playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool append, bool restart) {
+void Sound::playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool append, bool restart, bool loop) {
 	if (!g_engine->_sound || !g_engine->_sound->_fxOn)
 		return;
 	if (g_engine->isEnhanced())
@@ -127,7 +127,7 @@ void Sound::playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool
 	if (!append && !restart && g_engine->_sound->_speaker.isPlaying())
 		return;
 
-	g_engine->_sound->_speaker.playPitSequence(sequence, kBiosTimerTickMicros, append);
+	g_engine->_sound->_speaker.playPitSequence(sequence, kBiosTimerTickMicros, append, loop);
 }
 
 void Sound::sound(SoundId soundNum) {
@@ -161,10 +161,10 @@ void Sound::sound(SoundId soundNum) {
 void Sound::sound2(SoundId soundNum) {
 	switch (soundNum) {
 	case SOUND_TITLE:
-		playSequence(kMM1Sound0, true);
+		playSequence(kMM1Sound0, false, false, true);
 		break;
 	case SOUND_1:
-		playSequence(kMM1Sound1, true);
+		playSequence(kMM1Sound1, false, true);
 		break;
 	case SOUND_2:
 		playSequence(kMM1Sound2, true);
@@ -189,9 +189,15 @@ void Sound::sound2(SoundId soundNum) {
 	}
 }
 
-void Sound::stopSound() {
+void Sound::update() {
 	if (g_engine->_sound)
+		g_engine->_sound->_speaker.update();
+}
+
+void Sound::stopSound() {
+	if (g_engine->_sound) {
 		g_engine->_sound->_speaker.stop();
+	}
 }
 
 } // namespace MM1
diff --git a/engines/mm/mm1/sound.h b/engines/mm/mm1/sound.h
index 59e924ae728..e03decdc8fd 100644
--- a/engines/mm/mm1/sound.h
+++ b/engines/mm/mm1/sound.h
@@ -38,7 +38,7 @@ class Sound : public Shared::Xeen::Sound {
 private:
 	Shared::Classic::PcSpeaker _speaker;
 
-	static void playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool append = false, bool restart = false);
+	static void playSequence(const Shared::Classic::PitSequenceEntry *sequence, bool append = false, bool restart = false, bool loop = false);
 
 public:
 	Sound(Audio::Mixer *mixer);
@@ -46,6 +46,7 @@ public:
 
 	static void sound(SoundId soundNum);
 	static void sound2(SoundId soundNum);
+	static void update();
 	static void stopSound();
 };
 
diff --git a/engines/mm/shared/classic/pc_speaker.cpp b/engines/mm/shared/classic/pc_speaker.cpp
index 1834be138ec..018c9232f59 100644
--- a/engines/mm/shared/classic/pc_speaker.cpp
+++ b/engines/mm/shared/classic/pc_speaker.cpp
@@ -42,6 +42,9 @@ bool PcSpeaker::init() {
 }
 
 void PcSpeaker::stop() {
+	_loopingPitSequence = nullptr;
+	_loopingPitTickMicros = 0;
+
 	if (_ready)
 		_speaker->stop();
 }
@@ -65,13 +68,7 @@ void PcSpeaker::queueSilence(uint32 lengthUs) {
 		_speaker->playQueue(Audio::PCSpeaker::kWaveFormSilence, 0.0f, lengthUs);
 }
 
-void PcSpeaker::playPitSequence(const PitSequenceEntry *sequence, uint32 tickMicros, bool append) {
-	if (!_ready || !sequence)
-		return;
-
-	if (!append)
-		stop();
-
+void PcSpeaker::queuePitSequence(const PitSequenceEntry *sequence, uint32 tickMicros) {
 	for (const PitSequenceEntry *entry = sequence; entry->durationTicks != 0; ++entry) {
 		const uint32 lengthUs = entry->durationTicks * tickMicros;
 		if (entry->pitDivisor == 0) {
@@ -82,6 +79,18 @@ void PcSpeaker::playPitSequence(const PitSequenceEntry *sequence, uint32 tickMic
 	}
 }
 
+void PcSpeaker::playPitSequence(const PitSequenceEntry *sequence, uint32 tickMicros, bool append, bool loop) {
+	if (!_ready || !sequence)
+		return;
+
+	if (!append)
+		stop();
+
+	_loopingPitSequence = loop ? sequence : nullptr;
+	_loopingPitTickMicros = loop ? tickMicros : 0;
+	queuePitSequence(sequence, tickMicros);
+}
+
 void PcSpeaker::playFrequencySequence(const FrequencySequenceEntry *sequence) {
 	if (!_ready || !sequence)
 		return;
@@ -96,6 +105,11 @@ void PcSpeaker::playFrequencySequence(const FrequencySequenceEntry *sequence) {
 	}
 }
 
+void PcSpeaker::update() {
+	if (_loopingPitSequence && !isPlaying())
+		queuePitSequence(_loopingPitSequence, _loopingPitTickMicros);
+}
+
 } // namespace Classic
 } // namespace Shared
 } // namespace MM
diff --git a/engines/mm/shared/classic/pc_speaker.h b/engines/mm/shared/classic/pc_speaker.h
index 6f7f89f20f1..9a558d701f6 100644
--- a/engines/mm/shared/classic/pc_speaker.h
+++ b/engines/mm/shared/classic/pc_speaker.h
@@ -45,8 +45,12 @@ struct FrequencySequenceEntry {
 class PcSpeaker {
 private:
 	Audio::PCSpeaker *_speaker = nullptr;
+	const PitSequenceEntry *_loopingPitSequence = nullptr;
+	uint32 _loopingPitTickMicros = 0;
 	bool _ready = false;
 
+	void queuePitSequence(const PitSequenceEntry *sequence, uint32 tickMicros);
+
 public:
 	PcSpeaker();
 	~PcSpeaker();
@@ -58,8 +62,9 @@ public:
 	void playTone(int frequency, int32 lengthMs);
 	void queueTone(float frequency, uint32 lengthUs);
 	void queueSilence(uint32 lengthUs);
-	void playPitSequence(const PitSequenceEntry *sequence, uint32 tickMicros, bool append = false);
+	void playPitSequence(const PitSequenceEntry *sequence, uint32 tickMicros, bool append = false, bool loop = false);
 	void playFrequencySequence(const FrequencySequenceEntry *sequence);
+	void update();
 };
 
 } // namespace Classic


Commit: f6c38ae6fbf0110e20e4e6eb6a867f767a9e1000
    https://github.com/scummvm/scummvm/commit/f6c38ae6fbf0110e20e4e6eb6a867f767a9e1000
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-06T21:37:57+10:00

Commit Message:
MM: MM1: Add classic sound test command

Changed paths:
    engines/mm/mm1/console.cpp
    engines/mm/mm1/console.h


diff --git a/engines/mm/mm1/console.cpp b/engines/mm/mm1/console.cpp
index 78e36c45977..958f1999027 100644
--- a/engines/mm/mm1/console.cpp
+++ b/engines/mm/mm1/console.cpp
@@ -22,15 +22,371 @@
 #include "common/file.h"
 #include "common/savefile.h"
 #include "common/system.h"
+#include "common/util.h"
 #include "mm/shared/utils/strings.h"
 #include "mm/mm1/console.h"
 #include "mm/mm1/globals.h"
 #include "mm/mm1/events.h"
 #include "mm/mm1/game/spells_party.h"
+#include "mm/mm1/messages.h"
+#include "mm/mm1/sound.h"
 
 namespace MM {
 namespace MM1 {
 
+namespace {
+
+const char *const SOUND_TESTS[] = {
+	"wall",
+	"message",
+	"triple",
+	"encounter",
+	"combat",
+	"search",
+	"cast",
+	"death"
+};
+
+const SoundId SOUND_TEST_SOUND_IDS[] = {
+	SOUND_1, SOUND_2, SOUND_3
+};
+
+const SoundId SOUND_TEST_SOUND2_IDS[] = {
+	SOUND_TITLE, SOUND_1, SOUND_2, SOUND_3, SOUND_4, SOUND_5, SOUND_8, SOUND_9
+};
+
+class SoundTestRunner;
+SoundTestRunner *g_soundTestRunner = nullptr;
+
+void faceDirection(Maps::DirMask dir) {
+	Maps::Maps &maps = g_globals->_maps;
+
+	for (int i = 0; i < 4 && maps._forwardMask != dir; ++i)
+		maps.turnLeft();
+}
+
+bool stageWallBump() {
+	Maps::Maps &maps = g_globals->_maps;
+	maps.loadTown(Maps::SORPIGAL);
+	g_globals->_intangible = false;
+
+	static const Maps::DirMask DIRS[] = {
+		Maps::DIRMASK_N, Maps::DIRMASK_E, Maps::DIRMASK_S, Maps::DIRMASK_W
+	};
+
+	for (int y = 0; y < MAP_H; ++y) {
+		for (int x = 0; x < MAP_W; ++x) {
+			const uint offset = y * MAP_W + x;
+			if (maps._currentMap->_states[offset] & Maps::CELL_SPECIAL)
+				continue;
+
+			for (uint i = 0; i < ARRAYSIZE(DIRS); ++i) {
+				const Maps::DirMask dir = DIRS[i];
+				if (!(maps._currentMap->_walls[offset] & dir) ||
+						!(maps._currentMap->_states[offset] & 0x55 & dir))
+					continue;
+
+				maps._mapPos = Common::Point(x, y);
+				faceDirection(dir);
+				maps._mapOffset = offset;
+				maps._currentWalls = maps._currentMap->_walls[offset];
+				maps._currentState = maps._currentMap->_states[offset];
+				g_events->send("View", ActionMessage(KEYBIND_FORWARDS));
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+Character *findSpellTester() {
+	for (uint i = 0; i < g_globals->_party.size(); ++i) {
+		Character &c = g_globals->_party[i];
+		if (c._class == CLERIC || c._class == SORCERER || c._class == ARCHER)
+			return &c;
+	}
+
+	return g_globals->_party.empty() ? nullptr : &g_globals->_party[0];
+}
+
+void stageSpellDone() {
+	Character *c = findSpellTester();
+	if (!c)
+		return;
+
+	g_globals->_currCharacter = c;
+	c->_class = SORCERER;
+	c->_spellLevel = 7;
+	c->_sp = 100;
+	c->_gems = 100;
+
+	int spellIndex = Console::getSpellIndex(c, 1, 5);
+	g_events->send("CastSpell", GameMessage("SPELL", spellIndex));
+}
+
+void stageSearchTreasure() {
+	g_globals->_treasure.clear();
+	g_globals->_treasure._container = CLOTH_SACK;
+	g_globals->_treasure._trapType = 2;
+	g_globals->_treasure.setGold(6);
+	g_globals->_treasure.setGems(0);
+	g_events->send("Search", GameMessage("SHOW"));
+}
+
+void stageEncounter() {
+	if (g_globals->_party.empty()) {
+		warning("MM1 sound test: encounter requires an active party");
+		return;
+	}
+
+	Game::Encounter &enc = g_globals->_encounters;
+	const bool encountersOn = g_globals->_encountersOn;
+	g_globals->_encountersOn = true;
+
+	enc.clearMonsters();
+	enc.addMonster(1, 1);
+	enc._manual = true;
+	enc._levelIndex = 80;
+	enc._encounterType = Game::FORCE_SURPRISED;
+	enc.execute();
+
+	g_globals->_encountersOn = encountersOn;
+}
+
+void stageTripleBeepSpecial() {
+	g_events->send("GameMessages", InfoMessage(
+		0, 1, STRING["maps.map13.snake_pit"],
+		0, 2, STRING["maps.map13.levitation2"]
+	));
+	Sound::sound(SOUND_3);
+}
+
+void stageCombatKill() {
+	Character *c = g_globals->_party.empty() ? nullptr : &g_globals->_party[0];
+	if (!c)
+		return;
+
+	c->_class = KNIGHT;
+	c->_condition = FINE;
+	c->_level = 50;
+	c->_hp = c->_hpCurrent = c->_hpMax = 200;
+	c->_might = 50;
+	c->_accuracy = 50;
+	c->_speed = 50;
+	c->_physicalAttr = 50;
+	g_globals->_currCharacter = c;
+
+	stageEncounter();
+	g_events->send("Combat", GameMessage("COMBAT"));
+}
+
+bool runSoundTestCase(const Common::String &testName) {
+	if (!scumm_stricmp(testName.c_str(), "wall")) {
+		return stageWallBump();
+
+	} else if (!scumm_stricmp(testName.c_str(), "message")) {
+		g_events->send("GameMessages", SoundMessage(STRING["dialogs.search.nothing"]));
+		return true;
+
+	} else if (!scumm_stricmp(testName.c_str(), "triple")) {
+		stageTripleBeepSpecial();
+		return true;
+
+	} else if (!scumm_stricmp(testName.c_str(), "encounter")) {
+		stageEncounter();
+		return true;
+
+	} else if (!scumm_stricmp(testName.c_str(), "combat")) {
+		stageCombatKill();
+		return true;
+
+	} else if (!scumm_stricmp(testName.c_str(), "search")) {
+		stageSearchTreasure();
+		return true;
+
+	} else if (!scumm_stricmp(testName.c_str(), "death") ||
+			!scumm_stricmp(testName.c_str(), "dead")) {
+		g_events->addView("Dead");
+		return true;
+
+	} else if (!scumm_stricmp(testName.c_str(), "cast")) {
+		stageSpellDone();
+		return true;
+	}
+
+	return false;
+}
+
+bool parseSoundId(const char *str, SoundId &soundId) {
+	char *end = nullptr;
+	const long value = strtol(str, &end, 10);
+
+	if (!str[0] || *end)
+		return false;
+
+	switch (value) {
+	case SOUND_TITLE:
+	case SOUND_1:
+	case SOUND_2:
+	case SOUND_3:
+	case SOUND_4:
+	case SOUND_5:
+	case SOUND_8:
+	case SOUND_9:
+		soundId = (SoundId)value;
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool isSoundIdSupported(const SoundId *ids, uint count, SoundId soundId) {
+	for (uint i = 0; i < count; ++i) {
+		if (ids[i] == soundId)
+			return true;
+	}
+
+	return false;
+}
+
+bool runDirectSoundTest(const Common::String &routine, SoundId soundId) {
+	if (!scumm_stricmp(routine.c_str(), "sound")) {
+		if (!isSoundIdSupported(SOUND_TEST_SOUND_IDS, ARRAYSIZE(SOUND_TEST_SOUND_IDS), soundId))
+			return false;
+
+		Sound::sound(soundId);
+		return true;
+	}
+
+	if (!scumm_stricmp(routine.c_str(), "sound2")) {
+		if (!isSoundIdSupported(SOUND_TEST_SOUND2_IDS, ARRAYSIZE(SOUND_TEST_SOUND2_IDS), soundId))
+			return false;
+
+		Sound::sound2(soundId);
+		return true;
+	}
+
+	return false;
+}
+
+bool runDirectSoundTestAlias(const Common::String &testName) {
+	const char *name = testName.c_str();
+	SoundId soundId;
+
+	if (!strncmp(name, "sound2-", 7) && parseSoundId(name + 7, soundId))
+		return runDirectSoundTest("sound2", soundId);
+
+	if (!strncmp(name, "sound-", 6) && parseSoundId(name + 6, soundId))
+		return runDirectSoundTest("sound", soundId);
+
+	return false;
+}
+
+void soundTestPromptAccepted();
+
+class SoundTestRunner : public UIElement {
+private:
+	enum State {
+		ST_INACTIVE,
+		ST_PROMPT,
+		ST_RUN,
+		ST_WAIT
+	};
+
+	State _state = ST_INACTIVE;
+	uint _testIndex = 0;
+	uint32 _waitUntil = 0;
+	bool _promptOpen = false;
+	bool _promptAccepted = false;
+
+	void nextTest() {
+		++_testIndex;
+		_promptOpen = false;
+		_promptAccepted = false;
+
+		if (_testIndex >= ARRAYSIZE(SOUND_TESTS)) {
+			_state = ST_INACTIVE;
+			close();
+		} else {
+			_state = ST_PROMPT;
+		}
+	}
+
+	uint32 waitMillisForTest(const Common::String &testName) const {
+		if (!scumm_stricmp(testName.c_str(), "encounter"))
+			return 5000;
+		if (!scumm_stricmp(testName.c_str(), "combat"))
+			return 8000;
+		if (!scumm_stricmp(testName.c_str(), "cast"))
+			return 3000;
+		if (!scumm_stricmp(testName.c_str(), "search"))
+			return 3000;
+		return 2000;
+	}
+
+public:
+	SoundTestRunner(UIElement *owner) : UIElement("SoundTestRunner", owner) {}
+
+	void start() {
+		_state = ST_PROMPT;
+		_testIndex = 0;
+		_promptOpen = false;
+		_promptAccepted = false;
+		_waitUntil = 0;
+	}
+
+	void promptAccepted() {
+		_promptAccepted = true;
+	}
+
+	bool tick() override {
+		if (_state == ST_INACTIVE)
+			return false;
+
+		const Common::String testName = SOUND_TESTS[_testIndex];
+
+		switch (_state) {
+		case ST_PROMPT:
+			if (!_promptOpen) {
+				_promptOpen = true;
+				g_events->send("GameMessages", InfoMessage(
+					0, 3,
+					Common::String::format("proceed with %s test", testName.c_str()),
+					soundTestPromptAccepted));
+				return true;
+			}
+
+			if (_promptAccepted)
+				_state = ST_RUN;
+			return true;
+
+		case ST_RUN:
+			runSoundTestCase(testName);
+			_waitUntil = g_system->getMillis() + waitMillisForTest(testName);
+			_state = ST_WAIT;
+			return true;
+
+		case ST_WAIT:
+			if (g_system->getMillis() >= _waitUntil)
+				nextTest();
+			return true;
+
+		default:
+			break;
+		}
+
+		return false;
+	}
+};
+
+void soundTestPromptAccepted() {
+	if (g_soundTestRunner)
+		g_soundTestRunner->promptAccepted();
+}
+
+} // namespace
+
 Console::Console() : GUI::Debugger() {
 	registerCmd("dump_map", WRAP_METHOD(Console, cmdDumpMap));
 	registerCmd("dump_monsters", WRAP_METHOD(Console, cmdDumpMonsters));
@@ -47,6 +403,28 @@ Console::Console() : GUI::Debugger() {
 	registerCmd("specials", WRAP_METHOD(Console, cmdSpecials));
 	registerCmd("special", WRAP_METHOD(Console, cmdSpecial));
 	registerCmd("view", WRAP_METHOD(Console, cmdView));
+	registerCmd("sound", WRAP_METHOD(Console, cmdSoundTest));
+}
+
+Console::~Console() {
+	if (g_soundTestRunner == _soundTestRunner)
+		g_soundTestRunner = nullptr;
+	delete _soundTestRunner;
+}
+
+void Console::postEnter() {
+	GUI::Debugger::postEnter();
+
+	if (_pendingSoundTest) {
+		_pendingSoundTest = false;
+		if (!_soundTestRunner) {
+			UIElement *game = g_events->findView("Game");
+			assert(game);
+			_soundTestRunner = new SoundTestRunner(game);
+			g_soundTestRunner = static_cast<SoundTestRunner *>(_soundTestRunner);
+		}
+		static_cast<SoundTestRunner *>(_soundTestRunner)->start();
+	}
 }
 
 bool Console::cmdDumpMap(int argc, const char **argv) {
@@ -489,5 +867,49 @@ bool Console::cmdView(int argc, const char **argv) {
 	}
 }
 
+bool Console::cmdSoundTest(int argc, const char **argv) {
+	Common::String testName;
+	SoundId soundId;
+
+	if (argc > 1 && !scumm_stricmp(argv[1], "list")) {
+		debugPrintf("sound [test|list|wall|message|triple|encounter|combat|search|cast|death]\n");
+		debugPrintf("sound sound <1|2|3>\n");
+		debugPrintf("sound sound2 <0|1|2|3|4|5|8|9>\n");
+		debugPrintf("  sound <name> stages one gameplay event for classic sound testing.\n");
+		debugPrintf("  sound test runs the staged checks interactively.\n");
+		debugPrintf("  sound sound <id> calls the original sound(id) routine directly.\n");
+		debugPrintf("  sound sound2 <id> calls the original sound2(id) routine directly.\n");
+		for (uint i = 0; i < ARRAYSIZE(SOUND_TESTS); ++i)
+			debugPrintf("  %u: %s\n", i + 1, SOUND_TESTS[i]);
+		for (uint i = 0; i < ARRAYSIZE(SOUND_TEST_SOUND_IDS); ++i)
+			debugPrintf("  sound-%d\n", SOUND_TEST_SOUND_IDS[i]);
+		for (uint i = 0; i < ARRAYSIZE(SOUND_TEST_SOUND2_IDS); ++i)
+			debugPrintf("  sound2-%d\n", SOUND_TEST_SOUND2_IDS[i]);
+		return true;
+	}
+
+	if (argc == 1 || !scumm_stricmp(argv[1], "test")) {
+		_pendingSoundTest = true;
+		return false;
+	}
+
+	if (argc == 3 && parseSoundId(argv[2], soundId)) {
+		if (runDirectSoundTest(argv[1], soundId))
+			return false;
+
+		debugPrintf("Sound routine '%s' does not support id %d\n", argv[1], soundId);
+		debugPrintf("Use 'sound list' to show known tests.\n");
+		return true;
+	}
+
+	testName = argv[1];
+	if (runDirectSoundTestAlias(testName) || runSoundTestCase(testName))
+		return false;
+
+	debugPrintf("Unknown sound test '%s'\n", argv[1]);
+	debugPrintf("Use 'sound list' to show known tests.\n");
+	return true;
+}
+
 } // namespace MM1
 } // namespace MM
diff --git a/engines/mm/mm1/console.h b/engines/mm/mm1/console.h
index 5c180eaff44..05d9a7b7375 100644
--- a/engines/mm/mm1/console.h
+++ b/engines/mm/mm1/console.h
@@ -28,8 +28,16 @@
 namespace MM {
 namespace MM1 {
 
+class UIElement;
+
 class Console : public GUI::Debugger, public MM1::Game::SpellCasting {
+private:
+	bool _pendingSoundTest = false;
+	UIElement *_soundTestRunner = nullptr;
+
 protected:
+	void postEnter() override;
+
 	/**
 	 * Used to dump a map's code and data
 	 */
@@ -105,9 +113,14 @@ protected:
 	 */
 	bool cmdDumpRoster(int argc, const char **argv);
 
+	/**
+	 * Stages gameplay events used to test classic sounds
+	 */
+	bool cmdSoundTest(int argc, const char **argv);
+
 public:
 	Console();
-	~Console() override {}
+	~Console() override;
 };
 
 } // namespace MM1




More information about the Scummvm-git-logs mailing list