[Scummvm-git-logs] scummvm master -> 1c50d0abd6f997e514ca8f969f678c9f83c04d4c

bluegr noreply at scummvm.org
Mon Dec 23 00:36:41 UTC 2024


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

Summary:
1c50d0abd6 SCUMM: Split the CD audio functionality into a separate sound class


Commit: 1c50d0abd6f997e514ca8f969f678c9f83c04d4c
    https://github.com/scummvm/scummvm/commit/1c50d0abd6f997e514ca8f969f678c9f83c04d4c
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2024-12-23T02:36:06+02:00

Commit Message:
SCUMM: Split the CD audio functionality into a separate sound class

This helps to cleanly separate the CD audio logic from the main digital
audio functionality

Changed paths:
  A engines/scumm/soundcd.cpp
  A engines/scumm/soundcd.h
    engines/scumm/module.mk
    engines/scumm/players/player_towns.cpp
    engines/scumm/saveload.cpp
    engines/scumm/scumm.cpp
    engines/scumm/sound.cpp
    engines/scumm/sound.h


diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 46bc0a2019e..5c1edf0996e 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -84,6 +84,7 @@ MODULE_OBJS := \
 	script.o \
 	scumm.o \
 	sound.o \
+	soundcd.o \
 	soundse.o \
 	string.o \
 	usage_bits.o \
