SCUMM: Add support for CD audio tracks in the Steam versions of Loom

Many Thanks to Ben Castricum for the original patch
This commit is contained in:
Filippos Karapetis 2014-07-03 00:14:28 +03:00
parent 902a140f3e
commit 53d3ee07df
6 changed files with 246 additions and 8 deletions

120
engines/scumm/cdda.cpp Normal file
View file

@ -0,0 +1,120 @@
/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "scumm/cdda.h"
#include "common/stream.h"
#include "audio/audiostream.h"
namespace Scumm {
#pragma mark -
#pragma mark --- CDDA stream ---
#pragma mark -
#define START_OF_CDDA_DATA 800
#define BLOCK_SIZE 1177
class CDDAStream : public Audio::SeekableAudioStream {
private:
Common::SeekableReadStream *_stream;
DisposeAfterUse::Flag _disposeAfterUse;
byte _shiftLeft;
byte _shiftRight;
uint32 _pos;
Audio::Timestamp _length;
public:
CDDAStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
virtual ~CDDAStream();
int readBuffer(int16 *buffer, const int numSamples);
bool isStereo() const { return true; }
int getRate() const { return 44100; }
bool endOfData() const { return _stream->eos(); }
bool seek(const Audio::Timestamp &where);
Audio::Timestamp getLength() const { return _length; }
};
CDDAStream::CDDAStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) :
_stream(stream), _disposeAfterUse(disposeAfterUse), _pos(START_OF_CDDA_DATA) {
_stream->seek(START_OF_CDDA_DATA, SEEK_SET);
// The total size of CDDA.SOU is 289,808,802 bytes or (289808802 - 800) / 1177 = 246226 blocks
// We also deduct the shift values to return the correct length
uint32 blocks = (_stream->size() - START_OF_CDDA_DATA) / BLOCK_SIZE;
_length = Audio::Timestamp(0, (_stream->size() - START_OF_CDDA_DATA - blocks) / (isStereo() ? 2 : 1), getRate());
}
CDDAStream::~CDDAStream() {
if (_disposeAfterUse == DisposeAfterUse::YES)
delete _stream;
}
bool CDDAStream::seek(const Audio::Timestamp &where) {
const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
uint32 blocks = seekSample / 1176;
// Before seeking, read the shift values from the beginning of that block
_stream->seek(START_OF_CDDA_DATA + blocks * BLOCK_SIZE, SEEK_SET);
byte shiftVal = _stream->readByte();
_shiftLeft = shiftVal >> 4;
_shiftRight = shiftVal & 0x0F;
_pos = START_OF_CDDA_DATA + blocks + seekSample;
return _stream->seek(_pos, SEEK_SET);
}
int CDDAStream::readBuffer(int16 *buffer, const int numSamples) {
int samples;
for (samples = 0 ; samples < numSamples && !_stream->eos() ; ) {
if (!((_pos - START_OF_CDDA_DATA) % BLOCK_SIZE)) {
byte shiftVal = _stream->readByte();
_shiftLeft = shiftVal >> 4;
_shiftRight = shiftVal & 0x0F;
_pos++;
}
buffer[samples++] = _stream->readSByte() << _shiftLeft;
buffer[samples++] = _stream->readSByte() << _shiftRight;
_pos += 2;
}
return samples;
}
#pragma mark -
#pragma mark --- CDDA factory functions ---
#pragma mark -
Audio::SeekableAudioStream *makeCDDAStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse) {
Audio::SeekableAudioStream *s = new CDDAStream(stream, disposeAfterUse);
if (s && s->endOfData()) {
delete s;
return 0;
} else {
return s;
}
return 0;
}
} // End of namespace Scumm

58
engines/scumm/cdda.h Normal file
View file

