Costly to delete Sounds all the time?

kungfoomasta

10-01-2008 18:34:47

I'm wondering if its a bad idea to delete sounds and create new ones all the time?

Assuming deleting sounds all the time is not costly, I would like to request a feature where you can specify a sound to be deleted after it finishes playing.

Not sure what an appropriate interface would be, could be something like SoundManager::destroyOnCompletion(Sound* s), or Sound::deleteOnFinish(), etc.

My scenario is that I would like to play ambient sounds for GUI interaction, but I don't want to manage deletion of sounds after they are used. Basically I want to fire off a sound and be able to forget about it, assuming it is properly cleaned up.

Alternatively, a feature request would be to be able to support a user defined callback, executed when a sound finishes playing. That way I'd just point all my sounds to some function that will delete it after its finished. This also serves multiple purposes. 8)

CaseyB

10-01-2008 19:07:17

I like the idea of the user defined callback. I think that's a lot more flexible and I'm sure there are a lot of things that people would like to do upon completion of a sound. Good call! I'll look into setting this up. I really like the way MyGUI does callbacks, I haven't looked too much into QuickGUI, but I will have a look and see what approach I want to take.

Thanks for the idea kungfoomasta!

kungfoomasta

10-01-2008 19:42:25

Dunno how MyGUI works. :P

QuickGUI improves the CEGUI method, and you don't need Boost.

First, you have a base class like the following:


class _QuickGUIExport MemberFunctionSlot
{
public:
virtual ~MemberFunctionSlot() {};
virtual void execute(Sound* source) = 0;
};


And then using templates you create a derived class:


template<typename T>
class MemberFunctionPointer :
public MemberFunctionSlot
{
public:
typedef void (T::*MemberFunction)(Sound* source);
public:
MemberFunctionPointer() :
d_undefined(true)
{}
MemberFunctionPointer(MemberFunction func, T* obj) :
d_function(func),
d_object(obj),
d_undefined(false)
{}
virtual ~MemberFunctionPointer() {}

void execute(const EventArgs& args)
{
if(!d_undefined)
(d_object->*d_function)(args);
}
protected:
MemberFunction d_function;
T* d_object;
bool d_undefined;
};


Now in your Sound class you can use the Base class to define your list of user defined events:

std::vector<MemberFunctionSlot*> mCallbacks;

And you can execute them fairly easily

mCallbacks[0]->execute(this);

To add user defined callbacks you'd use a function like this:


template<typename T> void addOnFinishedHandler(void (T::*function)(Sound* source), T* obj)
{
mCallbacks.push_back(new MemberFunctionPointer<T>(function,obj));
}


For users, we would make use of this like:


Sound* mySound = mSoundManager->createSound("someName","somesound.ogg");
mySound->addOnFinishedHandler(&myClass::myFunction,this);


This will work as long as your function has the following signature:
void myClass::myFunction(Sound* source) { ... }

There you have it, I laid it all out for you, since I really like this approach. :wink:

But are there any performance issues with deleting sounds a lot? For example, every time you click a button, you create, play, and then destroy a *click* sound. No problems with this?

CaseyB

10-01-2008 20:12:26

I really do like that approach too! It's very clean!

As for being expensive, it is kind of. It does a find in a map, does a delete on the Sound object which requests OpenAL to destroy the buffers it was using and then removes the buffer from the SoundFactory map. and then it releases its Source back to the SoundManager's SourcePool. I mean it's not TERRIBLE, but it's not something you would want to do many times a second. I am trying to create something that would be a LOT lighter weight. I am calling it a "SoundParticle" and it will work like a normal Particle where you can just toss them around and they don't have much overhead. They won't have much interaction either though, they are intended for multiple short-lived sounds, like machine gun fire.

kungfoomasta

10-01-2008 20:20:00

Ok, it doesn't sound like it would be too costly. I have UI button clicking and interaction in mind, and since that isn't the main focus of the game, nor does it occur a lot when the game is in play, it doesn't look like a problem.

So all that's left is to figure out when a sound is finished playing, maybe the sound manager has to poll it or something, at defined intervals. Let us know when its in place, so I can finish working with ambient sounds. :)

CaseyB

10-01-2008 20:45:13

I am working on it now. It shouldn't be too bad, I've already implemented your approach to callbacks and have added them to where streams loop or end. Getting the end of a non-streamed sounds shouldn't be too bad, but lopping might be an issue. When else might you want to be notified?

kungfoomasta

10-01-2008 21:07:48

Maybe when you pause or resume a sound. Actually the scenario in my mind would be when you pause/resume all sounds. For example you play some ring sound when you pause the game, and some other sound when you resume the game. (I think I remember hearing a coin sound in Mario for the NES when you press start.. lol)

It all depends on how much flexibility you want to add, but it doesn't hurt to allow these options, right? :wink:

- When a sound loops
- When the gain changes
- When all sounds are paused/resumed (SoundManager functionality)

I'm not familiar with doppler and other affects that are implemented, my knowledge is pretty basic.

kungfoomasta

11-01-2008 22:31:29

Ok I take that back. For now, just implement the feature for when a sound finishes, and the rest can be addressed when a practical use arises for them. (YAGNI approach) :wink:

Manually Stopped sounds, or Looping sounds, should not produce this notification.

CaseyB

12-01-2008 02:50:58

Manually Stopped sounds, or Looping sounds, should not produce this notification.
Yeah, I knid of figured if you stopped it you would know! ;)

Game_Ender

12-01-2008 06:48:55

If you ever want something that is a little more easily expandable (and probably faster) check out FastDelegate. Its just 2 header files and replaces Boost.Bind and Boost.Function (uses the same syntax too). Saves you from having to write a bunch of those custom templates every time you want pass around a different type.

