2.6 KiB
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 rankedMatchResult[].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
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:
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 }(default0.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<number, number[]> since duplicate hashes inside one fingerprint are common (the same
constellation pair can recur at different times). The histogram is also a
Map<number, number> 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).