Tap for Tempo Button

Is this possible with OSC and possibly could be implemented? I’ve noticed on Junkie XL’s template or Lorne Balfes, or Hans Zimmer they all have this amazing little button that lets you tap for tempo and it’ll update and show you the tempo you tapped. After a certain while of non-tapping, it’ll reset back to the default number. Here is a pic below:


I’m learning to code and find my way around OSC alot over the past couple months but this seems a little more advanced than I’m capable of even starting to code; or, if it’s even possible yet in OSC? Or maybe it’s already in OSC and I just don’t see it yet, haha!


Here is an implementation example with a custom module that does the maths and sends the bpm value back to the interface. You should be able to adapt it to your needs.

Here is the module’s code (inspired by this pen: https://codepen.io/dxinteractive/pen/bpaMMy)

const MIN_BPM = 40

var tapDurations, tapIndex,
    lastTapTime = 0,
    maxTapLength = 1000 * 60 / MIN_BPM

function reset(){

    tapDurations = [0, 0, 0, 0, 0]
    tapIndex = -1


function receiveTap(data){

    var tapTime = Date.now()

    // if last tap is too far (based on MIN_BPM), reset stored value and wait until next tap
    if (tapTime - lastTapTime > maxTapLength) {
        lastTapTime = tapTime

    // store tap duration
    tapDurations[tapIndex % tapDurations.length] = tapTime - lastTapTime
    lastTapTime = tapTime

    // update bpm


function updateBpm(data){

    // filter tap durations (exclude zeros)
    var durations = tapDurations.filter(x=>x>0)

    // we need at least one value
    if (durations.length < 1) return

    // compute average tap duration
    var sum = durations.reduce((previous, current) => current += previous),
        avg = sum / durations.length,
        bpm = Math.round(60 / (avg / 1000))

    // send bpm back to the interface
    receive('/bpm', bpm)

    // send bpm to the button's original target/address ?
    send(data.host, data.port, data.address, bpm)

module.exports = {

    oscOutFilter: function(data) {

        var {host, port, address, args} = data

        if (address === '/bpm/tap' && args[0].value === 1) {
            // update bpm when the interface sends /bpm/tap 1

            // bypass original osc message

        // process other messages normally
        return data


1 Like

AMAZING! Thank you so much @jean-emmanuel, this is exactly! What I was looking for.

You’re the best!

Two questions @jean-emmanuel

  1. I’m trying to combine two custom modules into one master module reading from the documentation. I gave variables two separate numbers and called them back but its not working. Am I misunderstanding?

var 1 = require(’./OSC Midi Sequences.js’)
var 2 = require(’./tap_tempo_custom.js’)

module.exports = {
init: function(){
console.log(1) // 1
console.log(2) // 2
oscInFilter: function(data){
// etc

  1. I was reading over your script and testing it. It functions, and double-checking the math is right but it’s not outputting the correct BPM when tapped. I’ll tap roughly 120bpm and it’ll come out in the 200’s random numbers.

You can’t use require in custom modules: https://openstagecontrol.ammd.net/docs/custom-module/#managing-big-modules

Also, variable names can’t begin with a number.

I’ll tap roughly 120bpm and it’ll come out in the 200’s random numbers.

I tested it again, it works fine here, is your button any different from the one in the example session ?

Oh, once again I misread something. The main.js of OSC not the custom module. I understand now.

Here is a gif showing me tapping. This is the exact template and files you supplied. I just updated to v4.8 too.

I’m clicking on the OSC editor itself, but its the same if I just tap from the server side.

Ok I get it: open stage control has the send option set, thus making the button send two messages per hit. You can solve this by either:

  • ensuring the button sends only 1 message (clearing its target property, but then you’d require the send option to contain only one target)
  • adding a condition in the custom module to match the button’s port only:
if (address === '/bpm/tap' && port === 5555 && args[0].value === 1)
  • adding a condition block in the receiveTap function to ignore taps that are too short:
// after "var tapTime = Date.now()"
if (tapTime - lastTapTime < 100) {
  // 100ms == 600bpm -> ignore
1 Like

That fixed it and worked perfectly thank you!

Incase anyone is interested. I used CSS to place the Input widget behind the TAP button. Removed the TAP label and set its css properties to the following to back it transparent.

/*Tap Button */
:host {
background: transparent;
border: 5px solid transparent;

/* Input Widget */


1 Like

It’s also possible to do that without a custom module, using a script widget : tap.json (3.2 KB). It works the same way, although this method is less convenient to montain and update.


@DMDComposer are you using cubase? Would you mind sharing how you incorporated this tap button code into cubase? Generic remote etc? Cheers


I didn’t incorporate it into Cubase. I don’t think that’s possible at the moment or I couldn’t figure it out in the end. I just use it as a simple tap the tempo feature and then manually enter the tempo which still works great for my needs.


Aaaah, righto. For some reason I thought you could send the Tempo data to cubase.

Hey, any chance of taking a look at your osc template? Interested to see how you’re using it.

Cubase Ipad 01.json (1.8 MB)

1 Like

That is a thing of beauty my friend. Thanks for sharing.


How do you control the emptying of the trash?

Thanks, I can only take a little credit since its just my own rendition of the lovely provided template made by JoeHidden. I just revamped it for my quick needs and making use of my AHK scripts.

Autohotkey scripts.

I have a master script that awaits for a 3 digit code which then executes a AHK script.

Thus, the empty trash key sends key “600” which on autohotkey reads the following script

else if(command = 600){ ;~ Empty Recycle Bin
	Command_Gui({Title:"Empty Recycle Bin",Color:"#0489D8",Icon:"Recyclebin",Background:"White"})

most of my script won’t work so don’t copy and paste it, the command you’ll need is


1 Like