Coin Pinger

Acoustic bullion authentication — free, private, on-device

Launch app

Under the hood

How Coin Pinger's algorithm works

A coin's ring carries more information than its pitch. Coin Pinger reads the whole ring — the resonance peaks, how steady they are, and how long the ring lasts — and matches it to a reference. Here's exactly how, with the real code. It's all open source under the MIT License.

1 · High-resolution peak detection

An FFT (Fast Fourier Transform) is the standard way to break a sound into the individual frequencies it contains — the same public, decades-old, textbook math behind every spectrum analyzer, guitar tuner, and voice app. There's nothing proprietary about it; we just run a big one. We use the largest FFT the browser allows (32768 points) on the ring — roughly 1.4 Hz frequency bins — then refine each peak with parabolic interpolation for sub-bin, sub-Hz precision. A coin's lowest three free-disc vibration modes (c0d2 / c0d3 / c0d4) become three sharp peaks.

// refine each peak to sub-Hz precision
// (parabolic interpolation on the FFT bins)
const denom = left - 2 * peak + right;
const offset = denom ? 0.5 * (left - right) / denom : 0;
const hz = (bin + offset) * binHz;   // sub-bin frequency
Why it matters: finer resolution means the match band is set by the coin's real variation, not by coarse FFT bins — so genuine coins pass and near-misses don't.

2 · Is it even a coin ring?

Before any verdict, the captured sound has to look like a struck free disc. We reject anything that isn't: no sharp peak, too short to be a ring, an unsteady (drifting) pitch, or peaks whose spacing doesn't fit the 1 : 2.3 : 3.9 modal ratios of a vibrating disc. A dropped key or a clinking glass can't sneak through as a "match".

function classifyRing(peaks, sustainMs, ringFreqs) {
  if (weakPeak(peaks))         return "no sharp peak";
  if (sustainMs < 250)         return "too short";
  if (pitchDrifts(ringFreqs))  return "pitch not steady";
  if (!fitsModalRatios(peaks)) return "not a coin ring";
  return null;   // genuine ring — go to the verdict
}

// a struck disc's modes cluster near 1 : 2.3 : 3.9
function fitsModalRatios(peaks) {
  const [f1, f2, f3] = peaks.map(p => p.hz).sort();
  const near = (a, b) => Math.abs(a - b) / b <= 0.25;
  return near(f2 / f1, 2.3) && near(f3 / f1, 3.9);
}
Why it matters: pitch alone isn't proof — a tone near the right frequency could be anything. Coin Pinger first confirms the sound is genuinely a coin ring before it counts.

3 · Ring-decay analysis

This is the part pitch alone can't tell you. Solid precious metal has very low internal damping, so it rings for a long time; a plated or base-metal fake at the right pitch dies out fast. We fit a straight line to the ring's loudness (in dB) over time — a T30-style measurement — to get a decay rate in dB/s, then compare it to the reference. A fake that fooled the pitch check gets caught here.

// least-squares slope of loudness (dB) over time
// -> ring-decay rate in dB per second
function computeDecay(ringLevels) {
  const pts = ringBody(ringLevels);  // drop attack + tail
  const slope = linearFit(pts).slope;
  return Math.round(-slope);   // higher = dies faster
}

// flag a coin that rings out far faster than genuine
if (measured.decay / ref.decay > 2.5) {
  warn("Dies out fast — possible plating / base metal");
}
Why it matters: pitch tells you the size and stiffness; decay tells you the metal is solid. Checking both is what makes this a test of the metal, not just its pitch.

4 · Matching & the verdict

Each of the coin's modes must land inside its expected range (a per-mode band, wider only where that mode genuinely varies between specimens). Every mode in range → MATCH. Most but not all → "tap again" for a cleaner read. Clearly outside → an honest NO MATCH.

// each mode must fall inside its expected band
const matched = ref.peaks.filter((center, i) =>
  peaks.some(p =>
    Math.abs(p.hz - center) / center <= band(ref, i)));

if (matched.length === ref.peaks.length) verdict = "MATCH";
else if (matched.length >= 2) verdict = "tap again";  // partial
else verdict = "NO MATCH";
The difference: Coin Pinger can say NO MATCH. And you can either use a preset reference or calibrate your own genuine coin — its ring becomes a specimen-exact fingerprint you can test against and share.

Open and honest

The snippets above are simplified from the real code, which is open source under the MIT License. The ping test is one strong signal, not proof — pair it with weight and dimension checks, and for high-value items get a professional opinion. Reference ranges are our own estimates from official mint specs and public acoustic-physics modeling; see Terms & Legal for full sourcing.