I am trying to run a callback at a specified Clock Time (in milliseconds). Presently such a callback does not exist, correct? Ideally I could call ClockTime() to get the current time, then add some number of milliseconds to schedule a callback in the future.
If it does not exist, what is the best workaround?
Is there a way to use On SystemEvent?
Perhaps I could schedule a MIDI event and catch that
There is also the possibility of setting up a generator and using On TimePassing
On BeatChanged will not give me the granularity that I need for this project
Looking for the most efficient (least CPU) way to pull that off. Anyone have a recommendation?
Implementing my own tap tempo that looks at the timestamp of the last 8 taps and predicts the time and tempo for the next beat. Existing implementations of tap immediately alter BPM after a couple of taps and resulting BPM swings too much.
Not sure polling every ms is needed if one could set an interrupt to fire at a certain time. Also, there may be already that granularity of timing being done inside GP, in which case this would be an easier addition.
Pulling off events at a certain time are probably not that unusual.
I think you could implement that with a script.
Please wait, I will write a proof of concept
Here it is: One Button does an initialization, the other button is for calculating the bpm.
Quick & Dirty, but it should work.
var BUTTON : Widget
START : Widget
Time : Integer
index : Integer
NW1 : Integer
NW2 : Integer
bpm : double
on WidgetValueChanged (newValue : double) from BUTTON
index = index + 1
if index == 1 then
NW1 = TimeNow()
elsif index == 4 then
NW2 = TimeNow()
bpm = (600000.0 / (NW2-NW1)/4)
SetBPM(bpm)
end
end
on WidgetValueChanged (newValue : double) from START
index = 0
end
var BUTTON : Widget
index : Integer
NW1 : Integer
NW2 : Integer
bpm : double
on WidgetValueChanged (newValue : double) from BUTTON
index = index + 1
if index == 1 then
NW1 = TimeNow()
elsif index == 4 then
NW2 = TimeNow()
bpm = (600000.0 / (NW2-NW1)/4)
SetBPM(bpm)
index = 0
end
end
on Activate
index = 0
end
Thank you for your help and you have correctly found the BPM averaged over 4 taps. I am working on the next step which is to align the playhead to my taps.
Using a regression on the tap times I can easily calculate the time for the next beat. My thought was to set a callback for the desired time, then do an EnablePlayhead(). I was asking the group about the best way to pull that off.
Seems that setting up a generator may be overkill so I think I’ll schedule a MIDI event to happen at a specific time and get a callback on that. Happy to know if someone has a better idea!
Sure, that is if you give GP a tempo and meter it will count the beats for you. If you change tempo GP will count at the new BPM but it won’t necessarily be aligned to your taps. By doing an EnablePlayhead() at the right time you can have its downbeat match yours.
Yes, I want to use arps and pulsing / rhythmic patches with a live band, which currently does not play to a click track. If the tempo is off by even a small bit my arps don’t sound tight. I’ve tried many different tap tempo implementations and most start trying to extrapolate tempo after two taps – this leads to wide swings in BPM – and they often don’t align to the beat in my DAW. It ends up sounding sloppy and so I have avoided rhythmic stuff altogether.
My tap tempo function will not react until I have tapped eight times, and it will do a best fit to the taps to line things up. Not sure if it is going to be reliable enough but I want to try it.
This reminds me to “Beat Seeker” which is a M4L patch for Ableton Live.
With this you can route for example the signal of the kick drum into Ableton Live.
This way Ableton Live follows the tempo of the drummer!
When the drummer is not a total beginner this works really well.
In my experience it is best you play with a click track
Hmm, I’m surprised you found this happening with GP’s Tap Tempo function. I’ve used it very successfully to sync up to drummers in one of my bands.
Scripted tap BPM progression (stdev=0.18):
116.0, 116.2, 116.3, 116.5, 116.3
Part of the benefit here is that I don’t update until I get 8 taps, and then I do a linear regression on the tap times. Calculation is simplified since I know the X values are 0…7. Slope is the number of milliseconds per beat. Slope and intercept together tell the precise time when the next downbeat will occur.
Yes. If I keep tapping I could adjust every two measures (4/4 time). That’s how it works now.
I may update it to use the last 8 taps so it is more of a moving average. One reason not to do that is I don’t want to reset the playhead every beat because that causes my arps to reset. I am using BlueARP and you can set up to 64 steps. The way it works now it gets through two measures of the arp before jumping back to step 1.
So if I was only updating the BPM then I would use the last 8 taps moving average. If I am also resetting playhead on the next downbeat then I am happy forcing another 8 taps.
I was ready to test it last weekend when my drummer surprised me and showed up with a metronome.
Often it is the case that a better idea hits me long after I hit “Reply” — a better solution is:
don’t change BPM until I have two measures of taps (let’s say 8 taps for this example)
update BPM using the slope of the line fit on the previous 8 taps (rolling basis)
reset the playhead on the next downbeat following each two measures of taps (say, beat 9, 17, 25, etc.)
if approx three beats goes by with no tap reset state machine so the next tap is tap number one
i now do error checking so if calculated BPM would change by more than 1/8 its current value then skip the change, assuming the count got messed up. When playing live I expect gradual changes in tempo not drastic changes. And last thing I want is for my arps to get really out of whack because I accidentally tapped one time too many or I missed one
Here is a script that implements the above. It is a testament to GP Script that such a function can be built and run efficiently.
I noted that the playhead had to be disabled and then enabled in order to get it to start over at beat one. There was one surprise – I found I needed to trigger a callback to fire 420 ms before beat one in order to get it to align correctly.
Thanks for the kind words. . If you have some thoughts on what functionality would have made your script easier to develop, let us know ( no promises of course )