Just to let you know, I retro-fitted your most recent clock setup to my Grids port script, and all is now working great, so thank you again @artfwo!

1 Like

while it’s possible, i wouldn’t recommend mixing metro callbacks and coroutines for the sake of code clarity. you can use clock.sleep(x) inside the coroutine to resume its execution in x seconds.

PR created and a fix will be available in the next update. Thanks for reporting the issue!

How might that look?

Wouldn’t the clock re-sync itself when it resumes, defeating the object of being able to shift every other pulse an arbitrary amount?

Happy to help!

basically depends on how much you sleep, i.e.

function seq()
  local beat_duration = clock.get_beat_sec()

  clock.sync(4) -- will resume at 4
  clock.sleep(beat_duration / 4) -- will resume at 4.25
  clock.sync(4)  -- will resume at 8
  clock.sleep(beat_duration * 5) -- will resume at 13
  clock.sync(4)  -- will resume at 16
end

but if you want to resume at a particular fraction of a beat, better use clock.sync, as it will take the reference beat into account.

2 Likes

I’ve been playing around with implementing a 16th note swing based on the conversation above, and have it working, but don’t entirely understand why.

For example, I would expect if I am running clock.sync(1/4) at 60bpm, a 16th note happens every .25 seconds. So, to get a swing I could delay every other note by up to ~ .24 seconds, right? It’s very likely I’m being dumb at basic math (its been known to happen…)

Instead, swing stops working at about 0.125 (a 32nd note), and the sync rate sounds as if it slows down to a different beat division. If I cap the value it’s working great however. Maybe I’m thinking about this the wrong way?

Here’s my script, if anyone’s interested (with the offending line commented out):

engine.name = 'PolyPerc'

local swing = 0

function pulse(freq)
  while true do
    clock.sync(1/4)
    local swing_amt = params:get('swing')
    if swing == 1 and swing_amt > 0 then
      -- local delay = (clock.get_beat_sec()/4) * (swing_amt/100) -- doesnt work when swing above 49%
      local delay = (clock.get_beat_sec()/4) * (swing_amt*.49/100) -- value scaled to work up to 100%
      clock.sleep(delay)
      engine.hz(freq)
    else
      engine.hz(freq)
    end
    swing = (swing + 1) % 2
  end
end

function init()
  params:add_control('swing', 'swing', controlspec.new(0, 100, 'lin', 0, 0, '%'))
  clock_id = clock.run(pulse, 70)
end
1 Like

the code looks correct, but clock.sync might have a bug causing it to skip sync points, when you’re calling it shortly before the next sync point (<50% of the sync division actually).

a fix is already in norns master, so you can try that, if you’re comfortable building code, or wait till the next update and check if that solves the problem.

1 Like

Is that the same bug I found?

20 characters of yes :slight_smile:

1 Like

Is this fix applied in the latest update? I’m converting a script I’m working on to use the new clock system, and when I change the source from internal to crow it sounds rather imprecise, like a jittering clock or randomly missed triggers.

yes, the fix is released since norns 200604. could you share the (ideally minimal possible) code that exposes the issue?

The entire script is pretty minimal, bear in mind this is my very first attempt, trying to learn norns, lua, and a new sc engine at the same time, so I would not be surprised if I made an error somewhere.

--  arpcore
--  v0.01 by robotfunk
--  
--  /////////////////////
--  / / / / / / / / / / / / /
--  /  /  /  /  /  /  /  /  /  /  /
--  /   /   /   /   /   /   /   /   /   /   /
--  /    /    /    /    /    /    /    /    /    /    /
engine.name = 'KarplusRings'
musicutil = require 'musicutil'
er = require 'er'
mode = 1
toniclist = musicutil.NOTE_NAMES
tonicnum = 1
mode_name = musicutil.SCALES[mode].name
m = midi.connect()
m:start()

