From 2756d6226b1567dbe650284c56d93d722af906b2 Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Mon, 8 Aug 2016 07:35:55 +0200 Subject: MOHAWK: Add a Riven specific sound manager - Add ambient sound fading - Fix ambient sound volume to use the list-level volume --- engines/mohawk/console.cpp | 1 + engines/mohawk/module.mk | 1 + engines/mohawk/riven.cpp | 6 +- engines/mohawk/riven.h | 3 +- engines/mohawk/riven_external.cpp | 6 +- engines/mohawk/riven_graphics.cpp | 2 + engines/mohawk/riven_scripts.cpp | 46 ++-- engines/mohawk/riven_sound.cpp | 459 ++++++++++++++++++++++++++++++++++++++ engines/mohawk/riven_sound.h | 197 ++++++++++++++++ engines/mohawk/sound.cpp | 141 ------------ engines/mohawk/sound.h | 30 --- 11 files changed, 687 insertions(+), 205 deletions(-) create mode 100644 engines/mohawk/riven_sound.cpp create mode 100644 engines/mohawk/riven_sound.h (limited to 'engines') diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp index fd79e53b07..f08eee9677 100644 --- a/engines/mohawk/console.cpp +++ b/engines/mohawk/console.cpp @@ -42,6 +42,7 @@ #ifdef ENABLE_RIVEN #include "mohawk/riven.h" #include "mohawk/riven_external.h" +#include "mohawk/riven_sound.h" #endif namespace Mohawk { diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk index 83e541e3e4..3fc118d2b6 100644 --- a/engines/mohawk/module.mk +++ b/engines/mohawk/module.mk @@ -57,6 +57,7 @@ MODULE_OBJS += \ riven_graphics.o \ riven_saveload.o \ riven_scripts.o \ + riven_sound.o \ riven_vars.o endif diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index 054b6ca6bc..b7c83c0ff8 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -34,8 +34,8 @@ #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_saveload.h" +#include "mohawk/riven_sound.h" #include "mohawk/dialogs.h" -#include "mohawk/sound.h" #include "mohawk/video.h" #include "mohawk/console.h" @@ -125,7 +125,7 @@ Common::Error MohawkEngine_Riven::run() { SearchMan.add("arcriven.z", &_installerArchive, 0, false); _gfx = new RivenGraphics(this); - _sound = new Sound(this); + _sound = new RivenSoundManager(this); _console = new RivenConsole(this); _saveLoad = new RivenSaveLoad(this, _saveFileMan); _externalScriptHandler = new RivenExternal(this); @@ -202,6 +202,7 @@ Common::Error MohawkEngine_Riven::run() { void MohawkEngine_Riven::handleEvents() { // Update background running things checkTimer(); + _sound->updateSLST(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); @@ -713,6 +714,7 @@ void MohawkEngine_Riven::delayAndUpdate(uint32 ms) { uint32 startTime = _system->getMillis(); while (_system->getMillis() < startTime + ms && !shouldQuit()) { + _sound->updateSLST(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); diff --git a/engines/mohawk/riven.h b/engines/mohawk/riven.h index 4e52dfbc93..ce819ac970 100644 --- a/engines/mohawk/riven.h +++ b/engines/mohawk/riven.h @@ -41,6 +41,7 @@ class RivenExternal; class RivenConsole; class RivenSaveLoad; class RivenOptionsDialog; +class RivenSoundManager; // Riven Stack Types enum { @@ -121,7 +122,7 @@ public: MohawkEngine_Riven(OSystem *syst, const MohawkGameDescription *gamedesc); virtual ~MohawkEngine_Riven(); - Sound *_sound; + RivenSoundManager *_sound; RivenGraphics *_gfx; RivenExternal *_externalScriptHandler; Common::RandomSource *_rnd; diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp index 00075039fe..125630445e 100644 --- a/engines/mohawk/riven_external.cpp +++ b/engines/mohawk/riven_external.cpp @@ -24,7 +24,7 @@ #include "mohawk/riven.h" #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" -#include "mohawk/sound.h" +#include "mohawk/riven_sound.h" #include "mohawk/video.h" #include "gui/message.h" @@ -2429,7 +2429,7 @@ void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) { // Play the sound of not being able to move _vm->_cursor->setCursor(kRivenHideCursor); _vm->_system->updateScreen(); - _vm->_sound->playSoundBlocking(13); + _vm->_sound->playSound(13); } } else { // We're not at the bottom, and we can move down again @@ -2463,7 +2463,7 @@ void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) { // Play the sound of not being able to move _vm->_cursor->setCursor(kRivenHideCursor); _vm->_system->updateScreen(); - _vm->_sound->playSoundBlocking(13); + _vm->_sound->playSound(13); return; } diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp index db22dde22d..b583bc9710 100644 --- a/engines/mohawk/riven_graphics.cpp +++ b/engines/mohawk/riven_graphics.cpp @@ -23,6 +23,7 @@ #include "mohawk/resource.h" #include "mohawk/riven.h" #include "mohawk/riven_graphics.h" +#include "mohawk/riven_sound.h" #include "common/system.h" #include "engines/util.h" @@ -111,6 +112,7 @@ void RivenGraphics::drawPLST(uint16 x) { void RivenGraphics::updateScreen(Common::Rect updateRect) { if (_updatesEnabled) { _vm->runUpdateScreenScript(); + _vm->_sound->triggerDrawSound(); if (_dirtyScreen) { _activatedPLSTs.clear(); diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp index caa235ec8b..3655452603 100644 --- a/engines/mohawk/riven_scripts.cpp +++ b/engines/mohawk/riven_scripts.cpp @@ -25,7 +25,7 @@ #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_scripts.h" -#include "mohawk/sound.h" +#include "mohawk/riven_sound.h" #include "mohawk/video.h" #include "common/memstream.h" @@ -309,54 +309,44 @@ void RivenScript::switchCard(uint16 op, uint16 argc, uint16 *argv) { // Command 3: play an SLST from the script void RivenScript::playScriptSLST(uint16 op, uint16 argc, uint16 *argv) { - SLSTRecord slstRecord; int offset = 0, j = 0; + uint16 soundCount = argv[offset++]; + SLSTRecord slstRecord; slstRecord.index = 0; // not set by the scripts, so we set it to 0 - slstRecord.sound_count = argv[0]; - slstRecord.sound_ids = new uint16[slstRecord.sound_count]; - - offset = slstRecord.sound_count; + slstRecord.soundIds.resize(soundCount); - for (j = 0; j < slstRecord.sound_count; j++) - slstRecord.sound_ids[j] = argv[offset++]; - slstRecord.fade_flags = argv[offset++]; + for (j = 0; j < soundCount; j++) + slstRecord.soundIds[j] = argv[offset++]; + slstRecord.fadeFlags = argv[offset++]; slstRecord.loop = argv[offset++]; - slstRecord.global_volume = argv[offset++]; + slstRecord.globalVolume = argv[offset++]; slstRecord.u0 = argv[offset++]; - slstRecord.u1 = argv[offset++]; + slstRecord.suspend = argv[offset++]; - slstRecord.volumes = new uint16[slstRecord.sound_count]; - slstRecord.balances = new int16[slstRecord.sound_count]; - slstRecord.u2 = new uint16[slstRecord.sound_count]; + slstRecord.volumes.resize(soundCount); + slstRecord.balances.resize(soundCount); + slstRecord.u2.resize(soundCount); - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.volumes[j] = argv[offset++]; - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.balances[j] = argv[offset++]; // negative = left, 0 = center, positive = right - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.u2[j] = argv[offset++]; // Unknown // Play the requested sound list _vm->_sound->playSLST(slstRecord); - _vm->_activatedSLST = true; - - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; } // Command 4: play local tWAV resource (twav_id, volume, block) void RivenScript::playSound(uint16 op, uint16 argc, uint16 *argv) { - byte volume = Sound::convertRivenVolume(argv[1]); + uint16 volume = argv[1]; + bool playOnDraw = argv[2] == 1; - if (argv[2] == 1) - _vm->_sound->playSoundBlocking(argv[0], volume); - else - _vm->_sound->playSound(argv[0], volume); + _vm->_sound->playSound(argv[0], volume, playOnDraw); } // Command 7: set variable value (variable, value) diff --git a/engines/mohawk/riven_sound.cpp b/engines/mohawk/riven_sound.cpp new file mode 100644 index 0000000000..dd45f94ad3 --- /dev/null +++ b/engines/mohawk/riven_sound.cpp @@ -0,0 +1,459 @@ +/* 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 "common/debug.h" +#include "common/system.h" + +#include "audio/audiostream.h" + +#include "mohawk/riven_sound.h" +#include "mohawk/sound.h" + +namespace Mohawk { + +RivenSoundManager::RivenSoundManager(MohawkEngine *vm) : + _vm(vm), + _effect(nullptr), + _mainAmbientSoundId(-1), + _effectPlayOnDraw(false), + _nextFadeUpdate(0) { + +} + +RivenSoundManager::~RivenSoundManager() { + stopSound(); + stopAllSLST(false); +} + +Audio::RewindableAudioStream *RivenSoundManager::makeAudioStream(uint16 id) { + return makeMohawkWaveStream(_vm->getResource(ID_TWAV, id)); +} + +void RivenSoundManager::playSound(uint16 id, uint16 volume, bool playOnDraw) { + debug (0, "Playing sound %d", id); + + stopSound(); + + Audio::RewindableAudioStream *rewindStream = makeAudioStream(id); + if (!rewindStream) { + warning("Unable to play sound with id %d", id); + return; + } + + _effect = new RivenSound(_vm, rewindStream); + _effect->setVolume(volume); + + _effectPlayOnDraw = playOnDraw; + if (!playOnDraw) { + _effect->play(); + } +} + +void RivenSoundManager::playSLST(uint16 index, uint16 card) { + Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card); + + uint16 recordCount = slstStream->readUint16BE(); + + for (uint16 i = 0; i < recordCount; i++) { + SLSTRecord slstRecord; + slstRecord.index = slstStream->readUint16BE(); + + uint16 soundCount = slstStream->readUint16BE(); + slstRecord.soundIds.resize(soundCount); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.soundIds[j] = slstStream->readUint16BE(); + + slstRecord.fadeFlags = slstStream->readUint16BE(); + slstRecord.loop = slstStream->readUint16BE(); + slstRecord.globalVolume = slstStream->readUint16BE(); + slstRecord.u0 = slstStream->readUint16BE(); // Unknown + + if (slstRecord.u0 > 1) + warning("slstRecord.u0: %d non-boolean", slstRecord.u0); + + slstRecord.suspend = slstStream->readUint16BE(); + + if (slstRecord.suspend != 0) + warning("slstRecord.u1: %d non-zero", slstRecord.suspend); + + slstRecord.volumes.resize(soundCount); + slstRecord.balances.resize(soundCount); + slstRecord.u2.resize(soundCount); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.volumes[j] = slstStream->readUint16BE(); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right + + for (uint16 j = 0; j < soundCount; j++) { + slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown + + if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) + warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); + } + + if (slstRecord.index == index) { + playSLST(slstRecord); + delete slstStream; + return; + } + } + + delete slstStream; + + // If we have no matching entries, we do nothing and just let + // the previous ambient sounds continue. +} + +void RivenSoundManager::playSLST(const SLSTRecord &slstRecord) { + if (slstRecord.soundIds.empty()) { + return; + } + + if (slstRecord.soundIds[0] == _mainAmbientSoundId) { + if (slstRecord.soundIds.size() > _ambientSounds.sounds.size()) { + addAmbientSounds(slstRecord); + } + setAmbientLooping(slstRecord.loop); + setTargetVolumes(slstRecord); + _ambientSounds.suspend = slstRecord.suspend; + if (slstRecord.suspend) { + freePreviousAmbientSounds(); + pauseAmbientSounds(); + applyTargetVolumes(); + } else { + playAmbientSounds(); + } + } else { + _mainAmbientSoundId = slstRecord.soundIds[0]; + freePreviousAmbientSounds(); + moveAmbientSoundsToPreviousSounds(); + addAmbientSounds(slstRecord); + setAmbientLooping(slstRecord.loop); + setTargetVolumes(slstRecord); + _ambientSounds.suspend = slstRecord.suspend; + if (slstRecord.suspend) { + freePreviousAmbientSounds(); + applyTargetVolumes(); + } else { + startFadingAmbientSounds(slstRecord.fadeFlags); + } + } +} + +void RivenSoundManager::stopAllSLST(bool fade) { + _mainAmbientSoundId = -1; + freePreviousAmbientSounds(); + moveAmbientSoundsToPreviousSounds(); + startFadingAmbientSounds(fade ? kFadeOutPreviousSounds : 0); +} + +void RivenSoundManager::stopSound() { + if (_effect) { + delete _effect; + } + _effect = nullptr; + _effectPlayOnDraw = false; +} + +void RivenSoundManager::addAmbientSounds(const SLSTRecord &record) { + if (record.soundIds.size() > _ambientSounds.sounds.size()) { + uint oldSize = _ambientSounds.sounds.size(); + + // Resize the list to the new size + _ambientSounds.sounds.resize(record.soundIds.size()); + + // Add new elements to the list + for (uint i = oldSize; i < _ambientSounds.sounds.size(); i++) { + Audio::RewindableAudioStream *stream = makeAudioStream(record.soundIds[i]); + + RivenSound *sound = new RivenSound(_vm, stream); + sound->setVolume(record.volumes[i]); + sound->setBalance(record.balances[i]); + + _ambientSounds.sounds[i].sound = sound; + _ambientSounds.sounds[i].targetVolume = record.volumes[i]; + _ambientSounds.sounds[i].targetBalance = record.balances[i]; + } + } +} + +void RivenSoundManager::setTargetVolumes(const SLSTRecord &record) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].targetVolume = record.volumes[i] * record.globalVolume / 256; + _ambientSounds.sounds[i].targetBalance = record.balances[i]; + } + _ambientSounds.fading = true; +} + +void RivenSoundManager::freePreviousAmbientSounds() { + for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) { + delete _previousAmbientSounds.sounds[i].sound; + } + _previousAmbientSounds = AmbientSoundList(); +} + +void RivenSoundManager::moveAmbientSoundsToPreviousSounds() { + _previousAmbientSounds = _ambientSounds; + _ambientSounds = AmbientSoundList(); +} + +void RivenSoundManager::applyTargetVolumes() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _ambientSounds.sounds[i]; + RivenSound *sound = ambientSound.sound; + sound->setVolume(ambientSound.targetVolume); + sound->setBalance(ambientSound.targetBalance); + } + _ambientSounds.fading = false; +} + +void RivenSoundManager::startFadingAmbientSounds(uint16 flags) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _ambientSounds.sounds[i]; + uint16 volume; + if (flags & kFadeInNewSounds) { + volume = 0; + } else { + volume = ambientSound.targetVolume; + } + ambientSound.sound->setVolume(volume); + } + _ambientSounds.fading = true; + playAmbientSounds(); + + if (!_previousAmbientSounds.sounds.empty()) { + if (flags) { + _previousAmbientSounds.fading = true; + } else { + freePreviousAmbientSounds(); + } + + for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _previousAmbientSounds.sounds[i]; + if (flags & kFadeOutPreviousSounds) { + ambientSound.targetVolume = 0; + } else { + ambientSound.sound->setVolume(ambientSound.targetVolume); + } + } + } +} + +void RivenSoundManager::playAmbientSounds() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->play(); + } +} + +void RivenSoundManager::setAmbientLooping(bool loop) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->setLooping(loop); + } +} + +void RivenSoundManager::triggerDrawSound() { + if (_effectPlayOnDraw && _effect) { + _effect->play(); + } + _effectPlayOnDraw = false; +} + +void RivenSoundManager::pauseAmbientSounds() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->pause(); + } +} + +void RivenSoundManager::updateSLST() { + uint32 time = _vm->_system->getMillis(); + int32 delta = CLIP(time - _nextFadeUpdate, -50, 50); + if (_nextFadeUpdate == 0 || delta > 0) { + _nextFadeUpdate = time + 50 - delta; + + if (_ambientSounds.fading) { + fadeAmbientSoundList(_ambientSounds); + } + + if (_previousAmbientSounds.fading) { + fadeAmbientSoundList(_previousAmbientSounds); + } + + if (!_previousAmbientSounds.sounds.empty() && !_ambientSounds.fading && !_previousAmbientSounds.fading) { + freePreviousAmbientSounds(); + } + } +} + +void RivenSoundManager::fadeAmbientSoundList(AmbientSoundList &list) { + list.fading = false; + + for (uint i = 0; i < list.sounds.size(); i++) { + AmbientSound &ambientSound = list.sounds[i]; + list.fading |= fadeVolume(ambientSound); + list.fading |= fadeBalance(ambientSound); + } +} + +bool RivenSoundManager::fadeVolume(AmbientSound &ambientSound) { + uint16 volume = ambientSound.sound->getVolume(); + float delta = (ambientSound.targetVolume - volume) / 30.0f; + + if (ABS(delta) < 0.01f) { + ambientSound.sound->setVolume(ambientSound.targetVolume); + return false; + } else { + // Make sure the increment is not zero once converted to an integer + if (delta > 0 && delta < 1) { + delta = 1; + } else if (delta < 0 && delta > -1) { + delta = -1; + } + + ambientSound.sound->setVolume(volume + delta); + return true; + } +} + +bool RivenSoundManager::fadeBalance(RivenSoundManager::AmbientSound &ambientSound) { + int16 balance = ambientSound.sound->getBalance(); + float delta = (ambientSound.targetBalance - balance) / 10.0f; + + if (ABS(delta) < 0.01) { + ambientSound.sound->setBalance(ambientSound.targetBalance); + return false; + } else { + // Make sure the increment is not zero once converted to an integer + if (delta > 0 && delta < 1) { + delta = 1; + } else if (delta < 0 && delta > -1) { + delta = -1; + } + + ambientSound.sound->setBalance(balance + delta); + return true; + } +} + +RivenSound::RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream) : + _vm(vm), + _volume(Audio::Mixer::kMaxChannelVolume), + _balance(0), + _looping(false), + _stream(rewindStream) { + +} + +bool RivenSound::isPlaying() const { + return _vm->_mixer->isSoundHandleActive(_handle); +} + +void RivenSound::pause() { + _vm->_mixer->pauseHandle(_handle, true); +} + +void RivenSound::setVolume(uint16 volume) { + _volume = volume; + if (isPlaying()) { + byte mixerVolume = convertVolume(volume); + _vm->_mixer->setChannelVolume(_handle, mixerVolume); + } +} + +void RivenSound::setBalance(int16 balance) { + _balance = balance; + if (isPlaying()) { + int8 mixerBalance = convertBalance(balance); + _vm->_mixer->setChannelBalance(_handle, mixerBalance); + } +} + +void RivenSound::setLooping(bool loop) { + if (isPlaying() && _looping != loop) { + warning("Changing loop state while a sound is playing is not implemented."); + } + _looping = loop; +} + +void RivenSound::play() { + if (isPlaying()) { + // If the sound is already playing, make sure it is not paused + _vm->_mixer->pauseHandle(_handle, false); + return; + } + + if (!_stream) { + warning("Trying to play a sound without a stream"); + return; + } + + Audio::AudioStream *playStream; + if (_looping) { + playStream = new Audio::LoopingAudioStream(_stream, 0); + } else { + playStream = _stream; + } + + int8 mixerBalance = convertBalance(_balance); + byte mixerVolume = convertVolume(_volume); + _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, playStream, -1, mixerVolume, mixerBalance); + _stream = nullptr; +} + +byte RivenSound::convertVolume(uint16 volume) { + // The volume is a fixed point value in the Mohawk part of the original engine. + // It's not clear what happens when it is higher than one. + return (volume > 255) ? 255 : volume; +} + +int8 RivenSound::convertBalance(int16 balance) { + return (int8)(balance >> 8); +} + +RivenSound::~RivenSound() { + _vm->_mixer->stopHandle(_handle); + delete _stream; +} + +int16 RivenSound::getBalance() const { + return _balance; +} + +uint16 RivenSound::getVolume() const { + return _volume; +} + +RivenSoundManager::AmbientSound::AmbientSound() : + sound(nullptr), + targetVolume(0), + targetBalance(0) { + +} + +RivenSoundManager::AmbientSoundList::AmbientSoundList() : + fading(false), + suspend(false) { +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/riven_sound.h b/engines/mohawk/riven_sound.h new file mode 100644 index 0000000000..f673d1ee3f --- /dev/null +++ b/engines/mohawk/riven_sound.h @@ -0,0 +1,197 @@ +/* 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. + * + */ + +#ifndef MOHAWK_RIVEN_SOUND_H +#define MOHAWK_RIVEN_SOUND_H + +#include "common/array.h" +#include "common/str.h" + +#include "audio/mixer.h" + +namespace Audio { +class RewindableAudioStream; +} + +namespace Mohawk { + +class MohawkEngine; +class RivenSound; + +/** + * Ambient sound list + */ +struct SLSTRecord { + uint16 index; + Common::Array soundIds; + uint16 fadeFlags; + uint16 loop; + uint16 globalVolume; + uint16 u0; + uint16 suspend; + Common::Array volumes; + Common::Array balances; + Common::Array u2; +}; + +/** + * Sound manager for Riven + * + * The sound manager can play simulteaneously: + * - An effect sound + * - A list of ambient sounds + * + * The list of ambient sounds can be cross faded + * with the previously running ambient sounds. + */ +class RivenSoundManager { +public: + RivenSoundManager(MohawkEngine *vm); + ~RivenSoundManager(); + + /** + * Play an effect sound + * + * @param id Sound ID in the stack + * @param volume Playback volume, between 0 and 255 + * @param playOnDraw Start playing when the current card is drawn instead of immediatly + */ + void playSound(uint16 id, uint16 volume = 255, bool playOnDraw = false); + + /** Start playing the scheduled on-draw effect sound, if any. Called by the GraphicsManager. */ + void triggerDrawSound(); + + /** Stop playing the current effect sound, if any */ + void stopSound(); + + /** Start playing an ambient sound list */ + void playSLST(const SLSTRecord &slstRecord); + + /** Start playing an ambient sound list from a resource */ + void playSLST(uint16 index, uint16 card); + + /** Stop playing the current ambient sounds */ + void stopAllSLST(bool fade = false); + + /** Update the ambient sounds for fading. Called once per frame. */ + void updateSLST(); + +private: + struct AmbientSound { + RivenSound *sound; + uint16 targetVolume; + int16 targetBalance; + + AmbientSound(); + }; + + struct AmbientSoundList { + bool fading; + bool suspend; + Common::Array sounds; + + AmbientSoundList(); + }; + + enum FadeFlags { + kFadeOutPreviousSounds = 1, + kFadeInNewSounds = 2 + }; + + MohawkEngine *_vm; + + int16 _mainAmbientSoundId; + AmbientSoundList _ambientSounds; + AmbientSoundList _previousAmbientSounds; + uint32 _nextFadeUpdate; + + RivenSound *_effect; + bool _effectPlayOnDraw; + + Audio::RewindableAudioStream *makeAudioStream(uint16 id); + + // Ambient sound management + void addAmbientSounds(const SLSTRecord &record); + void playAmbientSounds(); + void pauseAmbientSounds(); + void moveAmbientSoundsToPreviousSounds(); + void freePreviousAmbientSounds(); + + // Ambient sound fading + void setTargetVolumes(const SLSTRecord &record); + void applyTargetVolumes(); + void startFadingAmbientSounds(uint16 flags); + void fadeAmbientSoundList(AmbientSoundList &list); + bool fadeVolume(AmbientSound &ambientSound); + bool fadeBalance(AmbientSound &ambientSound); + void setAmbientLooping(bool loop); +}; + +/** + * A sound used internally by the SoundManager + */ +class RivenSound { +public: + RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream); + ~RivenSound(); + + /** Start playing the sound stream passed to the constructor */ + void play(); + + /** Is the sound currently playing ar paused? */ + bool isPlaying() const; + + /** Pause the playback, the play method resumes */ + void pause(); + + /** Get the current volume */ + uint16 getVolume() const; + + /** Change the playback volume */ + void setVolume(uint16 volume); + + /** Get the current balance */ + int16 getBalance() const; + + /** Change the balance */ + void setBalance(int16 balance); + + /** Set the sound to indefinitely loop. Must be called before startting the playback */ + void setLooping(bool loop); + +private: + static byte convertVolume(uint16 volume); + static int8 convertBalance(int16 balance); + + MohawkEngine *_vm; + + Audio::SoundHandle _handle; + Audio::RewindableAudioStream *_stream; + + uint16 _volume; + int16 _balance; + bool _looping; +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp index dd9325250d..0711561068 100644 --- a/engines/mohawk/sound.cpp +++ b/engines/mohawk/sound.cpp @@ -191,7 +191,6 @@ Sound::Sound(MohawkEngine* vm) : _vm(vm) { Sound::~Sound() { stopSound(); - stopAllSLST(); stopBackgroundMyst(); if (_midiParser) { @@ -378,146 +377,6 @@ void Sound::stopMidi() { _midiParser->unloadMusic(); } -byte Sound::convertRivenVolume(uint16 volume) { - return (volume == 256) ? 255 : volume; -} - -void Sound::playSLST(uint16 index, uint16 card) { - Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card); - SLSTRecord slstRecord; - uint16 recordCount = slstStream->readUint16BE(); - - for (uint16 i = 0; i < recordCount; i++) { - slstRecord.index = slstStream->readUint16BE(); - slstRecord.sound_count = slstStream->readUint16BE(); - slstRecord.sound_ids = new uint16[slstRecord.sound_count]; - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.sound_ids[j] = slstStream->readUint16BE(); - - slstRecord.fade_flags = slstStream->readUint16BE(); - slstRecord.loop = slstStream->readUint16BE(); - slstRecord.global_volume = slstStream->readUint16BE(); - slstRecord.u0 = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u0 > 1) - warning("slstRecord.u0: %d non-boolean", slstRecord.u0); - - slstRecord.u1 = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u1 != 0) - warning("slstRecord.u1: %d non-zero", slstRecord.u1); - - slstRecord.volumes = new uint16[slstRecord.sound_count]; - slstRecord.balances = new int16[slstRecord.sound_count]; - slstRecord.u2 = new uint16[slstRecord.sound_count]; - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.volumes[j] = slstStream->readUint16BE(); - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right - - for (uint16 j = 0; j < slstRecord.sound_count; j++) { - slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) - warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); - } - - if (slstRecord.index == index) { - playSLST(slstRecord); - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; - delete slstStream; - return; - } - - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; - } - - delete slstStream; - - // If we have no matching entries, we do nothing and just let - // the previous ambient sounds continue. -} - -void Sound::playSLST(SLSTRecord slstRecord) { - // End old sounds - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) { - bool noLongerPlay = true; - for (uint16 j = 0; j < slstRecord.sound_count; j++) - if (_currentSLSTSounds[i].id == slstRecord.sound_ids[j]) - noLongerPlay = false; - if (noLongerPlay) - stopSLSTSound(i, (slstRecord.fade_flags & 1) != 0); - } - - // Start new sounds - for (uint16 i = 0; i < slstRecord.sound_count; i++) { - bool alreadyPlaying = false; - for (uint16 j = 0; j < _currentSLSTSounds.size(); j++) { - if (_currentSLSTSounds[j].id == slstRecord.sound_ids[i]) - alreadyPlaying = true; - } - if (!alreadyPlaying) { - playSLSTSound(slstRecord.sound_ids[i], - (slstRecord.fade_flags & (1 << 1)) != 0, - slstRecord.loop != 0, - slstRecord.volumes[i], - slstRecord.balances[i]); - } - } -} - -void Sound::stopAllSLST(bool fade) { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) { - // TODO: Fade out, if requested - _vm->_mixer->stopHandle(*_currentSLSTSounds[i].handle); - delete _currentSLSTSounds[i].handle; - } - - _currentSLSTSounds.clear(); -} - -static int8 convertBalance(int16 balance) { - return (int8)(balance >> 8); -} - -void Sound::playSLSTSound(uint16 id, bool fade, bool loop, uint16 volume, int16 balance) { - // WORKAROUND: Some Riven SLST entries have a volume of 0, so we just ignore them. - if (volume == 0) - return; - - SLSTSndHandle sndHandle; - sndHandle.handle = new Audio::SoundHandle(); - sndHandle.id = id; - _currentSLSTSounds.push_back(sndHandle); - - Audio::RewindableAudioStream *rewindStream = makeMohawkWaveStream(_vm->getResource(ID_TWAV, id)); - - // Loop here if necessary - Audio::AudioStream *audStream = rewindStream; - if (loop) - audStream = Audio::makeLoopingAudioStream(rewindStream, 0); - - // TODO: Handle fading, possibly just raise the volume of the channel in increments? - - _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, sndHandle.handle, audStream, -1, convertRivenVolume(volume), convertBalance(balance)); -} - -void Sound::stopSLSTSound(uint16 index, bool fade) { - // TODO: Fade out, if requested - _vm->_mixer->stopHandle(*_currentSLSTSounds[index].handle); - delete _currentSLSTSounds[index].handle; - _currentSLSTSounds.remove_at(index); -} - Audio::RewindableAudioStream *Sound::makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream) { uint16 header = stream->readUint16BE(); uint16 rate = 0; diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h index 6b50c411c2..2b4b1ce091 100644 --- a/engines/mohawk/sound.h +++ b/engines/mohawk/sound.h @@ -42,20 +42,6 @@ namespace Mohawk { #define MAX_CHANNELS 2 // Can there be more than 2? -struct SLSTRecord { - uint16 index; - uint16 sound_count; - uint16 *sound_ids; - uint16 fade_flags; - uint16 loop; - uint16 global_volume; - uint16 u0; - uint16 u1; - uint16 *volumes; - int16 *balances; - uint16 *u2; -}; - enum SndHandleType { kFreeHandle, kUsedHandle @@ -68,11 +54,6 @@ struct SndHandle { uint16 id; }; -struct SLSTSndHandle { - Audio::SoundHandle *handle; - uint16 id; -}; - struct ADPCMStatus { // Holds ADPCM status data, but is irrelevant for us. uint32 size; uint16 itemCount; @@ -144,12 +125,6 @@ public: void stopBackgroundMyst(); void changeBackgroundVolumeMyst(uint16 vol); - // Riven-specific sound functions - void playSLST(uint16 index, uint16 card); - void playSLST(SLSTRecord slstRecord); - void stopAllSLST(bool fade = false); - static byte convertRivenVolume(uint16 volume); - private: MohawkEngine *_vm; MidiDriver *_midiDriver; @@ -166,11 +141,6 @@ private: // Myst-specific SndHandle _mystBackgroundSound; - - // Riven-specific - void playSLSTSound(uint16 index, bool fade, bool loop, uint16 volume, int16 balance); - void stopSLSTSound(uint16 id, bool fade); - Common::Array _currentSLSTSounds; }; } // End of namespace Mohawk -- cgit v1.2.3