import processing.core.PApplet;
import ddf.minim.AudioBuffer;
import ddf.minim.Minim;
public class BeatDetectPlus
{
private int algorithm;
private int sampleRate;
private int timeSize;
private int valCnt;
private float[] valGraph;
private int sensitivity;
private int insertAt;
private boolean[] fIsOnset;
private FFT spect;
private float[][] feBuffer;
private float[][] fdBuffer;
private long[] fTimer;
private float[] varGraph;
private int varCnt;
private int _logAverage1;
private int _logAverage2;
private int height2;
/* Constructor
____________________________________________ */
public BeatDetectPlus(int timeSize, float sampleRate, int logAverage1, int logAverage2)
{
_logAverage1 = logAverage1;
_logAverage2 = logAverage2;
this.sampleRate = (int) sampleRate;
this.timeSize = timeSize;
initFEResources(logAverage1, logAverage2);
initGraphs();
sensitivity = 10;
textFont(createFont("FFFCompactBold-8.vlw", 8));
textAlign(CENTER);
height2 = height - 45;
}
private void initGraphs()
{
valCnt = varCnt = 0;
valGraph = new float[512];
varGraph = new float[512];
}
private void initFEResources(int logAverage1, int logAverage2)
{
spect = new FFT(timeSize, sampleRate);
spect.logAverages(logAverage1, logAverage2);
int numAvg = spect.avgSize();
fIsOnset = new boolean[numAvg];
feBuffer = new float[numAvg][sampleRate / timeSize];
fdBuffer = new float[numAvg][sampleRate / timeSize];
fTimer = new long[numAvg];
long start = System.currentTimeMillis();
for (int i = 0; i < fTimer.length; i++)
{
fTimer[i] = start;
}
insertAt = 0;
}
/**
* Analyze the samples in ab. This is a cumulative process,
* so you must call this function every frame.
*
* @param ab
* the AudioBuffer to analyze.
*/
public void detect(AudioBuffer ab)
{
detect(ab.toArray());
}
/**
* Analyze the samples in buffer. This is a cumulative
* process, so you must call this function every frame.
*
* @param buffer
* the buffer to analyze
*/
public void detect(float[] buffer)
{
fEnergy(buffer);
}
/**
* In frequency energy mode this returns true when a beat has been detect in
* the ith frequency band. In sound energy mode
* this always returns false.
*
* @param i
* the frequency band to query
* @return true if a beat has been detected in the requested band
*/
public boolean isOnset(int i)
{
return fIsOnset[i];
}
/**
* In frequency energy mode this returns true if at least
* threshold bands of the bands included in the range
* [low, high] have registered a beat. In sound energy mode
* this always returns false.
*
* @param low
* the index of the lower band
* @param high
* the index of the higher band
* @param threshold
* the smallest number of bands in the range
* [low, high] that need to have registered a beat
* for this to return true
* @return true if at least threshold bands of the bands
* included in the range [low, high] have registered a
* beat
*/
public boolean isRange(int low, int high, int threshold)
{
int num = 0;
for (int i = low; i < high + 1; i++)
{
if (isOnset(i))
{
num++;
}
}
return num >= threshold;
}
/**
* Sets the sensitivity of the algorithm. After a beat has been detected, the
* algorithm will wait for s milliseconds before allowing
* another beat to be reported. You can use this to dampen the algorithm if
* it is giving too many false-positives. The default value is 10, which is
* essentially no damping. If you try to set the sensitivity to a negative
* value, an error will be reported and it will be set to 10 instead.
*
* @param s
* the sensitivity in milliseconds
*/
public void setSensitivity(int s)
{
if (s < 0)
{
Minim.error("BeatDetect: sensitivity cannot be less than zero. Defaulting to 10.");
sensitivity = 10;
}
else
{
sensitivity = s;
}
}
/**
* Draws some debugging visuals in the passed PApplet. The visuals drawn when
* in frequency energy mode are a good way to determine what values to use
* with inRange() if the provided drum detecting functions
* aren't what you need or aren't working well.
*
* @param p
* the PApplet to draw in
*/
public void drawGraph()
{
rectMode(CORNERS);
//background(0);
stroke(255);
// draw the logarithmic averages
//spect.forward(jingle.mix);
int w = int(width/spect.avgSize());
for(int i = 0; i < fTimer.length; i++)
{
int xPos = i*w;
// Draw numbers
fill(255);
text(i, xPos + (w/2), height2 + 20);
// check fill for beat
long clock = System.currentTimeMillis();
if (clock - fTimer[i] < sensitivity)
{
noStroke();
float h = PApplet.map(clock - fTimer[i], 0, sensitivity, 255, 0);
fill(h);
ellipse(xPos, height2 + 40, 15, 15);
}
stroke(255);
noFill();
rect(xPos, height2, xPos + w, height2 - spect.getAvg(i));
}
}
private void fEnergy(float[] in)
{
spect.forward(in);
float instant, E, V, C, diff, dAvg, diff2;
for (int i = 0; i < feBuffer.length; i++)
{
instant = spect.getAvg(i);
E = average(feBuffer[i]);
V = variance(feBuffer[i], E);
C = (-0.0025714f * V) + 1.5142857f;
diff = PApplet.max(instant - C * E, 0);
dAvg = specAverage(fdBuffer[i]);
diff2 = PApplet.max(diff - dAvg, 0);
if (System.currentTimeMillis() - fTimer[i] < sensitivity)
{
fIsOnset[i] = false;
}
else if (diff2 > 0)
{
fIsOnset[i] = true;
fTimer[i] = System.currentTimeMillis();
}
else
{
fIsOnset[i] = false;
}
feBuffer[i][insertAt] = instant;
fdBuffer[i][insertAt] = diff;
}
insertAt++;
if (insertAt == feBuffer[0].length)
{
insertAt = 0;
}
}
private void pushVal(float v)
{
if (valCnt == valGraph.length)
{
valCnt = 0;
valGraph = new float[valGraph.length];
}
valGraph[valCnt] = v;
valCnt++;
}
private void pushVar(float v)
{
if (varCnt == varGraph.length)
{
varCnt = 0;
varGraph = new float[varGraph.length];
}
varGraph[varCnt] = v;
varCnt++;
}
private float average(float[] arr)
{
float avg = 0;
for (int i = 0; i < arr.length; i++)
{
avg += arr[i];
}
avg /= arr.length;
return avg;
}
private float specAverage(float[] arr)
{
float avg = 0;
float num = 0;
for (int i = 0; i < arr.length; i++)
{
if (arr[i] > 0)
{
avg += arr[i];
num++;
}
}
if (num > 0)
{
avg /= num;
}
return avg;
}
private float variance(float[] arr, float val)
{
float V = 0;
for (int i = 0; i < arr.length; i++)
{
V += PApplet.pow(arr[i] - val, 2);
}
V /= arr.length;
return V;
}
public FFT getFFT()
{
return spect;
}
}