@ -0,0 +1,58 @@
/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/**
* @file
* CD audio decoder used in the Steam versions of Loom
*/
#ifndef SCUMM_CDDA_H
#define SCUMM_CDDA_H
#include "common/types.h"
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class SeekableAudioStream;
}
namespace Scumm {
/**
* Create a new SeekableAudioStream from the CDDA data in the given stream.
* Allows for seeking (which is why we require a SeekableReadStream).
*
* @param stream the SeekableReadStream from which to read the CDDA data
* @param disposeAfterUse whether to delete the stream after use
* @return a new SeekableAudioStream, or NULL, if an error occurred
*/
Audio::SeekableAudioStream *makeCDDAStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse);
} // End of namespace Audio
#endif // #ifndef SCUMM_CDDA_H

View file

@ -7,6 +7,7 @@ MODULE_OBJS := \
bomp.o \ bomp.o \
boxes.o \ boxes.o \
camera.o \ camera.o \
cdda.o \
charset.o \ charset.o \
charset-fontdata.o \ charset-fontdata.o \
costume.o \ costume.o \

View file

@ -1453,7 +1453,7 @@ void ScummEngine::saveOrLoad(Serializer *s) {
// forever, then resume playing it. This helps a lot when the audio CD // forever, then resume playing it. This helps a lot when the audio CD
// is used to provide ambient music (see bug #788195). // is used to provide ambient music (see bug #788195).
if (s->isLoading() && info.playing && info.numLoops < 0) if (s->isLoading() && info.playing && info.numLoops < 0)
_system->getAudioCDManager()->play(info.track, info.numLoops, info.start, info.duration); _sound->playCDTrackInternal(info.track, info.numLoops, info.start, info.duration);
} }

View file

