@sorisdk/matcher 0.6.5 → 0.6.7

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 CHANGED
@@ -89,6 +89,39 @@ per-entry limits remain independently enforced.
89
89
 
90
90
  If you want campaign events and browser recognition flow on top of matching, use `@sorisdk/web-audio`.
91
91
 
92
+ ## Audio-marker detection limits
93
+
94
+ `MatcherSession.detectAudioMarker(...)` preserves its existing signature and
95
+ preflights PCM before the browser copy into WASM. The defaults are 336,000
96
+ PCM16 bytes, 168,000 samples, 3,500 ms, and 1,000,000,000 conservative detector
97
+ work units. These are exported as `DEFAULT_MAX_AUDIO_MARKER_*` constants.
98
+ Input-budget failures return a normal absent detection whose `reason` is
99
+ `pcm_input_budget_exceeded`; work failures use
100
+ `detector_work_budget_exceeded`, and checked-estimate overflow uses
101
+ `detector_work_overflow`. Input is rejected, never silently truncated.
102
+
103
+ Use the additive API only for bounded, trusted offline input:
104
+
105
+ ```ts
106
+ const detection = await session.detectAudioMarkerWithLimits(
107
+ trustedPcm16Le,
108
+ 48_000,
109
+ undefined,
110
+ {
111
+ maxPcmBytes: 960_000,
112
+ maxSamples: 480_000,
113
+ maxDurationMillis: 10_000,
114
+ maxWorkUnits: 3_000_000_000
115
+ }
116
+ );
117
+ ```
118
+
119
+ The generated WASM module exposes the corresponding
120
+ `detectAudioMarkerWithLimits` function and method. It inspects the JavaScript
121
+ `Uint8Array` length before allocating a Rust-owned copy. Raising these budgets
122
+ permits greater memory and CPU use; it does not protect an untrusted host after
123
+ the caller opts into the larger envelope.
124
+
92
125
  ## Metadata lookup
93
126
 
94
127
  Audiopack footer metadata is available through explicit lookup rather than being
