Norns: migrating scripts from `beatclock` to the new global clock (a how-to!)

hey y’all :slight_smile:

ahead of tomorrow’s norns release (which features the much-anticipated global clock), I figured it might help to document how I revisited super old code (less concepts) and successfully updated a beatclock-driven script to take advantage of the global clock! :sparkles:

I figure we can use this as a space for community support getting scripts updated, so please feel free post your questions or share your speedbumps + successes!

overview: by using the new clock module, you can drive your script with a co-routine that syncs to the global clock and can be driven internally or thru MIDI, Ableton Link, or crow – without having to manage each of those sources/destinations separately in your script!


rounding up the old

first, I search my script for the word beatclock to find my old beatclock calls.

line 109 yields our first appearance
local beatclock = include "lib/beatclock-crow"
clk = beatclock.new()
clk_midi = midi.connect()
clk_midi.event = function(data) clk:process_midi(data) end
clk.on_select_external = function() clk:reset() end --from nattog

since I had aliased beatclock as clk throughout my script, I took a peek to see what clk stuff was being called elsewhere in my script (a search turns up 17 instances of the word ‘clk’).

line 369 yields a bunch of stuff in my `init()`
  clk.on_step = function() iterate() end
  clk.on_select_internal = function() clk:start() crow.input[2].mode("none") end
  clk.on_select_external = function() print("external MIDI") crow.input[2].mode("none") end
  clk.on_select_crow = function()
    crow.input[2].mode("change",2,0.1,"both")
    crow.input[2].change = change
  end
  clk:add_clock_params()
  params:add{type = "number", id = "midi_device", name = "midi device", min = 1, max = 4, default = 1, action = function(value)
    clk_midi.event = nil
    clk_midi = midi.connect(value)
    clk_midi.event = function(data) clk:process_midi(data) end
  end}
and line 450 is where I started the old `beatclock` running
clk:start()

first blood

since I don’t want to use beatclock, I don’t need the instantiation stuff at line 109. the new global clock system is always running, from when norns wakes to when it sleeps.

what I do need to do is to tell my script how I want it to sync to the global clock. I do this by establishing a coroutine. coroutines are dope – can be synchronous or asynchronous, with the flexibility to pause and resume on demand.

for my script, I want to establish a coroutine that syncs to the global clock at a 16th note rate and performs the action that makes my script go.

when I was using beatclock, that go action was:

clk.on_step = function() iterate() end

so, I’ll instantiate a coroutine to simply do the same thing:

  function pulse()
    while true do
      clock.sync(1/4)
      iterate()
    end
  end

this just says:

  • while my coroutine is running / true (we’ll get it running a little later)…
  • sync to the global clock at a subdivided rate (1 == 1 beat, so 1/4 == 1/4 of a beat == 16th notes at 4/4) and…
  • execute the iterate() function on every tick!

since the new global clock handles internal clocking, MIDI, crow, and Link, I don’t need to differentiate between the sources in my script – the system handles all the swapping!

now, I can delete all of that garbage from line 369! wowowow, we got rid of so much (honestly, sorta unreadable) code already and replaced it with a terse 6 line coroutine!

i’ll also kill the line at 450, which started the old beatclock, and replace it with a command that starts my pulse coroutine running:

clock.run(pulse)

I fire up the new coroutine-ified less concepts and confirm it’s all working!


tying up loose ends

nb. this bit isn’t typical of what most scripts will have to do. less concepts was written a long while ago and features some wonky choices™

removing my script’s beatclock dependency will also change my script’s params (beatclock supplies three: “clock”, “bpm”, and “clock_out”), so I do a quick search to see if I’ve got any hard references to these throughout my script.

turns out, I reference beatclock params in two places:

line 978 has getters
io.write(params:get("bpm") .. "\n")
io.write(params:get("clock_out") .. "\n")
line 1022 has setters
params:set("bpm", load_bpm)
params:set("clock_out", load_clock)

just to keep the experience equal for users of both the pre- and post-beatclock versions, I’ll just swap these out with the new params references:

978 becomes:

