[Scummvm-git-logs] scummvm master -> 3bb244c6ca91c316376fbb495647b311087fb0b2

neuromancer noreply at scummvm.org
Tue Jun 23 14:17:26 UTC 2026


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

Summary:
1a204a9e56 FREESCAPE: Add base class for sound implementations
307c8b6e12 FREESCAPE: Move CPC sound code into a separate class
a1c7622fdd FREESCAPE: Move digital sound code into separate classes
f1cc3ba98e FREESCAPE: Move PC Speaker sound code into separate classes
a3b5da4831 FREESCAPE: Use an enum to specify the sound type
c25163dc83 FREESCAPE: Move Amiga sound code into a separate class
3bb244c6ca FREESCAPE: Change the C64 sound classes to inherit from the Sound class


Commit: 1a204a9e56189eb84b7d28911de2afa775d1848b
    https://github.com/scummvm/scummvm/commit/1a204a9e56189eb84b7d28911de2afa775d1848b
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Add base class for sound implementations

Changed paths:
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/sound.h
    engines/freescape/sound/common.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index 100e95c826f..e0d89adf5d8 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -122,7 +122,7 @@ byte getCPCPixel(byte cpc_byte, int index, bool mode1) {
 }
 
 FreescapeEngine::FreescapeEngine(OSystem *syst, const ADGameDescription *gd)
