Oh MyUtils

Metronome - Tap Tempo BPM Detector & Click Track Online

Detect BPM by tapping along to music, play a precision click track with adjustable tempo, time signatures, subdivisions, and visual beat indicator — 100% client-side using Web Audio API.

Protect your ears. Start at a low volume level.

Tap to start...

100% Client-Side — All audio is generated in your browser, no data sent to server

Frequently Asked Questions

What is a metronome and what is it used for?

A metronome is a device or tool that produces steady clicks or beats at a configurable tempo, measured in beats per minute (BPM). Musicians use metronomes to develop consistent timing, practice at specific tempos, and gradually increase speed while maintaining rhythmic accuracy. This online metronome also includes a tap tempo feature that lets you detect the BPM of any song by tapping along with the beat.

How do I use the tap tempo feature?

Tap the large TAP button repeatedly in time with the music you are listening to. After 2 or more taps, the tool calculates the average interval between your taps and displays the detected BPM. For best accuracy, tap along for 4-8 beats following the kick drum or main pulse. The stability indicator shows how consistent your tapping is. Once you have a BPM reading, click Use this BPM to transfer it directly to the metronome for practice.

How do I use the metronome click track?

Switch to the Metronome tab, set your desired BPM using the slider or numeric input, select a time signature (e.g., 4/4 for common time), and press Play. The metronome produces audible clicks with an accented downbeat on beat 1. Watch the visual beat indicator to see the current position within the measure. Adjust volume, subdivision, and click sound to suit your practice needs.

Is my data sent to a server?

No. This tool is 100% client-side and uses the Web Audio API built into your browser. All audio synthesis, tempo detection, and beat scheduling happen entirely on your device. No audio data, BPM readings, or settings are ever transmitted to a server. The tool works fully offline after the initial page load.

Why is precise timing important and how does this metronome achieve it?

Musical practice requires timing accuracy within a few milliseconds. JavaScript's built-in setTimeout/setInterval are not precise enough on their own because the main thread can be delayed by other tasks. This metronome uses the industry-standard lookahead scheduler pattern: a setInterval timer fires frequently and schedules audio events ahead of time using the Web Audio API's high-resolution AudioContext.currentTime clock. This ensures clicks are accurate to within 1-2 milliseconds with no drift, even during long practice sessions.

What are time signatures and which ones are supported?

A time signature indicates how many beats are in each measure and which note value gets one beat. This tool supports: 4/4 (common time, used in most pop/rock), 3/4 (waltz), 2/4 (march), 6/8 (compound duple, common in jigs), 5/4 (irregular, as in Take Five), and 7/8 (irregular, common in Balkan music). Each time signature has appropriate accent patterns to help you feel the rhythmic structure.

What is the difference between subdivisions?

Subdivisions divide each main beat into smaller units for more granular rhythmic practice. None plays only quarter-note beats. 8th Notes adds a click between each beat (2 per beat). Triplets divides each beat into 3 equal parts. 16th Notes divides each beat into 4 equal parts. Subdivision clicks are quieter than main beats to maintain the pulse hierarchy.

Code Examples

// Web Audio API Metronome with Lookahead Scheduler

class Metronome {
  constructor(bpm = 120, beatsPerMeasure = 4) {
    this.bpm = bpm;
    this.beatsPerMeasure = beatsPerMeasure;
    this.isPlaying = false;
    this.currentBeat = 0;
    this.nextNoteTime = 0;
    this.timerID = null;
    this.audioContext = null;
  }

  start() {
    if (this.isPlaying) return;
    this.audioContext = new AudioContext();
    this.isPlaying = true;
    this.currentBeat = 0;
    this.nextNoteTime = this.audioContext.currentTime;
    this.timerID = setInterval(() => this._scheduler(), 25);
  }

  stop() {
    this.isPlaying = false;
    if (this.timerID) clearInterval(this.timerID);
    if (this.audioContext) this.audioContext.close();
  }

  _scheduler() {
    while (this.nextNoteTime < this.audioContext.currentTime + 0.1) {
      this._playClick(this.nextNoteTime, this.currentBeat === 0);
      this.nextNoteTime += 60.0 / this.bpm;
      this.currentBeat = (this.currentBeat + 1) % this.beatsPerMeasure;
    }
  }

  _playClick(time, isAccent) {
    const osc = this.audioContext.createOscillator();
    const gain = this.audioContext.createGain();
    osc.frequency.value = isAccent ? 1000 : 800;
    gain.gain.setValueAtTime(isAccent ? 1.0 : 0.7, time);
    gain.gain.exponentialRampToValueAtTime(0.001, time + 0.03);
    osc.connect(gain);
    gain.connect(this.audioContext.destination);
    osc.start(time);
    osc.stop(time + 0.03);
  }
}

// Tap Tempo Detector
function createTapDetector(maxTaps = 8, resetMs = 2000) {
  const taps = [];
  let resetTimer = null;

  return {
    tap() {
      const now = performance.now();
      if (resetTimer) clearTimeout(resetTimer);
      resetTimer = setTimeout(() => { taps.length = 0; }, resetMs);

      taps.push(now);
      if (taps.length > maxTaps) taps.shift();
      if (taps.length < 2) return null;

      const intervals = [];
      for (let i = 1; i < taps.length; i++) {
        intervals.push(taps[i] - taps[i - 1]);
      }
      const avg = intervals.reduce((a, b) => a + b) / intervals.length;
      return Math.round(60000 / avg);
    },
    reset() {
      taps.length = 0;
    },
  };
}

// Usage
const metronome = new Metronome(120, 4);
metronome.start();
setTimeout(() => metronome.stop(), 5000);

Related Tools