/* 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. * * Additional copyright for this file: * Copyright (C) 1995 Presto Studios, Inc. * * 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 3 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, see . * */ #include "audio/audiostream.h" #include "audio/mixer.h" #include "audio/decoders/wave.h" #include "common/archive.h" #include "common/events.h" #include "common/system.h" #include "buried/buried.h" #include "buried/graphics.h" #include "buried/resources.h" #include "buried/sound.h" namespace Buried { #define TIMED_EFFECT_NONE 0x00 #define TIMED_EFFECT_VOLUME 0x01 #define SOUND_FLAG_DESTROY_AFTER_COMPLETION 0x01 static inline int clipVolume(int volume) { return CLIP(volume, 0, Audio::Mixer::kMaxChannelVolume); } SoundManager::SoundManager(BuriedEngine *vm) : _vm(vm) { _fileIDFootsteps = -1; _lastAmbient = 1; startup(); } SoundManager::~SoundManager() { for (int i = 0; i < kMaxSounds; i++) delete _soundData[i]; } bool SoundManager::startup() { _paused = false; for (int i = 0; i < kMaxSounds; i++) _soundData[i] = new Sound(); return true; } void SoundManager::shutDown() { if (_paused) return; for (int i = 0; i < kMaxSounds; i++) { delete _soundData[i]; _soundData[i] = new Sound(); } } void SoundManager::pause(bool shouldPause) { for (int i = 0; i < kMaxSounds; i++) _soundData[i]->pause(shouldPause); } bool SoundManager::setAmbientSound(const Common::Path &fileName, bool fade, byte finalVolumeLevel) { // Determine which of the two ambient tracks to use int newAmbientTrack = (_lastAmbient == 0) ? 1 : 0; // If this ambient track is currently in use, stop and kill it now if (_soundData[kAmbientIndexBase + newAmbientTrack]->isPlaying()) { delete _soundData[kAmbientIndexBase + newAmbientTrack]; _soundData[kAmbientIndexBase + newAmbientTrack] = new Sound(); } bool retVal = true; if (fileName.empty()) { if (fade) { // Set parameters for the current ambient, if there is one if (_soundData[kAmbientIndexBase + _lastAmbient]->isPlaying()) { _soundData[kAmbientIndexBase + _lastAmbient]->_loop = true; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kAmbientIndexBase + _lastAmbient]->_flags = SOUND_FLAG_DESTROY_AFTER_COMPLETION; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectSteps = 16; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectDelta = -(_soundData[kAmbientIndexBase + _lastAmbient]->_volume / 16); _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectStart = g_system->getMillis(); _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectRemaining = 2000; // Reset parameters for current ambient g_system->getMixer()->setChannelVolume(*_soundData[kAmbientIndexBase + _lastAmbient]->_handle, clipVolume(_soundData[kAmbientIndexBase + _lastAmbient]->_volume << 1)); // clone2727: The original resets the loop count, but I don't think that's necessary } } else { // Stop the current ambient delete _soundData[kAmbientIndexBase + _lastAmbient]; _soundData[kAmbientIndexBase + _lastAmbient] = new Sound(); } return true; } if (fade) { // Set parameters for the current ambient, if there is one if (_soundData[kAmbientIndexBase + _lastAmbient]->_handle) { _soundData[kAmbientIndexBase + _lastAmbient]->_loop = true; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kAmbientIndexBase + _lastAmbient]->_flags = SOUND_FLAG_DESTROY_AFTER_COMPLETION; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectSteps = 16; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectDelta = -(_soundData[kAmbientIndexBase + _lastAmbient]->_volume / 16); _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectStart = g_system->getMillis(); _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectRemaining = 2000; // Reset parameters for the current ambient g_system->getMixer()->setChannelVolume(*_soundData[kAmbientIndexBase + _lastAmbient]->_handle, clipVolume(_soundData[kAmbientIndexBase + _lastAmbient]->_volume << 1)); } // Load the new ambient if (_soundData[kAmbientIndexBase + newAmbientTrack]->load(fileName)) { // Set the parameters of the new ambient _soundData[kAmbientIndexBase + newAmbientTrack]->_volume = 0; _soundData[kAmbientIndexBase + newAmbientTrack]->_loop = true; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kAmbientIndexBase + newAmbientTrack]->_flags = 0; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectSteps = 16; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectDelta = finalVolumeLevel / 16; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectStart = g_system->getMillis(); _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectRemaining = 2000; _soundData[kAmbientIndexBase + newAmbientTrack]->_soundType = Audio::Mixer::kMusicSoundType; // Start the new ambient retVal = _soundData[kAmbientIndexBase + newAmbientTrack]->start(); } } else { // Load the new ambient if (!fileName.empty()) { if (_soundData[kAmbientIndexBase + newAmbientTrack]->load(fileName)) { // Set some parameters _soundData[kAmbientIndexBase + newAmbientTrack]->_volume = finalVolumeLevel; _soundData[kAmbientIndexBase + newAmbientTrack]->_loop = true; _soundData[kAmbientIndexBase + newAmbientTrack]->_soundType = Audio::Mixer::kMusicSoundType; // Stop the current ambient delete _soundData[kAmbientIndexBase + _lastAmbient]; _soundData[kAmbientIndexBase + _lastAmbient] = new Sound(); // Start the new ambient retVal = _soundData[kAmbientIndexBase + newAmbientTrack]->start(); } } else { // Stop the current ambient delete _soundData[kAmbientIndexBase + _lastAmbient]; _soundData[kAmbientIndexBase + _lastAmbient] = new Sound(); } } // Reset the last ambient index _lastAmbient = newAmbientTrack; // Return success return retVal; } bool SoundManager::adjustAmbientSoundVolume(byte newVolumeLevel, bool fade, byte steps, uint32 fadeLength) { // If we are not playing an ambient track, simply return false if (!_soundData[kAmbientIndexBase + _lastAmbient]->_handle) return false; // Compare the new volume level to the current one, returning success if they are the same if (_soundData[kAmbientIndexBase + _lastAmbient]->_volume == newVolumeLevel) return true; // If we already have any timed channels in the current ambient channel, kill them now if (_soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectIndex != TIMED_EFFECT_NONE) { _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectIndex = TIMED_EFFECT_NONE; _soundData[kAmbientIndexBase + _lastAmbient]->_flags = 0; // clone2727 says: is this right? _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectSteps = 0; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectDelta = 0; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectStart = 0; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectRemaining = 0; } // Switch on whether or not we are fading to the new volume level if (fade) { _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectSteps = steps; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectDelta = ((int)newVolumeLevel - (int)_soundData[kAmbientIndexBase + _lastAmbient]->_volume) / (int)steps; _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectStart = g_system->getMillis(); _soundData[kAmbientIndexBase + _lastAmbient]->_timedEffectRemaining = fadeLength; } else { // We are not fading between the current and new volume levels, so simply change the level // and reset the volume for the sample _soundData[kAmbientIndexBase + _lastAmbient]->_volume = newVolumeLevel; g_system->getMixer()->setChannelVolume(*_soundData[kAmbientIndexBase + _lastAmbient]->_handle, clipVolume(newVolumeLevel << 1)); } // Return success return true; } bool SoundManager::isAmbientSoundPlaying() { return _soundData[kAmbientIndexBase + _lastAmbient]->_handle != nullptr; } bool SoundManager::setSecondaryAmbientSound(const Common::Path &fileName, bool fade, byte finalVolumeLevel) { if (fileName.empty()) return false; // Determine which of the two ambient tracks to use int newAmbientTrack = (_lastAmbient == 0) ? 1 : 0; // If this ambient track is currently in use, stop and kill it now if (_soundData[kAmbientIndexBase + newAmbientTrack]->_handle) { delete _soundData[kAmbientIndexBase + newAmbientTrack]; _soundData[kAmbientIndexBase + newAmbientTrack] = new Sound(); } // Are we flagged for fade? if (fade) { // Load the new ambient if (!_soundData[kAmbientIndexBase + newAmbientTrack]->load(fileName)) return false; // Set the parameters for the new ambient _soundData[kAmbientIndexBase + newAmbientTrack]->_volume = 0; _soundData[kAmbientIndexBase + newAmbientTrack]->_loop = true; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kAmbientIndexBase + newAmbientTrack]->_flags = 0; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectSteps = 16; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectDelta = finalVolumeLevel / 16; _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectStart = g_system->getMillis(); _soundData[kAmbientIndexBase + newAmbientTrack]->_timedEffectRemaining = 2000; _soundData[kAmbientIndexBase + newAmbientTrack]->_soundType = Audio::Mixer::kMusicSoundType; // Start the new ambient return _soundData[kAmbientIndexBase + newAmbientTrack]->start(); } // Load the new ambient if (!_soundData[kAmbientIndexBase + newAmbientTrack]->load(fileName)) return false; // Set some parameters _soundData[kAmbientIndexBase + newAmbientTrack]->_volume = finalVolumeLevel; _soundData[kAmbientIndexBase + newAmbientTrack]->_loop = true; _soundData[kAmbientIndexBase + newAmbientTrack]->_soundType = Audio::Mixer::kMusicSoundType; // Start the new ambient return _soundData[kAmbientIndexBase + newAmbientTrack]->start(); } bool SoundManager::adjustSecondaryAmbientSoundVolume(byte newVolumeLevel, bool fade, byte steps, uint32 fadeLength) { // Determine which of the two ambient tracks to modify int ambientTrack = (_lastAmbient == 0) ? 1 : 0; // If we are not playing an ambient track, simply return false if (!_soundData[kAmbientIndexBase + ambientTrack]->_handle) return false; // Compare the new volume level to the current one, returning success if they are the same if (_soundData[kAmbientIndexBase + ambientTrack]->_volume == newVolumeLevel) return true; // If we already have any timed channels in the current ambient channel, kill them now if (_soundData[kAmbientIndexBase + ambientTrack]->_timedEffectIndex != TIMED_EFFECT_NONE) { _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectIndex = TIMED_EFFECT_NONE; _soundData[kAmbientIndexBase + ambientTrack]->_flags = 0; // clone2727 says: is this right? _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectSteps = 0; _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectDelta = 0; _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectStart = 0; _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectRemaining = 0; } // Switch on whether or not we are fading to the new volume level if (fade) { _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectSteps = steps; _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectDelta = ((int)newVolumeLevel - (int)_soundData[kAmbientIndexBase + ambientTrack]->_volume) / (int)steps; _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectStart = g_system->getMillis(); _soundData[kAmbientIndexBase + ambientTrack]->_timedEffectRemaining = fadeLength; } else { // We are not fading between the current and new volume levels, so simply change the level // and reset the volume for the sample _soundData[kAmbientIndexBase + ambientTrack]->_volume = newVolumeLevel; g_system->getMixer()->setChannelVolume(*_soundData[kAmbientIndexBase + ambientTrack]->_handle, clipVolume(newVolumeLevel << 1)); } // Return success return true; } uint32 SoundManager::getSecondaryAmbientPosition() { int ambientTrack = (_lastAmbient == 0) ? 1 : 0; if (!_soundData[kAmbientIndexBase + ambientTrack]->isPlaying()) return 0; // We need to return the position in *bytes* in the buffer here // So, let's work some magic with this Audio::Timestamp time = g_system->getMixer()->getElapsedTime(*_soundData[kAmbientIndexBase + ambientTrack]->_handle); // Convert to the sound rate before getting to the magic. time = time.convertToFramerate(_soundData[kAmbientIndexBase + ambientTrack]->_soundData->getRate()); // Here's the magic. We're assuming everything is 8-bit, mono. return time.totalNumberOfFrames(); } bool SoundManager::restartSecondaryAmbientSound() { int ambientTrack = (_lastAmbient == 0) ? 1 : 0; if (!_soundData[kAmbientIndexBase + ambientTrack]->isPlaying()) return 0; _soundData[kAmbientIndexBase + ambientTrack]->start(); return true; } bool SoundManager::playSynchronousAIComment(const Common::Path &fileName) { if (_paused) return false; // Load the sound file if (!_soundData[kAIVoiceIndex]->load(fileName)) return false; _soundData[kAIVoiceIndex]->_soundType = Audio::Mixer::kSpeechSoundType; // Play the file bool retVal = _soundData[kAIVoiceIndex]->start(); while (retVal && !_vm->shouldQuit() && _soundData[kAIVoiceIndex]->isPlaying()) { timerCallback(); _vm->yield(nullptr, kAIVoiceIndex); } // Now that is has been played, kill it here and now delete _soundData[kAIVoiceIndex]; _soundData[kAIVoiceIndex] = new Sound(); // Return success return true; } bool SoundManager::playAsynchronousAIComment(const Common::Path &fileName) { if (_paused) return false; // Load the sound file if (!_soundData[kAIVoiceIndex]->load(fileName)) return false; // Set some parameters _soundData[kAIVoiceIndex]->_flags = SOUND_FLAG_DESTROY_AFTER_COMPLETION; _soundData[kAIVoiceIndex]->_volume = 127; _soundData[kAIVoiceIndex]->_soundType = Audio::Mixer::kSpeechSoundType; // Play the file return _soundData[kAIVoiceIndex]->start(); } bool SoundManager::isAsynchronousAICommentPlaying() { if (_paused) return false; return _soundData[kAIVoiceIndex]->isPlaying(); } void SoundManager::stopAsynchronousAIComment() { if (isAsynchronousAICommentPlaying()) { _soundData[kAIVoiceIndex]->stop(); } } int SoundManager::playSoundEffect(const Common::Path &fileName, int volume, bool loop, bool oneShot) { if (fileName.empty()) return -1; if (_paused) return -1; // Make sure that we have a free sound effect channel int effectChannel = -1; if (!_soundData[kEffectsIndexB]->_handle) effectChannel = 1; if (!_soundData[kEffectsIndexA]->_handle) effectChannel = 0; if (effectChannel == -1) return -1; // Reinitialize the structure delete _soundData[kEffectsIndexBase + effectChannel]; _soundData[kEffectsIndexBase + effectChannel] = new Sound(); // Load the sound file if (!_soundData[kEffectsIndexBase + effectChannel]->load(fileName)) return -1; // Set some parameters _soundData[kEffectsIndexBase + effectChannel]->_volume = volume; _soundData[kEffectsIndexBase + effectChannel]->_loop = loop; if (oneShot) _soundData[kEffectsIndexBase + effectChannel]->_flags = SOUND_FLAG_DESTROY_AFTER_COMPLETION; _soundData[kEffectsIndexBase + effectChannel]->_soundType = Audio::Mixer::kSFXSoundType; // Play the file _soundData[kEffectsIndexBase + effectChannel]->start(); // Return the index of the channel used return effectChannel; } bool SoundManager::playSynchronousSoundEffect(const Common::Path &fileName, int volume) { // Reset the cursor Cursor oldCursor = _vm->_gfx->setCursor(kCursorWait); g_system->updateScreen(); // Attempt to start the sound playing using the standard sound effect playback function int soundChannel = playSoundEffect(fileName, volume, false, true); // If the sound channel passed to us was invalid, return false right now if (soundChannel < 0) return false; // Otherwise, assume the sound has started playing and enter a wait and see loop until // the sound finishes playing do { timerCallback(); _vm->yield(nullptr, kEffectsIndexBase + soundChannel); } while (!_vm->shouldQuit() && isSoundEffectPlaying(soundChannel)); // One last callback check timerCallback(); // Reset the cursor _vm->_gfx->setCursor(oldCursor); g_system->updateScreen(); // Return success return true; } bool SoundManager::stopSoundEffect(int effectID) { if (_paused) return false; // Confirm that we have a valid sound effect channel if (effectID < 0 || effectID > 1) return false; // Return the result return _soundData[kEffectsIndexBase + effectID]->stop(); } bool SoundManager::isSoundEffectPlaying(int effectID) { if (_paused) return false; // Confirm that we have a valid sound effect channel if (effectID < 0 || effectID > 1) return false; // Return the result return _soundData[kEffectsIndexBase + effectID]->isPlaying(); } bool SoundManager::adjustSoundEffectSoundVolume(int effectID, byte newVolumeLevel, bool fade, byte steps, uint32 fadeLength) { // Confirm that we have a valid sound effect channel if (effectID < 0 || effectID > 1) return false; // If the effect is not playing, then stop it now if (!_soundData[kEffectsIndexBase + effectID]->isPlaying()) return false; // Compare the new volume level to the current one, returning success if they are the same if (_soundData[kEffectsIndexBase + effectID]->_volume == newVolumeLevel) return true; // If we already have any timed channels in the current effect channel, kill them now if (_soundData[kEffectsIndexBase + effectID]->_timedEffectIndex != TIMED_EFFECT_NONE) { _soundData[kEffectsIndexBase + effectID]->_timedEffectIndex = TIMED_EFFECT_NONE; _soundData[kEffectsIndexBase + effectID]->_flags = 0; // clone2727 says: is this right? _soundData[kEffectsIndexBase + effectID]->_timedEffectSteps = 0; _soundData[kEffectsIndexBase + effectID]->_timedEffectDelta = 0; _soundData[kEffectsIndexBase + effectID]->_timedEffectStart = 0; _soundData[kEffectsIndexBase + effectID]->_timedEffectRemaining = 0; } // Switch on whether or not we are fading to the new volume level if (fade) { _soundData[kEffectsIndexBase + effectID]->_timedEffectIndex = TIMED_EFFECT_VOLUME; _soundData[kEffectsIndexBase + effectID]->_timedEffectSteps = steps; _soundData[kEffectsIndexBase + effectID]->_timedEffectDelta = ((int)newVolumeLevel - (int)_soundData[kEffectsIndexBase + effectID]->_volume) / (int)steps; _soundData[kEffectsIndexBase + effectID]->_timedEffectStart = g_system->getMillis(); _soundData[kEffectsIndexBase + effectID]->_timedEffectRemaining = fadeLength; } else { // We are not fading between the current and new volume levels, so simply change the level // and reset the volume for the sample _soundData[kEffectsIndexBase + effectID]->_volume = newVolumeLevel; g_system->getMixer()->setChannelVolume(*_soundData[kEffectsIndexBase + effectID]->_handle, clipVolume(newVolumeLevel << 1)); } // Return success return true; } bool SoundManager::playInterfaceSound(const Common::Path &fileName) { if (_paused) return false; // If we have a sound playing, stop and destroy it if (_soundData[kInterfaceIndex]->_handle) { delete _soundData[kInterfaceIndex]; _soundData[kInterfaceIndex] = new Sound(); } // Load the sound file if (!_soundData[kInterfaceIndex]->load(fileName)) return false; _soundData[kInterfaceIndex]->_flags = SOUND_FLAG_DESTROY_AFTER_COMPLETION; _soundData[kInterfaceIndex]->_soundType = Audio::Mixer::kSFXSoundType; // Play the file return _soundData[kInterfaceIndex]->start(); } bool SoundManager::stopInterfaceSound() { if (_paused) return false; // Stop the sound delete _soundData[kInterfaceIndex]; _soundData[kInterfaceIndex] = new Sound(); return true; } bool SoundManager::isInterfaceSoundPlaying() { if (_paused) return false; return _soundData[kInterfaceIndex]->isPlaying(); } bool SoundManager::startFootsteps(int footstepsID) { if (_paused) return false; // Check the passed ID if (footstepsID < 0) return false; // Compare the ID against the current ID if (_fileIDFootsteps != footstepsID) { // Swap the current footsteps ID _fileIDFootsteps = footstepsID; // Dispose and reinitialize the current footsteps sample delete _soundData[kFootstepsIndex]; _soundData[kFootstepsIndex] = new Sound(); // Load the footsteps sample data and modify the internal flags _soundData[kFootstepsIndex]->load(_vm->getFilePath(IDS_FOOTSTEPS_FILENAME_BASE + footstepsID)); _soundData[kFootstepsIndex]->_loop = true; _soundData[kFootstepsIndex]->_soundType = Audio::Mixer::kSFXSoundType; } // Play the footsteps _soundData[kFootstepsIndex]->start(); // Return success return true; } bool SoundManager::stopFootsteps() { if (_paused) return false; // Make sure that if the footsteps are currently playing, they are stopped _soundData[kFootstepsIndex]->stop(); // Return success return true; } bool SoundManager::stop() { if (_paused) return true; // Stop any playing sounds, but keep them in memory for (int i = 0; i < kMaxSounds; i++) { if (_soundData[i]->stop()) { if (i < 2) { _soundData[i]->_wasPlaying = true; } else { delete _soundData[i]; _soundData[i] = new Sound(); } } } _paused = true; return true; } void SoundManager::stopSound(int soundId) { _soundData[soundId]->stop(); } bool SoundManager::restart() { if (!_paused) return true; // Check all samples, and if they were playing, restart them now for (int i = 0; i < kMaxSounds; i++) { if (_soundData[i]->_wasPlaying) { _soundData[i]->start(); _soundData[i]->_wasPlaying = false; } } _paused = false; // Return success return true; } void SoundManager::timerCallback() { if (_paused) return; // Check the playing sounds, and if they are playing, check for time-based effects for (int i = 0; i < kMaxSounds; i++) { if (_soundData[i]->_handle) { if (_soundData[i]->_timedEffectIndex != TIMED_EFFECT_NONE) { // Has the right amount of time passed for a change to be made? if (g_system->getMillis() >= (_soundData[i]->_timedEffectStart + (_soundData[i]->_timedEffectRemaining / _soundData[i]->_timedEffectSteps))) { // Change the specified member and notify the mixer of the change if (_soundData[i]->_timedEffectIndex == TIMED_EFFECT_VOLUME) { _soundData[i]->_volume += _soundData[i]->_timedEffectDelta; g_system->getMixer()->setChannelVolume(*_soundData[i]->_handle, clipVolume(_soundData[i]->_volume << 1)); } // Update the start time, step counter, and remaining time _soundData[i]->_timedEffectRemaining -= (_soundData[i]->_timedEffectRemaining / _soundData[i]->_timedEffectSteps); _soundData[i]->_timedEffectStart = g_system->getMillis(); _soundData[i]->_timedEffectSteps--; // If the effect has finished, then remove the transition type if (_soundData[i]->_timedEffectSteps == 0) { // If we are flagged for destruction after completion, do it now if (_soundData[i]->_flags & SOUND_FLAG_DESTROY_AFTER_COMPLETION) { delete _soundData[i]; _soundData[i] = new Sound(); } // Reset effect data _soundData[i]->_timedEffectIndex = TIMED_EFFECT_NONE; _soundData[i]->_flags = 0; _soundData[i]->_timedEffectSteps = 0; _soundData[i]->_timedEffectDelta = 0; _soundData[i]->_timedEffectStart = 0; _soundData[i]->_timedEffectRemaining = 0; } } } else { // Even though we are not flagged for a timed effect, is this sound flagged for destruction? if ((_soundData[i]->_flags & SOUND_FLAG_DESTROY_AFTER_COMPLETION) && !_soundData[i]->isPlaying()) { delete _soundData[i]; _soundData[i] = new Sound(); } } } } } SoundManager::Sound::Sound() { _soundData = nullptr; _handle = nullptr; _volume = 127; _loop = false; _flags = 0; _timedEffectIndex = TIMED_EFFECT_NONE; _timedEffectSteps = 0; _timedEffectDelta = 0; _timedEffectStart = 0; _timedEffectRemaining = 0; _wasPlaying = false; _soundType = Audio::Mixer::kPlainSoundType; } SoundManager::Sound::~Sound() { stop(); delete _soundData; } bool SoundManager::Sound::load(const Common::Path &fileName) { if (fileName.empty()) return false; Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); if (!stream) return false; _soundData = Audio::makeWAVStream(stream, DisposeAfterUse::YES); return _soundData != nullptr; } bool SoundManager::Sound::start() { if (!_soundData) return false; stop(); _handle = new Audio::SoundHandle(); Audio::AudioStream *audioStream = _soundData; DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO; _soundData->rewind(); if (_loop) { audioStream = new Audio::LoopingAudioStream(_soundData, 0, DisposeAfterUse::NO); disposeAfterUse = DisposeAfterUse::YES; } g_system->getMixer()->playStream(_soundType, _handle, audioStream, -1, clipVolume(_volume << 1), 0, disposeAfterUse); return true; } bool SoundManager::Sound::isPlaying() const { return _soundData && _handle && g_system->getMixer()->isSoundHandleActive(*_handle); } bool SoundManager::Sound::stop() { if (!isPlaying()) return false; g_system->getMixer()->stopHandle(*_handle); delete _handle; _handle = nullptr; return true; } void SoundManager::Sound::pause(bool shouldPause) { if (_soundData && _handle) g_system->getMixer()->pauseHandle(*_handle, shouldPause); } } // End of namespace Buried