Made SoundManager and test

This commit is contained in:
Nicolay Korslund 2010-08-11 14:16:56 +02:00
parent a202371ef1
commit 6443c16146
7 changed files with 493 additions and 1 deletions

2
mangle

@ -1 +1 @@
Subproject commit 4692375491fb16da59642022c8d7c891d68ba665
Subproject commit 87f6e739759244cef8e9730b8f94e628b8d64681

110
misc/list.hpp Normal file
View file

@ -0,0 +1,110 @@
#ifndef MISC_LIST_H
#define MISC_LIST_H
#include <assert.h>
namespace Misc{
/*
This is just a suggested data structure for List. You can use
anything that has next and prev pointers.
*/
template <typename X>
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 <typename Elem>
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

208
sound/sndmanager.cpp Normal file
View file

@ -0,0 +1,208 @@
#include "sndmanager.hpp"
#include <misc/list.hpp>
#include <boost/weak_ptr.hpp>
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<Mangle::Sound::Sound> 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<ManagedSound> 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();
}

93
sound/sndmanager.hpp Normal file
View file

@ -0,0 +1,93 @@
#ifndef OENGINE_SOUND_MANAGER_H
#define OENGINE_SOUND_MANAGER_H
#include <mangle/sound/filters/pure_filter.hpp>
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<SoundManager> SoundManagerPtr;
}
}
#endif

13
sound/tests/Makefile Normal file
View file

@ -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

View file

@ -0,0 +1,50 @@
#include <iostream>
#include <exception>
#include <assert.h>
#include <mangle/stream/servers/file_stream.hpp>
#include <mangle/sound/filters/openal_audiere.hpp>
#include <sound/sndmanager.hpp>
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;
}

18
sound/tests/test.sh Executable file
View file

@ -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