Norns: clock

hey y’all – ahead of tomorrow’s release exposing the global clock, i put together a bit of a “how I migrated code I hadn’t looked in months from beatclock to global clock, survived, and thrived!”: Norns: migrating scripts from `beatclock` to the new global clock (a how-to!)

10 Likes

it’s worth mentioning that since its first release the clock module has got many important fixes by @Dewb and @ngwese, documentation by @okyeron and @dan_derks, and of course thanks to @tehn who implemented the params menu integration and did the whole release management for this update! cheers to you all, guys! :tada:

12 Likes

I’m curious if it would it be better to use the new clock system in my code where I have multiple sequences running off of a master metro at different clock divisions.

Typically I’ve been following this pattern in my main metro callback:

for i=1,#sequencers do
    sequencers[i].div_count = sequencers[i].div_count % clock_division + 1
    if sequencers[i].div_count == 1 then
      -- do sequencer stuff
    end
end

so if clock_division is 4, the sequencer will only play every 4 iterations when the modulo calculation equals 1.

My understanding is with the new system I could instead define multiple coroutines for each sequence, where each one syncs to master tempo at a different division. Is this more stable and/or performant? Are there benefits for me to refactor this to use coroutines other than it maybe looking cleaner and being more readable?

there are no performance or stability benefits. if you don’t use the coroutine features extensively and don’t need external sync sources, it’s just cleaner code.

1 Like

Is there a way to query which source the clock is using? The use case here would be to allow a user to change clock tempo with an encoder if the source is internal. I see a setter, but no getter.

2 Likes

everything’s in the params, so I think you can just call params:get("clock_source"). If it returns 1 the source is internal. You can see all the param IDs here

2 Likes

also params:string(“clock_source”)

6 Likes

ah! Thank you. I’ve somehow overlooked params:string all this time. Meanwhile I’m over here trying to keep track of all these dang indices

4 Likes

Very cool. I just updated my sequencer script. Drop-in replacement for what I was doing before.

1 Like

This is great. Excited to see Norns development is continuing in a direction that the apps I’m interested in writing will greatly benefit from.

One issue I’m running into (this is my first app so it may be the way I’ve written my sequencer): When synced to external midi and setting clock.sync within a while true do loop, there is a tiny delay on the 1st downbeat every time I start my external sequencer. If I move the engine.hz outside of the while loop then it sounds the moment I hit start on my sequencer (but only once obviously).

Here is the relevant code:

function clock.transport.start()
  clock_id = clock.run(sequencer)
end

function sequencer()
  sequencer_pos = 0
  
  while true do
    clock.sync(1/4)
    engine.hz(get_note())
  end
end

Any help would be greatly appreciated - thanks!

Completely untested random thought - try putting engine.hz inside a function that is used in the while loop?

1 Like

The behavior you are experiencing makes sense to me. According to the docs, clock.sync suspends the coroutine until the given duration has passed. Therefore, on transport start, you are immediately calling sequencer(), which will first suspend for a quarter note (first iteration of the while loop), and then call engine.hz, hence the delay. That’s my guess. There would be multiple ways to fix it, swap .sync and .hz or call .hz once before the while loop, etc.

2 Likes

Thanks for the thought @okyeron! engine.hz is actually inside of a separate function - I just simplified the code for the sake of this thread.

@Artaos I think I had actually tried both of those suggestions with no luck, but I’m not positive I had tried to call .hz once outside of the loop. The delay doesn’t seem to be a quarter note’s length (and the length seems to change on every start message). If neither of those suggestions work I’ll take a short video demonstrating the behavior. Thanks for the help!

1 Like

Swapping it does indeed have the first note land on the 1, but there is still sometimes (maybe 10% of the time) an unquantized gap between the 1st and 2nd note and then things seem to even out. Here’s a super short video: https://imgur.com/a/d4vr9Dg

I’ll experiment with the rest of the code and report back if I gain some insight in case anyone else runs into this issue.

What’s the expected type of data for clock.get_tempo()?

Looks to be a big float like 130.00013500014 when I do screen.text(clock.get_tempo()) and taking clock from Link.

Best practice here would be to use util.round (number, quant) perhaps when displaying the tempo?

Should that value be left alone as a float when setting tempo? like params:set("clock_tempo",clock.get_tempo()+delta) or better to round it first?

What level of precision would be recommended here?

I’m curious about that, too.

I noticed that external clock values are always rounded to set the “clock_tempo” param, which is updated every 1 s:

Link transport control?

docs say:

remote start/stop events can be generated by link or external midi.

but how do you do that?

I have these functions setup, but don’t get any transport start/stop from link. Do I need something else?

function clock.transport.start()
  print("transport.start")
  id = clock.run(pulse)
end
function clock.transport.stop()
  print("transport.stop")
  clock.cancel(id)
end

EDIT - top post says: no start/stop synchronization in link

Has this been added since then or is it still not functional?

Is this little delay related to this Github item

starting coroutines inside transport callbacks may be a bit tricky. it’s safe to assume that clock.sync(1/4) in your couroutine will wake up at 0.25 beats, and not at 0 beats as expected.

it might help to start the coroutine with the engine.hz call as follows:

function sequencer()
  engine.hz(get_note())

  while true do
    clock.sync(1/4)
    engine.hz(get_note())
  end
end

or wait for 1 or more whole beats before you start your norns sequencer:

function sequencer()
  clock.sync(1)

  while true do
    engine.hz(get_note())
    clock.sync(1/4)
  end
end

we’re working on fixing the syncing behaviour on transport start, but for now i don’t recommend relying on it for all use cases.

2 Likes

clock.get_tempo() returns a float. so the answer to your question really depends on what you want to show in your script. if you want to round the number down to n digits, use string.format.

clock_tempo parameter is updated every 1 second so it’s in sync with the external source tempo. the value is rounded indeed (parameter type is number and can only be increased/decreased by 1).

should work (but see the transport quirk described above). do you also get the tempo and beats synchronized via link? does your other link client show the norns node in “links” counter?

1 Like