{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * Copyright (C) 2020 Online Mic Test\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published\n * by the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n * @license\n */\nconsole.log('Licensed under AGPL-3.0: https://github.com/onlinemictest/pitch-detector')\n\ntype NoteString = 'C' | 'C#' | 'D' | 'D#' | 'E' | 'F' | 'F#' | 'G' | 'G#' | 'A' | 'A#' | 'B';\n\nconst middleA = 440;\n\nconst SEMI_TONE = 69;\nconst WHEEL_NOTES = 24;\nconst BUFFER_SIZE = 4096;\nconst NOTE_STRINGS: NoteString[] = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];\n\nconst toggleClass = (element: HTMLElement, ...cls: string[]) => {\n element.classList.remove(...cls);\n\n // Force layout reflow\n void element.offsetWidth;\n\n element.classList.add(...cls);\n};\n\nfunction initGetUserMedia() {\n // @ts-ignore\n window.AudioContext = window.AudioContext || window.webkitAudioContext\n if (!window.AudioContext) {\n return alert('AudioContext not supported')\n }\n\n // Older browsers might not implement mediaDevices at all, so we set an empty object first\n if (navigator.mediaDevices === undefined) {\n // @ts-ignore\n navigator.mediaDevices = {}\n }\n\n // Some browsers partially implement mediaDevices. We can't just assign an object\n // with getUserMedia as it would overwrite existing properties.\n // Here, we will just add the getUserMedia property if it's missing.\n if (navigator.mediaDevices.getUserMedia === undefined) {\n navigator.mediaDevices.getUserMedia = function (constraints) {\n // First get ahold of the legacy getUserMedia, if present\n // @ts-ignore\n const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia\n\n // Some browsers just don't implement it - return a rejected promise with an error\n // to keep a consistent interface\n if (!getUserMedia) {\n alert('getUserMedia is not implemented in this browser')\n }\n\n // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise\n return new Promise(function (resolve, reject) {\n getUserMedia.call(navigator, constraints, resolve, reject)\n })\n }\n }\n}\n\ninterface Note {\n value: number,\n index: number,\n name: NoteString\n cents: number\n octave: number,\n frequency: number,\n}\n\nfunction getNote(frequency: number): Note {\n const noteIndex = getNoteIndex(frequency);\n return {\n value: noteIndex % 12,\n index: noteIndex,\n name: NOTE_STRINGS[noteIndex % 12],\n cents: getCents(frequency, noteIndex),\n octave: Math.trunc(noteIndex / 12) - 1,\n frequency: frequency,\n };\n}\n\nfunction groupBy(f: (x: X) => K) {\n return function (xs: Iterable): Map {\n const res = new Map();\n for (const x of xs) {\n const key = f(x);\n const group = res.get(key) || [];\n res.set(key, [...group, x]);\n }\n return res;\n };\n}\n\n/**\n * Get musical note from frequency\n */\nfunction getNoteIndex(frequency: number) {\n const note = 12 * (Math.log(frequency / middleA) / Math.log(2))\n return Math.round(note) + SEMI_TONE\n}\n\n/**\n * Get the musical note's standard frequency\n */\nfunction getStandardFrequency(note: number) {\n return middleA * Math.pow(2, (note - SEMI_TONE) / 12)\n}\n\n/**\n * Get cents difference between given frequency and musical note's standard frequency\n */\nfunction getCents(frequency: number, note: number) {\n return Math.floor((1200 * Math.log(frequency / getStandardFrequency(note))) / Math.log(2));\n}\n\n// @ts-expect-error\nAubio().then(({ Pitch }) => {\n initGetUserMedia();\n\n if (\n !('WebAssembly' in window) ||\n !('AudioContext' in window) ||\n !('createAnalyser' in AudioContext.prototype) ||\n !('createScriptProcessor' in AudioContext.prototype) ||\n !('trunc' in Math)\n ) {\n return alert('Browser not supported')\n }\n\n const wheel = document.getElementById('pitch-wheel')?.querySelector('svg') as SVGElement | null;\n const freqSpan = document.getElementById('pitch-freq')?.querySelector('.freq') as HTMLElement | null;\n const noteSpan = document.getElementById('pitch-freq')?.querySelector('.note') as HTMLElement | null;\n const octaveSpan = document.getElementById('pitch-freq')?.querySelector('.octave') as HTMLElement | null;\n const startEl = document.getElementById('audio-start') as HTMLButtonElement | null;\n const pauseEl = document.getElementById('audio-pause') as HTMLButtonElement | null;\n const freqTextEl = document.getElementById('pitch-freq-text') as HTMLElement | null;\n const block2 = document.querySelector('.audio-block-2') as HTMLElement | null;\n if (!wheel || !freqSpan || !noteSpan || !octaveSpan || !startEl || !pauseEl || !freqTextEl) return;\n\n let audioContext: AudioContext;\n let analyser: AnalyserNode;\n let scriptProcessor: ScriptProcessorNode;\n let pitchDetector: Aubio.Pitch;\n // let stream: MediaStream;\n\n pauseEl.addEventListener('click', () => {\n scriptProcessor.disconnect(audioContext.destination);\n analyser.disconnect(scriptProcessor);\n audioContext.close();\n // stream.getTracks().forEach(track => track.stop());\n startEl.style.display = 'block';\n pauseEl.style.display = 'none';\n freqTextEl.style.display = 'none';\n if (block2) block2.style.display = 'block';\n toggleClass(startEl, 'blob-animation');\n })\n\n startEl.addEventListener('click', () => {\n audioContext = new AudioContext();\n analyser = audioContext.createAnalyser();\n scriptProcessor = audioContext.createScriptProcessor(BUFFER_SIZE, 1, 1);\n pitchDetector = new Pitch('default', BUFFER_SIZE, 1, audioContext.sampleRate);\n\n navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {\n // stream = s;\n audioContext.createMediaStreamSource(stream).connect(analyser);\n analyser.connect(scriptProcessor);\n scriptProcessor.connect(audioContext.destination);\n\n startEl.style.display = 'none';\n pauseEl.style.display = 'block';\n freqTextEl.style.display = 'block';\n if (block2) block2.style.display = 'none';\n toggleClass(pauseEl, 'shrink-animation');\n\n let prevDeg = 0;\n\n scriptProcessor.addEventListener('audioprocess', event => {\n const frequency = pitchDetector.do(event.inputBuffer.getChannelData(0));\n const note = getNote(frequency);\n\n const unit = (360 / WHEEL_NOTES);\n const deg = note.index * unit + (note.cents / 100) * unit;\n\n if (note.name) {\n const degDiff = Math.trunc(Math.abs(prevDeg - deg));\n prevDeg = deg;\n const transformTime = (degDiff + 25) * 15;\n\n freqSpan.innerText = note.frequency.toFixed(1);\n noteSpan.innerText = note.name;\n octaveSpan.innerText = note.octave.toString();\n\n wheel.style.transition = `transform ${transformTime}ms ease`;\n wheel.style.transform = `rotate(-${deg}deg)`;\n }\n });\n });\n });\n});\n\n"],"names":["console","log","NOTE_STRINGS","toggleClass","element","_i","cls","_a","classList","remove","offsetWidth","_b","add","getNote","frequency","noteIndex","note","Math","round","getNoteIndex","value","index","name","cents","getCents","octave","trunc","floor","pow","getStandardFrequency","Aubio","then","Pitch","window","AudioContext","webkitAudioContext","alert","undefined","navigator","mediaDevices","getUserMedia","constraints","webkitGetUserMedia","mozGetUserMedia","Promise","resolve","reject","call","initGetUserMedia","prototype","audioContext","analyser","scriptProcessor","pitchDetector","wheel","document","getElementById","querySelector","freqSpan","noteSpan","octaveSpan","startEl","pauseEl","freqTextEl","block2","addEventListener","disconnect","destination","close","style","display","createAnalyser","createScriptProcessor","sampleRate","audio","stream","createMediaStreamSource","connect","prevDeg","event","do","inputBuffer","getChannelData","deg","degDiff","abs","transformTime","innerText","toFixed","toString","transition","transform"],"mappings":"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"}