From d2f38b67b66dd5050f1e337d425f3a6282b5b8f1 Mon Sep 17 00:00:00 2001 From: Matthew Huntington Date: Wed, 31 Jul 2024 05:30:03 +0200 Subject: [PATCH] analyze pitch --- analyze.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 10 +++++++ 2 files changed, 88 insertions(+) create mode 100644 analyze.js diff --git a/analyze.js b/analyze.js new file mode 100644 index 0000000..f3f4518 --- /dev/null +++ b/analyze.js @@ -0,0 +1,78 @@ +document.querySelector('#analyze-pitch button').addEventListener('click', startListening); + +function startListening() { + navigator.mediaDevices.getUserMedia({ audio: true, video: false }) + .then(stream => { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const analyser = audioContext.createAnalyser(); + const microphone = audioContext.createMediaStreamSource(stream); + const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1); + + analyser.smoothingTimeConstant = 0.3; + analyser.fftSize = 2048; + + microphone.connect(analyser); + analyser.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = () => { + const buffer = new Float32Array(analyser.fftSize); + analyser.getFloatTimeDomainData(buffer); + const pitch = autoCorrelate(buffer, audioContext.sampleRate); + document.querySelector('#analyze-pitch dd').textContent = pitch.toFixed(2); + }; + }) + .catch(err => { + console.error('Error accessing microphone: ' + err); + }); +} + +function autoCorrelate(buffer, sampleRate) { + const SIZE = buffer.length; + const MAX_SAMPLES = Math.floor(SIZE / 2); + const MIN_SAMPLES = 0; + const GOOD_ENOUGH_CORRELATION = 0.9; + + let bestOffset = -1; + let bestCorrelation = 0; + let rms = 0; + let foundGoodCorrelation = false; + let correlations = new Array(MAX_SAMPLES); + + for (let i = 0; i < SIZE; i++) { + const val = buffer[i]; + rms += val * val; + } + rms = Math.sqrt(rms / SIZE); + + if (rms < 0.01) // not enough signal + return -1; + + let lastCorrelation = 1; + for (let offset = MIN_SAMPLES; offset < MAX_SAMPLES; offset++) { + let correlation = 0; + + for (let i = 0; i < MAX_SAMPLES; i++) { + correlation += Math.abs((buffer[i]) - (buffer[i + offset])); + } + correlation = 1 - (correlation / MAX_SAMPLES); + correlations[offset] = correlation; + + if ((correlation > GOOD_ENOUGH_CORRELATION) && (correlation > lastCorrelation)) { + foundGoodCorrelation = true; + if (correlation > bestCorrelation) { + bestCorrelation = correlation; + bestOffset = offset; + } + } else if (foundGoodCorrelation) { + const shift = (correlations[bestOffset + 1] - correlations[bestOffset - 1]) / correlations[bestOffset]; + return sampleRate / (bestOffset + (8 * shift)); + } + lastCorrelation = correlation; + } + if (bestCorrelation > 0.01) { + return sampleRate / bestOffset; + } + return -1; +} + diff --git a/index.html b/index.html index 13e3df3..9ed7390 100644 --- a/index.html +++ b/index.html @@ -5,10 +5,12 @@ +

Tuner

+

Tone Generator

Tone:
@@ -17,5 +19,13 @@
+
+

Pitch Analyzer

+ +
+
Pitch:
+
+
+