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() { } }