Comments (4)
Here's my MPMPitchDetector
template class, based on your implementation, and using the more friendly-licensed FFTS
instead of FFTW
.
Usage:
MPMPitchDetector<4096> detector(sampleRate, 80);
detector.estimate(samples); // samples is a vector<double> of size 4096
Source:
// MPMPitchDetector.h
#pragma once
#include <vector>
#include <ffts/ffts.h>
#include <climits>
#define MPM_CUTOFF 0.93
#define MPM_SMALL_CUTOFF 0.5
static inline std::pair<double, double> parabolicInterpolation(const std::vector<double> &array, double x) {
int x_adjusted;
if (x < 1) {
x_adjusted = (array[x] <= array[x + 1]) ? x : x + 1;
} else if (x > signed(array.size()) - 1) {
x_adjusted = (array[x] <= array[x - 1]) ? x : x - 1;
} else {
double den = array[x + 1] + array[x - 1] - 2 * array[x];
double delta = array[x - 1] - array[x + 1];
return (!den) ? std::make_pair(x, array[x])
: std::make_pair(x + delta / (2 * den),
array[x] - delta * delta / (8 * den));
}
return std::make_pair(x_adjusted, array[x_adjusted]);
}
template <size_t SIZE>
class MPMPitchDetector {
public:
MPMPitchDetector(double sampleRate, double minFreq) :
sampleRate(sampleRate), minFreq(minFreq) {
assert(SIZE && ((SIZE & (SIZE - 1)) == 0)); // (optional) assert SIZE is a power of 2
fftForward = ffts_init_1d(SIZE * 2, false);
fftBackward = ffts_init_1d(SIZE * 2, false);
}
~MPMPitchDetector() {
ffts_free(fftForward);
ffts_free(fftBackward);
}
// estimates the base frequency for the samples provided
// using the McLeod Pitch Method.
double estimate(const std::vector<double> &samples) {
assert(samples.size() == SIZE);
std::vector<double> nsdf = normalizedSquareDifference(samples);
std::vector<int> max_positions = pickPeaks(nsdf);
std::vector<std::pair<double, double>> estimates;
double highest_amplitude = -DBL_MAX;
for (int i : max_positions) {
highest_amplitude = std::max(highest_amplitude, nsdf[i]);
if (nsdf[i] > MPM_SMALL_CUTOFF) {
auto x = parabolicInterpolation(nsdf, i);
estimates.push_back(x);
highest_amplitude = std::max(highest_amplitude, x.second);
}
}
if (estimates.empty())
return -1;
double actual_cutoff = MPM_CUTOFF * highest_amplitude;
double period = 0;
for (auto i : estimates) {
if (i.second >= actual_cutoff) {
period = i.first;
break;
}
}
double pitch_estimate = sampleRate / period;
return pitch_estimate > minFreq ? pitch_estimate : -1;
}
private:
const double sampleRate;
const double minFreq;
ffts_plan_t *fftForward, *fftBackward;
std::vector<double> normalizedSquareDifference(const std::vector<double> &samples) {
std::complex<float> acf_complex[SIZE * 2];
std::vector<double> acf_real(SIZE);
std::vector<double> acf_real_2(SIZE);
autocorrelation(samples, acf_complex);
for (size_t i = 0; i < SIZE; ++i)
acf_real[i] = (acf_complex[i + SIZE]).real() / acf_complex[SIZE].real();
for (size_t i = SIZE; i < SIZE * 2; i++) {
acf_real_2.push_back(acf_complex[i].real() / acf_complex[SIZE].real());
}
return acf_real;
}
void autocorrelation(const std::vector<double> &data, std::complex<float> *result) {
std::complex<float> signala_ext[SIZE * 2];
std::complex<float> signalb_ext[SIZE * 2];
// zero padding of input data
for (int i = 0; i < SIZE; i++) {
signala_ext[SIZE + i] = data[i];
signalb_ext[i] = data[i];
}
std::complex<float> outa[SIZE * 2];
std::complex<float> outb[SIZE * 2];
std::complex<float> out[SIZE * 2];
ffts_execute(fftForward, signala_ext, outa);
ffts_execute(fftForward, signalb_ext, outb);
for (int i = 0; i < SIZE * 2; ++i) {
out[i] = outa[i] * std::conj(outb[i]);
}
ffts_execute(fftBackward, out, result);
}
std::vector<int> pickPeaks(const std::vector<double> &nsdf) {
std::vector<int> max_positions{};
int pos = 0;
int cur_max_pos = 0;
ssize_t size = nsdf.size();
while (pos < (size - 1) / 3 && nsdf[pos] > 0)
pos++;
while (pos < size - 1 && nsdf[pos] <= 0.0)
pos++;
if (pos == 0)
pos = 1;
while (pos < size - 1) {
if (nsdf[pos] > nsdf[pos - 1] && nsdf[pos] >= nsdf[pos + 1]) {
if (cur_max_pos == 0) {
cur_max_pos = pos;
} else if (nsdf[pos] > nsdf[cur_max_pos]) {
cur_max_pos = pos;
}
}
pos++;
if (pos < size - 1 && nsdf[pos] <= 0) {
if (cur_max_pos > 0) {
max_positions.push_back(cur_max_pos);
cur_max_pos = 0;
}
while (pos < size - 1 && nsdf[pos] <= 0.0) {
pos++;
}
}
}
if (cur_max_pos > 0) {
max_positions.push_back(cur_max_pos);
}
return max_positions;
}
};
from pitch-detection.
FYI:
std::vector<double> acf_real(SIZE);
std::vector<double> acf_real_2(SIZE);
autocorrelation(samples, acf_complex);
for (size_t i = 0; i < SIZE; ++i)
acf_real[i] = (acf_complex[i + SIZE]).real() / acf_complex[SIZE].real();
for (size_t i = SIZE; i < SIZE * 2; i++) {
acf_real_2.push_back(acf_complex[i].real() / acf_complex[SIZE].real());
}
I think acf_real_2
here is a leftover of some debugging experiment I committed by accident. If you still have it in your code you can get rid of it.
I'm experimenting with FFTS right now.
from pitch-detection.
from pitch-detection.
Looks good to me! Thank you!
from pitch-detection.
Related Issues (20)
- Transcriber example HOT 2
- Absorb yin and mpm repos here
- Guitar tuner example HOT 3
- Docs HOT 1
- Could you give an example code to detect pitch? HOT 6
- ma'c
- How can I use it in mac os? HOT 1
- YIN vs McLeod pitch detection accuracy HOT 7
- Implement pYIN (probabilistic YIN) HOT 6
- Implement SWIPE HOT 6
- Implement CREPE HOT 2
- C API
- wrong results with different buffer sizes in the sinewave example HOT 4
- Add Aud-SWIPE-P (parallel SWIPE)
- Android/Oboe tutorial/doc
- the error of the project HOT 6
- Additional compiler flags maybe needed HOT 2
- Using autocorrelation instead of calculating NSDF in MPM HOT 5
- Create a GitHub Action that builds and runs unit tests HOT 5
- systematic comparison of methods HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from pitch-detection.