-	: Engine(syst), _gameDescription(gd), _gfx(nullptr) {
+	: Engine(syst), _gameDescription(gd), _gfx(nullptr), _sound(nullptr) {
 	if (!ConfMan.hasKey("render_mode") || ConfMan.get("render_mode").empty())
 		_renderMode = Common::kRenderEGA;
 	else
@@ -381,6 +381,7 @@ FreescapeEngine::~FreescapeEngine() {
 
 	removeTimers();
 	delete _rnd;
+	delete _sound;
 
 	if (_title && _title != _border) {
 		_title->free();
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index f17fe4fad25..22a6e8358bd 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -514,6 +514,7 @@ public:
 	Audio::SoundHandle _musicHandle;
 	Audio::SoundHandle _movementSoundHandle;
 	Freescape::SizedPCSpeaker *_speaker;
+	Sound *_sound;
 
 	bool _syncSound;
 	bool _firstSound;
@@ -524,8 +525,6 @@ public:
 	void playSound(int index, bool sync, Audio::SoundHandle &handle);
 	void playWav(const Common::Path &filename);
 	void playMusic(const Common::Path &filename);
-	void queueSoundConst(double hzFreq, int duration);
-	void playSilence(int duration, bool sync);
 	void playSoundConst(double hzFreq, int duration, bool sync);
 	void playSoundSweepIncWL(double hzFreq1, double hzFreq2, double wlStepPerMS, int resolution, bool sync);
 	uint16 playSoundDOSSpeaker(uint16 startFrequency, soundSpeakerFx *speakerFxInfo);
diff --git a/engines/freescape/sound.h b/engines/freescape/sound.h
index ab420cc8c4b..37c11748854 100644
--- a/engines/freescape/sound.h
+++ b/engines/freescape/sound.h
@@ -67,6 +67,15 @@ public:
 	bool endOfStream() const override { return !isPlaying(); }
 };
 
+class Sound {
+public:
+	virtual ~Sound() {}
+
+	virtual void playSound(int index) = 0;
+	virtual void stopSound() = 0;
+	virtual bool isPlayingSound() const = 0;
+};
+
 } // End of namespace Freescape
 
 #endif // FREESCAPE_SOUND_H
diff --git a/engines/freescape/sound/common.cpp b/engines/freescape/sound/common.cpp
index 5d69e261363..633196d9720 100644
--- a/engines/freescape/sound/common.cpp
+++ b/engines/freescape/sound/common.cpp
@@ -40,6 +40,11 @@ void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle
 	_syncSound = sync;
 
 	debugC(1, kFreescapeDebugMedia, "Playing sound %d with sync: %d", index, sync);
+	if (_sound) {
+		_sound->playSound(index);
+		return;
+	}
+
 	if (isC64()) {
 		playSoundC64(index);
 		return;
@@ -128,36 +133,27 @@ void FreescapeEngine::playSoundFx(int index, bool sync) {
 
 void FreescapeEngine::stopAllSounds(Audio::SoundHandle &handle) {
 	debugC(1, kFreescapeDebugMedia, "Stopping sound");
-	_mixer->stopHandle(handle);
+	if (_sound)
+		_sound->stopSound();
+	else
+		_mixer->stopHandle(handle);
 }
 
 void FreescapeEngine::waitForSounds() {
-	if (_usePrerecordedSounds || isAmiga() || isAtariST() || isCPC())
-		while (_mixer->isSoundHandleActive(_soundFxHandle))
-			waitInLoop(10);
-	else {
-		while (!_speaker->endOfStream())
-			waitInLoop(10);
-	}
+	while (isPlayingSound())
+		waitInLoop(10);
 }
 
 bool FreescapeEngine::isPlayingSound() {
+	if (_sound)
+		return _sound->isPlayingSound();
+
 	if (_usePrerecordedSounds || isAmiga() || isAtariST() || isCPC())
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 
 	return (!_speaker->endOfStream());
 }
 
-void FreescapeEngine::playSilence(int duration, bool sync) {
-	_speaker->playQueue(Audio::PCSpeaker::kWaveFormSilence, 0, 1000 * 10 * duration);
-	_mixer->stopHandle(_soundFxHandle);
-	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
-}
-
-void FreescapeEngine::queueSoundConst(double hzFreq, int duration) {
-	_speaker->playQueue(Audio::PCSpeaker::kWaveFormSquare, hzFreq, 1000 * 10 * duration);
-}
-
 void FreescapeEngine::loadSoundsFx(Common::SeekableReadStream *file, int offset, int number) {
 	file->seek(offset);
 	soundFx *sound = nullptr;


Commit: 307c8b6e1220c9913042fff76f02a51ec905013d
    https://github.com/scummvm/scummvm/commit/307c8b6e1220c9913042fff76f02a51ec905013d
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Move CPC sound code into a separate class

Changed paths:
    engines/freescape/freescape.h
    engines/freescape/games/castle/cpc.cpp
    engines/freescape/games/dark/cpc.cpp
    engines/freescape/games/driller/cpc.cpp
    engines/freescape/games/eclipse/cpc.cpp
    engines/freescape/sound/common.cpp
    engines/freescape/sound/cpc.cpp


diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 22a6e8358bd..f07dc961e21 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -530,7 +530,6 @@ public:
 	uint16 playSoundDOSSpeaker(uint16 startFrequency, soundSpeakerFx *speakerFxInfo);
 	void playSoundDOS(soundSpeakerFx *speakerFxInfo, bool sync, Audio::SoundHandle &handle);
 
-	void playSoundCPC(int index, Audio::SoundHandle &handle);
 	virtual void playSoundC64(int index);
 	virtual void playSoundFx(int index, bool sync);
 	virtual void loadSoundsFx(Common::SeekableReadStream *file, int offset, int number);
@@ -543,10 +542,7 @@ public:
 	void playSoundZX(Common::Array<soundUnitZX> *data, Audio::SoundHandle &handle);
 	Common::HashMap<uint16, Common::Array<soundUnitZX>*> _soundsSpeakerFxZX;
 
-	void loadSoundsCPC(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef);
-	Common::Array<byte> _soundsCPCToneTable;
-	Common::Array<byte> _soundsCPCEnvelopeTable;
-	Common::Array<byte> _soundsCPCSoundDefTable;
+	Sound *loadSoundsCPC(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef);
 
 	void loadSoundsAmigaDemo(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset = 0x3D5A6);
 	void playSoundAmiga(int index, Audio::SoundHandle &handle);
diff --git a/engines/freescape/games/castle/cpc.cpp b/engines/freescape/games/castle/cpc.cpp
index 5a2896b7de0..769c5e896cf 100644
--- a/engines/freescape/games/castle/cpc.cpp
+++ b/engines/freescape/games/castle/cpc.cpp
@@ -238,7 +238,7 @@ void CastleEngine::loadAssetsCPCFullGame() {
 	loadMessagesVariableSize(&file, messagesOffset, 75);
 	loadRiddles(&file, riddlesOffset, 9);
 	load8bitBinary(&file, 0x791a, 16);
-	loadSoundsCPC(&file, 0x21E2, 48, 0x2212, 204, 0x2179, 105);
+	_sound = loadSoundsCPC(&file, 0x21E2, 48, 0x2212, 204, 0x2179, 105);
 
 	file.seek(0x2724);
 	for (int i = 0; i < 90; i++) {
diff --git a/engines/freescape/games/dark/cpc.cpp b/engines/freescape/games/dark/cpc.cpp
index 9f67d7e1701..1e6cf14c7cb 100644
--- a/engines/freescape/games/dark/cpc.cpp
+++ b/engines/freescape/games/dark/cpc.cpp
@@ -143,7 +143,7 @@ void DarkEngine::loadAssetsCPCFullGame() {
 	loadFonts(&file, 0x60f3);
 	loadGlobalObjects(&file, 0x9a, 23);
 	load8bitBinary(&file, 0x6255, 16);
-	loadSoundsCPC(&file, 0x09B7, 160, 0x0A57, 284, 0x0B73, 203);
+	_sound = loadSoundsCPC(&file, 0x09B7, 160, 0x0A57, 284, 0x0B73, 203);
 	loadCPCIndicators(&file);
 }
 
diff --git a/engines/freescape/games/driller/cpc.cpp b/engines/freescape/games/driller/cpc.cpp
index 72ec651dc48..ba1f861c8c3 100644
--- a/engines/freescape/games/driller/cpc.cpp
+++ b/engines/freescape/games/driller/cpc.cpp
@@ -139,27 +139,13 @@ void DrillerEngine::loadAssetsCPCFullGame() {
 	if (!file.isOpen())
 		error("Failed to open DRILL.BIN");
 
-	loadSoundsCPC(&file, 0x23D2 + 0x80, 68, 0x2416 + 0x80, 104, 0x247E + 0x80, 140);
+	_sound = loadSoundsCPC(&file, 0x23D2 + 0x80, 68, 0x2416 + 0x80, 104, 0x247E + 0x80, 140);
 	loadMessagesFixedSize(&file, 0x214c, 14, 20);
 	loadFonts(&file, 0x5b69);
 	loadGlobalObjects(&file, 0x1d07, 8);
 	load8bitBinary(&file, 0x5ccb, 16);
 }
 
-void FreescapeEngine::loadSoundsCPC(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef) {
-	_soundsCPCToneTable.resize(sizeTone);
-	file->seek(offsetTone);
-	file->read(_soundsCPCToneTable.data(), sizeTone);
-
-	_soundsCPCEnvelopeTable.resize(sizeEnvelope);
-	file->seek(offsetEnvelope);
-	file->read(_soundsCPCEnvelopeTable.data(), sizeEnvelope);
-
-	_soundsCPCSoundDefTable.resize(sizeSoundDef);
-	file->seek(offsetSoundDef);
-	file->read(_soundsCPCSoundDefTable.data(), sizeSoundDef);
-}
-
 void DrillerEngine::drawCPCUI(Graphics::Surface *surface) {
 	uint32 color = _currentArea->_underFireBackgroundColor;
 	uint8 r, g, b;
diff --git a/engines/freescape/games/eclipse/cpc.cpp b/engines/freescape/games/eclipse/cpc.cpp
index 43480d310b2..6bb24396765 100644
--- a/engines/freescape/games/eclipse/cpc.cpp
+++ b/engines/freescape/games/eclipse/cpc.cpp
@@ -122,12 +122,12 @@ void EclipseEngine::loadAssetsCPCFullGame() {
 		loadFonts(&file, 0x60bc);
 		loadMessagesFixedSize(&file, 0x326, 16, 34);
 		load8bitBinary(&file, 0x62b4, 16);
-		loadSoundsCPC(&file, 0x0879, 104, 0x08E1, 165, 0x07E6, 147);
+		_sound = loadSoundsCPC(&file, 0x0879, 104, 0x08E1, 165, 0x07E6, 147);
 	} else {
 		loadFonts(&file, 0x6076);
 		loadMessagesFixedSize(&file, 0x326, 16, 30);
 		load8bitBinary(&file, 0x626e, 16);
-		loadSoundsCPC(&file, 0x07C9, 104, 0x0831, 165, 0x0736, 147);
+		_sound = loadSoundsCPC(&file, 0x07C9, 104, 0x0831, 165, 0x0736, 147);
 	}
 
 	loadColorPalette();
@@ -169,7 +169,7 @@ void EclipseEngine::loadAssetsCPCDemo() {
 	loadMessagesFixedSize(&file, 0x362, 16, 23);
 	loadMessagesFixedSize(&file, 0x570b, 264, 5);
 	load8bitBinary(&file, 0x65c6, 16);
-	loadSoundsCPC(&file, 0x0805, 104, 0x086D, 165, 0x0772, 147);
+	_sound = loadSoundsCPC(&file, 0x0805, 104, 0x086D, 165, 0x0772, 147);
 	loadColorPalette();
 	swapPalette(1);
 	loadHeartFramesCPC(&file, 0x0D17, 0x0D49);
diff --git a/engines/freescape/sound/common.cpp b/engines/freescape/sound/common.cpp
index 633196d9720..c1fd167858f 100644
--- a/engines/freescape/sound/common.cpp
+++ b/engines/freescape/sound/common.cpp
@@ -66,9 +66,6 @@ void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle
 	} else if (isSpectrum()) {
 		playSoundZX(index, handle);
 		return;
-	} else if (isCPC()) {
-		playSoundCPC(index, handle);
-		return;
 	}
 
 	Common::Path filename;
@@ -148,7 +145,7 @@ bool FreescapeEngine::isPlayingSound() {
 	if (_sound)
 		return _sound->isPlayingSound();
 
-	if (_usePrerecordedSounds || isAmiga() || isAtariST() || isCPC())
+	if (_usePrerecordedSounds || isAmiga() || isAtariST())
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 
 	return (!_speaker->endOfStream());
diff --git a/engines/freescape/sound/cpc.cpp b/engines/freescape/sound/cpc.cpp
index 36ca4e327bc..efc412f5a23 100644
--- a/engines/freescape/sound/cpc.cpp
+++ b/engines/freescape/sound/cpc.cpp
@@ -71,13 +71,11 @@ namespace Freescape {
  *     Byte 2: limit - ticks between each application
  */
 
-class CPCSfxStream : public Audio::AY8912Stream {
+// TODO: Remove direct inheritance from the mixer stream
+class CPCSound final : public Sound, private Audio::AY8912Stream {
 public:
-	CPCSfxStream(int index, const byte *soundDefTable, int soundDefTableSize,
-	             const byte *toneTable, const byte *envelopeTable, int rate = 44100)
-		: AY8912Stream(rate, 1000000),
-		  _soundDefTable(soundDefTable), _soundDefTableSize(soundDefTableSize),
-		  _toneTable(toneTable), _envelopeTable(envelopeTable) {
+	CPCSound(Audio::Mixer *mixer, int rate = 44100)
+		: AY8912Stream(rate, 1000000), _mixer(mixer) {
 		_finished = false;
 		_tickSampleCount = 0;
 
@@ -88,7 +86,38 @@ public:
 		setReg(6, 0x07);
 
 		memset(&_ch, 0, sizeof(_ch));
+	}
+
+	void loadSounds(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef) {
+		_toneTable.resize(sizeTone);
+		file->seek(offsetTone);
+		file->read(_toneTable.data(), sizeTone);
+
+		_envelopeTable.resize(sizeEnvelope);
+		file->seek(offsetEnvelope);
+		file->read(_envelopeTable.data(), sizeEnvelope);
+
+		_soundDefTable.resize(sizeSoundDef);
+		file->seek(offsetSoundDef);
+		file->read(_soundDefTable.data(), sizeSoundDef);
+	}
+
+	void playSound(int index) override {
+		if (_soundDefTable.empty()) {
+			debugC(1, kFreescapeDebugMedia, "CPC sound tables not loaded");
+			return;
+		}
+		debugC(1, kFreescapeDebugMedia, "Playing CPC sound %d", index);
 		setupSound(index);
+		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, toAudioStream(), -1, kFreescapeDefaultVolume, 0, DisposeAfterUse::NO);
+	}
+
+	void stopSound() override {
+		_mixer->stopHandle(_soundFxHandle);
+	}
+
+	bool isPlayingSound() const override {
+		return _mixer->isSoundHandleActive(_soundFxHandle);
 	}
 
 	int readBuffer(int16 *buffer, const int numSamples) override {
@@ -126,14 +155,16 @@ public:
 	bool endOfStream() const override { return _finished; }
 
 private:
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundFxHandle;
+
 	bool _finished;
 	int _tickSampleCount; // Samples generated in current tick
 
-	// Pointers to table data loaded from game binary (owned by FreescapeEngine)
-	const byte *_soundDefTable;
-	int _soundDefTableSize;      // Size in bytes (numSounds * 7)
-	const byte *_toneTable;      // Volume envelope data
-	const byte *_envelopeTable;  // Pitch sweep data
+	// Table data loaded from game binary
+	Common::Array<byte> _soundDefTable;
+	Common::Array<byte> _toneTable;      // Volume envelope data
+	Common::Array<byte> _envelopeTable;  // Pitch sweep data
 
 	/**
 	 * Channel state - mirrors the 23-byte per-channel structure
@@ -181,8 +212,9 @@ private:
 	}
 
 	void setupSound(int index) {
-		int maxSounds = _soundDefTableSize / 7;
+		int maxSounds = _soundDefTable.size() / 7;
 		if (index >= 1 && index <= maxSounds) {
+			_finished = false;
 			setupSub4760h(index);
 		} else {
 			_finished = true;
@@ -193,7 +225,7 @@ private:
 	 * Sound initialization - loads 7-byte entry and configures AY registers.
 	 */
 	void setupSub4760h(int soundNum) {
-		int maxSounds = _soundDefTableSize / 7;
+		int maxSounds = _soundDefTable.size() / 7;
 		if (soundNum < 1 || soundNum > maxSounds) {
 			_finished = true;
 			return;
@@ -292,8 +324,8 @@ private:
 			return;
 		}
 
-		const byte *toneRaw = _toneTable;
-		const byte *envRaw = _envelopeTable;
+		const byte *toneRaw = _toneTable.data();
+		const byte *envRaw = _envelopeTable.data();
 
 		// === PITCH UPDATE ===
 		_ch.pitchLimitCur--;
@@ -392,16 +424,10 @@ private:
 	}
 };
 
-void FreescapeEngine::playSoundCPC(int index, Audio::SoundHandle &handle) {
-	if (_soundsCPCSoundDefTable.empty()) {
-		debugC(1, kFreescapeDebugMedia, "CPC sound tables not loaded");
-		return;
-	}
-	debugC(1, kFreescapeDebugMedia, "Playing CPC sound %d", index);
-	CPCSfxStream *stream = new CPCSfxStream(index,
-		_soundsCPCSoundDefTable.data(), _soundsCPCSoundDefTable.size(),
-		_soundsCPCToneTable.data(), _soundsCPCEnvelopeTable.data());
-	_mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, stream->toAudioStream(), -1, kFreescapeDefaultVolume, 0, DisposeAfterUse::YES);
+Sound *FreescapeEngine::loadSoundsCPC(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef) {
+	CPCSound *sound = new CPCSound(_mixer);
+	sound->loadSounds(file, offsetTone, sizeTone, offsetEnvelope, sizeEnvelope, offsetSoundDef, sizeSoundDef);
+	return sound;
 }
 
 } // namespace Freescape


Commit: a1c7622fdd166a7c65633685d911aa2c1a99df66
    https://github.com/scummvm/scummvm/commit/a1c7622fdd166a7c65633685d911aa2c1a99df66
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Move digital sound code into separate classes

Changed paths:
  A engines/freescape/sound/fx.cpp
  A engines/freescape/sound/fx_dos.cpp
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/games/dark/amiga.cpp
    engines/freescape/games/dark/atari.cpp
    engines/freescape/games/driller/amiga.cpp
    engines/freescape/games/driller/atari.cpp
    engines/freescape/games/eclipse/amiga.cpp
    engines/freescape/games/eclipse/atari.cpp
    engines/freescape/games/eclipse/dos.cpp
    engines/freescape/games/eclipse/eclipse.cpp
    engines/freescape/games/eclipse/eclipse.h
    engines/freescape/module.mk
    engines/freescape/sound.h
    engines/freescape/sound/common.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index e0d89adf5d8..eab00dfcc1d 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -418,13 +418,6 @@ FreescapeEngine::~FreescapeEngine() {
 		delete it;
 	}
 
-	for (auto &it : _soundsFx) {
-		if (it._value) {
-			free(it._value->data);
-			free(it._value);
-		}
-	}
-
 	if (_savedScreen) {
 		_savedScreen->free();
 		delete _savedScreen;
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index f07dc961e21..6396d012e0c 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -531,9 +531,9 @@ public:
 	void playSoundDOS(soundSpeakerFx *speakerFxInfo, bool sync, Audio::SoundHandle &handle);
 
 	virtual void playSoundC64(int index);
-	virtual void playSoundFx(int index, bool sync);
-	virtual void loadSoundsFx(Common::SeekableReadStream *file, int offset, int number);
-	Common::HashMap<uint16, soundFx *> _soundsFx;
+	virtual void playSoundFx(int index, bool sync) {}
+	Sound *loadSoundsFx(Common::SeekableReadStream *file, int offset, int number);
+	Sound *loadSoundsFxDOS(Common::SeekableReadStream *file, int offset, int number);
 	void loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetDuration, int numberSounds);
 	void loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData);
 	Common::HashMap<uint16, soundSpeakerFx *> _soundsSpeakerFx;
diff --git a/engines/freescape/games/dark/amiga.cpp b/engines/freescape/games/dark/amiga.cpp
index 4a2728682c8..44f7c266e54 100644
--- a/engines/freescape/games/dark/amiga.cpp
+++ b/engines/freescape/games/dark/amiga.cpp
@@ -170,7 +170,7 @@ void DarkEngine::loadAssetsAmigaFullGame() {
 	loadPalettes(stream, 0x2e528);
 	loadGlobalObjects(stream, 0x30f0 - 50, 24);
 	loadMessagesVariableSize(stream, 0x3d37, 66);
-	loadSoundsFx(stream, 0x34738 + 2, 11);
+	_sound = loadSoundsFx(stream, 0x34738 + 2, 11);
 
 	// Load HDSMUSIC.AM music data (Wally Beben custom engine)
 	// HDSMUSIC.AM is an embedded GEMDOS executable at stream offset $BA64
diff --git a/engines/freescape/games/dark/atari.cpp b/engines/freescape/games/dark/atari.cpp
index 8b25e6c266e..f2f00c2d1b2 100644
--- a/engines/freescape/games/dark/atari.cpp
+++ b/engines/freescape/games/dark/atari.cpp
@@ -69,7 +69,7 @@ void DarkEngine::loadAssetsAtariFullGame() {
 	loadMessagesVariableSize(stream, 0x3f6f, 66);
 	loadPalettes(stream, 0x204d6);
 	loadGlobalObjects(stream, 0x32f6, 24);
-	loadSoundsFx(stream, 0x266e8, 11);
+	_sound = loadSoundsFx(stream, 0x266e8, 11);
 
 	for (auto &area : _areaMap) {
 		// Center and pad each area name so we do not have to do it at each frame
diff --git a/engines/freescape/games/driller/amiga.cpp b/engines/freescape/games/driller/amiga.cpp
index 902ecc97e68..0eb595d7e7c 100644
--- a/engines/freescape/games/driller/amiga.cpp
+++ b/engines/freescape/games/driller/amiga.cpp
@@ -226,7 +226,7 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 		loadGlobalObjects(&file, 0xbd62, 8);
 		load8bitBinary(&file, 0x29c16, 16);
 		loadPalettes(&file, 0x297d4);
-		loadSoundsFx(&file, 0x30e80, 25);
+		_sound = loadSoundsFx(&file, 0x30e80, 25);
 
 		byte *palette = getPaletteFromNeoImage(&file, 0x137f4);
 		loadRigSprites(&file, 0x2407A);
@@ -282,7 +282,7 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 		if (!file.isOpen())
 			error("Failed to open 'soundfx' executable for Amiga");
 
-		loadSoundsFx(&file, 0, 25);
+		_sound = loadSoundsFx(&file, 0, 25);
 	} else
 		error("Invalid or unknown Amiga release");
 
@@ -374,7 +374,7 @@ void DrillerEngine::loadAssetsAmigaDemo() {
 	if (!file.isOpen())
 		error("Failed to open 'soundfx' executable for Amiga");
 
-	loadSoundsFx(&file, 0, 25);
+	_sound = loadSoundsFx(&file, 0, 25);
 
 	_indicators.push_back(loadBundledImage("driller_tank_indicator_0"));
 	_indicators.push_back(loadBundledImage("driller_tank_indicator_1"));
diff --git a/engines/freescape/games/driller/atari.cpp b/engines/freescape/games/driller/atari.cpp
index 6d8e8c9345b..a64a251bd06 100644
--- a/engines/freescape/games/driller/atari.cpp
+++ b/engines/freescape/games/driller/atari.cpp
@@ -80,7 +80,7 @@ void DrillerEngine::loadAssetsAtariFullGame() {
 		loadGlobalObjects(stream, 0xd116, 8);
 		load8bitBinary(stream, 0x2afb8, 16);
 		loadPalettes(stream, 0x2ab76);
-		loadSoundsFx(stream, 0x30da6 + 0x147c, 25);
+		_sound = loadSoundsFx(stream, 0x30da6 + 0x147c, 25);
 	} else if (_variant & GF_ATARI_BUDGET) {
 		Common::File file;
 		file.open("x.prg");
@@ -105,7 +105,7 @@ void DrillerEngine::loadAssetsAtariFullGame() {
 			loadGlobalObjects(&file, 0xbccc - 0x1da, 8);
 			load8bitBinary(&file, 0x29b3c - 0x1d6, 16);
 			loadPalettes(&file, 0x296fa - 0x1d6);
-			loadSoundsFx(&file, 0x30da6 - 0x1d6, 25);
+			_sound = loadSoundsFx(&file, 0x30da6 - 0x1d6, 25);
 		} else {
 			_border = loadAndConvertNeoImage(stream, 0x1371a);
 			_title = loadAndConvertNeoImage(stream, 0x396);
@@ -121,7 +121,7 @@ void DrillerEngine::loadAssetsAtariFullGame() {
 			loadGlobalObjects(stream, 0xbccc, 8);
 			load8bitBinary(stream, 0x29b3c, 16);
 			loadPalettes(stream, 0x296fa);
-			loadSoundsFx(stream, 0x30da6, 25);
+			_sound = loadSoundsFx(stream, 0x30da6, 25);
 
 			// Budget Atari full-game indicator blocks match the Amiga retail data
 			// shifted by -0xDA in the validated 293062-byte x.prg layout.
@@ -231,7 +231,7 @@ void DrillerEngine::loadAssetsAtariDemo() {
 	if (!file.isOpen())
 		error("Failed to open 'soundfx' executable for AtariST demo");
 
-	loadSoundsFx(&file, 0, 25);
+	_sound = loadSoundsFx(&file, 0, 25);
 
 	for (auto &area : _areaMap) {
 		// Center and pad each area name so we do not have to do it at each frame
diff --git a/engines/freescape/games/eclipse/amiga.cpp b/engines/freescape/games/eclipse/amiga.cpp
index f8bb9708554..0602c5b670a 100644
--- a/engines/freescape/games/eclipse/amiga.cpp
+++ b/engines/freescape/games/eclipse/amiga.cpp
@@ -72,7 +72,7 @@ void EclipseEngine::loadAssetsAmigaFullGame() {
 		memcpy(pal, kBorderPalette, 6 * 3);
 	}
 
-	loadSoundsFx(stream, 0x3030c + kAmigaDelta, 6);
+	_sound = loadSoundsFx(stream, 0x3030c + kAmigaDelta, 6);
 
 	// Load TEMUSIC.AM — Wally Beben custom Paula engine (same family as Dark Side)
 	// GEMDOS executable embedded at stream offset 0x10F5E, TEXT size 0xEB20
diff --git a/engines/freescape/games/eclipse/atari.cpp b/engines/freescape/games/eclipse/atari.cpp
index d2d97733d58..1c98539713d 100644
--- a/engines/freescape/games/eclipse/atari.cpp
+++ b/engines/freescape/games/eclipse/atari.cpp
@@ -846,7 +846,7 @@ void EclipseEngine::loadAssetsAtariFullGame() {
 		memcpy(pal, kBorderPalette, 6 * 3);
 	}
 
-	loadSoundsFx(stream, 0x3030c, 6);
+	_sound = loadSoundsFx(stream, 0x3030c, 6);
 
 	// Load TEMUSIC.ST (GEMDOS executable at file offset $11F5A, skip $1C header, TEXT size $11E8)
 	static const uint32 kTEMusicOffset = 0x11F5A;
diff --git a/engines/freescape/games/eclipse/dos.cpp b/engines/freescape/games/eclipse/dos.cpp
index e0dc3707f7e..5b9c5058c71 100644
--- a/engines/freescape/games/eclipse/dos.cpp
+++ b/engines/freescape/games/eclipse/dos.cpp
@@ -121,7 +121,7 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 			error("Failed to open TOTEE.EXE");
 
 		loadMessagesFixedSize(&file, 0x710f, 16, 20);
-		loadSoundsFx(&file, 0xd670, 5);
+		_soundFx = loadSoundsFxDOS(&file, 0xd670, 5);
 		loadSpeakerFxDOS(&file, 0x7396 + 0x200, 0x72a1 + 0x200, 20);
 		loadFonts(&file, 0xd403);
 		load8bitBinary(&file, 0x3ce0, 16);
@@ -149,7 +149,7 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 			error("Failed to open TOTEC.EXE");
 
 		loadMessagesFixedSize(&file, 0x594f, 16, 20);
-		loadSoundsFx(&file, 0xb9f0, 5);
+		_soundFx = loadSoundsFxDOS(&file, 0xb9f0, 5);
 		loadSpeakerFxDOS(&file, 0x5BD6 + 0x200, 0x5AE1 + 0x200, 20);
 		loadFonts(&file, 0xb785);
 		load8bitBinary(&file, 0x2530, 4);
@@ -250,57 +250,11 @@ void EclipseEngine::drawDOSUI(Graphics::Surface *surface) {
 	drawCompass(surface, 229, 177, _yaw, 10, black);
 }
 
-soundFx *EclipseEngine::load1bPCM(Common::SeekableReadStream *file, int offset) {
-	soundFx *sound = (soundFx *)malloc(sizeof(soundFx));
-	file->seek(offset);
-	sound->size = file->readUint16LE();
-	debugC(1, kFreescapeDebugParser, "size: %d", sound->size);
-	sound->sampleRate = file->readUint16LE();
-	debugC(1, kFreescapeDebugParser, "sample rate?: %f", sound->sampleRate);
-
-	uint8 *data = (uint8 *)malloc(sound->size * sizeof(uint8) * 8);
-	for (int i = 0; i < sound->size; i++) {
-		uint8 byte = file->readByte();
-		for (int j = 0; j < 8; j++) {
-			data[8 * i + j] = byte & 1 ? 255 : 0;
-			byte = byte >> 1;
-		}
-	}
-	sound->size = sound->size * 8;
-	sound->data = (byte *)data;
-	return sound;
-}
-
-void EclipseEngine::loadSoundsFx(Common::SeekableReadStream *file, int offset, int number) {
-	if (isAmiga() || isAtariST()) {
-		FreescapeEngine::loadSoundsFx(file, offset, number);
-		return;
-	}
-
-	for (int i = 0; i < number; i++) {
-		_soundsFx[i] = load1bPCM(file, offset);
-		offset += (_soundsFx[i]->size / 8) + 4;
-	}
-}
-
-
 void EclipseEngine::playSoundFx(int index, bool sync) {
-	if (isAmiga() || isAtariST()) {
-		FreescapeEngine::playSoundFx(index, sync);
-		return;
-	}
-
-	if (_soundsFx.size() == 0) {
-		debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded");
-		return;
-	}
-
-	int size = _soundsFx[index]->size;
-	//int sampleRate = _soundsFx[index]->sampleRate;
-	byte *data = _soundsFx[index]->data;
-
-	Audio::SeekableAudioStream *stream = Audio::makeRawStream(data, size, 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
-	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream, -1, kFreescapeDefaultVolume / 10);
+	if (_soundFx)
+		_soundFx->playSound(index);
+	else
+		playSound(index, sync, _soundFxHandle);
 }
 
 
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index 57b2465bdbd..be3eef369e4 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -127,6 +127,13 @@ EclipseEngine::EclipseEngine(OSystem *syst, const ADGameDescription *gd) : Frees
 	_atariAreaDark = false;
 	_resting = false;
 	_flashlightOn = false;
+
+	_soundFx = nullptr;
+}
+
+EclipseEngine::~EclipseEngine() {
+	if (_soundFx)
+		delete _soundFx;
 }
 
 void EclipseEngine::stopBackgroundMusic() {
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index a36195c3696..64797e28644 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -109,7 +109,6 @@ public:
 	Common::String getScoreString(int score);
 	void drawScoreString(int score, int x, int y, uint32 front, uint32 back, Graphics::Surface *surface);
 
-	soundFx *load1bPCM(Common::SeekableReadStream *file, int offset);
 	void loadHeartFramesCPC(Common::SeekableReadStream *file, int restOffset, int beatOffset);
 	void loadHeartFramesZX(Common::SeekableReadStream *file, int restOffset, int beatOffset);
 	void loadHeartFramesDOS(Common::SeekableReadStream *file, int restOffset, int beatOffset);
@@ -181,11 +180,13 @@ public:
 	bool triggerWinCondition() override;
 	bool checkIfGameEnded() override;
 	void endGame() override;
-	void loadSoundsFx(Common::SeekableReadStream *file, int offset, int number) override;
 	void playSoundFx(int index, bool sync) override;
 
 	Common::Error saveGameStreamExtended(Common::WriteStream *stream, bool isAutosave = false) override;
 	Common::Error loadGameStreamExtended(Common::SeekableReadStream *stream) override;
+
+private:
+	Sound *_soundFx;
 };
 
 }
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index cb5881321fd..9b8d2c02940 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -69,6 +69,8 @@ MODULE_OBJS := \
 	sound/common.o \
 	sound/cpc.o \
 	sound/dos.o \
+	sound/fx.o \
+	sound/fx_dos.o \
 	sound/zx.o \
 	ui.o \
 	unpack.o \
diff --git a/engines/freescape/sound.h b/engines/freescape/sound.h
index 37c11748854..b2ae241637c 100644
--- a/engines/freescape/sound.h
+++ b/engines/freescape/sound.h
@@ -27,13 +27,6 @@
 
 namespace Freescape {
 
-struct soundFx {
-	int size;
-	float sampleRate;
-	int repetitions;
-	byte *data;
-};
-
 struct soundUnitZX {
 	bool isRaw;
 	uint16 freqTimesSeconds;
diff --git a/engines/freescape/sound/common.cpp b/engines/freescape/sound/common.cpp
index c1fd167858f..5c5ba52356d 100644
--- a/engines/freescape/sound/common.cpp
+++ b/engines/freescape/sound/common.cpp
@@ -21,7 +21,6 @@
 
 #include "common/file.h"
 #include "audio/audiostream.h"
-#include "audio/decoders/raw.h"
 #include "audio/decoders/wave.h"
 
 #include "freescape/freescape.h"
@@ -50,11 +49,6 @@ void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle
 		return;
 	}
 
-	if (isAmiga() || isAtariST()) {
-		playSoundFx(index, sync);
-		return;
-	}
-
 	if (isDOS()) {
 		soundSpeakerFx *speakerFxInfo = _soundsSpeakerFx[index];
 		if (speakerFxInfo)
@@ -99,35 +93,6 @@ void FreescapeEngine::playMusic(const Common::Path &filename) {
 	}
 }
 
-void FreescapeEngine::playSoundFx(int index, bool sync) {
-	if (!_amigaSfxTable.empty()) {
-		playSoundAmiga(index, _soundFxHandle);
-		return;
-	}
-
-	if (_soundsFx.size() == 0) {
-		debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded");
-		return;
-	}
-
-	if (index < 0 || index >= int(_soundsFx.size())) {
-		debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d not available", index);
-		return;
-	}
-
-	int size = _soundsFx[index]->size;
-	int sampleRate = _soundsFx[index]->sampleRate;
-	int repetitions = _soundsFx[index]->repetitions;
-	byte *data = _soundsFx[index]->data;
-
-	if (size > 4) {
-		Audio::SeekableAudioStream *s = Audio::makeRawStream(data, size, sampleRate, Audio::FLAG_16BITS, DisposeAfterUse::NO);
-		Audio::AudioStream *stream = new Audio::LoopingAudioStream(s, repetitions);
-		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream);
-	} else
-		debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is empty", index);
-}
-
 void FreescapeEngine::stopAllSounds(Audio::SoundHandle &handle) {
 	debugC(1, kFreescapeDebugMedia, "Stopping sound");
 	if (_sound)
@@ -145,31 +110,10 @@ bool FreescapeEngine::isPlayingSound() {
 	if (_sound)
 		return _sound->isPlayingSound();
 
-	if (_usePrerecordedSounds || isAmiga() || isAtariST())
+	if (_usePrerecordedSounds)
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 
 	return (!_speaker->endOfStream());
 }
 
-void FreescapeEngine::loadSoundsFx(Common::SeekableReadStream *file, int offset, int number) {
-	file->seek(offset);
-	soundFx *sound = nullptr;
-	_soundsFx[0] = sound;
-	for (int i = 1; i < number + 1; i++) {
-		sound = (soundFx *)malloc(sizeof(soundFx));
-		int zero = file->readUint16BE();
-		assert(zero == 0);
-		int size = file->readUint16BE();
-		float sampleRate = float(file->readUint16BE()) / 2;
-		debugC(1, kFreescapeDebugParser, "Loading sound: %d (size: %d, sample rate: %f) at %" PRIx64, i, size, sampleRate, file->pos());
-		byte *data = (byte *)malloc(size * sizeof(byte));
-		file->read(data, size);
-		sound->sampleRate = sampleRate;
-		sound->size = size;
-		sound->data = (byte *)data;
-		sound->repetitions = 1;
-		_soundsFx[i] = sound;
-	}
-}
-
 } // namespace Freescape
diff --git a/engines/freescape/sound/fx.cpp b/engines/freescape/sound/fx.cpp
new file mode 100644
index 00000000000..4020499eb26
--- /dev/null
+++ b/engines/freescape/sound/fx.cpp
@@ -0,0 +1,115 @@
+/* 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/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "freescape/freescape.h"
+
+namespace Freescape {
+
+struct soundFx {
+	int size;
+	float sampleRate;
+	int repetitions;
+	byte *data;
+};
+
+class SoundFX final : public Sound {
+public:
+	SoundFX(Audio::Mixer *mixer) : _mixer(mixer) {}
+
+	~SoundFX() override {
+		for (auto &it : _soundsFx) {
+			if (it._value) {
+				free(it._value->data);
+				free(it._value);
+			}
+		}
+	}
+
+	void loadSounds(Common::SeekableReadStream *file, int offset, int number) {
+		file->seek(offset);
+		soundFx *sound = nullptr;
+		_soundsFx[0] = sound;
+		for (int i = 1; i < number + 1; i++) {
+			sound = (soundFx *)malloc(sizeof(soundFx));
+			int zero = file->readUint16BE();
+			assert(zero == 0);
+			int size = file->readUint16BE();
+			float sampleRate = float(file->readUint16BE()) / 2;
+			debugC(1, kFreescapeDebugParser, "Loading sound: %d (size: %d, sample rate: %f) at %" PRIx64, i, size, sampleRate, file->pos());
+			byte *data = (byte *)malloc(size * sizeof(byte));
+			file->read(data, size);
+			sound->sampleRate = sampleRate;
+			sound->size = size;
+			sound->data = (byte *)data;
+			sound->repetitions = 1;
+			_soundsFx[i] = sound;
+		}
+	}
+
+	void playSound(int index) override {
+		if (_soundsFx.size() == 0) {
+			debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded");
+			return;
+		}
+
+		if (index < 0 || index >= int(_soundsFx.size())) {
+			debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d not available", index);
+			return;
+		}
+
+		int size = _soundsFx[index]->size;
+		int sampleRate = _soundsFx[index]->sampleRate;
+		int repetitions = _soundsFx[index]->repetitions;
+		byte *data = _soundsFx[index]->data;
+
+		if (size > 4) {
+		Audio::SeekableAudioStream *s = Audio::makeRawStream(data, size, sampleRate, Audio::FLAG_16BITS, DisposeAfterUse::NO);
+			Audio::AudioStream *stream = new Audio::LoopingAudioStream(s, repetitions);
+			_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream);
+		} else
+			debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is empty", index);
+	}
+
+	void stopSound() override {
+		_mixer->stopHandle(_soundFxHandle);
+	}
+
+	bool isPlayingSound() const override {
+		return _mixer->isSoundHandleActive(_soundFxHandle);
+	}
+
+private:
+	Common::HashMap<uint16, soundFx *> _soundsFx;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundFxHandle;
+};
+
+Sound *FreescapeEngine::loadSoundsFx(Common::SeekableReadStream *file, int offset, int number) {
+	SoundFX *sound = new SoundFX(_mixer);
+	sound->loadSounds(file, offset, number);
+	return sound;
+}
+
+} // namespace Freescape
diff --git a/engines/freescape/sound/fx_dos.cpp b/engines/freescape/sound/fx_dos.cpp
new file mode 100644
index 00000000000..4f7f62597df
--- /dev/null
+++ b/engines/freescape/sound/fx_dos.cpp
@@ -0,0 +1,113 @@
+/* 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/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "freescape/freescape.h"
+
+namespace Freescape {
+
+struct soundFx {
+	int size;
+	float sampleRate;
+	int repetitions;
+	byte *data;
+};
+
+class SoundFX_DOS final : public Sound {
+public:
+	SoundFX_DOS(Audio::Mixer *mixer) : _mixer(mixer) {}
+
+	~SoundFX_DOS() override {
+		for (auto &it : _soundsFx) {
+			if (it._value) {
+				free(it._value->data);
+				free(it._value);
+			}
+		}
+	}
+
+	void loadSounds(Common::SeekableReadStream *file, int offset, int number) {
+		for (int i = 0; i < number; i++) {
+			_soundsFx[i] = load1bPCM(file, offset);
+			offset += (_soundsFx[i]->size / 8) + 4;
+		}
+	}
+
+	void playSound(int index) override {
+		if (_soundsFx.size() == 0) {
+			debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded");
+			return;
+		}
+
+		int size = _soundsFx[index]->size;
+		//int sampleRate = _soundsFx[index]->sampleRate;
+		byte *data = _soundsFx[index]->data;
+
+		Audio::SeekableAudioStream *stream = Audio::makeRawStream(data, size, 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
+		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream, -1, kFreescapeDefaultVolume / 10);
+	}
+
+	void stopSound() override {
+		_mixer->stopHandle(_soundFxHandle);
+	}
+
+	bool isPlayingSound() const override {
+		return _mixer->isSoundHandleActive(_soundFxHandle);
+	}
+
+private:
+	Common::HashMap<uint16, soundFx *> _soundsFx;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundFxHandle;
+
+	soundFx *load1bPCM(Common::SeekableReadStream *file, int offset) {
+		soundFx *sound = (soundFx *)malloc(sizeof(soundFx));
+		file->seek(offset);
+		sound->size = file->readUint16LE();
+		debugC(1, kFreescapeDebugParser, "size: %d", sound->size);
+		sound->sampleRate = file->readUint16LE();
+		debugC(1, kFreescapeDebugParser, "sample rate?: %f", sound->sampleRate);
+
+		uint8 *data = (uint8 *)malloc(sound->size * sizeof(uint8) * 8);
+		for (int i = 0; i < sound->size; i++) {
+			uint8 byte = file->readByte();
+			for (int j = 0; j < 8; j++) {
+				data[8 * i + j] = byte & 1 ? 255 : 0;
+				byte = byte >> 1;
+			}
+		}
+		sound->size = sound->size * 8;
+		sound->data = (byte *)data;
+		return sound;
+	}
+};
+
+
+Sound *FreescapeEngine::loadSoundsFxDOS(Common::SeekableReadStream *file, int offset, int number) {
+	SoundFX_DOS *sound = new SoundFX_DOS(_mixer);
+	sound->loadSounds(file, offset, number);
+	return sound;
+}
+
+} // End of namespace Freescape


Commit: f1cc3ba98eeba50327cb22c27df9f23f05c02f75
    https://github.com/scummvm/scummvm/commit/f1cc3ba98eeba50327cb22c27df9f23f05c02f75
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Move PC Speaker sound code into separate classes

Changed paths:
  R engines/freescape/games/driller/sounds.cpp
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/games/castle/castle.cpp
    engines/freescape/games/castle/castle.h
    engines/freescape/games/castle/cpc.cpp
    engines/freescape/games/castle/dos.cpp
    engines/freescape/games/castle/zx.cpp
    engines/freescape/games/dark/dark.cpp
    engines/freescape/games/dark/dos.cpp
    engines/freescape/games/dark/zx.cpp
    engines/freescape/games/driller/dos.cpp
    engines/freescape/games/driller/driller.h
    engines/freescape/games/driller/zx.cpp
    engines/freescape/games/eclipse/dos.cpp
    engines/freescape/games/eclipse/zx.cpp
    engines/freescape/module.mk
    engines/freescape/movement.cpp
    engines/freescape/sound.h
    engines/freescape/sound/common.cpp
    engines/freescape/sound/dos.cpp
    engines/freescape/sound/zx.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index eab00dfcc1d..3fa55aff246 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -288,7 +288,6 @@ FreescapeEngine::FreescapeEngine(OSystem *syst, const ADGameDescription *gd)
 	_viewArea = _fullscreenViewArea;
 	_rnd = new Common::RandomSource("freescape");
 	_gfx = nullptr;
-	_speaker = nullptr;
 	_savedScreen = nullptr;
 
 	_timerStarted = false;
@@ -411,7 +410,6 @@ FreescapeEngine::~FreescapeEngine() {
 
 	delete _gfx;
 	delete _dataBundle;
-	delete _speaker;
 
 	for (auto &it : _indicators) {
 		it->free();
@@ -1158,7 +1156,6 @@ Common::Error FreescapeEngine::run() {
 	//_screenW = g_system->getWidth();
 	//_screenH = g_system->getHeight();
 	_gfx = createRenderer(_screenW, _screenH, _renderMode, ConfMan.getBool("authentic_graphics"));
-	_speaker = new SizedPCSpeaker();
 	_crossairPosition.x = _screenW / 2;
 	_crossairPosition.y = _screenH / 2;
 
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 6396d012e0c..57fe1a7099c 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -513,7 +513,6 @@ public:
 	Audio::SoundHandle _soundFxHandle;
 	Audio::SoundHandle _musicHandle;
 	Audio::SoundHandle _movementSoundHandle;
-	Freescape::SizedPCSpeaker *_speaker;
 	Sound *_sound;
 
 	bool _syncSound;
@@ -525,23 +524,14 @@ public:
 	void playSound(int index, bool sync, Audio::SoundHandle &handle);
 	void playWav(const Common::Path &filename);
 	void playMusic(const Common::Path &filename);
-	void playSoundConst(double hzFreq, int duration, bool sync);
-	void playSoundSweepIncWL(double hzFreq1, double hzFreq2, double wlStepPerMS, int resolution, bool sync);
-	uint16 playSoundDOSSpeaker(uint16 startFrequency, soundSpeakerFx *speakerFxInfo);
-	void playSoundDOS(soundSpeakerFx *speakerFxInfo, bool sync, Audio::SoundHandle &handle);
 
 	virtual void playSoundC64(int index);
 	virtual void playSoundFx(int index, bool sync) {}
 	Sound *loadSoundsFx(Common::SeekableReadStream *file, int offset, int number);
 	Sound *loadSoundsFxDOS(Common::SeekableReadStream *file, int offset, int number);
-	void loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetDuration, int numberSounds);
-	void loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData);
-	Common::HashMap<uint16, soundSpeakerFx *> _soundsSpeakerFx;
-
-	virtual void playSoundZX(int index, Audio::SoundHandle &handle);
-	void playSoundZX(Common::Array<soundUnitZX> *data, Audio::SoundHandle &handle);
-	Common::HashMap<uint16, Common::Array<soundUnitZX>*> _soundsSpeakerFxZX;
-
+	Sound *loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetDuration, int numberSounds);
+	Sound *loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData, int numberSounds);
+	Sound *loadSpeakerFxDrillerZX();
 	Sound *loadSoundsCPC(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef);
 
 	void loadSoundsAmigaDemo(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset = 0x3D5A6);
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 01cab328349..ee883423615 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -1974,7 +1974,7 @@ void CastleEngine::checkSensors() {
 	}
 
 	if (!ghostInArea()) {
-		_mixer->stopHandle(_soundFxGhostHandle);
+		/*_mixer->stopHandle(_soundFxGhostHandle);*/
 		_gfx->_shakeOffset = Common::Point();
 		return;
 	}
@@ -1983,8 +1983,10 @@ void CastleEngine::checkSensors() {
 		return;
 
 	/*if (!_mixer->isSoundHandleActive(_soundFxGhostHandle)) {
-		_speaker->play(Audio::PCSpeaker::kWaveFormSquare, 25.0f, -1);
-		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxGhostHandle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+		// TODO: Reimplement inside Sound class using existing chip instances
+		SizedPCSpeaker *speaker = new SizedPCSpeaker();
+		speaker->play(Audio::PCSpeaker::kWaveFormSquare, 25.0f, -1);
+		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxGhostHandle, speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
 	}*/
 
 	// This is the frequency to shake the screen
diff --git a/engines/freescape/games/castle/castle.h b/engines/freescape/games/castle/castle.h
index 04b9224e26e..525bb4184dc 100644
--- a/engines/freescape/games/castle/castle.h
+++ b/engines/freescape/games/castle/castle.h
@@ -207,7 +207,7 @@ private:
 	bool ghostInArea();
 	void updateThunder();
 
-	Audio::SoundHandle _soundFxGhostHandle;
+	/*Audio::SoundHandle _soundFxGhostHandle;*/
 	Texture *_optionTexture;
 	Font _fontRiddle;
 	int _droppingGateStartTicks;
diff --git a/engines/freescape/games/castle/cpc.cpp b/engines/freescape/games/castle/cpc.cpp
index 769c5e896cf..922d4e60982 100644
--- a/engines/freescape/games/castle/cpc.cpp
+++ b/engines/freescape/games/castle/cpc.cpp
@@ -203,7 +203,7 @@ void CastleEngine::loadAssetsCPCFullGame() {
 			loadRiddles(&file, 0x1470 - 4 - 2 - 9 * 2, 9);
 			loadMessagesVariableSize(&file, 0xf3d, 71);
 			load8bitBinary(&file, 0x6aab - 2, 16);
-			loadSpeakerFxZX(&file, 0xca0, 0xcdc);
+			_sound = loadSpeakerFxZX(&file, 0xca0, 0xcdc, 25);
 
 			file.seek(0x1218 + 16);
 			for (int i = 0; i < 90; i++) {
diff --git a/engines/freescape/games/castle/dos.cpp b/engines/freescape/games/castle/dos.cpp
index ffd3d575045..abaae81b605 100644
--- a/engines/freescape/games/castle/dos.cpp
+++ b/engines/freescape/games/castle/dos.cpp
@@ -155,7 +155,7 @@ void CastleEngine::loadAssetsDOSFullGame() {
 		file.open("CME.EXE");
 		stream = unpackEXE(file);
 		if (stream) {
-			loadSpeakerFxDOS(stream, 0x636d + 0x200, 0x63ed + 0x200, 30);
+			_sound = loadSpeakerFxDOS(stream, 0x636d + 0x200, 0x63ed + 0x200, 30);
 
 			stream->seek(0x197c0);
 			_endGameBackgroundFrame = loadFrameFromPlanes(stream, 112, 108);
@@ -325,7 +325,7 @@ void CastleEngine::loadAssetsDOSDemo() {
 		file.open("CMDE.EXE");
 		stream = unpackEXE(file);
 		if (stream) {
-			loadSpeakerFxDOS(stream, 0x636d + 0x200, 0x63ed + 0x200, 30);
+			_sound = loadSpeakerFxDOS(stream, 0x636d + 0x200, 0x63ed + 0x200, 30);
 
 			stream->seek(0x197c0 - 0x2a0);
 			_endGameBackgroundFrame = loadFrameFromPlanes(stream, 112, 108);
diff --git a/engines/freescape/games/castle/zx.cpp b/engines/freescape/games/castle/zx.cpp
index d1979d13b36..c572c213b8f 100644
--- a/engines/freescape/games/castle/zx.cpp
+++ b/engines/freescape/games/castle/zx.cpp
@@ -96,7 +96,7 @@ void CastleEngine::loadAssetsZXFullGame() {
 		}
 
 		load8bitBinary(&file, 0x6682, 16);
-		loadSpeakerFxZX(&file, 0x0bbf, 0x0bfb);
+		_sound = loadSpeakerFxZX(&file, 0x0bbf, 0x0bfb, 25);
 
 		file.seek(0x1147);
 		for (int i = 0; i < 90; i++) {
@@ -113,7 +113,7 @@ void CastleEngine::loadAssetsZXFullGame() {
 			case Common::ES_ESP:
 				loadRiddles(&file, 0x1458, 9);
 				load8bitBinary(&file, 0x6aa9, 16);
-				loadSpeakerFxZX(&file, 0xca0, 0xcdc);
+				_sound = loadSpeakerFxZX(&file, 0xca0, 0xcdc, 25);
 
 				file.seek(0x1228);
 				for (int i = 0; i < 90; i++) {
@@ -130,12 +130,12 @@ void CastleEngine::loadAssetsZXFullGame() {
 				if (_variant & GF_ZX_RETAIL) {
 					loadRiddles(&file, 0x1448, 9);
 					load8bitBinary(&file, 0x6a3b, 16);
-					loadSpeakerFxZX(&file, 0xc91, 0xccd);
+					_sound = loadSpeakerFxZX(&file, 0xc91, 0xccd, 25);
 					file.seek(0x1219);
 				} else if (_variant & GF_ZX_DISC) {
 					loadRiddles(&file, 0x1457, 9);
 					load8bitBinary(&file, 0x6a9b, 16);
-					loadSpeakerFxZX(&file, 0xca0, 0xcdc);
+					_sound = loadSpeakerFxZX(&file, 0xca0, 0xcdc, 25);
 					file.seek(0x1228);
 				} else {
 					error("Unknown Castle Master ZX variant");
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index 5b617b97d2b..c340f83c230 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -772,12 +772,14 @@ void DarkEngine::pressedKey(const int keycode) {
 			_flyMode = false;
 			insertTemporaryMessage(_messagesList[13], _countdown - 2);
 		} else if (_flyMode) {
+			// TODO: Reimplement inside Sound class using existing chip instances
+			SizedPCSpeaker *speaker = new SizedPCSpeaker();
 			float hzFreq = 1193180.0f / 0xd537;
-			_speaker->play(Audio::PCSpeaker::kWaveFormSquare, hzFreq, -1);
-			_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandleJetpack, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+			speaker->play(Audio::PCSpeaker::kWaveFormSquare, hzFreq, -1);
+			_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandleJetpack, speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
 			insertTemporaryMessage(_messagesList[11], _countdown - 2);
 		} else {
-			_speaker->stop();
+			_mixer->stopHandle(_soundFxHandleJetpack);
 			resolveCollisions(_position);
 			if (!_hasFallen)
 				insertTemporaryMessage(_messagesList[12], _countdown - 2);
diff --git a/engines/freescape/games/dark/dos.cpp b/engines/freescape/games/dark/dos.cpp
index e2e2496abf5..ffd8f3e68ea 100644
--- a/engines/freescape/games/dark/dos.cpp
+++ b/engines/freescape/games/dark/dos.cpp
@@ -53,7 +53,7 @@ void DarkEngine::loadAssetsDOSDemo() {
 		if (!file.isOpen())
 			error("Failed to open DSIDEE.EXE");
 
-		loadSpeakerFxDOS(&file, 0x4837 + 0x200, 0x46e8 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x4837 + 0x200, 0x46e8 + 0x200, 20);
 		loadMessagesFixedSize(&file, 0x4525, 16, 27);
 		loadMessagesFixedSize(&file, 0x993f - 2, 308, 5);
 		loadFonts(&file, 0xa598);
@@ -82,7 +82,7 @@ void DarkEngine::loadAssetsDOSDemo() {
 		if (!file.isOpen())
 			error("Failed to open DSIDEC.EXE");
 
-		loadSpeakerFxDOS(&file, 0x3077 + 0x200, 0x2f28 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x3077 + 0x200, 0x2f28 + 0x200, 20);
 		loadFonts(&file, 0x8907);
 		loadMessagesFixedSize(&file, 0x2d65, 16, 27);
 		loadMessagesFixedSize(&file, 0x7c3a, 308, 5);
@@ -110,7 +110,7 @@ void DarkEngine::loadAssetsDOSFullGame() {
 		if (!file.isOpen())
 			error("Failed to open DSIDEE.EXE");
 
-		loadSpeakerFxDOS(&file, 0x4837 + 0x200, 0x46e8 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x4837 + 0x200, 0x46e8 + 0x200, 20);
 		loadFonts(&file, 0xa113);
 		loadMessagesFixedSize(&file, 0x4525, 16, 27);
 		loadGlobalObjects(&file, 0x3d04, 23);
@@ -138,7 +138,7 @@ void DarkEngine::loadAssetsDOSFullGame() {
 		if (!file.isOpen())
 			error("Failed to open DSIDEC.EXE");
 
-		loadSpeakerFxDOS(&file, 0x3077 + 0x200, 0x2f28 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x3077 + 0x200, 0x2f28 + 0x200, 20);
 		loadFonts(&file, 0x8496);
 		loadMessagesFixedSize(&file, 0x2d65, 16, 27);
 		loadGlobalObjects(&file, 0x2554, 23);
diff --git a/engines/freescape/games/dark/zx.cpp b/engines/freescape/games/dark/zx.cpp
index cd231aac36e..63e843dee59 100644
--- a/engines/freescape/games/dark/zx.cpp
+++ b/engines/freescape/games/dark/zx.cpp
@@ -76,7 +76,7 @@ void DarkEngine::loadAssetsZXFullGame() {
 	loadFonts(&file, 0x5d60 - 6);
 	loadGlobalObjects(&file, 0x1a, 23);
 	load8bitBinary(&file, 0x5ec0 - 4, 4);
-	loadSpeakerFxZX(&file, 0x9c1, 0xa55);
+	_sound = loadSpeakerFxZX(&file, 0x9c1, 0xa55, 34);
 
 	_indicators.push_back(loadBundledImage("dark_fallen_indicator"));
 	_indicators.push_back(loadBundledImage("dark_crouch_indicator"));
@@ -110,7 +110,7 @@ void DarkEngine::loadAssetsZXDemo() {
 
 	loadMessagesFixedSize(&file, 0x56b, 16, 27);
 	loadMessagesFixedSize(&file, 0x5761, 264, 5);
-	loadSpeakerFxZX(&file, 0x9c7, 0xa5b);
+	_sound = loadSpeakerFxZX(&file, 0x9c7, 0xa5b, 34);
 
 	loadFonts(&file, 0x6164);
 	loadGlobalObjects(&file, 0x20, 23);
diff --git a/engines/freescape/games/driller/dos.cpp b/engines/freescape/games/driller/dos.cpp
index 29aaa90e2e4..c487684e2de 100644
--- a/engines/freescape/games/driller/dos.cpp
+++ b/engines/freescape/games/driller/dos.cpp
@@ -223,7 +223,7 @@ void DrillerEngine::loadAssetsDOSFullGame() {
 		if (!file.isOpen())
 			error("Failed to open DRILLE.EXE");
 
-		loadSpeakerFxDOS(&file, 0x4397 + 0x200, 0x4324 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x4397 + 0x200, 0x4324 + 0x200, 20);
 		loadMessagesFixedSize(&file, 0x4135, 14, 20);
 		loadFonts(&file, 0x99dd);
 		loadGlobalObjects(&file, 0x3b42, 8);
@@ -248,7 +248,7 @@ void DrillerEngine::loadAssetsDOSFullGame() {
 		if (!file.isOpen())
 			error("Failed to open DRILLC.EXE");
 
-		loadSpeakerFxDOS(&file, 0x27e7 + 0x200, 0x2774 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x27e7 + 0x200, 0x2774 + 0x200, 20);
 
 		loadFonts(&file, 0x07a4a);
 		loadMessagesFixedSize(&file, 0x2585, 14, 20);
@@ -269,7 +269,7 @@ void DrillerEngine::loadAssetsDOSFullGame() {
 		if (!file.isOpen())
 			error("Failed to open DRILLH.EXE");
 
-		//loadSpeakerFxDOS(&file, 0x27e7 + 0x200, 0x2774 + 0x200, 20);
+		//_sound = loadSpeakerFxDOS(&file, 0x27e7 + 0x200, 0x2774 + 0x200, 20);
 
 		loadFonts(&file, 0x8871);
 		loadMessagesFixedSize(&file, 0x3411, 14, 20);
diff --git a/engines/freescape/games/driller/driller.h b/engines/freescape/games/driller/driller.h
index 408826ce7a4..96db7f66625 100644
--- a/engines/freescape/games/driller/driller.h
+++ b/engines/freescape/games/driller/driller.h
@@ -69,7 +69,6 @@ public:
 
 	void gotoArea(uint16 areaID, int entranceID) override;
 
-	void playSoundZX(int index, Audio::SoundHandle &handle) override;
 	void drawInfoMenu() override;
 	void drawSensorShoot(Sensor *sensor) override;
 	void drawCompass(Graphics::Surface *surface, int x, int y, double degrees, double magnitude, double fov, uint32 color);
diff --git a/engines/freescape/games/driller/sounds.cpp b/engines/freescape/games/driller/sounds.cpp
deleted file mode 100644
index 60f02efb49c..00000000000
--- a/engines/freescape/games/driller/sounds.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/* 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 "freescape/freescape.h"
-#include "freescape/games/driller/driller.h"
-
-namespace Freescape {
-
-void DrillerEngine::playSoundZX(int index, Audio::SoundHandle &handle) {
-	debugC(1, kFreescapeDebugMedia, "Playing Driller ZX sound %d", index);
-	Common::Array<soundUnitZX> soundUnits;
-
-	auto addTone = [&](uint16 hl, uint16 de, float multiplier) {
-		soundUnitZX s;
-		s.isRaw = false;
-		s.tStates = hl; // HL determines period
-		s.freqTimesSeconds = de; // DE determines duration (number of cycles)
-		s.multiplier = multiplier;
-		soundUnits.push_back(s);
-	};
-
-	// Linear Sweep: Period increases -> Pitch decreases
-	auto addSweep = [&](uint16 startHl, uint16 endHl, uint16 step, uint16 duration) {
-		for (uint16 hl = startHl; hl < endHl; hl += step) {
-			addTone(hl, duration, 10.0f);
-		}
-	};
-
-	// Zap effect: Decreasing Period (E decrements) -> Pitch increases
-	auto addZap = [&](uint16 startE, uint16 endE, uint16 duration) {
-		for (uint16 e = startE; e > endE; e--) {
-			// Map E (delay loops) to HL (tStates)
-			// Small E -> Short Period -> High Freq
-			uint16 hl = (24 + e) * 4;
-			addTone(hl, duration, 10.0f);
-		}
-	};
-
-	// Sweep Down: Increasing Period (E increments) -> Pitch decreases
-	auto addSweepDown = [&](uint16 startE, uint16 endE, uint16 step, uint16 duration, float multiplier) {
-		for (uint16 e = startE; e < endE; e += step) {
-			uint16 hl = (24 + e) * 4;
-			addTone(hl, duration, multiplier);
-		}
-	};
-
-	switch (index) {
-	case 1: // Shoot (FUN_95A1 -> 95AF)
-		// Laser: High Pitch -> Low Pitch
-		// Adjusted pitch to be even lower (0x200-0x600 is approx 850Hz-280Hz)
-		addSweepDown(0x200, 0x600, 20, 1, 2.0f);
-		break;
-	case 2: // Collide/Bump (FUN_95DE)
-		// Low tone sequence
-		addTone(0x93c, 0x40, 10.0f); // 64 cycles ~340ms
-		addTone(0x7a6, 0x30, 10.0f); // 48 cycles
-		break;
-	case 3: // Step (FUN_95E5)
-		// Short blip
-		// Increased duration significantly again (0xC0 = 192 cycles)
-		addTone(0x7a6, 0xC0, 10.0f);
-		break;
-	case 4: // Silence (FUN_95F7)
-		break;
-	case 5: // Area Change? (FUN_95F8)
-		addTone(0x1f0, 0x60, 10.0f); // High pitch, longer
-		break;
-	case 6: // Menu (Silence?) (FUN_9601)
-		break;
-	case 7: // Hit? (Sweep FUN_9605)
-		// Sweep down (Period increases)
-		addSweep(0x200, 0xC00, 64, 2);
-		break;
-	case 8: // Zap (FUN_961F)
-		// Zap: Low -> High
-		addZap(0xFF, 0x10, 2);
-		break;
-	case 9: // Sweep (FUN_9673)
-		addSweep(0x100, 0x600, 16, 4);
-		break;
-	case 10: // Area Change (FUN_9696)
-		addSweep(0x100, 0x500, 16, 4);
-		break;
-	case 11: // Explosion (FUN_96B9)
-		{
-			soundUnitZX s;
-			s.isRaw = true;
-			s.rawFreq = 0.0f; // Noise
-			s.rawLengthus = 100000; // 100ms noise
-			soundUnits.push_back(s);
-		}
-		break;
-	case 12: // Sweep Down (FUN_96E4)
-		addSweepDown(0x01, 0xFF, 1, 2, 10.0f);
-		break;
-	case 13: // Fall? (FUN_96FD)
-		addSweep(300, 800, 16, 2);
-		break;
-	default:
-		debugC(1, kFreescapeDebugMedia, "Unknown Driller ZX sound %d", index);
-		break;
-	}
-
-	FreescapeEngine::playSoundZX(&soundUnits, handle);
-}
-
-} // namespace Freescape
diff --git a/engines/freescape/games/driller/zx.cpp b/engines/freescape/games/driller/zx.cpp
index ad89604a36e..3d61cbcef66 100644
--- a/engines/freescape/games/driller/zx.cpp
+++ b/engines/freescape/games/driller/zx.cpp
@@ -87,6 +87,8 @@ void DrillerEngine::loadAssetsZXFullGame() {
 
 	else
 		error("Unknown ZX spectrum variant");
+
+	_sound = loadSpeakerFxDrillerZX();
 }
 
 void DrillerEngine::drawZXUI(Graphics::Surface *surface) {
diff --git a/engines/freescape/games/eclipse/dos.cpp b/engines/freescape/games/eclipse/dos.cpp
index 5b9c5058c71..a2b27f46d41 100644
--- a/engines/freescape/games/eclipse/dos.cpp
+++ b/engines/freescape/games/eclipse/dos.cpp
@@ -122,7 +122,7 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 
 		loadMessagesFixedSize(&file, 0x710f, 16, 20);
 		_soundFx = loadSoundsFxDOS(&file, 0xd670, 5);
-		loadSpeakerFxDOS(&file, 0x7396 + 0x200, 0x72a1 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x7396 + 0x200, 0x72a1 + 0x200, 20);
 		loadFonts(&file, 0xd403);
 		load8bitBinary(&file, 0x3ce0, 16);
 
@@ -150,7 +150,7 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 
 		loadMessagesFixedSize(&file, 0x594f, 16, 20);
 		_soundFx = loadSoundsFxDOS(&file, 0xb9f0, 5);
-		loadSpeakerFxDOS(&file, 0x5BD6 + 0x200, 0x5AE1 + 0x200, 20);
+		_sound = loadSpeakerFxDOS(&file, 0x5BD6 + 0x200, 0x5AE1 + 0x200, 20);
 		loadFonts(&file, 0xb785);
 		load8bitBinary(&file, 0x2530, 4);
 		_border = load8bitBinImage(&file, 0x210);
diff --git a/engines/freescape/games/eclipse/zx.cpp b/engines/freescape/games/eclipse/zx.cpp
index dabb21644bc..512639cfa2e 100644
--- a/engines/freescape/games/eclipse/zx.cpp
+++ b/engines/freescape/games/eclipse/zx.cpp
@@ -112,12 +112,12 @@ void EclipseEngine::loadAssetsZXFullGame() {
 	if (isEclipse2()) {
 		loadMessagesFixedSize(&file, 0x2ac, 16, 30);
 		loadFonts(&file, 0x61c3);
-		loadSpeakerFxZX(&file, 0x8c6, 0x91a);
+		_sound = loadSpeakerFxZX(&file, 0x8c6, 0x91a, 25);
 		load8bitBinary(&file, 0x63bb, 4);
 	} else {
 		loadMessagesFixedSize(&file, 0x2ac, 16, 23);
 		loadFonts(&file, 0x6163);
-		loadSpeakerFxZX(&file, 0x816, 0x86a);
+		_sound = loadSpeakerFxZX(&file, 0x816, 0x86a, 25);
 		load8bitBinary(&file, 0x635b, 4);
 		loadHeartFramesZX(&file, 0x0D62, 0x0D7C);
 
@@ -157,13 +157,13 @@ void EclipseEngine::loadAssetsZXDemo() {
 		error("Failed to open totaleclipse.zx.data");
 
 	if (_variant & GF_ZX_DEMO_MICROHOBBY) {
-		loadSpeakerFxZX(&file, 0x798, 0x7ec);
+		_sound = loadSpeakerFxZX(&file, 0x798, 0x7ec, 21);
 		loadMessagesFixedSize(&file, 0x2ac, 16, 23);
 		loadMessagesFixedSize(&file, 0x56e6, 264, 1);
 		loadFonts(&file, 0x5f7b);
 		load8bitBinary(&file, 0x6173, 4);
 	} else if (_variant & GF_ZX_DEMO_CRASH) {
-		loadSpeakerFxZX(&file, 0x65c, 0x6b0);
+		_sound = loadSpeakerFxZX(&file, 0x65c, 0x6b0, 25);
 		loadMessagesFixedSize(&file, 0x364, 16, 9);
 		loadMessagesFixedSize(&file, 0x5901, 264, 5);
 		loadFonts(&file, 0x6589);
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index 9b8d2c02940..b93980b11c6 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -38,7 +38,6 @@ MODULE_OBJS := \
 	games/driller/dos.o \
 	games/driller/driller.o \
 	games/driller/opl.music.o \
-	games/driller/sounds.o \
 	games/driller/zx.o \
 	games/eclipse/amiga.o \
 	games/eclipse/atari.o \
diff --git a/engines/freescape/movement.cpp b/engines/freescape/movement.cpp
index 5303d8c79a5..19d21e2f42f 100644
--- a/engines/freescape/movement.cpp
+++ b/engines/freescape/movement.cpp
@@ -714,7 +714,7 @@ bool FreescapeEngine::runCollisionConditions(Math::Vector3d const lastPosition,
 	Object *collided = nullptr;
 	_gotoExecuted = false;
 
-	_speaker->stop();
+	stopAllSounds(_movementSoundHandle);
 
 	Math::Ray ray(newPosition, -_upVector);
 	collided = _currentArea->checkCollisionRay(ray, _playerHeight + 3);
diff --git a/engines/freescape/sound.h b/engines/freescape/sound.h
index b2ae241637c..c6b7a42073d 100644
--- a/engines/freescape/sound.h
+++ b/engines/freescape/sound.h
@@ -23,28 +23,9 @@
 #define FREESCAPE_SOUND_H
 
 #include "audio/softsynth/pcspk.h"
-#include "common/array.h"
 
 namespace Freescape {
 
-struct soundUnitZX {
-	bool isRaw;
-	uint16 freqTimesSeconds;
-	uint16 tStates;
-	float rawFreq;
-	uint32 rawLengthus;
-	float multiplier;
-};
-
-struct soundSpeakerFx {
-	uint16 frequencyStart;
-	uint8 frequencyDuration;
-	uint8 frequencyStepsNumber;
-	uint16 frequencyStep;
-	uint8 repetitions;
-	Common::Array<struct soundSpeakerFx *>additionalSteps;
-};
-
 struct AmigaSfxEntry {
 	byte priority;
 	Common::Array<uint16> commands;
diff --git a/engines/freescape/sound/common.cpp b/engines/freescape/sound/common.cpp
index 5c5ba52356d..4bdc22a3ec1 100644
--- a/engines/freescape/sound/common.cpp
+++ b/engines/freescape/sound/common.cpp
@@ -49,19 +49,6 @@ void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle
 		return;
 	}
 
-	if (isDOS()) {
-		soundSpeakerFx *speakerFxInfo = _soundsSpeakerFx[index];
-		if (speakerFxInfo)
-			playSoundDOS(speakerFxInfo, sync, handle);
-		else
-			debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is not available", index);
-
-		return;
-	} else if (isSpectrum()) {
-		playSoundZX(index, handle);
-		return;
-	}
-
 	Common::Path filename;
 	filename = Common::String::format("%s-%d.wav", _targetName.c_str(), index);
 	debugC(1,  kFreescapeDebugMedia, "Playing sound %s", filename.toString().c_str());
@@ -113,7 +100,7 @@ bool FreescapeEngine::isPlayingSound() {
 	if (_usePrerecordedSounds)
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 
-	return (!_speaker->endOfStream());
+	return false;
 }
 
 } // namespace Freescape
diff --git a/engines/freescape/sound/dos.cpp b/engines/freescape/sound/dos.cpp
index 8b6ee48e84e..adf4e6d7f4d 100644
--- a/engines/freescape/sound/dos.cpp
+++ b/engines/freescape/sound/dos.cpp
@@ -19,11 +19,62 @@
  *
  */
 
+#include "common/array.h"
+
 #include "freescape/freescape.h"
 
 namespace Freescape {
 
-void FreescapeEngine::loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetTable, int numberSounds) {
+struct soundSpeakerFx {
+	uint16 frequencyStart;
+	uint8 frequencyDuration;
+	uint8 frequencyStepsNumber;
+	uint16 frequencyStep;
+	uint8 repetitions;
+	Common::Array<struct soundSpeakerFx *>additionalSteps;
+};
+
+// TODO: Migrate to Audio::PCSpeaker
+class SoundDOS : public Sound {
+public:
+	SoundDOS(Audio::Mixer *mixer) : _mixer(mixer) {
+		_speaker = new SizedPCSpeaker();
+	}
+
+	~SoundDOS() {
+		delete _speaker;
+	}
+
+	void loadSpeakerFx(Common::SeekableReadStream *file, int offsetFreq, int offsetTable, int numberSounds);
+
+	void playSound(int index) override {
+		soundSpeakerFx *speakerFxInfo = _soundsSpeakerFx[index];
+		if (speakerFxInfo)
+			playSoundDOS(speakerFxInfo);
+		else
+			debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is not available", index);
+	}
+
+	void stopSound() override {
+		_mixer->stopHandle(_soundFxHandle);
+	}
+
+	bool isPlayingSound() const override {
+		return !_speaker->endOfStream();
+	}
+
+private:
+	Common::HashMap<uint16, soundSpeakerFx *> _soundsSpeakerFx;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundFxHandle;
+	SizedPCSpeaker *_speaker;
+
+	uint16 playSoundDOSSpeaker(uint16 frequencyStart, soundSpeakerFx *speakerFxInfo);
+	void playSoundDOS(soundSpeakerFx *speakerFxInfo);
+};
+
+void SoundDOS::loadSpeakerFx(Common::SeekableReadStream *file, int offsetFreq, int offsetTable, int numberSounds) {
 	debugC(1, kFreescapeDebugParser, "Reading PC speaker sound table for DOS");
 	for (int i = 1; i <= numberSounds; i++) {
 		debugC(1, kFreescapeDebugParser, "Reading sound table entry: %d ", i);
@@ -91,7 +142,7 @@ void FreescapeEngine::loadSpeakerFxDOS(Common::SeekableReadStream *file, int off
 	}
 }
 
-uint16 FreescapeEngine::playSoundDOSSpeaker(uint16 frequencyStart, soundSpeakerFx *speakerFxInfo) {
+uint16 SoundDOS::playSoundDOSSpeaker(uint16 frequencyStart, soundSpeakerFx *speakerFxInfo) {
 	uint8 frequencyStepsNumber = speakerFxInfo->frequencyStepsNumber;
 	int16 frequencyStep = speakerFxInfo->frequencyStep;
 	uint8 frequencyDuration = speakerFxInfo->frequencyDuration;
@@ -117,7 +168,7 @@ uint16 FreescapeEngine::playSoundDOSSpeaker(uint16 frequencyStart, soundSpeakerF
 	return freq;
 }
 
-void FreescapeEngine::playSoundDOS(soundSpeakerFx *speakerFxInfo, bool sync, Audio::SoundHandle &handle) {
+void SoundDOS::playSoundDOS(soundSpeakerFx *speakerFxInfo) {
 	uint freq = speakerFxInfo->frequencyStart;
 
 	for (int i = 0; i < speakerFxInfo->repetitions; i++) {
@@ -129,8 +180,14 @@ void FreescapeEngine::playSoundDOS(soundSpeakerFx *speakerFxInfo, bool sync, Aud
 		}
 	}
 
-	_mixer->stopHandle(handle);
-	_mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+	_mixer->stopHandle(_soundFxHandle);
+	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+}
+
+Sound *FreescapeEngine::loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetTable, int numberSounds) {
+	SoundDOS *sound = new SoundDOS(_mixer);
+	sound->loadSpeakerFx(file, offsetFreq, offsetTable, numberSounds);
+	return sound;
 }
 
 } // namespace Freescape
diff --git a/engines/freescape/sound/zx.cpp b/engines/freescape/sound/zx.cpp
index 301dded776e..b3648376d5c 100644
--- a/engines/freescape/sound/zx.cpp
+++ b/engines/freescape/sound/zx.cpp
@@ -20,19 +20,56 @@
  */
 
 #include "freescape/freescape.h"
-#include "freescape/games/eclipse/eclipse.h"
 
 namespace Freescape {
 
-void FreescapeEngine::loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData) {
-	debugC(1, kFreescapeDebugParser, "Reading sound table for ZX");
-	int numberSounds = 25;
+struct soundUnitZX {
+	bool isRaw;
+	uint16 freqTimesSeconds;
+	uint16 tStates;
+	float rawFreq;
+	uint32 rawLengthus;
+	float multiplier;
+};
+
+// TODO: Migrate to Audio::PCSpeaker
+class SoundZX : public Sound {
+public:
+	SoundZX(Audio::Mixer *mixer) : _mixer(mixer) {
+		_speaker = new SizedPCSpeaker();
+	}
+
+	~SoundZX() {
+		delete _speaker;
+	}
+
+	void loadSpeakerFx(Common::SeekableReadStream *file, int sfxTable, int sfxData, int numberSounds);
+
+	void playSound(int index) override {
+		playSoundZX(_soundsSpeakerFxZX[index]);
+	}
 
-	if (isDark())
-		numberSounds = 34;
+	void stopSound() override {
+		_mixer->stopHandle(_soundFxHandle);
+	}
+
+	bool isPlayingSound() const override {
+		return !_speaker->endOfStream();
+	}
+
+protected:
+	void playSoundZX(Common::Array<soundUnitZX> *data);
 
-	if (isEclipse() && (_variant & GF_ZX_DEMO_MICROHOBBY))
-		numberSounds = 21;
+private:
+	Common::HashMap<uint16, Common::Array<soundUnitZX>*> _soundsSpeakerFxZX;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundFxHandle;
+	SizedPCSpeaker *_speaker;
+};
+
+void SoundZX::loadSpeakerFx(Common::SeekableReadStream *file, int sfxTable, int sfxData, int numberSounds) {
+	debugC(1, kFreescapeDebugParser, "Reading sound table for ZX");
 
 	for (int i = 1; i < numberSounds; i++) {
 		debugC(1, kFreescapeDebugParser, "Reading sound table entry: %d ", i);
@@ -227,7 +264,7 @@ void FreescapeEngine::loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxT
 	//assert(0);
 }
 
-void FreescapeEngine::playSoundZX(Common::Array<soundUnitZX> *data, Audio::SoundHandle &handle) {
+void SoundZX::playSoundZX(Common::Array<soundUnitZX> *data) {
 	for (auto &it : *data) {
 		soundUnitZX value = it;
 
@@ -253,11 +290,123 @@ void FreescapeEngine::playSoundZX(Common::Array<soundUnitZX> *data, Audio::Sound
 	}
 
 	_mixer->stopHandle(_soundFxHandle);
-	_mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, _speaker, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
+}
+
+Sound *FreescapeEngine::loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData, int numberSounds) {
+	SoundZX *sound = new SoundZX(_mixer);
+	sound->loadSpeakerFx(file, sfxTable, sfxData, numberSounds);
+	return sound;
+}
+
+class SoundDrillerZX final : public SoundZX {
+public:
+	SoundDrillerZX(Audio::Mixer *mixer) : SoundZX(mixer) {}
+
+	void playSound(int index) override;
+};
+
+void SoundDrillerZX::playSound(int index) {
+	debugC(1, kFreescapeDebugMedia, "Playing Driller ZX sound %d", index);
+	Common::Array<soundUnitZX> soundUnits;
+
+	auto addTone = [&](uint16 hl, uint16 de, float multiplier) {
+		soundUnitZX s;
+		s.isRaw = false;
+		s.tStates = hl; // HL determines period
+		s.freqTimesSeconds = de; // DE determines duration (number of cycles)
+		s.multiplier = multiplier;
+		soundUnits.push_back(s);
+	};
+
+	// Linear Sweep: Period increases -> Pitch decreases
+	auto addSweep = [&](uint16 startHl, uint16 endHl, uint16 step, uint16 duration) {
+		for (uint16 hl = startHl; hl < endHl; hl += step) {
+			addTone(hl, duration, 10.0f);
+		}
+	};
+
+	// Zap effect: Decreasing Period (E decrements) -> Pitch increases
+	auto addZap = [&](uint16 startE, uint16 endE, uint16 duration) {
+		for (uint16 e = startE; e > endE; e--) {
+			// Map E (delay loops) to HL (tStates)
+			// Small E -> Short Period -> High Freq
+			uint16 hl = (24 + e) * 4;
+			addTone(hl, duration, 10.0f);
+		}
+	};
+
+	// Sweep Down: Increasing Period (E increments) -> Pitch decreases
+	auto addSweepDown = [&](uint16 startE, uint16 endE, uint16 step, uint16 duration, float multiplier) {
+		for (uint16 e = startE; e < endE; e += step) {
+			uint16 hl = (24 + e) * 4;
+			addTone(hl, duration, multiplier);
+		}
+	};
+
+	switch (index) {
+	case 1: // Shoot (FUN_95A1 -> 95AF)
+		// Laser: High Pitch -> Low Pitch
+		// Adjusted pitch to be even lower (0x200-0x600 is approx 850Hz-280Hz)
+		addSweepDown(0x200, 0x600, 20, 1, 2.0f);
+		break;
+	case 2: // Collide/Bump (FUN_95DE)
+		// Low tone sequence
+		addTone(0x93c, 0x40, 10.0f); // 64 cycles ~340ms
+		addTone(0x7a6, 0x30, 10.0f); // 48 cycles
+		break;
+	case 3: // Step (FUN_95E5)
+		// Short blip
+		// Increased duration significantly again (0xC0 = 192 cycles)
+		addTone(0x7a6, 0xC0, 10.0f);
+		break;
+	case 4: // Silence (FUN_95F7)
+		break;
+	case 5: // Area Change? (FUN_95F8)
+		addTone(0x1f0, 0x60, 10.0f); // High pitch, longer
+		break;
+	case 6: // Menu (Silence?) (FUN_9601)
+		break;
+	case 7: // Hit? (Sweep FUN_9605)
+		// Sweep down (Period increases)
+		addSweep(0x200, 0xC00, 64, 2);
+		break;
+	case 8: // Zap (FUN_961F)
+		// Zap: Low -> High
+		addZap(0xFF, 0x10, 2);
+		break;
+	case 9: // Sweep (FUN_9673)
+		addSweep(0x100, 0x600, 16, 4);
+		break;
+	case 10: // Area Change (FUN_9696)
+		addSweep(0x100, 0x500, 16, 4);
+		break;
+	case 11: // Explosion (FUN_96B9)
+		{
+			soundUnitZX s;
+			s.isRaw = true;
+			s.rawFreq = 0.0f; // Noise
+			s.rawLengthus = 100000; // 100ms noise
+			soundUnits.push_back(s);
+		}
+		break;
+	case 12: // Sweep Down (FUN_96E4)
+		addSweepDown(0x01, 0xFF, 1, 2, 10.0f);
+		break;
+	case 13: // Fall? (FUN_96FD)
+		addSweep(300, 800, 16, 2);
+		break;
+	default:
+		debugC(1, kFreescapeDebugMedia, "Unknown Driller ZX sound %d", index);
+		break;
+	}
+
+	playSoundZX(&soundUnits);
 }
 
-void FreescapeEngine::playSoundZX(int index, Audio::SoundHandle &handle) {
-	playSoundZX(_soundsSpeakerFxZX[index], handle);
+Sound *FreescapeEngine::loadSpeakerFxDrillerZX() {
+	SoundZX *sound = new SoundDrillerZX(_mixer);
+	return sound;
 }
 
 } // namespace Freescape


Commit: a3b5da4831102f46c869fd41e9bebd06d57336d3
    https://github.com/scummvm/scummvm/commit/a3b5da4831102f46c869fd41e9bebd06d57336d3
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Use an enum to specify the sound type

Changed paths:
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/games/castle/castle.cpp
    engines/freescape/games/dark/dark.cpp
    engines/freescape/games/driller/driller.cpp
    engines/freescape/games/eclipse/dos.cpp
    engines/freescape/games/eclipse/eclipse.cpp
    engines/freescape/games/eclipse/eclipse.h
    engines/freescape/language/instruction.cpp
    engines/freescape/movement.cpp
    engines/freescape/sound.h
    engines/freescape/sound/common.cpp
    engines/freescape/sound/cpc.cpp
    engines/freescape/sound/dos.cpp
    engines/freescape/sound/fx.cpp
    engines/freescape/sound/fx_dos.cpp
    engines/freescape/sound/zx.cpp
    engines/freescape/ui.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index 3fa55aff246..81d5fb51276 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -982,7 +982,7 @@ void FreescapeEngine::processInput() {
 				_savedScreen = nullptr;
 				break;
 			case kActionChangeMode:
-				playSound(_soundIndexCollide, false, _movementSoundHandle);
+				playSound(_soundIndexCollide, false, Sound::kTypeMovement);
 				_shootMode = !_shootMode;
 				centerCrossair();
 				if (!_shootMode) {
@@ -1312,32 +1312,32 @@ bool FreescapeEngine::checkIfGameEnded() {
 		return false;
 
 	if (_gameStateVars[k8bitVariableShield] == 0) {
-		playSound(_soundIndexNoShield, true, _soundFxHandle);
+		playSound(_soundIndexNoShield, true);
 
 		if (!_noShieldMessage.empty())
 			insertTemporaryMessage(_noShieldMessage, _countdown - 2);
 		_gameStateControl = kFreescapeGameStateEnd;
 	} else if (_gameStateVars[k8bitVariableEnergy] == 0 && isDriller()) {
-		playSound(_soundIndexNoEnergy, true, _soundFxHandle);
+		playSound(_soundIndexNoEnergy, true);
 
 		if (!_noEnergyMessage.empty())
 			insertTemporaryMessage(_noEnergyMessage, _countdown - 2);
 		_gameStateControl = kFreescapeGameStateEnd;
 	} else if (_hasFallen) {
 		_hasFallen = false;
-		playSound(_soundIndexFallen, false, _soundFxHandle);
+		playSound(_soundIndexFallen, false);
 
 		if (!_fallenMessage.empty())
 			insertTemporaryMessage(_fallenMessage, _countdown - 4);
 		_gameStateControl = kFreescapeGameStateEnd;
 	} else if (_countdown <= 0) {
-		playSound(_soundIndexTimeout, false, _soundFxHandle);
+		playSound(_soundIndexTimeout, false);
 
 		if (!_timeoutMessage.empty())
 			insertTemporaryMessage(_timeoutMessage, _countdown - 4);
 		_gameStateControl = kFreescapeGameStateEnd;
 	} else if (_playerWasCrushed) {
-		playSound(_soundIndexCrushed, true, _soundFxHandle);
+		playSound(_soundIndexCrushed, true);
 
 		_playerWasCrushed = false;
 		if (!_crushedMessage.empty())
@@ -1347,7 +1347,7 @@ bool FreescapeEngine::checkIfGameEnded() {
 		// so no need to wait for the end of the game
 		_endGameDelayTicks = 0;
 	} else if (_forceEndGame) {
-		playSound(_soundIndexForceEndGame, true, _soundFxHandle);
+		playSound(_soundIndexForceEndGame, true);
 
 		_forceEndGame = false;
 		if (!_forceEndGameMessage.empty())
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 57fe1a7099c..912c355621f 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -512,21 +512,20 @@ public:
 	// Sound
 	Audio::SoundHandle _soundFxHandle;
 	Audio::SoundHandle _musicHandle;
-	Audio::SoundHandle _movementSoundHandle;
 	Sound *_sound;
 
 	bool _syncSound;
 	bool _firstSound;
 	bool _usePrerecordedSounds;
-	void waitForSounds();
-	void stopAllSounds(Audio::SoundHandle &handle);
-	bool isPlayingSound();
-	void playSound(int index, bool sync, Audio::SoundHandle &handle);
+	void waitForSounds(Sound::Type type = Sound::kTypeNormal);
+	void stopAllSounds(Sound::Type type = Sound::kTypeNormal);
+	bool isPlayingSound(Sound::Type type = Sound::kTypeNormal);
+	void playSound(int index, bool sync, Sound::Type type = Sound::kTypeNormal);
 	void playWav(const Common::Path &filename);
 	void playMusic(const Common::Path &filename);
 
 	virtual void playSoundC64(int index);
-	virtual void playSoundFx(int index, bool sync) {}
+	virtual void playSoundFx(int index, bool sync, Sound::Type type = Sound::kTypeNormal) {}
 	Sound *loadSoundsFx(Common::SeekableReadStream *file, int offset, int number);
 	Sound *loadSoundsFxDOS(Common::SeekableReadStream *file, int offset, int number);
 	Sound *loadSpeakerFxDOS(Common::SeekableReadStream *file, int offsetFreq, int offsetDuration, int numberSounds);
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index ee883423615..792d29d0db0 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -582,9 +582,9 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
 
 	if (areaID == _startArea && entranceID == _startEntrance) {
 		if (getGameBit(31))
-			playSound(13, true, _soundFxHandle);
+			playSound(13, true);
 		else
-			playSound(_soundIndexStart, false, _soundFxHandle);
+			playSound(_soundIndexStart, false);
 
 		// Start ProTracker background music for Amiga and Atari ST builds.
 		if ((isAmiga() || isAtariST()) && !_modData.empty() && !_mixer->isSoundHandleActive(_musicHandle)) {
@@ -598,9 +598,9 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
 	} else {
 		// If escaped, play a different sound
 		if (hasEscaped())
-			playSound(13, true, _soundFxHandle);
+			playSound(13, true);
 		else
-			playSound(_soundIndexAreaChange, true, _soundFxHandle);
+			playSound(_soundIndexAreaChange, true);
 	}
 
 	debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z());
@@ -730,7 +730,7 @@ bool CastleEngine::checkIfGameEnded() {
 	if (_gameStateControl == kFreescapeGameStatePlaying) {
 		if (_hasFallen && _avoidRenderingFrames == 0) {
 			_hasFallen = false;
-			playSound(_soundIndexFallen, false, _soundFxHandle);
+			playSound(_soundIndexFallen, false);
 
 			stopMovement();
 			// If shield is less than 11 after a fall, the game ends
@@ -1427,9 +1427,9 @@ void CastleEngine::drawFullscreenGameOverAndWait() {
 	_droppingGateStartTicks = _ticks;
 
 	if (isDOS()) {
-		// TODO: playSound(X, false, _soundFxHandle);
+		// TODO: playSound(X, false);
 	} else if (isSpectrum() || isCPC()) {
-		playSound(9, false, _soundFxHandle);
+		playSound(9, false);
 	}
 
 	if (!isDOS() && hasEscaped()) {
@@ -2486,7 +2486,7 @@ void CastleEngine::updateThunder() {
 
 		if (_thunderFrameDuration == 0)
 			if (isSpectrum() || isCPC() || isDOS())
-				playSound(8, false, _soundFxHandle);
+				playSound(8, false);
 		return;
 	}
 
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index c340f83c230..ba4d04a7132 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -568,8 +568,8 @@ bool DarkEngine::checkIfGameEnded() {
 		} else {
 			restoreECD(*_currentArea, index);
 			insertTemporaryMessage(_messagesList[1], _countdown - 2);
-			stopAllSounds(_movementSoundHandle);
-			playSound(_soundIndexRestoreECD, false, _soundFxHandle);
+			stopAllSounds(Sound::kTypeMovement);
+			playSound(_soundIndexRestoreECD, false);
 		}
 		_gameStateVars[kVariableDarkECD] = 0;
 
@@ -714,11 +714,11 @@ void DarkEngine::gotoArea(uint16 areaID, int entranceID) {
 	_gameStateVars[0x1f] = 0;
 
 	if (areaID == _startArea && entranceID == _startEntrance) {
-		playSound(_soundIndexStart, true, _soundFxHandle);
+		playSound(_soundIndexStart, true);
 	} else if (areaID == _endArea && entranceID == _endEntrance) {
 		_pitch = 10;
 	} else {
-		playSound(_soundIndexAreaChange, false, _soundFxHandle);
+		playSound(_soundIndexAreaChange, false);
 	}
 
 	debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z());
@@ -1020,7 +1020,7 @@ void DarkEngine::drawIndicator(Graphics::Surface *surface, int xPosition, int yP
 void DarkEngine::drawSensorShoot(Sensor *sensor) {
 	if (_gameStateControl == kFreescapeGameStatePlaying) {
 		// Avoid playing new sounds, so the endgame can progress
-		playSound(_soundIndexHit, true, _soundFxHandle);
+		playSound(_soundIndexHit, true);
 	}
 
 	Math::Vector3d target;
@@ -1113,7 +1113,7 @@ void DarkEngine::drawInfoMenu() {
 					toggleC64Sound();
 					_eventManager->purgeKeyboardEvents();
 				} else if (isDOS() && event.customType == kActionToggleSound) {
-					playSound(6, true, _soundFxHandle);
+					playSound(6, true);
 					_eventManager->purgeKeyboardEvents();
 				} else if (event.customType == kActionEscape) {
 					_forceEndGame = true;
diff --git a/engines/freescape/games/driller/driller.cpp b/engines/freescape/games/driller/driller.cpp
index 78b0837663a..b008f0f0841 100644
--- a/engines/freescape/games/driller/driller.cpp
+++ b/engines/freescape/games/driller/driller.cpp
@@ -264,9 +264,9 @@ void DrillerEngine::gotoArea(uint16 areaID, int entranceID) {
 		if (isC64()) {
 			if (!_c64UseSFX && _playerMusic)
 				_playerMusic->startMusic();
-			playSound(_soundIndexStart, true, _soundFxHandle);
+			playSound(_soundIndexStart, true);
 		} else {
-			playSound(_soundIndexStart, true, _soundFxHandle);
+			playSound(_soundIndexStart, true);
 			if (_playerMusic)
 				_playerMusic->startMusic();
 			else
@@ -280,7 +280,7 @@ void DrillerEngine::gotoArea(uint16 areaID, int entranceID) {
 		// Show the number of completed areas
 		_areaMap[127]->_name.replace(0, isAmiga() || isAtariST() ? 4 : 3, Common::String::format("%4d", _gameStateVars[32]));
 	} else
-		playSound(_soundIndexAreaChange, false, _soundFxHandle);
+		playSound(_soundIndexAreaChange, false);
 
 	debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z());
 	clearTemporalMessages();
@@ -601,7 +601,7 @@ void DrillerEngine::pressedKey(const int keycode) {
 		// RIG POSITIONED / NO GAS FOUND messages are displayed and
 		// before subsequent state changes can clobber the channel).
 		if (isDOS() || isAmiga() || isAtariST())
-			playSound(_soundIndexAreaChange, false, _soundFxHandle);
+			playSound(_soundIndexAreaChange, false);
 		insertTemporaryMessage(_messagesList[3], _countdown - 2);
 		addDrill(drill, success > 0);
 		if (success <= 0) {
@@ -678,7 +678,7 @@ void DrillerEngine::pressedKey(const int keycode) {
 		_drillSuccessByArea[areaID] = 0;
 		executeMovementConditions();
 		if (isDOS() || isAmiga() || isAtariST())
-			playSound(_soundIndexAreaChange, false, _soundFxHandle);
+			playSound(_soundIndexAreaChange, false);
 	}
 }
 
@@ -1005,7 +1005,7 @@ void DrillerEngine::endGame() {
 			// The original routine is blocking, so keep the redraw pacing in the
 			// engine's wait loop instead of stretching it across separate frames.
 			const int areaScale = MAX<int>(_currentArea ? _currentArea->getScale() : 1, 1);
-			playSound(11, false, _soundFxHandle);
+			playSound(11, false);
 			_position.z() -= 8000.0f / areaScale;
 			_position.y() += 3000.0f / areaScale;
 			_lastPosition = _position;
@@ -1077,7 +1077,7 @@ bool DrillerEngine::onScreenControls(Common::Point mouse) {
 void DrillerEngine::drawSensorShoot(Sensor *sensor) {
 	if (_underFireFrames == 1 && _gameStateControl == kFreescapeGameStatePlaying) {
 		// Avoid playing new sounds, so the endgame can progress
-		playSound(_soundIndexHit, true, _soundFxHandle);
+		playSound(_soundIndexHit, true);
 	}
 
 	Math::Vector3d sensorPos = sensor->getOrigin();
diff --git a/engines/freescape/games/eclipse/dos.cpp b/engines/freescape/games/eclipse/dos.cpp
index a2b27f46d41..03f64f3902b 100644
--- a/engines/freescape/games/eclipse/dos.cpp
+++ b/engines/freescape/games/eclipse/dos.cpp
@@ -250,11 +250,11 @@ void EclipseEngine::drawDOSUI(Graphics::Surface *surface) {
 	drawCompass(surface, 229, 177, _yaw, 10, black);
 }
 
-void EclipseEngine::playSoundFx(int index, bool sync) {
+void EclipseEngine::playSoundFx(int index, bool sync, Sound::Type type) {
 	if (_soundFx)
-		_soundFx->playSound(index);
+		_soundFx->playSound(index, type);
 	else
-		playSound(index, sync, _soundFxHandle);
+		playSound(index, sync, type);
 }
 
 
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index be3eef369e4..60cbe469bfe 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -131,11 +131,6 @@ EclipseEngine::EclipseEngine(OSystem *syst, const ADGameDescription *gd) : Frees
 	_soundFx = nullptr;
 }
 
-EclipseEngine::~EclipseEngine() {
-	if (_soundFx)
-		delete _soundFx;
-}
-
 void EclipseEngine::stopBackgroundMusic() {
 	if (_playerMusic)
 		_playerMusic->stopMusic();
@@ -171,6 +166,8 @@ EclipseEngine::~EclipseEngine() {
 		_compassBackground->free();
 		delete _compassBackground;
 	}
+	if (_soundFx)
+		delete _soundFx;
 	delete _playerMusic;
 	delete _playerC64Sfx;
 }
@@ -267,7 +264,7 @@ bool EclipseEngine::checkIfGameEnded() {
 			if (isDOS())
 				playSoundFx(4, false);
 			else
-				playSound(_soundIndexStartFalling, false, _soundFxHandle);
+				playSound(_soundIndexStartFalling, false);
 
 			stopMovement();
 			// If shield is less than 11 after a fall, the game ends
@@ -324,7 +321,7 @@ void EclipseEngine::endGame() {
 
 	if (_endGameKeyPressed && (_countdown == 0 || _countdown == -3600)) {
 		if (isSpectrum())
-			playSound(5, true, _soundFxHandle);
+			playSound(5, true);
 		_gameStateControl = kFreescapeGameStateRestart;
 	}
 	_endGameKeyPressed = false;
@@ -434,7 +431,7 @@ void EclipseEngine::gotoArea(uint16 areaID, int entranceID) {
 	if (areaID == _startArea && entranceID == _startEntrance) {
 		if (_pitch >= 180)
 			_pitch = 360 - _pitch;
-		playSound(_soundIndexStart, false, _soundFxHandle);
+		playSound(_soundIndexStart, false);
 		if (isEclipse2()) {
 			_gameStateControl = kFreescapeGameStateStart;
 			_pitch = -10;
@@ -447,7 +444,7 @@ void EclipseEngine::gotoArea(uint16 areaID, int entranceID) {
 		else
 			_pitch = 10;
 	} else {
-		playSound(_soundIndexAreaChange, false, _soundFxHandle);
+		playSound(_soundIndexAreaChange, false);
 	}
 
 	_gfx->_keyColor = 0;
@@ -616,7 +613,7 @@ void EclipseEngine::drawInfoMenu() {
 						toggleC64Sound();
 						_eventManager->purgeKeyboardEvents();
 					} else {
-						playSound(_soundIndexMenu, false, _soundFxHandle);
+						playSound(_soundIndexMenu, false);
 					}
 				} else if ((isDOS() || isCPC() || isSpectrum()) && event.customType == kActionEscape) {
 					_forceEndGame = true;
@@ -1020,7 +1017,7 @@ void EclipseEngine::drawHeartIndicator(Graphics::Surface *surface, int x, int y)
 		_lastHeartIndicatorFrame = frame;
 
 		if (!isPaused() && phase == beatStart && _lastHeartbeatSoundTick != _ticks) {
-			playSound(1, false, _soundFxHandle);
+			playSound(1, false);
 			_lastHeartbeatSoundTick = _ticks;
 		}
 	}
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index 64797e28644..835f745d964 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -180,7 +180,7 @@ public:
 	bool triggerWinCondition() override;
 	bool checkIfGameEnded() override;
 	void endGame() override;
-	void playSoundFx(int index, bool sync) override;
+	void playSoundFx(int index, bool sync, Sound::Type type = Sound::kTypeNormal) override;
 
 	Common::Error saveGameStreamExtended(Common::WriteStream *stream, bool isAutosave = false) override;
 	Common::Error loadGameStreamExtended(Common::SeekableReadStream *stream) override;
diff --git a/engines/freescape/language/instruction.cpp b/engines/freescape/language/instruction.cpp
index 4ecf47b01e9..0e65eba54e1 100644
--- a/engines/freescape/language/instruction.cpp
+++ b/engines/freescape/language/instruction.cpp
@@ -389,12 +389,12 @@ void FreescapeEngine::executeExecute(FCLInstruction &instruction) {
 }
 
 void FreescapeEngine::executeSound(FCLInstruction &instruction) {
-	stopAllSounds(_movementSoundHandle);
+	stopAllSounds(Sound::kTypeMovement);
 	_firstSound = false;
 	uint16 index = instruction._source;
 	bool sync = instruction._additional;
 	debugC(1, kFreescapeDebugCode, "Playing sound %d", index);
-	playSound(index, sync, _soundFxHandle);
+	playSound(index, sync);
 }
 
 void FreescapeEngine::executeDelay(FCLInstruction &instruction) {
diff --git a/engines/freescape/movement.cpp b/engines/freescape/movement.cpp
index 19d21e2f42f..def9cda67f4 100644
--- a/engines/freescape/movement.cpp
+++ b/engines/freescape/movement.cpp
@@ -308,7 +308,7 @@ void FreescapeEngine::shoot() {
 	if (_shootingFrames > 0) // No more than one shot at a time
 		return;
 
-	playSound(_soundIndexShoot, false, _movementSoundHandle);
+	playSound(_soundIndexShoot, false, Sound::kTypeMovement);
 	g_system->delayMillis(2);
 	_shootingFrames = 8;
 
@@ -608,7 +608,7 @@ void FreescapeEngine::resolveCollisions(Math::Vector3d const position) {
 		if ((lastPosition - newPosition).length() < 1) { // Something is blocking the player
 			if (!executed && !isCastle())
 				setGameBit(31);
-			playSound(_soundIndexCollide, false, _movementSoundHandle);
+			playSound(_soundIndexCollide, false, Sound::kTypeMovement);
 		}
 		_position = newPosition;
 		return;
@@ -657,7 +657,7 @@ void FreescapeEngine::resolveCollisions(Math::Vector3d const position) {
 		if (isEclipse()) // No need for an variable index, since these are special types of sound
 			playSoundFx(0, true);
 		else
-			playSound(_soundIndexFall, false, _movementSoundHandle);
+			playSound(_soundIndexFall, false, Sound::kTypeMovement);
 
 		if (_hasFallen)
 			stopMovement();
@@ -681,16 +681,16 @@ void FreescapeEngine::resolveCollisions(Math::Vector3d const position) {
 
 	if (isSteppingUp)  {
 		//debug("Stepping up sound!");
-		if (!_mixer->isSoundHandleActive(_movementSoundHandle))
-			playSound(_soundIndexStepUp, false, _movementSoundHandle);
+		if (!isPlayingSound(Sound::kTypeMovement))
+			playSound(_soundIndexStepUp, false, Sound::kTypeMovement);
 	} else if (isSteppingDown) {
 		//debug("Stepping down sound!");
-		if (!_mixer->isSoundHandleActive(_movementSoundHandle))
-			playSound(_soundIndexStepDown, false, _movementSoundHandle);
+		if (!isPlayingSound(Sound::kTypeMovement))
+			playSound(_soundIndexStepDown, false, Sound::kTypeMovement);
 	} else if (isCollidingWithWall) {
 		//debug("Colliding with wall sound!");
-		if (!_mixer->isSoundHandleActive(_movementSoundHandle))
-			playSound(_soundIndexCollide, false, _movementSoundHandle);
+		if (!isPlayingSound(Sound::kTypeMovement))
+			playSound(_soundIndexCollide, false, Sound::kTypeMovement);
 	}
 
 	_position = newPosition;
@@ -714,7 +714,7 @@ bool FreescapeEngine::runCollisionConditions(Math::Vector3d const lastPosition,
 	Object *collided = nullptr;
 	_gotoExecuted = false;
 
-	stopAllSounds(_movementSoundHandle);
+	stopAllSounds(Sound::kTypeMovement);
 
 	Math::Ray ray(newPosition, -_upVector);
 	collided = _currentArea->checkCollisionRay(ray, _playerHeight + 3);
diff --git a/engines/freescape/sound.h b/engines/freescape/sound.h
index c6b7a42073d..b9f2df3088f 100644
--- a/engines/freescape/sound.h
+++ b/engines/freescape/sound.h
@@ -43,11 +43,16 @@ public:
 
 class Sound {
 public:
+	enum Type {
+		kTypeNormal,
+		kTypeMovement
+	};
+
 	virtual ~Sound() {}
 
-	virtual void playSound(int index) = 0;
-	virtual void stopSound() = 0;
-	virtual bool isPlayingSound() const = 0;
+	virtual void playSound(int index, Type type) = 0;
+	virtual void stopSound(Type type) = 0;
+	virtual bool isPlayingSound(Type type) const = 0;
 };
 
 } // End of namespace Freescape
diff --git a/engines/freescape/sound/common.cpp b/engines/freescape/sound/common.cpp
index 4bdc22a3ec1..af86151c308 100644
--- a/engines/freescape/sound/common.cpp
+++ b/engines/freescape/sound/common.cpp
@@ -27,7 +27,7 @@
 
 namespace Freescape {
 
-void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle) {
+void FreescapeEngine::playSound(int index, bool sync, Sound::Type type) {
 	if (index < 0) {
 		debugC(1, kFreescapeDebugMedia, "Sound not specified");
 		return;
@@ -40,7 +40,7 @@ void FreescapeEngine::playSound(int index, bool sync, Audio::SoundHandle &handle
 
 	debugC(1, kFreescapeDebugMedia, "Playing sound %d with sync: %d", index, sync);
 	if (_sound) {
-		_sound->playSound(index);
+		_sound->playSound(index, type);
 		return;
 	}
 
@@ -80,22 +80,22 @@ void FreescapeEngine::playMusic(const Common::Path &filename) {
 	}
 }
 
-void FreescapeEngine::stopAllSounds(Audio::SoundHandle &handle) {
+void FreescapeEngine::stopAllSounds(Sound::Type type) {
 	debugC(1, kFreescapeDebugMedia, "Stopping sound");
 	if (_sound)
-		_sound->stopSound();
+		_sound->stopSound(type);
 	else
-		_mixer->stopHandle(handle);
+		_mixer->stopHandle(_soundFxHandle);
 }
 
-void FreescapeEngine::waitForSounds() {
-	while (isPlayingSound())
+void FreescapeEngine::waitForSounds(Sound::Type type) {
+	while (isPlayingSound(type))
 		waitInLoop(10);
 }
 
-bool FreescapeEngine::isPlayingSound() {
+bool FreescapeEngine::isPlayingSound(Sound::Type type) {
 	if (_sound)
-		return _sound->isPlayingSound();
+		return _sound->isPlayingSound(type);
 
 	if (_usePrerecordedSounds)
 		return _mixer->isSoundHandleActive(_soundFxHandle);
diff --git a/engines/freescape/sound/cpc.cpp b/engines/freescape/sound/cpc.cpp
index efc412f5a23..71e61884b3b 100644
--- a/engines/freescape/sound/cpc.cpp
+++ b/engines/freescape/sound/cpc.cpp
@@ -102,7 +102,7 @@ public:
 		file->read(_soundDefTable.data(), sizeSoundDef);
 	}
 
-	void playSound(int index) override {
+	void playSound(int index, Type type) override {
 		if (_soundDefTable.empty()) {
 			debugC(1, kFreescapeDebugMedia, "CPC sound tables not loaded");
 			return;
@@ -112,11 +112,11 @@ public:
 		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, toAudioStream(), -1, kFreescapeDefaultVolume, 0, DisposeAfterUse::NO);
 	}
 
-	void stopSound() override {
+	void stopSound(Type type) override {
 		_mixer->stopHandle(_soundFxHandle);
 	}
 
-	bool isPlayingSound() const override {
+	bool isPlayingSound(Type type) const override {
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 	}
 
diff --git a/engines/freescape/sound/dos.cpp b/engines/freescape/sound/dos.cpp
index adf4e6d7f4d..ddd0fba5bca 100644
--- a/engines/freescape/sound/dos.cpp
+++ b/engines/freescape/sound/dos.cpp
@@ -47,7 +47,7 @@ public:
 
 	void loadSpeakerFx(Common::SeekableReadStream *file, int offsetFreq, int offsetTable, int numberSounds);
 
-	void playSound(int index) override {
+	void playSound(int index, Type type) override {
 		soundSpeakerFx *speakerFxInfo = _soundsSpeakerFx[index];
 		if (speakerFxInfo)
 			playSoundDOS(speakerFxInfo);
@@ -55,11 +55,11 @@ public:
 			debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is not available", index);
 	}
 
-	void stopSound() override {
+	void stopSound(Type type) override {
 		_mixer->stopHandle(_soundFxHandle);
 	}
 
-	bool isPlayingSound() const override {
+	bool isPlayingSound(Type type) const override {
 		return !_speaker->endOfStream();
 	}
 
diff --git a/engines/freescape/sound/fx.cpp b/engines/freescape/sound/fx.cpp
index 4020499eb26..d8c164d98ad 100644
--- a/engines/freescape/sound/fx.cpp
+++ b/engines/freescape/sound/fx.cpp
@@ -67,7 +67,7 @@ public:
 		}
 	}
 
-	void playSound(int index) override {
+	void playSound(int index, Type type) override {
 		if (_soundsFx.size() == 0) {
 			debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded");
 			return;
@@ -91,11 +91,11 @@ public:
 			debugC(1, kFreescapeDebugMedia, "WARNING: Sound %d is empty", index);
 	}
 
-	void stopSound() override {
+	void stopSound(Type type) override {
 		_mixer->stopHandle(_soundFxHandle);
 	}
 
-	bool isPlayingSound() const override {
+	bool isPlayingSound(Type type) const override {
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 	}
 
diff --git a/engines/freescape/sound/fx_dos.cpp b/engines/freescape/sound/fx_dos.cpp
index 4f7f62597df..8fc9cd0d5a4 100644
--- a/engines/freescape/sound/fx_dos.cpp
+++ b/engines/freescape/sound/fx_dos.cpp
@@ -53,7 +53,7 @@ public:
 		}
 	}
 
-	void playSound(int index) override {
+	void playSound(int index, Type type) override {
 		if (_soundsFx.size() == 0) {
 			debugC(1, kFreescapeDebugMedia, "WARNING: Sounds are not loaded");
 			return;
@@ -67,11 +67,11 @@ public:
 		_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream, -1, kFreescapeDefaultVolume / 10);
 	}
 
-	void stopSound() override {
+	void stopSound(Type type) override {
 		_mixer->stopHandle(_soundFxHandle);
 	}
 
-	bool isPlayingSound() const override {
+	bool isPlayingSound(Type type) const override {
 		return _mixer->isSoundHandleActive(_soundFxHandle);
 	}
 
diff --git a/engines/freescape/sound/zx.cpp b/engines/freescape/sound/zx.cpp
index b3648376d5c..bf57552f5f2 100644
--- a/engines/freescape/sound/zx.cpp
+++ b/engines/freescape/sound/zx.cpp
@@ -45,15 +45,15 @@ public:
 
 	void loadSpeakerFx(Common::SeekableReadStream *file, int sfxTable, int sfxData, int numberSounds);
 
-	void playSound(int index) override {
+	void playSound(int index, Type type) override {
 		playSoundZX(_soundsSpeakerFxZX[index]);
 	}
 
-	void stopSound() override {
+	void stopSound(Type type) override {
 		_mixer->stopHandle(_soundFxHandle);
 	}
 
-	bool isPlayingSound() const override {
+	bool isPlayingSound(Type type) const override {
 		return !_speaker->endOfStream();
 	}
 
@@ -303,10 +303,10 @@ class SoundDrillerZX final : public SoundZX {
 public:
 	SoundDrillerZX(Audio::Mixer *mixer) : SoundZX(mixer) {}
 
-	void playSound(int index) override;
+	void playSound(int index, Type type) override;
 };
 
-void SoundDrillerZX::playSound(int index) {
+void SoundDrillerZX::playSound(int index, Type type) {
 	debugC(1, kFreescapeDebugMedia, "Playing Driller ZX sound %d", index);
 	Common::Array<soundUnitZX> soundUnits;
 
diff --git a/engines/freescape/ui.cpp b/engines/freescape/ui.cpp
index 283ea1512be..351c6a20eba 100644
--- a/engines/freescape/ui.cpp
+++ b/engines/freescape/ui.cpp
@@ -398,7 +398,7 @@ void FreescapeEngine::drawBorderScreenAndWait(Graphics::Surface *surface, int ma
 		delete compositedSurface;
 	}
 	pauseToken.clear();
-	playSound(_soundIndexMenu, false, _soundFxHandle);
+	playSound(_soundIndexMenu, false);
 	_gfx->clear(0, 0, 0, true);
 }
 


Commit: c25163dc836aab12ce9afef562cb0c3004e6df28
    https://github.com/scummvm/scummvm/commit/c25163dc836aab12ce9afef562cb0c3004e6df28
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Move Amiga sound code into a separate class

Changed paths:
    engines/freescape/freescape.h
    engines/freescape/games/castle/amiga.cpp
    engines/freescape/sound.h
    engines/freescape/sound/amiga.cpp


diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 912c355621f..236d399f830 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -532,11 +532,7 @@ public:
 	Sound *loadSpeakerFxZX(Common::SeekableReadStream *file, int sfxTable, int sfxData, int numberSounds);
 	Sound *loadSpeakerFxDrillerZX();
 	Sound *loadSoundsCPC(Common::SeekableReadStream *file, int offsetTone, int sizeTone, int offsetEnvelope, int sizeEnvelope, int offsetSoundDef, int sizeSoundDef);
-
-	void loadSoundsAmigaDemo(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset = 0x3D5A6);
-	void playSoundAmiga(int index, Audio::SoundHandle &handle);
-	Common::Array<AmigaSfxEntry> _amigaSfxTable;
-	Common::Array<AmigaDmaSample> _amigaDmaSamples;
+	Sound *loadSoundsAmigaDemo(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset);
 
 	int _soundIndexShoot;
 	int _soundIndexCollide;
diff --git a/engines/freescape/games/castle/amiga.cpp b/engines/freescape/games/castle/amiga.cpp
index 57011e8647b..64362e875cf 100644
--- a/engines/freescape/games/castle/amiga.cpp
+++ b/engines/freescape/games/castle/amiga.cpp
@@ -1404,7 +1404,7 @@ void CastleEngine::loadAssetsAmigaDemo() {
 
 	// Load synthesized sound effects from command table
 	// Table at file offset 0x1469E (memory 0x14682), 30 entries
-	loadSoundsAmigaDemo(&file, 0x1469E, 30);
+	_sound = loadSoundsAmigaDemo(&file, 0x1469E, 30, 0x3D5A6);
 
 	// Load embedded ProTracker module for background music
 	// Module is at file offset 0x3D5A6 (memory 0x3D58A), ~86260 bytes
@@ -1727,7 +1727,7 @@ void CastleEngine::loadAssetsAmigaFullGame() {
 	// Sound effects command table (30 entries). Pass the full-game MOD
 	// offset so DMA sample extraction reads from the right place — the
 	// demo's 0x3D5A6 hardcoded default is wrong for the full binary.
-	loadSoundsAmigaDemo(&file, 0x13cf2, 30, 0x3cbfa);
+	_sound = loadSoundsAmigaDemo(&file, 0x13cf2, 30, 0x3cbfa);
 
 	// Embedded ProTracker module for background music.
 	static const int kModOffset = 0x3cbfa;
diff --git a/engines/freescape/sound.h b/engines/freescape/sound.h
index b9f2df3088f..239b37588f3 100644
--- a/engines/freescape/sound.h
+++ b/engines/freescape/sound.h
@@ -26,15 +26,6 @@
 
 namespace Freescape {
 
-struct AmigaSfxEntry {
-	byte priority;
-	Common::Array<uint16> commands;
-};
-
-struct AmigaDmaSample {
-	Common::Array<int8> data;
-};
-
 // TODO: Migrate to Audio::PCSpeaker
 class SizedPCSpeaker : public Audio::PCSpeakerStream {
 public:
diff --git a/engines/freescape/sound/amiga.cpp b/engines/freescape/sound/amiga.cpp
index cd9db335d1c..463c8ecb556 100644
--- a/engines/freescape/sound/amiga.cpp
+++ b/engines/freescape/sound/amiga.cpp
@@ -29,6 +29,15 @@
 
 namespace Freescape {
 
+struct AmigaSfxEntry {
+	byte priority;
+	Common::Array<uint16> commands;
+};
+
+struct AmigaDmaSample {
+	Common::Array<int8> data;
+};
+
 /**
  * Amiga Sound Effect Synthesizer
  *
@@ -394,7 +403,31 @@ private:
 	}
 };
 
-void FreescapeEngine::loadSoundsAmigaDemo(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset) {
+class SoundAmigaDemo final : public Sound {
+public:
+	SoundAmigaDemo(Audio::Mixer *mixer) : _mixer(mixer) {}
+
+	void loadSounds(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset);
+
+	void playSound(int index, Type type) override;
+
+	void stopSound(Type type) override {
+		_mixer->stopHandle(_soundFxHandle);
+	}
+
+	bool isPlayingSound(Type type) const override {
+		return _mixer->isSoundHandleActive(_soundFxHandle);
+	}
+
+private:
+	Common::Array<AmigaSfxEntry> _amigaSfxTable;
+	Common::Array<AmigaDmaSample> _amigaDmaSamples;
+
+	Audio::Mixer *_mixer;
+	Audio::SoundHandle _soundFxHandle;
+};
+
+void SoundAmigaDemo::loadSounds(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset) {
 	file->seek(offset);
 	_amigaSfxTable.clear();
 	for (int i = 0; i < numSounds; i++) {
@@ -436,7 +469,7 @@ void FreescapeEngine::loadSoundsAmigaDemo(Common::SeekableReadStream *file, int
 	}
 }
 
-void FreescapeEngine::playSoundAmiga(int index, Audio::SoundHandle &handle) {
+void SoundAmigaDemo::playSound(int index, Type type) {
 	if (index < 0 || index >= (int)_amigaSfxTable.size()) {
 		debugC(1, kFreescapeDebugMedia, "Amiga sound %d out of range (have %d)", index, (int)_amigaSfxTable.size());
 		return;
@@ -452,9 +485,15 @@ void FreescapeEngine::playSoundAmiga(int index, Audio::SoundHandle &handle) {
 		index, entry.priority, (int)entry.commands.size());
 
 	AmigaSfxStream *stream = new AmigaSfxStream(entry.commands.data(), entry.commands.size(), &_amigaDmaSamples);
-	_mixer->stopHandle(handle);
-	_mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, stream, -1,
+	_mixer->stopHandle(_soundFxHandle);
+	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundFxHandle, stream, -1,
 		Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
 }
 
+Sound *FreescapeEngine::loadSoundsAmigaDemo(Common::SeekableReadStream *file, int offset, int numSounds, int modOffset) {
+	SoundAmigaDemo *sound = new SoundAmigaDemo(_mixer);
+	sound->loadSounds(file, offset, numSounds, modOffset);
+	return sound;
+}
+
 } // namespace Freescape


Commit: 3bb244c6ca91c316376fbb495647b311087fb0b2
    https://github.com/scummvm/scummvm/commit/3bb244c6ca91c316376fbb495647b311087fb0b2
Author: Cameron Cawley (ccawley2011 at gmail.com)
Date: 2026-06-23T16:17:18+02:00

Commit Message:
FREESCAPE: Change the C64 sound classes to inherit from the Sound class

Changed paths:
    engines/freescape/freescape.h
    engines/freescape/games/dark/c64.cpp
    engines/freescape/games/dark/c64.sfx.cpp
    engines/freescape/games/dark/c64.sfx.h
    engines/freescape/games/dark/dark.cpp
    engines/freescape/games/dark/dark.h
    engines/freescape/games/driller/c64.cpp
    engines/freescape/games/driller/c64.sfx.cpp
    engines/freescape/games/driller/c64.sfx.h
    engines/freescape/games/driller/driller.cpp
    engines/freescape/games/driller/driller.h
    engines/freescape/games/eclipse/c64.cpp
    engines/freescape/games/eclipse/c64.sfx.cpp
    engines/freescape/games/eclipse/c64.sfx.h
    engines/freescape/games/eclipse/eclipse.cpp
    engines/freescape/games/eclipse/eclipse.h
    engines/freescape/sound/common.cpp


diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 236d399f830..092ff619940 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -524,7 +524,6 @@ public:
 	void playWav(const Common::Path &filename);
 	void playMusic(const Common::Path &filename);
 
-	virtual void playSoundC64(int index);
 	virtual void playSoundFx(int index, bool sync, Sound::Type type = Sound::kTypeNormal) {}
 	Sound *loadSoundsFx(Common::SeekableReadStream *file, int offset, int number);
 	Sound *loadSoundsFxDOS(Common::SeekableReadStream *file, int offset, int number);
diff --git a/engines/freescape/games/dark/c64.cpp b/engines/freescape/games/dark/c64.cpp
index 656ce2ae407..67470fb8aea 100644
--- a/engines/freescape/games/dark/c64.cpp
+++ b/engines/freescape/games/dark/c64.cpp
@@ -353,14 +353,10 @@ void DarkEngine::loadAssetsC64FullGame() {
 	_playerMusic = new DarkSideC64MusicPlayer();
 }
 
-void DarkEngine::playSoundC64(int index) {
-	debugC(1, kFreescapeDebugMedia, "Playing Dark Side C64 SFX %d", index);
-	if (_playerC64Sfx && _c64UseSFX)
-		_playerC64Sfx->playSfx(index);
-}
-
 void DarkEngine::toggleC64Sound() {
 	if (_c64UseSFX) {
+		if (_sound == _playerC64Sfx)
+			_sound = nullptr;
 		if (_playerC64Sfx)
 			_playerC64Sfx->destroySID();
 		if (_playerMusic)
@@ -371,6 +367,8 @@ void DarkEngine::toggleC64Sound() {
 			_playerMusic->stopMusic();
 		if (_playerC64Sfx)
 			_playerC64Sfx->initSID();
+		if (_sound == nullptr)
+			_sound = _playerC64Sfx;
 		_c64UseSFX = true;
 	}
 }
diff --git a/engines/freescape/games/dark/c64.sfx.cpp b/engines/freescape/games/dark/c64.sfx.cpp
index 0be1ad19ef2..bdb66ad7a21 100644
--- a/engines/freescape/games/dark/c64.sfx.cpp
+++ b/engines/freescape/games/dark/c64.sfx.cpp
@@ -260,11 +260,11 @@ void DarkSideC64SFXPlayer::onTimer() {
 	sfxTick();
 }
 
-bool DarkSideC64SFXPlayer::isSfxActive() const {
+bool DarkSideC64SFXPlayer::isPlayingSound(Type type) const {
 	return _state != 0;
 }
 
-void DarkSideC64SFXPlayer::stopAllSfx() {
+void DarkSideC64SFXPlayer::stopSound(Type type) {
 	_state = 0;
 	silenceAll();
 }
@@ -340,7 +340,7 @@ void DarkSideC64SFXPlayer::setupSfx(int index) {
 	_state = 1;
 }
 
-void DarkSideC64SFXPlayer::playSfx(int sfxIndex) {
+void DarkSideC64SFXPlayer::playSound(int sfxIndex, Type type) {
 	// Guard: SFX indices are 1-based, table is 0-based
 	if (sfxIndex < 1 || sfxIndex > 25) {
 		debugC(1, kFreescapeDebugMedia, "Dark Side C64 SFX: invalid index %d", sfxIndex);
diff --git a/engines/freescape/games/dark/c64.sfx.h b/engines/freescape/games/dark/c64.sfx.h
index e57e1b42c7d..6ee77f86508 100644
--- a/engines/freescape/games/dark/c64.sfx.h
+++ b/engines/freescape/games/dark/c64.sfx.h
@@ -24,19 +24,21 @@
 
 #include "audio/sid.h"
 #include "freescape/sid.h"
+#include "freescape/sound.h"
 
 namespace Freescape {
 
-class DarkSideC64SFXPlayer {
+class DarkSideC64SFXPlayer : public Sound {
 public:
 	DarkSideC64SFXPlayer();
 	~DarkSideC64SFXPlayer();
 
-	void playSfx(int sfxIndex);
+	void playSound(int sfxIndex, Type type) override;
+	void stopSound(Type type) override;
+	bool isPlayingSound(Type type) const override;
+
 	void sfxTick();
-	void stopAllSfx();
 
-	bool isSfxActive() const;
 	void initSID();
 	void destroySID();
 
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index ba4d04a7132..1bb05238581 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -105,7 +105,8 @@ DarkEngine::DarkEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEn
 }
 
 DarkEngine::~DarkEngine() {
-	delete _playerC64Sfx;
+	if (_sound != _playerC64Sfx)
+		delete _playerC64Sfx;
 	delete _playerMusic;
 
 	for (auto &indicator : _cpcIndicators) {
diff --git a/engines/freescape/games/dark/dark.h b/engines/freescape/games/dark/dark.h
index e16193463ff..885d1e4ae36 100644
--- a/engines/freescape/games/dark/dark.h
+++ b/engines/freescape/games/dark/dark.h
@@ -149,7 +149,7 @@ public:
 	bool _c64CompassInitialized;
 	int _c64CompassPosition;
 	Common::Array<byte> _c64CompassTable;
-	void playSoundC64(int index) override;
+
 	void toggleC64Sound();
 
 	Common::Array<byte> _musicData; // HDSMUSIC.AM TEXT segment (Amiga)
diff --git a/engines/freescape/games/driller/c64.cpp b/engines/freescape/games/driller/c64.cpp
index c58787b70fc..d36fb058e68 100644
--- a/engines/freescape/games/driller/c64.cpp
+++ b/engines/freescape/games/driller/c64.cpp
@@ -185,14 +185,10 @@ void DrillerEngine::loadAssetsC64FullGame() {
 	_soundIndexMissionComplete = 14; // SFX #14 - 3-step chord
 }
 
-void DrillerEngine::playSoundC64(int index) {
-	debugC(1, kFreescapeDebugMedia, "Playing C64 SFX %d", index);
-	if (_playerC64Sfx && _c64UseSFX)
-		_playerC64Sfx->playSfx(index);
-}
-
 void DrillerEngine::toggleC64Sound() {
 	if (_c64UseSFX) {
+		if (_sound == _playerC64Sfx)
+			_sound = nullptr;
 		if (_playerC64Sfx)
 			_playerC64Sfx->destroySID();
 		if (_playerMusic)
@@ -203,6 +199,8 @@ void DrillerEngine::toggleC64Sound() {
 			_playerMusic->stopMusic();
 		if (_playerC64Sfx)
 			_playerC64Sfx->initSID();
+		if (_sound == nullptr)
+			_sound = _playerC64Sfx;
 		_c64UseSFX = true;
 	}
 }
diff --git a/engines/freescape/games/driller/c64.sfx.cpp b/engines/freescape/games/driller/c64.sfx.cpp
index 5a494e0f7e1..dc52265bbb1 100644
--- a/engines/freescape/games/driller/c64.sfx.cpp
+++ b/engines/freescape/games/driller/c64.sfx.cpp
@@ -78,11 +78,11 @@ void DrillerC64SFXPlayer::onTimer() {
 	sfxTick();
 }
 
-bool DrillerC64SFXPlayer::isSfxActive() const {
+bool DrillerC64SFXPlayer::isPlayingSound(Type type) const {
 	return (_v1Counter != 0xFF) || (_v3Counter != 0xFF) || (_noiseTimer != 0) || (_sfxPhase != 0);
 }
 
-void DrillerC64SFXPlayer::stopAllSfx() {
+void DrillerC64SFXPlayer::stopSound(Type type) {
 	_v1Counter = 0xFF;
 	_v3Counter = 0xFF;
 	_noiseTimer = 0;
@@ -279,7 +279,7 @@ void DrillerC64SFXPlayer::tickPhase() {
 
 // --- SFX dispatch ---
 
-void DrillerC64SFXPlayer::playSfx(int sfxIndex) {
+void DrillerC64SFXPlayer::playSound(int sfxIndex, Type type) {
 	debugC(1, kFreescapeDebugMedia, "DrillerC64SFX: Playing SFX %d", sfxIndex);
 
 	// Stop any ongoing SFX state before starting new one
diff --git a/engines/freescape/games/driller/c64.sfx.h b/engines/freescape/games/driller/c64.sfx.h
index de66d3aa485..0aeada25852 100644
--- a/engines/freescape/games/driller/c64.sfx.h
+++ b/engines/freescape/games/driller/c64.sfx.h
@@ -24,19 +24,21 @@
 
 #include "audio/sid.h"
 #include "freescape/sid.h"
+#include "freescape/sound.h"
 
 namespace Freescape {
 
-class DrillerC64SFXPlayer {
+class DrillerC64SFXPlayer : public Sound {
 public:
 	DrillerC64SFXPlayer();
 	~DrillerC64SFXPlayer();
 
-	void playSfx(int sfxIndex);
+	void playSound(int sfxIndex, Type type) override;
+	void stopSound(Type type) override;
+	bool isPlayingSound(Type type) const override;
+
 	void sfxTick();           // Called every frame (50Hz) from onTimer
-	void stopAllSfx();
 
-	bool isSfxActive() const;
 	void initSID();
 	void destroySID();
 
diff --git a/engines/freescape/games/driller/driller.cpp b/engines/freescape/games/driller/driller.cpp
index b008f0f0841..5b061e73995 100644
--- a/engines/freescape/games/driller/driller.cpp
+++ b/engines/freescape/games/driller/driller.cpp
@@ -100,7 +100,8 @@ DrillerEngine::DrillerEngine(OSystem *syst, const ADGameDescription *gd) : Frees
 
 DrillerEngine::~DrillerEngine() {
 	delete _playerMusic;
-	delete _playerC64Sfx;
+	if (_sound != _playerC64Sfx)
+		delete _playerC64Sfx;
 
 	if (_borderExtra) {
 		delete _borderExtra;
diff --git a/engines/freescape/games/driller/driller.h b/engines/freescape/games/driller/driller.h
index 96db7f66625..c7708995e87 100644
--- a/engines/freescape/games/driller/driller.h
+++ b/engines/freescape/games/driller/driller.h
@@ -50,7 +50,6 @@ public:
 	DrillerC64SFXPlayer *_playerC64Sfx;
 	bool _c64UseSFX;
 
-	void playSoundC64(int index) override;
 	void toggleC64Sound();
 
 	// Only used for Amiga and Atari ST
diff --git a/engines/freescape/games/eclipse/c64.cpp b/engines/freescape/games/eclipse/c64.cpp
index 657f5434c4f..a6ff892308f 100644
--- a/engines/freescape/games/eclipse/c64.cpp
+++ b/engines/freescape/games/eclipse/c64.cpp
@@ -152,14 +152,10 @@ void EclipseEngine::loadAssetsC64FullGame() {
 	_playerC64Sfx->destroySID();
 }
 
-void EclipseEngine::playSoundC64(int index) {
-	debugC(1, kFreescapeDebugMedia, "Playing Eclipse C64 SFX %d", index);
-	if (_playerC64Sfx && _c64UseSFX)
-		_playerC64Sfx->playSfx(index);
-}
-
 void EclipseEngine::toggleC64Sound() {
 	if (_c64UseSFX) {
+		if (_sound == _playerC64Sfx)
+			_sound = nullptr;
 		if (_playerC64Sfx)
 			_playerC64Sfx->destroySID();
 		if (_playerMusic)
@@ -170,6 +166,8 @@ void EclipseEngine::toggleC64Sound() {
 			_playerMusic->stopMusic();
 		if (_playerC64Sfx)
 			_playerC64Sfx->initSID();
+		if (_sound == nullptr)
+			_sound = _playerC64Sfx;
 		_c64UseSFX = true;
 	}
 }
diff --git a/engines/freescape/games/eclipse/c64.sfx.cpp b/engines/freescape/games/eclipse/c64.sfx.cpp
index 64a55e003cd..220ff89d837 100644
--- a/engines/freescape/games/eclipse/c64.sfx.cpp
+++ b/engines/freescape/games/eclipse/c64.sfx.cpp
@@ -236,11 +236,11 @@ void EclipseC64SFXPlayer::onTimer() {
 	sfxTick();
 }
 
-bool EclipseC64SFXPlayer::isSfxActive() const {
+bool EclipseC64SFXPlayer::isPlayingSound(Type type) const {
 	return _state != 0;
 }
 
-void EclipseC64SFXPlayer::stopAllSfx() {
+void EclipseC64SFXPlayer::stopSound(Type type) {
 	_state = 0;
 	silenceAll();
 }
@@ -296,7 +296,7 @@ void EclipseC64SFXPlayer::setupSfx(int index) {
 	_state = 1;
 }
 
-void EclipseC64SFXPlayer::playSfx(int sfxIndex) {
+void EclipseC64SFXPlayer::playSound(int sfxIndex, Type type) {
 	if (sfxIndex < 1 || sfxIndex > 21) {
 		debugC(1, kFreescapeDebugMedia, "Eclipse C64 SFX: invalid index %d", sfxIndex);
 		return;
diff --git a/engines/freescape/games/eclipse/c64.sfx.h b/engines/freescape/games/eclipse/c64.sfx.h
index 24cb414ed20..ee501fc6b7b 100644
--- a/engines/freescape/games/eclipse/c64.sfx.h
+++ b/engines/freescape/games/eclipse/c64.sfx.h
@@ -24,19 +24,21 @@
 
 #include "audio/sid.h"
 #include "freescape/sid.h"
+#include "freescape/sound.h"
 
 namespace Freescape {
 
-class EclipseC64SFXPlayer {
+class EclipseC64SFXPlayer : public Sound {
 public:
 	EclipseC64SFXPlayer();
 	~EclipseC64SFXPlayer();
 
-	void playSfx(int sfxIndex);
+	void playSound(int sfxIndex, Type type) override;
+	void stopSound(Type type) override;
+	bool isPlayingSound(Type type) const override;
+
 	void sfxTick();
-	void stopAllSfx();
 
-	bool isSfxActive() const;
 	void initSID();
 	void destroySID();
 
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index 60cbe469bfe..827e5f364e5 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -169,7 +169,8 @@ EclipseEngine::~EclipseEngine() {
 	if (_soundFx)
 		delete _soundFx;
 	delete _playerMusic;
-	delete _playerC64Sfx;
+	if (_sound != _playerC64Sfx)
+		delete _playerC64Sfx;
 }
 
 void EclipseEngine::initGameState() {
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index 835f745d964..eb275b6d243 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -122,7 +122,6 @@ public:
 	Common::Array<byte> _c64MusicData;
 	EclipseC64SFXPlayer *_playerC64Sfx;
 	bool _c64UseSFX;
-	void playSoundC64(int index) override;
 	void toggleC64Sound();
 
 	MusicPlayer *_playerMusic;
diff --git a/engines/freescape/sound/common.cpp b/engines/freescape/sound/common.cpp
index af86151c308..d9807025946 100644
--- a/engines/freescape/sound/common.cpp
+++ b/engines/freescape/sound/common.cpp
@@ -44,20 +44,12 @@ void FreescapeEngine::playSound(int index, bool sync, Sound::Type type) {
 		return;
 	}
 
-	if (isC64()) {
-		playSoundC64(index);
-		return;
-	}
-
 	Common::Path filename;
 	filename = Common::String::format("%s-%d.wav", _targetName.c_str(), index);
 	debugC(1,  kFreescapeDebugMedia, "Playing sound %s", filename.toString().c_str());
 	playWav(filename);
 	_syncSound = sync;
 }
-void FreescapeEngine::playSoundC64(int index) {
-	debugC(1, kFreescapeDebugMedia, "C64 sound %d not implemented for this engine", index);
-}
 
 void FreescapeEngine::playWav(const Common::Path &filename) {
 




More information about the Scummvm-git-logs mailing list