Could someone explain how the
delay function works in Crow? And, ideally, provide an example? I’m assuming it’s similar to
time.sleep() in Python but I can’t quite figure it out.
I’m referring to this from crowlib.lua:
function delay(action, time, repeats)
What is are
repeats expecting? I’m assuming
time is looking for a pause length in seconds.
Not really like
time.sleep since calling
delay doesn’t block – that is, other code after your call to
delay will run immediately, crow doesn’t wait at all before proceeding. Instead you pass in a function that you want to execute
time seconds in the future, and that function will run when the time is up. This is the “callback based” method for dealing with asynchronous code (code that needs to wait for something: a timer, a network request, disk access…) where you ask the system to “call you back” when the wait is over by invoking your function. For example the
repeats sets the number of times the callback function will be called, waiting
time seconds between each call. It defaults to 1, but
delay(f, 1, 5) means “call
f once a second, stopping after 5 times”.
action can be a function that takes 0 or 1 arguments, where the argument passed in is a number that tells you which repeat you’re on.
As an example, try this out in Druid:
delay(function(n) print('call #' .. n) end, 1, 5)
This is basically a convenience wrapper around
metro and uses
metro in its implementation, it just cancels the metro after a certain number of repetitions.
delay also returns the metro so you can
m:stop() it early if you assigned
m = delay(...)
Thanks! Really helpful. I think I can immediately put it to use in a quad LFO I’m trying to build.
Now, to find some equivalent of time.sleep, as I use that all the time in Python…
But can’t you already do that with ‘metro’ via the ‘times’ parameter?
Yes absolutely! The
delay function was added in response to a user request for the ability to delay an action without setting up a whole metro. The
repeats argument is optional so you can simply delay a function call by
You can do the same thing as
time.sleep by splitting your functions into the before-and-after sleep sections, then chaining with the
You can also manage events over time using ASL, but that’s a not-well-documented feature currently.
@Galapagoose Thanks for the additional context. And the tip on splitting functions to get a rough
Last question… how can I get this to work with a function not nested within the statement? I.e., some
function someFunction(n) that I have elsewhere in the script? Can’t seem to get that working. No issues as written though, with all manner of modifications.
delay( after, 1) — wait 1s then call after()
before() — start the sequence
I don’t believe there’s an exact equivalent of this, because
time.sleep blocks the thread and crow only has the one thread of execution. It’s possible to implement a sleep just by busy-waiting (spinning in a loop while keeping track of some counter) but this chews up CPU, is finicky to get totally accurate, and crow would spend all its time running the busy-wait loop. So while it was sleeping, it would not be able to continue running LFOs, reading inputs, or even communicating over the USB port.
crow can seem like it’s doing multiple things at once because it shares CPU time between handling different events. For this to work smoothly no given event handler (metro action or whatever) can take too long, or other event handlers will be left waiting. So generally you can’t say “pause for n seconds”, instead you ask the event loop to keep track of a timer and call you back n seconds in the future. In Python
time.sleep avoids locking up your whole machine because the implementation of
time.sleep asks the operating system to call Python back – the operating system is essentially processing its own event loop, and Python is sharing time with other programs.
You can however write asynchronous code in an imperative style in Lua, thanks to coroutines. A coroutine (wikipedia) is a generalization of a subroutine (function) that can basically return values multiple times, picking up where it left off when another value is requested. Using coroutines and
delay you can write something like this:
local ok, tnext = coroutine.resume(co)
if ok and tnext then
delay(function () cometro(co) end, tnext)
output.action = lfo(1, 5)
while true do
print'so. . .'
print'. . .very. . .'
print'. . .sleepy!'
If you run this you will get text printed to druid with pauses in between, and the LFO will keep running simultaneously. The way this works is kind of inside out: when
coroutine.resume is called, the code wrapped by the coroutine argument
co gets to run until the next
coroutine.resume returns a flag indicating whether resuming the coroutine was successful (unpacked into the
ok variable) followed by any arguments that were passed to
coroutine.yield – here we’re using the argument to
yield to indicate the number of seconds to
delay. This takes a bit of getting used to but is a very powerful tool for advanced control flow.
Meant to respond to this, this ought to work with just
delay(someFunction, 1.0). Would need to see your whole script to troubleshoot further probably.