CaseyB

12-01-2008 07:40:32

Cool! I'll look into that thanks!

scriptkid

12-01-2008 12:48:43

@kungfoomasta: why would you like to delete a sound after it has been played? If you are worried about unused occupied soundcard slots, that's not an issue (anymore). As you might know, OgreAl will ditch sounds when there is no more space left. Stopped sounds are the first to be replaced.

However i can imagine that you want to unload large sounds immediataly when you know they won't play again, in order to save memory.

kungfoomasta

12-01-2008 18:50:24

I want users to be able to create sounds and not have to worry about them. Basically I have a Window class, and I have the following API:


void pauseAmbientSound();
void playAmbientSound(const std::string& fileName);
void resumeAmbientSound();
void stopAmbientSound();


Whenever you play a sound it will play once, and then be cleaned up without any further work from the user.

This is just for my ambient noises, but I also think this feature would allow for special effects, such as performing some action after a sound has finished. (not limited to cleanup, as in my scenario)

OvermindDL1

12-01-2008 20:52:24

People are *still* talking about fastdelegates?

You do realize that fastdelegate and boost::function compile to the exact same machine code (have you ever looked at it?). Boosts optionally (but is not required) adds an exception if the pointer is null. Boosts version can also bind to functors, which is something that fastdelegates cannot do. boost::function and boost::bind is also in the next C++ standard, and you can go ahead and download the TR1 of the next C++ version (which will work in current compilers) from the boost libraries as well if you wish to get a head start.

In short, there is no reason to use fastdelegates. You can just copy the headers you want from boost (function and bind are header-only files) and include them in your project (the license encourages you to, and it does not infect anything like the crap gpl). Boosts versions are just as fast and more powerful (I use functors more often then I use normal pointers in most of my code, fastdelegates would be a horror if I tried to bind them with my code, I would have to make temporary functions all over the place to pass them into it, may as well use C function pointers at that point). After all, it makes sense that boosts would be better considering it is made by the industries top programmers, compared to one person whipping up fastdelegates on a little code snippet website.

Sorry for the rant, just fastdelegates ticks me off. Have tried binding to projects that use it and I end up having to write just as much extra code as if they used C function pointers instead, compared to if they used the C++'s standard (for the next C++ version, boost or TR1 for the current version) then everything would have been worlds better.

And yes, using a function pointer class like boost::function is far better then the above methods as stated above.

kungfoomasta

22-01-2008 20:46:19

So quiet...

I don't think there are any problems to solve for this feature, other than figuring out when a sound has ended. What is the best approach to determine this? Polling? Specifically, what low level OpenAL functions are there to help in accomplishing this feature?

CaseyB

22-01-2008 22:01:45

Yeah, it's just going to involve polling each update to see if the sound has ended of its own accord. Sorry this has been lagging a bit, but I just started a new job and there have been some other IRL things that have come up. I will get to this by this weekend at the absolute latest!

kungfoomasta

22-01-2008 22:46:26

No worries, I have a ton of real life obligations myself. Was just wondering what you're up to. If you don't make it this weekend no sweat. :)

kungfoomasta

31-01-2008 18:44:05

Wow CaseyB, you must be incredibly super busy, or hate working with OgreAL, haha.

The thought occurred to me that if I want to use the notification to destroy the sound, the SoundManager will need the ability to queue sounds for deletion. You can delete the sound in its notification, a crash will occur when finishing execution of the notification code. (can't go back to the sound code, its been destroyed!)

So we'd need something like

SoundManager::queueForDeletion(Sound* s);

Hopefully the SoundManager is a frame listener, so it can run through the queue every frame and delete sounds queued for deletion.

I'm moving into my house and will be inactive for the next 5-8 days. After I release QuickGUI v0.9.7 I'll start looking into this. I can't imagine it would take more than an hour of time to implement. :P

CaseyB

03-02-2008 07:24:27

Ok, I am very sorry this has taken so long, but things have been REALLY crazy! I have committed a few new things including the callbacks and the queue for deletion. You will still call SoundManager::destroySound(), but it will add the sounds to a queue to be deleted the following frame. I haven't had time to test this as much as I would like so, please, if you run into any problems, let me know!

reptor

09-03-2008 01:12:04

Hi,

after starting to code the sound part of my application, using OgreAL, I soon noticed that I would like to have this functionality too, or it's very close at least :)

Was a bit of a surprise to me when I noticed that there is no way in SoundManager's public interface to do "garbage collection" of stopped sounds. I can't even go through the SoundList vector and check if the sound is stopped and then destroy it...

"Garbage collection" of sounds is needed I think. First of all, I don't think it is reasonable to expect that sounds would always be deleted right after use. But they should be cached for later use most often. Then, it should be possible to say, when I create a sound, if I want to cache it or not. If I don't want to cache it, the sound should be automatically destroyed right after it has stopped. If I want to cache it, I should have possibility to call "garbageCollect" to get rid of the cached, but stopped sounds. I could do all this by myself in my application's code but the OgreAL SoundManager public interface looks to me like it isn't allowing me to do that. No wait, I could actually do this by keeping my own list of sound names, and having a polling loop going over those sounds every now and then, but this would duplicate data and thus is not good.

Sorry if I'm wrong, but that's what I could figure out...

CaseyB

10-03-2008 04:02:30

You can choose to be notified when a sound stops by setting the callback in the Sound. This way you can destroy the sound when it finishes if you like. You could need to call OgreAL::Sound::addSoundFinishedHandler() and pass is a pointer to the class that holds the method and the method pointer.