Skip to content

Commit f33e8a1

Browse files
committed
Added FourierPitchDetector class with testPatch
1 parent 709e731 commit f33e8a1

3 files changed

Lines changed: 206 additions & 3 deletions

File tree

LibSource/PitchDetector.hpp

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,113 @@
1-
#ifndef __PitchDetectors_hpp__
2-
#define __PitchDetectors_hpp__
1+
#ifndef __PitchDetector_hpp__
2+
#define __PitchDetector_hpp__
33

44
#include "FloatArray.h"
55

6+
class FourierPitchDetector{
7+
private:
8+
FastFourierTransform fft;
9+
float samplingRate;
10+
float minBin;
11+
float maxBin;
12+
float binSize;
13+
float frequency;
14+
ComplexFloatArray fd;
15+
FloatArray magnitudes;
16+
FloatArray timeDomain;
17+
int writePointer;
18+
Window window;
19+
public:
20+
FourierPitchDetector(){
21+
22+
};
23+
FourierPitchDetector(int fftSize, float aSamplingRate){
24+
init(fftSize, aSamplingRate);
25+
};
26+
~FourierPitchDetector(){
27+
ComplexFloatArray::destroy(fd);
28+
FloatArray::destroy(magnitudes);
29+
FloatArray::destroy(timeDomain);
30+
Window::destroy(window);
31+
}
32+
void init(int fftSize, float aSamplingRate){
33+
samplingRate=aSamplingRate;
34+
frequency=0;
35+
writePointer=0;
36+
fft.init(fftSize);
37+
binSize=samplingRate/fftSize;
38+
fd=ComplexFloatArray::create(fftSize);
39+
magnitudes=FloatArray::create(fftSize);
40+
timeDomain=FloatArray::create(fftSize);
41+
window=Window::create(Window::HannWindow, fftSize);
42+
}
43+
int getSize(){
44+
return fft.getSize();
45+
}
46+
void setsamplingRate(float asamplingRate){
47+
samplingRate=asamplingRate;
48+
}
49+
void setMinFrequency(float aMinFrequency){
50+
minBin=(int)(aMinFrequency/binSize);
51+
}
52+
void setMaxFrequency(float aMaxFrequency){
53+
maxBin=(int)(aMaxFrequency/binSize+1);
54+
}
55+
int process(FloatArray input){ //return values: 0 no fft performed, 1 fft performed
56+
ASSERT(input.getSize()<=fd.getSize(), "wrong size");
57+
if(input.getSize()==fft.getSize()){ //if the two sizes match, no need to copy the input into the local timeDomain FloatArray
58+
input.multiply(window, timeDomain);
59+
fft.fft(timeDomain, fd);
60+
return 1;
61+
}
62+
// otherwise keep filling the input buffer TODO: could multiply by the window while copying
63+
int samplesToCopy=min(input.getSize(),timeDomain.getSize()-writePointer);
64+
timeDomain.insert(input, 0, writePointer, samplesToCopy);
65+
writePointer+=samplesToCopy;
66+
if(writePointer<fft.getSize()){ // if it is not full, keep going
67+
return 0;
68+
}
69+
if(writePointer==fft.getSize()){ // if it is full, reset pointer and do fft
70+
writePointer=0;
71+
timeDomain.multiply(window);
72+
fft.fft(timeDomain, fd);
73+
return 1;
74+
}
75+
}
76+
float computeFrequency(){// this could have been implemented into process().
77+
//The reason why it is in a separate method is to allow to distribute the computational load across different audio blocks
78+
ComplexFloatArray fdsub=fd.subArray((int)minBin, (int)maxBin-(int)minBin);
79+
FloatArray magnitudesSub=magnitudes.subArray((int)minBin, (int)maxBin-(int)minBin);
80+
fdsub.getMagnitudeSquaredValues(magnitudesSub);
81+
int maxIndex=magnitudesSub.getMaxIndex();
82+
//do quadratic interpolation https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
83+
//this single iteration method gives the following accuracy using as input a sinewave in the range 100Hz-200Hz
84+
//fftSize=512; max(abs(error))=15Hz well, with this fftSize, the binsize is 93.75 Hz, with a 100Hz input
85+
//the peak is in the first quarter of the second bin and the estimate cannot be precise!
86+
// performance improve drastically above 140Hz (error <2Hz)
87+
//fftSize=1024; max(abs(error))=0.7495Hz
88+
//fftSize=2048; max(abs(error))=0.37531Hz
89+
//fftSize=4096; max(abs(error))=0.18746Hz
90+
if(maxIndex==0||maxIndex==magnitudesSub.getSize()-1) { //value out of range
91+
frequency=0; //TODO: what to do if value is out of range?
92+
return getFrequency();
93+
}
94+
//note that we are working on the values in Bel (10*dB).
95+
//Using the logarithmic scale gives better accuracy of the peak estimate
96+
//in this application
97+
float alpha=log10(magnitudesSub[maxIndex-1]);
98+
float beta=log10(magnitudesSub[maxIndex]);
99+
float gamma=log10(magnitudesSub[maxIndex+1]);
100+
float p=0.5*(alpha-gamma)/(alpha-2*beta+gamma);
101+
// ASSERT(p>=-0.5 && p<=0.5, "Wrong range for p");
102+
int bin=maxIndex+minBin;
103+
frequency=(bin+p)*binSize;
104+
return getFrequency();
105+
}
106+
float getFrequency(){
107+
return frequency;
108+
}
109+
};
110+
6111
class ZeroCrossingPitchDetector{
7112
private:
8113
BiquadFilter *filter;
@@ -99,4 +204,4 @@ class ZeroCrossingPitchDetector{
99204
}
100205
};
101206

