From 7c39ec3c2bf47c9636626efc45ddbce50a4464cc Mon Sep 17 00:00:00 2001 From: Aaron Gutierrez Date: Mon, 27 Dec 2021 18:01:57 -0800 Subject: [PATCH] Polish - show FFT bucket-width in cents - visual polish - up FFT size to max --- tuner.css | 7 ++++--- tuner.html | 8 ++++---- tuner.js | 53 +++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/tuner.css b/tuner.css index 4ab0563..aa85bbb 100644 --- a/tuner.css +++ b/tuner.css @@ -5,7 +5,8 @@ body { color: white; display: flex; flex-direction: column; - font-size: 48px; + font-family: sans-serif; + font-size: 2em; height: 100vh; margin: 0; overflow: hidden; @@ -28,10 +29,10 @@ h1, h2 { } h1 { - font-size: 96px; + font-size: 6em; } h2 { - font-size: 72px; + font-size: 3em; } diff --git a/tuner.html b/tuner.html index 9b69b59..6180ca0 100644 --- a/tuner.html +++ b/tuner.html @@ -3,11 +3,11 @@ + Tuner -

Tuner

-
Listening...
-
?? kHz
-

A+

+

Listening...

+
+
(?? kHz sample rate)
diff --git a/tuner.js b/tuner.js index ee89877..e5b4eb7 100644 --- a/tuner.js +++ b/tuner.js @@ -1,7 +1,5 @@ const NOTE_NAMES = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"] -const TIMESLICE_MS = 500; - // We don't care about fundamenatls above 4kHz, so setting a lower sample rate // gives us finer-graned FFT buckets const TARGET_SAMPLE_RATE = 8000; @@ -11,6 +9,7 @@ let dom_rate; let dom_note; let dom_tune; + const setup = () => { dom_frequency = document.getElementById("frequency"); dom_rate = document.getElementById("rate"); @@ -24,27 +23,39 @@ const setup = () => { console.error("Error calling getUserMedia", err); }); }; + + if (navigator.wakeLock && navigator.wakeLock.request) { + try { + navigator.wakeLock + .request('screen') + .then(wakeLock => + setTimeout(() => wakeLock.release(), 60000) + ); + } catch (err) {} + } }; + const handleStream = stream => { const audioContext = new AudioContext({ sampleRate: TARGET_SAMPLE_RATE, }); const analyser = audioContext.createAnalyser(); - analyser.fftSize = 8192; + analyser.fftSize = 32768; analyser.minDecibels = -90; analyser.maxDecibels = -10; - analyser.smoothingTimeConstant = 0.1; + analyser.smoothingTimeConstant = 0; const bufferLength = analyser.frequencyBinCount; const data = new Uint8Array(bufferLength); const source = audioContext.createMediaStreamSource(stream); source.connect(analyser); - setInterval(tune(analyser, data), 200); + setInterval(tune(analyser, data), 500); }; + const tune = (analyser, data) => () => { analyser.getByteFrequencyData(data); @@ -55,8 +66,6 @@ const tune = (analyser, data) => () => { let max = 0; let maxBucket = -1; - - data.forEach((value, bucket) => { if (value > max) { max = value; @@ -64,18 +73,26 @@ const tune = (analyser, data) => () => { } }); + if (maxBucket === -1) { + return; + } + const frequency = maxBucket * bucketWidth; - dom_frequency.innerText = `${frequency} Hz`; + dom_frequency.innerText = `${Number.parseFloat(frequency).toFixed(2)} Hz`; const semitones = frequencyToSemitones(frequency); + const margin = frequencyToSemitones(frequency + bucketWidth / 2) - semitones; + dom_note.innerText = semitonesToNote(semitones); - dom_tune.innerText = errorPercentage(semitones); - document.body.className = semitonesToClassname(semitones); + dom_tune.innerText = errorPercentage(semitones, margin); + document.body.className = semitonesToClassname(semitones, margin); }; + const frequencyToSemitones = frequency => 12 * Math.log2(frequency / 440) + 69; + const semitonesToNote = semitones => { const rounded = Math.round(semitones - 69); @@ -86,17 +103,25 @@ const semitonesToNote = semitones => { return NOTE_NAMES[index]; } -const errorPercentage = semitones => { + +const errorPercentage = (semitones, margin) => { const rounded = Math.round(semitones); - return Math.round((semitones - rounded ) * 100) + "%"; + const cents = Math.round((semitones - rounded) * 100); + const accuracy = Number.parseFloat(margin * 100).toFixed(1); + const sign = cents > 0 ? "+" : "" + + return `${sign}${cents} cents ± ${accuracy}`; } -const semitonesToClassname = semitones => { + +const semitonesToClassname = (semitones, margin) => { const rounded = Math.round(semitones); const error = Math.abs(semitones-rounded); - if (error <= 0.05) { + const ok = margin > 0.05 ? margin : 0.05 + + if (error <= ok) { return ""; }