How to dispose audio players correctly

hi all,

I have lots of buttons and an audio controller which plays the sound when hovering. For every mouseover, I create an object which holds an audio player. A for each region with a keep, removes “finished” audio players.

I noticed that performance is degrading the longer I have this patch running. It starts at smooth 60frames, but after one day it’s down to 17fps. No memory leak - just performance is down. F8F5 and we are back to 60fps.

I removed the audio component and have the patch running now for several hours without a performance drop now. So I guess it has to be this audio player keep thing.

Is there a safer way to dispose of those audio players?

can you share a minimal patch that demonstrates this?

if you manage instances of data types that need a dispose, always think of this pattern:
create → update as often as needed → dispose

so when a player is finished, make sure you call dispose. if you placed the player in a class, then dispose this class.

the dispose of a process node only gets called automatically by the system if all parents up to the application root patch are process nodes (and/or stateful regions). so you dont need to do that if you, for example, place the player as process node in a loop and change the count of the loop.

as soon as dynamic instances are managed in a spread (or any other collection), you need to call dispose on the instances that you don’t need anymore.

I created a Dispose function in my AudioPlayer object and connected it to a pad with the datatype of my object. When it is finished, I call TryDispose(instance). Is this correct?

The dispose does not remove the objects from the collection. It is a spread. Is this supposed to work this way? For removing the players I still use a foreach region. I did the keep in another region after. Would this also work in one region? IDK when the keep function gets called, and a frame delay is a nogo I suppose?

Why is BW_Audioplayer a record?

hard to tell what you’re doing here without a patch… but you can try this to check if dispose was called:

image
add this little logger here, assigned to Dispose. Then, when dispose is called, you see it in the log window. on F8 it should get called and - depending on your patch - maybe also in other situations.

1 Like

What I am really trying to do here is play sounds for my UI interactions. my buttons send observables, and the audio controller should play the matching sound file. Like the oneshot from unity.

If there is a grid of buttons and I move over them, I want to be able to play multiple sounds simulanousely. For this I need multiple players.

^^ what log window, is there something like tty in gamma or do you mean a windows log function?

well, take this sample patch:
AudioTest.vl (27.1 KB)

add a WriteLine to the Audioplayer like shown above.

and then it should look like this. (change the path to the audiofile to some existing one)

for the log window press CTRL+F1 or go via the menu.

1 Like

i strongly recommend to make small test patches that isolate a problem or task and solve them there until you understand or figure out what you need. then integrate the learnt stuff in your big patch.

i mean, look at your last screenshot… what does it add to the original problem except confusion? All these names don’t say anything to me, because i have no clue about your patch. :D

@schlonzo the only thing that i can see in your screenshot is that you should create the player before the HoldLatest in the moment of the event. not on update in the main loop. because the HoldLatest might miss events that happen between frames.

So link any mutable collection (e.g. SpreadBuilder) into the event region and add a new player cass there. when the player is finished, call dispose on it and remove it from the collection.

@sebl thanks for the advice! I will test extensively and try to pinpoint the problem.
I thought you were wondering why I need multiple audio players, and wanted to provide more context. next time I will keep my problem isolated to make it more clear.

Why do you use the “do” region? Comment inside says: "This is a general purpose way to instantiate/update and dispose of whatever the user patched inside a region. "*
I wonder, because using a safe way to dispose of my dispose function?

And do I really have to get the slice, call dispose and remove it? Could I do this all in one foreach region (screenshot) or would this lead to errors?

@tonfilm I understand it makes no sense for me to go back to the main thread since I can create and manage my players async. But how do I update my objects within the mutable collection? the first time I use a forreach region, I am back to an immutable collection.

audio.vl (31.7 KB)

just connect the collection to the ForEach in your screenshot. and you dont need the Do region, sebl only used it to make sure that the execution order is correct.

and you can also have the Keep filter in the same loop,

The collection changes from spradbuilder to spread after the (not reactive) for each region. updating and playing back works, but my objects do not get filtered out by the keep, because I do not write back into the collection’s pad.

The audioPlayers are instantiated async in the event region, but processed and updated in the main loop? Is this what is happening here? I am a little bit confused.

If I do it this way I can remove slices from the mutable spread builder. of course I get an exception from update, each frame I remove the slice, while it is still running.


(that keep does nothing, old screenshot.)

it works! this fixed my problem. thank you both!

instantiating the audioplayer async frees up the main loop. no mini framedrops any more. and disposing the audioplayer like sebl showed fixed my long term performance problem.

yay

right, for mutable collections you need two iterations to remove old instances. you shouldn’t remove slices while iterating.

for that use an “Alive” property of the particle and filter the particles after the Update loop. there is a RemoveAll region that lets you check the elements in a SpreadBuilder and remove them. In that region you can also call the Dispose, if Alive is false.

How do I call Update, while keeping my collection a “spreadbuilder”? As soon as I leave a region through a sink, I am back to a “spread” collection. Doing it this way works, but I cannot control order of operation. Is there a way to call update for each element in a spreadbuilder?

Have a look here: Execution Order | vvvv gamma documentation

You can just link into the foreach with an input splicer and have no output, but put all of that into a Do region or an If region that is always true.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.