Crash: Track:setInstrument() while a note is playing (missing Lua Wrapper)

Using:

hardware 1.13.1 (affected)
Mac OS simulator 1.13.1 (NOT affected)

Tried this using both a WaveSynth and SampleSynth. Both affected.

Creating a sequence from a midi and creating instruments works fine before playing the sequence.

Now, while the sequence is playing, I might want to change the instrument for a track. This also works fine as long as no note is playing for that track.

If there is a note playing, however there is a high chance of a crash on device, with the error

SoundSource 0x<address> doesn't have a lua wrapper

I have isolated this to the setInstrument call; creation of the instrument in itself does not cause the crash.

When removing the sequence:play() call, the crash does not occur immediately, but the track goes silent. It might then crash when the sequence:play() call is invoked at a later time.

Sample code:

function EditorViewModel:getTrack(trackNum)
    return self.sequence:getTrackAtIndex(trackNum)
end

function masterplayer.newWaveSynth(trackProps)
    local s = snd.synth.new(trackProps.instrument.id or snd.kWaveSawtooth)
    s:setVolume(trackProps.volume or defaultWaveVolume)
    s:setADSR(
        trackProps.attack or defaultWaveAttack,
        trackProps.decay or defaultWaveDecay,
        trackProps.sustain or defaultWaveSustain,
        trackProps.release or defaultWaveRelease
    )
    return s
end


local inst = snd.instrument.new()
for _= 1, polyphony do
    inst:addVoice(masterplayer.newWaveSynth(trackProps))
end

local track = self:getTrack(trackNum)
track:setInstrument(inst) -- when commenting this line, crash does not occur
 --while the sequence is already playing, calling :play()  notifies it of the changes
self.sequence:play()
1 Like

Seems this bug has flown under the radar.

Unfortunately, it's the only bug that is keeping my Midi Master project from being released, so I do hope it'll get picked up some time.

To make this report a little more inviting; I'll add a sample project.

sequence-lua-wrapper-crash.zip (2.0 MB)

It's the midi player SDK sample; modified to set a new instrument for all tracks for every frame update.

On Simulator we get the expected behavior, and on device we get a crash.

Simulator: glitchy playback; to be expected when updating instruments 30 times per second
device: Crashes after a short while with aforementioned error.

@dave I appreciate there are many priorities to manage. Could you add this to the backlog?

3 Likes

Thanks for the demo! I think when I saw the first post I didn't see an easy way for me to put the code together to try and reproduce the issue so I put it on the "look at this later" stack and moved on. That stack is growing way too fast these days. :frowning:

I also get the crash in the simulator, which makes it a lot easier to debug. Looks like the problem is there's a missing reference to the synth, so the synth gets cleaned up between the time it calls the finish callback--which, for synths created in Lua, is a shim that passes the message from the audio task to the main task--and when the main task picks it up. Ah! There's no reference because the playing is done in lower-level code out of sight of Lua. I'll have to add a way for sequences to notify when notes start (and stop, for symmetry's sake). If you track the synth lifespan manually by adding them to a table after creating them then removing them when they've been replaced you should be able to avoid this bug. The trick is to store the reference in on the key side of the table so you get fast lookups:

activeSynths[synth] = true -- adds a reference so synth doesn't scope out
activeSynths[synth] = nil -- clears the reference, allowing it to be collected

As for the problem of instruments not working right when you replace them in a playing sequence, the general issue there is I hadn't expected anyone would do that. :sweat_smile: But specifically, instruments are only added to their sound channels when you call sequence:play(). I was going to say that you could manage this manually by adding the instruments to a separate channel then removing them, but in the Lua code we delay adding sources to channels until you call play()/playNote() on them. And we're stuck again with the problem that Lua never sees those calls when triggered by a sequence.

I've filed a handful of issues for this. Thank you so much for digging these up, and especially for the demo. Having a reproducible test case that shows exactly what the issue is and that I can run in the debugger makes a world of difference.

2 Likes

Thanks for getting back to this.

I've been able to validate the keep-a-reference workaround, but it's a handful to work with since you need to keep 2 references for every synth: the previous one and the one you are replacing it with.

Workaround demonstration: (working)

In the process I found a memory leak where sequences are not cleaned up. In simulator, this code crashes after about 70 iterations. Note that it already incorporates the track:clearNotes workaround from [How to free up memory from a sound.track? - #3 by AdamsImmersive]:

I have a fix in for these--finally added reference counting so that the system doesn't have to try and enforce a strict ownership policy. Here's a test build if you'd like to try it out in the simulator: Dropbox - File Deleted - Simplify your life If you want to test on the device, DM me your serial number and I'll put you in a test cohort on memfault, then you'll just run Check for Updates on the device and it'll install it.

And thanks again for putting together the demos, made it really easy to track down the root problem. :pray:

Great you got round to fixing this. Since the issue only affects device for me, I'll DM you a serial number

1 Like

@dave I wasn't able to break it so I think you did a great job :blush:

1 Like