aboutsummaryrefslogtreecommitdiff
path: root/engines/tony/sound.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/tony/sound.cpp')
-rw-r--r--engines/tony/sound.cpp685
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