norns: clock

20 characters of yes!

1 Like

for “wait for the specified number of beats” cases, using clock.sleep for the duration of a beat (or many beats) as measured by clock.get_beat_sec() could work:

function init()
  pending = false
end

function key(n,z)
  if z == 1 then
    if not pending then
      clock.run(play_after_x_beats,6)
      pending = true
    end
  end
end

function play_after_x_beats(x)
  clock.sync(1) -- quantizes to the nearest beat
  print("current beat: "..clock.get_beats())
  clock.sleep(clock.get_beat_sec() * x)
  print("delayed till: "..clock.get_beats())
  pending = false
end

will return:

current beat: 1830.0018375
delayed till: 1836.003975

Thanks @artfwo & @dan_derks.

I’ve been experimenting with recording midi (which is obviously the gold standard :slight_smile: ) into ableton and this pattern gave slightly better accuracy over the length of the sequence.

local waitCount = 0

while true do
  if waitCount == 0 then
	  -- turn off last note
	  -- play next note
	  waitCount = (<note length in beats> / 0.25) - 1
	else
	  waitcount = waitCount - 1
  end
  clock.sync(0.25)
end
1 Like

oh, hell yeah – i was actually writing a double-back post to mention that the code i shared is pretty literal (waiting a specified amount of time) and won’t likely perform exactly as desired in a traditional clock-sync situation where:

  • each beat coming from a leader might not an exact duration
  • we just want the things to happen at the same time, because that feels rhythmically “tight”

so yeah, an approach i’ve traditionally used is a counter-based system where a counter increments at your smallest desired beat duration and you track those for event execution, which is totally where you landed! :slight_smile: thank you for closing the loop with your results :sparkles:

artem also outlined a nice approach here, which is new to the clock improvements and leverages clock.sync()'s return of the current beat as a built-in counter method (which i need to get in the docs asap :sweat_smile: )

1 Like

with the new clock scheduler this shouldn’t be necessary, i.e. the following code:

local x = 1

clock.sync(x / 4)
if (clock.get_beats() > (x + wait_count)) then play() end
clock.sync(x / 4)
if (clock.get_beats() > (x + wait_count)) then play() end
clock.sync(x / 4)
if (clock.get_beats() > (x + wait_count)) then play() end
clock.sync(x / 4)
if (clock.get_beats() > (x + wait_count)) then play() end

wait_count = wait_count + 1

can be replaced with

local x = 1
clock.sync(x) -- guaranteed to resume at xth beat
play()

since the new scheduler follows the source very closely, tempo changes and midi signal jitter are less of a problem now.

in musical functions i recommend choosing sync over sleep for most cases, as it guarantees stable rhythms in the script with graceful handling of tempo and source changes and better transport syncing.

1 Like

with norns: update 210630 it’s possible to specify an offset for a sync position as follows:

 -- 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)

-- 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)
    -- do something
    swing = swing ~ 1
  end
end)

a bit of extra care should be taken with negative offsets, as they can lead to somewhat unexpected delays, e.g. clock.sync(2, -2.5) called at beat 0 will schedule resumption of the coroutine at beat 1.5, which 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. hope this makes sense :slight_smile:

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.

4 Likes

ran into a bit of an issue…
how slow can the norns BPM receive?

ran into an issue last night with Cheat Codes while trying to send it Ext MIDI Clock at 50bpm.
:stuck_out_tongue:

hope all’s well :slight_smile:

1-300bpm is the supported range — lmk if there was a cc2 issue tho in the thread and i’ll take a peek!

1 Like

Are there any big things to avoid when working with clock coroutines? I am trying to use clock.sync to synchronize 2 pattern_time patterns. Everything works fine if I’m only using one of the patterns in clock-synced mode, i.e. only one clock coroutine running at a time, but as soon as I try to use both synced to the clock (2 clock coroutines running at once) I get some very strange behavior.

I do have both patterns stored in a table, and the coroutines both access the table to modify(record/start/etc) a pattern at index n, but both should be accessing different indices and different patterns.

maybe share the code? sorry i don’t quite get it.

… actually that’s ok, it’s enough i think. i’ll try and reproduce or make a working example.

Here’s the code: orison/orison.lua at 51f95423debb6d1222dc895bd95e9b15646b5793 · evannjohnson/orison · GitHub

It’s quite messy, and I’ll try to make a distilled version to illustrate what I’m talking about. There’s a table called “patterns”, indices 1 and 2 store tables with a pattern and a couple state variables, such that you can access pattern n with “patterns[n].pattern”.

All the clock synced stuff happens in record_start_sync(), record_stop_sync(), start_sync(), stop_sync(), and reset_sync(). All but reset_sync are started in g.key. Reset_sync is started by a special event I put near the end of the patterns, it is started at the top of pattern_note().

The gist of the script is that columns 2-16 is a grid of notes you can play, column 1 is control functions, (1,3) controls pattern 1, (1,4) controls pattern 2. Holding (1,7) while pressing a pattern button clears that pattern starts recording unsynced, pressing the pattern button again stops recording and then controls stopping/starting. Holding (1,6) while pressing a pattern button does the same, but synced to the clock. You can toggle the grid flashing to the clock by pressing key2 on norns while holding (1,7).

Feel free to wait for me to give you a distilled example, my code is pretty rough.

