DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

Building a Chromatic Tuner in the Browser with the Web Audio API

I play guitar and I used to carry a clip-on tuner. Then I realized that my phone has a microphone, a fast processor, and a screen. Everything a tuner needs. The browser has the Web Audio API, which gives JavaScript direct access to the microphone and real-time audio analysis. Building a chromatic tuner is one of the most satisfying browser projects because the feedback loop is instant and physical.

How pitch detection works

A chromatic tuner does one thing: listen to a sound and determine its fundamental frequency in Hz. The mapping from frequency to note is:

A4 = 440 Hz
Each semitone up:   frequency * 2^(1/12)
Each semitone down: frequency * 2^(-1/12)
Each octave up:     frequency * 2
Enter fullscreen mode Exit fullscreen mode

The 12th root of 2 (approximately 1.05946) is the frequency ratio between adjacent semitones in equal temperament tuning. This means:

A4  = 440.00 Hz
A#4 = 466.16 Hz
B4  = 493.88 Hz
C5  = 523.25 Hz
...
Enter fullscreen mode Exit fullscreen mode

To determine which note a frequency corresponds to:

function frequencyToNote(freq) {
  const semitones = 12 * Math.log2(freq / 440);
  const nearestSemitone = Math.round(semitones);
  const centsOff = (semitones - nearestSemitone) * 100;
  const noteNames = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];
  const noteIndex = ((nearestSemitone % 12) + 12) % 12;
  return { note: noteNames[noteIndex], cents: centsOff };
}
Enter fullscreen mode Exit fullscreen mode

Cents measure how far off-pitch you are. 100 cents = 1 semitone. A value of +10 cents means slightly sharp. -10 cents means slightly flat. Most musicians can hear differences of about 5 cents.

The autocorrelation method

The hardest part of a tuner is not the note mapping. It is extracting the fundamental frequency from raw audio data. The standard approach is autocorrelation.

Autocorrelation compares a signal with a time-shifted copy of itself. When the shift equals one period of the fundamental frequency, the correlation peaks. By finding the first peak after the zero-lag peak, you determine the period, and from the period, the frequency.

function autoCorrelate(buffer, sampleRate) {
  let bestOffset = -1;
  let bestCorrelation = 0;
  let foundGoodCorrelation = false;

  for (let offset = 1; offset < buffer.length / 2; offset++) {
    let correlation = 0;
    for (let i = 0; i < buffer.length / 2; i++) {
      correlation += buffer[i] * buffer[i + offset];
    }
    correlation /= buffer.length / 2;

    if (correlation > 0.9 && correlation > bestCorrelation) {
      bestCorrelation = correlation;
      bestOffset = offset;
      foundGoodCorrelation = true;
    }
  }

  if (!foundGoodCorrelation) return -1;
  return sampleRate / bestOffset;
}
Enter fullscreen mode Exit fullscreen mode

This basic version works but lacks precision. Refinements include parabolic interpolation around the peak (to get sub-sample accuracy) and windowing the input buffer (to reduce spectral leakage).

The Web Audio API pipeline

const audioContext = new AudioContext();
const analyser = audioContext.createAnalyser();
analyser.fftSize = 4096;

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);

const buffer = new Float32Array(analyser.fftSize);

function detect() {
  analyser.getFloatTimeDomainData(buffer);
  const frequency = autoCorrelate(buffer, audioContext.sampleRate);
  if (frequency > 0) {
    const { note, cents } = frequencyToNote(frequency);
    updateDisplay(note, cents);
  }
  requestAnimationFrame(detect);
}
detect();
Enter fullscreen mode Exit fullscreen mode

The key settings: fftSize of 4096 at a 44.1kHz sample rate gives about 93ms of audio per frame. This is sufficient for detecting frequencies down to about 80Hz (low E on guitar is 82Hz). For bass instruments, increase the buffer size.

Why browser-based tuners work now

Five years ago, browser audio latency was too high for real-time tuning. The Web Audio API's AudioContext now runs on a separate thread with low-latency audio processing. On modern browsers, the latency from microphone input to visual feedback is under 50ms -- fast enough to feel instantaneous.

I built a chromatic tuner at zovo.one/free-tools/tuner that uses this exact pipeline. It detects pitch in real time, shows the nearest note, displays cents deviation, and provides a visual indicator for sharp/flat. Works with any instrument. No app download required.

I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.

Top comments (0)