mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-30 16:20:21 +00:00
Made SoundManager and test
This commit is contained in:
parent
a202371ef1
commit
6443c16146
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…
x
Reference in New Issue
Block a user