Crow: Delay function

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.