When you schedule a single event for a specific time, as in:
amy.send(ticks=96, synth=1, note=60, vel=1)
the ticks time is interpreted as absolute, i.e. the number of sequencer ticks since the timebase was last reset. One common idiom is to establish a timebase for "now", then schedule relative to it:
now = amy.sequencer_ticks()
amy.send(ticks=now + 48, synth=1, note=60, vel=1)
This leads to some agita in the code, where a command to reset the timebase may or may not have been executed before a subsequent read of the system clock:
amy.send(reset=amy.RESET_TIMEBASE)
now = amy.sequencer_ticks() # Has the timebase reset yet?
This led originally to a special-case for RESET_TIMEBASE (in 7e55ec6), and more recently to some work to ensure thread-safety in #1081.
I'm trying to think of a way to make this less uneven. I suspect there's no real use for the now = amy.sequencer_ticks() idiom - if you want to schedule things at a specific time relative to now, you should just RESET_TIMEBASE and specify when you want them to happen assuming that time just started. We wouldn't need to read sequencer_ticks at all.
This would be bad if you had two programs trying to use the timebase at the same time - if one reset the timebase, the other might be thrown off. But I'm not sure how realistic a problem this is.
The idiom shows up in "real-time" scheduling, where the illusion is that amy.send() commands are executed immediately (rather than being added to a queue), and the caller does "loose" scheduling by waiting for time to reach some particular point before issuing the commands. This is the common pattern for AMYboard sketches exemplified by acid_generator.py - the sketch counts how many times loop() has been called, then issue "immediate" amy.send commands pertinent to the current beat. This works because loop() is called in synchrony to the sequencer clock. Significantly, acid_generator never actually looks at sequencer_ticks().
If we deprecated sequencer_ticks, we could enforce "you don't know where the sequencer is because you're just sending commands asynchronously, but you can tell me when to play them relative to RESET_TIMEBASE". Then we could do without the the #1081 machinery to make reads of the clock correct even if the timebase reset hasn't yet kicked in -- it feels like "bending over backwards" to preserve a user's fantasy that everything happens immediately. I think it's more elegant to have an interface that implicitly reflects the engine's semantics that commands will be executed in order (or at the scheduled time on the engine's timebase), but the caller doesn't actually know when that is relative to when the commands are sent.
When you schedule a single event for a specific time, as in:
the
tickstime is interpreted as absolute, i.e. the number of sequencer ticks since the timebase was last reset. One common idiom is to establish a timebase for "now", then schedule relative to it:This leads to some agita in the code, where a command to reset the timebase may or may not have been executed before a subsequent read of the system clock:
This led originally to a special-case for
RESET_TIMEBASE(in 7e55ec6), and more recently to some work to ensure thread-safety in #1081.I'm trying to think of a way to make this less uneven. I suspect there's no real use for the
now = amy.sequencer_ticks()idiom - if you want to schedule things at a specific time relative to now, you should justRESET_TIMEBASEand specify when you want them to happen assuming that time just started. We wouldn't need to readsequencer_ticksat all.This would be bad if you had two programs trying to use the timebase at the same time - if one reset the timebase, the other might be thrown off. But I'm not sure how realistic a problem this is.
The idiom shows up in "real-time" scheduling, where the illusion is that
amy.send()commands are executed immediately (rather than being added to a queue), and the caller does "loose" scheduling by waiting for time to reach some particular point before issuing the commands. This is the common pattern for AMYboard sketches exemplified byacid_generator.py- the sketch counts how many timesloop()has been called, then issue "immediate"amy.sendcommands pertinent to the current beat. This works becauseloop()is called in synchrony to the sequencer clock. Significantly,acid_generatornever actually looks atsequencer_ticks().If we deprecated
sequencer_ticks, we could enforce "you don't know where the sequencer is because you're just sending commands asynchronously, but you can tell me when to play them relative toRESET_TIMEBASE". Then we could do without the the #1081 machinery to make reads of the clock correct even if the timebase reset hasn't yet kicked in -- it feels like "bending over backwards" to preserve a user's fantasy that everything happens immediately. I think it's more elegant to have an interface that implicitly reflects the engine's semantics that commands will be executed in order (or at the scheduled time on the engine's timebase), but the caller doesn't actually know when that is relative to when the commands are sent.