diff --git a/mangle b/mangle index 469237549..87f6e7397 160000 --- a/mangle +++ b/mangle @@ -1 +1 @@ -Subproject commit 4692375491fb16da59642022c8d7c891d68ba665 +Subproject commit 87f6e739759244cef8e9730b8f94e628b8d64681 diff --git a/misc/list.hpp b/misc/list.hpp new file mode 100644 index 000000000..07e5ddc8d --- /dev/null +++ b/misc/list.hpp @@ -0,0 +1,110 @@ +#ifndef MISC_LIST_H +#define MISC_LIST_H + +#include + +namespace Misc{ + +/* + This is just a suggested data structure for List. You can use + anything that has next and prev pointers. + */ +template +struct ListElem +{ + X data; + ListElem *next; + ListElem *prev; +}; + +/* + A generic class that contains a doubly linked list of elements. It + does not do any allocation of elements, it just keeps pointers to + them. +*/ +template +struct List +{ + List() : head(0), tail(0), totalNum(0) {} + + // Insert an element at the end of the list. The element cannot be + // part of any other list when this is called. + void insert(Elem *p) + { + if(tail) + { + // There are existing elements. Insert the node at the end of + // the list. + assert(head && totalNum > 0); + tail->next = p; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = p; + } + + // These have to be done in either case + p->prev = tail; + p->next = 0; + tail = p; + + totalNum++; + } + + // Remove element from the list. The element MUST be part of the + // list when this is called. + void remove(Elem *p) + { + assert(totalNum > 0); + + if(p->next) + { + // There's an element following us. Set it up correctly. + p->next->prev = p->prev; + assert(tail && tail != p); + } + else + { + // We're the tail + assert(tail == p); + tail = p->prev; + } + + // Now do exactly the same for the previous element + if(p->prev) + { + p->prev->next = p->next; + assert(head && head != p); + } + else + { + assert(head == p); + head = p->next; + } + + totalNum--; + } + + // Pop the first element off the list + Elem *pop() + { + Elem *res = getHead(); + if(res) remove(res); + return res; + } + + Elem* getHead() { return head; } + Elem* getTail() { return tail; } + unsigned int getNum() { return totalNum; } + +private: + + Elem *head; + Elem *tail; + unsigned int totalNum; +}; + +} +#endif diff --git a/sound/sndmanager.cpp b/sound/sndmanager.cpp new file mode 100644 index 000000000..d05dc1a49 --- /dev/null +++ b/sound/sndmanager.cpp @@ -0,0 +1,208 @@ +#include "sndmanager.hpp" + +#include +#include + +using namespace OEngine::Sound; +using namespace Mangle::Sound; + +/** This is our own internal implementation of the + Mangle::Sound::Sound interface. This class links a SoundPtr to + itself and prevents itself from being deleted as long as the sound + is playing. + */ +struct OEngine::Sound::ManagedSound : SoundFilter +{ +private: + /** Who's your daddy? This is set if and only if we are listed + internally in the given SoundManager. + + It may be NULL if the manager has been deleted but the user + keeps their own SoundPtrs to the object. + */ + SoundManager *mgr; + + /** Keep a weak pointer to ourselves, which we convert into a + 'strong' pointer when we are playing. When 'self' is pointing to + ourselves, the object will never be deleted. + + This is used to make sure the sound is not deleted while + playing, unless it is explicitly ordered to do so by the + manager. + + TODO: This kind of construct is useful. If we need it elsewhere + later, template it. It would be generally useful in any system + where we poll to check if a resource is still needed, but where + manual references are allowed. + */ + typedef boost::weak_ptr WSoundPtr; + WSoundPtr weak; + SoundPtr self; + + // Keep this object from being deleted + void lock() + { + self = SoundPtr(weak); + } + + // Release the lock. This may or may not delete the object. Never do + // anything after calling unlock()! + void unlock() + { + self.reset(); + } + +public: + // Used for putting ourselves in linked lists + ManagedSound *next, *prev; + + /** Detach this sound from its manager. This means that the manager + will no longer know we exist. Typically only called when either + the sound or the manager is about to get deleted. + + Since this means update() will no longer be called, we also have + to unlock the sound manually since it will no longer be able to + do that itself. This means that the sound may be deleted, even + if it is still playing, when the manager is deleted. + + However, you are still allowed to keep and manage your own + SoundPtr references, but the lock/unlock system is disabled + after the manager is gone. + */ + void detach() + { + if(mgr) + { + mgr->detach(this); + mgr = NULL; + } + + // Unlock must be last command. Object may get deleted at this + // point. + unlock(); + } + + ManagedSound(SoundPtr snd, SoundManager *mg) + : SoundFilter(snd), mgr(mg) + {} + ~ManagedSound() { detach(); } + + // Needed to set up the weak pointer + void setup(SoundPtr self) + { + weak = WSoundPtr(self); + } + + // Override play() to mark the object as locked + void play() + { + SoundFilter::play(); + + // Lock the object so that it is not deleted while playing. Only + // do this if we have a manager, otherwise the object will never + // get unlocked. + if(mgr) lock(); + } + + // Override stop() and pause() + + // Called regularly by the manager + void update() + { + // If we're no longer playing, don't force object retention. + if(!isPlaying()) + unlock(); + + // unlock() may delete the object, so don't do anything below this + // point. + } + + // Not implemented yet + SoundPtr clone() const + { return SoundPtr(); } +}; + +struct SoundManager::SoundManagerList +{ +private: + // A linked list of ManagedSound objects. + typedef Misc::List SoundList; + SoundList list; + +public: + // Add a new sound to the list + void addNew(ManagedSound* snd) + { + list.insert(snd); + } + + // Remove a sound from the list + void remove(ManagedSound *snd) + { + list.remove(snd); + } + + // Number of sounds in the list + int numSounds() { return list.getNum(); } + + // Update all sounds + void updateAll() + { + for(ManagedSound *s = list.getHead(); s != NULL; s=s->next) + s->update(); + } + + // Detach and unlock all sounds + void detachAll() + { + for(ManagedSound *s = list.getHead(); s != NULL; s=s->next) + s->detach(); + } +}; + +SoundManager::SoundManager(SoundFactoryPtr fact) + : FactoryFilter(fact) +{ + needsUpdate = true; + list = new SoundManagerList; +} + +SoundManager::~SoundManager() +{ + // Detach all sounds + list->detachAll(); +} + +SoundPtr SoundManager::wrap(SoundPtr client) +{ + // Create and set up the sound wrapper + ManagedSound *snd = new ManagedSound(client,this); + SoundPtr ptr(snd); + snd->setup(ptr); + + // Add ourselves to the list of all sounds + list->addNew(snd); + + return ptr; +} + +// Remove the sound from this manager. +void SoundManager::detach(ManagedSound *sound) +{ + list->remove(sound); +} + +int SoundManager::numSounds() +{ + return list->numSounds(); +} + +void SoundManager::update() +{ + // Update all the sounds we own + list->updateAll(); + + // Update the source if it needs it + if(client->needsUpdate) + client->update(); +} diff --git a/sound/sndmanager.hpp b/sound/sndmanager.hpp new file mode 100644 index 000000000..5ea0c4fc3 --- /dev/null +++ b/sound/sndmanager.hpp @@ -0,0 +1,93 @@ +#ifndef OENGINE_SOUND_MANAGER_H +#define OENGINE_SOUND_MANAGER_H + +#include + +namespace OEngine +{ + namespace Sound + { + using namespace Mangle::Sound; + + class ManagedSound; + + /** A manager of Mangle::Sounds. + + The sound manager is a wrapper around the more low-level + SoundFactory - although it is also itself an implementation of + SoundFactory. It will: + - keep a list of all created sounds + - let you iterate the list + - keep references to playing sounds so you don't have to + - auto-release references to sounds that are finished playing + (ie. deleting them if you're not referencing them) + */ + class SoundManager : public FactoryFilter + { + // Shove the implementation details into the cpp file. + struct SoundManagerList; + SoundManagerList *list; + + // Create a new sound wrapper based on the given source sound. + SoundPtr wrap(SoundPtr snd); + + /** Internal function. Will completely disconnect the given + sound from this manager. Called from ManagedSound. + */ + friend class ManagedSound; + void detach(ManagedSound *sound); + public: + SoundManager(SoundFactoryPtr fact); + ~SoundManager(); + void update(); + + /// Get number of sounds currently managed by this manager. + int numSounds(); + + SoundPtr loadRaw(SampleSourcePtr input) + { return wrap(client->loadRaw(input)); } + + SoundPtr load(Mangle::Stream::StreamPtr input) + { return wrap(client->load(input)); } + + SoundPtr load(const std::string &file) + { return wrap(client->load(file)); } + + // Play a sound immediately, and release when done unless you + // keep the returned SoundPtr. + SoundPtr play(Mangle::Stream::StreamPtr sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + SoundPtr play(const std::string &sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + // Ditto for 3D sounds + SoundPtr play3D(Mangle::Stream::StreamPtr sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + + SoundPtr play3D(const std::string &sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + }; + + typedef boost::shared_ptr SoundManagerPtr; + } +} +#endif diff --git a/sound/tests/Makefile b/sound/tests/Makefile new file mode 100644 index 000000000..9e84cb580 --- /dev/null +++ b/sound/tests/Makefile @@ -0,0 +1,13 @@ +GCC=g++ -I../ + +all: sound_manager_test + +L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +sound_manager_test: sound_manager_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +clean: + rm *_test diff --git a/sound/tests/sound_manager_test.cpp b/sound/tests/sound_manager_test.cpp new file mode 100644 index 000000000..7c09cd583 --- /dev/null +++ b/sound/tests/sound_manager_test.cpp @@ -0,0 +1,50 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + + cout << "Playing " << sound << "\n"; + + assert(mg->numSounds() == 0); + + /** Start the sound playing, and then let the pointer go out of + scope. Lower-level players (like 'oaf' above) will immediately + delete the sound. SoundManager OTOH will keep it until it's + finished. + */ + mg->play(sound); + + assert(mg->numSounds() == 1); + + // Loop while there are still sounds to manage + int i=0; + while(mg->numSounds() != 0) + { + i++; + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + } + cout << "Done playing.\n"; + + assert(mg->numSounds() == 0); + + return 0; +} diff --git a/sound/tests/test.sh b/sound/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/sound/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done