OpenWareLaboratory
PitchDetector.h
Go to the documentation of this file.
1 #ifndef __PitchDetector_h__
2 #define __PitchDetector_h__
3 
4 #include "FloatArray.h"
5 #include "ComplexFloatArray.h"
6 #include "FastFourierTransform.h"
7 #include "BiquadFilter.h"
8 #include "Window.h"
9 
11 private:
13  float samplingRate;
14  float minBin;
15  float maxBin;
16  float binSize;
17  float frequency;
19  FloatArray magnitudes;
20  FloatArray timeDomain;
21  size_t writePointer;
22  Window window;
23 public:
25 
26  };
27  FourierPitchDetector(int fftSize, float aSamplingRate){
28  init(fftSize, aSamplingRate);
29  };
32  FloatArray::destroy(magnitudes);
33  FloatArray::destroy(timeDomain);
34  Window::destroy(window);
35  }
36  void init(int fftSize, float aSamplingRate){
37  samplingRate=aSamplingRate;
38  frequency=0;
39  writePointer=0;
40  fft.init(fftSize);
41  binSize=samplingRate/fftSize;
42  fd=ComplexFloatArray::create(fftSize);
43  magnitudes=FloatArray::create(fftSize);
44  timeDomain=FloatArray::create(fftSize);
45  window=Window::create(Window::HannWindow, fftSize);
46  }
47  size_t getSize(){
48  return fft.getSize();
49  }
50  void setSamplingRate(float asamplingRate){
51  samplingRate=asamplingRate;
52  }
53  void setMinFrequency(float aMinFrequency){
54  minBin=(int)(aMinFrequency/binSize);
55  }
56  void setMaxFrequency(float aMaxFrequency){
57  maxBin=(int)(aMaxFrequency/binSize+1);
58  }
59  int process(FloatArray input){ //return values: 0 no fft performed, 1 fft performed
60  ASSERT(input.getSize()<=fd.getSize(), "wrong size");
61  if(input.getSize()==fft.getSize()){ //if the two sizes match, no need to copy the input into the local timeDomain FloatArray
62  input.multiply(window, timeDomain);
63  fft.fft(timeDomain, fd);
64  return 1;
65  }
66  // otherwise keep filling the input buffer TODO: could multiply by the window while copying
67  size_t samplesToCopy=min(input.getSize(),timeDomain.getSize()-writePointer);
68  timeDomain.insert(input, 0, writePointer, samplesToCopy);
69  writePointer+=samplesToCopy;
70  if(writePointer==fft.getSize()){ // if it is full, reset pointer and do fft
71  writePointer=0;
72  timeDomain.multiply(window);
73  fft.fft(timeDomain, fd);
74  return 1;
75  }
76  return 0;
77  }
78  float computeFrequency(){// this could have been implemented into process().
79  //The reason why it is in a separate method is to allow to distribute the computational load across different audio blocks
80  ComplexFloatArray fdsub=fd.subArray((int)minBin, (int)maxBin-(int)minBin);
81  FloatArray magnitudesSub=magnitudes.subArray((int)minBin, (int)maxBin-(int)minBin);
82  fdsub.getMagnitudeSquaredValues(magnitudesSub);
83  size_t maxIndex=magnitudesSub.getMaxIndex();
84  //do quadratic interpolation https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
85  //this single iteration method gives the following accuracy using as input a sinewave in the range 100Hz-200Hz
86  //fftSize=512; max(abs(error))=15Hz well, with this fftSize, the binsize is 93.75 Hz, with a 100Hz input
87  //the peak is in the first quarter of the second bin and the estimate cannot be precise!
88  // performance improve drastically above 140Hz (error <2Hz)
89  //fftSize=1024; max(abs(error))=0.7495Hz
90  //fftSize=2048; max(abs(error))=0.37531Hz
91  //fftSize=4096; max(abs(error))=0.18746Hz
92  if(maxIndex==0||maxIndex==magnitudesSub.getSize()-1) { //value out of range
93  frequency=0; //TODO: what to do if value is out of range?
94  return getFrequency();
95  }
96  //note that we are working on the values in Bel (10*dB).
97  //Using the logarithmic scale gives better accuracy of the peak estimate
98  //in this application
99  float alpha=log10(magnitudesSub[maxIndex-1]);
100  float beta=log10(magnitudesSub[maxIndex]);
101  float gamma=log10(magnitudesSub[maxIndex+1]);
102  float p=0.5*(alpha-gamma)/(alpha-2*beta+gamma);
103  // ASSERT(p>=-0.5 && p<=0.5, "Wrong range for p");
104  int bin=maxIndex+minBin;
105  frequency=(bin+p)*binSize;
106  return getFrequency();
107  }
108  float getFrequency(){
109  return frequency;
110  }
111 };
112 
114 private:
115  float samplingRate;
116  int numLowPassStages;
117  int numHighPassStages;
118  BiquadFilter *filter;
119  FloatArray counts;
120  FloatArray filterOutput;
121  const static int POINTS_AVERAGE = 10;
122 public:
123  ZeroCrossingPitchDetector(float aSamplingRate, int blocksize, int aNumLowPassStages=1, int aNumHighPassStages=1) :
124  samplingRate(aSamplingRate),
125  numLowPassStages(aNumLowPassStages),
126  numHighPassStages(aNumHighPassStages) {
127  // RAII constructor
128  filterOutput = FloatArray::create(blocksize);
129  counts = FloatArray::create(POINTS_AVERAGE); //number of zcc to be averaged
130  filter = BiquadFilter::create(aSamplingRate, numLowPassStages+numHighPassStages);
131  setLowPassCutoff(0.03);
132  setHighPassCutoff(0.001);
133  }
135  FloatArray::destroy(counts);
136  FloatArray::destroy(filterOutput);
137  BiquadFilter::destroy(filter);
138  }
139  void setSamplingRate(float aSamplingRate){
140  samplingRate = aSamplingRate;
141  }
142  void setLowPassCutoff(float fc){
143  if(numLowPassStages<1)
144  return;
146  };
147  void setHighPassCutoff(float fc){
148  if(numHighPassStages<1)
149  return;
151  };
152  void process(FloatArray input){
153  ASSERT(input.getSize()<=filterOutput.getSize(), "wrong size");
154  static float lastValue=0;
155  static size_t countsPointer=0;
156  static float count;
157  filter->process(input, filterOutput);
158  // filterOutput.copyTo(input);
159  for(size_t n=0; n<input.getSize(); n++){
160  float currentValue=filterOutput[n];
161  if(currentValue>0 && lastValue<=0){
162  /*
163  counts[countsPointer]=count; //Could use nearest neighbour, but
164  count=0;
165  */
166  //linear interpolation gives a better estimate of the zero crossing time:
167  float offset=(-lastValue)/(lastValue+currentValue);
168  counts[countsPointer]=count+offset;
169  count=-offset;
170  countsPointer++;
171  if(countsPointer==counts.getSize()) //use counts as a circular buffer
172  countsPointer=0;
173  }
174  lastValue=currentValue;
175  count++;
176  }
177  }
178  float getFrequency(){
179  float mean = counts.getMean();
180  if(mean > 0.0) // avoid divide by zero
181  return samplingRate/mean;
182  return 0.0;
183  }
185  return filter;
186  }
187 };
188 
189 #endif /* __PitchDetector_h__ */
#define min(a, b)
Definition: basicmaths.h:38
void setHighPass(float fc, float q)
Definition: BiquadFilter.h:352
void setLowPass(float fc, float q)
Definition: BiquadFilter.h:347
static BiquadFilter * create(float sr, size_t stages=1)
Definition: BiquadFilter.h:417
static void destroy(BiquadFilter *filter)
Definition: BiquadFilter.h:421
void process(float *input, float *output, size_t size)
Definition: BiquadFilter.h:276
void getMagnitudeSquaredValues(FloatArray destination)
The squared magnitudes of the elements of the array.
static void destroy(ComplexFloatArray)
Destroys a ComplexFloatArray created with the create() method.
ComplexFloatArray subArray(int offset, size_t length)
A subset of the array.
static ComplexFloatArray create(size_t size)
Creates a new ComplexFloatArray.
This class performs direct and inverse Fast Fourier Transform.
size_t getSize()
Get the size of the FFT.
void init(size_t aSize)
Initialize the instance.
void fft(FloatArray input, ComplexFloatArray output)
Perform the direct FFT.
static constexpr float BUTTERWORTH_Q
Definition: BiquadFilter.h:14
This class contains useful methods for manipulating arrays of floats.
Definition: FloatArray.h:12
void multiply(FloatArray operand2, FloatArray destination)
Element-wise multiplication between arrays.
Definition: FloatArray.cpp:290
int getMaxIndex()
Get the index of the maximum value in the array.
Definition: FloatArray.cpp:69
static void destroy(FloatArray array)
Destroys a FloatArray created with the create() method.
Definition: FloatArray.cpp:472
float getMean()
Mean of the array.
Definition: FloatArray.cpp:136
static FloatArray create(int size)
Creates a new FloatArray.
Definition: FloatArray.cpp:466
FloatArray subArray(int offset, size_t length)
A subset of the array.
Definition: FloatArray.cpp:215
int process(FloatArray input)
Definition: PitchDetector.h:59
FourierPitchDetector(int fftSize, float aSamplingRate)
Definition: PitchDetector.h:27
void setMaxFrequency(float aMaxFrequency)
Definition: PitchDetector.h:56
void init(int fftSize, float aSamplingRate)
Definition: PitchDetector.h:36
void setSamplingRate(float asamplingRate)
Definition: PitchDetector.h:50
void setMinFrequency(float aMinFrequency)
Definition: PitchDetector.h:53
size_t getSize() const
Definition: SimpleArray.h:31
void insert(SimpleArray< T > source, int destinationOffset, size_t len)
Copies the content of an array into a subset of the array.
Definition: SimpleArray.h:99
Definition: Window.h:20
static Window create(WindowType type, int size)
Definition: Window.h:50
@ HannWindow
Definition: Window.h:26
void setSamplingRate(float aSamplingRate)
void setHighPassCutoff(float fc)
void setLowPassCutoff(float fc)
ZeroCrossingPitchDetector(float aSamplingRate, int blocksize, int aNumLowPassStages=1, int aNumHighPassStages=1)
void process(FloatArray input)
BiquadFilter * getFilter()
#define ASSERT(cond, msg)
Definition: message.h:16