package be.tarsos.dsp.pitch;
import be.tarsos.dsp.AudioEvent;
import be.tarsos.dsp.AudioProcessor;
import be.tarsos.dsp.pitch.Goertzel.FrequenciesDetectedHandler;
import be.tarsos.dsp.util.Complex;
import be.tarsos.dsp.util.fft.HammingWindow;
import be.tarsos.dsp.util.fft.WindowFunction;
/**
* <a href="http://download.springer.com/static/pdf/14/art%253A10.1186%252F1687-6180-2012-56.pdf?auth66=1409747532_189c92c583694c81b3a0095e2f665c9e&ext=.pdf">Goertzel algorithm generalized to non-integer multiples of fundamental frequency</a>
* Petr Sysel and Pavel Rajmic
*
*
* @author Joren Six
*
*/
public class GeneralizedGoertzel implements AudioProcessor{
/**
* A list of frequencies to detect.
*/
private final double[] frequenciesToDetect;
private final double[] indvec;
/**
* Cached cosine calculations for each frequency to detect.
*/
private final double[] precalculatedCosines;
/**
* Cached wnk calculations for each frequency to detect.
*/
private final double[] precalculatedWnk;
/**
* A calculated power for each frequency to detect. This array is reused for
* performance reasons.
*/
private final double[] calculatedPowers;
private final Complex[] calculatedComplex;
private final FrequenciesDetectedHandler handler;
public GeneralizedGoertzel(final float audioSampleRate, final int bufferSize,
double[] frequencies, FrequenciesDetectedHandler handler){
frequenciesToDetect = frequencies;
indvec = new double[frequenciesToDetect.length];
for (int j = 0; j < frequenciesToDetect.length; j++) {
indvec[j] = frequenciesToDetect[j]/(audioSampleRate/(float)bufferSize);
}
precalculatedCosines = new double[frequencies.length];
precalculatedWnk = new double[frequencies.length];
this.handler = handler;
calculatedPowers = new double[frequencies.length];
calculatedComplex = new Complex[frequencies.length];
for (int i = 0; i < frequenciesToDetect.length; i++) {
precalculatedCosines[i] = 2 * Math.cos(2 * Math.PI
* frequenciesToDetect[i] / audioSampleRate);
precalculatedWnk[i] = Math.exp(-2 * Math.PI
* frequenciesToDetect[i] / audioSampleRate);
}
}
@Override
public boolean process(AudioEvent audioEvent) {
float[] x = audioEvent.getFloatBuffer();
WindowFunction f = new HammingWindow();
f.apply(x);
for (int j = 0; j < frequenciesToDetect.length; j++) {
double pik_term = 2 * Math.PI * indvec[j]/(float) audioEvent.getBufferSize();
double cos_pik_term2 = Math.cos(pik_term) * 2;
Complex cc = new Complex(0,-1*pik_term).exp();
double s0=0;
double s1=0;
double s2=0;
for(int i = 0 ; i < audioEvent.getBufferSize() ; i++ ){
s0 = x[i]+cos_pik_term2*s1-s2;
s2=s1;
s1=s0;
}
s0 = cos_pik_term2 * s1 - s2;
calculatedComplex[j] = cc.times(new Complex(-s1,0)).plus(new Complex(s0,0));
calculatedPowers[j] = calculatedComplex[j].mod();
}
handler.handleDetectedFrequencies(audioEvent.getTimeStamp(),frequenciesToDetect.clone(), calculatedPowers.clone(),
frequenciesToDetect.clone(), calculatedPowers.clone());
return true;
}
@Override
public void processingFinished() {
}
}