node-pitchfinder
Version:
A pitch-detection library for node (using C++ Addon)
192 lines (160 loc) • 5.32 kB
JavaScript
'use strict';
var DEFAULT_SAMPLE_RATE = 44100;
var MAX_FLWT_LEVELS = 6;
var MAX_F = 3000;
var DIFFERENCE_LEVELS_N = 3;
var MAXIMA_THRESHOLD_RATIO = 0.75;
module.exports = function () {
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var sampleRate = config.sampleRate || DEFAULT_SAMPLE_RATE;
return function DynamicWaveletDetector(data) {
'use strict';
var float32AudioBuffer = data;
if (!(data instanceof Float64Array)) float32AudioBuffer = Float64Array.from(data);
var mins = [];
var maxs = [];
var bufferLength = float32AudioBuffer.length;
var freq = null;
var theDC = 0;
var minValue = 0;
var maxValue = 0;
// Compute max amplitude, amplitude threshold, and the DC.
for (var i = 0; i < bufferLength; i++) {
var sample = float32AudioBuffer[i];
theDC = theDC + sample;
maxValue = Math.max(maxValue, sample);
minValue = Math.min(minValue, sample);
}
theDC /= bufferLength;
minValue -= theDC;
maxValue -= theDC;
var amplitudeMax = maxValue > -1 * minValue ? maxValue : -1 * minValue;
var amplitudeThreshold = amplitudeMax * MAXIMA_THRESHOLD_RATIO;
// levels, start without downsampling...
var curLevel = 0;
var curModeDistance = -1;
var curSamNb = float32AudioBuffer.length;
var delta = void 0,
nbMaxs = void 0,
nbMins = void 0;
// Search:
while (true) {
delta = ~~(sampleRate / (Math.pow(2, curLevel) * MAX_F));
if (curSamNb < 2) break;
var dv = void 0;
var previousDV = -1000;
var lastMinIndex = -1000000;
var lastMaxIndex = -1000000;
var findMax = false;
var findMin = false;
nbMins = 0;
nbMaxs = 0;
for (var _i = 2; _i < curSamNb; _i++) {
var si = float32AudioBuffer[_i] - theDC;
var si1 = float32AudioBuffer[_i - 1] - theDC;
if (si1 <= 0 && si > 0) findMax = true;
if (si1 >= 0 && si < 0) findMin = true;
// min or max ?
dv = si - si1;
if (previousDV > -1000) {
if (findMin && previousDV < 0 && dv >= 0) {
// minimum
if (Math.abs(si) >= amplitudeThreshold) {
if (_i > lastMinIndex + delta) {
mins[nbMins++] = _i;
lastMinIndex = _i;
findMin = false;
}
}
}
if (findMax && previousDV > 0 && dv <= 0) {
// maximum
if (Math.abs(si) >= amplitudeThreshold) {
if (_i > lastMaxIndex + delta) {
maxs[nbMaxs++] = _i;
lastMaxIndex = _i;
findMax = false;
}
}
}
}
previousDV = dv;
}
if (nbMins === 0 && nbMaxs === 0) {
// No best distance found!
break;
}
var d = void 0;
var distances = [];
for (var _i2 = 0; _i2 < curSamNb; _i2++) {
distances[_i2] = 0;
}
for (var _i3 = 0; _i3 < nbMins; _i3++) {
for (var j = 1; j < DIFFERENCE_LEVELS_N; j++) {
if (_i3 + j < nbMins) {
d = Math.abs(mins[_i3] - mins[_i3 + j]);
distances[d] += 1;
}
}
}
var bestDistance = -1;
var bestValue = -1;
for (var _i4 = 0; _i4 < curSamNb; _i4++) {
var summed = 0;
for (var _j = -1 * delta; _j <= delta; _j++) {
if (_i4 + _j >= 0 && _i4 + _j < curSamNb) {
summed += distances[_i4 + _j];
}
}
if (summed === bestValue) {
if (_i4 === 2 * bestDistance) {
bestDistance = _i4;
}
} else if (summed > bestValue) {
bestValue = summed;
bestDistance = _i4;
}
}
// averaging
var distAvg = 0;
var nbDists = 0;
for (var _j2 = -delta; _j2 <= delta; _j2++) {
if (bestDistance + _j2 >= 0 && bestDistance + _j2 < bufferLength) {
var nbDist = distances[bestDistance + _j2];
if (nbDist > 0) {
nbDists += nbDist;
distAvg += (bestDistance + _j2) * nbDist;
}
}
}
// This is our mode distance.
distAvg /= nbDists;
// Continue the levels?
if (curModeDistance > -1) {
if (Math.abs(distAvg * 2 - curModeDistance) <= 2 * delta) {
// two consecutive similar mode distances : ok !
freq = sampleRate / (Math.pow(2, curLevel - 1) * curModeDistance);
break;
}
}
// not similar, continue next level;
curModeDistance = distAvg;
curLevel++;
if (curLevel >= MAX_FLWT_LEVELS || curSamNb < 2) {
break;
}
// do not modify original audio buffer, make a copy buffer, if
// downsampling is needed (only once).
var newFloat32AudioBuffer = float32AudioBuffer.subarray(0);
if (curSamNb === distances.length) {
newFloat32AudioBuffer = new Float32Array(curSamNb / 2);
}
for (var _i5 = 0; _i5 < curSamNb / 2; _i5++) {
newFloat32AudioBuffer[_i5] = (float32AudioBuffer[2 * _i5] + float32AudioBuffer[2 * _i5 + 1]) / 2;
}
float32AudioBuffer = newFloat32AudioBuffer;
curSamNb /= 2;
}
return freq;
};
};