Realtime Control of Synth Parameters for Playing MIDI Sequences

Hi! I'm experimenting with playing a MIDI file. I'm sorry if it's a dumb question, but I am quite confused. Is there any way to adjust the synth parameters for sequence playing at runtime?

I mean, everything is playing fine if I set the synth parameters once during the setup and apply them to the MIDI track instruments. But is there a way to control sustain, attack, and release levels at runtime using a crank or any other input? Repeating all the setup methods for instruments in the update loop doesn't work and seems like an extremely inefficient way to achieve it anyway.

It works if I reassign all track instruments once a button is pressed or whatever, but it looks like it stops the playing sequence for one tick while reassigning, so the first time I forgot to call play() again.

So, it's okay as a one-time trigger, but I'm wondering if it is possible to do it gradually. If I place this method to be called in the update, it would surely sound interrupted.

Maybe there is a way to pass not a synth.copy() in addVoice() every time but instead have some "static" synth? Is that even possible? If so, what should I use for it, maybe control signals or an envelope?

function GetInstrument(n)
	local inst = snd.instrument.new()
	for i=1,n do
		inst:addVoice(synth:copy())
	end
	return inst
end

function TestChangeInstruments()

    local crankPosition = playdate.getCrankPosition()
    local sustain = 0
    if crankPosition <= 180 then
        sustain = map(crankPosition, 0, 180, 0, 0.2)
    else
        sustain= map(crankPosition, 180.01, 359.9, 0.8, 1)
    end
        synth:setVolume(0.2)
        synth:setAttack(0)
        synth:setDecay(0.15)
        synth:setSustain(sustain)
        synth:setRelease(0)

    for i=1,ntracks do
        local track = s:getTrackAtIndex(i)
        if track ~= nil then
            local n = track:getPolyphony(i)
            track:setInstrument(GetInstument(n))
        end
    end
    s:play()
end

Ok, looks like I got it to work! I made an array of synths and assigned each to instruments instead of copying the same one. Now I just change the values of these synths in a loop instead of reassigning all instruments, so there is no need to call sequence:play() each time.

But maybe there is a much smarter way to achieve the same thing, I don't know.

function GetSynth()
    local synth = snd.synth.new(snd.kWaveTriangle)
    synth:setVolume(0.2)
    synth:setAttack(0)
    synth:setDecay(0.15)
    synth:setSustain(0.2)
    synth:setRelease(0)
    return synth
end

local synths = {}
for i = 1, 18 do 
    synths[i] = GetSynth()
end

local synthCounter = 1

function GetInstrument(n)
	local inst = snd.instrument.new()
	for i=1,n do
		inst:addVoice(synths[synthCounter])
        synthCounter = synthCounter+1
	end
	return inst
end

local ntracks = s:getTrackCount()

for i=1,ntracks do
	local track = s:getTrackAtIndex(i)
	if track ~= nil then
		local n = track:getPolyphony(i)
		track:setInstrument(GetInstrument(n))
	end
end


function ChangeInstruments()

    local crankPosition = playdate.getCrankPosition()
    local sustain = 0
    if crankPosition <= 180 then
        sustain = map(crankPosition, 0, 180, 0, 0.2)
    else
        sustain= map(crankPosition, 180.01, 359.9, 0.8, 1)
    end

    for i = 1, #synths do
        synths[i]:setSustain(sustain)
    end
end