Norns Beatclock - how to get 32 beats (also calculating bpm)?

I don’t know what all those words mean, but will read more now :laughing:

1 Like

Not intending to be salty, but: first quote is me 18h ago in this very thread. I agree the API is dumb; we just slapped grttimeofday() in there, fast.

I will PR a convenience method to get secs directly :dog2:
And improve doc while at it :poodle:
Unless someone beats me to it :fox_face:

Apologies… wasn’t meaning to complain, but rather that I was confused by the docs (and spaced out your previous comment). :dolphin:

I’m still kinda fuzzy on if the incoming clock is just jittery or if the time() calculation is not accurate enough (or timing in that loop is otherwise affected by system things)

EDIT - also I’m still trying to wrap my head around the clock co-routines stuff, which might address this somewhere?

Whoa… hold the phone. Is this what we need?: clock.get_tempo

actually it is my turn to apologize because both my memory failed me and the comments matched my failed memory. (its possible that someone made a change without updating the comment.)

so anyways, util.time() does at least attempt to compute seconds, (sorry, again!) so i will look at jitter there… i’d be a little surprised, but who knows… writing some tests now and will report back.

(one thing that comes to mind is that MIDI clocks do have their own jitter, and you can’t always get a super clean BPM estimate without some form of filtering.)

and yes, for the specific use case of following link and MIDI, the new clock update has a simple tempo calc built into the C layer, which should bypass any timing problems from the event loop. (this is an awesome update from @artfwo, it is super fresh :sunflower: so anyone who can use these features and doesn’t mind testing, may want to give it a spin)

(to be clear, my particular goal in this game is making sure that time() gives usable results for musical gestures in general.)


reporting:
using norns util.time() to measure a metro (which i know to be pretty accurate), at a typical kind of BPM (127bpm with 24ppq ticks), i get results that i would call quite good, and def. much better than is needed for BMP estimation. (in a nutshell, a sliding mean filter would give you estimation of dT accurate within 1 microsecond.)

using this script, bumping num samples up to 2048:

  • absolute range of measured dT: just under 2 milliseconds.
  • standard deviation: about 63 microseconds.

here’s the histogram:

and raw data if you want it
https://pastebin.com/U1vMMu4p


(oh, NB and [ed]: since original post i’ve updated the script to show error instead of raw dT measurements, which is easier to interpret)

there is also nothing else going on to interfere with event handling in this test. (which might not be the case when following midi clock.)

anyways i’d expect util.time() to be accurate even if the event loop is bogged.

TL/DR: i would look for other sources of jitter (like the event loop, esp. if there is a lot of graphics stuff going on; some changes are in the pipeline to address that particular timing bottleneck.)

3 Likes

after all this, and reading the original post, it seems like among the many lua utilities that would be useful, would be a little windowed averager.

4 Likes

the clock module doesn’t solve the incoming midi jitter problem, i’ve been also thinking of buffering and averaging the intervals

2 Likes

Really glad this conversation is happening, I’m just about to take on clock-in to crow for ekombi. Maybe we should revise Beatclock to be flexible to midi/crow as well as adapting to different ppq counts, it seems like it could be pretty useful in most of the scripts around here.

1 Like

(apologies if you’ve seen this)
lots of convo about global clock revision here: https://github.com/monome/norns/issues/129

until then, I’ve been rolling my own crow-infused version. i can’t tell if it’s useful for others, but it’s been helpful to quickly retrofit crow integration. this is the version I used most recently for the awake update: https://github.com/tehn/awake/blob/master/lib/beatclock-crow.lua

plumbing required:

  • placing if params:get("crow_clock_out") == 2 then crow.output[4]:execute() end into current clock callback for external clock from crow’s 4th output
  • setting up a little crow_init() to define what you want to do with pulses going into crow
  • defining functions for clk.on_select_internal, clk.on_select_midi, and clk.on_select_crow
1 Like

Are the filters added to master today those ultils? (just pulling that now to test new things)

If so - Any quickie examples I could try?

Yes, the first thing id try for bpm estimation would be just throwing a windowed median filter on the delta-times

(And compare with windowed mean)

1 Like

mean seems to give a better result in this case using my TR-09:

Test Script (requires norns master branch from 3-26-2020)
local filters = require 'filters'

m = midi.connect()
local ticks_per_step = 6
local steps_per_beat = 4
local t = 0 -- last tap time
local dt = 1 -- last tapped delta
local z = 0
local bpm = 100

function init()
  currenttime = util.time()
  previoustime = util.time()
  bpmwindow = filters.mean.new(25)
end

m.event = function(data)
  local d = midi.to_msg(data)
  if d.type == "clock" then
    tap()
    -- only update the screen once per 24 clocks
    z=z+1
    if z > 24 then
      redraw()
      z = 0
    end
  end
end

function tap()
   local t1 = util.time()
   dt = t1 - t
   t = t1
   rawbpm = (60 / dt / (ticks_per_step * steps_per_beat))
   bpm = util.round(bpmwindow:next(rawbpm),0.1) 
   --print(bpm)
end

-- screen redraw function
function redraw()
  screen.clear()
  screen.level(15)
  screen.line_width(1)
  screen.font_face(0)
  screen.font_size(8)

  screen.move(0,10)
  screen.text("MIDI CLOCK TEST")
  
  screen.move(0,20)
  screen.text("BPM: ".. bpm)

  screen.update()
end

values wiggle between ~.4 bpm or so

That’s interesting… Tr-09 is USB midi?

It would be useful to compare measurement in lua with output of clock.get_tempo, which is computed before passing through the event loop. To see if the jitter is “below” our whole stack or if event loop latency is contributing significantly. ( I guess the former.)

To put that in context… back of envelope: difference of 0.4bpm would be a dt variance of… A little over 200 microseconds for a 24ppq tick? It’s probably about as good as one can expect. If you want to really damp fluctuation in the output you can always chain it with a 1-pole smoother, also in the filters module.

1 Like

Yes it is USB MIDI.

Eyeballing the numbers side-by-side the jitter/variance seems to be the same between the two with the same mean filter.

Curious though - I need to multiple clock.get_tempo() by 4 to get the right bpm. Not sure I understand if that’s a bug or by design.

Gonna try a smoother here real quick maybe not just yet. not sure how to do that yet and I need to sleep :slight_smile:

did you set clock source to MIDI in your script?

more smoothing would be like

bpmwindow = filters.mean.new(25)
 -- initialize the smoother with (approximate) tick rate and convergence time
-- the actual samplerate will be the BPM-dependent tick rate,
-- so convergence time will vary with tempo... whatever
smoother = filters.smoother(24, 2) --- 2 seconds for 60bpm, 24ppq

  ...
bpm = bpmwindow:next(rawbpm)
bpm = smoother:next(bpm)
bpm = util.round(bpm, 0.1) 

i do think the jitter here really is in the incoming clock. <1ms timing accuracy for each tick is probably considered sufficient in that context.

1 Like

Yes I did. I will re-test again later, but I think I got the same result without it being explicitly set. Perhaps I’m doing something incorrectly.

okay, thanks for heads-up. i am going to be working on a demo app for clock this weekend and can look into midi tempo as well.

2 Likes

I’m going to be doing some hacking for a bit and would be happy to test anything if you had any forward motion on clock demos. :slight_smile:

A post was split to a new topic: Norns: link (beta)