This is on SDK/simulator 1.9.3 on MacBookPro running MacOS 11.2.2
when i run the attached code, and you press the A and B buttons it plays various note sequences. I know this isn't the right way to do this, but it probably shouldn't kill the simulator.
Press A or B and it spins up a new synth, track, and sequencer, and Play()'s it. the Play has a callback that gets called when the note sequence ends.
If you wait until the playing sequence ends, the callback works just fine.
So if you press the button before the previous sequence ends, it seems to overwrite it (i'm not entirely sure how the engine works or Lua handles garbage collection and all of that, so i'm speculating a lot here).
When the sequence ends, it attempts to call the callback for the ending sequence, but that one has already been replaced by a new one.
I think the garbage collection is getting rid of the old one, since it's a local within the function, so when it goes to get the callback function name, it faults out.
change the line within the function from
local seq = snd.sequence.new()
to just
seq = snd.sequence.new()
then it will use the "local" version of the variable outside of that function instead, due to scope. It will then fail but in a safe way, with "attempt to call a nil value" in red in the console. The running pdx is crashed, but the simulator is graceful about it.
I would expect it to fail more gracefully, perhaps the sequence just ends without the callback being called, or some other behavior... Having the sequencer just up and die is... unexpected and surprising, ;D
Regardless, I don't expect what I'm doing to work. This isn't an issue with the audio code or garbage collection or anything like that... but of how the simulator handles the bad variable. :}
import "CoreLibs/utilities/where"
import "CoreLibs/object"
import "CoreLibs/timer"
import "CoreLibs/ui"
------------
class('Tunez').extends( "Object" )
local all_songs <const> = {
default = {
tempo = 12,
env = { a=0.0, d=0.3, s=0.0, r=0.2 }, -- standard tone
},
cursor = {
prio = 0,
tempo = 2,
env = { a=0.0, d=0.1, s=0.0, r=0.2 }, -- bleep
seq = { 'A4' }
},
pitfall = {
prio = 25,
tempo = 48,
seq = { "G5", "F#5", "F5", "E5", "D#5", "D5", "C#5", "C5",
"B4", "A#4", "A4", "G#4", "G4", "F#4", "F4", "E4",
"END"
}
},
}
snd = playdate.sound
function tnz_finished_cb( seq )
-- trampoline
print( "CB ", seq)
end
local seq = nil
function PlaySong( nom )
print( seq )
if( seq ~= nil ) then
seq:stop()
seq = nil
end
local the_song = all_songs[ nom ]
if( the_song == nil ) then return end
local synth1 = playdate.sound.synth.new( playdate.sound.kWaveSquare )
local env = the_song.env or all_songs.default.env
synth1:setADSR( env.a, env.d, env.s, env.r )
t = snd.track.new()
t:setInstrument( synth1 )
local ts = 0
for i,p in pairs( the_song.seq ) do
if( p ~= 'END' and p ~= 'REST' ) then
t:addNote(1+ ( ts*3), p, 2 )
end
ts += 1
end
local seq = snd.sequence.new()
seq:setTempo( the_song.tempo or all_songs.default.tempo )
seq:addTrack(t)
seq:play( tnz_finished_cb )
end
------------
local gfx = playdate.graphics
gfx.setColor(gfx.kColorBlack)
function playdate.update()
playdate.timer.updateTimers()
gfx.sprite.update()
gfx.fillRect(0, 0, 400, 240)
playdate.drawFPS(0,0)
end
function playdate.AButtonDown()
print( "A" )
PlaySong( 'pitfall' )
end
function playdate.BButtonDown()
print( "B" )
PlaySong( 'cursor' )
end