How Accurate Is Your Metronome App? The Engineering Behind the Click

The question "is Google's metronome accurate enough?" comes up surprisingly often. Millions of musicians use their phone's built-in tools and reasonably wonder whether a free, general-purpose metronome is precise enough for serious practice. The answer turns on a single detail that is invisible to users: who is in charge of the timing?

When you set a metronome to 120BPM you expect it to click 120 times a minute, every minute, without variation. What you actually get depends entirely on the engineering underneath. There are two fundamentally different approaches to metronome timing on a phone, and the difference between them is audible at fast tempos.

What "Accurate" Means for a Metronome

Accuracy in a metronome has two distinct components, and it is worth separating them because they affect your playing differently.

Drift is the average BPM wandering from the target over time. A metronome set to 120BPM that actually averages 119.8BPM over a long session is drifting. Drift is real but relatively rare in modern software. Any app that uses the system clock as its reference point will avoid significant drift, because system clocks are accurate to well under a millisecond per second.

Jitter is individual beats landing slightly off the expected instant, some 3ms early, some 5ms late, with no pattern. At 120BPM, a beat lands every 500ms. Jitter of 10ms is 2% of that interval. Research on rhythm perception places most musicians' detection threshold at roughly 5 to 10ms in a familiar tempo, and that threshold drops lower for trained listeners or at faster tempos. Consistent jitter at 160BPM makes the click feel fuzzy or elastic, even when the average tempo is technically correct. This is the problem that matters in practice.

How Timer-Based Metronomes Work (and Why Jitter Happens)

The simplest way to implement a metronome is a repeating timer callback: schedule a function to run every N milliseconds, play a sound when it fires. Android provides several mechanisms for this, including Handler.postDelayed() and scheduled executors. The problem is that these callbacks are dispatched by the operating system's scheduler, not by audio hardware.

Between the moment a callback is scheduled and the moment it actually fires, the OS may be handling an interrupt, running a garbage collection pass, processing a network event, or drawing a frame to the screen. These are not pathological scenarios. They happen on every phone, continuously, as normal housekeeping. Even on an idle device with nothing else running, scheduling jitter of 5 to 20ms is routine for software timers. On a busy phone with background apps active, it can be considerably worse. The click fires when the scheduler gets around to it, not when the beat was supposed to land.

The root problem: software timer callbacks are scheduled by the operating system, which shares its attention across everything running on the phone. A beat that was supposed to land at exactly 500ms may land at 507ms, or 493ms, with no warning and no recourse from the app's side.

AudioTrack STREAM Mode: Letting the Hardware Drive

Metro Gnome's metronome engine takes a different approach. Instead of scheduling timer callbacks, it uses Android's AudioTrack API in STREAM mode and runs a blocking write loop.

The loop works like this: build an audio buffer containing exactly one beat's worth of samples (the click transient followed by silence), hand that buffer to AudioTrack.write(), and wait. The write() call is blocking in STREAM mode. It does not return until the hardware audio ring buffer has accepted the entire buffer. That acceptance is gated by the audio hardware clock, not the OS scheduler. The hardware plays audio at exactly 44,100 samples per second, driven by a crystal oscillator. Nothing on the OS side can change that rate.

At 44,100Hz and 120BPM, one beat is exactly 22,050 samples. The engine computes this as the integer value of (44100 times 60.0 divided by bpm). Those 22,050 samples take exactly 500.000ms to play. Not approximately. Not on average. Exactly, because the crystal running the audio DAC is the reference, and it does not yield to the OS scheduler. When write() returns and the loop begins building the next beat's buffer, it does so with that guarantee already satisfied by the hardware.

The result is that timing jitter is reduced to the order of the audio hardware latency, which is fixed and typically under 30ms on modern Android devices. More importantly, it does not accumulate and does not vary randomly. The click you hear at beat 100 is spaced just as precisely from beat 99 as beat 2 was from beat 1.

The onBeat Timing Decision

There is a subtle engineering point built into how the visual animations stay in sync with the click you hear. AudioTrack.write() in STREAM mode blocks for approximately one full beat of wall-clock time while the hardware consumes the previous buffer. If the code called onBeat() after write(), the UI animation would fire almost one full beat too late relative to the sound.

The solution is to call onBeat() before write(). The visual callback fires at the moment the buffer is queued, which is the same moment the previous beat's audio begins being consumed from the hardware ring buffer. The audio hardware introduces a fixed latency of around 23ms between queueing audio and it arriving at the speaker. A Compose animation frame takes around 16ms to render. Those two figures are close enough that the bouncing gnome and the click you hear land within one display frame of each other. The 23ms hardware latency is the same on every beat; it does not introduce jitter.

Pre-Generated Click Sounds

Click synthesis happens once, at app startup, not on each beat. At initialisation, the engine generates eight different click buffers covering all sound types: a standard click at 1,100Hz lasting 38ms, an accent click at 1,800Hz lasting 50ms, hi-hat variants at 9,000Hz, woodblock variants at 600Hz and 800Hz, and a warm click at 350Hz lasting 130ms. The warm click includes three early reflections at delays of 28ms, 52ms, and 80ms at decreasing amplitudes, simulating room reverb without a convolution reverb unit.

Pre-generating these buffers means the audio thread has zero CPU work on the beat itself. At the start of each beat loop iteration, it selects the appropriate pre-built buffer and copies it into the output. No floating-point synthesis, no memory allocation, no garbage collector pressure during the write. The thread does exactly one thing on the critical path: write data the hardware is waiting for.

Thread Isolation

The entire write loop runs on a Kotlin coroutine backed by Dispatchers.Default, a worker thread pool that is separate from the UI thread. The UI thread handles touch events, screen rendering, and animations. A slow frame caused by a complex layout or a garbage collection pause cannot delay the audio write loop, because they run on different threads. The audio thread does not draw anything and the UI thread does not write audio. The isolation is total.

This matters at fast tempos and during heavy UI activity. Scrolling through settings, opening dialogs, triggering animations: none of these compete with the audio thread for CPU time in any way that could introduce a missed beat.

Is Google's Metronome Accurate Enough?

Google's built-in metronome, accessible by searching "metronome" in Google Search or Chrome, is a well-made tool for its purpose. Its timing is driven by the browser's Web Audio API scheduler, which is better than a raw JavaScript timer but is still subject to the OS scheduling constraints described above. For keeping a rehearsal from speeding up, counting bars, or having a reference click in the background while you record on another device, it works perfectly well.

For precision practice at fast tempos, for training a tight internal pulse, or for work where 10ms of jitter is perceptible, an audio-clock-anchored engine makes a measurable difference. Not every metronome app makes this distinction clear, and some that appear to use audio hardware still introduce jitter through their architecture. The test is simple: at 200BPM, does the click feel like a machine or does it feel elastic?

The engineering described here is not a feature you will find in a settings menu. It is the foundation underneath everything else. A precise click is not just nicer to practise with. It is the only kind that gives your internal sense of time something true to lock onto.

Metro Gnome: Free Metronome, Tuner and Speed Trainer

A sample-accurate metronome, a smart chromatic tuner, a structured Speed Trainer, and Groove Check mic timing feedback. Free on Android, no subscription ever.

Get it Free on Google Play