clocks
The clock library provides a way to create timed function calls: loops, repetition, and delays. Synchronization is possible and the timebase 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:
sections
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 commands or the UI menu.
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 up to 100 coroutines at once.
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
offset sync
We can also specify offsets for a sync
position:
-- sync and resume at the next whole beat as usual:
-- clock.sync(1)
-- sync and resume at the next whole beat with a half-beat delay:
-- clock.sync(1, 0.5)
-- sync to the next 4th beat, but resume earlier by a quarter-beat:
-- clock.sync(4, -1/4)
engine.name = 'PolyPerc'
-- create a swinging feel for a coroutine
-- by delaying even beats by half of the beat length
local beat = 1/4
local offset = beat / 2
local swing = 0
clock.run(function()
while true do
clock.sync(beat, offset * swing)
engine.hz(333)
swing = swing ~ 1
end
end)
A bit of extra care should be taken with negative offsets, as they can lead to somewhat unexpected delays. For example, clock.sync(2, -2.5)
called at beat 0 will schedule resumption of the coroutine at beat 1.5
. This is computed as 4 - 2.5
, where 4
is the least possible beat divisible by the sync value of 2
which can also be scheduled in the future with an offset of -2.5
beats.
With positive offsets, sync will just be delayed by the time specified (in beats).
Generally this allows relatively painless implementation of swinging rhythms in scripts and provides quite interesting results when different swinging patterns are used concurrently.
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 and/or the norns clock parameters), 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
working with multiple norns over Link
It is possible to sync multiple norns units using Link without any additional devices involved. Though Link is a protocol developed and supported by Ableton, it’s use does not require Ableton Live to be running on any device in the chain. Two norns could absolutely stay in sync across a network without any Digital Audio Workstation. Tempo and clock phase can be shared wirelessly by setting PARAMETERS > CLOCK > source
to link
on both devices and ensuring they have the same quantum
.
We’ll also likely want a script running on each unit to start and stop at the same time. norns has special handlers for Link transport start and stop: clock.link.start()
and clock.link.stop()
. In norns-to-norns communications, these Link-specific functions also call the script-defined clock.transport.start()
and clock.transport.stop()
functions.
The code below demonstrates how multiple norns units on the same network can communicate tempo and transport start/stop between each other. Run this script on each unit and press K3 on any unit to start the transport on all of them:
engine.name = 'PolyPerc'
function key(n,z)
if n == 3 and z == 1 and not transport_running then
clock.link.start() -- start Link clock
elseif n == 3 and z == 1 and transport_running then
clock.link.stop() -- stop Link clock
end
end
function clock.transport.start()
print("we begin")
id = clock.run(pulse)
transport_running = true
redraw()
end
function clock.transport.stop()
clock.cancel(id)
transport_running = false
pulse_count = 0
redraw()
end
function pulse()
while true do
clock.sync(1)
engine.hz(333)
pulse_count = util.wrap(pulse_count + 1,1,4)
redraw()
end
end
function redraw()
screen.clear()
screen.move(20,40)
screen.text(transport_running and 'running' or 'not running')
screen.move(pulse_count * 20,60)
screen.text('|')
screen.update()
end
function init()
transport_running = false
pulse_count = 0
capture_preinit()
end
function capture_preinit()
preinit_clock_source = params:get('clock_source')
preinit_sync = params:get('link_start_stop_sync')
preinit_quantum = params:get('link_quantum')
preinit_tempo = params:get('clock_tempo')
print('setting clock source to Link')
params:set('clock_source',3)
print('enabling start/stop sync')
params:set('link_start_stop_sync',2)
print('setting quantum to 1 beat')
params:set('link_quantum',1)
print('setting tempo to 95 bpm')
params:set('clock_tempo',95)
end
function cleanup()
-- important! stop Link clock with script cleanup:
clock.link.stop()
print('resetting parameters')
params:set('clock_source',preinit_clock_source)
params:set('link_start_stop_sync',preinit_sync)
params:set('link_quantum',preinit_quantum)
params:set('clock_tempo',preinit_tempo)
end
If you run into any trouble with establishing a Link connection between multiple norns units or between norns and any another device, be sure to check out this detailed troubleshooting document from Ableton.
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.