# `fingerprint-matcher` — Time-offset histogram match scoring _(no submodules)_ ## Purpose `FingerprintMatcher` scores a query fingerprint against a corpus of candidate fingerprints using the classic Shazam time-offset histogramming technique. For every hash that appears in both query and candidate, the matcher accumulates `(candidateOffset − queryOffset)` into a 10 ms-wide histogram bucket; the heaviest bucket count is the match score and its midpoint is the alignment offset. Results sort by descending score, ready for ranked display. ## Features - `match(query, corpus)` returns ranked `MatchResult[]`. - `matchOne(query, candidate)` returns the best bin only — useful for unit tests and direct pairwise comparison. - Configurable `binWidthSec` (default 0.01 s). - Hash-index pre-step keeps the cost at `O(|query| + |candidate|)` per pair. - Wraps iteration-time corpus failures in `REACT_AUDIO_FINGERPRINT.MATCH_FAILED`. - Empty inputs return zero-scored results without throwing. ## Usage: Basic Example ```ts import { FingerprintMatcher } from "@suaveplan/react-audio-fingerprint/fingerprint/fingerprint-matcher"; const ranked = new FingerprintMatcher().match(queryFp, corpus); console.log(ranked[0]?.id, ranked[0]?.score); ``` ## Usage: Advanced Example Drive a tighter alignment window for short clips: ```ts import { FingerprintMatcher } from "@suaveplan/react-audio-fingerprint/fingerprint/fingerprint-matcher"; const m = new FingerprintMatcher(); const one = m.matchOne(queryFp, candidateFp, { binWidthSec: 0.005 }); console.log(one.score, one.offsetSec, one.matchedHashCount); ``` ## API Reference - `class FingerprintMatcher`. - `match(query, corpus, opts?): MatchResult[]`. - `matchOne(query, candidate, opts?): MatchOneResult`. - `MatchResult = { id, score, offsetSec, matchedHashCount }`. - `CorpusLike = { values(): Iterable<{ id; fingerprint }> }`. - `MatcherOptions = { binWidthSec?: number }` (default `0.01`). ## Implementation Notes A candidate's hash table is built lazily on each call — caching is left to callers because corpora are typically streamed. Hash indices use `Map` since duplicate hashes inside one fingerprint are common (the same constellation pair can recur at different times). The histogram is also a `Map` because the bucket range is unbounded in principle. Bucket midpoints (rather than left edges) keep the reported offset symmetric around the true alignment. The matcher is pure — given the same inputs it produces the same outputs — so callers can safely memoise results keyed by `(queryFp, corpusVersion)`.