Page 1 of 2

Audio Source GC

Posted: Wed Jan 03, 2024 8:31 pm
by windowlicker
Hello! I’m adding sounds and doing this by creating persistent decoder and sound data instances - and then each time I want to play a sound/sfx I create a new source and call play. I’m wondering if a) this is a recommended/conventional way of playing sfx and b) if I need to explicitly call Object:release() to free up the source or will it automatically be garbage collected if nothing in Lua is referencing it?

Re: Audio Source GC

Posted: Wed Jan 03, 2024 11:42 pm
by soulmata
You should not create a new source every time you call play. If the thing you are assigning it to is being reused, it should GC just fine, but you're tanking your performance doing that and that could cause other problems like if the source is already playing when you recreate it. You will be using a ton of CPU and IO just re-reading the same files every frame, or however often you are calling this if you doing it on every frame. There is no need to release manually.

But if you are only doing this based on user input you should be fine - especially if say you don't know in advance what audio file is being worked with. But if you're talking about a finite set, you can load them all in advance and reference them.

Are you making some sort of music editor or music player something? That would be a use case for creating a new source on play, but I'd still want to assign it to something unique that gets cleaned up later when it's no longer used - and so long as it isn't referenced, it will get released.

Here's an example of how I load music in Endless Dark:

Code: Select all

  local muslist = love.filesystem.getDirectoryItems( audiopath .. "mus" )
  if (muslist) then
    if (type(muslist) == "table") then
      for k,v in ipairs(muslist) do
        local fn = string.upper(v:gsub("%.+.*", ""))
        if ((string.match(v, ".ogg")) or (string.match(v, ".wav"))) then -- only load audio files
          audio.mus[fn] = love.audio.newSource(audiopath .. "mus/" .. v, "stream")
        end
      end
    end
  end
Then, when going to play it, I have a single global variable that determines what music should be playing, and I ensure all others are stopped:

Code: Select all

function f_ed_change_music(id)
  if not(id) then return false end
  if not(type(id) == "string") then return false end
  if not(audio) then return false end
  local key = "mus"
  if not(audio[key]) then return false end
  if not(type(audio) == "table") then return false end
  if not(audio[key][id]) then return false end

  for k,v in pairs(audio[key]) do
    if (k ~= id) then
      love.audio.stop ( audio[key][k] )
    end
  end
  for k,v in pairs(audio[key]) do
    if (k ~= id) then
      love.audio.stop ( audio[key][k] )
    end
  end

  f_ed_set_audio(key,id,"play")

end

I have similar code for SFX that happens on every frame. For sounds that are short, I don't bother checking if they should continue to play - but instead I have a curated list of sounds that should stop playing if some condition is lost, and I call their stop on every frame, e.g.:

Code: Select all

  else -- if the sound is especially long (>a second or two), consider stopping it here
    f_ed_set_audio("sfx","GENERICACTION","stop")
    f_ed_set_audio("sfx","TAPPING1","stop")
    f_ed_set_audio("sfx","TAPPING2","stop")

I only ever create the audio source once, on load, and I reference the table that contains those sounds. I can iterate thousands of sounds on every frame with basically no performance hit at all. I don't bother to even check if a given sound is playing and shouldn't, I just spam stop on every frame for those that shouldn't be currently playing. It's not the most efficient in the world, but it's efficient enough, and scales easily. If I was trying to make a music player, I would instead track what is currently playing, then stop that, and move on to the next, and unload the old track once the new one was finished loading.

Re: Audio Source GC

Posted: Thu Jan 04, 2024 12:06 pm
by dusoft
soulmata wrote: Wed Jan 03, 2024 11:42 pm I only ever create the audio source once, on load, and I reference the table that contains those sounds. I can iterate thousands of sounds on every frame with basically no performance hit at all.
This is indeed good to know. No performance hits and you tested it on multiple systems? If I understand correctly, you just basically play each sound and stop it on every frame based on delta time and then continue playing again on next? Doesn't it sound choppy?

