diff options
Diffstat (limited to 'engines/tony/sound.cpp')
-rw-r--r-- | engines/tony/sound.cpp | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/engines/tony/sound.cpp b/engines/tony/sound.cpp new file mode 100644 index 0000000000..90ae241db0 --- /dev/null +++ b/engines/tony/sound.cpp @@ -0,0 +1,685 @@ +/* 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. + * + */ + +/* + * This code is based on original Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "audio/audiostream.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/wave.h" +#include "common/textconsole.h" +#include "tony/game.h" +#include "tony/tony.h" + +namespace Tony { + +/* + * Tony uses a [0,63] volume scale (where 0 is silent and 63 is loudest). + * The original game engine linearly mapped this scale into DirectSound's + * [-10000, 0] scale (where -10000 is silent), which is a logarithmic scale. + * + * This means that Tony's scale is logarithmic as well, and must be converted + * to the linear scale used by the mixer. + */ +static int remapVolume(int volume) { + double dsvol = (double)(63 - volume) * -10000.0 / 63.0; + return (int)((double)Audio::Mixer::kMaxChannelVolume * pow(10.0, dsvol / 2000.0) + 0.5); +} + +/****************************************************************************\ +* FPSOUND Methods +\****************************************************************************/ + +/** + * Default constructor. Initializes the attributes. + * + */ +FPSound::FPSound() { + _soundSupported = false; +} + +/** + * Initializes the object, and prepare everything you need to create streams and sound effects. + * + * @returns True is everything is OK, False otherwise + */ +bool FPSound::init() { + _soundSupported = g_system->getMixer()->isReady(); + return _soundSupported; +} + +/** + * Destroy the object and free the memory + * + */ +FPSound::~FPSound() { +} + +/** + * Allocates an object of type FPStream, and return its pointer + * + * @param streamPtr Will contain a pointer to the object you just created. + * + * @returns True is everything is OK, False otherwise + */ +bool FPSound::createStream(FPStream **streamPtr) { + (*streamPtr) = new FPStream(_soundSupported); + + return (*streamPtr != NULL); +} + +/** + * Allocates an object of type FpSfx, and return its pointer + * + * @param soundPtr Will contain a pointer to the object you just created. + * + * @returns True is everything is OK, False otherwise + */ +bool FPSound::createSfx(FPSfx **sfxPtr) { + (*sfxPtr) = new FPSfx(_soundSupported); + + return (*sfxPtr != NULL); +} + +/** + * Set the general volume + * + * @param volume Volume to set (0-63) + */ +void FPSound::setMasterVolume(int volume) { + if (!_soundSupported) + return; + + // WORKAROUND: We don't use remapVolume() here, so that the main option screen exposes + // a linear scale to the user. This is an improvement over the original game + // where the user had to deal with a logarithmic volume scale. + g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, CLIP<int>(volume, 0, 63) * Audio::Mixer::kMaxChannelVolume / 63); +} + +/** + * Get the general volume + * + * @param volumePtr Variable that will contain the volume (0-63) + */ +void FPSound::getMasterVolume(int *volumePtr) { + if (!_soundSupported) + return; + + *volumePtr = g_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) * 63 / Audio::Mixer::kMaxChannelVolume; +} + +/** + * Default constructor. + * + * @remarks Do *NOT* declare an object directly, but rather + * create it using FPSound::CreateSfx() + * + */ +FPSfx::FPSfx(bool soundOn) { + _soundSupported = soundOn; + _fileLoaded = false; + _lastVolume = 63; + _hEndOfBuffer = CoroScheduler.createEvent(true, false); + _isVoice = false; + _loopStream = 0; + _rewindableStream = 0; + _paused = false; + + g_vm->_activeSfx.push_back(this); +} + +/** + * Default Destructor. + * + * @remarks It is also stops the sound effect that may be + * currently played, and free the memory it uses. + * + */ +FPSfx::~FPSfx() { + if (!_soundSupported) + return; + + g_system->getMixer()->stopHandle(_handle); + g_vm->_activeSfx.remove(this); + + if (_loopStream) + delete _loopStream; // _rewindableStream is deleted by deleting _loopStream + else + delete _rewindableStream; + + // Free the buffer end event + CoroScheduler.closeEvent(_hEndOfBuffer); +} + +/** + * Releases the memory used by the object. + * + * @remarks Must be called when the object is no longer used and + * **ONLY** if the object was created by + * FPSound::CreateStream(). + * Object pointers are no longer valid after this call. + */ +void FPSfx::release() { + delete this; +} + +bool FPSfx::loadWave(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + _rewindableStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES); + + if (!_rewindableStream) + return false; + + _fileLoaded = true; + setVolume(_lastVolume); + return true; +} + +bool FPSfx::loadVoiceFromVDB(Common::File &vdbFP) { + if (!_soundSupported) + return true; + + uint32 size = vdbFP.readUint32LE(); + uint32 rate = vdbFP.readUint32LE(); + _isVoice = true; + + _rewindableStream = Audio::makeADPCMStream(vdbFP.readStream(size), DisposeAfterUse::YES, 0, Audio::kADPCMDVI, rate, 1); + + _fileLoaded = true; + setVolume(62); + return true; +} + +/** + * Opens a file and loads a sound effect. + * + * @param fileName Sfx filename + * @param codec CODEC used to uncompress the samples + * + * @returns True is everything is OK, False otherwise + */ +bool FPSfx::loadFile(const char *fileName, uint32 codec) { + if (!_soundSupported) + return true; + + Common::File file; + if (!file.open(fileName)) { + warning("FPSfx::LoadFile(): Cannot open sfx file!"); + return false; + } + + if (file.readUint32BE() != MKTAG('A', 'D', 'P', 0x10)) { + warning("FPSfx::LoadFile(): Invalid ADP header!"); + return false; + } + + uint32 rate = file.readUint32LE(); + uint32 channels = file.readUint32LE(); + + Common::SeekableReadStream *buffer = file.readStream(file.size() - file.pos()); + + if (codec == FPCODEC_ADPCM) { + _rewindableStream = Audio::makeADPCMStream(buffer, DisposeAfterUse::YES, 0, Audio::kADPCMDVI, rate, channels); + } else { + byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN; + + if (channels == 2) + flags |= Audio::FLAG_STEREO; + + _rewindableStream = Audio::makeRawStream(buffer, rate, flags, DisposeAfterUse::YES); + } + + _fileLoaded = true; + return true; +} + +/** + * Play the Sfx in memory. + * + * @returns True is everything is OK, False otherwise + */ +bool FPSfx::play() { + stop(); // sanity check + + if (_fileLoaded) { + CoroScheduler.resetEvent(_hEndOfBuffer); + + _rewindableStream->rewind(); + + Audio::AudioStream *stream = _rewindableStream; + + if (_loop) { + if (!_loopStream) + _loopStream = Audio::makeLoopingAudioStream(_rewindableStream, 0); + + stream = _loopStream; + } + + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, stream, -1, + Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + + setVolume(_lastVolume); + + if (_paused) + g_system->getMixer()->pauseHandle(_handle, true); + } + + return true; +} + +/** + * Stops a Sfx. + * + * @returns True is everything is OK, False otherwise + */ +bool FPSfx::stop() { + if (_fileLoaded) { + g_system->getMixer()->stopHandle(_handle); + _paused = false; + } + + return true; +} + +/** + * Enables or disables the Sfx loop. + * + * @param loop True to enable the loop, False to disable + * + * @remarks The loop must be activated BEFORE the sfx starts + * playing. Any changes made during the play will have + * no effect until the sfx is stopped then played again. + */ +void FPSfx::setLoop(bool loop) { + _loop = loop; +} + +/** + * Pauses a Sfx. + * + */ +void FPSfx::setPause(bool pause) { + if (_fileLoaded) { + if (g_system->getMixer()->isSoundHandleActive(_handle) && (pause ^ _paused)) + g_system->getMixer()->pauseHandle(_handle, pause); + + _paused = pause; + } +} + +/** + * Change the volume of Sfx + * + * @param volume Volume to be set (0-63) + * + */ +void FPSfx::setVolume(int volume) { + if (volume > 63) + volume = 63; + + if (volume < 0) + volume = 0; + + _lastVolume = volume; + + if (_isVoice) { + if (!GLOBALS._bCfgDubbing) + volume = 0; + else { + volume -= (10 - GLOBALS._nCfgDubbingVolume) * 2; + if (volume < 0) + volume = 0; + } + } else { + if (!GLOBALS._bCfgSFX) + volume = 0; + else { + volume -= (10 - GLOBALS._nCfgSFXVolume) * 2; + if (volume < 0) + volume = 0; + } + } + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelVolume(_handle, remapVolume(volume)); +} + +/** + * Gets the Sfx volume + * + * @param volumePtr Will contain the current Sfx volume + * + */ +void FPSfx::getVolume(int *volumePtr) { + if (g_system->getMixer()->isSoundHandleActive(_handle)) + *volumePtr = _lastVolume; + else + *volumePtr = 0; +} + +/** + * Returns true if the underlying sound has ended + */ +bool FPSfx::endOfBuffer() const { + return !g_system->getMixer()->isSoundHandleActive(_handle) && (!_rewindableStream || _rewindableStream->endOfData()); +} + +/** + * Continually checks to see if active sounds have finished playing + * Sets the event signalling the sound has ended + */ +void FPSfx::soundCheckProcess(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + Common::List<FPSfx *>::iterator i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + for (;;) { + // Check each active sound + for (_ctx->i = g_vm->_activeSfx.begin(); _ctx->i != g_vm->_activeSfx.end(); ++_ctx->i) { + FPSfx *sfx = *_ctx->i; + if (sfx->endOfBuffer()) + CoroScheduler.setEvent(sfx->_hEndOfBuffer); + } + + // Delay until the next check is done + CORO_INVOKE_1(CoroScheduler.sleep, 50); + } + + CORO_END_CODE; +} + +/** + * Default constructor. + * + * @remarks Do *NOT* declare an object directly, but rather + * create it using FPSound::CreateStream() + */ +FPStream::FPStream(bool soundOn) { + _soundSupported = soundOn; + _fileLoaded = false; + _paused = false; + _loop = false; + _doFadeOut = false; + _syncExit = false; + _bufferSize = _size = 0; + _lastVolume = 0; + _syncToPlay = NULL; + _loopStream = NULL; + _rewindableStream = NULL; +} + +/** + * Default destructor. + * + * @remarks It calls CloseFile() if needed. + */ +FPStream::~FPStream() { + if (!_soundSupported) + return; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + stop(); + + if (_fileLoaded) + unloadFile(); + + _syncToPlay = NULL; +} + +/** + * Releases the memory object. + * + * @remarks Must be called when the object is no longer used + * and **ONLY** if the object was created by + * FPSound::CreateStream(). + * Object pointers are no longer valid after this call. + */ +void FPStream::release() { + delete this; +} + +/** + * Opens a file stream + * + * @param fileName Filename to be opened + * @param codec CODEC to be used to uncompress samples + * + * @returns True is everything is OK, False otherwise + */ +bool FPStream::loadFile(const Common::String &fileName, uint32 codec, int bufSize) { + if (!_soundSupported) + return true; + + if (_fileLoaded) + unloadFile(); + + // Save the codec type + _codec = codec; + + // Open the file stream for reading + if (!_file.open(fileName)) { + // Fallback: try with an extra '0' prefix + if (!_file.open("0" + fileName)) + return false; + warning("FPStream::loadFile(): Fallback from %s to %s", fileName.c_str(), _file.getName()); + } + + // Save the size of the stream + _size = _file.size(); + + switch (_codec) { + case FPCODEC_RAW: + _rewindableStream = Audio::makeRawStream(&_file, 44100, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN | Audio::FLAG_STEREO, DisposeAfterUse::NO); + break; + + case FPCODEC_ADPCM: + _rewindableStream = Audio::makeADPCMStream(&_file, DisposeAfterUse::NO, 0, Audio::kADPCMDVI, 44100, 2); + break; + + default: + _file.close(); + return false; + } + + // All done + _fileLoaded = true; + _paused = false; + + setVolume(63); + + return true; +} + +/** + * Closes a file stream (opened or not). + * + * @returns For safety, the destructor calls unloadFile() if it has not + * been mentioned explicitly. + * + * @remarks It is necessary to call this function to free the + * memory used by the stream. + */ +bool FPStream::unloadFile() { + if (!_soundSupported || !_fileLoaded) + return true; + + assert(!g_system->getMixer()->isSoundHandleActive(_handle)); + + // Closes the file handle stream + delete _loopStream; + delete _rewindableStream; + _loopStream = NULL; + _rewindableStream = NULL; + _file.close(); + + // Flag that the file is no longer in memory + _fileLoaded = false; + + return true; +} + +/** + * Play the stream. + * + * @returns True is everything is OK, False otherwise + */ + +bool FPStream::play() { + if (!_soundSupported || !_fileLoaded) + return false; + + stop(); + + _rewindableStream->rewind(); + + Audio::AudioStream *stream = _rewindableStream; + + if (_loop) { + if (!_loopStream) + _loopStream = new Audio::LoopingAudioStream(_rewindableStream, 0, DisposeAfterUse::NO); + + stream = _loopStream; + } + + // FIXME: Should this be kMusicSoundType or KPlainSoundType? + g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handle, stream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + setVolume(_lastVolume); + _paused = false; + + return true; +} + +/** + * Closes the stream. + * + * @returns True is everything is OK, False otherwise + * + */ +bool FPStream::stop() { + if (!_soundSupported) + return true; + + if (!_fileLoaded) + return false; + + if (!g_system->getMixer()->isSoundHandleActive(_handle)) + return false; + + g_system->getMixer()->stopHandle(_handle); + + _paused = false; + + return true; +} + +void FPStream::waitForSync(FPStream *toPlay) { + // FIXME: The idea here is that you wait for this stream to reach + // a buffer which is a multiple of nBufSize/nSync, and then the + // thread stops it and immediately starts the 'toplay' stream. + + stop(); + toPlay->play(); +} + +/** + * Unables or disables stream loop. + * + * @param loop True enable loop, False disables it + * + * @remarks The loop must be activated BEFORE the stream starts + * playing. Any changes made during the play will have no + * effect until the stream is stopped then played again. + */ +void FPStream::setLoop(bool loop) { + _loop = loop; +} + +/** + * Pause sound effect + * + * @param pause True enables pause, False disables it + */ +void FPStream::setPause(bool pause) { + if (!_fileLoaded) + return; + + if (pause == _paused) + return; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->pauseHandle(_handle, pause); + + _paused = pause; + + // Trick to reset the volume after a possible new sound configuration + setVolume(_lastVolume); +} + +/** + * Change the volume of the stream + * + * @param volume Volume to be set (0-63) + * + */ +void FPStream::setVolume(int volume) { + if (volume > 63) + volume = 63; + + if (volume < 0) + volume = 0; + + _lastVolume = volume; + + if (!GLOBALS._bCfgMusic) + volume = 0; + else { + volume -= (10 - GLOBALS._nCfgMusicVolume) * 2; + if (volume < 0) + volume = 0; + } + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelVolume(_handle, remapVolume(volume)); +} + +/** + * Gets the volume of the stream + * + * @param volumePtr Variable that will contain the current volume + * + */ +void FPStream::getVolume(int *volumePtr) { + if (g_system->getMixer()->isSoundHandleActive(_handle)) + *volumePtr = _lastVolume; + else + *volumePtr = 0; +} + +} // End of namespace Tony |