@sorisdk/matcher 0.6.8 → 0.6.10
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.
- package/README.md +73 -1
- package/dist/generated/core.d.ts +10 -0
- package/dist/generated/core.js +1 -1
- package/dist/generated/core_bg.js +63 -5
- package/dist/generated/core_bg.wasm +0 -0
- package/dist/generated/core_bg.wasm.d.ts +3 -2
- package/dist/index.d.ts +37 -1
- package/dist/index.js +163 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,38 @@ const session = new MatcherSession({
|
|
|
55
55
|
});
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
## Continuous recognition and reset
|
|
59
|
+
|
|
60
|
+
Every query returns its matching result, including repeated material matches.
|
|
61
|
+
`match`, `bestMatch`, and their variant methods share one event observer:
|
|
62
|
+
continuous A → A emits one `match`, while A → B → A emits three. Material
|
|
63
|
+
identity includes the fingerprint type. There is no material cooldown.
|
|
64
|
+
|
|
65
|
+
Every successful observation resets the consecutive-miss counter and renews
|
|
66
|
+
the run, even when its public event is suppressed. Two consecutive no-match
|
|
67
|
+
results end the run and emit one `nomatch`; a single transient miss does not.
|
|
68
|
+
A gap **greater than 30 seconds since the last successful observation** also
|
|
69
|
+
ends continuity, including periods with no queries. This boundary is evaluated
|
|
70
|
+
on the next query: a success emits a new `match` immediately, or a no-match
|
|
71
|
+
emits `nomatch` without waiting for a second miss. The exported
|
|
72
|
+
`MATCH_CONTINUITY_GAP_MS` is shared with Web activity reporting.
|
|
73
|
+
|
|
74
|
+
Call `session.resetRecognitionState()` at an explicit input/capture boundary.
|
|
75
|
+
It synchronously clears event suppression and invalidates pending queries from
|
|
76
|
+
the previous run, retaining the loaded database. Invalidated queries return
|
|
77
|
+
`null` (best-match methods) or `[]` (multi-match methods) without match/no-match
|
|
78
|
+
events. Successful pack replacement and entry mutations also reset recognition
|
|
79
|
+
state; a failed pack load retains the previous state and entries.
|
|
80
|
+
|
|
81
|
+
The low-level generated WASM matcher always returns successful observations;
|
|
82
|
+
event deduplication belongs to `MatcherSession`. Its existing
|
|
83
|
+
`resetDistinctMatchState()` export remains available for compatibility.
|
|
84
|
+
|
|
85
|
+
For source regression tests, build the package first with
|
|
86
|
+
`corepack pnpm --filter @sorisdk/matcher build`, then run
|
|
87
|
+
`corepack pnpm --filter @sorisdk/matcher test`. Tests include production WASM
|
|
88
|
+
instantiated with the TypeScript session, using deterministic fingerprints.
|
|
89
|
+
|
|
58
90
|
## Pack loading
|
|
59
91
|
|
|
60
92
|
`loadPack(...)` accepts:
|
|
@@ -89,6 +121,44 @@ per-entry limits remain independently enforced.
|
|
|
89
121
|
|
|
90
122
|
If you want campaign events and browser recognition flow on top of matching, use `@sorisdk/web-audio`.
|
|
91
123
|
|
|
124
|
+
## Audio-marker default policy (0.6.10)
|
|
125
|
+
|
|
126
|
+
`session.detectAudioMarker(pcm16Le, actualSampleRate)` and
|
|
127
|
+
`detectAudioMarkerWithLimits` now select the shared level-robust v1 policy.
|
|
128
|
+
An empty config or waveform/search-only config also uses this default. No PCM
|
|
129
|
+
amplification is applied: `score`, `syncScore`, `margin`, and runner-up scores
|
|
130
|
+
remain in raw input-amplitude units. `confidence` uses the v1 calibration.
|
|
131
|
+
|
|
132
|
+
Results keep the existing flat detection shape and add `levelPolicy`,
|
|
133
|
+
`diagnostics`, `candidateEligible`, and `materialFallbackGuardEligible`.
|
|
134
|
+
Eligibility flags come directly from the shared DSP. They do not authorize a
|
|
135
|
+
marker hit: only a non-null accepted `code` is eligible for activity enrichment.
|
|
136
|
+
The Web path does not apply extra legacy raw-score gates or implement native
|
|
137
|
+
SDK temporal voting/material-fallback heuristics. Early JS input rejections
|
|
138
|
+
include false eligibility flags and may omit DSP diagnostics.
|
|
139
|
+
|
|
140
|
+
For compatibility, explicitly setting any of `minSyncScore`, `minScore`,
|
|
141
|
+
`minMargin`, `minRatio`, `minConfidence`, or `minVotes` retains legacy behavior.
|
|
142
|
+
Choose `levelPolicy` explicitly to override this compatibility rule:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
await session.detectAudioMarker(pcm, rate, { levelPolicy: "legacy" });
|
|
146
|
+
await session.detectAudioMarker(pcm, rate, {
|
|
147
|
+
levelPolicy: "levelRobust", minVotes: 2
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
With explicit robust policy, shared v1 amplitude gates replace `minSyncScore`,
|
|
152
|
+
`minScore`, and `minMargin`; waveform/search/codebook/vote parameters and stricter
|
|
153
|
+
ratio/confidence requirements retain the shared Rust semantics. The generated
|
|
154
|
+
low-level `detectAudioMarker` / `detectPatternMark` exports and their bounded
|
|
155
|
+
variants remain legacy APIs; `detectAudioMarkerLevelRobust` returns the shared
|
|
156
|
+
nested envelope. Rust, C, Python, and native Node legacy APIs are unchanged.
|
|
157
|
+
|
|
158
|
+
Upgrade the JS package and its generated WASM together. A custom loader lacking
|
|
159
|
+
the robust export fails with an upgrade error on the default path; it never
|
|
160
|
+
silently downgrades. An explicit legacy config remains supported with old WASM.
|
|
161
|
+
|
|
92
162
|
## Audio-marker detection limits
|
|
93
163
|
|
|
94
164
|
`MatcherSession.detectAudioMarker(...)` preserves its existing signature and
|
|
@@ -117,7 +187,9 @@ const detection = await session.detectAudioMarkerWithLimits(
|
|
|
117
187
|
```
|
|
118
188
|
|
|
119
189
|
The generated WASM module exposes the corresponding
|
|
120
|
-
`detectAudioMarkerWithLimits` function and method
|
|
190
|
+
`detectAudioMarkerWithLimits` legacy function and method, and the robust
|
|
191
|
+
`detectAudioMarkerLevelRobust(pcm, rate, config, limits)` function. The session
|
|
192
|
+
selects the correct export from the policy above. Each inspects the JavaScript
|
|
121
193
|
`Uint8Array` length before allocating a Rust-owned copy. Raising these budgets
|
|
122
194
|
permits greater memory and CPU use; it does not protect an untrusted host after
|
|
123
195
|
the caller opts into the larger envelope.
|
package/dist/generated/core.d.ts
CHANGED
|
@@ -35,11 +35,21 @@ export class WasmMatcherEngine {
|
|
|
35
35
|
matchQuery(database: WasmMatcherDatabase, query: Uint8Array, config?: any | null): any;
|
|
36
36
|
matchQueryWithVariants(database: WasmMatcherDatabase, query: Uint8Array, pitch_query: any, config?: any | null, variant_config?: any | null): any;
|
|
37
37
|
constructor();
|
|
38
|
+
/**
|
|
39
|
+
* Compatibility no-op: continuous-run deduplication belongs to MatcherSession.
|
|
40
|
+
* The binding returns every successful observation and retains no run state.
|
|
41
|
+
*/
|
|
38
42
|
resetDistinctMatchState(): void;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export function detectAudioMarker(pcm16_le: Uint8Array, sample_rate: number, config?: any | null): any;
|
|
42
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Explicit v1 policy; existing marker exports remain on the legacy policy.
|
|
49
|
+
* Result includes raw detection, normalized diagnostics and shared eligibility.
|
|
50
|
+
*/
|
|
51
|
+
export function detectAudioMarkerLevelRobust(pcm16_le: Uint8Array, sample_rate: number, config?: any | null, limits?: any | null): any;
|
|
52
|
+
|
|
43
53
|
export function detectAudioMarkerWithLimits(pcm16_le: Uint8Array, sample_rate: number, config?: any | null, limits?: any | null): any;
|
|
44
54
|
|
|
45
55
|
export function detectPatternMark(pcm16_le: Uint8Array, sample_rate: number, config?: any | null): any;
|
package/dist/generated/core.js
CHANGED
|
@@ -5,5 +5,5 @@ import { __wbg_set_wasm } from "./core_bg.js";
|
|
|
5
5
|
__wbg_set_wasm(wasm);
|
|
6
6
|
wasm.__wbindgen_start();
|
|
7
7
|
export {
|
|
8
|
-
WasmMatcherDatabase, WasmMatcherEngine, detectAudioMarker, detectAudioMarkerWithLimits, detectPatternMark, detectPatternMarkWithLimits, estimatePitchRatio, estimatePitchRatioAgainstReference, estimatePitchRatioAgainstSpectralFeatures, extractPitchReference, extractSpectralShiftReference, init_matcher_wasm
|
|
8
|
+
WasmMatcherDatabase, WasmMatcherEngine, detectAudioMarker, detectAudioMarkerLevelRobust, detectAudioMarkerWithLimits, detectPatternMark, detectPatternMarkWithLimits, estimatePitchRatio, estimatePitchRatioAgainstReference, estimatePitchRatioAgainstSpectralFeatures, extractPitchReference, extractSpectralShiftReference, init_matcher_wasm
|
|
9
9
|
} from "./core_bg.js";
|
|
@@ -359,6 +359,10 @@ export class WasmMatcherEngine {
|
|
|
359
359
|
WasmMatcherEngineFinalization.register(this, this.__wbg_ptr, this);
|
|
360
360
|
return this;
|
|
361
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Compatibility no-op: continuous-run deduplication belongs to MatcherSession.
|
|
364
|
+
* The binding returns every successful observation and retains no run state.
|
|
365
|
+
*/
|
|
362
366
|
resetDistinctMatchState() {
|
|
363
367
|
wasm.wasmmatcherengine_resetDistinctMatchState(this.__wbg_ptr);
|
|
364
368
|
}
|
|
@@ -379,6 +383,23 @@ export function detectAudioMarker(pcm16_le, sample_rate, config) {
|
|
|
379
383
|
return takeFromExternrefTable0(ret[0]);
|
|
380
384
|
}
|
|
381
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Explicit v1 policy; existing marker exports remain on the legacy policy.
|
|
388
|
+
* Result includes raw detection, normalized diagnostics and shared eligibility.
|
|
389
|
+
* @param {Uint8Array} pcm16_le
|
|
390
|
+
* @param {number} sample_rate
|
|
391
|
+
* @param {any | null} [config]
|
|
392
|
+
* @param {any | null} [limits]
|
|
393
|
+
* @returns {any}
|
|
394
|
+
*/
|
|
395
|
+
export function detectAudioMarkerLevelRobust(pcm16_le, sample_rate, config, limits) {
|
|
396
|
+
const ret = wasm.detectAudioMarkerLevelRobust(pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config), isLikeNone(limits) ? 0 : addToExternrefTable0(limits));
|
|
397
|
+
if (ret[2]) {
|
|
398
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
399
|
+
}
|
|
400
|
+
return takeFromExternrefTable0(ret[0]);
|
|
401
|
+
}
|
|
402
|
+
|
|
382
403
|
/**
|
|
383
404
|
* @param {Uint8Array} pcm16_le
|
|
384
405
|
* @param {number} sample_rate
|
|
@@ -599,6 +620,10 @@ export function __wbg_call_14b169f759b26747() { return handleError(function (arg
|
|
|
599
620
|
const ret = arg0.call(arg1);
|
|
600
621
|
return ret;
|
|
601
622
|
}, arguments); }
|
|
623
|
+
export function __wbg_call_a24592a6f349a97e() { return handleError(function (arg0, arg1, arg2) {
|
|
624
|
+
const ret = arg0.call(arg1, arg2);
|
|
625
|
+
return ret;
|
|
626
|
+
}, arguments); }
|
|
602
627
|
export function __wbg_done_9158f7cc8751ba32(arg0) {
|
|
603
628
|
const ret = arg0.done;
|
|
604
629
|
return ret;
|
|
@@ -618,10 +643,22 @@ export function __wbg_error_a6fa202b58aa1cd3(arg0, arg1) {
|
|
|
618
643
|
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
|
619
644
|
}
|
|
620
645
|
}
|
|
646
|
+
export function __wbg_getOwnPropertyDescriptor_131bd582a45a6f5d(arg0, arg1) {
|
|
647
|
+
const ret = Object.getOwnPropertyDescriptor(arg0, arg1);
|
|
648
|
+
return ret;
|
|
649
|
+
}
|
|
650
|
+
export function __wbg_getPrototypeOf_a56da9261bbd1e5b(arg0) {
|
|
651
|
+
const ret = Object.getPrototypeOf(arg0);
|
|
652
|
+
return ret;
|
|
653
|
+
}
|
|
621
654
|
export function __wbg_get_1affdbdd5573b16a() { return handleError(function (arg0, arg1) {
|
|
622
655
|
const ret = Reflect.get(arg0, arg1);
|
|
623
656
|
return ret;
|
|
624
657
|
}, arguments); }
|
|
658
|
+
export function __wbg_get_6011fa3a58f61074() { return handleError(function (arg0, arg1) {
|
|
659
|
+
const ret = Reflect.get(arg0, arg1);
|
|
660
|
+
return ret;
|
|
661
|
+
}, arguments); }
|
|
625
662
|
export function __wbg_get_8360291721e2339f(arg0, arg1) {
|
|
626
663
|
const ret = arg0[arg1 >>> 0];
|
|
627
664
|
return ret;
|
|
@@ -716,10 +753,6 @@ export function __wbg_next_7646edaa39458ef7(arg0) {
|
|
|
716
753
|
const ret = arg0.next;
|
|
717
754
|
return ret;
|
|
718
755
|
}
|
|
719
|
-
export function __wbg_now_a9b7df1cbee90986() {
|
|
720
|
-
const ret = Date.now();
|
|
721
|
-
return ret;
|
|
722
|
-
}
|
|
723
756
|
export function __wbg_prototypesetcall_a6b02eb00b0f4ce2(arg0, arg1, arg2) {
|
|
724
757
|
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
|
725
758
|
}
|
|
@@ -740,6 +773,26 @@ export function __wbg_stack_3b0d974bbf31e44f(arg0, arg1) {
|
|
|
740
773
|
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
|
741
774
|
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
|
742
775
|
}
|
|
776
|
+
export function __wbg_static_accessor_GLOBAL_8cfadc87a297ca02() {
|
|
777
|
+
const ret = typeof global === 'undefined' ? null : global;
|
|
778
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
779
|
+
}
|
|
780
|
+
export function __wbg_static_accessor_GLOBAL_THIS_602256ae5c8f42cf() {
|
|
781
|
+
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
|
782
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
783
|
+
}
|
|
784
|
+
export function __wbg_static_accessor_SELF_e445c1c7484aecc3() {
|
|
785
|
+
const ret = typeof self === 'undefined' ? null : self;
|
|
786
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
787
|
+
}
|
|
788
|
+
export function __wbg_static_accessor_WINDOW_f20e8576ef1e0f17() {
|
|
789
|
+
const ret = typeof window === 'undefined' ? null : window;
|
|
790
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
791
|
+
}
|
|
792
|
+
export function __wbg_toStringTag_d6b1716c86d4892d() {
|
|
793
|
+
const ret = Symbol.toStringTag;
|
|
794
|
+
return ret;
|
|
795
|
+
}
|
|
743
796
|
export function __wbg_value_ee3a06f4579184fa(arg0) {
|
|
744
797
|
const ret = arg0.value;
|
|
745
798
|
return ret;
|
|
@@ -755,11 +808,16 @@ export function __wbindgen_cast_0000000000000002(arg0) {
|
|
|
755
808
|
return ret;
|
|
756
809
|
}
|
|
757
810
|
export function __wbindgen_cast_0000000000000003(arg0, arg1) {
|
|
811
|
+
// Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`.
|
|
812
|
+
const ret = getArrayU8FromWasm0(arg0, arg1);
|
|
813
|
+
return ret;
|
|
814
|
+
}
|
|
815
|
+
export function __wbindgen_cast_0000000000000004(arg0, arg1) {
|
|
758
816
|
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
759
817
|
const ret = getStringFromWasm0(arg0, arg1);
|
|
760
818
|
return ret;
|
|
761
819
|
}
|
|
762
|
-
export function
|
|
820
|
+
export function __wbindgen_cast_0000000000000005(arg0) {
|
|
763
821
|
// Cast intrinsic for `U64 -> Externref`.
|
|
764
822
|
const ret = BigInt.asUintN(64, arg0);
|
|
765
823
|
return ret;
|
|
Binary file
|
|
@@ -4,6 +4,7 @@ export const memory: WebAssembly.Memory;
|
|
|
4
4
|
export const __wbg_wasmmatcherdatabase_free: (a: number, b: number) => void;
|
|
5
5
|
export const __wbg_wasmmatcherengine_free: (a: number, b: number) => void;
|
|
6
6
|
export const detectAudioMarker: (a: any, b: number, c: number) => [number, number, number];
|
|
7
|
+
export const detectAudioMarkerLevelRobust: (a: any, b: number, c: number, d: number) => [number, number, number];
|
|
7
8
|
export const detectAudioMarkerWithLimits: (a: any, b: number, c: number, d: number) => [number, number, number];
|
|
8
9
|
export const detectPatternMarkWithLimits: (a: any, b: number, c: number, d: number) => [number, number, number];
|
|
9
10
|
export const estimatePitchRatio: (a: number, b: number, c: number) => [number, number, number];
|
|
@@ -27,7 +28,6 @@ export const wasmmatcherengine_bestMatch: (a: number, b: number, c: number, d: n
|
|
|
27
28
|
export const wasmmatcherengine_bestMatchWithVariants: (a: number, b: number, c: number, d: number, e: any, f: number, g: number) => [number, number, number];
|
|
28
29
|
export const wasmmatcherengine_detectAudioMarker: (a: number, b: any, c: number, d: number) => [number, number, number];
|
|
29
30
|
export const wasmmatcherengine_detectAudioMarkerWithLimits: (a: number, b: any, c: number, d: number, e: number) => [number, number, number];
|
|
30
|
-
export const wasmmatcherengine_detectPatternMark: (a: number, b: any, c: number, d: number) => [number, number, number];
|
|
31
31
|
export const wasmmatcherengine_detectPatternMarkWithLimits: (a: number, b: any, c: number, d: number, e: number) => [number, number, number];
|
|
32
32
|
export const wasmmatcherengine_estimatePitchRatio: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
33
33
|
export const wasmmatcherengine_estimatePitchRatioAgainstReference: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number];
|
|
@@ -36,10 +36,11 @@ export const wasmmatcherengine_extractPitchReference: (a: number, b: number, c:
|
|
|
36
36
|
export const wasmmatcherengine_extractSpectralShiftReference: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
37
37
|
export const wasmmatcherengine_matchQuery: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
|
|
38
38
|
export const wasmmatcherengine_matchQueryWithVariants: (a: number, b: number, c: number, d: number, e: any, f: number, g: number) => [number, number, number];
|
|
39
|
-
export const wasmmatcherengine_new: () => number;
|
|
40
39
|
export const wasmmatcherengine_resetDistinctMatchState: (a: number) => void;
|
|
40
|
+
export const wasmmatcherengine_new: () => number;
|
|
41
41
|
export const init_matcher_wasm: () => void;
|
|
42
42
|
export const detectPatternMark: (a: any, b: number, c: number) => [number, number, number];
|
|
43
|
+
export const wasmmatcherengine_detectPatternMark: (a: number, b: any, c: number, d: number) => [number, number, number];
|
|
43
44
|
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
44
45
|
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
45
46
|
export const __wbindgen_exn_store: (a: number) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ declare const DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS = 1000000000;
|
|
|
20
20
|
declare const AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON = "pcm_input_budget_exceeded";
|
|
21
21
|
declare const AUDIO_MARKER_WORK_BUDGET_EXCEEDED_REASON = "detector_work_budget_exceeded";
|
|
22
22
|
declare const AUDIO_MARKER_WORK_OVERFLOW_REASON = "detector_work_overflow";
|
|
23
|
+
type AudioMarkerLevelPolicy = "levelRobust" | "legacy";
|
|
23
24
|
interface AudioMarkerConfig {
|
|
25
|
+
/** Defaults to levelRobust; explicit thresholds retain legacy semantics unless overridden. */
|
|
26
|
+
levelPolicy?: AudioMarkerLevelPolicy;
|
|
24
27
|
bandLowHz?: number;
|
|
25
28
|
bandHighHz?: number;
|
|
26
29
|
syncDurationMillis?: number;
|
|
@@ -43,7 +46,31 @@ interface AudioMarkerLimits {
|
|
|
43
46
|
maxDurationMillis?: number;
|
|
44
47
|
maxWorkUnits?: number;
|
|
45
48
|
}
|
|
49
|
+
/** Shared v1 diagnostics; levels are band estimates, not calibrated SPL or SNR. */
|
|
50
|
+
interface AudioMarkerLevelDiagnostics {
|
|
51
|
+
policyVersion: number;
|
|
52
|
+
markerBandDbfs: number;
|
|
53
|
+
lowerNeighborDbfs: number;
|
|
54
|
+
upperNeighborDbfs: number;
|
|
55
|
+
normalizationRms: number;
|
|
56
|
+
effectiveGain: number;
|
|
57
|
+
normalizedSyncScore: number;
|
|
58
|
+
normalizedScore: number;
|
|
59
|
+
normalizedMargin: number;
|
|
60
|
+
policyConfidence: number;
|
|
61
|
+
codeHalfBalance: number | null;
|
|
62
|
+
effectiveMinSyncScore: number;
|
|
63
|
+
effectiveMinScore: number;
|
|
64
|
+
effectiveMinMargin: number;
|
|
65
|
+
qualityReason: string | null;
|
|
66
|
+
}
|
|
46
67
|
interface AudioMarkerDetection {
|
|
68
|
+
/** Present on level-robust session results; raw score units below are unchanged. */
|
|
69
|
+
levelPolicy?: AudioMarkerLevelPolicy;
|
|
70
|
+
diagnostics?: AudioMarkerLevelDiagnostics;
|
|
71
|
+
/** Shared eligibility only; a candidate is not an accepted marker identity. */
|
|
72
|
+
candidateEligible?: boolean;
|
|
73
|
+
materialFallbackGuardEligible?: boolean;
|
|
47
74
|
pattern?: string | null;
|
|
48
75
|
code?: string | null;
|
|
49
76
|
confidence?: number | null;
|
|
@@ -230,6 +257,7 @@ declare class MatcherSession {
|
|
|
230
257
|
private matcher;
|
|
231
258
|
private destroyed;
|
|
232
259
|
private packLoadTail;
|
|
260
|
+
private recognitionGeneration;
|
|
233
261
|
private readonly distinctMatchDeduper;
|
|
234
262
|
constructor(options?: MatcherSessionOptions);
|
|
235
263
|
on<T extends MatcherSessionEventName>(eventName: T, listener: MatcherSessionListener<T>): this;
|
|
@@ -250,11 +278,16 @@ declare class MatcherSession {
|
|
|
250
278
|
extractPitchReference(pcm16Le: Uint8Array, sampleRate: number): Promise<PitchReferenceEstimate>;
|
|
251
279
|
estimatePitchRatioAgainstReference(pcm16Le: Uint8Array, sampleRate: number, referencePitchHz: number, referenceConfidence?: number | null): Promise<PitchRatioEstimate>;
|
|
252
280
|
estimatePitchRatioAgainstReferenceFeatures(pcm16Le: Uint8Array, sampleRate: number, referenceFeatures: string, referencePositionMillis: number, referenceConfidence?: number | null): Promise<PitchRatioEstimate>;
|
|
281
|
+
/** Uses level-robust detection by default, preserving explicit legacy thresholds. */
|
|
253
282
|
detectAudioMarker(pcm16Le: Uint8Array, sampleRate: number, config?: AudioMarkerConfig): Promise<AudioMarkerDetection>;
|
|
254
283
|
/** Detect an audio marker with explicit limits for trusted offline input. */
|
|
255
284
|
detectAudioMarkerWithLimits(pcm16Le: Uint8Array, sampleRate: number, config: AudioMarkerConfig | undefined, limits: AudioMarkerLimits): Promise<AudioMarkerDetection>;
|
|
256
285
|
matchWithVariants(query: Uint8Array, pitchQuery: VariantMatchQuery, config?: MatchConfig, variantConfig?: VariantMatchConfig): Promise<VariantMatch[]>;
|
|
257
286
|
bestMatchWithVariants(query: Uint8Array, pitchQuery: VariantMatchQuery, config?: MatchConfig, variantConfig?: VariantMatchConfig): Promise<VariantMatch | null>;
|
|
287
|
+
/** End the current recognition run without removing loaded entries.
|
|
288
|
+
* Pending queries from the previous run cannot update event suppression state.
|
|
289
|
+
*/
|
|
290
|
+
resetRecognitionState(): void;
|
|
258
291
|
destroy(): void;
|
|
259
292
|
private bootstrap;
|
|
260
293
|
private ensureLiveReady;
|
|
@@ -268,4 +301,7 @@ declare function __setMatcherWasmModuleForTests(moduleLike: unknown): void;
|
|
|
268
301
|
declare function __setMatcherEmbeddedPackForTests(bytes: Uint8Array): void;
|
|
269
302
|
declare function __resetMatcherWasmForTests(): void;
|
|
270
303
|
|
|
271
|
-
|
|
304
|
+
/** Maximum gap between successful observations in one continuous material run. */
|
|
305
|
+
declare const MATCH_CONTINUITY_GAP_MS = 30000;
|
|
306
|
+
|
|
307
|
+
export { AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON, AUDIO_MARKER_WORK_BUDGET_EXCEEDED_REASON, AUDIO_MARKER_WORK_OVERFLOW_REASON, AudioFingerprintType, type AudioMarkerCode, type AudioMarkerConfig, type AudioMarkerDetection, type AudioMarkerLevelDiagnostics, type AudioMarkerLevelPolicy, type AudioMarkerLimits, DEFAULT_MAX_AUDIO_MARKER_DURATION_MILLIS, DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES, DEFAULT_MAX_AUDIO_MARKER_SAMPLES, DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS, DEFAULT_MAX_PACK_BYTES, type EntryMetadata, type InitMatcherOptions, type InitMatcherResult, MATCH_CONTINUITY_GAP_MS, type MatchConfig, type MatchResult, type MatcherEphemeralAuthOptions, type MatcherEphemeralAuthResult, type MatcherEphemeralKeyExchangeMetadata, type MatcherEphemeralKeyExchangeOptions, MatcherSession, type MatcherSessionEventMap, type MatcherSessionEventName, type MatcherSessionListener, type MatcherSessionOptions, type PackSource, type PitchRatioEstimate, type PitchReferenceEstimate, type VariantDecision, type VariantDecisionStatus, type VariantMatch, type VariantMatchConfig, type VariantMatchQuery, __resetMatcherWasmForTests, __setMatcherEmbeddedPackForTests, __setMatcherWasmModuleForTests, initMatcher };
|
package/dist/index.js
CHANGED
|
@@ -327,28 +327,31 @@ var AUDIO_MARKER_WORK_OVERFLOW_REASON = "detector_work_overflow";
|
|
|
327
327
|
|
|
328
328
|
// src/match-deduper.ts
|
|
329
329
|
var DEFAULT_NO_MATCH_CONFIRMATION_COUNT = 2;
|
|
330
|
+
var MATCH_CONTINUITY_GAP_MS = 3e4;
|
|
330
331
|
function getMatchIdentity(match) {
|
|
331
332
|
return `${match.name}:${match.afpType}`;
|
|
332
333
|
}
|
|
333
334
|
var DistinctMatchDeduper = class {
|
|
334
335
|
lastIdentity = null;
|
|
335
336
|
pendingNoMatchCount = 0;
|
|
336
|
-
|
|
337
|
+
lastObservedAt = null;
|
|
338
|
+
observe(best, now = Date.now()) {
|
|
339
|
+
const expired = this.lastObservedAt !== null && now - this.lastObservedAt > MATCH_CONTINUITY_GAP_MS;
|
|
337
340
|
if (!best) {
|
|
338
341
|
if (this.lastIdentity === null) {
|
|
339
342
|
return { kind: "repeat", best: null };
|
|
340
343
|
}
|
|
341
344
|
this.pendingNoMatchCount += 1;
|
|
342
|
-
if (this.pendingNoMatchCount < DEFAULT_NO_MATCH_CONFIRMATION_COUNT) {
|
|
345
|
+
if (!expired && this.pendingNoMatchCount < DEFAULT_NO_MATCH_CONFIRMATION_COUNT) {
|
|
343
346
|
return { kind: "repeat", best: null };
|
|
344
347
|
}
|
|
345
|
-
this.
|
|
346
|
-
this.pendingNoMatchCount = 0;
|
|
348
|
+
this.reset();
|
|
347
349
|
return { kind: "nomatch" };
|
|
348
350
|
}
|
|
349
351
|
const nextIdentity = getMatchIdentity(best);
|
|
350
352
|
this.pendingNoMatchCount = 0;
|
|
351
|
-
|
|
353
|
+
this.lastObservedAt = now;
|
|
354
|
+
if (!expired && this.lastIdentity === nextIdentity) {
|
|
352
355
|
return { kind: "repeat", best };
|
|
353
356
|
}
|
|
354
357
|
this.lastIdentity = nextIdentity;
|
|
@@ -357,6 +360,7 @@ var DistinctMatchDeduper = class {
|
|
|
357
360
|
reset() {
|
|
358
361
|
this.lastIdentity = null;
|
|
359
362
|
this.pendingNoMatchCount = 0;
|
|
363
|
+
this.lastObservedAt = null;
|
|
360
364
|
}
|
|
361
365
|
};
|
|
362
366
|
|
|
@@ -711,6 +715,19 @@ function normalizeAudioMarkerDetection(raw) {
|
|
|
711
715
|
runnerUpAccuracy: normalizeOptionalNumber(record.runnerUpAccuracy, record.runner_up_accuracy)
|
|
712
716
|
};
|
|
713
717
|
}
|
|
718
|
+
function normalizeLevelRobustDetection(raw) {
|
|
719
|
+
const envelope = raw;
|
|
720
|
+
if (!envelope || typeof envelope.detection !== "object" || !envelope.detection || typeof envelope.diagnostics !== "object" || !envelope.diagnostics || typeof envelope.candidateEligible !== "boolean" || typeof envelope.materialFallbackGuardEligible !== "boolean") {
|
|
721
|
+
throw new Error("Invalid level-robust marker result envelope");
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
...normalizeAudioMarkerDetection(envelope.detection),
|
|
725
|
+
levelPolicy: "levelRobust",
|
|
726
|
+
diagnostics: { ...envelope.diagnostics },
|
|
727
|
+
candidateEligible: envelope.candidateEligible,
|
|
728
|
+
materialFallbackGuardEligible: envelope.materialFallbackGuardEligible
|
|
729
|
+
};
|
|
730
|
+
}
|
|
714
731
|
function isMapLike(raw) {
|
|
715
732
|
return raw instanceof Map || Object.prototype.toString.call(raw) === "[object Map]" && typeof raw.entries === "function";
|
|
716
733
|
}
|
|
@@ -753,11 +770,6 @@ function normalizeResults(rawResults) {
|
|
|
753
770
|
function normalizeVariantMatches(rawResults) {
|
|
754
771
|
return rawResults.filter((item) => typeof item === "object" && item !== null).map((item) => normalizeVariantMatch(item));
|
|
755
772
|
}
|
|
756
|
-
function isInternallyDuplicatedBestMatch(result) {
|
|
757
|
-
return Boolean(
|
|
758
|
-
result && typeof result === "object" && result[INTERNAL_DUPLICATE_MATCH_FLAG] === true
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
773
|
function resolveFactory(moduleLike, names) {
|
|
762
774
|
const fn = getCallable(moduleLike, names);
|
|
763
775
|
return fn ? fn.bind(moduleLike) : null;
|
|
@@ -783,6 +795,10 @@ function createBindingsFromModule(moduleLike) {
|
|
|
783
795
|
"estimatePitchRatioAgainstReferenceFeatures",
|
|
784
796
|
"estimate_pitch_ratio_against_reference_features"
|
|
785
797
|
]);
|
|
798
|
+
const detectAudioMarkerLevelRobustFn = resolveFactory(mod, [
|
|
799
|
+
"detectAudioMarkerLevelRobust",
|
|
800
|
+
"detect_audio_marker_level_robust"
|
|
801
|
+
]);
|
|
786
802
|
const detectAudioMarkerFn = resolveFactory(mod, [
|
|
787
803
|
"detectAudioMarker",
|
|
788
804
|
"detect_audio_marker",
|
|
@@ -927,6 +943,26 @@ function createBindingsFromModule(moduleLike) {
|
|
|
927
943
|
destroy?.call(matcher);
|
|
928
944
|
}
|
|
929
945
|
},
|
|
946
|
+
detectAudioMarkerLevelRobust(pcm16Le, sampleRate, config, limits) {
|
|
947
|
+
if (detectAudioMarkerLevelRobustFn) {
|
|
948
|
+
return normalizeLevelRobustDetection(
|
|
949
|
+
detectAudioMarkerLevelRobustFn(pcm16Le, sampleRate, config, limits)
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
const matcher = createMatcherFactory ? createMatcherFactory() : new MatcherCtor();
|
|
953
|
+
try {
|
|
954
|
+
const fn = getCallable(matcher, [
|
|
955
|
+
"detectAudioMarkerLevelRobust",
|
|
956
|
+
"detect_audio_marker_level_robust"
|
|
957
|
+
]);
|
|
958
|
+
if (!fn) {
|
|
959
|
+
throw new Error("WASM matcher is missing detectAudioMarkerLevelRobust; upgrade the WASM assets or explicitly select levelPolicy: legacy");
|
|
960
|
+
}
|
|
961
|
+
return normalizeLevelRobustDetection(fn.call(matcher, pcm16Le, sampleRate, config, limits));
|
|
962
|
+
} finally {
|
|
963
|
+
getCallable(matcher, ["destroy", "free"])?.call(matcher);
|
|
964
|
+
}
|
|
965
|
+
},
|
|
930
966
|
detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits) {
|
|
931
967
|
if (detectAudioMarkerWithLimitsFn) {
|
|
932
968
|
return normalizeAudioMarkerDetection(
|
|
@@ -971,6 +1007,42 @@ async function getBindings(options) {
|
|
|
971
1007
|
function toUint8ArrayCopy(input) {
|
|
972
1008
|
return new Uint8Array(input);
|
|
973
1009
|
}
|
|
1010
|
+
var typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
1011
|
+
var typedArrayByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
|
|
1012
|
+
var typedArrayTag = Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag).get;
|
|
1013
|
+
var typedArraySet = Uint8Array.prototype.set;
|
|
1014
|
+
function markerByteLength(input) {
|
|
1015
|
+
if (typedArrayTag.call(input) !== "Uint8Array") {
|
|
1016
|
+
throw new TypeError("Audio marker PCM must be a Uint8Array");
|
|
1017
|
+
}
|
|
1018
|
+
return typedArrayByteLength.call(input);
|
|
1019
|
+
}
|
|
1020
|
+
function copyMarkerPcm(input) {
|
|
1021
|
+
const copy = new Uint8Array(markerByteLength(input));
|
|
1022
|
+
typedArraySet.call(copy, input);
|
|
1023
|
+
return copy;
|
|
1024
|
+
}
|
|
1025
|
+
function usesLevelRobust(config) {
|
|
1026
|
+
if (config?.levelPolicy !== void 0) {
|
|
1027
|
+
if (config.levelPolicy !== "levelRobust" && config.levelPolicy !== "legacy") {
|
|
1028
|
+
throw new RangeError("Invalid audio marker levelPolicy");
|
|
1029
|
+
}
|
|
1030
|
+
return config.levelPolicy === "levelRobust";
|
|
1031
|
+
}
|
|
1032
|
+
return ![
|
|
1033
|
+
config?.minSyncScore,
|
|
1034
|
+
config?.minScore,
|
|
1035
|
+
config?.minMargin,
|
|
1036
|
+
config?.minRatio,
|
|
1037
|
+
config?.minConfidence,
|
|
1038
|
+
config?.minVotes
|
|
1039
|
+
].some((value) => value !== void 0);
|
|
1040
|
+
}
|
|
1041
|
+
function detectorConfig(config) {
|
|
1042
|
+
if (!config || config.levelPolicy === void 0) return config;
|
|
1043
|
+
const { levelPolicy: _policy, ...rest } = config;
|
|
1044
|
+
return rest;
|
|
1045
|
+
}
|
|
974
1046
|
function resolveAudioMarkerLimits(limits) {
|
|
975
1047
|
const resolved = {
|
|
976
1048
|
maxPcmBytes: limits?.maxPcmBytes ?? DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES,
|
|
@@ -986,13 +1058,17 @@ function resolveAudioMarkerLimits(limits) {
|
|
|
986
1058
|
return resolved;
|
|
987
1059
|
}
|
|
988
1060
|
function audioMarkerInputFailureReason(pcm16Le, sampleRate, limits) {
|
|
989
|
-
|
|
1061
|
+
const byteLength = markerByteLength(pcm16Le);
|
|
1062
|
+
if (byteLength > limits.maxPcmBytes) {
|
|
990
1063
|
return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
|
|
991
1064
|
}
|
|
992
|
-
if (
|
|
1065
|
+
if (byteLength % 2 !== 0) {
|
|
993
1066
|
return "invalid_pcm16_byte_length";
|
|
994
1067
|
}
|
|
995
|
-
|
|
1068
|
+
if (!Number.isInteger(sampleRate) || sampleRate <= 0 || sampleRate > 4294967295) {
|
|
1069
|
+
return "invalid_sample_rate";
|
|
1070
|
+
}
|
|
1071
|
+
const sampleCount = byteLength / 2;
|
|
996
1072
|
if (sampleCount > limits.maxSamples) {
|
|
997
1073
|
return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
|
|
998
1074
|
}
|
|
@@ -1050,6 +1126,7 @@ var MatcherSession = class {
|
|
|
1050
1126
|
matcher = null;
|
|
1051
1127
|
destroyed = false;
|
|
1052
1128
|
packLoadTail = Promise.resolve();
|
|
1129
|
+
recognitionGeneration = 0;
|
|
1053
1130
|
distinctMatchDeduper = new DistinctMatchDeduper();
|
|
1054
1131
|
constructor(options) {
|
|
1055
1132
|
this.fetcher = options?.fetch;
|
|
@@ -1102,8 +1179,7 @@ var MatcherSession = class {
|
|
|
1102
1179
|
}
|
|
1103
1180
|
this.database = stagedDatabase;
|
|
1104
1181
|
previousDatabase.destroy();
|
|
1105
|
-
this.
|
|
1106
|
-
this.matcher?.resetDistinctMatchState?.();
|
|
1182
|
+
this.resetRecognitionState();
|
|
1107
1183
|
this.events.emit("packload", {
|
|
1108
1184
|
sourceType: normalized.sourceType,
|
|
1109
1185
|
byteLength: normalized.bytes.byteLength
|
|
@@ -1116,8 +1192,7 @@ var MatcherSession = class {
|
|
|
1116
1192
|
try {
|
|
1117
1193
|
await this.ensureLiveReady();
|
|
1118
1194
|
this.databaseOrThrow().addEntry(name, afpType, toUint8ArrayCopy(fingerprint));
|
|
1119
|
-
this.
|
|
1120
|
-
this.matcher?.resetDistinctMatchState?.();
|
|
1195
|
+
this.resetRecognitionState();
|
|
1121
1196
|
} catch (error) {
|
|
1122
1197
|
handleError(this.events, "addEntry", error);
|
|
1123
1198
|
}
|
|
@@ -1131,8 +1206,7 @@ var MatcherSession = class {
|
|
|
1131
1206
|
toUint8ArrayCopy(fingerprint),
|
|
1132
1207
|
metadata
|
|
1133
1208
|
);
|
|
1134
|
-
this.
|
|
1135
|
-
this.matcher?.resetDistinctMatchState?.();
|
|
1209
|
+
this.resetRecognitionState();
|
|
1136
1210
|
} catch (error) {
|
|
1137
1211
|
handleError(this.events, "addOrReplace", error);
|
|
1138
1212
|
}
|
|
@@ -1141,8 +1215,7 @@ var MatcherSession = class {
|
|
|
1141
1215
|
try {
|
|
1142
1216
|
await this.ensureLiveReady();
|
|
1143
1217
|
this.databaseOrThrow().setEntryVariantMetadata(name, encodedMetadata);
|
|
1144
|
-
this.
|
|
1145
|
-
this.matcher?.resetDistinctMatchState?.();
|
|
1218
|
+
this.resetRecognitionState();
|
|
1146
1219
|
} catch (error) {
|
|
1147
1220
|
handleError(this.events, "setEntryVariantMetadata", error);
|
|
1148
1221
|
}
|
|
@@ -1151,8 +1224,7 @@ var MatcherSession = class {
|
|
|
1151
1224
|
try {
|
|
1152
1225
|
await this.ensureLiveReady();
|
|
1153
1226
|
this.databaseOrThrow().removeEntry(name);
|
|
1154
|
-
this.
|
|
1155
|
-
this.matcher?.resetDistinctMatchState?.();
|
|
1227
|
+
this.resetRecognitionState();
|
|
1156
1228
|
} catch (error) {
|
|
1157
1229
|
handleError(this.events, "removeEntry", error);
|
|
1158
1230
|
}
|
|
@@ -1161,8 +1233,7 @@ var MatcherSession = class {
|
|
|
1161
1233
|
try {
|
|
1162
1234
|
await this.ensureLiveReady();
|
|
1163
1235
|
this.databaseOrThrow().clear();
|
|
1164
|
-
this.
|
|
1165
|
-
this.matcher?.resetDistinctMatchState?.();
|
|
1236
|
+
this.resetRecognitionState();
|
|
1166
1237
|
} catch (error) {
|
|
1167
1238
|
handleError(this.events, "clear", error);
|
|
1168
1239
|
}
|
|
@@ -1176,27 +1247,38 @@ var MatcherSession = class {
|
|
|
1176
1247
|
}
|
|
1177
1248
|
}
|
|
1178
1249
|
async match(query, config) {
|
|
1250
|
+
const generation = this.recognitionGeneration;
|
|
1179
1251
|
try {
|
|
1180
1252
|
await this.ensureLiveReady();
|
|
1253
|
+
if (generation !== this.recognitionGeneration) {
|
|
1254
|
+
return [];
|
|
1255
|
+
}
|
|
1181
1256
|
const safeQuery = toUint8ArrayCopy(query);
|
|
1182
1257
|
this.events.emit("querystart", { queryByteLength: safeQuery.byteLength });
|
|
1258
|
+
if (generation !== this.recognitionGeneration || this.destroyed) {
|
|
1259
|
+
return [];
|
|
1260
|
+
}
|
|
1183
1261
|
const results = this.matcherOrThrow().findMatches(this.databaseOrThrow(), safeQuery, config);
|
|
1184
|
-
this.emitDistinctMatchEvent(results[0] ?? null, safeQuery.byteLength, results);
|
|
1262
|
+
this.emitDistinctMatchEvent(generation, results[0] ?? null, safeQuery.byteLength, results);
|
|
1185
1263
|
return results;
|
|
1186
1264
|
} catch (error) {
|
|
1187
1265
|
handleError(this.events, "match", error);
|
|
1188
1266
|
}
|
|
1189
1267
|
}
|
|
1190
1268
|
async bestMatch(query, config) {
|
|
1269
|
+
const generation = this.recognitionGeneration;
|
|
1191
1270
|
try {
|
|
1192
1271
|
await this.ensureLiveReady();
|
|
1272
|
+
if (generation !== this.recognitionGeneration) {
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1193
1275
|
const safeQuery = toUint8ArrayCopy(query);
|
|
1194
1276
|
this.events.emit("querystart", { queryByteLength: safeQuery.byteLength });
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
return best;
|
|
1277
|
+
if (generation !== this.recognitionGeneration || this.destroyed) {
|
|
1278
|
+
return null;
|
|
1198
1279
|
}
|
|
1199
|
-
this.
|
|
1280
|
+
const best = this.matcherOrThrow().bestMatch(this.databaseOrThrow(), safeQuery, config);
|
|
1281
|
+
this.emitDistinctMatchEvent(generation, best, safeQuery.byteLength, best ? [best] : []);
|
|
1200
1282
|
return best;
|
|
1201
1283
|
} catch (error) {
|
|
1202
1284
|
handleError(this.events, "bestMatch", error);
|
|
@@ -1288,15 +1370,24 @@ var MatcherSession = class {
|
|
|
1288
1370
|
handleError(this.events, "estimatePitchRatioAgainstReferenceFeatures", error);
|
|
1289
1371
|
}
|
|
1290
1372
|
}
|
|
1373
|
+
/** Uses level-robust detection by default, preserving explicit legacy thresholds. */
|
|
1291
1374
|
async detectAudioMarker(pcm16Le, sampleRate, config) {
|
|
1292
1375
|
try {
|
|
1293
1376
|
await this.ensureLiveReady();
|
|
1377
|
+
const robust = usesLevelRobust(config);
|
|
1294
1378
|
const limits = resolveAudioMarkerLimits();
|
|
1295
1379
|
const failureReason = audioMarkerInputFailureReason(pcm16Le, sampleRate, limits);
|
|
1296
1380
|
if (failureReason) {
|
|
1297
|
-
return
|
|
1381
|
+
return {
|
|
1382
|
+
...absentAudioMarkerDetection(config, failureReason),
|
|
1383
|
+
...robust ? { levelPolicy: "levelRobust", candidateEligible: false, materialFallbackGuardEligible: false } : {}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
const safePcm = copyMarkerPcm(pcm16Le);
|
|
1387
|
+
config = detectorConfig(config);
|
|
1388
|
+
if (robust) {
|
|
1389
|
+
return this.bindings.detectAudioMarkerLevelRobust(safePcm, sampleRate, config);
|
|
1298
1390
|
}
|
|
1299
|
-
const safePcm = toUint8ArrayCopy(pcm16Le);
|
|
1300
1391
|
const matcher = this.matcherOrThrow();
|
|
1301
1392
|
if (matcher.detectAudioMarker) {
|
|
1302
1393
|
return matcher.detectAudioMarker(safePcm, sampleRate, config);
|
|
@@ -1313,6 +1404,7 @@ var MatcherSession = class {
|
|
|
1313
1404
|
async detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits) {
|
|
1314
1405
|
try {
|
|
1315
1406
|
await this.ensureLiveReady();
|
|
1407
|
+
const robust = usesLevelRobust(config);
|
|
1316
1408
|
const resolvedLimits = resolveAudioMarkerLimits(limits);
|
|
1317
1409
|
const failureReason = audioMarkerInputFailureReason(
|
|
1318
1410
|
pcm16Le,
|
|
@@ -1320,9 +1412,16 @@ var MatcherSession = class {
|
|
|
1320
1412
|
resolvedLimits
|
|
1321
1413
|
);
|
|
1322
1414
|
if (failureReason) {
|
|
1323
|
-
return
|
|
1415
|
+
return {
|
|
1416
|
+
...absentAudioMarkerDetection(config, failureReason),
|
|
1417
|
+
...robust ? { levelPolicy: "levelRobust", candidateEligible: false, materialFallbackGuardEligible: false } : {}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
const safePcm = copyMarkerPcm(pcm16Le);
|
|
1421
|
+
config = detectorConfig(config);
|
|
1422
|
+
if (robust) {
|
|
1423
|
+
return this.bindings.detectAudioMarkerLevelRobust(safePcm, sampleRate, config, resolvedLimits);
|
|
1324
1424
|
}
|
|
1325
|
-
const safePcm = toUint8ArrayCopy(pcm16Le);
|
|
1326
1425
|
const matcher = this.matcherOrThrow();
|
|
1327
1426
|
if (matcher.detectAudioMarkerWithLimits) {
|
|
1328
1427
|
return matcher.detectAudioMarkerWithLimits(
|
|
@@ -1346,10 +1445,17 @@ var MatcherSession = class {
|
|
|
1346
1445
|
}
|
|
1347
1446
|
}
|
|
1348
1447
|
async matchWithVariants(query, pitchQuery, config, variantConfig) {
|
|
1448
|
+
const generation = this.recognitionGeneration;
|
|
1349
1449
|
try {
|
|
1350
1450
|
await this.ensureLiveReady();
|
|
1451
|
+
if (generation !== this.recognitionGeneration) {
|
|
1452
|
+
return [];
|
|
1453
|
+
}
|
|
1351
1454
|
const safeQuery = toUint8ArrayCopy(query);
|
|
1352
1455
|
this.events.emit("querystart", { queryByteLength: safeQuery.byteLength });
|
|
1456
|
+
if (generation !== this.recognitionGeneration || this.destroyed) {
|
|
1457
|
+
return [];
|
|
1458
|
+
}
|
|
1353
1459
|
const results = this.matcherOrThrow().findMatchesWithVariants(
|
|
1354
1460
|
this.databaseOrThrow(),
|
|
1355
1461
|
safeQuery,
|
|
@@ -1358,6 +1464,7 @@ var MatcherSession = class {
|
|
|
1358
1464
|
variantConfig
|
|
1359
1465
|
);
|
|
1360
1466
|
this.emitDistinctMatchEvent(
|
|
1467
|
+
generation,
|
|
1361
1468
|
results[0]?.matchResult ?? null,
|
|
1362
1469
|
safeQuery.byteLength,
|
|
1363
1470
|
results.map((result) => result.matchResult)
|
|
@@ -1368,10 +1475,17 @@ var MatcherSession = class {
|
|
|
1368
1475
|
}
|
|
1369
1476
|
}
|
|
1370
1477
|
async bestMatchWithVariants(query, pitchQuery, config, variantConfig) {
|
|
1478
|
+
const generation = this.recognitionGeneration;
|
|
1371
1479
|
try {
|
|
1372
1480
|
await this.ensureLiveReady();
|
|
1481
|
+
if (generation !== this.recognitionGeneration) {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1373
1484
|
const safeQuery = toUint8ArrayCopy(query);
|
|
1374
1485
|
this.events.emit("querystart", { queryByteLength: safeQuery.byteLength });
|
|
1486
|
+
if (generation !== this.recognitionGeneration || this.destroyed) {
|
|
1487
|
+
return null;
|
|
1488
|
+
}
|
|
1375
1489
|
const best = this.matcherOrThrow().bestMatchWithVariants(
|
|
1376
1490
|
this.databaseOrThrow(),
|
|
1377
1491
|
safeQuery,
|
|
@@ -1380,6 +1494,7 @@ var MatcherSession = class {
|
|
|
1380
1494
|
variantConfig
|
|
1381
1495
|
);
|
|
1382
1496
|
this.emitDistinctMatchEvent(
|
|
1497
|
+
generation,
|
|
1383
1498
|
best?.matchResult ?? null,
|
|
1384
1499
|
safeQuery.byteLength,
|
|
1385
1500
|
best ? [best.matchResult] : []
|
|
@@ -1389,10 +1504,19 @@ var MatcherSession = class {
|
|
|
1389
1504
|
handleError(this.events, "bestMatchWithVariants", error);
|
|
1390
1505
|
}
|
|
1391
1506
|
}
|
|
1507
|
+
/** End the current recognition run without removing loaded entries.
|
|
1508
|
+
* Pending queries from the previous run cannot update event suppression state.
|
|
1509
|
+
*/
|
|
1510
|
+
resetRecognitionState() {
|
|
1511
|
+
this.recognitionGeneration += 1;
|
|
1512
|
+
this.distinctMatchDeduper.reset();
|
|
1513
|
+
this.matcher?.resetDistinctMatchState?.();
|
|
1514
|
+
}
|
|
1392
1515
|
destroy() {
|
|
1393
1516
|
if (this.destroyed) {
|
|
1394
1517
|
return;
|
|
1395
1518
|
}
|
|
1519
|
+
this.resetRecognitionState();
|
|
1396
1520
|
this.destroyed = true;
|
|
1397
1521
|
this.matcher?.destroy();
|
|
1398
1522
|
this.database?.destroy();
|
|
@@ -1456,7 +1580,10 @@ var MatcherSession = class {
|
|
|
1456
1580
|
}
|
|
1457
1581
|
return this.database;
|
|
1458
1582
|
}
|
|
1459
|
-
emitDistinctMatchEvent(best, queryByteLength, results) {
|
|
1583
|
+
emitDistinctMatchEvent(generation, best, queryByteLength, results) {
|
|
1584
|
+
if (generation !== this.recognitionGeneration || this.destroyed) {
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1460
1587
|
const observation = this.distinctMatchDeduper.observe(best);
|
|
1461
1588
|
if (observation.kind === "match") {
|
|
1462
1589
|
this.events.emit("match", { results, best: observation.best });
|
|
@@ -1489,6 +1616,7 @@ export {
|
|
|
1489
1616
|
DEFAULT_MAX_AUDIO_MARKER_SAMPLES,
|
|
1490
1617
|
DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS,
|
|
1491
1618
|
DEFAULT_MAX_PACK_BYTES,
|
|
1619
|
+
MATCH_CONTINUITY_GAP_MS,
|
|
1492
1620
|
MatcherSession,
|
|
1493
1621
|
__resetMatcherWasmForTests,
|
|
1494
1622
|
__setMatcherEmbeddedPackForTests,
|