io.write(params:get("clock_tempo") .. "\n")
io.write(params:get("clock_midi_out") .. "\n")

and 1022 becomes:

params:set("clock_tempo", load_bpm)
params:set("clock_midi_out", load_clock)

…and that’s it! I slashed a ton of unreadable muck from my ancient script (see the full new version) and inherited all the shiny new global clock goodies. hope this helps y’all contextualize / approach yours a bit more easily :hugs:

30 Likes

20 characters of super excited!

Longer post… I just got a norns again after selling my first one because sorting out timing was the barrier that pushed me to look towards pre-baked solutions instead. Finally caved and got a grid and norns again last month to dig in deeper and this update is exactly on time with where I am in understanding the lua code involved here :smiley:

Thanks to all involved in getting this release together!

3 Likes

Thank you @dan_derks, this is great. As someone who can spend a lot of time struggling through reading the codebase and other people’s scripts in order to figure stuff out, these kinds of walkthroughs are much appreciated.

6 Likes

Thanks for this write up @dan_derks! Now, to de-beatclock some scripts :smiley:

Dan, this is so helpful. My question probably falls more into coding 101 terrority, but here goes. In Compass, the command sequence has some clock-related functions that increase/decrease my current metro’s speed. If I was using the global clock system instead, could I simply have those pass an argument to my function, like so?

function pulse(division) 
  while true do 
    clock.sync(division)
    iterate() 
  end
end

Hope that makes sense.

I don’t think you can pass arguments to pulse here. I believe you want division to be a param or global variable, so it grabs the new value everytime it runs, like so

function pulse() 
  while true do 
    clock.sync(params:get('division'))
    iterate() 
  end
end

edit: I’m wrong about passing args… listen to Dan (though the above should work also)

3 Likes

I can test a bit later, but I think this is cased out in the docs as possible:

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
2 Likes

ah my apologies. I confess I skipped over the docs and assumed clock.run just took a function as a single arg.

Thank you both – will experiment later today.

you’re both right! these are different use cases. depends if you want global control: then @crim is correct and that’s how i did it in awake. but the clock study shows a nice way to pass args when making a bunch of static coroutines

3 Likes

ok fantastic - that was easy to add even with ratcheting

HOWEVER

syncing to the bar position is currently doing my head in :slight_smile:

The solution here might help.

Note: it’s a workaround due to a bug that’s been fixed but not released yet.

The simpler code that will work once the bug has been fixed is a few posts further up the same page.

1 Like

thank you!!! saved me a lot of head scratching :slight_smile:

The snippet itself has me scratching my head, to be honest, but glad it works.

I’m really struggling to get my head around how to use the new clock, but once I had bar syncing working, being able to sync my Fates to Samplr on my iPad was something of a revelation.

1 Like

ah nice - all locked in - iPad , kria and modular all locked together!!!

How’re you syncing your modular gear? I made a Spink0 module, but haven’t had much success with syncing it to my iPad.

Crow!

although in the past, before crow, I used to do things like using a track of a sequencer to put out a clock via midi/expert-sleepers fh-1. Crow working nicely though

I have an FH-2, so will try that. It should be able to receiver MIDI clock and reset messages from the new Clock (though obviously that only works with Fates as the clock master).

Having said that, maybe the FH-2 also sends its internal clock to connected MIDI devices. Never thought to try.

Crow seems… dare I say it… a little over-priced for what it does.

If you just use it as a CV/Gate expander for Norns - possibly - but I think it has a huge amount of potential (which I’m not seeing used by current scripts yet). For me it opens up options for making more tools that move easily amongst my various sound making devices. I’ve certainly been having fun driving it via druid and on board scripts directly doing stuff I don’t know how to do any other way

Fair enough.

I’m new to the “Monomeverse” (Norns Shield was my “gateway drug”), so from my outsider’s perspective, it seems there’s quite a lot of overlap between the various Monome products.

Ansible, for example, seems to be very similar to Crow (though with more IO).

Neither seem to appear very often on the 2nd-hand market (I’m a cheapskate, so most of my gear is either DIY or 2nd-hand).