Updated October 2026
The homepage's "How it works" section gives the short version. This page walks through the actual signal chain in more detail — the same functions that run when you click "Start Listening," in the order they run, with the real numbers the code uses.
Raw microphone audio contains everything in the room: voices, HVAC rumble, traffic, footsteps — almost all of it low-frequency. A watch's tick, by contrast, is a short, sharp transient click with most of its energy well up in the audio spectrum. The tool runs the captured audio through a highpass filter at 2000Hz (a standard RBJ-cookbook biquad, the same math a Web Audio BiquadFilterNode uses internally, with Q=0.707 for a flat passband) before anything else happens. Everything below 2000Hz — which is most of what makes a room "noisy" to begin with — gets attenuated, leaving mostly the tick's own click energy to work with.
A raw audio waveform swings positive and negative dozens of times within a single tick, which isn't a useful shape to search for "the moment of the tick." The next stage, envelopeFollow(), rectifies the signal (takes the absolute value) and smooths it with an asymmetric one-pole filter: a very fast 0.5ms attack so the envelope jumps up almost instantly the moment a tick's energy arrives, and a slower 25ms release so it decays back down gradually rather than chattering. The result is a clean loudness contour with one clear bump per tick instead of a noisy oscillating waveform.
detectOnsets() then scans that envelope for peaks, but it doesn't use one fixed volume threshold for the whole recording — a fixed threshold would either miss quiet ticks or false-trigger on a noisy room, depending where you set it. Instead it maintains a trailing statistical window (0.3 seconds) of the envelope's own recent mean and standard deviation, and triggers when the envelope exceeds mean + 2.5 × stddev of that recent window — with a floor of at least 25% of the loudest value seen so far in the whole capture, so a run of unusually quiet background noise can't make the threshold collapse to near zero. This threshold adapts automatically as room noise and tick loudness vary through the capture.
Two more checks keep a single tick from being counted as multiple onsets. A 40ms refractory period blocks any new onset for 40ms after one is detected — shorter than the shortest realistic tick interval even at the fastest supported beat rate, but long enough to suppress a tick's own decaying echo from re-triggering. A second check requires the candidate point to be a genuine local peak (nothing louder in the next 3 samples) before accepting it, which avoids triggering early on the rising edge of a tick before it's actually reached its loudest point.
Once onsets are collected as precise timestamps, analyzeTicks() does the actual measurement. It first computes the interval between every consecutive pair of ticks, then rejects outliers — intervals shorter than 0.5× or longer than 1.8× the theoretical interval for your selected beat rate are discarded, since those almost always mean a missed tick (interval looks doubled) or a spurious noise trigger (interval looks tiny), not real timing data. The surviving "clean" intervals are averaged, and compared to the theoretical interval for your watch's stated bph to compute rate in seconds/day: 86400 × (theoretical/actual − 1). Beat error comes from splitting the original (unfiltered, to preserve tick/tock alternation) interval sequence into even-indexed and odd-indexed groups — the alternating tick-to-tock and tock-to-tick gaps — and taking the absolute difference of their averages, in milliseconds, as long as at least 3 clean intervals survive in each group.
Rate and beat error are both ratios or differences of timing values, which don't need any absolute calibration to be meaningful. Amplitude is different — a true angular-amplitude measurement needs a calibrated sensor, which an open-air phone or laptop mic isn't. So the tool reports only a relative loudness percentage, normalized against the single loudest tick detected in that same capture session. See why this tool can't measure true amplitude for the full explanation of that limitation.
Is any of this processing done on a server? No — every step above runs in your browser tab using the Web Audio API and plain JavaScript. See the privacy policy.
Why 2000Hz specifically for the highpass cutoff? It's chosen to sit well above typical room noise, voice fundamentals, and low-frequency rumble, while still being comfortably below the sharp transient energy a mechanical tick produces — a practical middle ground rather than a value tuned to one specific watch.
Can I see the exact source code? The DSP functions are plain, readable JavaScript shipped as part of the site's own files — view-source on the page to see timegrapher-dsp.js directly.
Why reject intervals outside 0.5×-1.8× instead of a tighter range? Real ticks have some natural timing jitter even on a healthy watch, and the goal is to catch genuinely bad data (missed ticks, noise spikes) without discarding legitimate small variation that's exactly what the rate and beat-error measurement is trying to capture.