function init()
  generate_notes()
  noteIndex = 1
  length = 3
  stride = 1
  offset = 0
  pulses = 1
  End = 16
  generate_gates()
  --general params
  --params:add{type = "option", id = "use_karplus_rings", name = "Use Karplus Rings", options = {'yes','no'}, default = 1}
  params:add{type = "option", id = "send_midi", name = "Send MIDI Out", options = {'yes','no'}, default = 1}
  params:add{type = "option", id = "send_crow", name = "Send Crow Out", options = {'yes','no'}, default = 2}
  params:add{type = "option", id = "crow_outs", name = "Crow Outputs", options = {'1/2','3/4'}, default = 1}
--  params:add{type = "option", id = "send_osc", name = "Send OSC Out", options = {'yes','no'}, default = 2}
  params:add_separator()
--KarplusRings params
  --cs.AMP = cs.new(0,1,'lin',0,0.5,'')
  params:add_control("amp","amp", controlspec.new(0,1,"lin",0,0.5,""))
  params:set_action("amp", function(x) engine.amp(x) end)
  --cs.BPF_FREQ = cs.new(100,10000,'lin',0,0.5,'')
  params:add_control("bpf_freq","bpf_freq", controlspec.new(100,10000,"lin",0,0.5,""))
  params:set_action("bpf_freq", function(x) engine.bpf_freq(x) end)
  --cs.BPF_RES = cs.new(0,4,'lin',0,0.5,'')
  params:add_control("bpf_res","bpf_res", controlspec.new(1,2,"lin",0,0.5,""))
  params:set_action("bpf_res", function(x) engine.bpf_res(x) end)
  --cs.COEF = cs.new(0,1,'lin',0,0.11,'')
  params:add_control("coef","coef", controlspec.new(0,0.75,"lin",0,0.11,""))
  params:set_action("coef", function(x) engine.coef(x) end)
  -- cs.DECAY = cs.new(0.1,15,'lin',0,3.6,'s')
  params:add_control("decay","decay", controlspec.new(0,15,"lin",0,3.6,"s"))
  params:set_action("decay", function(x) engine.decay(x) end)
  --cs.LPF_FREQ = cs.new(100,10000,'lin',0,4500,'')
  params:add_control("lpf_freq","lpf_freq", controlspec.new(100,10000,"lin",0,4500,""))
  params:set_action("lpf_freq", function(x) engine.lpf_freq(x) end)
  --cs.LPF_GAIN = cs.new(0,3.2,'lin',0,0.5,'')
  params:add_control("lpf_gain","lpf_gain", controlspec.new(0,3.2,"lin",0,0.5,""))
  params:set_action("lpf_gain", function(x) engine.lpf_gain(x) end)
--seq params
--length
  params:add_control("length","length", controlspec.new(1,16,"lin",1,3,""))
  params:set_action("length", function(x) length=x end)--numNotes
--stride
  params:add_control("stride","stride", controlspec.new(-16,16,"lin",1,1,""))
  params:set_action("stride", function(x) stride=x end)
--pulses
  params:add_control("pulses","pulses", controlspec.new(1,16,"lin",1,1,""))
  params:set_action("pulses", function(x) pulses=x end)
  params:bang()
  clk=clock.run(pulse)
end
  
function clock.transport.start()
  clock.cancel(clk)
  clk = clock.run(pulse)
end

function clock.transport.stop()
  clock.cancel(id)
end
  
function pulse()
  while true do
    clock.sync(1/4)
    step()
  end
end

function step()
  if (gates[noteIndex]) then
    engine.hz((440 / 32) * (2 ^ ((notes[noteIndex] - 9) / 12)))
    if (params:get("send_midi")==1) then
      chan=1
      note=notes[noteIndex]
      m:note_on(note, 100, 1)
      noteoff_delayed = metro.init()
      noteoff_delayed.time = .05
      noteoff_delayed.count = 1
      noteoff_delayed.event = 
        function(chan, note) 
          m:note_off(note, 0, 1)
          metro.free(noteoff_delayed.id)
        end--function
      noteoff_delayed:start()
    end
  end
  noteIndex = noteIndex + stride
  while (noteIndex < 1) do
    noteIndex = noteIndex + length
  end
  while (noteIndex > length) do
    noteIndex = noteIndex - length
  end
  redraw()
end
  
