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 action
and repeats
expecting? I’m assuming time
is looking for a pause length in seconds.
1 Like
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 setTimeout
function in JavaScript is similar.
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(...)
2 Likes
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…
ETA:
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 time
.
You can do the same thing as time.sleep
by splitting your functions into the before-and-after sleep sections, then chaining with the delay
function.
You can also manage events over time using ASL, but that’s a not-well-documented feature currently.
1 Like
@Galapagoose Thanks for the additional context. And the tip on splitting functions to get a rough time.sleep
equivalent.
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.
function before()
print’before’
delay( after, 1) — wait 1s then call after()
end
function after()
print’after’
end
before() — start the sequence
2 Likes
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:
function cometro(co)
local ok, tnext = coroutine.resume(co)
if ok and tnext then
delay(function () cometro(co) end, tnext)
end
end
output[1].action = lfo(1, 5)
output[1]()
cometro(coroutine.create(function ()
while true do
print'so. . .'
coroutine.yield(1)
print'. . .very. . .'
coroutine.yield(2)
print'. . .sleepy!'
coroutine.yield(3)
end
end))
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.yield
statement. 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.
1 Like
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.