@ -27,6 +27,7 @@
#include "common/substream.h" #include "common/substream.h"
#include "scumm/actor.h" #include "scumm/actor.h"
#include "scumm/cdda.h"
#include "scumm/file.h" #include "scumm/file.h"
#include "scumm/imuse/imuse.h" #include "scumm/imuse/imuse.h"
#include "scumm/imuse_digi/dimuse.h" #include "scumm/imuse_digi/dimuse.h"
@ -36,8 +37,6 @@
#include "scumm/sound.h" #include "scumm/sound.h"
#include "scumm/util.h" #include "scumm/util.h"
#include "backends/audiocd/audiocd.h"
#include "audio/decoders/adpcm.h" #include "audio/decoders/adpcm.h"
#include "audio/decoders/flac.h" #include "audio/decoders/flac.h"
#include "audio/mididrv.h" #include "audio/mididrv.h"
@ -89,11 +88,21 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer)
memset(_mouthSyncTimes, 0, sizeof(_mouthSyncTimes)); memset(_mouthSyncTimes, 0, sizeof(_mouthSyncTimes));
_musicType = MDT_NONE; _musicType = MDT_NONE;
_loomSteamCD.playing = false;
_loomSteamCD.track = 0;
_loomSteamCD.start = 0;
_loomSteamCD.duration = 0;
_loomSteamCD.numLoops = 0;
_loomSteamCD.volume = Audio::Mixer::kMaxChannelVolume;
_loomSteamCD.balance = 0;
_isLoomSteam = _vm->_game.id == GID_LOOM && Common::File::exists("CDDA.SOU");
} }
Sound::~Sound() { Sound::~Sound() {
stopCDTimer(); stopCDTimer();
g_system->getAudioCDManager()->stop(); stopCD();
free(_offsetTable); free(_offsetTable);
} }
@ -1033,7 +1042,7 @@ void Sound::playCDTrack(int track, int numLoops, int startFrame, int duration) {
// Play it // Play it
if (!_soundsPaused) if (!_soundsPaused)
g_system->getAudioCDManager()->play(track, numLoops, startFrame, duration); playCDTrackInternal(track, numLoops, startFrame, duration);
// Start the timer after starting the track. Starting an MP3 track is // Start the timer after starting the track. Starting an MP3 track is
// almost instantaneous, but a CD player may take some time. Hopefully // almost instantaneous, but a CD player may take some time. Hopefully
@ -1041,16 +1050,59 @@ void Sound::playCDTrack(int track, int numLoops, int startFrame, int duration) {
startCDTimer(); startCDTimer();
} }
void Sound::playCDTrackInternal(int track, int numLoops, int startFrame, int duration) {
_loomSteamCD.track = track;
_loomSteamCD.numLoops = numLoops;
_loomSteamCD.start = startFrame;
_loomSteamCD.duration = duration;
if (!_isLoomSteam) {
g_system->getAudioCDManager()->play(track, numLoops, startFrame, duration);
} else {
// Stop any currently playing track
_mixer->stopHandle(_loomSteamCDAudioHandle);
Common::File *cddaFile = new Common::File();
if (cddaFile->open("CDDA.SOU")) {
Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
Audio::Timestamp end = Audio::Timestamp(0, startFrame + duration, 75);
Audio::SeekableAudioStream *stream = makeCDDAStream(cddaFile, DisposeAfterUse::YES);
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_loomSteamCDAudioHandle,
Audio::makeLoopingAudioStream(stream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops));
} else {
delete cddaFile;
}
}
}
void Sound::stopCD() { void Sound::stopCD() {
g_system->getAudioCDManager()->stop(); if (!_isLoomSteam)
g_system->getAudioCDManager()->stop();
else
_mixer->stopHandle(_loomSteamCDAudioHandle);
} }
int Sound::pollCD() const { int Sound::pollCD() const {
return g_system->getAudioCDManager()->isPlaying(); if (!_isLoomSteam)
return g_system->getAudioCDManager()->isPlaying();
else
return _mixer->isSoundHandleActive(_loomSteamCDAudioHandle);
} }
void Sound::updateCD() { void Sound::updateCD() {
g_system->getAudioCDManager()->updateCD(); if (!_isLoomSteam)
g_system->getAudioCDManager()->updateCD();
}
AudioCDManager::Status Sound::getCDStatus() {
if (!_isLoomSteam)
return g_system->getAudioCDManager()->getStatus();
else {
AudioCDManager::Status info = _loomSteamCD;
_loomSteamCD.playing = _mixer->isSoundHandleActive(_loomSteamCDAudioHandle);
return info;
}
} }
void Sound::saveLoadWithSerializer(Serializer *ser) { void Sound::saveLoadWithSerializer(Serializer *ser) {

View file

@ -27,6 +27,7 @@
#include "audio/audiostream.h" #include "audio/audiostream.h"
#include "audio/mididrv.h" #include "audio/mididrv.h"
#include "audio/mixer.h" #include "audio/mixer.h"
#include "backends/audiocd/audiocd.h"
#include "scumm/saveload.h" #include "scumm/saveload.h"
namespace Audio { namespace Audio {
@ -86,6 +87,10 @@ protected:
int16 _currentCDSound; int16 _currentCDSound;
int16 _currentMusic; int16 _currentMusic;
Audio::SoundHandle _loomSteamCDAudioHandle;
bool _isLoomSteam;
AudioCDManager::Status _loomSteamCD;
public: public:
Audio::SoundHandle _talkChannelHandle; // Handle of mixer channel actor is talking on Audio::SoundHandle _talkChannelHandle; // Handle of mixer channel actor is talking on
@ -119,9 +124,11 @@ public:
void stopCDTimer(); void stopCDTimer();
void playCDTrack(int track, int numLoops, int startFrame, int duration); void playCDTrack(int track, int numLoops, int startFrame, int duration);
void playCDTrackInternal(int track, int numLoops, int startFrame, int duration);
void stopCD(); void stopCD();
int pollCD() const; int pollCD() const;
void updateCD(); void updateCD();
AudioCDManager::Status getCDStatus();
int getCurrentCDSound() const { return _currentCDSound; } int getCurrentCDSound() const { return _currentCDSound; }
// Used by the save/load system: // Used by the save/load system: