Tap Tempo on a TouchOSC Surface

A more detailed look at some of the custom scripts and surface elements on my TouchOSC surface for Percy Jackson. For more on mixing this show, see the post I wrote about the experience.


TouchOSC has been such an invaluable resource for mixing this show through the Soundcraft console. Honestly, the ability to cast my DAW (Reaper) interface to a touchscreen surface running on an iPad has made the act of mixing much more intuitive and dynamic. Once I got the system playing nicely with itself, the entire experience opened up back into levels that I feel comfortable in1 and I could finally inject the artistic polish that I knew I wanted. You know: pushing a soloist out from the sea of “untrained children singing too loudly” in large ensemble numbers, throwing some thicc reverb on vocalists in a cinematic song. But— One effect still eluded me: Delay.

Delay

The problem with getting delay to work can be summed up simply: I don’t want to fiddle with knobs. Whether that means fiddling with the parameter knobs on the physical Soundcraft2, or fiddling with the per-channel sends3, or adjusting the sends in the DAW with a mouse cursor while I’ve got both hands already busy. Not to mention: the Soundcraft only has faders on the FX return, and I don’t want to mix the return4, because that means killing that nice reverb ringing out.

All this being said, that also means I don’t want to have to:

  1. Open the FX plugin on the channel
  2. Find the right setting to tweak
  3. Tweak the setting with the mouse

On reverb, this isn’t so bad. Personally, all I ever really tweak there is the decay time and that’s a-ok to just eyeball until it feels or sounds right. But delay is a whole other ball game for me. If the tempo doesn’t match just right, the whole effect just sounds “off” and ruins any benefit. While I may have good rhythm, I don’t have the ability to just guess the tempo based on what I feel and here, so enter:

Tap Tempo

Tap tempo is the solution to the problem, and it’s a solution that I have available on pretty much every other digital board that I’ve mixed on. The concept is simple: tap a button in time with a song, and it sets the tempo to the correct BPM5 automatically. Chain this into the delay effect parameter, and all of a sudden you don’t have to turn a knob and guess based on muscle memory.

Making it work in Reaper

This was fairly straightforward, so I’m just going to gloss over it a bit. Reaper comes stock with a Tap-Tempo button: It’s right above the tempo selection area of the transport control. You can also bind this to a keypress using the “Custom Actions” interface, but what if I didn’t want to take my hands off the board or iPad? Reaper’s OSC implementation defines a message to set tempo, by sending a float value to the address /tempo/raw. Now, all I need to do is find a way to set that value in TouchOSC.

TouchOSC

I created a custom control grouping in TouchOSC. Overall, it’s a pretty simple layout:

  • A button to tap
  • A label that says “TAP” over the button
  • A label that shows the current tempo

In an ideal scenario, I could make the button blink at the selected tempo6, but I haven’t been able to figure out how to make TouchOSC buttons blink without registering a “touch” event, which I want to avoid to prevent a feedback loop in my tempo commands. Alternatively, it would work to make an indicator lamp control that blinks in tempo, but I’m also worried about that creating a UI update loop that could override the priority of my control messages and introduce stutter/lag in communications that could cause issues. This is why I settled for the tempo label. As long as I’m confident that my tapping is in time with the song, I can believe my tempo readout is accurate

FX and VCA control area

Also of note in the above screenshot is: The top two faders are the Send channel faders for Delayleft and Reverbright along with the corresponding VU meters for Send and Return. This lets me monitor the FX levels without having to keep those channels pinned in the DAW. Below the two FX faders are two faders for the Leadsleft and Ensembleright VCAs. Finally, there is the Decay Time rotary control for the Reverb effect, and the Tap-Tempo button/display.

Tap Tempo Script

As mentioned above, the message for sending/receiving Tempo is pretty straightforward. Both controls, the button and the feedback label, are addressing the /tempo/raw parameter, which is a float value representing the tempo in BPM.

For tapping the tempo, I had to write a custom script on the button control. Thankfully, TouchOSC allows for custom scripting in Lua:

-- Some constants and initializations
local TOTAL_TAP_VALUES = 5
local MS_UNTIL_CHAIN_RESET = 2000
local SKIPPED_TAP_THRESHOLD_LOW = 1.75
local SKIPPED_TAP_THRESHOLD_HIGH = 2.75

-- A table/array to hold the last 5 tap times for averaging
local tapDurations = {0, 0, 0, 0, 0}
local tapDurationIndex = 0
local tapsInChain = 0

local sinceResetMS = 0;
local sinceResetMSOld = 0;
local beatMS = 500;
local lastTapMS = 0;
local lastTapSkipped = false;
local beatProgress = 0;

-- Get average duration between taps
function getAverageTapDuration()
  local amount = tapsInChain - 1
  if amount > TOTAL_TAP_VALUES then
    amount = TOTAL_TAP_VALUES
  end

  local runningTotal = 0
  for i = 0,amount,1
  do
    runningTotal = runningTotal + tapDurations[i]
  end
  return math.floor(runningTotal/amount)
end

-- Store and calculate the time between taps
function tap(ms)
  tapsInChain = tapsInChain + 1
  if tapsInChain == 1 then
    lastTapMS = ms
    return -1
  end

  local duration = ms - lastTapMS

  if(tapsInChain > 1 and not lastTapSkipped and duration > (beatMS * SKIPPED_TAP_THRESHOLD_LOW) and duration < (beatMS * SKIPPED_TAP_THRESHOLD_HIGH)) then
    duration = math.floor(duration * 0.5)
    lastTapSkipped = true
  else
    lastTapSkipped = false
  end

  tapDurations[tapDurationIndex] = duration
  tapDurationIndex = tapDurationIndex + 1
  if tapDurationIndex == TOTAL_TAP_VALUES then
    tapDurationIndex = 0
  end

  local newBeatMS = getAverageTapDuration()
  lastTapMS = ms
  return newBeatMS
end

-- Resets the tap chain on the first new tap
function resetTapChain(ms)
  tapsInChain = 0
  tapDurationIndex = 0
  resetMS = ms
  for i = 0,TOTAL_TAP_VALUES,1
  do
    tapDurations[i] = 0
  end
end

-- onValueChanged: A built in function that fires on each tap
function onValueChanged()
  -- Checks to see if this is "not touch": ie. release
  if(not self.values.touch) then
    local ms = getMillis()

    if lastTapMS + MS_UNTIL_CHAIN_RESET < ms then
      resetTapChain(ms)
    end

    local newBeatMS = tap(ms)
    if newBeatMS ~= -1 then
      beatMS = newBeatMS
    end

    sinceResetMS = ms - resetMS
    sinceResetMSOld = sinceResetMS

    local bpm = 60000/beatMS
    sendOSC("/tempo/raw", bpm)
  end
end

This script, adapted from a javascript implementation (later adapted to an Arduino script) measures the time between button presses7 and logs them to an array. With each tap, it averages all of the recorded times (in milliseconds, as provided by the TouchOSC getMillis() function) to normalize any stray taps and to account for minor differences in tap speed.

It then computes the BPM by converting the average milliseconds per beat into beats per minute before invoking the TouchOSC sendOSC command to send the resulting tempo to the /tempo/raw address. When Reaper receives this message, it replies with the new tempo, which is then picked up by the label listening for messages to that address in order to display the tempo as visual feedback.

Does it work?

In testing, it does seem to work. But that was testing with the multitrack playing back through Reaper and not in a real-time scenario. I guess I’ll just have to find out on Friday when I go back to the theater.


  1. Coming from my background experience in digital consoles↩︎

  2. Which does include two FX processors↩︎

  3. I’m sure someone more experienced in analogue mixing wouldn’t have as rough a time finding the correct send to tweak, but since I like to throw the send: scanning across 16 channels to find the right one takes too long to be effective.↩︎

  4. Though, I guess: now that I’ve removed most grouping from the physical board, I could probably tap off there and use that as a send…↩︎

  5. Beats per Minute↩︎

  6. A good bit of visual feedback common on other control surfaces↩︎

  7. Technically, button releases↩︎