forked from mirror/openmw-tes3mp
Made SoundManager and test
This commit is contained in:
parent
a202371ef1
commit
6443c16146
7 changed files with 493 additions and 1 deletions
2
mangle
2
mangle
|
@ -1 +1 @@
|
|||
Subproject commit 4692375491fb16da59642022c8d7c891d68ba665
|
||||
Subproject commit 87f6e739759244cef8e9730b8f94e628b8d64681
|
110
misc/list.hpp
Normal file
110
misc/list.hpp
Normal 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
208
sound/sndmanager.cpp
Normal 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
93
sound/sndmanager.hpp
Normal 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
13
sound/tests/Makefile
Normal 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
|
50
sound/tests/sound_manager_test.cpp
Normal file
50
sound/tests/sound_manager_test.cpp
Normal 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
18
sound/tests/test.sh
Executable 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
|
Loading…
Reference in a new issue