@@ -24,7 +24,9 @@ export class WasmMatcherEngine {
24
24
  bestMatch(database: WasmMatcherDatabase, query: Uint8Array, config?: any | null): any;
25
25
  bestMatchWithVariants(database: WasmMatcherDatabase, query: Uint8Array, pitch_query: any, config?: any | null, variant_config?: any | null): any;
26
26
  detectAudioMarker(pcm16_le: Uint8Array, sample_rate: number, config?: any | null): any;
27
+ detectAudioMarkerWithLimits(pcm16_le: Uint8Array, sample_rate: number, config?: any | null, limits?: any | null): any;
27
28
  detectPatternMark(pcm16_le: Uint8Array, sample_rate: number, config?: any | null): any;
29
+ detectPatternMarkWithLimits(pcm16_le: Uint8Array, sample_rate: number, config?: any | null, limits?: any | null): any;
28
30
  estimatePitchRatio(pcm16_le: Uint8Array, sample_rate: number): any;
29
31
  estimatePitchRatioAgainstReference(pcm16_le: Uint8Array, sample_rate: number, reference_pitch_hz: number, reference_confidence?: number | null): any;
30
32
  estimatePitchRatioAgainstSpectralFeatures(pcm16_le: Uint8Array, sample_rate: number, reference_features: string, reference_position_millis: number, reference_confidence?: number | null): any;
@@ -38,8 +40,12 @@ export class WasmMatcherEngine {
38
40
 
39
41
  export function detectAudioMarker(pcm16_le: Uint8Array, sample_rate: number, config?: any | null): any;
40
42
 
43
+ export function detectAudioMarkerWithLimits(pcm16_le: Uint8Array, sample_rate: number, config?: any | null, limits?: any | null): any;
44
+
41
45
  export function detectPatternMark(pcm16_le: Uint8Array, sample_rate: number, config?: any | null): any;
42
46
 
47
+ export function detectPatternMarkWithLimits(pcm16_le: Uint8Array, sample_rate: number, config?: any | null, limits?: any | null): any;
48
+
43
49
  export function estimatePitchRatio(pcm16_le: Uint8Array, sample_rate: number): any;
44
50
 
45
51
  export function estimatePitchRatioAgainstReference(pcm16_le: Uint8Array, sample_rate: number, reference_pitch_hz: number, reference_confidence?: number | null): any;
@@ -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, detectPatternMark, estimatePitchRatio, estimatePitchRatioAgainstReference, estimatePitchRatioAgainstSpectralFeatures, extractPitchReference, extractSpectralShiftReference, init_matcher_wasm
8
+ WasmMatcherDatabase, WasmMatcherEngine, detectAudioMarker, detectAudioMarkerWithLimits, detectPatternMark, detectPatternMarkWithLimits, estimatePitchRatio, estimatePitchRatioAgainstReference, estimatePitchRatioAgainstSpectralFeatures, extractPitchReference, extractSpectralShiftReference, init_matcher_wasm
9
9
  } from "./core_bg.js";
@@ -195,9 +195,21 @@ export class WasmMatcherEngine {
195
195
  * @returns {any}
196
196
  */
197
197
  detectAudioMarker(pcm16_le, sample_rate, config) {
198
- const ptr0 = passArray8ToWasm0(pcm16_le, wasm.__wbindgen_malloc);
199
- const len0 = WASM_VECTOR_LEN;
200
- const ret = wasm.wasmmatcherengine_detectAudioMarker(this.__wbg_ptr, ptr0, len0, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
198
+ const ret = wasm.wasmmatcherengine_detectAudioMarker(this.__wbg_ptr, pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
199
+ if (ret[2]) {
200
+ throw takeFromExternrefTable0(ret[1]);
201
+ }
202
+ return takeFromExternrefTable0(ret[0]);
203
+ }
204
+ /**
205
+ * @param {Uint8Array} pcm16_le
206
+ * @param {number} sample_rate
207
+ * @param {any | null} [config]
208
+ * @param {any | null} [limits]
209
+ * @returns {any}
210
+ */
211
+ detectAudioMarkerWithLimits(pcm16_le, sample_rate, config, limits) {
212
+ const ret = wasm.wasmmatcherengine_detectAudioMarkerWithLimits(this.__wbg_ptr, pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config), isLikeNone(limits) ? 0 : addToExternrefTable0(limits));
201
213
  if (ret[2]) {
202
214
  throw takeFromExternrefTable0(ret[1]);
203
215
  }
@@ -210,9 +222,21 @@ export class WasmMatcherEngine {
210
222
  * @returns {any}
211
223
  */
212
224
  detectPatternMark(pcm16_le, sample_rate, config) {
213
- const ptr0 = passArray8ToWasm0(pcm16_le, wasm.__wbindgen_malloc);
214
- const len0 = WASM_VECTOR_LEN;
215
- const ret = wasm.wasmmatcherengine_detectPatternMark(this.__wbg_ptr, ptr0, len0, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
225
+ const ret = wasm.wasmmatcherengine_detectPatternMark(this.__wbg_ptr, pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
226
+ if (ret[2]) {
227
+ throw takeFromExternrefTable0(ret[1]);
228
+ }
229
+ return takeFromExternrefTable0(ret[0]);
230
+ }
231
+ /**
232
+ * @param {Uint8Array} pcm16_le
233
+ * @param {number} sample_rate
234
+ * @param {any | null} [config]
235
+ * @param {any | null} [limits]
236
+ * @returns {any}
237
+ */
238
+ detectPatternMarkWithLimits(pcm16_le, sample_rate, config, limits) {
239
+ const ret = wasm.wasmmatcherengine_detectPatternMarkWithLimits(this.__wbg_ptr, pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config), isLikeNone(limits) ? 0 : addToExternrefTable0(limits));
216
240
  if (ret[2]) {
217
241
  throw takeFromExternrefTable0(ret[1]);
218
242
  }
@@ -348,9 +372,22 @@ if (Symbol.dispose) WasmMatcherEngine.prototype[Symbol.dispose] = WasmMatcherEng
348
372
  * @returns {any}
349
373
  */
350
374
  export function detectAudioMarker(pcm16_le, sample_rate, config) {
351
- const ptr0 = passArray8ToWasm0(pcm16_le, wasm.__wbindgen_malloc);
352
- const len0 = WASM_VECTOR_LEN;
353
- const ret = wasm.detectAudioMarker(ptr0, len0, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
375
+ const ret = wasm.detectAudioMarker(pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
376
+ if (ret[2]) {
377
+ throw takeFromExternrefTable0(ret[1]);
378
+ }
379
+ return takeFromExternrefTable0(ret[0]);
380
+ }
381
+
382
+ /**
383
+ * @param {Uint8Array} pcm16_le
384
+ * @param {number} sample_rate
385
+ * @param {any | null} [config]
386
+ * @param {any | null} [limits]
387
+ * @returns {any}
388
+ */
389
+ export function detectAudioMarkerWithLimits(pcm16_le, sample_rate, config, limits) {
390
+ const ret = wasm.detectAudioMarkerWithLimits(pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config), isLikeNone(limits) ? 0 : addToExternrefTable0(limits));
354
391
  if (ret[2]) {
355
392
  throw takeFromExternrefTable0(ret[1]);
356
393
  }
@@ -364,9 +401,22 @@ export function detectAudioMarker(pcm16_le, sample_rate, config) {
364
401
  * @returns {any}
365
402
  */
366
403
  export function detectPatternMark(pcm16_le, sample_rate, config) {
367
- const ptr0 = passArray8ToWasm0(pcm16_le, wasm.__wbindgen_malloc);
368
- const len0 = WASM_VECTOR_LEN;
369
- const ret = wasm.detectPatternMark(ptr0, len0, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
404
+ const ret = wasm.detectPatternMark(pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config));
405
+ if (ret[2]) {
406
+ throw takeFromExternrefTable0(ret[1]);
407
+ }
408
+ return takeFromExternrefTable0(ret[0]);
409
+ }
410
+
411
+ /**
412
+ * @param {Uint8Array} pcm16_le
413
+ * @param {number} sample_rate
414
+ * @param {any | null} [config]
415
+ * @param {any | null} [limits]
416
+ * @returns {any}
417
+ */
418
+ export function detectPatternMarkWithLimits(pcm16_le, sample_rate, config, limits) {
419
+ const ret = wasm.detectPatternMarkWithLimits(pcm16_le, sample_rate, isLikeNone(config) ? 0 : addToExternrefTable0(config), isLikeNone(limits) ? 0 : addToExternrefTable0(limits));
370
420
  if (ret[2]) {
371
421
  throw takeFromExternrefTable0(ret[1]);
372
422
  }
Binary file
@@ -3,8 +3,9 @@
3
3
  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
- export const detectAudioMarker: (a: number, b: number, c: number, d: number) => [number, number, number];
7
- export const detectPatternMark: (a: number, b: number, c: number, d: number) => [number, number, number];
6
+ export const detectAudioMarker: (a: any, b: number, c: number) => [number, number, number];
7
+ export const detectAudioMarkerWithLimits: (a: any, b: number, c: number, d: number) => [number, number, number];
8
+ export const detectPatternMarkWithLimits: (a: any, b: number, c: number, d: number) => [number, number, number];
8
9
  export const estimatePitchRatio: (a: number, b: number, c: number) => [number, number, number];
9
10
  export const estimatePitchRatioAgainstReference: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
10
11
  export const estimatePitchRatioAgainstSpectralFeatures: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number];
@@ -24,8 +25,10 @@ export const wasmmatcherdatabase_removeEntry: (a: number, b: number, c: number)
24
25
  export const wasmmatcherdatabase_setEntryVariantMetadata: (a: number, b: number, c: number, d: number, e: number) => [number, number];
25
26
  export const wasmmatcherengine_bestMatch: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
26
27
  export const wasmmatcherengine_bestMatchWithVariants: (a: number, b: number, c: number, d: number, e: any, f: number, g: number) => [number, number, number];
27
- export const wasmmatcherengine_detectAudioMarker: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
28
- export const wasmmatcherengine_detectPatternMark: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
28
+ export const wasmmatcherengine_detectAudioMarker: (a: number, b: any, c: number, d: number) => [number, number, number];
29
+ 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
+ export const wasmmatcherengine_detectPatternMarkWithLimits: (a: number, b: any, c: number, d: number, e: number) => [number, number, number];
29
32
  export const wasmmatcherengine_estimatePitchRatio: (a: number, b: number, c: number, d: number) => [number, number, number];
30
33
  export const wasmmatcherengine_estimatePitchRatioAgainstReference: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number];
31
34
  export const wasmmatcherengine_estimatePitchRatioAgainstSpectralFeatures: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => [number, number, number];
@@ -36,6 +39,7 @@ export const wasmmatcherengine_matchQueryWithVariants: (a: number, b: number, c:
36
39
  export const wasmmatcherengine_new: () => number;
37
40
  export const wasmmatcherengine_resetDistinctMatchState: (a: number) => void;
38
41
  export const init_matcher_wasm: () => void;
42
+ export const detectPatternMark: (a: any, b: number, c: number) => [number, number, number];
39
43
  export const __wbindgen_malloc: (a: number, b: number) => number;
40
44
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
41
45
  export const __wbindgen_exn_store: (a: number) => void;
package/dist/index.d.ts CHANGED
@@ -13,6 +13,13 @@ interface AudioMarkerCode {
13
13
  code: string;
14
14
  pattern: string;
15
15
  }
16
+ declare const DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES = 336000;
17
+ declare const DEFAULT_MAX_AUDIO_MARKER_SAMPLES = 168000;
18
+ declare const DEFAULT_MAX_AUDIO_MARKER_DURATION_MILLIS = 3500;
19
+ declare const DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS = 1000000000;
20
+ declare const AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON = "pcm_input_budget_exceeded";
21
+ declare const AUDIO_MARKER_WORK_BUDGET_EXCEEDED_REASON = "detector_work_budget_exceeded";
22
+ declare const AUDIO_MARKER_WORK_OVERFLOW_REASON = "detector_work_overflow";
16
23
  interface AudioMarkerConfig {
17
24
  bandLowHz?: number;
18
25
  bandHighHz?: number;
@@ -29,6 +36,13 @@ interface AudioMarkerConfig {
29
36
  maxSyncCandidates?: number;
30
37
  codebook?: AudioMarkerCode[];
31
38
  }
39
+ /** Trusted-input overrides for one-shot audio-marker detection. */
40
+ interface AudioMarkerLimits {
41
+ maxPcmBytes?: number;
42
+ maxSamples?: number;
43
+ maxDurationMillis?: number;
44
+ maxWorkUnits?: number;
45
+ }
32
46
  interface AudioMarkerDetection {
33
47
  pattern?: string | null;
34
48
  code?: string | null;
@@ -237,6 +251,8 @@ declare class MatcherSession {
237
251
  estimatePitchRatioAgainstReference(pcm16Le: Uint8Array, sampleRate: number, referencePitchHz: number, referenceConfidence?: number | null): Promise<PitchRatioEstimate>;
238
252
  estimatePitchRatioAgainstReferenceFeatures(pcm16Le: Uint8Array, sampleRate: number, referenceFeatures: string, referencePositionMillis: number, referenceConfidence?: number | null): Promise<PitchRatioEstimate>;
239
253
  detectAudioMarker(pcm16Le: Uint8Array, sampleRate: number, config?: AudioMarkerConfig): Promise<AudioMarkerDetection>;
254
+ /** Detect an audio marker with explicit limits for trusted offline input. */
255
+ detectAudioMarkerWithLimits(pcm16Le: Uint8Array, sampleRate: number, config: AudioMarkerConfig | undefined, limits: AudioMarkerLimits): Promise<AudioMarkerDetection>;
240
256
  matchWithVariants(query: Uint8Array, pitchQuery: VariantMatchQuery, config?: MatchConfig, variantConfig?: VariantMatchConfig): Promise<VariantMatch[]>;
241
257
  bestMatchWithVariants(query: Uint8Array, pitchQuery: VariantMatchQuery, config?: MatchConfig, variantConfig?: VariantMatchConfig): Promise<VariantMatch | null>;
242
258
  destroy(): void;
@@ -252,4 +268,4 @@ declare function __setMatcherWasmModuleForTests(moduleLike: unknown): void;
252
268
  declare function __setMatcherEmbeddedPackForTests(bytes: Uint8Array): void;
253
269
  declare function __resetMatcherWasmForTests(): void;
254
270
 
255
- export { AudioFingerprintType, type AudioMarkerCode, type AudioMarkerConfig, type AudioMarkerDetection, DEFAULT_MAX_PACK_BYTES, type EntryMetadata, type InitMatcherOptions, type InitMatcherResult, 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 };
271
+ 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 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, 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
@@ -198,6 +198,12 @@ function assertKnownByteLength(byteLength, maxPackBytes) {
198
198
  throw packSizeError(maxPackBytes);
199
199
  }
200
200
  }
201
+ function cancelWithoutWaiting(cancel) {
202
+ try {
203
+ void cancel().catch(() => void 0);
204
+ } catch {
205
+ }
206
+ }
201
207
  function classifyPackSource(source) {
202
208
  if (source instanceof Uint8Array) {
203
209
  return "uint8array";
@@ -231,7 +237,8 @@ function resolveFetch3(fetcher) {
231
237
  }
232
238
  return globalThis.fetch.bind(globalThis);
233
239
  }
234
- async function readResponseAsBytes(response, maxPackBytes) {
240
+ async function readResponseAsBytes(response, maxPackBytes = DEFAULT_MAX_PACK_BYTES) {
241
+ maxPackBytes = validateMaxPackBytes(maxPackBytes);
235
242
  if (!response.ok) {
236
243
  throw new Error(`Failed to load pack: HTTP ${response.status}`);
237
244
  }
@@ -239,7 +246,13 @@ async function readResponseAsBytes(response, maxPackBytes) {
239
246
  if (contentLength !== null && /^\d+$/.test(contentLength.trim())) {
240
247
  const declaredLength = Number(contentLength);
241
248
  if (Number.isSafeInteger(declaredLength)) {
242
- assertKnownByteLength(declaredLength, maxPackBytes);
249
+ if (declaredLength > maxPackBytes) {
250
+ const body = response.body;
251
+ if (body) {
252
+ cancelWithoutWaiting(() => body.cancel());
253
+ }
254
+ throw packSizeError(maxPackBytes);
255
+ }
243
256
  }
244
257
  }
245
258
  if (!response.body) {
@@ -256,7 +269,7 @@ async function readResponseAsBytes(response, maxPackBytes) {
256
269
  }
257
270
  byteLength += value.byteLength;
258
271
  if (byteLength > maxPackBytes) {
259
- await reader.cancel();
272
+ cancelWithoutWaiting(() => reader.cancel());
260
273
  throw packSizeError(maxPackBytes);
261
274
  }
262
275
  chunks.push(value);
@@ -297,6 +310,21 @@ async function normalizePackSource(source, fetcher, maxPackBytes = DEFAULT_MAX_P
297
310
  throw new Error("Unsupported pack source type");
298
311
  }
299
312
 
313
+ // src/types.ts
314
+ var AudioFingerprintType = /* @__PURE__ */ ((AudioFingerprintType3) => {
315
+ AudioFingerprintType3[AudioFingerprintType3["Livestream"] = 1] = "Livestream";
316
+ AudioFingerprintType3[AudioFingerprintType3["ArchiveCf"] = 2] = "ArchiveCf";
317
+ AudioFingerprintType3[AudioFingerprintType3["ArchivePg"] = 4] = "ArchivePg";
318
+ return AudioFingerprintType3;
319
+ })(AudioFingerprintType || {});
320
+ var DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES = 336e3;
321
+ var DEFAULT_MAX_AUDIO_MARKER_SAMPLES = 168e3;
322
+ var DEFAULT_MAX_AUDIO_MARKER_DURATION_MILLIS = 3500;
323
+ var DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS = 1e9;
324
+ var AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON = "pcm_input_budget_exceeded";
325
+ var AUDIO_MARKER_WORK_BUDGET_EXCEEDED_REASON = "detector_work_budget_exceeded";
326
+ var AUDIO_MARKER_WORK_OVERFLOW_REASON = "detector_work_overflow";
327
+
300
328
  // src/match-deduper.ts
301
329
  var DEFAULT_NO_MATCH_CONFIRMATION_COUNT = 2;
302
330
  function getMatchIdentity(match) {
@@ -504,6 +532,23 @@ var WrappedMatcher = class {
504
532
  detectAudioMarkerFn.call(this.inner, pcm16Le, sampleRate, config)
505
533
  );
506
534
  }
535
+ const detectAudioMarkerWithLimitsFn = getCallable(this.inner, [
536
+ "detectAudioMarkerWithLimits",
537
+ "detect_audio_marker_with_limits",
538
+ "detectPatternMarkWithLimits",
539
+ "detect_pattern_mark_with_limits"
540
+ ]);
541
+ if (detectAudioMarkerWithLimitsFn) {
542
+ this.detectAudioMarkerWithLimits = (pcm16Le, sampleRate, config, limits) => normalizeAudioMarkerDetection(
543
+ detectAudioMarkerWithLimitsFn.call(
544
+ this.inner,
545
+ pcm16Le,
546
+ sampleRate,
547
+ config,
548
+ limits
549
+ )
550
+ );
551
+ }
507
552
  }
508
553
  inner;
509
554
  estimatePitchRatio;
@@ -511,6 +556,7 @@ var WrappedMatcher = class {
511
556
  estimatePitchRatioAgainstReference;
512
557
  estimatePitchRatioAgainstReferenceFeatures;
513
558
  detectAudioMarker;
559
+ detectAudioMarkerWithLimits;
514
560
  findMatches(database, query, config) {
515
561
  const fn = getCallable(this.inner, ["findMatches", "find_matches", "matchQuery", "match_query", "match"]);
516
562
  if (!fn) {
@@ -743,6 +789,12 @@ function createBindingsFromModule(moduleLike) {
743
789
  "detectPatternMark",
744
790
  "detect_pattern_mark"
745
791
  ]);
792
+ const detectAudioMarkerWithLimitsFn = resolveFactory(mod, [
793
+ "detectAudioMarkerWithLimits",
794
+ "detect_audio_marker_with_limits",
795
+ "detectPatternMarkWithLimits",
796
+ "detect_pattern_mark_with_limits"
797
+ ]);
746
798
  const DatabaseCtor = mod.WasmMatcherDatabase ?? mod.MatcherDatabase ?? mod.Database;
747
799
  const MatcherCtor = mod.WasmMatcherEngine ?? mod.MatcherEngine ?? mod.Matcher ?? mod.AudioMatcher;
748
800
  if (!createDatabaseFactory && !DatabaseCtor) {
@@ -874,6 +926,24 @@ function createBindingsFromModule(moduleLike) {
874
926
  const destroy = getCallable(matcher, ["destroy", "free"]);
875
927
  destroy?.call(matcher);
876
928
  }
929
+ },
930
+ detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits) {
931
+ if (detectAudioMarkerWithLimitsFn) {
932
+ return normalizeAudioMarkerDetection(
933
+ detectAudioMarkerWithLimitsFn(pcm16Le, sampleRate, config, limits)
934
+ );
935
+ }
936
+ const matcher = createMatcherFactory ? createMatcherFactory() : new MatcherCtor();
937
+ try {
938
+ const wrapped = new WrappedMatcher(matcher);
939
+ if (!wrapped.detectAudioMarkerWithLimits) {
940
+ throw new Error("WASM matcher is missing detectAudioMarkerWithLimits-compatible method");
941
+ }
942
+ return wrapped.detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits);
943
+ } finally {
944
+ const destroy = getCallable(matcher, ["destroy", "free"]);
945
+ destroy?.call(matcher);
946
+ }
877
947
  }
878
948
  };
879
949
  }
@@ -901,6 +971,61 @@ async function getBindings(options) {
901
971
  function toUint8ArrayCopy(input) {
902
972
  return new Uint8Array(input);
903
973
  }
974
+ function resolveAudioMarkerLimits(limits) {
975
+ const resolved = {
976
+ maxPcmBytes: limits?.maxPcmBytes ?? DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES,
977
+ maxSamples: limits?.maxSamples ?? DEFAULT_MAX_AUDIO_MARKER_SAMPLES,
978
+ maxDurationMillis: limits?.maxDurationMillis ?? DEFAULT_MAX_AUDIO_MARKER_DURATION_MILLIS,
979
+ maxWorkUnits: limits?.maxWorkUnits ?? DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS
980
+ };
981
+ for (const [name, value] of Object.entries(resolved)) {
982
+ if (!Number.isSafeInteger(value) || value < 0) {
983
+ throw new RangeError(`Invalid audio marker limits: ${name} must be a non-negative safe integer`);
984
+ }
985
+ }
986
+ return resolved;
987
+ }
988
+ function audioMarkerInputFailureReason(pcm16Le, sampleRate, limits) {
989
+ if (pcm16Le.byteLength > limits.maxPcmBytes) {
990
+ return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
991
+ }
992
+ if (pcm16Le.byteLength % 2 !== 0) {
993
+ return "invalid_pcm16_byte_length";
994
+ }
995
+ const sampleCount = pcm16Le.byteLength / 2;
996
+ if (sampleCount > limits.maxSamples) {
997
+ return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
998
+ }
999
+ if (Number.isSafeInteger(sampleRate) && sampleRate > 0 && sampleCount * 1e3 > limits.maxDurationMillis * sampleRate) {
1000
+ return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
1001
+ }
1002
+ return null;
1003
+ }
1004
+ function absentAudioMarkerDetection(config, reason) {
1005
+ return {
1006
+ pattern: null,
1007
+ code: null,
1008
+ confidence: null,
1009
+ score: 0,
1010
+ margin: 0,
1011
+ accuracy: 0,
1012
+ bandLowHz: config?.bandLowHz ?? 15e3,
1013
+ bandHighHz: config?.bandHighHz ?? 15800,
1014
+ syncScore: 0,
1015
+ chipCount: 0,
1016
+ frameStartSample: 0,
1017
+ frameCount: 0,
1018
+ voteCount: 0,
1019
+ reason,
1020
+ candidatePattern: null,
1021
+ candidateCode: null,
1022
+ candidateConfidence: null,
1023
+ runnerUpPattern: null,
1024
+ runnerUpCode: null,
1025
+ runnerUpScore: null,
1026
+ runnerUpAccuracy: null
1027
+ };
1028
+ }
904
1029
  function handleError(emitter, phase, unknownError) {
905
1030
  const error = unknownError instanceof Error ? unknownError : new Error(String(unknownError));
906
1031
  emitter.emit("error", { phase, error });
@@ -1166,6 +1291,11 @@ var MatcherSession = class {
1166
1291
  async detectAudioMarker(pcm16Le, sampleRate, config) {
1167
1292
  try {
1168
1293
  await this.ensureLiveReady();
1294
+ const limits = resolveAudioMarkerLimits();
1295
+ const failureReason = audioMarkerInputFailureReason(pcm16Le, sampleRate, limits);
1296
+ if (failureReason) {
1297
+ return absentAudioMarkerDetection(config, failureReason);
1298
+ }
1169
1299
  const safePcm = toUint8ArrayCopy(pcm16Le);
1170
1300
  const matcher = this.matcherOrThrow();
1171
1301
  if (matcher.detectAudioMarker) {
@@ -1179,6 +1309,42 @@ var MatcherSession = class {
1179
1309
  handleError(this.events, "detectAudioMarker", error);
1180
1310
  }
1181
1311
  }
1312
+ /** Detect an audio marker with explicit limits for trusted offline input. */
1313
+ async detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits) {
1314
+ try {
1315
+ await this.ensureLiveReady();
1316
+ const resolvedLimits = resolveAudioMarkerLimits(limits);
1317
+ const failureReason = audioMarkerInputFailureReason(
1318
+ pcm16Le,
1319
+ sampleRate,
1320
+ resolvedLimits
1321
+ );
1322
+ if (failureReason) {
1323
+ return absentAudioMarkerDetection(config, failureReason);
1324
+ }
1325
+ const safePcm = toUint8ArrayCopy(pcm16Le);
1326
+ const matcher = this.matcherOrThrow();
1327
+ if (matcher.detectAudioMarkerWithLimits) {
1328
+ return matcher.detectAudioMarkerWithLimits(
1329
+ safePcm,
1330
+ sampleRate,
1331
+ config,
1332
+ resolvedLimits
1333
+ );
1334
+ }
1335
+ if (!this.bindings) {
1336
+ throw new Error("Matcher bindings are not initialized");
1337
+ }
1338
+ return this.bindings.detectAudioMarkerWithLimits(
1339
+ safePcm,
1340
+ sampleRate,
1341
+ config,
1342
+ resolvedLimits
1343
+ );
1344
+ } catch (error) {
1345
+ handleError(this.events, "detectAudioMarkerWithLimits", error);
1346
+ }
1347
+ }
1182
1348
  async matchWithVariants(query, pitchQuery, config, variantConfig) {
1183
1349
  try {
1184
1350
  await this.ensureLiveReady();
@@ -1313,16 +1479,15 @@ function __resetMatcherWasmForTests() {
1313
1479
  __resetEmbeddedPackLoaderForTests();
1314
1480
  initializedBindingsPromise = null;
1315
1481
  }
1316
-
1317
- // src/types.ts
1318
- var AudioFingerprintType = /* @__PURE__ */ ((AudioFingerprintType2) => {
1319
- AudioFingerprintType2[AudioFingerprintType2["Livestream"] = 1] = "Livestream";
1320
- AudioFingerprintType2[AudioFingerprintType2["ArchiveCf"] = 2] = "ArchiveCf";
1321
- AudioFingerprintType2[AudioFingerprintType2["ArchivePg"] = 4] = "ArchivePg";
1322
- return AudioFingerprintType2;
1323
- })(AudioFingerprintType || {});
1324
1482
  export {
1483
+ AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON,
1484
+ AUDIO_MARKER_WORK_BUDGET_EXCEEDED_REASON,
1485
+ AUDIO_MARKER_WORK_OVERFLOW_REASON,
1325
1486
  AudioFingerprintType,
1487
+ DEFAULT_MAX_AUDIO_MARKER_DURATION_MILLIS,
1488
+ DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES,
1489
+ DEFAULT_MAX_AUDIO_MARKER_SAMPLES,
1490
+ DEFAULT_MAX_AUDIO_MARKER_WORK_UNITS,
1326
1491
  DEFAULT_MAX_PACK_BYTES,
1327
1492
  MatcherSession,
1328
1493
  __resetMatcherWasmForTests,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sorisdk/matcher",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Browser WASM SDK for audio fingerprint matching",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",