… here’s an example of two parallel clock-driven sequences. one is a steady 16th note and the other steps between a few different sync divisions in sequence.

… does that help at all?


as far as race conditions…

well, our lua VM is single threaded, the question is can we guarantee an order of execution for clock routines that are sync’d to the same beat. that’s a good question; i’ll take a quick look and see if there is an obvious answer.

for now best, to assume that you can’t and try and avoid the possibility of race conditions. (i mean once you get into any sort of complex rhythmic relationship you probably want to assume this anyway.)

so, on the other side of it i will make a brief attempt to grok your script’s management of runtime state. we’ll see.

going on your general description - it seems fine for multiple clock threads to mutate different fields in a table. it might be a generally bad idea to have both threads adding or removing elements in the same table, but even then it depends on the method - there are ways to make it safe.

4 Likes

Thanks for your help - I guess I should finally start making some diagrams so that I can fully grok the script’s management of runtime state…

This is the kind of stuff that was in the last CS course I took, and failed. Time to dig back into it I guess :sweat_smile:

1 Like

ah well, i didn’t mean anything complicated and probably phrased that badly.

so far i haven’t spotted anything obviously scary. ah, well ok, i am skeptical of / would investigate if/how pattern_reset_sync(n) and one or more pattern_*_start_sync(n) are interacting when they run simultaneously with the same n. that seems like it coud easily become hard to predict.

but, i guess you said it’s working with only one pattern… so, sorry - nothing obvious (assuming the ns really are distinct and really are getting passed on to the clock update functions correctly.)

like ok, here’s my dummy check: add print statements (print n in each coro, print the returned id of each coro) to make triple sure that the ns are distinct. (who knows, we could have some bug in the way the clock wrappers handle varargs or something.)

1 Like

I’ve added the print statements: orison/orison.lua at b56b0ddd0708860a1dbcd41a5e1735064ff13d25 · evannjohnson/orison · GitHub

There’s some kind of mixup happening with the patterns, but I’m pretty sure n is being properly passed to all functions. Here’s a sample of the output:

encountered syncer event for pattern 1
pattern reset sync1
742
encountered syncer event for pattern 1
pattern reset sync1
743
pattern_time: not playing
pattern rec start sync2
pattern_time: not playing
744
encountered syncer event for pattern 1
pattern reset sync1
745
pattern rec start
pat2.rec
0
encountered syncer event for pattern 1
pattern reset sync1
746
encountered syncer event for pattern 1
pattern reset sync1
747
encountered syncer event for pattern 1
pattern reset sync1
748
encountered syncer event for pattern 1
pattern reset sync1
749
encountered syncer event for pattern 1
pattern reset sync1
750
pat1.rec
1

Pattern 1 was playing synced, you can see the syncer event being processed. Then I attempt to start recording pattern 2 synced, and it does print pattern rec start sync2, so it’s getting the correct n. You can then see further down that pattern_time has printed pattern rec start, on beat. But, pat2.rec = 0 and pat1.rec = 1, so something has gone wrong here, despite pattern_record_start_sync correctly being passed 2. I can’t figure out why.
̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶ ̶
I got it working. First tried moving all variables out of the table to eliminate weirdness from 2 coroutines accessing the table at the same time. That didn’t work, the pattern_sync functions would still cause weirdness when being run at the same time. So I made separate sync functions for the patterns, to try and divorce the flow of execution (am I using that term right?) for the coroutines as much as possible. And now it seems to be working as intended.

It feels pretty clumsy, but it’ll have to do until I better understand coroutines.

Here’s the working code: orison/orison.lua at d995ec527f93c55ec94ca1c71571a7f9adcbc642 · evannjohnson/orison · GitHub

apologies if there’s a better place to find this, but: has anyone had luck sending clock from norns to Logic Pro X? The following steps aren’t working for me:

  1. Set norns clock to source: internal, whatever tempo. Set norns clock midi out to e.g. port 1
  2. Open up Logic Pro X, and in File > Project Settings > Synchronization, try Sync Mode: MTC. Also go to “MIDI” and turn on “Listen to MIDI Machine Control (MMC) Input”, just for kicks.

Logic does not seem to receive norns’ tempo or transport messages with these settings

hihi!

i think Logic doesn’t receive sync from anything unless it’s MTC (MIDI Time Code), which norns doesn’t currently transmit. seems like for regular MIDI clock devices, Logic needs to be the ‘central transmitting device’: Sync multiple MIDI devices to Logic Pro - Apple Support :confused:

edit: maybe there’s a way to send pulses to Logic’s Tempo Interpreter?
seems like the ‘tap tempo’ command is just a keyboard shortcut. weiiiird.

2 Likes

I’m very new to norns and trying to get an overview of the current sync options. Is the idea of having a global synced transport still alive – or a long term plan? Until then: are there any sequencer apps with synced start/stop when setting the global clock to Ableton Link?

1 Like

norns supports transport sync on the API level, but doesn’t enforce scripts to support it, given that different scripts can have very different ways of following time and user input.

for instance, some scripts will trigger sample playback when a button on the grid is pressed, so there will be no notion of “transport start” in such script completely.

i am not using any scripts synced to transport myself, but i am sure some of them do it.

3 Likes

I’d bet most norns sequencers do. I know Cyrene: a drum sequencer based on Mutable Instruments Grids does