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:

image

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!

Thanks!

Hi,
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
        reset()
        return
    }

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

    // update bpm
    updateBpm(data)

}

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
            receiveTap(data)

            // bypass original osc message
            return
        }

        // 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
  return
}
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 */
:host{
z-index:2;
}

image

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.

2 Likes