/* 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 < record.volumes.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