Clock generators bpm accuracy

I started writing a clock generator module with audio metronome clicks, so I ran into the bpm accuracy problem.
I have tested the accuracy of some clock generators in the library and found very different results especially when using non-divisible tempos like 121bpm, for obvious reasons.
For the test I wrote another module with an input trigger that displays the bpm calculated by counting the samples between one clock and another.
Can this be a reliable test or are there other methods?

Do you have tried KlokSpid, from Ohmer Modules? (I’m the developer). Clocking is very accurate (any BPM). Free (& open source) plugin. During 2017 (VCV Rack v0.5) I’ve developed it initially for personal use, because the most used during this epoch, “AS BPM Clock” module, wasn’t reliable (it had serious drift, even after 1 minute run!).

GitHub (source): https://github.com/DomiKamu/Ohmer

However, I’m working on new more powerful version (8 outputs, 8 inputs/CVs, many waveforms, touchscreen-like menus, and so on), but remaining in development…

Cheers.

Dominique.

3 Likes

Yes, I remember I tried clockspid too. I will send my test results with it

1 Like

Of course the fastest way to establish a BPM (slave module) is to calculate the DSP frame difference (two consecutive pulses, in general on their rising edges), but this may not 100% reliable (due to sample rate aliasing / resolution). Of course, precision (number of DSP frames) will be accurate at higher sample rates. Keep in mind we’re living in digital world. :wink: The best way to do a reliable measurement is to count the number of ticks for 60 seconds (or longer). By running @ BPM 121 during exactly 10 minutes (600s) will result exactly 1210 ticks no more no less, even at 44.1kHz. If true, it’s meaning the source clock is reliable.

It’s not an obvious reason: odd BPM and (always) even sample rate.

Do no forget any cable always adds 1 DSP frame latency (cumulative).

KlokSpid… Okay I’m waiting for your test results. :ok_hand: Also I’m curious about your metering module (source code)…

1 Like

clock generators (and back in the 80’s midi sequencer clocks) typically need to alternate between two periods if they are operating on a fixed timing interval (sample rate in this case, a fixed 1ms clock in the case of my ancient DOS sequencer.)

The clocks generator then accumulates any error from a single period and adjusts when it can. Typically with a phase accumulator, just like the way almost every digital VCO uses a phase accumulator to hit the pitches “in between” the even ones.

To measure the actual rate of something like this you need to just keep measuring over time until the result is accurate enough. As you say, a minute is a good guestimate.

Of course not all clock generators are going to work this way. The creator of the clock may not have cared about hitting these “in between” tempos, or they may not have known about the issue at all.

1 Like

I’m curious about the obvious reason :thinking:

I never thought one second about drifting clock generators. Musically, it doesn’t matter.

3 Likes

I think you mean “for the stuff that I do, I don’t care if it drifts a little, but if it drifted a lot (like 110 bpm to 115 bpm) then I would care.” - yes?

But, for sure, it’s mostly less common use cases where this is critical. Off the top of my head:

  • It better not audibly change.
  • If you are using a clock to put “beats” against a “tape loop”, then it will drift out of sync and sound bad if the clock isn’t stable.
  • If you are making a long piece of music for a film score you might want some particular musical event to happen very close to an event in the movie. If you tempo is not precise this won’t happen.

That last one is why I insisted that my DOS sequencer be accurate to one millisecond after two hours at all tempos.

3 Likes

My 2 cents here:

In case of syncing to other sources (audio or video), one should act as the master clock, and everything is in sync.

I thought about tempo drifts (or a tempo error) of about 0.01 bpm.

All the clock generator modules I know do not produce drifts in a range of up to 5 bpm.

1 Like

@Ohmer This is my test response on klokspid (sorry, klokspid! :slight_smile: )

the first three lines are calculated on the last clock received and the following ones are callculated on the timings shown.

@Ahornberg , in effect it is not obvious, but I was referring to the fact that you need to do something to get a stable clock with minimum error as possible.

Here it is the source code of my test module, I Hope I did nothing wrong…

