# `constellation` — Pair-hash construction over detected peaks _(no submodules)_ ## Purpose The constellation module converts a list of spectrogram peaks into a stable, hashable fingerprint by pairing each anchor peak with several subsequent peaks inside a fixed target zone. Each pair `(binA, binB, Δframes)` is packed into a 32-bit integer; the resulting `(hash, timeOffsetSec)` records form the fingerprint that the matcher consumes. This is the classic Shazam construction described by Wang (2003) without any modifications. ## Features - 32-bit packed hash with explicit `binA[12] | binB[12] | dt[8]` layout. - `packHash` / `unpackHash` round-trip helpers for diagnostics and tests. - Configurable `fanout`, `targetZoneFrames`, `minDeltaFrames` via Zod schema. - Deterministic output sorted by anchor `timeOffsetSec`. - Empty-input safe (no peaks → empty fingerprint, no throw). - Validates `hopSamples` / `sampleRate` and rejects non-positive grids with `ReactAudioFingerprintError(INVALID_OPTIONS)`. ## Usage: Basic Example ```ts import { buildConstellation } from "@suaveplan/react-audio-fingerprint/fingerprint/constellation"; const fp = buildConstellation(peaks, 512, 48_000); console.log(fp.length, fp[0]?.hash); ``` ## Usage: Advanced Example Tighten the target zone for short percussive clips: ```ts import { buildConstellation } from "@suaveplan/react-audio-fingerprint/fingerprint/constellation"; const fp = buildConstellation(peaks, 256, 48_000, { fanout: 8, targetZoneFrames: 80, minDeltaFrames: 2, }); ``` ## API Reference - `buildConstellation(peaks, hopSamples, sampleRate, opts?): Fingerprint`. - `Fingerprint = ReadonlyArray<{ hash: number; timeOffsetSec: number }>`. - `packHash(binA, binB, dt): number` — 32-bit pack. - `unpackHash(h): { binA; binB; dt }` — inverse. - `ConstellationOptionsSchema` — Zod schema with defaults `fanout=5`, `targetZoneFrames=200`, `minDeltaFrames=1`. ## Implementation Notes The Δt field is clamped to 8 bits, which limits the effective target zone to 255 hop-frames regardless of the schema value. At the default 512-sample hop and 48 kHz sample rate this is ~2.72 seconds — well inside the classic constellation target-zone width. Hashes are unsigned 32-bit integers; the `>>> 0` at the end of `packHash` reinterprets the bitwise result, which is otherwise a signed 32-bit integer in JavaScript's number type. Output sorting makes equal-fingerprint comparison trivial (`JSON.stringify` round-trip is enough for byte equality). Pair construction is O(`peaks × fanout`) because each anchor stops emitting once the fanout cap is hit or the target zone is exhausted.