diff --git a/engines/scumm/players/player_towns.cpp b/engines/scumm/players/player_towns.cpp
index b5c232add22..20f475a786f 100644
--- a/engines/scumm/players/player_towns.cpp
+++ b/engines/scumm/players/player_towns.cpp
@@ -266,7 +266,6 @@ void Player_Towns_v1::stopSound(int sound) {
 	if (sound == 0 || sound == _cdaCurrentSound) {
 		_cdaCurrentSound = 0;
 		_vm->_sound->stopCD();
-		_vm->_sound->stopCDTimer();
 	}
 
 	if (sound != 0 && sound == _eupCurrentSound) {
@@ -281,7 +280,6 @@ void Player_Towns_v1::stopSound(int sound) {
 void Player_Towns_v1::stopAllSounds() {
 	_cdaCurrentSound = 0;
 	_vm->_sound->stopCD();
-	_vm->_sound->stopCDTimer();
 
 	_eupCurrentSound = 0;
 	_eupLooping = false;
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index c436d171ba5..3cabc0edc32 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -2044,20 +2044,7 @@ void ScummEngine::saveLoadWithSerializer(Common::Serializer &s) {
 		syncWithSerializer(s, info);
 
 		if (s.isLoading() && info.playing) {
-			if (info.numLoops < 0 && _game.platform != Common::kPlatformFMTowns) {
-				// If we are loading, and the music being loaded was supposed to loop
-				// forever, then resume playing it. This helps a lot when the audio CD
-				// is used to provide ambient music (see bug #1150).
-				// FM-Towns versions handle this in Player_Towns_v1::restoreAfterLoad().
-				_sound->playCDTrackInternal(info.track, info.numLoops, info.start, info.duration);
-			} else if (_game.id == GID_LOOM && info.start != 0 && info.duration != 0) {
-				// Reload audio for LOOM CD/Steam. We move the offset forward by a little bit
-				// to restore the correct sync.
-				int startOffset = (int)(VAR(VAR_MUSIC_TIMER) * 1.25);
-
-				_sound->_cdMusicTimer = VAR(VAR_MUSIC_TIMER);
-				_sound->playCDTrackInternal(info.track, info.numLoops, info.start + startOffset, info.duration - VAR(VAR_MUSIC_TIMER));
-			}
+			_sound->restoreCDAudioAfterLoad(info);
 		}
 	}
 
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index a3efc1adda9..dd51b67327a 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2887,21 +2887,7 @@ void ScummEngine::scummLoop(int delta) {
 
 	scummLoop_updateScummVars();
 
-	if (_game.features & GF_AUDIOTRACKS) {
-		VAR(VAR_MUSIC_TIMER) = _sound->getCDMusicTimer();
-	} else if (VAR_MUSIC_TIMER != 0xFF) {
-		if (_sound->useReplacementAudio() && _sound->getCurrentCDSound()) {
-			// The replacement music timer operates on real time, adjusted to
-			// the expected length of the Loom Overture (since there are so
-			// many different recordings of it). It's completely independent of
-			// the SCUMM engine's timer frequency.
-			_sound->updateMusicTimer();
-			VAR(VAR_MUSIC_TIMER) = _sound->getMusicTimer();
-		} else if (_musicEngine) {
-			// The music engine generates the timer data for us.
-			VAR(VAR_MUSIC_TIMER) = _musicEngine->getMusicTimer() * getTimerFrequency() / 240.0;
-		}
-	}
+	_sound->updateMusicTimer();
 
 	// Another v8 quirk: runAllScripts() is called here; after that we can
 	// finally restore the blastTexts/blastObject rects...
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index a7557d64a12..2fd230e80ad 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -20,13 +20,11 @@
  */
 
 #include "common/config-manager.h"
-#include "common/timer.h"
 #include "common/util.h"
 #include "common/ptr.h"
 #include "common/substream.h"
 
 #include "scumm/actor.h"
-#include "scumm/cdda.h"
 #include "scumm/file.h"
 #include "scumm/imuse_digi/dimuse_engine.h"
 #include "scumm/players/player_towns.h"
@@ -35,11 +33,9 @@
 #include "scumm/sound.h"
 
 #include "audio/audiostream.h"
-#include "audio/timestamp.h"
 #include "audio/decoders/flac.h"
 #include "audio/mididrv.h"
 #include "audio/mixer.h"
-#include "audio/decoders/adpcm.h"
 #include "audio/decoders/mp3.h"
 #include "audio/decoders/raw.h"
 #include "audio/decoders/voc.h"
@@ -59,15 +55,9 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer, bool useReplacementAudioT
 	:
 	_vm(parent),
 	_mixer(mixer),
-	_useReplacementAudioTracks(useReplacementAudioTracks),
-	_replacementTrackStartTime(0),
-	_musicTimer(0),
-	_cdMusicTimerMod(0),
-	_cdMusicTimer(0),
 	_speechTimerMod(0),
 	_midiQueuePos(0),
 	_soundQueuePos(0),
-	_sfxFilename(),
 	_sfxFileEncByte(0),
 	_offsetTable(nullptr),
 	_numSoundEffects(0),
@@ -81,7 +71,6 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer, bool useReplacementAudioT
 	_mouthSyncMode(false),
 	_endOfMouthSync(false),
 	_curSoundPos(0),
-	_currentCDSound(0),
 	_currentMusic(0),
 	_lastSound(0),
 	_soundsPaused(false),
@@ -93,26 +82,11 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer, bool useReplacementAudioT
 
 	_musicType = MDT_NONE;
 
-	_fileBasedCDStatus.playing = false;
-	_fileBasedCDStatus.track = 0;
-	_fileBasedCDStatus.start = 0;
-	_fileBasedCDStatus.duration = 0;
-	_fileBasedCDStatus.numLoops = 0;
-	_fileBasedCDStatus.volume = Audio::Mixer::kMaxChannelVolume;
-	_fileBasedCDStatus.balance = 0;
-
-	_hasFileBasedCDAudio = _vm->_game.id == GID_LOOM && Common::File::exists("CDDA.SOU");
-	_loomOvertureTransition = DEFAULT_LOOM_OVERTURE_TRANSITION + ConfMan.getInt("loom_overture_ticks");
-
-	_fileBasedCDAudioHandle = new Audio::SoundHandle();
 	_talkChannelHandle = new Audio::SoundHandle();
 
-	if (_vm->_game.features & GF_DOUBLEFINE_PAK) {
+	if (_vm->_game.features & GF_DOUBLEFINE_PAK)
 		_soundSE = new SoundSE(_vm, _mixer);
-
-		if (_vm->_game.id == GID_MONKEY)
-			_hasFileBasedCDAudio = true;
-	}
+	_soundCD = new SoundCD(_vm, _mixer, _soundSE, useReplacementAudioTracks);
 
 	_useRemasteredAudio = ConfMan.getBool("use_remastered_audio");
 
@@ -125,94 +99,15 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer, bool useReplacementAudioT
 }
 
 Sound::~Sound() {
-	stopCDTimer();
-	stopCD();
 	free(_offsetTable);
-	delete _fileBasedCDAudioHandle;
 	delete _talkChannelHandle;
 	delete _soundSE;
+	delete _soundCD;
 	if (_vm->_game.version >= 5 && _vm->_game.version <= 7 && _vm->_game.heversion == 0) {
 		stopSpeechTimer();
 	}
 }
 
-bool Sound::isRolandLoom() const {
-	return
-		(_vm->_game.id == GID_LOOM) &&
-		(_vm->_game.version == 3) &&
-		(_vm->_game.platform == Common::kPlatformDOS) &&
-		(_vm->VAR(_vm->VAR_SOUNDCARD) == 4);
-}
-
-#define JIFFIES_TO_TICKS(x) (40 * ((double)(x)) / _vm->getTimerFrequency())
-#define TICKS_TO_JIFFIES(x) ((double)(x) * (_vm->getTimerFrequency() / 40))
-
-#define TICKS_TO_TIMER(x) ((((x) * 204) / _loomOvertureTransition) + 1)
-#define TIMER_TO_TICKS(x) ((((x) - 1) * _loomOvertureTransition) / 204)
-
-void Sound::updateMusicTimer() {
-	bool isLoomOverture = (isRolandLoom() && _currentCDSound == 56 && !(_vm->_game.features & GF_DEMO));
-
-	// If the replacement track has ended, reset the timer to 0 like when
-	// playing the original music. We make an exception for the Overture,
-	// since it may need to keep running after the track has ended.
-	//
-	// This is also why we can't query the CD audio manager for the current
-	// position. That, and the fact that the CD manager does not provide
-	// this information at the time of writing.
-
-	if (!pollCD() && !isLoomOverture) {
-		_currentCDSound = 0;
-		_musicTimer = 0;
-		_replacementTrackStartTime = 0;
-		return;
-	}
-
-	// Time is measured in "ticks", with ten ticks per second. This should
-	// be exact enough, while providing an easily understandable unit of
-	// measurement for the adjustment slider.
-
-	// The rate at which the timer is advanced is hard-coded for the Loom
-	// Overture. When playing the original music the rate is apparently
-	// based on the MIDI tempo of it. But at least for Loom, the Overture
-	// seems to be the only piece of music where timing matters.
-
-	// These are the values the timer will have to reach or exceed for the
-	// Overture to work correctly:
-
-	// 4   - Fade in the "OVERTURE" text
-	// 198 - Fade down the "OVERTURE" text
-	// 204 - Show the LucasFilm logo
-	// 278 - End the Overture
-
-	// VAR_TOTAL_TIMER measures time in "jiffies", or frames. This will
-	// eventually overflow, but I don't expect that to ever be a problem.
-
-	int32 now = _vm->VAR(_vm->VAR_TIMER_TOTAL);
-
-	int32 ticks = JIFFIES_TO_TICKS(now - _replacementTrackStartTime);
-
-	// If the track ends before the timer reaches 198, skip ahead. (If the
-	// timer didn't even reach 4 you weren't really trying, and must be
-	// punished for that!)
-
-	if (isLoomOverture && !pollCD()) {
-		int32 fadeDownTick = TIMER_TO_TICKS(198);
-		if (ticks < fadeDownTick) {
-			_replacementTrackStartTime = now - TICKS_TO_JIFFIES(fadeDownTick);
-			ticks = fadeDownTick;
-		}
-	}
-
-	_musicTimer = TICKS_TO_TIMER(ticks);
-
-	// But don't let the timer exceed 278 until the Overture has ended, or
-	// the music will be cut off.
-
-	if (isLoomOverture && pollCD() && _musicTimer >= 278)
-		_musicTimer = 277;
-}
-
 void Sound::startSound(int sound, int offset, int channel, int flags, int freq, int pan, int volume) {
 	if (_vm->VAR_LAST_SOUND != 0xFF)
 		_vm->VAR(_vm->VAR_LAST_SOUND) = sound;
@@ -281,58 +176,6 @@ void Sound::processSoundQueues() {
 	_midiQueuePos = 0;
 }
 
-int Sound::getReplacementAudioTrack(int soundID) {
-	int trackNr = -1;
-
-	if (_vm->_game.id == GID_LOOM) {
-		if (_vm->_game.features & GF_DEMO) {
-			// If I understand correctly, the shorter demo only
-			// has the Loom intro music. The longer demo has a
-			// couple of tracks that it will cycle through if
-			// you leave the demo running.
-
-			if (isRolandLoom())
-				soundID -= 10;
-
-			switch (soundID) {
-			case 19:
-				trackNr = 2;
-				break;
-			case 20:
-				trackNr = 4;
-				break;
-			case 21:
-				trackNr = 7;
-				break;
-			case 23:
-				trackNr = 8;
-				break;
-			case 26:
-				trackNr = 3;
-				break;
-			}
-		} else {
-			if (isRolandLoom())
-				soundID -= 32;
-
-			// The first track, the Overture, only exists as a
-			// Roland track.
-			if (soundID >= 24 && soundID <= 32) {
-				trackNr = soundID - 23;
-			} else if (soundID == 19) {
-				trackNr = 10;
-			} else if (soundID == 21) {
-				trackNr = 11;
-			}
-		}
-	}
-
-	if (trackNr != -1 && !_vm->existExtractedCDAudioFiles(trackNr))
-		trackNr = -1;
-
-	return trackNr;
-}
-
 void Sound::triggerSound(int soundID) {
 	byte *ptr;
 	byte *sound;
@@ -340,50 +183,8 @@ void Sound::triggerSound(int soundID) {
 	int size = -1;
 	int rate;
 
-	if (_useReplacementAudioTracks) {
-		// Note that music does not loop. Probably because it's likely
-		// to be interrupted by sound effects before it's over anyway.
-		//
-		// In the FM Towns version, music does play continuously (each
-		// track has two versions), probably because CD audio and sound
-		// effects are played independent of each other. Personally I
-		// find the game harder when the music is allowed to drown out
-		// the sound effects.
-
-		int trackNr = getReplacementAudioTrack(soundID);
-		if (trackNr != -1) {
-			_currentCDSound = soundID;
-			_replacementTrackStartTime = _vm->VAR(_vm->VAR_TIMER_TOTAL);
-			_musicTimer = 0;
-			g_system->getAudioCDManager()->play(trackNr, 1, 0, 0, true);
-			return;
-		}
-	}
-
-	if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) {
-		if (soundID >= 13 && soundID <= 32) {
-			static const char tracks[20] = {3, 4, 5, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 19, 20, 21};
-
-			_currentCDSound = soundID;
-
-			// The original game had hard-coded lengths for all
-			// tracks, but this one track is the only one (as far
-			// as we know) where this actually matters. See bug
-			// #4914 - LOOM-PCE: Music stops prematurely.
-
-			int track = tracks[soundID - 13];
-			if (track == 6) {
-				playCDTrack(track, 1, 0, 260);
-			} else {
-				playCDTrack(track, 1, 0, 0);
-			}
-		} else {
-			if (_vm->_musicEngine) {
-				_vm->_musicEngine->startSound(soundID);
-			}
-		}
+	if (_soundCD->triggerCDSound(soundID))
 		return;
-	}
 
 	debugC(DEBUG_SOUND, "triggerSound #%d", soundID);
 
@@ -499,7 +300,7 @@ void Sound::triggerSound(int soundID) {
 		if (type == 2) {
 			// CD track resource
 			ptr += 0x16;
-			if (soundID == _currentCDSound && pollCD() == 1)
+			if (soundID == _soundCD->_currentCDSound && _soundCD->pollCD() == 1)
 				return;
 
 			int track = ptr[0];
@@ -514,8 +315,8 @@ void Sound::triggerSound(int soundID) {
 				start += ((75 * adjustment) / 100);
 			}
 
-			playCDTrack(track, loops == 0xff ? -1 : loops, start, end <= start ? 0 : end - start);
-			_currentCDSound = soundID;
+			_soundCD->playCDTrack(track, loops == 0xff ? -1 : loops, start, end <= start ? 0 : end - start);
+			_soundCD->_currentCDSound = soundID;
 		} else {
 			// All other sound types are ignored
 			warning("Scumm::Sound::triggerSound: encountered audio resource with chunk type 'SOUN' and sound type %d", type);
@@ -544,7 +345,7 @@ void Sound::triggerSound(int soundID) {
 			_vm->_musicEngine->startSound(soundID);
 
 		if (_vm->_townsPlayer)
-			_currentCDSound = _vm->_townsPlayer->getCurrentCdaSound();
+			_soundCD->_currentCDSound = _vm->_townsPlayer->getCurrentCdaSound();
 	}
 }
 
@@ -1022,8 +823,8 @@ int Sound::isSoundRunning(int sound) const {
 	}
 #endif
 
-	if (sound == _currentCDSound)
-		return pollCD();
+	if (sound == _soundCD->_currentCDSound)
+		return _soundCD->pollCD();
 
 	if (_mixer->isSoundIDActive(sound))
 		return 1;
@@ -1064,8 +865,8 @@ bool Sound::isSoundInUse(int sound) const {
 		return (_vm->_imuseDigital->isSoundRunning(sound) != 0);
 #endif
 
-	if (sound == _currentCDSound)
-		return pollCD() != 0;
+	if (sound == _soundCD->_currentCDSound)
+		return _soundCD->pollCD() != 0;
 
 	if (_mixer->isSoundIDActive(sound))
 		return true;
@@ -1109,13 +910,7 @@ bool Sound::isSoundInQueue(int sound) const {
 void Sound::stopSound(int sound) {
 	int i;
 
-	if (sound != 0 && sound == _currentCDSound) {
-		_currentCDSound = 0;
-		_musicTimer = 0;
-		_replacementTrackStartTime = 0;
-		stopCD();
-		stopCDTimer();
-	}
+	_soundCD->stopCDSound(sound);
 
 	if (_vm->_game.version < 7)
 		_mixer->stopID(sound);
@@ -1137,13 +932,7 @@ void Sound::stopSound(int sound) {
 }
 
 void Sound::stopAllSounds() {
-	if (_currentCDSound != 0) {
-		_currentCDSound = 0;
-		_musicTimer = 0;
-		_replacementTrackStartTime = 0;
-		stopCD();
-		stopCDTimer();
-	}
+	_soundCD->stopAllCDSounds();
 
 	// Clear the (secondary) sound queue
 	_lastSound = 0;
@@ -1208,7 +997,7 @@ void Sound::pauseSounds(bool pause) {
 	if (_vm->_imuse)
 		_vm->_imuse->pause(pause);
 
-	_soundsPaused = pause;
+	_soundsPaused = _soundCD->_soundsPaused = pause;
 
 #ifdef ENABLE_SCUMM_7_8
 	if (_vm->_imuseDigital) {
@@ -1218,12 +1007,7 @@ void Sound::pauseSounds(bool pause) {
 
 	_mixer->pauseAll(pause);
 
-	if ((_vm->_game.features & GF_AUDIOTRACKS) && _vm->VAR_MUSIC_TIMER != 0xFF && _vm->VAR(_vm->VAR_MUSIC_TIMER) > 0) {
-		if (pause)
-			stopCDTimer();
-		else
-			startCDTimer();
-	}
+	_soundCD->pauseCDSounds(pause);
 }
 
 bool Sound::isSfxFileCompressed() {
@@ -1413,194 +1197,13 @@ bool Sound::speechIsPlaying() {
 	return _mixer->isSoundHandleActive(*_talkChannelHandle);
 }
 
-static void cdTimerHandler(void *refCon) {
-	Sound *snd = (Sound *)refCon;
-
-	// FIXME: Turn off the timer when it's no longer needed. In theory, it
-	// should be possible to check with pollCD(), but since CD sound isn't
-	// properly restarted when reloading a saved game, I don't dare to.
-	if ((snd->_cdMusicTimerMod++ & 3) == 0) {
-		snd->_cdMusicTimer++;
-	}
-}
-
-void Sound::startCDTimer() {
-	if (_useReplacementAudioTracks)
-		return;
-
-	// This CD timer implementation strictly follows the original interpreters for
-	// Monkey Island 1 CD and Loom CD: it works by incrementing _cdMusicTimerMod and _cdMusicTimer
-	// at each quarter frame (see ScummEngine::setTimerAndShakeFrequency() for what the exact
-	// frequency rate is for the particular game and engine version being ran).
-	//
-	// Again as per the interpreters, VAR_MUSIC_TIMER is then updated inside the SCUMM main loop.
-	int32 interval = 1000000 / _vm->getTimerFrequency();
-
-	// LOOM Steam uses a fixed 240Hz rate. This was probably done to get rid of some
-	// audio glitches which are confirmed to be in the original. So let's activate this
-	// fix for the DOS version of LOOM as well, if enhancements are enabled.
-	if (_vm->_game.id == GID_LOOM && (_hasFileBasedCDAudio || _vm->enhancementEnabled(kEnhMinorBugFixes)))
-		interval = 1000000 / LOOM_STEAM_CDDA_RATE;
-
-	_vm->getTimerManager()->removeTimerProc(&cdTimerHandler);
-	_vm->getTimerManager()->installTimerProc(&cdTimerHandler, interval, this, "scummCDtimer");
-}
-
-void Sound::stopCDTimer() {
-	if (_useReplacementAudioTracks)
-		return;
-
-	_vm->getTimerManager()->removeTimerProc(&cdTimerHandler);
-}
-
-void Sound::playCDTrack(int track, int numLoops, int startFrame, int duration) {
-	// Reset the music timer variable at the start of a new track
-	_vm->VAR(_vm->VAR_MUSIC_TIMER) = 0;
-	_cdMusicTimerMod = 0;
-	_cdMusicTimer = 0;
-
-	// Play it
-	if (!_soundsPaused)
-		playCDTrackInternal(track, numLoops, startFrame, duration);
-
-	// Start the timer after starting the track. Starting an MP3 track is
-	// almost instantaneous, but a CD player may take some time. Hopefully
-	// playCD() will block during that delay.
-	startCDTimer();
-}
-
-void Sound::playCDTrackInternal(int track, int numLoops, int startFrame, int duration) {
-	_fileBasedCDStatus.track = track;
-	_fileBasedCDStatus.numLoops = numLoops;
-	_fileBasedCDStatus.start = startFrame;
-	_fileBasedCDStatus.duration = duration;
-
-	if (!_hasFileBasedCDAudio) {
-		g_system->getAudioCDManager()->play(track, numLoops, startFrame, duration);
-	} else {
-		// Stop any currently playing track
-		_mixer->stopHandle(*_fileBasedCDAudioHandle);
-
-		Audio::SeekableAudioStream *stream = nullptr;
-
-		if (_vm->_game.id == GID_LOOM) {
-			stream = makeCDDAStream("CDDA.SOU", DisposeAfterUse::YES);
-		} else if (_soundSE) {
-			stream = _soundSE->getXWBTrack(track);
-		}
-
-		if (!stream)
-			return;
-
-		Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
-		Audio::Timestamp end = Audio::Timestamp(0, startFrame + duration, 75);
-		
-		_mixer->playStream(Audio::Mixer::kMusicSoundType, _fileBasedCDAudioHandle,
-		                    Audio::makeLoopingAudioStream(stream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops));
-	}
-}
-
-void Sound::stopCD() {
-	if (!_hasFileBasedCDAudio)
-		g_system->getAudioCDManager()->stop();
-	else
-		_mixer->stopHandle(*_fileBasedCDAudioHandle);
-}
-
-int Sound::pollCD() const {
-	if (!_hasFileBasedCDAudio)
-		return g_system->getAudioCDManager()->isPlaying();
-	else
-		return _mixer->isSoundHandleActive(*_fileBasedCDAudioHandle);
-}
-
-void Sound::updateCD() {
-	if (!_hasFileBasedCDAudio)
-		g_system->getAudioCDManager()->update();
-}
-
-AudioCDManager::Status Sound::getCDStatus() {
-	if (!_hasFileBasedCDAudio)
-		return g_system->getAudioCDManager()->getStatus();
-	else {
-		AudioCDManager::Status info = _fileBasedCDStatus;
-		info.playing = _mixer->isSoundHandleActive(*_fileBasedCDAudioHandle);
-		return info;
-	}
-}
-
 void Sound::saveLoadWithSerializer(Common::Serializer &s) {
-	s.syncAsSint16LE(_currentCDSound, VER(35));
+	s.syncAsSint16LE(_soundCD->_currentCDSound, VER(35));
 	s.syncAsSint16LE(_currentMusic, VER(35));
 }
 
-int Sound::getCDTrackIdFromSoundId(int soundId, int &loops, int &start) {
-	if (_vm->_game.id == GID_LOOM && _vm->_game.version == 4) {
-		loops = 0;
-		start = -1;
-		return 1;
-	}
-
-	if (soundId != -1 && _vm->getResourceAddress(rtSound, soundId)) {
-		uint8 *ptr = _vm->getResourceAddress(rtSound, soundId) + 0x18;
-		loops = ptr[1];
-		start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
-		return ptr[0];
-	}
-
-	loops = 1;
-	return -1;
-}
-
 void Sound::restoreAfterLoad() {
-	_musicTimer = 0;
-	_replacementTrackStartTime = 0;
-	int trackNr = -1;
-	int loops = 1;
-	int start = 0;
-	if (_currentCDSound) {
-		if (_useReplacementAudioTracks) {
-			trackNr = getReplacementAudioTrack(_currentCDSound);
-		} else if (_vm->_game.platform != Common::kPlatformFMTowns) {
-			trackNr = getCDTrackIdFromSoundId(_currentCDSound, loops, start);
-		}
-
-		if (trackNr != -1) {
-			if (_useReplacementAudioTracks) {
-				int32 now = _vm->VAR(_vm->VAR_TIMER_TOTAL);
-				uint32 frame;
-
-				_musicTimer = _vm->VAR(_vm->VAR_MUSIC_TIMER);
-
-				// We try to resume the audio track from where it was
-				// saved. The timer isn't very accurate, but it should
-				// be good enough.
-				//
-				// NOTE: This does not seem to work at the moment, since
-				// the track immediately gets restarted in the cases I
-				// tried.
-
-				if (_musicTimer > 0) {
-					int32 ticks = TIMER_TO_TICKS(_musicTimer);
-
-					_replacementTrackStartTime = now - TICKS_TO_JIFFIES(ticks);
-					frame = (75 * ticks) / 10;
-				} else {
-					_replacementTrackStartTime = now;
-					frame = 0;
-				}
-
-				// If the user has fiddled with the Loom overture
-				// setting, the calculated position could be outside
-				// the track. But it seems a warning message is as bad
-				// as it gets.
-
-				g_system->getAudioCDManager()->play(trackNr, 1, frame, 0, true);
-			} else if (_vm->_game.platform != Common::kPlatformFMTowns) {
-				g_system->getAudioCDManager()->play(trackNr, loops, start + _vm->VAR(_vm->VAR_MUSIC_TIMER), 0, true);
-			}
-		}
-	}
+	_soundCD->restoreAfterLoad();
 }
 
 bool Sound::isAudioDisabled() {
@@ -2551,5 +2154,22 @@ int ScummEngine::readSoundResourceSmallHeader(ResId idx) {
 	return 0;
 }
 
+void Sound::updateMusicTimer() {
+	if (_vm->_game.features & GF_AUDIOTRACKS) {
+		_vm->VAR(_vm->VAR_MUSIC_TIMER) = _soundCD->getCDMusicTimer();
+	} else if (_vm->VAR_MUSIC_TIMER != 0xFF) {
+		if (_soundCD->useReplacementAudio() && _soundCD->getCurrentCDSound()) {
+			// The replacement music timer operates on real time, adjusted to
+			// the expected length of the Loom Overture (since there are so
+			// many different recordings of it). It's completely independent of
+			// the SCUMM engine's timer frequency.
+			_soundCD->updateMusicTimer();
+			_vm->VAR(_vm->VAR_MUSIC_TIMER) = _soundCD->getMusicTimer();
+		} else if (_vm->_musicEngine) {
+			// The music engine generates the timer data for us.
+			_vm->VAR(_vm->VAR_MUSIC_TIMER) = _vm->_musicEngine->getMusicTimer() * _vm->getTimerFrequency() / 240.0;
+		}
+	}
+}
 
 } // End of namespace Scumm
diff --git a/engines/scumm/sound.h b/engines/scumm/sound.h
index dfdf6da978f..65088bf913f 100644
--- a/engines/scumm/sound.h
+++ b/engines/scumm/sound.h
@@ -26,16 +26,10 @@
 #include "common/serializer.h"
 #include "common/str.h"
 #include "audio/mididrv.h"
-#include "backends/audiocd/audiocd.h"
 #include "scumm/file.h"
+#include "scumm/soundcd.h"
 #include "scumm/soundse.h"
 
-// The number of "ticks" (1/10th of a second) into the Overture that the
-// LucasFilm logo should appear. This corresponds to a timer value of 204.
-// The default value is selected to work well with the Ozawa recording.
-
-#define DEFAULT_LOOM_OVERTURE_TRANSITION 1160
-
 #define DIGI_SND_MODE_EMPTY  0
 #define DIGI_SND_MODE_SFX    1
 #define DIGI_SND_MODE_TALKIE 2
@@ -103,17 +97,9 @@ protected:
 	uint16 _mouthSyncTimes[64];
 	uint _curSoundPos;
 
-	int16 _currentCDSound;
-	int16 _currentMusic;
-
-	Audio::SoundHandle *_fileBasedCDAudioHandle;
-	bool _hasFileBasedCDAudio;
-	AudioCDManager::Status _fileBasedCDStatus;
-	bool _useReplacementAudioTracks;
-	int _musicTimer;
-	int _loomOvertureTransition;
-	uint32 _replacementTrackStartTime;
+	int16 _currentMusic;	// used by HE games
 
+	SoundCD *_soundCD = nullptr;
 	SoundSE *_soundSE = nullptr;
 	bool _useRemasteredAudio = false;
 
@@ -123,8 +109,6 @@ public:
 	bool _soundsPaused;
 	byte _digiSndMode;
 	uint _lastSound;
-	uint32 _cdMusicTimerMod;
-	uint32 _cdMusicTimer;
 	uint32 _speechTimerMod;
 
 	MidiDriverFlags _musicType;
@@ -159,28 +143,31 @@ public:
 	void stopSpeechTimer();
 	bool speechIsPlaying(); // Used within MIDI iMUSE
 
-	void startCDTimer();
-	void stopCDTimer();
-
-	void playCDTrack(int track, int numLoops, int startFrame, int duration);
-	void playCDTrackInternal(int track, int numLoops, int startFrame, int duration);
-	void stopCD();
-	int pollCD() const;
-	void updateCD();
-	AudioCDManager::Status getCDStatus();
-	int getCurrentCDSound() const { return _currentCDSound; }
-	int getCDTrackIdFromSoundId(int soundId, int &loops, int &start);
-	bool isRolandLoom() const;
-	bool useReplacementAudio() const { return _useReplacementAudioTracks; }
-	void updateMusicTimer();
-	int getMusicTimer() const { return _musicTimer; }
-	int getCDMusicTimer() const { return _cdMusicTimer; }
-
 	void saveLoadWithSerializer(Common::Serializer &ser) override;
 	void restoreAfterLoad();
 
 	bool isAudioDisabled();
 
+	void updateMusicTimer();
+
+	// TODO: Duplicate this in Sound as well?
+	bool isRolandLoom() const { return _soundCD->isRolandLoom(); }
+
+	// CD audio wrapper methods
+	int pollCD() const { return _soundCD->pollCD(); }
+	void updateCD() { _soundCD->updateCD(); }
+	void stopCD() {
+		_soundCD->stopCD();
+		_soundCD->stopCDTimer();
+	}
+	void playCDTrack(int track, int numLoops, int startFrame, int duration) {
+		_soundCD->playCDTrack(track, numLoops, startFrame, duration);
+	}
+	int getCurrentCDSound() const { return _soundCD->getCurrentCDSound(); }
+	void restoreCDAudioAfterLoad(AudioCDManager::Status &info) {
+		_soundCD->restoreCDAudioAfterLoad(info);
+	}
+
 protected:
 	void setupSfxFile();
 	bool isSfxFinished() const;
@@ -189,8 +176,6 @@ protected:
 	bool isSoundInQueue(int sound) const;
 
 	virtual void processSoundQueues();
-
-	int getReplacementAudioTrack(int soundID);
 };
 
 
diff --git a/engines/scumm/soundcd.cpp b/engines/scumm/soundcd.cpp
new file mode 100644
index 00000000000..4beabb4c42d
--- /dev/null
+++ b/engines/scumm/soundcd.cpp
@@ -0,0 +1,468 @@
+/* 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 "common/config-manager.h"
+#include "common/timer.h"
+#include "engines/engine.h"
+#include "scumm/cdda.h"
+#include "scumm/music.h"
+#include "scumm/scumm.h"
+#include "scumm/soundcd.h"
+#include "scumm/soundse.h"
+
+namespace Scumm {
+
+SoundCD::SoundCD(ScummEngine *parent, Audio::Mixer *mixer, SoundSE *soundSE, bool useReplacementAudioTracks):
+	_vm(parent), _mixer(mixer), _soundSE(soundSE), _useReplacementAudioTracks(useReplacementAudioTracks) {
+	_fileBasedCDStatus.playing = false;
+	_fileBasedCDStatus.track = 0;
+	_fileBasedCDStatus.start = 0;
+	_fileBasedCDStatus.duration = 0;
+	_fileBasedCDStatus.numLoops = 0;
+	_fileBasedCDStatus.volume = Audio::Mixer::kMaxChannelVolume;
+	_fileBasedCDStatus.balance = 0;
+
+	_hasFileBasedCDAudio =
+		(_vm->_game.id == GID_LOOM && Common::File::exists("CDDA.SOU")) ||
+		((_vm->_game.features & GF_DOUBLEFINE_PAK) && _vm->_game.id == GID_MONKEY);
+
+	_loomOvertureTransition = DEFAULT_LOOM_OVERTURE_TRANSITION + ConfMan.getInt("loom_overture_ticks");
+
+	_fileBasedCDAudioHandle = new Audio::SoundHandle();
+}
+
+SoundCD::~SoundCD() {
+	stopCDTimer();
+	stopCD();
+	delete _fileBasedCDAudioHandle;
+}
+
+void SoundCD::stopCDSound(int sound) {
+	if (sound != 0 && sound == _currentCDSound) {
+		_currentCDSound = 0;
+		_musicTimer = 0;
+		_replacementTrackStartTime = 0;
+		stopCD();
+		stopCDTimer();
+	}
+}
+
+void SoundCD::stopAllCDSounds() {
+	if (_currentCDSound != 0) {
+		_currentCDSound = 0;
+		_musicTimer = 0;
+		_replacementTrackStartTime = 0;
+		stopCD();
+		stopCDTimer();
+	}
+}
+
+static void cdTimerHandler(void *refCon) {
+	SoundCD *snd = (SoundCD *)refCon;
+
+	// FIXME: Turn off the timer when it's no longer needed. In theory, it
+	// should be possible to check with pollCD(), but since CD sound isn't
+	// properly restarted when reloading a saved game, I don't dare to.
+	if ((snd->_cdMusicTimerMod++ & 3) == 0) {
+		snd->_cdMusicTimer++;
+	}
+}
+
+void SoundCD::startCDTimer() {
+	if (_useReplacementAudioTracks)
+		return;
+
+	// This CD timer implementation strictly follows the original interpreters for
+	// Monkey Island 1 CD and Loom CD: it works by incrementing _cdMusicTimerMod and _cdMusicTimer
+	// at each quarter frame (see ScummEngine::setTimerAndShakeFrequency() for what the exact
+	// frequency rate is for the particular game and engine version being ran).
+	//
+	// Again as per the interpreters, VAR_MUSIC_TIMER is then updated inside the SCUMM main loop.
+	int32 interval = 1000000 / _vm->getTimerFrequency();
+
+	// LOOM Steam uses a fixed 240Hz rate. This was probably done to get rid of some
+	// audio glitches which are confirmed to be in the original. So let's activate this
+	// fix for the DOS version of LOOM as well, if enhancements are enabled.
+	if (_vm->_game.id == GID_LOOM && (_hasFileBasedCDAudio || _vm->enhancementEnabled(kEnhMinorBugFixes)))
+		interval = 1000000 / LOOM_STEAM_CDDA_RATE;
+
+	_vm->getTimerManager()->removeTimerProc(&cdTimerHandler);
+	_vm->getTimerManager()->installTimerProc(&cdTimerHandler, interval, this, "scummCDtimer");
+}
+
+void SoundCD::stopCDTimer() {
+	if (_useReplacementAudioTracks)
+		return;
+
+	_vm->getTimerManager()->removeTimerProc(&cdTimerHandler);
+}
+
+void SoundCD::playCDTrack(int track, int numLoops, int startFrame, int duration) {
+	// Reset the music timer variable at the start of a new track
+	_vm->VAR(_vm->VAR_MUSIC_TIMER) = 0;
+	_cdMusicTimerMod = 0;
+	_cdMusicTimer = 0;
+
+	// Play it
+	if (!_soundsPaused)
+		playCDTrackInternal(track, numLoops, startFrame, duration);
+
+	// Start the timer after starting the track. Starting an MP3 track is
+	// almost instantaneous, but a CD player may take some time. Hopefully
+	// playCD() will block during that delay.
+	startCDTimer();
+}
+
+void SoundCD::playCDTrackInternal(int track, int numLoops, int startFrame, int duration) {
+	_fileBasedCDStatus.track = track;
+	_fileBasedCDStatus.numLoops = numLoops;
+	_fileBasedCDStatus.start = startFrame;
+	_fileBasedCDStatus.duration = duration;
+
+	if (!_hasFileBasedCDAudio) {
+		g_system->getAudioCDManager()->play(track, numLoops, startFrame, duration);
+	} else {
+		// Stop any currently playing track
+		_mixer->stopHandle(*_fileBasedCDAudioHandle);
+
+		Audio::SeekableAudioStream *stream = nullptr;
+
+		if (_vm->_game.id == GID_LOOM) {
+			stream = makeCDDAStream("CDDA.SOU", DisposeAfterUse::YES);
+		} else if (_soundSE) {
+			stream = _soundSE->getXWBTrack(track);
+		}
+
+		if (!stream)
+			return;
+
+		Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
+		Audio::Timestamp end = Audio::Timestamp(0, startFrame + duration, 75);
+
+		_mixer->playStream(Audio::Mixer::kMusicSoundType, _fileBasedCDAudioHandle,
+						   Audio::makeLoopingAudioStream(stream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops));
+	}
+}
+
+void SoundCD::stopCD() {
+	if (!_hasFileBasedCDAudio)
+		g_system->getAudioCDManager()->stop();
+	else
+		_mixer->stopHandle(*_fileBasedCDAudioHandle);
+}
+
+int SoundCD::pollCD() const {
+	if (!_hasFileBasedCDAudio)
+		return g_system->getAudioCDManager()->isPlaying();
+	else
+		return _mixer->isSoundHandleActive(*_fileBasedCDAudioHandle);
+}
+
+void SoundCD::updateCD() {
+	if (!_hasFileBasedCDAudio)
+		g_system->getAudioCDManager()->update();
+}
+
+AudioCDManager::Status SoundCD::getCDStatus() {
+	if (!_hasFileBasedCDAudio)
+		return g_system->getAudioCDManager()->getStatus();
+	else {
+		AudioCDManager::Status info = _fileBasedCDStatus;
+		info.playing = _mixer->isSoundHandleActive(*_fileBasedCDAudioHandle);
+		return info;
+	}
+}
+
+int SoundCD::getCDTrackIdFromSoundId(int soundId, int &loops, int &start) {
+	if (_vm->_game.id == GID_LOOM && _vm->_game.version == 4) {
+		loops = 0;
+		start = -1;
+		return 1;
+	}
+
+	if (soundId != -1 && _vm->getResourceAddress(rtSound, soundId)) {
+		uint8 *ptr = _vm->getResourceAddress(rtSound, soundId) + 0x18;
+		loops = ptr[1];
+		start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
+		return ptr[0];
+	}
+
+	loops = 1;
+	return -1;
+}
+
+bool SoundCD::isRolandLoom() const {
+	return
+		(_vm->_game.id == GID_LOOM) &&
+		(_vm->_game.version == 3) &&
+		(_vm->_game.platform == Common::kPlatformDOS) &&
+		(_vm->VAR(_vm->VAR_SOUNDCARD) == 4);
+}
+
+#define JIFFIES_TO_TICKS(x) (40 * ((double)(x)) / _vm->getTimerFrequency())
+#define TICKS_TO_JIFFIES(x) ((double)(x) * (_vm->getTimerFrequency() / 40))
+
+#define TICKS_TO_TIMER(x) ((((x)*204) / _loomOvertureTransition) + 1)
+#define TIMER_TO_TICKS(x) ((((x)-1) * _loomOvertureTransition) / 204)
+
+void SoundCD::updateMusicTimer() {
+	bool isLoomOverture = (isRolandLoom() && _currentCDSound == 56 && !(_vm->_game.features & GF_DEMO));
+
+	// If the replacement track has ended, reset the timer to 0 like when
+	// playing the original music. We make an exception for the Overture,
+	// since it may need to keep running after the track has ended.
+	//
+	// This is also why we can't query the CD audio manager for the current
+	// position. That, and the fact that the CD manager does not provide
+	// this information at the time of writing.
+
+	if (!pollCD() && !isLoomOverture) {
+		_currentCDSound = 0;
+		_musicTimer = 0;
+		_replacementTrackStartTime = 0;
+		return;
+	}
+
+	// Time is measured in "ticks", with ten ticks per second. This should
+	// be exact enough, while providing an easily understandable unit of
+	// measurement for the adjustment slider.
+
+	// The rate at which the timer is advanced is hard-coded for the Loom
+	// Overture. When playing the original music the rate is apparently
+	// based on the MIDI tempo of it. But at least for Loom, the Overture
+	// seems to be the only piece of music where timing matters.
+
+	// These are the values the timer will have to reach or exceed for the
+	// Overture to work correctly:
+
+	// 4   - Fade in the "OVERTURE" text
+	// 198 - Fade down the "OVERTURE" text
+	// 204 - Show the LucasFilm logo
+	// 278 - End the Overture
+
+	// VAR_TOTAL_TIMER measures time in "jiffies", or frames. This will
+	// eventually overflow, but I don't expect that to ever be a problem.
+
+	int32 now = _vm->VAR(_vm->VAR_TIMER_TOTAL);
+
+	int32 ticks = JIFFIES_TO_TICKS(now - _replacementTrackStartTime);
+
+	// If the track ends before the timer reaches 198, skip ahead. (If the
+	// timer didn't even reach 4 you weren't really trying, and must be
+	// punished for that!)
+
+	if (isLoomOverture && !pollCD()) {
+		int32 fadeDownTick = TIMER_TO_TICKS(198);
+		if (ticks < fadeDownTick) {
+			_replacementTrackStartTime = now - TICKS_TO_JIFFIES(fadeDownTick);
+			ticks = fadeDownTick;
+		}
+	}
+
+	_musicTimer = TICKS_TO_TIMER(ticks);
+
+	// But don't let the timer exceed 278 until the Overture has ended, or
+	// the music will be cut off.
+
+	if (isLoomOverture && pollCD() && _musicTimer >= 278)
+		_musicTimer = 277;
+}
+
+void SoundCD::restoreAfterLoad() {
+	_musicTimer = 0;
+	_replacementTrackStartTime = 0;
+	int trackNr = -1;
+	int loops = 1;
+	int start = 0;
+	if (_currentCDSound) {
+		if (_useReplacementAudioTracks) {
+			trackNr = getReplacementAudioTrack(_currentCDSound);
+		} else if (_vm->_game.platform != Common::kPlatformFMTowns) {
+			trackNr = getCDTrackIdFromSoundId(_currentCDSound, loops, start);
+		}
+
+		if (trackNr != -1) {
+			if (_useReplacementAudioTracks) {
+				int32 now = _vm->VAR(_vm->VAR_TIMER_TOTAL);
+				uint32 frame;
+
+				_musicTimer = _vm->VAR(_vm->VAR_MUSIC_TIMER);
+
+				// We try to resume the audio track from where it was
+				// saved. The timer isn't very accurate, but it should
+				// be good enough.
+				//
+				// NOTE: This does not seem to work at the moment, since
+				// the track immediately gets restarted in the cases I
+				// tried.
+
+				if (_musicTimer > 0) {
+					int32 ticks = TIMER_TO_TICKS(_musicTimer);
+
+					_replacementTrackStartTime = now - TICKS_TO_JIFFIES(ticks);
+					frame = (75 * ticks) / 10;
+				} else {
+					_replacementTrackStartTime = now;
+					frame = 0;
+				}
+
+				// If the user has fiddled with the Loom overture
+				// setting, the calculated position could be outside
+				// the track. But it seems a warning message is as bad
+				// as it gets.
+
+				g_system->getAudioCDManager()->play(trackNr, 1, frame, 0, true);
+			} else if (_vm->_game.platform != Common::kPlatformFMTowns) {
+				g_system->getAudioCDManager()->play(trackNr, loops, start + _vm->VAR(_vm->VAR_MUSIC_TIMER), 0, true);
+			}
+		}
+	}
+}
+
+void SoundCD::restoreCDAudioAfterLoad(AudioCDManager::Status &info) {
+	if (info.numLoops < 0 && _vm->_game.platform != Common::kPlatformFMTowns) {
+		// If we are loading, and the music being loaded was supposed to loop
+		// forever, then resume playing it. This helps a lot when the audio CD
+		// is used to provide ambient music (see bug #1150).
+		// FM-Towns versions handle this in Player_Towns_v1::restoreAfterLoad().
+		playCDTrackInternal(info.track, info.numLoops, info.start, info.duration);
+	} else if (_vm->_game.id == GID_LOOM && info.start != 0 && info.duration != 0) {
+		// Reload audio for LOOM CD/Steam. We move the offset forward by a little bit
+		// to restore the correct sync.
+		int startOffset = (int)(_vm->VAR(_vm->VAR_MUSIC_TIMER) * 1.25);
+
+		_cdMusicTimer = _vm->VAR(_vm->VAR_MUSIC_TIMER);
+		playCDTrackInternal(info.track, info.numLoops, info.start + startOffset, info.duration - _vm->VAR(_vm->VAR_MUSIC_TIMER));
+	}
+}
+
+bool SoundCD::triggerCDSound(int soundID) {
+	if (_useReplacementAudioTracks) {
+		// Note that music does not loop. Probably because it's likely
+		// to be interrupted by sound effects before it's over anyway.
+		//
+		// In the FM Towns version, music does play continuously (each
+		// track has two versions), probably because CD audio and sound
+		// effects are played independent of each other. Personally I
+		// find the game harder when the music is allowed to drown out
+		// the sound effects.
+
+		int trackNr = getReplacementAudioTrack(soundID);
+		if (trackNr != -1) {
+			_currentCDSound = soundID;
+			_replacementTrackStartTime = _vm->VAR(_vm->VAR_TIMER_TOTAL);
+			_musicTimer = 0;
+			g_system->getAudioCDManager()->play(trackNr, 1, 0, 0, true);
+			return true;
+		}
+	}
+
+	if (_vm->_game.id == GID_LOOM && _vm->_game.platform == Common::kPlatformPCEngine) {
+		if (soundID >= 13 && soundID <= 32) {
+			static const char tracks[20] = {3, 4, 5, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 19, 20, 21};
+
+			_currentCDSound = soundID;
+
+			// The original game had hard-coded lengths for all
+			// tracks, but this one track is the only one (as far
+			// as we know) where this actually matters. See bug
+			// #4914 - LOOM-PCE: Music stops prematurely.
+
+			int track = tracks[soundID - 13];
+			if (track == 6) {
+				playCDTrack(track, 1, 0, 260);
+			} else {
+				playCDTrack(track, 1, 0, 0);
+			}
+		} else {
+			if (_vm->_musicEngine) {
+				_vm->_musicEngine->startSound(soundID);
+			}
+		}
+		return true;
+	}
+
+	return false;
+}
+
+int SoundCD::getReplacementAudioTrack(int soundID) {
+	int trackNr = -1;
+
+	if (_vm->_game.id == GID_LOOM) {
+		if (_vm->_game.features & GF_DEMO) {
+			// If I understand correctly, the shorter demo only
+			// has the Loom intro music. The longer demo has a
+			// couple of tracks that it will cycle through if
+			// you leave the demo running.
+
+			if (isRolandLoom())
+				soundID -= 10;
+
+			switch (soundID) {
+			case 19:
+				trackNr = 2;
+				break;
+			case 20:
+				trackNr = 4;
+				break;
+			case 21:
+				trackNr = 7;
+				break;
+			case 23:
+				trackNr = 8;
+				break;
+			case 26:
+				trackNr = 3;
+				break;
+			}
+		} else {
+			if (isRolandLoom())
+				soundID -= 32;
+
+			// The first track, the Overture, only exists as a
+			// Roland track.
+			if (soundID >= 24 && soundID <= 32) {
+				trackNr = soundID - 23;
+			} else if (soundID == 19) {
+				trackNr = 10;
+			} else if (soundID == 21) {
+				trackNr = 11;
+			}
+		}
+	}
+
+	if (trackNr != -1 && !_vm->existExtractedCDAudioFiles(trackNr))
+		trackNr = -1;
+
+	return trackNr;
+}
+
+void SoundCD::pauseCDSounds(bool pause) {
+	if ((_vm->_game.features & GF_AUDIOTRACKS) && _vm->VAR_MUSIC_TIMER != 0xFF && _vm->VAR(_vm->VAR_MUSIC_TIMER) > 0) {
+		if (pause)
+			stopCDTimer();
+		else
+			startCDTimer();
+	}
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/soundcd.h b/engines/scumm/soundcd.h
new file mode 100644
index 00000000000..eb55c5a750c
--- /dev/null
+++ b/engines/scumm/soundcd.h
@@ -0,0 +1,98 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SCUMM_SOUNDCD_H
+#define SCUMM_SOUNDCD_H
+
+#include "backends/audiocd/audiocd.h"
+#include "common/scummsys.h"
+#include "common/serializer.h"
+
+namespace Scumm {
+
+// The number of "ticks" (1/10th of a second) into the Overture that the
+// LucasFilm logo should appear. This corresponds to a timer value of 204.
+// The default value is selected to work well with the Ozawa recording.
+
+#define DEFAULT_LOOM_OVERTURE_TRANSITION 1160
+
+class ScummEngine;
+class SoundSE;
+
+class SoundCD {
+private:
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	SoundSE *_soundSE;
+
+	Audio::SoundHandle *_fileBasedCDAudioHandle;
+	bool _hasFileBasedCDAudio = false;
+	AudioCDManager::Status _fileBasedCDStatus;
+
+	int _musicTimer = 0;
+
+	bool _useReplacementAudioTracks = false;
+	int _loomOvertureTransition = 0;
+	uint32 _replacementTrackStartTime = 0;
+
+public:
+	int16 _currentCDSound = 0;
+	uint32 _cdMusicTimerMod = 0;
+	uint32 _cdMusicTimer = 0;
+	bool _soundsPaused = false;
+
+	SoundCD(ScummEngine *parent, Audio::Mixer *mixer, SoundSE *soundSE, bool useReplacementAudioTracks);
+	~SoundCD();
+
+	bool triggerCDSound(int soundID);
+	void stopCDSound(int sound);
+	void stopAllCDSounds();
+	void pauseCDSounds(bool pause);
+
+	bool useReplacementAudio() const { return _useReplacementAudioTracks; }
+	void updateMusicTimer();
+	int getMusicTimer() const { return _musicTimer; }
+	int getCDMusicTimer() const { return _cdMusicTimer; }
+
+	void startCDTimer();
+	void stopCDTimer();
+
+	void playCDTrack(int track, int numLoops, int startFrame, int duration);
+	void stopCD();
+	int pollCD() const;
+	void updateCD();
+	AudioCDManager::Status getCDStatus();
+	int getCurrentCDSound() const { return _currentCDSound; }
+
+	void restoreAfterLoad();
+	void restoreCDAudioAfterLoad(AudioCDManager::Status &info);
+
+	bool isRolandLoom() const;
+
+private:
+	int getReplacementAudioTrack(int soundID);
+	void playCDTrackInternal(int track, int numLoops, int startFrame, int duration);
+	int getCDTrackIdFromSoundId(int soundId, int &loops, int &start);
+};
+
+} // End of namespace Scumm
+
+#endif




More information about the Scummvm-git-logs mailing list