norns: clock

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

I’m a bit confused about how the tempo set in the the Parameter > Clock menu can be applied to an actual clock.

This is my basic setup:

function init()

    -- Master sequencer step clock
    master_clock_id = clock.run(master_clock)

end -- End init

function master_clock()
  while true do
    clock.sync(1/4)
    pattern_generator.do_master_step()
    --print(clock.get_beat_sec())
    print(clock.get_tempo(master_clock))
  end
end

However, when I change the tempo in the menu, it still reports 90.0 bpm, and the rate at which my step function gets called doesn’t seem to vary.

I notice some other scripts use the Norns Clock system, but, have their own tempo display in the UI, and changes here don’t seem to be reflected in the Menu, either.

I’d ideally like to have the tempo set only in the Menu.

I have Clock Source set to “internal”, incidentally.

there’s an unrelated mistake in this line:

print(clock.get_tempo(master_clock))

since get_tempo doesn’t require any arguments, there’s no need to pass the parent function or anything to it.

otherwise, the snippet works fine for me. is the clock source set to internal?

1 Like

Thanks for getting back to me, @artfwo.

That’s strange. It was definitely not working for me. I wonder if it’s a Fates thing. I’m developing on a Fates, with the latest available Norns.

I’m pretty sure it was. The clock was running, and there wasn’t any other source for it, I don’t think.

I will investigate further when I’m back home.

it shouldn’t be - as this part of the software is not any different on the Fates hardware.

1 Like

I’m still not getting this to work, unfortunately. Is there something else I need to do, to get the tempo of the master clock to respond to the setting in the Clock menu?

Changes to the tempo in the menu seem to have literally no effect either on the reported tempo nor the rate at which the master_clock() function gets executed.

I notice clock settings aren’t getting saved into a preset for the script. I don’t know if those is expected behaviour or is related to the clock issue.

hi hi! hope all’s well :slight_smile:

super strange to see this persist – here’s a quick + dirty ‘it works’ script based on your code above:

function init()
  master_clock_id = clock.run(master_clock)
end

function master_clock()
  while true do
    clock.sync(1/4)
    pattern_generator.do_master_step()
  end
end

pattern_generator = {}

function pattern_generator.do_master_step()
  print(clock.get_tempo())
end

with clock set to internal, i see changes to the tempo parameter (either in menu or by live-executing params:set("clock_tempo",90) etc) print to the maiden repl. whenever you’re able, can you please confirm you aren’t seeing the same with the above code?

Hi @dan_derks thanks for getting back to me.

I can confirm that the code snippet above doesn’t work for me, in the sense that the tempo printed to Maiden remains 120.0, and the master_clock() function continues to be executed at the same rate, irrespective of the value that I set in the Clock menu.

Interestingly, running params:set("clock_tempo",90) does result in the value in the Clock menu changing - it’s just the actual rate master_clock() runs does not change.

UPDATE:

Aha! I reset my Fates using the Reset item in the System menu, and now it appears to be working as it should!

1 Like

I’m having a strange problem that seems to be related to the norns clock and I’d appreciate any advice anyone has.

I’m working on a sequencer for 16n and grid where the step length is selected on grid columns from 1-8 pulses. I’m a Lua beginner, but as far as I can tell I’m passing clock.sync() a value ranging from 0.25-2.0. Most of the time this is working well, but sometimes the clock seems to ignore the duration. I haven’t found a pattern for when it happens but I’ve found a way to consistently reproduce the problem:

In this screen recording you can see that when I enter a new value for time it creates the correct rhythm on the first pass (pausing on step 6), but on each following pass it continues as if there is a value of 1.

My only uses of clock are line 33 in the video: clock.sync(syncTime) and in my init() where line 50 is: clock.run(step).

Any ideas for what I might be doing wrong here?

EDIT: forgot to mention I have a factory norns running 221214

-- 16ngrid seq
-- seq using 16n pitch and grid rhythms

m = midi.connect()
g = grid.connect()

minimum = 1
maximum = 16

running = true
octaves = 3 --set range of each slider. add a scale thing here - i can probably get it from another script
syncSpeed = 0.25

m.event = function(data)
  local d = midi.to_msg(data)
  local p = d.cc - 31
  seq.notes[p] = d.val
end

seq = {
  pos = 1,
  length = 16,
  notes = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  times = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
}

function step()
  while true do
    local length = lengthLoop(seq.pos)
    local syncTime = (syncSpeed * seq.times[length])
    print("step:",length, " time:",seq.times[length],"  syncTime:",syncTime,"tempo:",clock.get_tempo())
    
    clock.sync(syncTime) 
    
    gridredraw()
    redraw()
    
    if running then
      seq.pos = seq.pos + 1
    end
    if seq.pos > seq.length then
      seq.pos = 1
    end

  end
end


function init()
  clock.run(step)
end

function clock.transport.start()
  running = true
end

function clock.transport.stop()
  running = false
end

