mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 19:29:33 +00:00
Clean release snapshot
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* YIN pitch detection worker.
|
||||
*
|
||||
* Receives { samples: Float32Array, sampleRate: number }, posts back
|
||||
* { freq, confidence, rms }. The samples ArrayBuffer should be passed
|
||||
* as transferable so no copy occurs across the worker boundary.
|
||||
*/
|
||||
// Guard allows the file to be required in Node.js test environments where
|
||||
// `self` is not defined; the worker message handler only runs in the browser.
|
||||
if (typeof self !== 'undefined') {
|
||||
self.onmessage = (e) => {
|
||||
const { samples, sampleRate } = e.data;
|
||||
self.postMessage(_yinDetect(samples, sampleRate));
|
||||
};
|
||||
}
|
||||
|
||||
function _yinDetect(buffer, sampleRate) {
|
||||
const threshold = 0.15;
|
||||
const halfLen = Math.floor(buffer.length / 2);
|
||||
const yinBuffer = new Float32Array(halfLen);
|
||||
|
||||
let rms = 0;
|
||||
for (let i = 0; i < buffer.length; i++) rms += buffer[i] * buffer[i];
|
||||
rms = Math.sqrt(rms / buffer.length);
|
||||
if (rms < 0.01) return { freq: 0, confidence: 0, rms };
|
||||
|
||||
let runningSum = 0;
|
||||
yinBuffer[0] = 1;
|
||||
for (let tau = 1; tau < halfLen; tau++) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < halfLen; i++) {
|
||||
const delta = buffer[i] - buffer[i + tau];
|
||||
sum += delta * delta;
|
||||
}
|
||||
yinBuffer[tau] = sum;
|
||||
runningSum += sum;
|
||||
yinBuffer[tau] = runningSum > 0 ? yinBuffer[tau] * tau / runningSum : 1;
|
||||
}
|
||||
|
||||
// Canonical YIN absolute-threshold step: walk tau upward and take the FIRST
|
||||
// local minimum that drops below the threshold — NOT the globally deepest
|
||||
// dip. The fundamental period is the smallest tau that satisfies the
|
||||
// difference function; its sub-octaves (2T, 3T, …) sit at LARGER tau and
|
||||
// often dip just as deep or deeper (the waveform realigns over two full
|
||||
// periods), so picking the deepest dip is what produced the octave-low
|
||||
// errors — D1 reported for a D2 string, or the common sub-harmonic of a
|
||||
// two-note pluck. Choosing the first qualifying dip rejects those at the
|
||||
// source. We still track the global minimum as a fallback for signals where
|
||||
// nothing crosses the threshold.
|
||||
let tau = -1;
|
||||
let minVal = 1, minTau = -1;
|
||||
for (let t = 2; t < halfLen; t++) {
|
||||
if (yinBuffer[t] < minVal) { minVal = yinBuffer[t]; minTau = t; }
|
||||
if (yinBuffer[t] < threshold) {
|
||||
// Descend to the bottom of this first qualifying dip, keeping the
|
||||
// global-min tracker current for the indices we skip (otherwise the
|
||||
// fallback below could interpolate around a non-minimum).
|
||||
while (t + 1 < halfLen && yinBuffer[t + 1] < yinBuffer[t]) {
|
||||
t++;
|
||||
if (yinBuffer[t] < minVal) { minVal = yinBuffer[t]; minTau = t; }
|
||||
}
|
||||
tau = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tau === -1) {
|
||||
// Nothing crossed the threshold — fall back to the global minimum so a
|
||||
// weak but periodic signal still yields an estimate (confidence will be
|
||||
// correspondingly low and is filtered downstream).
|
||||
if (minTau === -1) return { freq: 0, confidence: 0, rms };
|
||||
tau = minTau;
|
||||
}
|
||||
|
||||
const s0 = yinBuffer[tau - 1];
|
||||
const s1 = yinBuffer[tau];
|
||||
const s2 = tau + 1 < halfLen ? yinBuffer[tau + 1] : yinBuffer[tau];
|
||||
const denom = s0 - 2 * s1 + s2;
|
||||
let betterTau = denom === 0 ? tau : tau + (s0 - s2) / (2 * denom);
|
||||
// A near-zero (but nonzero) denom can fling the parabolic estimate far
|
||||
// outside the bracket; the true minimum is within ±1 sample of tau. The
|
||||
// negated test also rejects NaN.
|
||||
if (!(betterTau >= tau - 1 && betterTau <= tau + 1)) betterTau = tau;
|
||||
|
||||
return { freq: sampleRate / betterTau, confidence: 1 - yinBuffer[tau], rms };
|
||||
}
|
||||
|
||||
// Allow direct import in Node.js test environments; harmless in browser workers
|
||||
// where the `module` global is undefined.
|
||||
if (typeof module !== 'undefined') module.exports = { _yinDetect };
|
||||
Reference in New Issue
Block a user