diff --git a/doomsday/apps/client/include/audio/system.h b/doomsday/apps/client/include/audio/system.h index 5f42e68442..88f6ef3d26 100644 --- a/doomsday/apps/client/include/audio/system.h +++ b/doomsday/apps/client/include/audio/system.h @@ -235,6 +235,9 @@ class System : public de::System void aboutToUnloadMap(); void worldMapChanged(); + /// @todo refactor away. + void clearLogical(); + /** * Provides mutable access to the sound sample cache (waveforms). */ @@ -268,6 +271,14 @@ class System : public de::System #endif // __CLIENT__ + /** + * The sound is entered into the list of playing sounds. Called when a 'world class' + * sound is started, regardless of whether it's actually started on the local system. + * + * @todo Should not be exposed to users of this class. -ds + */ + void startLogical(int soundId, struct mobj_s *emitter); + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/apps/client/src/audio/system.cpp b/doomsday/apps/client/src/audio/system.cpp index 4ac68a11d5..35344ecb3f 100644 --- a/doomsday/apps/client/src/audio/system.cpp +++ b/doomsday/apps/client/src/audio/system.cpp @@ -1,7 +1,7 @@ /** @file system.cpp Audio subsystem module. * * @authors Copyright © 2003-2013 Jaakko Keränen - * @authors Copyright © 2006-2015 Daniel Swanson + * @authors Copyright © 2005-2015 Daniel Swanson * @authors Copyright © 2006-2007 Jamie Jones * * * @par License @@ -45,7 +45,6 @@ #include "Sector" #include "SectorCluster" -#include #include #include #ifdef __CLIENT__ @@ -54,10 +53,11 @@ # include #endif #include +#include +#include #ifdef __CLIENT__ # include # include -# include #endif using namespace de; @@ -559,6 +559,259 @@ DENG2_PIMPL(System) #endif // __CLIENT__ SfxSampleCache sfxSampleCache; ///< @todo should be __CLIENT__ only. + /** + * The Logical Sound Manager. Tracks all currently playing sounds in the + * world, regardless of whether Sfx is available or if the sounds are actually + * audible to anyone. + * + * Uses PU_MAP, so this has to be inited for every map. (Done via S_MapChange()). + * + * @todo The premise behind this functionality is fundamentally flawed in + * that it assumes that the same samples are used by both the Client and + * the Server, and that the later schedules remote playback of the former + * (determined by examining sample lengths on Server side). + * + * Furthermore, the Server should not be dictating 'oneSoundPerEmitter' + * policy so that Clients can be configured independently. + */ + struct LSM + { + static duint const PURGE_INTERVAL = 2000; ///< 2 seconds + static dint const LOGIC_HASH_SIZE = 64; + + struct LogicSound + { + LogicSound *next, *prev; + + dint soundId; + mobj_t *emitter; + duint endTime; + bool isRepeating; + }; + struct LogicHash + { + LogicSound *first, *last; + }; + LogicHash logicHash[LOGIC_HASH_SIZE]; ///< key: soundId + + duint lastPurge = 0; + bool oneSoundPerEmitter = false; ///< set at the start of the frame + duint (*soundLengthCallback)(dint) = nullptr; + + LSM::LSM() { zap(logicHash); } + + /** + * Initialize the Logical Sound Manager for a new map. + */ + void init() + { + // Memory in the hash table is PU_MAP, so this is acceptable. + zap(logicHash); + } + + /** + * Remove stopped logical sounds from the hash. + */ + void purge() + { + static duint lastTime = 0; + + // Too soon for a purge? + duint const nowTime = Timer_RealMilliseconds(); + if(nowTime - lastTime < PURGE_INTERVAL) return; + + // Peform the purge now. + lastTime = nowTime; + + // Check all sounds in the hash. + for(dint i = 0; i < LOGIC_HASH_SIZE; ++i) + { + LogicSound *it = logicHash[i].first; + while(it) + { + LogicSound *next = it->next; + if(!it->isRepeating && it->endTime < nowTime) + { + // This has stopped. + destroyLogicSound(it); + } + it = next; + } + } + } + + /** + * The sound is entered into the list of playing sounds. Called when a + * 'world class' sound is started, regardless of whether it's actually + * started on the local system. + */ + void start(dint soundId, mobj_t *emitter) + { + bool const isRepeating = Def_SoundIsRepeating(soundId); + + DENG2_ASSERT(soundLengthCallback != nullptr); + duint const length = (isRepeating ? 1 : soundLengthCallback(soundId)); + if(!length) + { + // This is not a valid sound. + return; + } + + // Only one sound per emitter? + if(emitter && oneSoundPerEmitter) + { + // Stop all other sounds. + stop(0, emitter); + } + + soundId &= ~DDSF_FLAG_MASK; + + LogicSound *node = newLogicSound(soundId); + node->emitter = emitter; + node->isRepeating = isRepeating; + node->endTime = Timer_RealMilliseconds() + length; + } + + /** + * The sound is removed from the list of playing sounds. Called whenever + * a sound is stopped, regardless of whether it was actually playing on + * the local system. + * + * @note If @a soundId == 0 and @a emitter == nullptr then stop everything. + * + * @return Number of sounds stopped. + */ + dint stop(dint soundId, mobj_t *emitter) + { + dint stopCount = 0; + + if(soundId) + { + LogicSound *it = getLogicHash(soundId).first; + while(it) + { + LogicSound *next = it->next; + if(it->soundId == soundId && it->emitter == emitter) + { + destroyLogicSound(it); + stopCount++; + } + it = next; + } + } + else + { + // Browse through the entire hash. + for(dint i = 0; i < LOGIC_HASH_SIZE; ++i) + { + LogicSound *it = logicHash[i].first; + while(it) + { + LogicSound *next = it->next; + if(!emitter || it->emitter == emitter) + { + destroyLogicSound(it); + stopCount++; + } + it = next; + } + } + } + + return stopCount; + } + + /** + * Returns true if the sound is currently playing somewhere in the world. It doesn't + * matter if it's audible or not. + * + * @param id @c 0= true if any sounds are playing using the specified @a emitter. + */ + bool isPlaying(dint soundId, mobj_t *emitter) + { + duint const nowTime = Timer_RealMilliseconds(); + + if(soundId) + { + for(LogicSound *it = getLogicHash(soundId).first; it; it = it->next) + { + if(it->soundId == soundId && it->emitter == emitter && + (it->endTime > nowTime || it->isRepeating)) + { + // This one is still playing. + return true; + } + } + } + else if(emitter) + { + // Check if the origin is playing any sound. + for(dint i = 0; i < LOGIC_HASH_SIZE; ++i) + for(LogicSound *it = logicHash[i].first; it; it = it->next) + { + if(it->emitter == emitter && (it->endTime > nowTime || it->isRepeating)) + { + // This one is playing. + return true; + } + } + } + + // The sound was not found. + return false; + } + + LogicHash &getLogicHash(dint soundId) + { + return logicHash[(duint) soundId % LOGIC_HASH_SIZE]; + } + + /** + * Create and link a new logical sound hash table node. + */ + LogicSound *newLogicSound(dint soundId) + { + auto *node = (LogicSound *) Z_Calloc(sizeof(LogicSound), PU_MAP, nullptr); + + LogicHash &hash = getLogicHash(soundId); + if(hash.last) + { + hash.last->next = node; + node->prev = hash.last; + } + hash.last = node; + if(!hash.first) + hash.first = node; + + node->soundId = soundId; + return node; + } + + /** + * Unlink and destroy a logical sound hash table node. + */ + void destroyLogicSound(LogicSound *node) + { + if(!node) return; + + LogicHash &hash = getLogicHash(node->soundId); + if(hash.first == node) + hash.first = node->next; + if(hash.last == node) + hash.last = node->prev; + + if(node->next) + node->next->prev = node->prev; + if(node->prev) + node->prev->next = node->next; + +#ifdef DENG2_DEBUG + std::memset(node, 0xDD, sizeof(*node)); +#endif + Z_Free(node); + } + } lsm; + Instance(Public *i) : Base(i) { theAudioSystem = thisPublic; @@ -1350,8 +1603,8 @@ void System::startFrame() #endif // Remove stopped sounds from the LSM. - Sfx_Logical_SetOneSoundPerEmitter(::sfxOneSoundPerEmitter); - Sfx_PurgeLogical(); + d->lsm.oneSoundPerEmitter = ::sfxOneSoundPerEmitter; + d->lsm.purge(); } #ifdef __CLIENT__ @@ -1412,7 +1665,7 @@ void System::initPlayback() /// of the samples? It is entirely possible that the Client is using a different /// set of samples so using this information on server side (for scheduling of /// remote playback events?) is not logical. -ds - Sfx_Logical_SetSampleLengthCallback(maybeCacheSampleAndReturnLength); + d->lsm.soundLengthCallback = maybeCacheSampleAndReturnLength; CommandLine &cmdLine = App::commandLine(); if(cmdLine.has("-nosound") || cmdLine.has("-noaudio")) @@ -1659,8 +1912,7 @@ void System::setSfxListener(mobj_t *newListener) bool System::soundIsPlaying(dint soundId, mobj_t *emitter) const { - // The Logical Sound Manager (under Sfx) provides a routine for this. - return Sfx_IsPlaying(soundId, emitter); + return d->lsm.isPlaying(soundId, emitter); #if 0 if(!d->sfxAvail) return false; @@ -1790,7 +2042,7 @@ void System::stopSound(dint soundId, mobj_t *emitter, dint flags) #endif // Notify the LSM. - if(Sfx_StopLogical(soundId, emitter)) + if(d->lsm.stop(soundId, emitter)) { #ifdef __SERVER__ // In netgames, the server is responsible for telling clients @@ -2170,10 +2422,20 @@ void System::requestSfxListenerUpdate() #endif +void System::clearLogical() +{ + d->lsm.init(); +} + +void System::startLogical(dint soundId, mobj_t *emitter) +{ + d->lsm.start(soundId, emitter); +} + void System::aboutToUnloadMap() { // Stop everything in the LSM. - Sfx_InitLogical(); + d->lsm.init(); #ifdef __CLIENT__ // Mobjs are about to be destroyed so stop all sound channels using one as an emitter. @@ -2588,25 +2850,25 @@ dint S_LocalSoundFrom(dint soundId, coord_t *fixedPos) return S_LocalSoundAtVolumeFrom(soundId, nullptr, fixedPos, 1); } -dint S_StartSound(dint soundId, mobj_t *origin) +dint S_StartSound(dint soundId, mobj_t *emitter) { #ifdef __SERVER__ // The sound is audible to everybody. - Sv_Sound(soundId, origin, SVSF_TO_ALL); + Sv_Sound(soundId, emitter, SVSF_TO_ALL); #endif - Sfx_StartLogical(soundId, origin, Def_SoundIsRepeating(soundId)); + App_AudioSystem().startLogical(soundId, emitter); - return S_LocalSound(soundId, origin); + return S_LocalSound(soundId, emitter); } -dint S_StartSoundEx(dint soundId, mobj_t *origin) +dint S_StartSoundEx(dint soundId, mobj_t *emitter) { #ifdef __SERVER__ - Sv_Sound(soundId, origin, SVSF_TO_ALL | SVSF_EXCLUDE_ORIGIN); + Sv_Sound(soundId, emitter, SVSF_TO_ALL | SVSF_EXCLUDE_ORIGIN); #endif - Sfx_StartLogical(soundId, origin, Def_SoundIsRepeating(soundId)); + App_AudioSystem().startLogical(soundId, emitter); - return S_LocalSound(soundId, origin); + return S_LocalSound(soundId, emitter); } dint S_StartSoundAtVolume(dint soundId, mobj_t *emitter, dfloat volume) @@ -2614,7 +2876,7 @@ dint S_StartSoundAtVolume(dint soundId, mobj_t *emitter, dfloat volume) #ifdef __SERVER__ Sv_SoundAtVolume(soundId, emitter, volume, SVSF_TO_ALL); #endif - Sfx_StartLogical(soundId, emitter, Def_SoundIsRepeating(soundId)); + App_AudioSystem().startLogical(soundId, emitter); // The sound is audible to everybody. return S_LocalSoundAtVolume(soundId, emitter, volume); diff --git a/doomsday/apps/client/src/dd_main.cpp b/doomsday/apps/client/src/dd_main.cpp index 4e55aec00d..43b37e9b74 100644 --- a/doomsday/apps/client/src/dd_main.cpp +++ b/doomsday/apps/client/src/dd_main.cpp @@ -51,7 +51,6 @@ #ifdef __CLIENT__ # include #endif -#include #include #include #include @@ -1432,7 +1431,7 @@ bool App_ChangeGame(Game &game, bool allowReload) App_ResourceSystem().clearAllAnimGroups(); App_ResourceSystem().clearAllColorPalettes(); - Sfx_InitLogical(); + App_AudioSystem().clearLogical(); Con_ClearDatabases(); diff --git a/doomsday/apps/libdoomsday/include/doomsday/audio/logical.h b/doomsday/apps/libdoomsday/include/doomsday/audio/logical.h deleted file mode 100644 index 3b95d490aa..0000000000 --- a/doomsday/apps/libdoomsday/include/doomsday/audio/logical.h +++ /dev/null @@ -1,84 +0,0 @@ -/** @file logical.h Logical Sound Manager. - * - * The Logical Sound Manager. Tracks all currently playing sounds in the world, - * regardless of whether Sfx is available or if the sounds are actually audible - * to anyone. - * - * Uses PU_MAP, so this has to be inited for every map. (Done via S_MapChange()). - * - * @todo This should be part of an audio system base class that can be used - * both by the client and the server. -jk - - * @authors Copyright © 2003-2013 Jaakko Keränen - * @authors Copyright © 2015 Daniel Swanson - * - * 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 St, Fifth Floor, - * Boston, MA 02110-1301 USA - */ - -#ifndef LIBDOOMSDAY_AUDIO_LOGICAL_H -#define LIBDOOMSDAY_AUDIO_LOGICAL_H - -#include "../libdoomsday.h" -#include "../world/mobj.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Initialize the Logical Sound Manager for a new map. - */ -LIBDOOMSDAY_PUBLIC void Sfx_InitLogical(void); - -/** - * Remove stopped logical sounds from the hash. - */ -LIBDOOMSDAY_PUBLIC void Sfx_PurgeLogical(void); - -/** - * The sound is entered into the list of playing sounds. Called when a 'world class' - * sound is started, regardless of whether it's actually started on the local system. - */ -LIBDOOMSDAY_PUBLIC void Sfx_StartLogical(int id, mobj_t *origin, dd_bool isRepeating); - -/** - * The sound is removed from the list of playing sounds. Called whenever a sound is - * stopped, regardless of whether it was actually playing on the local system. - * - * @note If @a id == 0 and @a origin == 0 then stop everything. - * - * @return Number of sounds stopped. - */ -LIBDOOMSDAY_PUBLIC int Sfx_StopLogical(int id, mobj_t *origin); - -/** - * Returns true if the sound is currently playing somewhere in the world. - * It doesn't matter if it's audible or not. - * - * @param id @c 0= true if any sounds are playing using the specified @a origin. - */ -LIBDOOMSDAY_PUBLIC dd_bool Sfx_IsPlaying(int id, mobj_t *origin); - -LIBDOOMSDAY_PUBLIC void Sfx_Logical_SetOneSoundPerEmitter(dd_bool enabled); - -LIBDOOMSDAY_PUBLIC void Sfx_Logical_SetSampleLengthCallback(uint (*callback)(int)); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // LIBDOOMSDAY_AUDIO_LOGICAL_H diff --git a/doomsday/apps/libdoomsday/src/audio/logical.cpp b/doomsday/apps/libdoomsday/src/audio/logical.cpp deleted file mode 100644 index 65e9e36d84..0000000000 --- a/doomsday/apps/libdoomsday/src/audio/logical.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/** @file logical.cpp Logical Sound Manager. - * - * @authors Copyright © 2003-2013 Jaakko Keränen - * @authors Copyright © 2015 Daniel Swanson - * - * @par License - * GPL: http://www.gnu.org/licenses/gpl.html - * - * 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, see: - * http://www.gnu.org/licenses - */ - -#include "doomsday/audio/logical.h" -#include -#include - -using namespace de; - -// The logical sounds hash table uses sound IDs as keys. -#define LOGIC_HASH_SIZE ( 64 ) - -#define PURGE_INTERVAL ( 2000 ) ///< 2 seconds - -struct logicsound_t -{ - logicsound_t *next, *prev; - - dint id; - mobj_t *origin; - duint endTime; - bool isRepeating; -}; - -struct logichash_t -{ - logicsound_t *first, *last; -}; -static logichash_t logicHash[LOGIC_HASH_SIZE]; - -static dd_bool logicalOneSoundPerEmitter; - -static duint (*logicalSoundLengthCallback)(dint); - -static logichash_t &getLogicHash(dint id) -{ - return ::logicHash[(duint) id % LOGIC_HASH_SIZE]; -} - -/** - * Create and link a new logical sound hash table node. - */ -static logicsound_t *newLogicSound(dint id) -{ - auto *node = reinterpret_cast(Z_Calloc(sizeof(logicsound_t), PU_MAP, nullptr)); - - logichash_t &hash = getLogicHash(id); - if(hash.last) - { - hash.last->next = node; - node->prev = hash.last; - } - hash.last = node; - if(!hash.first) - hash.first = node; - - node->id = id; - return node; -} - -/** - * Unlink and destroy a logical sound hash table node. - */ -static void destroyLogicSound(logicsound_t *node) -{ - if(!node) return; - - logichash_t &hash = getLogicHash(node->id); - if(hash.first == node) - hash.first = node->next; - if(hash.last == node) - hash.last = node->prev; - - if(node->next) - node->next->prev = node->prev; - if(node->prev) - node->prev->next = node->next; - -#ifdef DENG2_DEBUG - std::memset(node, 0xDD, sizeof(*node)); -#endif - Z_Free(node); -} - -void Sfx_Logical_SetOneSoundPerEmitter(dd_bool enabled) -{ - ::logicalOneSoundPerEmitter = enabled; -} - -void Sfx_Logical_SetSampleLengthCallback(duint (*callback)(dint)) -{ - ::logicalSoundLengthCallback = callback; -} - -void Sfx_InitLogical() -{ - // Memory in the hash table is PU_MAP, so this is acceptable. - std::memset(::logicHash, 0, sizeof(::logicHash)); -} - -void Sfx_StartLogical(dint id, mobj_t *origin, dd_bool isRepeating) -{ - DENG2_ASSERT(::logicalSoundLengthCallback != nullptr); - - duint const length = (isRepeating ? 1 : ::logicalSoundLengthCallback(id)); - if(!length) - { - // This is not a valid sound. - return; - } - - if(origin && ::logicalOneSoundPerEmitter) - { - // Stop all previous sounds from this origin (only one per origin). - Sfx_StopLogical(0, origin); - } - - id &= ~DDSF_FLAG_MASK; - - logicsound_t *node = newLogicSound(id); - node->origin = origin; - node->isRepeating = CPP_BOOL(isRepeating); - node->endTime = Timer_RealMilliseconds() + length; -} - -dint Sfx_StopLogical(dint id, mobj_t *origin) -{ - dint stopCount = 0; - - if(id) - { - logicsound_t *it = getLogicHash(id).first; - while(it) - { - logicsound_t *next = it->next; - if(it->id == id && it->origin == origin) - { - destroyLogicSound(it); - stopCount++; - } - it = next; - } - } - else - { - // Browse through the entire hash. - for(dint i = 0; i < LOGIC_HASH_SIZE; ++i) - { - logicsound_t *it = logicHash[i].first; - while(it) - { - logicsound_t *next = it->next; - if(!origin || it->origin == origin) - { - destroyLogicSound(it); - stopCount++; - } - it = next; - } - } - } - - return stopCount; -} - -void Sfx_PurgeLogical() -{ - static duint lastTime = 0; - - // Too soon for a purge? - duint const nowTime = Timer_RealMilliseconds(); - if(nowTime - lastTime < PURGE_INTERVAL) return; - - // Peform the purge now. - lastTime = nowTime; - - // Check all sounds in the hash. - for(dint i = 0; i < LOGIC_HASH_SIZE; ++i) - { - logicsound_t *it = logicHash[i].first; - while(it) - { - logicsound_t *next = it->next; - if(!it->isRepeating && it->endTime < nowTime) - { - // This has stopped. - destroyLogicSound(it); - } - it = next; - } - } -} - -dd_bool Sfx_IsPlaying(dint id, mobj_t *origin) -{ - duint const nowTime = Timer_RealMilliseconds(); - - if(id) - { - for(logicsound_t *it = getLogicHash(id).first; it; it = it->next) - { - if(it->id == id && it->origin == origin && - (it->endTime > nowTime || it->isRepeating)) - { - // This one is still playing. - return true; - } - } - } - else if(origin) - { - // Check if the origin is playing any sound. - for(dint i = 0; i < LOGIC_HASH_SIZE; ++i) - for(logicsound_t *it = logicHash[i].first; it; it = it->next) - { - if(it->origin == origin && (it->endTime > nowTime || it->isRepeating)) - { - // This one is playing. - return true; - } - } - } - - // The sound was not found. - return false; -}