clocks
the clock library provides a way to create timed function calls: loops, repetition, and delays. synchronization is possible and time base can come from a variety of sources. the clock library uses lua coroutines.
curious to see it all work together in realtime? here’s an introduction to working with clocks + softcut:
commands
id = clock.run( func )
— start a new coroutine with function func. returns idclock.cancel( id )
— cancel coroutine idclock.sleep( time )
— resume in time secondsclock.sync( beats )
— resume at beats count according to global tempobeats = clock.get_beats()
— returns current time in beatstempo = clock.get_tempo()
— returns current tempobeat_sec = clock.get_beat_sec()
— returns length of a single beat at current tempo in seconds
callbacks
these functions can be script defined and are called by system events:
clock.transport.start
— called by transport startclock.transport.stop
— called by transport stop
use
simple delay
clock.run
starts a new coroutine with the function you provide:
function init()
print("starting now")
clock.run(later)
print("done with init")
end
function later()
clock.sleep(2)
print("now awake")
end
when executed, you will see starting now, done with init, (two seconds delay and then): now awake. clock.run
starts a coroutine with the function later
. this function immediately sleeps for two seconds, which means the init
function resumes and finishes before later
is able to print its output.
repetition and sync
you can sleep according to units of tempo (beats) by using clock.sync
instead of clock.sleep
.
engine.name = 'PolyPerc'
function strum()
for i=1,8 do
clock.sync(1/4)
engine.hz(i*100)
end
end
function key(n,z)
if n==3 and z==1 then
clock.run(strum)
end
end
this script starts a new coroutine when K3 is pressed. the strum
function plays eight notes, sleeping for 1/4 of a beat (= 1/16th note in 4/4) between notes, synced to the global tempo.
you can change the global tempo using parameters via command or menu. see the later section.
arguments
pass arguments to a clock function by appending them within the clock.run
call:
engine.name = 'PolyPerc'
function strum(n,speed)
for i=1,n do
clock.sync(1/speed)
engine.hz(i*100)
end
end
function key(n,z)
if n==3 and z==1 then
clock.run(strum, math.random(16), math.random(8))
end
end
now each strum
is executed with different arguments. note that you can overlap multiple runs by pressing the key rapidly! a new coroutine is started with each run, so you can have numerous processes running at once.
clock.sync
sleeps until the next subdivision specified arrives, so the timing is effectively quantized to the global tempo (which is the goal of this use case).
loop
so far all of our coroutines end by themselves, by reaching the end of their own execution. we can create forever-running loops using a while true
inner loop. these will continue running until you launch a new script or cancel them manually with clock.cancel
.
to cancel a coroutine, you need to store the id
generated by clock.run
. we later use this id
with clock.cancel
.
engine.name = 'PolyPerc'
function forever(freq)
while true do
clock.sync(1/4)
engine.hz(freq)
end
end
function init()
clock_id = clock.run(forever, 220)
end
function key(n,z)
if n==3 and z==1 then
clock.cancel(clock_id)
end
end
multiples
you can run lots of coroutines at once: up to 100.
in this script we capture id
s using a table so we can cancel them in the future.
engine.name = 'PolyPerc'
function forever(freq,rate)
while true do
clock.sync(1/rate)
engine.hz(freq)
end
end
function init()
voice = {}
voice[1] = clock.run(forever,333,3)
voice[2] = clock.run(forever,666,1)
voice[3] = clock.run(forever,999,2)
voice[4] = clock.run(forever,111,0.33)
end
use maiden’s command line to cancel individual voices, eg clock.cancel(voice[3])
complex timing
multiple clock.sync
and clock.sleep
commands are allowed within the same function. this script creates a swing rhythm:
engine.name = 'PolyPerc'
function forever(freq)
while true do
clock.sync(2/3)
engine.hz(freq)
clock.sync(1/3)
engine.hz(freq)
end
end
function init()
clock.run(forever,333)
end
query
you can query the current tempo, beats, and seconds-per-beat:
print(clock.get_tempo())
t = clock.get_beats()
softcut.loop_start(1,clock.get_beat_sec())
transport callbacks
scripts can define their own callback functions for transport start and stop:
engine.name = 'PolyPerc'
function pulse()
clock.sync(1/4)
engine.hz(333)
end
function clock.transport.start()
print("we begin")
id = clock.run(pulse)
end
function clock.transport.stop()
clock.cancel(id)
end
remote start/stop events can be generated by Link or external MIDI.
nb. from Ableton: Link currently provides tempo sync and a grid to which apps can align. In Live 9, there are no Song Position or Start/Stop messages sent via Link, nor can any other MIDI data be sent. In Live 10 the feature “Start Stop Sync” (which can be found Live’s Link/MIDI preferences) additionally shares Transport Start and Stop Commands.
by defining clock.transport.start()
and clock.transport.stop()
in your script, you tell norns what to execute whenever a transport message is received. depending on the current clock source, norns has built-in handlers to help keep your devices in sync.
if clocking via MIDI, received “start” and “stop” MIDI messages will automatically call the clock transport functions.
if clocking via Ableton Link (and if ‘Start Stop Sync’ is enabled in Live’s Link Preferences), Live’s transport state will automatically call the clock transport functions.
the internal clock and crow are a bit more bare-metal – you have to define what should execute the clock transport functions.
here’s a longer example of a basic sequencer structure which uses the clock transport:
-- define what should happen when the transport starts
function clock.transport.start()
step = 0
-- assign a variable to a coroutine allows it to be canceled later
my_sequencer = clock.run(sequence)
-- keep track of the transport state:
transport_active = true
screen_dirty = true
end
-- define what should happen when the transport stops
function clock.transport.stop()
clock.cancel(my_sequencer)
transport_active = false
end
-- this function loops until canceled by clock.transport.stop()
-- it advances the sequencer by one step every 1/16th note
function sequence()
if params:string("clock_source") ~= "midi" then
clock.sync(4) -- wait until the "1" of a 4/4 count
end
while true do
step = util.wrap(step + 1,1,16)
if step == 1 then print(clock.get_beats()) end
screen_dirty = true
clock.sync(1/4) -- in 4/4, 1 beat is a quarter note, so sixteenths = 1/4 of a beat
end
end
function init()
-- clock.run doesn't always need to be pointed to external functions!
-- here, we define a screen redraw coroutine inside of our clock.run:
screen_redraw_clock = clock.run(
function()
while true do
clock.sleep(1/30) -- 30 fps
if screen_dirty == true then
redraw()
screen_dirty = false
end
end
end
)
step = 0
screen_dirty = true
end
function key(n,z)
-- since MIDI and Link offer their own start/stop messages,
-- we'll only need to manually start if using internal or crow clock sources:
if params:string("clock_source") == "internal" or params:string("clock_source") == "crow" then
if n == 3 and z == 1 then
if transport_active then
clock.transport.stop()
else
clock.transport.start()
end
screen_dirty = true
end
end
end
function redraw()
screen.clear()
for i = 1,16 do
screen.level(step == i and 15 or 3)
screen.move(10 + (i*5),30)
screen.text("|")
end
if params:string("clock_source") == "internal" or params:string("clock_source") == "crow" then
screen.move(20,50)
screen.level(15)
screen.text(transport_active and "K3 to stop" or "K3 to start")
end
screen.update()
end
parameters
the CLOCK
menu is available in the PARAMETERS
screen.
source
this sets the sync source for the global tempo.
internal
sets norns as the clock sourcemidi
takes any external midi clock via connected USB midi deviceslink
enables ableton link sync for the connecting wifi networkcrow
takes sync from input 1 of a connected crow device
link has a link quantum
parameter to set the quantum size.
crow additionally has a crow in div
parameter to specify the number of sub-divisions per beat.
the tempo
parameter sets the internal and link tempos. this tempo will be automatically updated if set by an external source (midi, crow, or remote link device).
you can set the tempo in your script by the normal method of setting parameters:
params:set("clock_tempo",100)
out
clock signals can be transmitted via midi or crow.
midi out
sets the port number (which midi device, via SYSTEM > DEVICES) on which to transmit.
crow out
sets the output number, and also has a crow out div
setting for beat subdivisions.