void process(const ProcessArgs &args) override {
		
		trigValue = inputs[TRIG_INPUT].getVoltage();
		
		if (trigValue >= 1 && prevTrigValue < 1){
			
			if (start10sCount) {
				beat10sCount++;
				accum10s += sampleCountClock;

			} else {
				start10sCount = true;
				beat10sCount = 0.0;
				sampleCount10s = 0.0;
				accum10s = 0.0;
			}

			if (start30sCount) {
				beat30sCount++;
				accum30s += sampleCountClock;
			} else {
				start30sCount = true;
				sampleCount30s = 0.0;
				accum30s = 0.0;
			}

			if (start1mCount) {
				beat1mCount++;
				accum1m += sampleCountClock;
			} else {
				start1mCount = true;
				beat1mCount = 0.0;
				sampleCount1m = 0.0;
				accum1m = 0.0;
			}

			if (startClockCount) {
				beatGeneralCount++;
				estimBpm = currentSampleRate * 60 / sampleCountClock;
				accumGeneral += sampleCountClock;
				averSamplesPerClock = accumGeneral / beatGeneralCount;

				debugDisplay[CLOCK_BPM] = "Last clock samples: " + to_string(sampleCountClock) + "\n" +
											"Estim clock Bpm: " + to_string(estimBpm) + "\n" +
											"Ave Samples per clock: " + to_string(averSamplesPerClock);

				sampleCountClock = 0;

			} else {
				startClockCount = true;
				start10sCount = true;
				start30sCount = true;
				start1mCount = true;

				sampleCountClock = 0;
				sampleCount10s = 0;
				sampleCount30s = 0;
				sampleCount1m = 0;
				sampleCount10m = 0;
				sampleCountGeneral = 0;
			}

		} 

		prevTrigValue = trigValue;

		if (sampleCount10s >= nrSamples10sec) {
			partialTenSecondsBpm = sampleCountClock / (accum10s / beat10sCount);
			debugDisplay[TEN_SECONDS_BPM] = "BPM 10s " + to_string( 6 * (beat10sCount + partialTenSecondsBpm));
			sampleCount10s = 0.0;
			accum10s = 0.0;
			beat10sCount = 0;
			start10sCount = false;
		}

		if (sampleCount30s >= nrSamples30sec) {
			partialThirtySecondsBpm = sampleCountClock / (accum30s / beat30sCount);
			debugDisplay[THIRTY_SECONDS_BPM] = "BPM 30s " + to_string( 2 * (beat30sCount + partialThirtySecondsBpm));
			sampleCount30s = 0.0;
			accum30s = 0.0;
			beat30sCount = 0;
			start30sCount = false;
		}

		if (sampleCount1m >= nrSamples60sec) {
			partialOneMinuteBpm = sampleCountClock / (accum1m / beat1mCount);
			debugDisplay[ONE_MINUTE_BPM] = "BPM 1m " + to_string(beat1mCount + partialOneMinuteBpm);
			sampleCount1m = 0.0;
			accum1m = 0.0;
			beat1mCount = 0;
			start1mCount = false;
		}

		sampleCountClock++;
		sampleCount10s++;
		sampleCount30s++;
		sampleCount1m++;
		sampleCount10m++;
		sampleCountGeneral++;

	}

2 Likes

What particular effect do you mean or may you post an example patch so others could hear the audible problem you want to correct?

I’m thinking about real problems to solve, not about academic mathematic games.

A feedback-delay with a high feedback setting is the only effect I can think of where the feedback loop can run out of sync. But here there are delay-modules with a clock/sync input to avoid this problem.

All timing issues can be solved by having one (and only one) singular master clock that all other clock-relevant modules have to follow. And then it is irrelevant if you are playing at exact 121 bpm or at 121.0001 bpm.

not that I’m adamant about this or anything…

I agree about the master clock and that the only real problem is sync. I don’t have any audible problems to fix.
At the beginning I simply asked how do I calculate the bpm, meaning I only wanted to solve a verification problem.

Beware the rabbit-hole :sunglasses:

1 Like

Perhaps you’ll can try the same test, but @ 48kHz or 96kHz (Engine → Sample rate + same setting on AUDIO-x module), and compare the results. It’s an idea, aren’t?

Here they are, I noticed the number of samples are fixed between clocks. The results are surely good, but I was thinking to make my module to have variable length clocks, with a sort of phase accumulator.
With my prototype o module I’m having exact expeted bpms in one minute, maybe because I’m using doubles, but I will consider using floats to improve cpu efficiency… to be tested too :wink:

For reference. In the not so distant past, I wrote VCV about the precision of VCV LFO.

PS. My “need” was purely artificial.

2 Likes

Ok, in this case it’s not a good idea to use floats, thanks

1 Like

Yep, I confirm float usage for precise timings is a bad idea! use double instead!

However, float is perfect for voltages.

If you’ll take a look on KlokSpid souce code, you’ll can notice it uses discrete integer frame counter (incremented on every void process) it’s impossible to be more precise.

1 Like

So excited to try it. is it necessary to use CV out from the interface or will usb or midi be ok to?