@sorisdk/matcher 0.6.9 → 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 +41 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +101 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,44 @@ per-entry limits remain independently enforced.
|
|
|
121
121
|
|
|
122
122
|
If you want campaign events and browser recognition flow on top of matching, use `@sorisdk/web-audio`.
|
|
123
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
|
+
|
|
124
162
|
## Audio-marker detection limits
|
|
125
163
|
|
|
126
164
|
`MatcherSession.detectAudioMarker(...)` preserves its existing signature and
|
|
@@ -149,7 +187,9 @@ const detection = await session.detectAudioMarkerWithLimits(
|
|
|
149
187
|
```
|
|
150
188
|
|
|
151
189
|
The generated WASM module exposes the corresponding
|
|
152
|
-
`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
|
|
153
193
|
`Uint8Array` length before allocating a Rust-owned copy. Raising these budgets
|
|
154
194
|
permits greater memory and CPU use; it does not protect an untrusted host after
|
|
155
195
|
the caller opts into the larger envelope.
|
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;
|
|
@@ -251,6 +278,7 @@ declare class MatcherSession {
|
|
|
251
278
|
extractPitchReference(pcm16Le: Uint8Array, sampleRate: number): Promise<PitchReferenceEstimate>;
|
|
252
279
|
estimatePitchRatioAgainstReference(pcm16Le: Uint8Array, sampleRate: number, referencePitchHz: number, referenceConfidence?: number | null): Promise<PitchRatioEstimate>;
|
|
253
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. */
|
|
254
282
|
detectAudioMarker(pcm16Le: Uint8Array, sampleRate: number, config?: AudioMarkerConfig): Promise<AudioMarkerDetection>;
|
|
255
283
|
/** Detect an audio marker with explicit limits for trusted offline input. */
|
|
256
284
|
detectAudioMarkerWithLimits(pcm16Le: Uint8Array, sampleRate: number, config: AudioMarkerConfig | undefined, limits: AudioMarkerLimits): Promise<AudioMarkerDetection>;
|
|
@@ -276,4 +304,4 @@ declare function __resetMatcherWasmForTests(): void;
|
|
|
276
304
|
/** Maximum gap between successful observations in one continuous material run. */
|
|
277
305
|
declare const MATCH_CONTINUITY_GAP_MS = 30000;
|
|
278
306
|
|
|
279
|
-
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, 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 };
|
|
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
|
@@ -715,6 +715,19 @@ function normalizeAudioMarkerDetection(raw) {
|
|
|
715
715
|
runnerUpAccuracy: normalizeOptionalNumber(record.runnerUpAccuracy, record.runner_up_accuracy)
|
|
716
716
|
};
|
|
717
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
|
+
}
|
|
718
731
|
function isMapLike(raw) {
|
|
719
732
|
return raw instanceof Map || Object.prototype.toString.call(raw) === "[object Map]" && typeof raw.entries === "function";
|
|
720
733
|
}
|
|
@@ -782,6 +795,10 @@ function createBindingsFromModule(moduleLike) {
|
|
|
782
795
|
"estimatePitchRatioAgainstReferenceFeatures",
|
|
783
796
|
"estimate_pitch_ratio_against_reference_features"
|
|
784
797
|
]);
|
|
798
|
+
const detectAudioMarkerLevelRobustFn = resolveFactory(mod, [
|
|
799
|
+
"detectAudioMarkerLevelRobust",
|
|
800
|
+
"detect_audio_marker_level_robust"
|
|
801
|
+
]);
|
|
785
802
|
const detectAudioMarkerFn = resolveFactory(mod, [
|
|
786
803
|
"detectAudioMarker",
|
|
787
804
|
"detect_audio_marker",
|
|
@@ -926,6 +943,26 @@ function createBindingsFromModule(moduleLike) {
|
|
|
926
943
|
destroy?.call(matcher);
|
|
927
944
|
}
|
|
928
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
|
+
},
|
|
929
966
|
detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits) {
|
|
930
967
|
if (detectAudioMarkerWithLimitsFn) {
|
|
931
968
|
return normalizeAudioMarkerDetection(
|
|
@@ -970,6 +1007,42 @@ async function getBindings(options) {
|
|
|
970
1007
|
function toUint8ArrayCopy(input) {
|
|
971
1008
|
return new Uint8Array(input);
|
|
972
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
|
+
}
|
|
973
1046
|
function resolveAudioMarkerLimits(limits) {
|
|
974
1047
|
const resolved = {
|
|
975
1048
|
maxPcmBytes: limits?.maxPcmBytes ?? DEFAULT_MAX_AUDIO_MARKER_PCM_BYTES,
|
|
@@ -985,13 +1058,17 @@ function resolveAudioMarkerLimits(limits) {
|
|
|
985
1058
|
return resolved;
|
|
986
1059
|
}
|
|
987
1060
|
function audioMarkerInputFailureReason(pcm16Le, sampleRate, limits) {
|
|
988
|
-
|
|
1061
|
+
const byteLength = markerByteLength(pcm16Le);
|
|
1062
|
+
if (byteLength > limits.maxPcmBytes) {
|
|
989
1063
|
return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
|
|
990
1064
|
}
|
|
991
|
-
if (
|
|
1065
|
+
if (byteLength % 2 !== 0) {
|
|
992
1066
|
return "invalid_pcm16_byte_length";
|
|
993
1067
|
}
|
|
994
|
-
|
|
1068
|
+
if (!Number.isInteger(sampleRate) || sampleRate <= 0 || sampleRate > 4294967295) {
|
|
1069
|
+
return "invalid_sample_rate";
|
|
1070
|
+
}
|
|
1071
|
+
const sampleCount = byteLength / 2;
|
|
995
1072
|
if (sampleCount > limits.maxSamples) {
|
|
996
1073
|
return AUDIO_MARKER_INPUT_BUDGET_EXCEEDED_REASON;
|
|
997
1074
|
}
|
|
@@ -1293,15 +1370,24 @@ var MatcherSession = class {
|
|
|
1293
1370
|
handleError(this.events, "estimatePitchRatioAgainstReferenceFeatures", error);
|
|
1294
1371
|
}
|
|
1295
1372
|
}
|
|
1373
|
+
/** Uses level-robust detection by default, preserving explicit legacy thresholds. */
|
|
1296
1374
|
async detectAudioMarker(pcm16Le, sampleRate, config) {
|
|
1297
1375
|
try {
|
|
1298
1376
|
await this.ensureLiveReady();
|
|
1377
|
+
const robust = usesLevelRobust(config);
|
|
1299
1378
|
const limits = resolveAudioMarkerLimits();
|
|
1300
1379
|
const failureReason = audioMarkerInputFailureReason(pcm16Le, sampleRate, limits);
|
|
1301
1380
|
if (failureReason) {
|
|
1302
|
-
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);
|
|
1303
1390
|
}
|
|
1304
|
-
const safePcm = toUint8ArrayCopy(pcm16Le);
|
|
1305
1391
|
const matcher = this.matcherOrThrow();
|
|
1306
1392
|
if (matcher.detectAudioMarker) {
|
|
1307
1393
|
return matcher.detectAudioMarker(safePcm, sampleRate, config);
|
|
@@ -1318,6 +1404,7 @@ var MatcherSession = class {
|
|
|
1318
1404
|
async detectAudioMarkerWithLimits(pcm16Le, sampleRate, config, limits) {
|
|
1319
1405
|
try {
|
|
1320
1406
|
await this.ensureLiveReady();
|
|
1407
|
+
const robust = usesLevelRobust(config);
|
|
1321
1408
|
const resolvedLimits = resolveAudioMarkerLimits(limits);
|
|
1322
1409
|
const failureReason = audioMarkerInputFailureReason(
|
|
1323
1410
|
pcm16Le,
|
|
@@ -1325,9 +1412,16 @@ var MatcherSession = class {
|
|
|
1325
1412
|
resolvedLimits
|
|
1326
1413
|
);
|
|
1327
1414
|
if (failureReason) {
|
|
1328
|
-
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);
|
|
1329
1424
|
}
|
|
1330
|
-
const safePcm = toUint8ArrayCopy(pcm16Le);
|
|
1331
1425
|
const matcher = this.matcherOrThrow();
|
|
1332
1426
|
if (matcher.detectAudioMarkerWithLimits) {
|
|
1333
1427
|
return matcher.detectAudioMarkerWithLimits(
|