function key(n,z)
  if ((n==2) and (z==1)) then 
    mode = mode + 1 
    if (mode>#musicutil.SCALES) then mode = 1 end
    generate_notes()
  end
  if ((n==3) and (z==1)) then shuffle(notes) end
end

function enc(n,d)
  if (n==1) then 
    params:delta("stride", d) 
  end
  if (n==2) then 
    params:delta("length", d) 
    generate_gates()
  end
  if (n==3) then 
    params:delta("pulses", d) 
    generate_gates()
  end
end

function get_value_by_shift(t, index, shift)
  index = (index + shift - 1)%#t + 1
  return t[index]
end

function redraw()
  End = (pulses + length) -1
  if (End>16) then End=End-16 end
  screen.clear()
  screen.level(15)
  screen.line_width(1)
  
  for i=1,16 do
    screen.rect( (i-1)*8+1 , 19, 7, 8)
    if (gates[i]) then screen.fill() else screen.stroke() end
  end
  
  screen.level(6)
  for i=1,16 do
    screen.rect( (i-1)*8+1 , 19, 7, 8)
    if (i==(noteIndex)) then screen.fill() else screen.stroke() end
  end
  --screen.stroke()
  --screen.move((pulses-1)*8+3, 25)
  --screen.text(">")
  --screen.move((End-1)*8+3, 25)
  --screen.text("<")
  screen.move(0,40)
  screen.text(mode .. " " .. musicutil.SCALES[mode].name)
  screen.move(0,8)
  screen.text("stride: "..stride)
  screen.move(0,60)  
  screen.text("length: "..length)
  screen.move(92,60)
  screen.text("pulses: "..pulses)
  screen.update()
end

function cleanup()
  -- deinitialization
end

function generate_notes()
  notes = musicutil.generate_scale_of_length(36+(tonicnum-1),musicutil.SCALES[mode].name,16)
end
  
function generate_gates()
  gates = er.gen(pulses, length)
end

function shuffle(tbl)
  for i = #tbl, 2, -1 do
    local j = math.random(i)
    tbl[i], tbl[j] = tbl[j], tbl[i]
  end
end

--copied from foulplay, not (yet?) used
local function rotate_pattern(t, rot, n, r)
  -- rotate_pattern comes to us via okyeron and stackexchange
  n, r = n or #t, {}
  rot = rot % n
  for i = 1, rot do
    r[i] = t[n - rot + i]
  end
  for i = rot + 1, n do
    r[i] = t[i - rot]
  end
  return r
end

thanks, so clock-wise the code looks mostly fine, except these lines:

function clock.transport.stop()
  clock.cancel(id)
end

i think you could call clock.cancel(clk) here instead.

crow clock takes signal from input 1. does you script sound of of sync with the signal on input 1?

Yeah I should fix that stop code, it has never been called so that can’t be the problem.

And yes, I’m feeding input one of crow with a square LFO, and it sounds jittery.

Last night I noticed that even in clock’s internal mode, very rarely I hear a jump as well, like in once every few minutes. When I select crow as clock source it jitters all the time.

hey, unfortunately i don’t have a crow to reproduce the problem, but just to exclude the problem with crow and/or external LFO could you try doing something like

crow.input[1].change = step
crow.input[1].mode("change", 1.0, 0.1, "rising")

i.e. triggering step on the actual change event from the crow input?

1 Like

That works and is tight as. Obviously I’d like to be able to actually clock it with real triggers. Tried a bunch of sources that I use to clock the rest of the modular, and everything results in the same jitter. Could it be that crow remembers some setting, or is even still running a script that interferes with the ability to clock input 1? I only barely tried crow months ago and I’m just returning to it now, so I’m not ruling out any silly mistakes on my part.

To follow up on my last post : if I do a crow.clear() at the beginning that should fix any weirdness from settings or scripts on crow, right? Because I just tried that and the jitters are back instantly.

That’s strange, does crow clock subdivision affect the jitter in any way?

1 Like

Not really, the jitter is pretty consistent in all subdivisions.
Looks like others are experiencing the same issue with crow clock inputs and MIDI as well : Norns: clock
I’ll try MIDI as source next, but I’m expecting the same jitter.

can you try removing redraw() from your step code as well and share the results?