function gridredraw()
  --local grid_h = g.rows
  g:all(0)
  for i=1,16 do
    g:led(i,seq.times[i],7)
  end
  g:led(seq.pos,seq.times[seq.pos],15)
  g:refresh()
end

function redraw()
  screen.clear()
  screen.update()
end

function g.key(x,y,z)
  if z == 1 then
    seq.times[x] = y
  end
  gridredraw()
end

function lengthLoop(l)
  local l = (seq.pos - 1)
  if l < minimum then l = maximum end
  return l
end

function cleanup()
end

hihi! hope all’s well :slight_smile:

ah! excellent q! clock.sync sleeps until the next subdivision specified arrives, though it’s easy to misinterpret as describing how many beats will pass before it moves onto the next ‘step’ – instead, it describes a multiple to sync to. so, if clock.sync(2) is executed at beat 175.75, it’ll execute the action at beat 176, since 176 is divisible by 2.

this gets explored a bit upthread:

in short, changing the clock.sync value dynamically isn’t a way to describe how long an event should hold for – that’s clock.sleep, which accepts an argument in seconds.

but ultimately, i’d just suggest keeping clock.sync’s value static and at the shortest duration you need to go down to and use a counter mechanism to track pulses passed.

stepping away from keyboard, but if it helps, here’s some quick code:

time_values = {}
-- if desired ppqn is 48, then describe time_values as how many 48ths of a beat a step should last for
for i = 1,16 do
  time_values[i] = 12 -- since 1 beat = 48 pulses, then 12 pulses = 1/16th note duration (in 4/4)
end
counter = 1
current_step = 1

function step()
  while true do
    if counter == time_values[current_step] then
      counter = 1
      current_step = util.wrap(current_step + 1,1,16)
      print(current_step) -- execute next step
    else
      counter = counter + 1
    end
    clock.sync(1/48) -- (48 ppqn)
  end
end

function init()
  print(current_step) -- execute first step on load
  clock.run(step)
end
you *should* also be able to use lattice to do this sorta thing! here's a start, which is still not drawing grid correctly, but seems like the print messages are working as intended!
-- 16ngrid seq
-- seq using 16n pitch and grid rhythms

m = midi.connect()
g = grid.connect()
lattice = require 'lattice'

l = lattice:new{
  auto = true,
  ppqn = 96
}

minimum = 1
maximum = 16

running = true
octaves = 3 --set range of each slider. add a scale thing here - i can probably get it from another script
syncSpeed = 0.25

 s = l:new_sprocket{
    action = function(t) step() end
  }

m.event = function(data)
  local d = midi.to_msg(data)
  local p = d.cc - 31
  seq.notes[p] = d.val
end

seq = {
  pos = 1,
  length = 16,
  notes = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  times = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
}

function step()
  local length = lengthLoop(seq.pos)
  local syncTime = (syncSpeed * seq.times[length])

  s.division = syncTime/4
  print("step:",length, " time:",seq.times[length],"  syncTime:",syncTime,"tempo:", clock.get_beats())
  
  if running then
    seq.pos = util.wrap(seq.pos + 1,1,16)
  end
  
  gridredraw()
  redraw()
end

function lengthLoop(l)
  local l = (seq.pos - 1)
  if l < minimum then l = maximum end
  return l
end

function init()
  -- clock.run(step)
  l:start()
end

function clock.transport.start()
  running = true
end

function clock.transport.stop()
  running = false
end

function gridredraw()
  --local grid_h = g.rows
  g:all(0)
  for i=1,16 do
    g:led(i,seq.times[i],7)
  end
  g:led(seq.pos,seq.times[seq.pos],15)
  g:refresh()
end

function redraw()
  screen.clear()
  screen.update()
end

function g.key(x,y,z)
  if z == 1 then
    seq.times[x] = y
  end
  gridredraw()
end

function cleanup()
end
5 Likes

Thank you so much Dan!!! <3
I really appreciate you taking the time to write this out - it’s a huge help for me both for this project and for seeing how you approach this type of coding. Learning about things like util.wrap() will save me a lot of awkward code in the future.

Sorry I missed the previous discussion of clock.sync() but this all makes sense now. I’ve incorporated your first code into mine and it all seems to be working perfectly! The lattice version is super interesting too. I’m hoping to dig into sequencers more over the next couple months so maybe I’ll learn lattice for the next one now that I’ve seen how it can be implemented in something like this.

2 Likes

I’m trying to send internal clock to two midi devices at the same time.
In params/clock only can only select one device at a time.
Any workaround for this?

hih! excellent timing – this exact case is address in a software update coming in the next few days:

currently running a beta for it in the norns discord if you want to jump on early :slight_smile:


if you want a scripting solution, you could inject something like this into your script:

clock.run(function()
  while true do
    clock.sync(1/24)
    for i = 1,16 do
      midi.vports[i]:clock()
    end
  end
end)
9 Likes

I tried out the scripting method and it works. All my midi devices are now happily sharing one clock :smile:

3 Likes