{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { PitchDetector } from 'pitchy'\n\nconst semitone = 69\nconst noteStrings = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B']\n\nexport interface TunerConfig {\n  a4?: number\n  clarityThreshold?: number\n  minVolumeDecibels?: number\n  bufferSize?: number\n  smoothingTimeConstant?: number\n  minFrequency?: number\n  maxFrequency?: number\n  updateInterval?: number\n  sampleRate?: number\n  onNote?: (note: Note) => void\n}\n\nexport const TunerDefaults: TunerConfig = {\n  a4: 440,\n  clarityThreshold: 0.9,\n  minVolumeDecibels: -1000,\n  bufferSize: 8192,\n  smoothingTimeConstant: 0.8,\n  minFrequency: 27.5, // A0, Lowest note on a piano\n  maxFrequency: 4186.01, // C8, Highest note on a piano\n  updateInterval: 50,\n  sampleRate: 44100, // Seems to work better than 48000 for some reason\n}\n\nexport interface Note {\n  name: string\n  value: number\n  cents: number\n  octave: number\n  frequency: number\n  clarity: number\n}\n\nexport function createTuner(config: TunerConfig = {}) {\n  config = { ...TunerDefaults, ...config }\n\n  const context = new AudioContext({ sampleRate: config.sampleRate })\n  const highpass = new BiquadFilterNode(context, { type: \"highpass\", frequency: config.minFrequency })\n  const lowpass = new BiquadFilterNode(context, { type: \"lowpass\", frequency: config.maxFrequency })\n  const analyser = new AnalyserNode(context, { fftSize: config.bufferSize, smoothingTimeConstant: config.smoothingTimeConstant })\n  lowpass.connect(highpass).connect(analyser)\n\n  const detector = PitchDetector.forFloat32Array(analyser.fftSize)\n  detector.minVolumeDecibels = config.minVolumeDecibels!;\n  const inputBuffer = new Float32Array(detector.inputLength)\n\n  let stream: MediaStream\n  let source: MediaStreamAudioSourceNode\n  let interval = 0;\n\n  async function start () {\n    stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n    source = context.createMediaStreamSource(stream)\n    source.connect(lowpass)\n\n    interval = setInterval(process, config.updateInterval!)\n  }\n\n  function process() {\n    const { clarityThreshold, minFrequency, maxFrequency, onNote } = config\n    analyser.getFloatTimeDomainData(inputBuffer);\n    const [frequency, clarity] = detector.findPitch(inputBuffer, context.sampleRate)\n\n    if (clarity > clarityThreshold! && frequency > minFrequency! && frequency < maxFrequency!) {\n      onNote?.(getNote(frequency, clarity))\n    }\n  }\n\n  function getNote (frequency: number, clarity?: number) {\n    const value = Math.round(12 * (Math.log(frequency / config.a4!) / Math.log(2))) + semitone\n    const standardFrequency = config.a4! * Math.pow(2, (value - semitone) / 12)\n    const cents = Math.floor((1200 * Math.log(frequency / standardFrequency)) / Math.log(2))\n    const name = noteStrings[value % 12]\n    const octave: number = Math.floor(value / 12) - 1\n    return { frequency, name, value, cents, octave, clarity } as Note\n  }\n\n  async function stop() {\n    clearInterval(interval)\n    stream.getTracks().forEach(track => track.stop())\n    stream.removeTrack(stream.getAudioTracks()[0])\n  }\n\n  return { start, stop, context, analyser, detector, config, getNote }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,EAAA,gBAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAA8B,kBAExBC,EAAW,GACXC,EAAc,CAAC,IAAK,UAAM,IAAK,UAAM,IAAK,IAAK,UAAM,IAAK,UAAM,IAAK,UAAM,GAAG,EAevEL,EAA6B,CACxC,GAAI,IACJ,iBAAkB,GAClB,kBAAmB,KACnB,WAAY,KACZ,sBAAuB,GACvB,aAAc,KACd,aAAc,QACd,eAAgB,GAChB,WAAY,KACd,EAWO,SAASC,EAAYK,EAAsB,CAAC,EAAG,CACpDA,EAAS,CAAE,GAAGN,EAAe,GAAGM,CAAO,EAEvC,IAAMC,EAAU,IAAI,aAAa,CAAE,WAAYD,EAAO,UAAW,CAAC,EAC5DE,EAAW,IAAI,iBAAiBD,EAAS,CAAE,KAAM,WAAY,UAAWD,EAAO,YAAa,CAAC,EAC7FG,EAAU,IAAI,iBAAiBF,EAAS,CAAE,KAAM,UAAW,UAAWD,EAAO,YAAa,CAAC,EAC3FI,EAAW,IAAI,aAAaH,EAAS,CAAE,QAASD,EAAO,WAAY,sBAAuBA,EAAO,qBAAsB,CAAC,EAC9HG,EAAQ,QAAQD,CAAQ,EAAE,QAAQE,CAAQ,EAE1C,IAAMC,EAAW,gBAAc,gBAAgBD,EAAS,OAAO,EAC/DC,EAAS,kBAAoBL,EAAO,kBACpC,IAAMM,EAAc,IAAI,aAAaD,EAAS,WAAW,EAErDE,EACAC,EACAC,EAAW,EAEf,eAAeC,GAAS,CACtBH,EAAS,MAAM,UAAU,aAAa,aAAa,CAAE,MAAO,EAAK,CAAC,EAClEC,EAASP,EAAQ,wBAAwBM,CAAM,EAC/CC,EAAO,QAAQL,CAAO,EAEtBM,EAAW,YAAYE,EAASX,EAAO,cAAe,CACxD,CAEA,SAASW,GAAU,CACjB,GAAM,CAAE,iBAAAC,EAAkB,aAAAC,EAAc,aAAAC,EAAc,OAAAC,CAAO,EAAIf,EACjEI,EAAS,uBAAuBE,CAAW,EAC3C,GAAM,CAACU,EAAWC,CAAO,EAAIZ,EAAS,UAAUC,EAAaL,EAAQ,UAAU,EAE3EgB,EAAUL,GAAqBI,EAAYH,GAAiBG,EAAYF,GAC1EC,IAASG,EAAQF,EAAWC,CAAO,CAAC,CAExC,CAEA,SAASC,EAASF,EAAmBC,EAAkB,CACrD,IAAME,EAAQ,KAAK,MAAM,IAAM,KAAK,IAAIH,EAAYhB,EAAO,EAAG,EAAI,KAAK,IAAI,CAAC,EAAE,EAAIF,EAC5EsB,EAAoBpB,EAAO,GAAM,KAAK,IAAI,GAAImB,EAAQrB,GAAY,EAAE,EACpEuB,EAAQ,KAAK,MAAO,KAAO,KAAK,IAAIL,EAAYI,CAAiB,EAAK,KAAK,IAAI,CAAC,CAAC,EACjFE,EAAOvB,EAAYoB,EAAQ,EAAE,EAC7BI,EAAiB,KAAK,MAAMJ,EAAQ,EAAE,EAAI,EAChD,MAAO,CAAE,UAAAH,EAAW,KAAAM,EAAM,MAAAH,EAAO,MAAAE,EAAO,OAAAE,EAAQ,QAAAN,CAAQ,CAC1D,CAEA,eAAeO,GAAO,CACpB,cAAcf,CAAQ,EACtBF,EAAO,UAAU,EAAE,QAAQkB,GAASA,EAAM,KAAK,CAAC,EAChDlB,EAAO,YAAYA,EAAO,eAAe,EAAE,CAAC,CAAC,CAC/C,CAEA,MAAO,CAAE,MAAAG,EAAO,KAAAc,EAAM,QAAAvB,EAAS,SAAAG,EAAU,SAAAC,EAAU,OAAAL,EAAQ,QAAAkB,CAAQ,CACrE","names":["src_exports","__export","TunerDefaults","createTuner","__toCommonJS","import_pitchy","semitone","noteStrings","config","context","highpass","lowpass","analyser","detector","inputBuffer","stream","source","interval","start","process","clarityThreshold","minFrequency","maxFrequency","onNote","frequency","clarity","getNote","value","standardFrequency","cents","name","octave","stop","track"]}