I'm creating an app that tests how fast in beats a user can achieve with taps, mouse clicks / button pushes etc.
Each time the user hits the button, I call this function, beat(); to calculate their current BPM and fill a graphic with that BPM. I also want to calculate an average at the end. See the code I am currently using below.
The issue I am currently having is that as the user clicks, the BPM is all over the place, sometimes its consistent, sometimes it is inaccurate. Reviewing my code has me confused, as I don't think I'm actually calculating BPM here, or at least, not accurately.
I also tried adding in a lerp between the old and new BPM values, to smooth the gauge, but it doesnt seem to fix the problem.
The closest thing I have found on stack overflow is this question : Tap BPM code in Processing
But I still am not sure if that is actually calculating the users BPM accurately. I would greatly appreciate some assistance here, specifically an explanation on the math behind how to calculate BPM with only 2 timestamps, an old and a new beat.
void beat()
{
if (beat0 == null)
{
beat0 = DateTime.Now;
return;
}
else if (beat1 == null)
{
beat1 = DateTime.Now;
}
else
{
beat0 = beat1;
beat1 = DateTime.Now;
}
if (beat0 != null && beat1 != null)
{
double delta = (beat1 - beat0).TotalSeconds;
double bpm = 60 / delta;
curBpm = (int)bpm;
if (oldBpm == -1)
{
oldBpm = curBpm;
return;
}
lerpBpm = (int)Mathf.Lerp(oldBpm, curBpm, (float)delta);
bpmText.text = lerpBpm.ToString();
FillGauge(lerpBpm);
oldBpm = lerpBpm;
}
}
I would have something like this..
An array for eg 5 samples, and a counter to know which array element is "current"
TimeSpan[] samples = new TimeSpan[5];
int index = 0;
I'd have a stopwatch, running. It's a high resolution timing class for .net
When the user clicks a button I would take the stopwatch reading and store it in the array:
var current = stopwatch.Elapsed; //you'll see why I store in a temp variable in a moment
samples[index] = current;
And bump the index on, using a modulo to keep it within the array bounds (circular array)
index = (index +1)%samples.Length;
Then I'd need to look at the min and the max in the array. You could use LINQ min/max, or could just use the fact that the index is now pointing to the oldest item in the array..
int oldest = samples[index];
Now, if you do current - oldest
you'll get the time it's taken to acquire 5 samples, divided by 5 gives the average time per sample over the last 5 samples
var ms = (current - oldest).TotalMilliseconds;
And if that's the time from one beat to the next you want to know how many beats you can fit into a time period 60000 ms long
var bpm = 60000 / ms;
Now all that remains I suppose is to put a bit of a check on if the earliest reading is TimeSpan.Zero (indicating it's never been set) so that you don't start calculating crazy bpm in the first five taps