Re: Audio Source GC

Posted: Thu Jan 04, 2024 5:11 pm
by soulmata
Not quite- the way the audio system I wrote works like this

1) On load, read all audio files into a global table and create a new audio source for them with the filename as the key

2) In-game, when an event wants to play a sound, like SFX, tell that source to play

3) Every frame, see what music and environmental sound should be playing. Set that source to play.

4) Every frame, have a curated list of SFX, and then all music/environmental sounds that shouldn't be playing, and set them to stop.


This works great on Windows or Linux. No choppiness or other issues. Very low overhead - I wrote a profiler in-game and the audio subsystem is less than 0.25% of the CPU time spent.

Re: Audio Source GC

Posted: Thu Jan 04, 2024 5:40 pm
by soulmata
Here's a short clip with audio showing off a bit of what I mean: https://i.imgur.com/FeDrNOz.mp4

There you can hear the background music (checking if it should play on every cycle), the environmental sounds (the creaking metal, etc), also checking every frame, the 1-time sounds (when I sabotage something), and the "Check on every frame" sounds, like the wrench sound when I am repairing something. It all works very smoothly.

Re: Audio Source GC

Posted: Thu Jan 04, 2024 7:42 pm
by zorg
I will add to this that while short sound effect sounds being loaded into RAM is fine, music files can be quite large (even streamed ones, since the non-decoded file does get loaded into ram in its entirety... a thing that can be toggled from löve 12.0 onwards), so keeping all tracks in there might not be a good idea, depending on how many bgm pieces you have, how long they are, and how much memory usage you want your game to have needlessly.

You can also call :clone on Sources, which can be useful if you want your sound effects, if called faster than they would have ended, to not get cut off by you just restarting one of each... although bookkeeping might turn a bit more complicated.

By the way, that iterating over all sound effects in a table bit... that is performant until you have like tens of thousands of sound samples; linearly going over that table is going to eat some processing power, so that could be done way smarter... and that includes just letting the sounds play out completely, and not stopping them every frame, testing lots of conditions.

Re: Audio Source GC

Posted: Thu Jan 04, 2024 10:39 pm
by soulmata
How much memory does decoded music use?

I load about 40 soundtracks - the game at present uses about 1G of RAM, and almost all of that is image data. Actually it used to be a lot more when I was using tiles, but now on load I render all the tiles, then convert the tiles to an image, and only display the image, that saved a lot of RAM. I did not know about the BGM staying in memory even if streamed, though.

Re: Audio Source GC

Posted: Fri Jan 05, 2024 12:19 am
by zorg
Streamed music that gets loaded in currently takes up as much space as it does on disk, so if your music is in mp3 or ogg files, then they'll take up less than if they'd be in flac or wav. (assuming an average of 10 MB per track, for 40 tracks, that's 400 megabytes)

Static sources completely decode the files into memory on creation, so it'd be equivalent to however large a wav file would be, considering those (usually) directly store all samplepoints at whatever sampling rate the file has.

Re: Audio Source GC

Posted: Fri Jan 05, 2024 2:47 pm
by windowlicker
Thanks for the replies/info! The reason I was creating multiple sources was so that I could have the same sfx overlapping. What I mean is, if I have a single source and play it - and then play it again next frame, it won’t do anything (because it’s already playing). If I create a new source each time, I can hear the sound playing twice.

Re: Audio Source GC

Posted: Fri Jan 05, 2024 2:51 pm
by windowlicker
zorg wrote: Thu Jan 04, 2024 7:42 pm
You can also call :clone on Sources, which can be useful if you want your sound effects, if called faster than they would have ended, to not get cut off by you just restarting one of each... although bookkeeping might turn a bit more complicated.
Thanks, clone sounds like it might be what I want/need. For those cloned sources, do you still need to call :release on each one?