@audio/mir-fingerprint 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/fingerprint.js +78 -0
  2. package/package.json +34 -0
package/fingerprint.js ADDED
@@ -0,0 +1,78 @@
1
+ // Landmark audio fingerprinting (Wang 2003, Shazam class): spectrogram peak
2
+ // constellation → anchor→target hashes (f1, f2, Δt) → matching = offset-histogram vote.
3
+ // Robust to noise and level; exact-match identification, not similarity.
4
+
5
+ import { fft } from 'fourier-transform'
6
+
7
+ /**
8
+ * @param {Float32Array} data — mono PCM
9
+ * @param {object} opts — { fs=44100, frameSize=1024, hop=512, peaksPerFrame=5, fanout=5,
10
+ * window=32 (max Δt frames for pairing) }
11
+ * @returns {Array<{hash:number, t:number}>} landmarks (t in frames; frame = hop/fs seconds)
12
+ */
13
+ export default function fingerprint (data, { frameSize = 1024, hop = 512, peaksPerFrame = 5, fanout = 5, window = 32 } = {}) {
14
+ let half = frameSize / 2
15
+ let nFrames = Math.max(0, Math.floor((data.length - frameSize) / hop) + 1)
16
+ let win = new Float64Array(frameSize)
17
+ for (let i = 0; i < frameSize; i++) win[i] = 0.5 - 0.5 * Math.cos(2 * Math.PI * i / frameSize)
18
+ let buf = new Float64Array(frameSize)
19
+
20
+ // constellation: per-frame prominent spectral peaks
21
+ let peaks = [] // [{t, f}]
22
+ for (let t = 0; t < nFrames; t++) {
23
+ for (let i = 0; i < frameSize; i++) buf[i] = data[t * hop + i] * win[i]
24
+ let [re, im] = fft(buf)
25
+ let mag = new Float64Array(half)
26
+ let mean = 0
27
+ for (let k = 1; k < half; k++) { mag[k] = re[k] * re[k] + im[k] * im[k]; mean += mag[k] }
28
+ mean /= half
29
+ let frame = []
30
+ for (let k = 3; k < half - 3; k++) {
31
+ if (mag[k] > 4 * mean && mag[k] > mag[k - 1] && mag[k] >= mag[k + 1] && mag[k] > mag[k - 2] && mag[k] >= mag[k + 2]) {
32
+ frame.push({ k, m: mag[k] })
33
+ }
34
+ }
35
+ frame.sort((a, b) => b.m - a.m)
36
+ for (let p of frame.slice(0, peaksPerFrame)) peaks.push({ t, f: p.k })
37
+ }
38
+
39
+ // pair anchors with forward targets
40
+ let landmarks = []
41
+ for (let i = 0; i < peaks.length; i++) {
42
+ let a = peaks[i], fan = 0
43
+ for (let j = i + 1; j < peaks.length && fan < fanout; j++) {
44
+ let b = peaks[j]
45
+ let dt = b.t - a.t
46
+ if (dt < 1) continue
47
+ if (dt > window) break
48
+ landmarks.push({ hash: (a.f * 512 + b.f) * 64 + dt, t: a.t })
49
+ fan++
50
+ }
51
+ }
52
+ return landmarks
53
+ }
54
+
55
+ /**
56
+ * Match two fingerprints: histogram of time offsets over shared hashes.
57
+ * @returns {{ score: number (votes for the best offset), offset: number (frames, b within a) }}
58
+ */
59
+ export function match (fpA, fpB) {
60
+ let index = new Map()
61
+ for (let l of fpA) {
62
+ let arr = index.get(l.hash)
63
+ if (arr) arr.push(l.t)
64
+ else index.set(l.hash, [l.t])
65
+ }
66
+ let hist = new Map()
67
+ for (let l of fpB) {
68
+ let arr = index.get(l.hash)
69
+ if (!arr) continue
70
+ for (let ta of arr) {
71
+ let off = ta - l.t
72
+ hist.set(off, (hist.get(off) || 0) + 1)
73
+ }
74
+ }
75
+ let score = 0, offset = 0
76
+ for (let [off, count] of hist) if (count > score) { score = count; offset = off }
77
+ return { score, offset }
78
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@audio/mir-fingerprint",
3
+ "version": "1.0.0",
4
+ "description": "Landmark fingerprinting — spectral peak constellation hashes + offset-vote matching (Wang 2003 class)",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "fingerprint.js",
8
+ "exports": {
9
+ ".": "./fingerprint.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "fingerprint.js"
14
+ ],
15
+ "dependencies": {
16
+ "fourier-transform": "^2.2.0"
17
+ },
18
+ "keywords": [
19
+ "audio",
20
+ "dsp",
21
+ "mir",
22
+ "fingerprint",
23
+ "shazam",
24
+ "landmark"
25
+ ],
26
+ "license": "MIT",
27
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }