#ifndef __PitchDetector_h__
#define __PitchDetector_h__
#include "FloatArray.h"
#include "ComplexFloatArray.h"
#include "FastFourierTransform.h"
#include "BiquadFilter.h"
#include "Window.h"
class FourierPitchDetector{
private:
FastFourierTransform fft;
float samplingRate;
float minBin;
float maxBin;
float binSize;
float frequency;
ComplexFloatArray fd;
FloatArray magnitudes;
FloatArray timeDomain;
size_t writePointer;
Window window;
public:
FourierPitchDetector(){
};
FourierPitchDetector(int fftSize, float aSamplingRate){
init(fftSize, aSamplingRate);
};
~FourierPitchDetector(){
ComplexFloatArray::destroy(fd);
FloatArray::destroy(magnitudes);
FloatArray::destroy(timeDomain);
Window::destroy(window);
}
void init(int fftSize, float aSamplingRate){
samplingRate=aSamplingRate;
frequency=0;
writePointer=0;
fft.init(fftSize);
binSize=samplingRate/fftSize;
fd=ComplexFloatArray::create(fftSize);
magnitudes=FloatArray::create(fftSize);
timeDomain=FloatArray::create(fftSize);
window=Window::create(Window::HannWindow, fftSize);
}
size_t getSize(){
return fft.getSize();
}
void setSamplingRate(float asamplingRate){
samplingRate=asamplingRate;
}
void setMinFrequency(float aMinFrequency){
minBin=(int)(aMinFrequency/binSize);
}
void setMaxFrequency(float aMaxFrequency){
maxBin=(int)(aMaxFrequency/binSize+1);
}
int process(FloatArray input){ ASSERT(input.getSize()<=fd.getSize(), "wrong size");
if(input.getSize()==fft.getSize()){ input.multiply(window, timeDomain);
fft.fft(timeDomain, fd);
return 1;
}
size_t samplesToCopy=min(input.getSize(),timeDomain.getSize()-writePointer);
timeDomain.insert(input, 0, writePointer, samplesToCopy);
writePointer+=samplesToCopy;
if(writePointer==fft.getSize()){ writePointer=0;
timeDomain.multiply(window);
fft.fft(timeDomain, fd);
return 1;
}
return 0;
}
float computeFrequency(){ ComplexFloatArray fdsub=fd.subArray((int)minBin, (int)maxBin-(int)minBin);
FloatArray magnitudesSub=magnitudes.subArray((int)minBin, (int)maxBin-(int)minBin);
fdsub.getMagnitudeSquaredValues(magnitudesSub);
size_t maxIndex=magnitudesSub.getMaxIndex();
if(maxIndex==0||maxIndex==magnitudesSub.getSize()-1) { frequency=0; return getFrequency();
}
float alpha=log10(magnitudesSub[maxIndex-1]);
float beta=log10(magnitudesSub[maxIndex]);
float gamma=log10(magnitudesSub[maxIndex+1]);
float p=0.5*(alpha-gamma)/(alpha-2*beta+gamma);
int bin=maxIndex+minBin;
frequency=(bin+p)*binSize;
return getFrequency();
}
float getFrequency(){
return frequency;
}
};
class ZeroCrossingPitchDetector{
private:
float samplingRate;
int numLowPassStages;
int numHighPassStages;
BiquadFilter *filter;
FloatArray counts;
FloatArray filterOutput;
const static int POINTS_AVERAGE = 10;
public:
ZeroCrossingPitchDetector(float aSamplingRate, int blocksize, int aNumLowPassStages=1, int aNumHighPassStages=1) :
samplingRate(aSamplingRate),
numLowPassStages(aNumLowPassStages),
numHighPassStages(aNumHighPassStages) {
filterOutput = FloatArray::create(blocksize);
counts = FloatArray::create(POINTS_AVERAGE); filter = BiquadFilter::create(aSamplingRate, numLowPassStages+numHighPassStages);
setLowPassCutoff(0.03);
setHighPassCutoff(0.001);
}
~ZeroCrossingPitchDetector(){
FloatArray::destroy(counts);
FloatArray::destroy(filterOutput);
BiquadFilter::destroy(filter);
}
void setSamplingRate(float aSamplingRate){
samplingRate = aSamplingRate;
}
void setLowPassCutoff(float fc){
if(numLowPassStages<1)
return;
filter->setLowPass(fc, FilterStage::BUTTERWORTH_Q);
};
void setHighPassCutoff(float fc){
if(numHighPassStages<1)
return;
filter->setHighPass(fc, FilterStage::BUTTERWORTH_Q);
};
void process(FloatArray input){
ASSERT(input.getSize()<=filterOutput.getSize(), "wrong size");
static float lastValue=0;
static size_t countsPointer=0;
static float count;
filter->process(input, filterOutput);
for(size_t n=0; n<input.getSize(); n++){
float currentValue=filterOutput[n];
if(currentValue>0 && lastValue<=0){
float offset=(-lastValue)/(lastValue+currentValue);
counts[countsPointer]=count+offset;
count=-offset;
countsPointer++;
if(countsPointer==counts.getSize()) countsPointer=0;
}
lastValue=currentValue;
count++;
}
}
float getFrequency(){
float mean = counts.getMean();
if(mean > 0.0) return samplingRate/mean;
return 0.0;
}
BiquadFilter* getFilter(){
return filter;
}
};
#endif