Link search Menu Expand Document

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 id
  • clock.cancel( id ) — cancel coroutine id
  • clock.sleep( time ) — resume in time seconds
  • clock.sync( beats ) — resume at beats count according to global tempo
  • beats = clock.get_beats() — returns current time in beats
  • tempo = clock.get_tempo() — returns current tempo
  • beat_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 start
  • clock.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 ids 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

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 source
  • midi takes any external midi clock via connected USB midi devices
  • link enables ableton link sync for the connecting wifi network
  • crow 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.