← Back to team overview

lucadestudios team mailing list archive

Re: OpenAL sound system for LuceneStudios project

 

2009/2/24 Sean <seanhodges@xxxxxxxxxxxxxx>:
> Gents,
>
> I've written down a few notes on the sound system for the LucadeStudios project. I thought I'd post them up for review.
>
> Let me know if you have any queries or suggestions, this is just rough plan at this stage.
>
>
> OBJECTIVE
>
> Sound library, providing a high-level interface to the OpenAL library with a focus on games development.
>
> Based on concepts from KALEngine, OGRE and Unity:
>
> http://techbase.kde.org/Development/Tutorials/Games/KALEngine
> http://www.ogre3d.org/wiki/index.php/OpenAL
> http://unity3d.com/support/documentation/Manual/Sound.html
>
>
> COMPONENTS
>
>  === Manager ===
>
> High-level controller for the sound system. In normal circumstances only one instance is needed. Sources are added to this before they can be played, and are made accessible by a user-defined key (stored in a hash map).
>
> Future consideration: At a later stage it may be desirable to include a "addFromConfig()" call; to build the sound sources from declarations in the config_reader. This could be available in addition to the manual addSource() method.
>
>        init()
>        shutdown()
>        addSource(key, source)
>        getSource(key)
>        play(key)
>        stop(key)
>        pause(key)
>
>

Yep cool, as long as the singleton pattern doesn't touch this I'm happy :)

>  === Source Streams ===
>
> - ogg_stream
> - wav_stream
>
> Sound sources, a source is initialised with a file name and passed to the manager with a key string. The associated file in a source stream is immutable, but other characteristics can be modified at any time (position, looping, volume, etc). As well as using the play() operation through the manager interface, you can also retrieve a reference to the stream and play it directly. By default, the sound data will be decompressed and loaded into a buffer on demand (when play() is invoked). This behaviour will be modifiable per sound stream, so that frequently triggered sounds can be available decompressed in memory.
>

Can you just clarify that this "decompression" of the stream happens
to already loaded data? We can't afford disk hits during gameplay,
unless you wanna make tea between frames ;)

> Future consideration: In the first pass, sound sources will be able to have a position, but will be static. We should work out a mechanism for setting a direction and velocity as a next phase.
>
>        play()
>        stop()
>        pause()
>        storeInBuffer(bool)
>        setLooping(bool)
>        setPosition(float, float, float)
>        dropPosition()
>
>

Hmm.. I've got some niggles with this (sorry!). Firstly, the same
sound needs to be played from many sources (locations), so
initializing with a filename will end up in duplication of loaded data
(unless there is some funky caching going on in the manager) I would
instead recommend you divide this into two separate concepts...
sources and streams. A source holds a (smart) pointer to a stream
stored in the manager. The manager controls loading the streams from
disk (think flyweight pattern). Of course, if a stream can only be
played once at a time, we need to clone them to the sources before
playing.

Also, remember there is the case that (for example) a game object may
be making one sound, and then swap to another sound. It would be nicer
if the object held a "source" instance, and just called
_source->setStream("somekey") which internally called
manager->getStream("somekey")  (which may cause a stream to be
cloned). We should also consider that a source may play more than one
sound... (we may also want crossfades between sounds, this is beyond
the scope for now, but we should bear it in mind).

So OK to sum up my waffling:

1. a source should represent "something" that plays a sound
2. a stream should represent that sound, it should be playable by more
than one source
3. Possibly the source interface should allow for extensions so we can
have more than one sound either playing, or queued to be played.


>  === Sound API Service ===
>
> OpenAL will be abstracted from the manager, so we can support other API's in the future (e.g. to support a platform that is incompatible with OpenAL). Currently, it will be the default and only service available to the engine's sound system.
>

Yep nice.

>
> EXAMPLE USAGE
>
> // Initialise the sound system
>
> sound_manager mgr();
> mgr.init();
>
> // Add some sources, the keys can be any string - format is down to the caller
>
> mgr.add_source("/global/menu/itemclick", new ogg_stream("beep.ogg"));
> mgr.add_source("/enemy/actions/fireweapon", new ogg_stream("bang.ogg"));
>
> // The sounds can be triggered from the manager
>
> mgr.play("/global/menu/itemclick");
>
> // Or, alternatively, a source can be retrieved later on, to manipulate it and/or play it directly
>
> ogg_stream* _fire_weapon_sound = mgr.get_source("/enemy/actions/fireweapon");
> _fire_weapon_sound->setPosition(50, 50, 0);
> _fire_weapon_sound->play();
>
> // Shut down the sound system when you're finished
> // We could possibly make this automatic by applying a scoped_ptr to the openal_service, I'll follow whatever the standard convention in the project is...
>
> mgr.shutdown();
>
>

With my suggested adjustments this becomes:

sound_manager mgr;
mgr.init();

//Add a stream
mgr.add_stream("global/menu/itemclick", shared_ptr<stream> (new
ogg_stream("beep.ogg")));

//Create a source (the source gets an internal pointer to the manager
- so it can find the stream)
shared_ptr<source> new_source =
mgr.add_source("global/menu/itemclick"); //calls manager->get_stream
internally
or
shared_ptr<source> new_source = mgr.add_source(); //no sound, can't be played
new_source->set_stream("global/menu/itemclick"); //Now it can be
new_source->set_position(50, 50, 0);
new_source->play();

Let me know what you think,

Luke.



Follow ups