format analyze file

master
Matthew Huntington 11 months ago
parent da730ccf8f
commit 4189a4cec6

Binary file not shown.

Binary file not shown.

@ -1,117 +1,117 @@
function startPitchDetection(callback) { function startPitchDetection(callback) {
// Check for browser support // Check for browser support
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error("getUserMedia is not supported in this browser."); console.error("getUserMedia is not supported in this browser.");
return; return;
} }
// Request access to the microphone // Request access to the microphone
//navigator.mediaDevices.getUserMedia({ audio: true }) //navigator.mediaDevices.getUserMedia({ audio: true })
navigator.mediaDevices.getUserMedia({ navigator.mediaDevices.getUserMedia({
audio: { audio: {
echoCancellation: false, echoCancellation: false,
noiseSuppression: false, noiseSuppression: false,
autoGainControl: false autoGainControl: false
} }
}) })
.then(stream => { .then(stream => {
// Create an audio context and an analyser node // Create an audio context and an analyser node
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser(); const analyser = audioContext.createAnalyser();
analyser.fftSize = 2048; // Set FFT size; higher values offer more precision but more latency analyser.fftSize = 2048; // Set FFT size; higher values offer more precision but more latency
// Connect the microphone stream to the analyser node // Connect the microphone stream to the analyser node
const source = audioContext.createMediaStreamSource(stream); const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser); source.connect(analyser);
// Create a buffer to hold the time domain data // Create a buffer to hold the time domain data
const bufferLength = analyser.fftSize; const bufferLength = analyser.fftSize;
const buffer = new Float32Array(bufferLength); const buffer = new Float32Array(bufferLength);
/** /**
* Autocorrelation algorithm to estimate pitch from the audio buffer. * Autocorrelation algorithm to estimate pitch from the audio buffer.
* Returns the estimated pitch in Hz, or -1 if no pitch is detected. * Returns the estimated pitch in Hz, or -1 if no pitch is detected.
*/ */
function autoCorrelate(buf, sampleRate) { function autoCorrelate(buf, sampleRate) {
const SIZE = buf.length; const SIZE = buf.length;
let rms = 0; let rms = 0;
// Compute Root Mean Square (RMS) to check if there's enough signal // Compute Root Mean Square (RMS) to check if there's enough signal
for (let i = 0; i < SIZE; i++) { for (let i = 0; i < SIZE; i++) {
const val = buf[i]; const val = buf[i];
rms += val * val; rms += val * val;
} }
rms = Math.sqrt(rms / SIZE); rms = Math.sqrt(rms / SIZE);
if (rms < 0.01) // Signal too weak likely silence if (rms < 0.01) // Signal too weak likely silence
return -1; return -1;
// Trim the buffer to remove noise at the beginning and end // Trim the buffer to remove noise at the beginning and end
let r1 = 0, r2 = SIZE - 1; let r1 = 0, r2 = SIZE - 1;
for (let i = 0; i < SIZE; i++) { for (let i = 0; i < SIZE; i++) {
if (Math.abs(buf[i]) < 0.2) { r1 = i; break; } if (Math.abs(buf[i]) < 0.2) { r1 = i; break; }
} }
for (let i = 1; i < SIZE; i++) { for (let i = 1; i < SIZE; i++) {
if (Math.abs(buf[SIZE - i]) < 0.2) { r2 = SIZE - i; break; } if (Math.abs(buf[SIZE - i]) < 0.2) { r2 = SIZE - i; break; }
} }
const trimmedBuffer = buf.slice(r1, r2); const trimmedBuffer = buf.slice(r1, r2);
const trimmedSize = trimmedBuffer.length; const trimmedSize = trimmedBuffer.length;
// Calculate the autocorrelation of the trimmed buffer // Calculate the autocorrelation of the trimmed buffer
const correlations = new Array(trimmedSize).fill(0); const correlations = new Array(trimmedSize).fill(0);
for (let lag = 0; lag < trimmedSize; lag++) { for (let lag = 0; lag < trimmedSize; lag++) {
for (let i = 0; i < trimmedSize - lag; i++) { for (let i = 0; i < trimmedSize - lag; i++) {
correlations[lag] += trimmedBuffer[i] * trimmedBuffer[i + lag]; correlations[lag] += trimmedBuffer[i] * trimmedBuffer[i + lag];
} }
} }
// Find the first dip in the autocorrelation skip lags before this point // Find the first dip in the autocorrelation skip lags before this point
let d = 0; let d = 0;
while (d < correlations.length - 1 && correlations[d] > correlations[d + 1]) { while (d < correlations.length - 1 && correlations[d] > correlations[d + 1]) {
d++; d++;
} }
// Search for the peak correlation after the dip // Search for the peak correlation after the dip
let maxVal = -1, maxPos = -1; let maxVal = -1, maxPos = -1;
for (let i = d; i < correlations.length; i++) { for (let i = d; i < correlations.length; i++) {
if (correlations[i] > maxVal) { if (correlations[i] > maxVal) {
maxVal = correlations[i]; maxVal = correlations[i];
maxPos = i; maxPos = i;
} }
} }
// Parabolic interpolation for a more accurate peak estimate // Parabolic interpolation for a more accurate peak estimate
let T0 = maxPos; let T0 = maxPos;
if (T0 > 0 && T0 < correlations.length - 1) { if (T0 > 0 && T0 < correlations.length - 1) {
const x1 = correlations[T0 - 1]; const x1 = correlations[T0 - 1];
const x2 = correlations[T0]; const x2 = correlations[T0];
const x3 = correlations[T0 + 1]; const x3 = correlations[T0 + 1];
const a = (x1 + x3 - 2 * x2) / 2; const a = (x1 + x3 - 2 * x2) / 2;
const b = (x3 - x1) / 2; const b = (x3 - x1) / 2;
if (a !== 0) { if (a !== 0) {
T0 = T0 - b / (2 * a); T0 = T0 - b / (2 * a);
} }
} }
// Convert lag to frequency // Convert lag to frequency
const pitch = sampleRate / T0; const pitch = sampleRate / T0;
return pitch; return pitch;
} }
// Continuously update and detect pitch // Continuously update and detect pitch
function updatePitch() { function updatePitch() {
// Get the latest time-domain data // Get the latest time-domain data
analyser.getFloatTimeDomainData(buffer); analyser.getFloatTimeDomainData(buffer);
// Estimate pitch using our autocorrelation function // Estimate pitch using our autocorrelation function
const pitch = autoCorrelate(buffer, audioContext.sampleRate); const pitch = autoCorrelate(buffer, audioContext.sampleRate);
// Pass the detected pitch (in Hz) to the provided callback // Pass the detected pitch (in Hz) to the provided callback
callback(pitch); callback(pitch);
// Continue the update loop // Continue the update loop
requestAnimationFrame(updatePitch); requestAnimationFrame(updatePitch);
} }
updatePitch(); updatePitch();
}) })
.catch(err => { .catch(err => {
console.error("Error accessing the microphone: ", err); console.error("Error accessing the microphone: ", err);
}); });
} }

Loading…
Cancel
Save