Sequence:setLoops doesn't stop when it should?

I'm trying this on Windows, SDK v2.0.3 but I think the problem is older than that.

This is what I'm doing:

  1. Create a sequence, add a track, add a synth
  2. Add Notes to a track on positions 1,5,9,13 (making the sequence greater than 5 steps)
  3. Set the sequence to loop from position 1 to 5 two times
  4. The sequence plays twice, but after that it continues to the end of the sequence. Shouldn't it stop at position 5?

sequence:setLoops(1,5,2)
The sequence plays like this:
1,2,3,4,5,1,2,3,4,5,6,7,8,9,10,11,12,13,stop

Example code:


local seq2 = playdate.sound.sequence.new()
seq2:setTempo(4)
local track = playdate.sound.track.new()
seq2:addTrack(track)

track:addNote(1, "C3", 1, 1)
track:addNote(5, "C4", 1, 1)
track:addNote(9, "C5", 1, 1)
track:addNote(13, "C6", 1, 1)

local synth = playdate.sound.synth.new(playdate.sound.kWavePOVosim)
synth:setADSR(0, 0.1, 0.6, 0)
local instrument = playdate.sound.instrument.new()
instrument:addVoice(synth)
track:setInstrument(instrument)

seq2:setLoops(1, 5, 2)
seq2:play()

note: it doesn't matter where I call setLoops(), the result is always the same.

I guess it's a little redundant for me to say "no I'd expect it to continue to the end of the sequence" since that's how I implemented it. Can you explain the case where you've got notes in a sequence but you don't want to play all of them? I can kind of imagine if you've got a large sequence and you want to play a section of it, though setting a loop range doesn't seem the obvious way to do that. We could add a setPlayRange() function like sampleplayer has, or add arguments to play() to set a range.

1 Like

I would love if the two had matching method names (and arguments)

sequencer_screenshot
I'm working on a music step sequencer. 16 steps per sequence, and you can create multiple sequences.
It's really just 1 playdate.sound.sequence, but I loop it in a range (seq 1 is step 1-16, seq2 is step 17-32, seq3 is step 33-48 and so on), as just by looping the steps I avoid creating multiple synths/instruments/sequences. This works just fine, as the loop is set to repeat forever.

To make this more useful, the next step is a song mode, where you should be able to set what sequences you want to play (in what order, and whether they should be repeated). I don't know if this is standard, but you can find examples of this in multiple devices (teenage engineering Pocket Operators, Synthstrom deluge, m8 tracker, nanoloop). For example:
you have sequences 1,2,3,4
in song mode, you set it to play the sequences 1,1,2,1,3,4,2,1. This is where making a loop not continue to the end is very useful. As I'd do something like

local sequences = {1,1,2,1,3,4,2,1}
local currentSeq=0
function playThisThing()
  currentSeq = currentSeq+1
  local currentStep = (sequences[currentSeq]-1)*16
  sequence:setLoops(currentStep, curerntStep+15, 1)
  sequence:play(playThisThing)
end

(I didn't test this code, but I think it shows the idea that rather than simply play a sequence, you can control when and how long to play a certain part of it).

Something like this would also allow to create effects (shortening loops for stutter, move randomly in the sequence for granular)

The easy alternative is to create a new playdate.sound.sequence when in song mode, where I'd copy the notes I need from the original sequence, in the order I need them. I feel like this would be a bit limiting, because the fun would be in continue modifying sequences, reordering, deleting live.
I haven't tested this yet, but something I know is that sometimes when you add a note in a sequence that is playing, it plays that note immediately (like, if the sequence is playing and is on step 3, and you add a note on step 5, it plays that new note, while it continues to step 4).

The other alternative is to monitor the sequence current step in a loop, but this is very innacurate, as the playdate timer doesn't run at the same rate as the sound timer, so when I find that a sequence is on step 16, it might be already a few milliseconds into that step, so moving it to step 32 messes the tempo (also, when developing, I noticed that moving to a step when the sequence is playing stops the sequence, not sure if that's still the case).

setPlayRange() would definitely solve what I'm trying to do.

sorry for the long reply.

2 Likes

Makes perfect sense, thank you for the thorough explanation! I've got that filed and hope to get to it soon. It looks like the sequence finish callback as it currently works won't get the timing spot on, so I'll see what we can do about that. Callbacks have to run on the main task which is, as you point out, out of sync with the audio task. One idea is if there's a play range set then the callback fires immediately after the last step is hit and then any change to the play range is picked up on the next step.. Hm. No, even then, that's too fast for a callback--at 120 bpm x 4 steps per beat, that's 1/480th of a second but if the game is running at 30 fps it's 16 times too slow. :thinking:

I'll keep thinking about this! Maybe setPlayRange() takes a list of ranges and plays the whole thing in order, like sequence:setPlayRange({{1,16},{1,16},{17,16},{1,16}}) to play pattern A twice then B then A again, if your sequence is a series of 16-step patterns.