first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
# `fingerprint-matcher` — Time-offset histogram match scoring
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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)`.