Metronome with gen~ (bpm, tap tempo)

zolikov's icon

Hi!

After years of struggling with sample-accurate timing, I decided to try gen~. Here is my first working patch and I feel relieved that it is actually possible. This is a simple metronome with tap tempo. I wanted the tap tempo to work in a way that it checks all the taps in a row and divides the time between the first and the last tap by the number of taps. This way the more you tap the closer you get to the wanted bpm. The last tap is the one which is not followed by a new one in 2 seconds (bpm 30). Tap automatically starts the metronome.

I'm very curious about your comments or if there is a more simple way to do this. I'd be also curious if it would make sense to rap it in a [poly~] object to downsample the whole process to save cpu power.

Max Patch
Copy patch and select New From Clipboard in Max.

Graham Wakefield's icon

(written before looking at the patcher:)

Some tap tempos just use the period between the last two taps, which is fine for a quick entry, some do more or less what you describe, which may be better for following along to a beat for a few taps.

Some use something like a phase-locked-loop or a delay-locked loop, which can sound sloppier initially but are usually better for tracking peak detectors for auto-bpm detection -- they require more 'beat' inputs to get themselves aligned, but can become super accurate. https://en.wikipedia.org/wiki/Delay-locked_loop (Fun to look at at least for the pseudo-3D signal flow diagram!) It's a bit like firefly entrainment.

There's really 2 things that are being computed -- the period, and the phase (in turntablism terms, one being the pitch adjust slider, the other being the 'nudge' to get the beats aligned).

For the raw period between taps, a simple [+= 1] (aka [accum 1]) works like a sample-counter, and the tap-impulse (you can feed it from buttons in Max via [click~]) will reset the count. (Or for more flexible tap inputs, a variety of [> 0] > [change] > [> 0], or [change] > [change] > [> 0], or similar.)

(written after looking at the patcher:)

In your case, the 'phase' is set by the most recent tap. There's a chance that might be off, and an improvement might be to also use the taps to nudge the phase offset a little less with each tap after the first. Hot tip for shifting phase of a phasor (without using the 2nd inlet): [phasor] -> [- offset] -> [wrap 0 1]. `offset` here is derived by sampling the value of the phasor (via latch]) at the moment of the tap; but you probably want to do a running average on that; that is, each tap has a smaller influence on updating the offset value. You could do this instantly at the moment of the latch, or you could smooth it in time, turning it into more of a turntable nudge, which might be a nice feature to expose to the outer world.

Your solution to reset the tap detection after half a second of inactivity is smart.

Tip: in your patch, you don't need to route the bpm output back to the input -- you can use [history bpm]. A named history object works like a param, but you can update the value inside the gen patch.

Regarding the q of CPU usage -- don't worry. The gen code here is pretty tiny, and might have less overhead than the UI boxes in your Max patch.

zolikov's icon

Thanks so much for all the comments.
I looked into PLL and DLL. It looks very interesting. At some point, it would be good to dive into details and apply them into a gen~ patcher one way or another.

For now, I can just go with the sync~ object to handle the tap tempo part. This way, this patch got to be quite simple.
I'm planning to generate some running integer and float as a base for a simple step sequencer and a sequencer which can provide MIDI CC messages, or/and CV data. Long way to go:)

Max Patch
Copy patch and select New From Clipboard in Max.