102-
#endif /* __PitchDetectors_hpp__ */
207+
#endif /* __PitchDetector_hpp__ */
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
3+
/*
4+
5+
6+
LICENSE:
7+
This program is free software: you can redistribute it and/or modify
8+
it under the terms of the GNU General Public License as published by
9+
the Free Software Foundation, either version 3 of the License, or
10+
(at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
You should have received a copy of the GNU General Public License
18+
along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
20+
*/
21+
22+
23+
/* created by the OWL team 2013 */
24+
25+
26+
////////////////////////////////////////////////////////////////////////////////////////////////////
27+
28+
29+
#ifndef __FourierPitchDetectorTestPatch_hpp__
30+
#define __FourierPitchDetectorTestPatch_hpp__
31+
32+
#include "StompBox.h"
33+
34+
class FourierPitchDetectorTestPatch : public Patch {
35+
public:
36+
BiquadFilter *filter;
37+
FourierPitchDetector fpd;
38+
FloatArray aux;
39+
int fftOccurrency;
40+
FourierPitchDetectorTestPatch() {
41+
registerParameter(PARAMETER_A, "Mix");
42+
registerParameter(PARAMETER_B, "HP cutoff");
43+
registerParameter(PARAMETER_C, "LP cutoff");
44+
registerParameter(PARAMETER_D, "gain");
45+
int fftSize=512;
46+
fpd.init(fftSize, getSampleRate());
47+
fftOccurrency=fftSize/getBlockSize();
48+
aux=FloatArray::create(getBlockSize());
49+
}
50+
void processAudio(AudioBuffer &buffer){
51+
static int count=0;
52+
static int prevRet=0;
53+
FloatArray fa=buffer.getSamples(0);
54+
float estimated=0;
55+
float minFrequency=getParameterValue(PARAMETER_B)*500+50;
56+
float maxFrequency=getParameterValue(PARAMETER_C)*1000+150;
57+
58+
fpd.setMinFrequency(minFrequency);
59+
fpd.setMaxFrequency(maxFrequency);
60+
int ret=fpd.process(fa);
61+
//if in the previous block an fft took place
62+
//update the frequency detector
63+
if(prevRet==1 || fftOccurrency==1){
64+
fpd.computeFrequency();
65+
}
66+
prevRet=ret;
67+
68+
estimated=fpd.getFrequency();
69+
ASSERT(ret==((fftOccurrency-1)==(count&(fftOccurrency-1))), "unexpected FFT");
70+
//synthesize a tone and mix it with the input sound;
71+
float mix=getParameterValue(PARAMETER_A);
72+
float envelope=fa.getRms();
73+
fa.multiply(1-mix);
74+
for(int n=0;n<fa.getSize(); n++){
75+
static float phase=0;
76+
static float pastEnvelope=0;
77+
phase += 2.0 * M_PI * estimated/getSampleRate();
78+
if(phase > 2.0 * M_PI)
79+
phase -= 2.0 * M_PI;
80+
if(phase > 4.0*M_PI)
81+
phase=0;
82+
envelope=0.1*envelope + pastEnvelope*0.9;
83+
pastEnvelope=envelope;
84+
fa[n]+=sin(phase)*mix*envelope;
85+
}
86+
fa.multiply(getParameterValue(PARAMETER_D)*10);
87+
fa.copyTo(buffer.getSamples(1));
88+
89+
count++;
90+
static float otherTime=0;
91+
if(ret==1){ //show that the elapsedBlockTime is much larger in blocks with fft
92+
debugMessage("estimated, elapsedBlockTime, otherTime", estimated, getElapsedBlockTime()*100, otherTime);
93+
} else //keep track of the elapsedBlockTime in blocks without fft
94+
otherTime=getElapsedBlockTime()*100;
95+
}
96+
};
97+
98+
#endif // __FourierPitchDetectorTestPatch_hpp__
File renamed without changes.

0 commit comments

Comments
 (0)