How to delete synths and instruments from memory

I want to delete synths and instruments from memory because I only need the ones for the current level loaded in memory.

I'm making a game in which there are hundreds of unique items spread out across tens of stages, and each item makes a unique synthesised sound when it's interacted with. If I understand the Playdate Lua audio library properly, this means I should make one instrument for each of these items (hundreds), and each instrument will have one or more voices.

So far in my experimentation with the audio library, I've found that synths and instruments are not garbage collected. (I was briefly trying to create a new instrument instance each time I wanted to play a sound effect, so that potentially tens of instances of the same sound could play overlapping in rapid succession. Yes, this is is going to be a noisy game. Anyway, that brief experiment had a big memory leak.) I suppose the sound engine must be maintaining a reference to each instrument that is created.

I'd like to delete instruments from memory when they are no longer relevant (when the stage ends and all of the items in it are unloaded).

I suspect the answer will be that I cannot do that, so in that case I am open to ideas of alternative approaches.

I'm using the SDK on Windows.

There was a bug related to this that prevented freeing memory when instruments were added to a channel. Just to check, and assuming you're on SDK 2.7.x, are you adding the instruments as sources to a specific channel or the global channel, and calling channel:removeSource() when done with the instrument? If you created a new channel for the instruments, are you also calling channel:remove() when the channel is no longer needed?

Yes, I'm on SDK 2.7.2. I was not adding them to a channel, so I suppose they must have been in the global channel.

I just tried adding them to a channel, then removing the channel. I basically tried my little experiment again, creating many instruments per second and playing one note on each of them. But this time I added them each to a channel beforehand, then removed the instrument from the channel and the channel itself, after a short delay.

local function playClank(pitch, volume)
    local synth = playdate.sound.synth.new(playdate.sound.kWaveNoise)
    synth:setADSR(0.005, 0.0125, 0, 0.05)
    local instrument = playdate.sound.instrument.new(synth)
    local channel = playdate.sound.channel.new()
    channel:setVolume(volume)
    channel:addSource(instrument)
    instrument:playNote(pitch)
    local deleteChannel = function ()
        channel:removeSource(instrument)
        channel:remove()
    end
    playdate.timer.performAfterDelay(100, deleteChannel)
end

Calling playClank frequently causes the malloc map to fill up.

To frame the problem differently by addressing another aspect of it: I'm trying to get around the limitation that an instrument can only play one note at a time. I want to potentially play an arbitrary number of notes, overlapping. If I create only one "clank" instrument instance, I can only play one "clank" at a time, and if I start playing a second clank, the first one is unfortunately interrupted. So far my work-around is to create many instruments in some way or another, but creating an arbitrary number of things without a way to delete them is problematic. Ideally, I want a way to play a synth sound, without interrupting any other sounds that are playing, without permanently adding something to the heap in the process.

If there's still a bug with releasing instruments from channels then @dave would certainly be of much more help here than me.

In terms of workflows and workarounds though, instruments should be able to play one note per voice, rather than one note overall. Does it work for you to add a new synth with instrument:addVoice() for each of the notes you want the instrument to play concurrently? I've only done this with sample-based synths and sequences so there may be caveats I don't know about with waveforms, but it should be the general way to achieve polyphony with instruments, and you can restrict each voice to an individual note/pitch or a range if needed.

It may not solve a memory leak, but it might at least help exhaust the heap less quickly. Similarly you can add all instruments to the same channel, unless each has to have an independent volume or set of effects applied.

I ended up finding a different solution.

  • create one channel per item
  • add the synths that make up the sfx for the item to its channel (no instrument)
  • add the item-specific effects to the item's channel
  • just accept that the sound can't play overlapping another copy of itself

I haven't written the part that switches from one stage to the next, so I will treat the heap issue as premature optimisation and ignore it for now, as my current solution creates a constant number of synths and channels and doesn't seem to slow the game down.

I'm not sure if this is the same bug you were originally seeing, but in this demo what's happening is the instrument gets added to a global "playing sources" table but because it gets removed from the channel before it finishes playing, it doesn't run the "finished playing" callback that removes it from the table. I've fixed that by removing it from the table when it's forcibly removed from the channel.

And sorry for the slow response! We've been way too busy getting Season 2 out, but with that done I finally have time to get back to developer support.

1 Like