@octoseq/mir 0.1.0-main.2e286ce

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/chunk-DUWYCAVG.js +1525 -0
  2. package/dist/chunk-DUWYCAVG.js.map +1 -0
  3. package/dist/index.d.ts +450 -0
  4. package/dist/index.js +1234 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/runMir-CSIBwNZ3.d.ts +84 -0
  7. package/dist/runner/runMir.d.ts +2 -0
  8. package/dist/runner/runMir.js +3 -0
  9. package/dist/runner/runMir.js.map +1 -0
  10. package/dist/runner/workerProtocol.d.ts +169 -0
  11. package/dist/runner/workerProtocol.js +11 -0
  12. package/dist/runner/workerProtocol.js.map +1 -0
  13. package/dist/types-BE3py4fZ.d.ts +83 -0
  14. package/package.json +55 -0
  15. package/src/dsp/fft.ts +22 -0
  16. package/src/dsp/fftBackend.ts +53 -0
  17. package/src/dsp/fftBackendFftjs.ts +60 -0
  18. package/src/dsp/hpss.ts +152 -0
  19. package/src/dsp/hpssGpu.ts +101 -0
  20. package/src/dsp/mel.ts +219 -0
  21. package/src/dsp/mfcc.ts +119 -0
  22. package/src/dsp/onset.ts +205 -0
  23. package/src/dsp/peakPick.ts +112 -0
  24. package/src/dsp/spectral.ts +95 -0
  25. package/src/dsp/spectrogram.ts +176 -0
  26. package/src/gpu/README.md +34 -0
  27. package/src/gpu/context.ts +44 -0
  28. package/src/gpu/helpers.ts +87 -0
  29. package/src/gpu/hpssMasks.ts +116 -0
  30. package/src/gpu/kernels/hpssMasks.wgsl.ts +137 -0
  31. package/src/gpu/kernels/melProject.wgsl.ts +48 -0
  32. package/src/gpu/kernels/onsetEnvelope.wgsl.ts +56 -0
  33. package/src/gpu/melProject.ts +98 -0
  34. package/src/gpu/onsetEnvelope.ts +81 -0
  35. package/src/gpu/webgpu.d.ts +176 -0
  36. package/src/index.ts +121 -0
  37. package/src/runner/runMir.ts +431 -0
  38. package/src/runner/workerProtocol.ts +189 -0
  39. package/src/search/featureVectorV1.ts +123 -0
  40. package/src/search/fingerprintV1.ts +230 -0
  41. package/src/search/refinedModelV1.ts +321 -0
  42. package/src/search/searchTrackV1.ts +206 -0
  43. package/src/search/searchTrackV1Guided.ts +863 -0
  44. package/src/search/similarity.ts +98 -0
  45. package/src/types.ts +105 -0
  46. package/src/util/display.ts +80 -0
  47. package/src/util/normalise.ts +58 -0
  48. package/src/util/stats.ts +25 -0
package/dist/index.js ADDED
@@ -0,0 +1,1234 @@
1
+ import { peakPick } from './chunk-DUWYCAVG.js';
2
+ export { delta, deltaDelta, hpss, melSpectrogram, mfcc, onsetEnvelopeFromMel, onsetEnvelopeFromMelGpu, onsetEnvelopeFromSpectrogram, peakPick, runMir, spectralCentroid, spectralFlux, spectrogram } from './chunk-DUWYCAVG.js';
3
+
4
+ // src/gpu/context.ts
5
+ var MirGPU = class _MirGPU {
6
+ device;
7
+ queue;
8
+ constructor(device) {
9
+ this.device = device;
10
+ this.queue = device.queue;
11
+ }
12
+ static async create() {
13
+ if (typeof navigator === "undefined") {
14
+ throw new Error(
15
+ "@octoseq/mir: WebGPU is only available in the browser (navigator is undefined)."
16
+ );
17
+ }
18
+ const nav = navigator;
19
+ if (!nav.gpu) {
20
+ throw new Error(
21
+ "@octoseq/mir: WebGPU is unavailable (navigator.gpu is missing). Use CPU mode or a WebGPU-capable browser."
22
+ );
23
+ }
24
+ const adapter = await nav.gpu.requestAdapter();
25
+ if (!adapter) {
26
+ throw new Error(
27
+ "@octoseq/mir: Failed to acquire a WebGPU adapter. WebGPU may be disabled or unsupported."
28
+ );
29
+ }
30
+ const device = await adapter.requestDevice();
31
+ return new _MirGPU(device);
32
+ }
33
+ };
34
+
35
+ // src/util/normalise.ts
36
+ function normaliseForWaveform(data, options = {}) {
37
+ const center = options.center ?? false;
38
+ const targetMin = options.min ?? (center ? -1 : 0);
39
+ const targetMax = options.max ?? 1;
40
+ if (!Number.isFinite(targetMin) || !Number.isFinite(targetMax)) {
41
+ throw new Error("@octoseq/mir: normaliseForWaveform min/max must be finite");
42
+ }
43
+ if (targetMax === targetMin) {
44
+ throw new Error("@octoseq/mir: normaliseForWaveform max must differ from min");
45
+ }
46
+ let srcMin = Infinity;
47
+ let srcMax = -Infinity;
48
+ for (let i = 0; i < data.length; i++) {
49
+ const v = data[i] ?? 0;
50
+ if (v < srcMin) srcMin = v;
51
+ if (v > srcMax) srcMax = v;
52
+ }
53
+ if (!Number.isFinite(srcMin) || !Number.isFinite(srcMax) || srcMax === srcMin) {
54
+ const out2 = new Float32Array(data.length);
55
+ const mid = (targetMin + targetMax) / 2;
56
+ out2.fill(mid);
57
+ return out2;
58
+ }
59
+ const out = new Float32Array(data.length);
60
+ const scale = (targetMax - targetMin) / (srcMax - srcMin);
61
+ for (let i = 0; i < data.length; i++) {
62
+ const v = data[i] ?? 0;
63
+ out[i] = targetMin + (v - srcMin) * scale;
64
+ }
65
+ return out;
66
+ }
67
+
68
+ // src/util/display.ts
69
+ function spectrogramToDb(magnitudes2d, options = {}) {
70
+ const eps = options.epsilon ?? 1e-12;
71
+ const floorDb = options.floorDb;
72
+ const out = new Array(magnitudes2d.length);
73
+ for (let t = 0; t < magnitudes2d.length; t++) {
74
+ const row = magnitudes2d[t] ?? new Float32Array(0);
75
+ const dbRow = new Float32Array(row.length);
76
+ for (let k = 0; k < row.length; k++) {
77
+ const mag = row[k] ?? 0;
78
+ const db = 20 * Math.log10(Math.max(eps, mag));
79
+ dbRow[k] = floorDb !== void 0 ? Math.max(floorDb, db) : db;
80
+ }
81
+ out[t] = dbRow;
82
+ }
83
+ return out;
84
+ }
85
+ function clampDb(db2d, minDb, maxDb) {
86
+ const out = new Array(db2d.length);
87
+ for (let t = 0; t < db2d.length; t++) {
88
+ const row = db2d[t] ?? new Float32Array(0);
89
+ const clamped = new Float32Array(row.length);
90
+ for (let k = 0; k < row.length; k++) {
91
+ const v = row[k] ?? 0;
92
+ clamped[k] = v < minDb ? minDb : v > maxDb ? maxDb : v;
93
+ }
94
+ out[t] = clamped;
95
+ }
96
+ return out;
97
+ }
98
+
99
+ // src/util/stats.ts
100
+ function minMax(values) {
101
+ const n = values.length >>> 0;
102
+ if (n === 0) return { min: Infinity, max: -Infinity };
103
+ let min = Infinity;
104
+ let max = -Infinity;
105
+ for (let i = 0; i < n; i++) {
106
+ const v = values[i] ?? 0;
107
+ if (v < min) min = v;
108
+ if (v > max) max = v;
109
+ }
110
+ return { min, max };
111
+ }
112
+
113
+ // src/search/fingerprintV1.ts
114
+ function l2Norm(v) {
115
+ let sum = 0;
116
+ for (let i = 0; i < v.length; i++) {
117
+ const x = v[i] ?? 0;
118
+ sum += x * x;
119
+ }
120
+ return Math.sqrt(sum);
121
+ }
122
+ function weightedStats(frames, start, endExclusive, dimHint = 0) {
123
+ const nFrames = Math.max(0, endExclusive - start);
124
+ const first = frames[start];
125
+ const dim = first ? first.length : dimHint;
126
+ const mean = new Float32Array(dim);
127
+ const variance = new Float32Array(dim);
128
+ if (nFrames <= 0 || dim <= 0) return { mean, variance };
129
+ const weights = new Float32Array(nFrames);
130
+ const normFrames = new Array(nFrames);
131
+ let totalWeight = 0;
132
+ for (let i = 0; i < nFrames; i++) {
133
+ const f = frames[start + i];
134
+ if (!f) {
135
+ normFrames[i] = new Float32Array(dim);
136
+ continue;
137
+ }
138
+ const w = l2Norm(f);
139
+ weights[i] = w;
140
+ totalWeight += w;
141
+ const nf = new Float32Array(dim);
142
+ const d = w > 1e-12 ? w : 1;
143
+ for (let j = 0; j < dim; j++) nf[j] = f[j] / d;
144
+ normFrames[i] = nf;
145
+ }
146
+ if (totalWeight <= 1e-12) totalWeight = 1;
147
+ for (let i = 0; i < nFrames; i++) {
148
+ const w = weights[i];
149
+ const nf = normFrames[i];
150
+ if (!w || w <= 0) continue;
151
+ const scale = w / totalWeight;
152
+ for (let j = 0; j < dim; j++) {
153
+ mean[j] += nf[j] * scale;
154
+ }
155
+ }
156
+ for (let i = 0; i < nFrames; i++) {
157
+ const w = weights[i];
158
+ const nf = normFrames[i];
159
+ if (!w || w <= 0) continue;
160
+ const scale = w / totalWeight;
161
+ for (let j = 0; j < dim; j++) {
162
+ const diff = nf[j] - mean[j];
163
+ variance[j] += diff * diff * scale;
164
+ }
165
+ }
166
+ return { mean, variance };
167
+ }
168
+ function findFrameWindow(times, t0, t1) {
169
+ let start = 0;
170
+ while (start < times.length && (times[start] ?? 0) < t0) start++;
171
+ let end = start;
172
+ while (end < times.length && (times[end] ?? 0) <= t1) end++;
173
+ return { startFrame: start, endFrameExclusive: Math.max(start, end) };
174
+ }
175
+ function fingerprintV1(params) {
176
+ const { t0, t1, mel, onsetEnvelope, mfcc: mfcc2 } = params;
177
+ const tt0 = Math.min(t0, t1);
178
+ const tt1 = Math.max(t0, t1);
179
+ const dur = Math.max(1e-6, tt1 - tt0);
180
+ const melDimHint = mel.melBands.find((f) => f?.length)?.length ?? 0;
181
+ const melWindow = findFrameWindow(mel.times, tt0, tt1);
182
+ const melStats = weightedStats(mel.melBands, melWindow.startFrame, melWindow.endFrameExclusive, melDimHint);
183
+ let onsetSum = 0;
184
+ let onsetMax = -Infinity;
185
+ let onsetN = 0;
186
+ for (let i = 0; i < onsetEnvelope.times.length; i++) {
187
+ const t = onsetEnvelope.times[i] ?? 0;
188
+ if (t < tt0 || t > tt1) continue;
189
+ const v = onsetEnvelope.values[i] ?? 0;
190
+ onsetSum += v;
191
+ onsetN++;
192
+ if (v > onsetMax) onsetMax = v;
193
+ }
194
+ const onsetMean = onsetN > 0 ? onsetSum / onsetN : 0;
195
+ const onsetMaxSafe = Number.isFinite(onsetMax) ? onsetMax : 0;
196
+ const peaks = peakPick(onsetEnvelope.times, onsetEnvelope.values, {
197
+ minIntervalSec: params.peakPick?.minIntervalSec,
198
+ threshold: params.peakPick?.threshold,
199
+ adaptive: params.peakPick?.adaptiveFactor ? { method: "meanStd", factor: params.peakPick.adaptiveFactor } : void 0,
200
+ strict: true
201
+ });
202
+ const peaksInWindow = peaks.filter((p) => p.time >= tt0 && p.time <= tt1);
203
+ const peakDensityHz = peaksInWindow.length / dur;
204
+ let mfccStats;
205
+ const mfccDimHint = mfcc2?.values.find((f) => f?.length)?.length ?? 0;
206
+ if (mfcc2) {
207
+ const mfccWindow = findFrameWindow(mfcc2.times, tt0, tt1);
208
+ const mfccFramesSliced = [];
209
+ for (let i = mfccWindow.startFrame; i < mfccWindow.endFrameExclusive; i++) {
210
+ const full = mfcc2.values[i] ?? new Float32Array(0);
211
+ const start = Math.min(1, full.length);
212
+ const end = Math.min(13, full.length);
213
+ mfccFramesSliced.push(full.subarray(start, end));
214
+ }
215
+ const s = weightedStats(mfccFramesSliced, 0, mfccFramesSliced.length, mfccDimHint ? Math.max(0, mfccDimHint - 1) : 0);
216
+ mfccStats = { mean: s.mean, variance: s.variance };
217
+ }
218
+ return {
219
+ version: "v1",
220
+ t0: tt0,
221
+ t1: tt1,
222
+ mel: {
223
+ mean: melStats.mean,
224
+ variance: melStats.variance
225
+ },
226
+ onset: {
227
+ mean: onsetMean,
228
+ max: onsetMaxSafe,
229
+ peakDensityHz
230
+ },
231
+ ...mfccStats ? { mfcc: mfccStats } : {}
232
+ };
233
+ }
234
+
235
+ // src/search/similarity.ts
236
+ function l2Norm2(v) {
237
+ let sum = 0;
238
+ for (let i = 0; i < v.length; i++) {
239
+ const x = v[i] ?? 0;
240
+ sum += x * x;
241
+ }
242
+ return Math.sqrt(sum);
243
+ }
244
+ function normaliseL2InPlace(v, eps = 1e-12) {
245
+ const n = l2Norm2(v);
246
+ const d = n > eps ? n : 1;
247
+ for (let i = 0; i < v.length; i++) v[i] = (v[i] ?? 0) / d;
248
+ }
249
+ function cosineSimilarity(a, b) {
250
+ if (a.length !== b.length) throw new Error("@octoseq/mir: cosineSimilarity length mismatch");
251
+ let dot2 = 0;
252
+ let aa = 0;
253
+ let bb = 0;
254
+ for (let i = 0; i < a.length; i++) {
255
+ const x = a[i] ?? 0;
256
+ const y = b[i] ?? 0;
257
+ dot2 += x * y;
258
+ aa += x * x;
259
+ bb += y * y;
260
+ }
261
+ const denom = Math.sqrt(aa) * Math.sqrt(bb);
262
+ if (denom <= 0) return 0;
263
+ const cos = dot2 / denom;
264
+ return Math.max(0, cos);
265
+ }
266
+ function pushScaled(dst, src, scale) {
267
+ for (let i = 0; i < src.length; i++) dst.push((src[i] ?? 0) * scale);
268
+ }
269
+ function fingerprintToVectorV1(fp, weights = {}) {
270
+ const wMel = weights.mel ?? 1;
271
+ const wTrans = weights.transient ?? 1;
272
+ const wMfcc = weights.mfcc ?? 1;
273
+ const melBlock = new Float32Array(fp.mel.mean.length + fp.mel.variance.length);
274
+ melBlock.set(fp.mel.mean, 0);
275
+ melBlock.set(fp.mel.variance, fp.mel.mean.length);
276
+ normaliseL2InPlace(melBlock);
277
+ const transBlock = new Float32Array([fp.onset.mean, fp.onset.max, fp.onset.peakDensityHz]);
278
+ normaliseL2InPlace(transBlock);
279
+ let mfccBlock = null;
280
+ if (fp.mfcc) {
281
+ mfccBlock = new Float32Array(fp.mfcc.mean.length + fp.mfcc.variance.length);
282
+ mfccBlock.set(fp.mfcc.mean, 0);
283
+ mfccBlock.set(fp.mfcc.variance, fp.mfcc.mean.length);
284
+ normaliseL2InPlace(mfccBlock);
285
+ }
286
+ const out = [];
287
+ pushScaled(out, melBlock, wMel);
288
+ pushScaled(out, transBlock, wTrans);
289
+ if (mfccBlock) pushScaled(out, mfccBlock, wMfcc);
290
+ return new Float32Array(out);
291
+ }
292
+ function similarityFingerprintV1(a, b, weights = {}) {
293
+ const va = fingerprintToVectorV1(a, weights);
294
+ const vb = fingerprintToVectorV1(b, weights);
295
+ return cosineSimilarity(va, vb);
296
+ }
297
+
298
+ // src/search/searchTrackV1.ts
299
+ function nowMs() {
300
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
301
+ }
302
+ function clamp01(x) {
303
+ if (x <= 0) return 0;
304
+ if (x >= 1) return 1;
305
+ return x;
306
+ }
307
+ async function searchTrackV1(params) {
308
+ const tStart = nowMs();
309
+ const options = params.options ?? {};
310
+ const hopSec = Math.max(5e-3, options.hopSec ?? 0.03);
311
+ const threshold = clamp01(options.threshold ?? 0.75);
312
+ const qt0 = Math.min(params.queryRegion.t0, params.queryRegion.t1);
313
+ const qt1 = Math.max(params.queryRegion.t0, params.queryRegion.t1);
314
+ const windowSec = Math.max(1e-3, qt1 - qt0);
315
+ const minSpacingSec = Math.max(0, options.minCandidateSpacingSec ?? windowSec * 0.8);
316
+ const tFp0 = nowMs();
317
+ const queryFp = fingerprintV1({
318
+ t0: qt0,
319
+ t1: qt1,
320
+ mel: params.mel,
321
+ onsetEnvelope: params.onsetEnvelope,
322
+ mfcc: params.mfcc,
323
+ peakPick: options.queryPeakPick
324
+ });
325
+ const fingerprintMs = nowMs() - tFp0;
326
+ const scanStartMs = nowMs();
327
+ const trackDuration = Math.max(
328
+ params.mel.times.length ? params.mel.times[params.mel.times.length - 1] ?? 0 : 0,
329
+ params.onsetEnvelope.times.length ? params.onsetEnvelope.times[params.onsetEnvelope.times.length - 1] ?? 0 : 0
330
+ );
331
+ const nWindows = Math.max(0, Math.floor((trackDuration - windowSec) / hopSec) + 1);
332
+ const times = new Float32Array(nWindows);
333
+ const sim = new Float32Array(nWindows);
334
+ let skippedWindows = 0;
335
+ let scannedWindows = 0;
336
+ for (let w = 0; w < nWindows; w++) {
337
+ if (options.isCancelled?.()) {
338
+ throw new Error("@octoseq/mir: cancelled");
339
+ }
340
+ const t0 = w * hopSec;
341
+ const t1 = t0 + windowSec;
342
+ if (options.skipWindowOverlap) {
343
+ const s0 = options.skipWindowOverlap.t0;
344
+ const s1 = options.skipWindowOverlap.t1;
345
+ const overlaps = t0 < s1 && t1 > s0;
346
+ if (overlaps) {
347
+ times[w] = t0;
348
+ sim[w] = 0;
349
+ skippedWindows++;
350
+ continue;
351
+ }
352
+ }
353
+ times[w] = t0;
354
+ const fp = fingerprintV1({
355
+ t0,
356
+ t1,
357
+ mel: params.mel,
358
+ onsetEnvelope: params.onsetEnvelope,
359
+ mfcc: params.mfcc,
360
+ peakPick: options.queryPeakPick
361
+ });
362
+ const score = similarityFingerprintV1(queryFp, fp, options.weights);
363
+ sim[w] = clamp01(score);
364
+ scannedWindows++;
365
+ }
366
+ const scanMs = nowMs() - scanStartMs;
367
+ const events = peakPick(times, sim, {
368
+ threshold,
369
+ minIntervalSec: minSpacingSec,
370
+ strict: options.candidatePeakPick?.strict ?? true
371
+ });
372
+ const candidates = events.map((e) => {
373
+ const windowStartSec = e.time;
374
+ const windowEndSec = windowStartSec + windowSec;
375
+ return {
376
+ timeSec: e.time,
377
+ score: e.strength,
378
+ windowStartSec,
379
+ windowEndSec
380
+ };
381
+ });
382
+ const totalMs = nowMs() - tStart;
383
+ return {
384
+ times,
385
+ similarity: sim,
386
+ candidates,
387
+ meta: {
388
+ fingerprintMs,
389
+ scanMs,
390
+ totalMs,
391
+ windowSec,
392
+ hopSec,
393
+ skippedWindows,
394
+ scannedWindows
395
+ }
396
+ };
397
+ }
398
+
399
+ // src/search/featureVectorV1.ts
400
+ function makeFeatureVectorLayoutV1(params) {
401
+ const melDim = Math.max(0, params.melDim);
402
+ const mfccDim = Math.max(0, params.mfccDim ?? 0);
403
+ const includeContrast = params.includeContrast ?? true;
404
+ let offset = 0;
405
+ const melMeanFg = { offset, length: melDim };
406
+ offset += melDim;
407
+ const melVarianceFg = { offset, length: melDim };
408
+ offset += melDim;
409
+ const onsetFg = { offset, length: 3 };
410
+ offset += 3;
411
+ const layout = { dim: 0, melMeanFg, melVarianceFg, onsetFg };
412
+ if (mfccDim > 0) {
413
+ layout.mfccMeanFg = { offset, length: mfccDim };
414
+ layout.mfccVarianceFg = { offset: offset + mfccDim, length: mfccDim };
415
+ offset += mfccDim * 2;
416
+ }
417
+ if (includeContrast) {
418
+ layout.melContrast = { offset, length: melDim };
419
+ offset += melDim;
420
+ layout.onsetContrast = { offset, length: 3 };
421
+ offset += 3;
422
+ if (mfccDim > 0) {
423
+ layout.mfccMeanContrast = { offset, length: mfccDim };
424
+ layout.mfccVarianceContrast = { offset: offset + mfccDim, length: mfccDim };
425
+ offset += mfccDim * 2;
426
+ }
427
+ }
428
+ layout.dim = offset;
429
+ return layout;
430
+ }
431
+
432
+ // src/search/refinedModelV1.ts
433
+ function clamp012(x) {
434
+ return x <= 0 ? 0 : x >= 1 ? 1 : x;
435
+ }
436
+ function sigmoid(x) {
437
+ const z = x > 20 ? 20 : x < -20 ? -20 : x;
438
+ return 1 / (1 + Math.exp(-z));
439
+ }
440
+ function dot(a, b) {
441
+ const n = Math.min(a.length, b.length);
442
+ let s = 0;
443
+ for (let i = 0; i < n; i++) s += (a[i] ?? 0) * (b[i] ?? 0);
444
+ return s;
445
+ }
446
+ function sliceDot(w, x, offset, length) {
447
+ const end = Math.min(w.length, x.length, offset + length);
448
+ let sum = 0;
449
+ for (let i = offset; i < end; i++) sum += (w[i] ?? 0) * (x[i] ?? 0);
450
+ return sum;
451
+ }
452
+ function logitContributionsByGroupV1(w, b, x, layout) {
453
+ const melForeground = sliceDot(w, x, layout.melMeanFg.offset, layout.melMeanFg.length) + sliceDot(w, x, layout.melVarianceFg.offset, layout.melVarianceFg.length);
454
+ const melContrast = layout.melContrast ? sliceDot(w, x, layout.melContrast.offset, layout.melContrast.length) : 0;
455
+ const onsetForeground = sliceDot(w, x, layout.onsetFg.offset, layout.onsetFg.length);
456
+ const onsetContrast = layout.onsetContrast ? sliceDot(w, x, layout.onsetContrast.offset, layout.onsetContrast.length) : 0;
457
+ const mfccForeground = layout.mfccMeanFg && layout.mfccVarianceFg ? sliceDot(w, x, layout.mfccMeanFg.offset, layout.mfccMeanFg.length) + sliceDot(w, x, layout.mfccVarianceFg.offset, layout.mfccVarianceFg.length) : 0;
458
+ const mfccContrast = layout.mfccMeanContrast && layout.mfccVarianceContrast ? sliceDot(w, x, layout.mfccMeanContrast.offset, layout.mfccMeanContrast.length) + sliceDot(w, x, layout.mfccVarianceContrast.offset, layout.mfccVarianceContrast.length) : 0;
459
+ const mel = melForeground + melContrast;
460
+ const onset = onsetForeground + onsetContrast;
461
+ const mfcc2 = mfccForeground + mfccContrast;
462
+ const logit = mel + onset + mfcc2 + b;
463
+ return {
464
+ logit,
465
+ bias: b,
466
+ mel,
467
+ melForeground,
468
+ ...layout.melContrast ? { melContrast } : {},
469
+ onset,
470
+ onsetForeground,
471
+ ...layout.onsetContrast ? { onsetContrast } : {},
472
+ ...layout.mfccMeanFg || layout.mfccMeanContrast ? {
473
+ mfcc: mfcc2,
474
+ mfccForeground,
475
+ ...layout.mfccMeanContrast ? { mfccContrast } : {}
476
+ } : {}
477
+ };
478
+ }
479
+ function l2Norm3(v) {
480
+ let sum = 0;
481
+ for (let i = 0; i < v.length; i++) {
482
+ const x = v[i] ?? 0;
483
+ sum += x * x;
484
+ }
485
+ return Math.sqrt(sum);
486
+ }
487
+ function cosineSimilarity01(a, b) {
488
+ const n = Math.min(a.length, b.length);
489
+ let ab = 0;
490
+ let aa = 0;
491
+ let bb = 0;
492
+ for (let i = 0; i < n; i++) {
493
+ const x = a[i] ?? 0;
494
+ const y = b[i] ?? 0;
495
+ ab += x * y;
496
+ aa += x * x;
497
+ bb += y * y;
498
+ }
499
+ const denom = Math.sqrt(aa) * Math.sqrt(bb);
500
+ if (denom <= 0) return 0;
501
+ const cos = ab / denom;
502
+ const clamped = Math.max(-1, Math.min(1, cos));
503
+ return (clamped + 1) / 2;
504
+ }
505
+ function sliceSumSquares(w, offset, length) {
506
+ let sum = 0;
507
+ const end = Math.min(w.length, offset + length);
508
+ for (let i = offset; i < end; i++) {
509
+ const x = w[i] ?? 0;
510
+ sum += x * x;
511
+ }
512
+ return sum;
513
+ }
514
+ function summariseWeightL2ByGroup(w, layout) {
515
+ const melForegroundSq = sliceSumSquares(w, layout.melMeanFg.offset, layout.melMeanFg.length) + sliceSumSquares(w, layout.melVarianceFg.offset, layout.melVarianceFg.length);
516
+ const melContrastSq = layout.melContrast ? sliceSumSquares(w, layout.melContrast.offset, layout.melContrast.length) : 0;
517
+ const onsetForegroundSq = sliceSumSquares(w, layout.onsetFg.offset, layout.onsetFg.length);
518
+ const onsetContrastSq = layout.onsetContrast ? sliceSumSquares(w, layout.onsetContrast.offset, layout.onsetContrast.length) : 0;
519
+ const mfccForegroundSq = layout.mfccMeanFg && layout.mfccVarianceFg ? sliceSumSquares(w, layout.mfccMeanFg.offset, layout.mfccMeanFg.length) + sliceSumSquares(w, layout.mfccVarianceFg.offset, layout.mfccVarianceFg.length) : 0;
520
+ const mfccContrastSq = layout.mfccMeanContrast && layout.mfccVarianceContrast ? sliceSumSquares(w, layout.mfccMeanContrast.offset, layout.mfccMeanContrast.length) + sliceSumSquares(w, layout.mfccVarianceContrast.offset, layout.mfccVarianceContrast.length) : 0;
521
+ const mel = Math.sqrt(melForegroundSq + melContrastSq);
522
+ const onset = Math.sqrt(onsetForegroundSq + onsetContrastSq);
523
+ const mfcc2 = mfccForegroundSq + mfccContrastSq > 0 ? Math.sqrt(mfccForegroundSq + mfccContrastSq) : void 0;
524
+ return {
525
+ mel,
526
+ melForeground: Math.sqrt(melForegroundSq),
527
+ ...melContrastSq > 0 ? { melContrast: Math.sqrt(melContrastSq) } : {},
528
+ onset,
529
+ onsetForeground: Math.sqrt(onsetForegroundSq),
530
+ ...onsetContrastSq > 0 ? { onsetContrast: Math.sqrt(onsetContrastSq) } : {},
531
+ ...mfcc2 != null ? {
532
+ mfcc: mfcc2,
533
+ mfccForeground: Math.sqrt(mfccForegroundSq),
534
+ ...mfccContrastSq > 0 ? { mfccContrast: Math.sqrt(mfccContrastSq) } : {}
535
+ } : {}
536
+ };
537
+ }
538
+ function trainLogisticModelV1(params) {
539
+ const pos = params.positives;
540
+ const neg = params.negatives;
541
+ const dim = params.layout.dim;
542
+ const iterations = Math.max(1, params.options?.iterations ?? 80);
543
+ const learningRate = Math.max(1e-4, params.options?.learningRate ?? 0.15);
544
+ const l2 = Math.max(0, params.options?.l2 ?? 0.01);
545
+ const w = new Float32Array(dim);
546
+ let b = 0;
547
+ const posW = pos.length > 0 ? 0.5 / pos.length : 0;
548
+ const negW = neg.length > 0 ? 0.5 / neg.length : 0;
549
+ let lastLoss = Infinity;
550
+ let itersUsed = 0;
551
+ for (let iter = 0; iter < iterations; iter++) {
552
+ itersUsed = iter + 1;
553
+ const gradW = new Float32Array(dim);
554
+ let gradB = 0;
555
+ let loss = 0;
556
+ const accumulate = (x, y, weight) => {
557
+ const s = dot(w, x) + b;
558
+ const p = sigmoid(s);
559
+ const err = p - y;
560
+ gradB += weight * err;
561
+ for (let j = 0; j < dim; j++) gradW[j] = (gradW[j] ?? 0) + weight * err * (x[j] ?? 0);
562
+ const pSafe = Math.min(1 - 1e-9, Math.max(1e-9, p));
563
+ loss += weight * (y ? -Math.log(pSafe) : -Math.log(1 - pSafe));
564
+ };
565
+ for (const x of pos) accumulate(x, 1, posW);
566
+ for (const x of neg) accumulate(x, 0, negW);
567
+ if (l2 > 0) {
568
+ for (let j = 0; j < dim; j++) {
569
+ gradW[j] = (gradW[j] ?? 0) + l2 * (w[j] ?? 0);
570
+ }
571
+ loss += l2 * l2Norm3(w) ** 2 / 2;
572
+ }
573
+ const lr = learningRate / (1 + iter * 0.01);
574
+ for (let j = 0; j < dim; j++) w[j] = (w[j] ?? 0) - lr * (gradW[j] ?? 0);
575
+ b -= lr * gradB;
576
+ if (Math.abs(lastLoss - loss) < 1e-6) break;
577
+ lastLoss = loss;
578
+ }
579
+ return {
580
+ kind: "logistic",
581
+ w,
582
+ b,
583
+ explain: {
584
+ kind: "logistic",
585
+ positives: pos.length,
586
+ negatives: neg.length,
587
+ weightL2: summariseWeightL2ByGroup(w, params.layout),
588
+ training: { iterations: itersUsed, finalLoss: Number.isFinite(lastLoss) ? lastLoss : 0 }
589
+ }
590
+ };
591
+ }
592
+ function buildPrototypeModelV1(params) {
593
+ const dim = params.layout.dim;
594
+ const proto = new Float32Array(dim);
595
+ const n = Math.max(1, params.positives.length);
596
+ for (const x of params.positives) {
597
+ for (let j = 0; j < dim; j++) proto[j] = (proto[j] ?? 0) + (x[j] ?? 0) / n;
598
+ }
599
+ return {
600
+ kind: "prototype",
601
+ prototype: proto,
602
+ explain: {
603
+ kind: "prototype",
604
+ positives: params.positives.length,
605
+ negatives: 0
606
+ }
607
+ };
608
+ }
609
+ function scoreWithModelV1(model, x) {
610
+ if (model.kind === "baseline") return 0;
611
+ if (model.kind === "prototype") return clamp012(cosineSimilarity01(model.prototype, x));
612
+ return clamp012(sigmoid(dot(model.w, x) + model.b));
613
+ }
614
+
615
+ // src/search/searchTrackV1Guided.ts
616
+ function nowMs2() {
617
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
618
+ }
619
+ function clamp013(x) {
620
+ if (x <= 0) return 0;
621
+ if (x >= 1) return 1;
622
+ return x;
623
+ }
624
+ function zScoreInPlace(out, mean, invStd) {
625
+ const n = Math.min(out.length, mean.length, invStd.length);
626
+ for (let j = 0; j < n; j++) out[j] = ((out[j] ?? 0) - (mean[j] ?? 0)) * (invStd[j] ?? 1);
627
+ }
628
+ function decideModelKind(params) {
629
+ if (!params.enabled) return { kind: "baseline", positives: [], negatives: [] };
630
+ const positives = params.labels.filter((l) => l.status === "accepted");
631
+ const negatives = params.labels.filter((l) => l.status === "rejected");
632
+ if (positives.length < 2) return { kind: "baseline", positives, negatives };
633
+ if (negatives.length === 0) return { kind: "prototype", positives, negatives };
634
+ return { kind: "logistic", positives, negatives };
635
+ }
636
+ function advanceStartIndex(times, start, t0) {
637
+ let i = start;
638
+ while (i < times.length && (times[i] ?? 0) < t0) i++;
639
+ return i;
640
+ }
641
+ function advanceEndIndex(times, endExclusive, t1) {
642
+ let i = endExclusive;
643
+ while (i < times.length && (times[i] ?? 0) <= t1) i++;
644
+ return i;
645
+ }
646
+ function cosineSimilarity01ByBlocks(query, window, layout, weights) {
647
+ const wMel = weights?.mel ?? 1;
648
+ const wTrans = weights?.transient ?? 1;
649
+ const wMfcc = weights?.mfcc ?? 1;
650
+ const addBlock = (offset, length, weight, acc2) => {
651
+ if (length <= 0 || weight === 0) return;
652
+ const ww = weight * weight;
653
+ const eps = 1e-12;
654
+ let dotRaw = 0;
655
+ let qSumSq = 0;
656
+ let xSumSq = 0;
657
+ const end = Math.min(query.length, window.length, offset + length);
658
+ for (let i = offset; i < end; i++) {
659
+ const q = query[i] ?? 0;
660
+ const x = window[i] ?? 0;
661
+ dotRaw += q * x;
662
+ qSumSq += q * q;
663
+ xSumSq += x * x;
664
+ }
665
+ const qNorm = Math.sqrt(qSumSq);
666
+ const xNorm = Math.sqrt(xSumSq);
667
+ if (qNorm > eps) acc2.aa += ww;
668
+ if (xNorm > eps) acc2.bb += ww;
669
+ if (!(qNorm > eps && xNorm > eps)) return;
670
+ acc2.dot += ww * (dotRaw / (qNorm * xNorm));
671
+ };
672
+ const acc = { dot: 0, aa: 0, bb: 0 };
673
+ addBlock(layout.melMeanFg.offset, layout.melMeanFg.length + layout.melVarianceFg.length, wMel, acc);
674
+ addBlock(layout.onsetFg.offset, layout.onsetFg.length, wTrans, acc);
675
+ if (layout.mfccMeanFg && layout.mfccVarianceFg) {
676
+ addBlock(layout.mfccMeanFg.offset, layout.mfccMeanFg.length + layout.mfccVarianceFg.length, wMfcc, acc);
677
+ }
678
+ if (layout.melContrast) addBlock(layout.melContrast.offset, layout.melContrast.length, wMel, acc);
679
+ if (layout.onsetContrast) addBlock(layout.onsetContrast.offset, layout.onsetContrast.length, wTrans, acc);
680
+ if (layout.mfccMeanContrast && layout.mfccVarianceContrast) {
681
+ addBlock(layout.mfccMeanContrast.offset, layout.mfccMeanContrast.length + layout.mfccVarianceContrast.length, wMfcc, acc);
682
+ }
683
+ const denom = Math.sqrt(acc.aa) * Math.sqrt(acc.bb);
684
+ if (denom <= 0) return 0;
685
+ const cos = acc.dot / denom;
686
+ const clamped = Math.max(-1, Math.min(1, cos));
687
+ return (clamped + 1) / 2;
688
+ }
689
+ function buildSparseTableMax(values, isCancelled) {
690
+ const n = values.length;
691
+ const log = new Uint8Array(n + 1);
692
+ for (let i = 2; i <= n; i++) log[i] = (log[i >>> 1] ?? 0) + 1;
693
+ const maxK = log[n] ?? 0;
694
+ const table = [];
695
+ table[0] = values;
696
+ for (let k = 1; k <= maxK; k++) {
697
+ const span = 1 << k;
698
+ const half = span >>> 1;
699
+ const prev = table[k - 1];
700
+ const len = Math.max(0, n - span + 1);
701
+ const cur = new Float32Array(len);
702
+ for (let i = 0; i < len; i++) {
703
+ if ((i & 2047) === 0 && isCancelled?.()) throw new Error("@octoseq/mir: cancelled");
704
+ const a = prev[i] ?? 0;
705
+ const b = prev[i + half] ?? 0;
706
+ cur[i] = a > b ? a : b;
707
+ }
708
+ table[k] = cur;
709
+ }
710
+ return {
711
+ query: (start, endExclusive) => {
712
+ const l = Math.max(0, start | 0);
713
+ const r = Math.min(n, endExclusive | 0);
714
+ const len = r - l;
715
+ if (len <= 0) return -Infinity;
716
+ const k = log[len] ?? 0;
717
+ const span = 1 << k;
718
+ const row = table[k];
719
+ const a = row[l] ?? -Infinity;
720
+ const b = row[r - span] ?? -Infinity;
721
+ return a > b ? a : b;
722
+ }
723
+ };
724
+ }
725
+ function computeBackgroundWindow(params) {
726
+ const fgStartSec = Math.min(params.fgStartSec, params.fgEndSec);
727
+ const fgEndSec = Math.max(params.fgStartSec, params.fgEndSec);
728
+ const fgDur = Math.max(1e-6, fgEndSec - fgStartSec);
729
+ const desired = Math.max(fgDur, fgDur * Math.max(1, params.backgroundScale));
730
+ const maxDur = Math.max(fgDur, params.trackDurationSec);
731
+ const dur = Math.min(desired, maxDur);
732
+ const center = (fgStartSec + fgEndSec) / 2;
733
+ let bgStart = center - dur / 2;
734
+ let bgEnd = bgStart + dur;
735
+ if (bgStart < 0) {
736
+ bgStart = 0;
737
+ bgEnd = Math.min(params.trackDurationSec, dur);
738
+ }
739
+ if (bgEnd > params.trackDurationSec) {
740
+ bgEnd = params.trackDurationSec;
741
+ bgStart = Math.max(0, bgEnd - dur);
742
+ }
743
+ bgStart = Math.min(bgStart, fgStartSec);
744
+ bgEnd = Math.max(bgEnd, fgEndSec);
745
+ return { bgStartSec: bgStart, bgEndSec: bgEnd };
746
+ }
747
+ var SlidingMoments = class {
748
+ constructor(dim, addFrame, removeFrame) {
749
+ this.dim = dim;
750
+ this.addFrame = addFrame;
751
+ this.removeFrame = removeFrame;
752
+ this.sum = new Float64Array(dim);
753
+ this.sumSq = new Float64Array(dim);
754
+ }
755
+ start = 0;
756
+ end = 0;
757
+ sum;
758
+ sumSq;
759
+ update(newStart, newEnd) {
760
+ const s = Math.max(0, newStart | 0);
761
+ const e = Math.max(s, newEnd | 0);
762
+ while (this.end < e) {
763
+ this.addFrame(this.end, this.sum, this.sumSq);
764
+ this.end++;
765
+ }
766
+ while (this.start < s) {
767
+ this.removeFrame(this.start, this.sum, this.sumSq);
768
+ this.start++;
769
+ }
770
+ if (this.start > s || this.end > e) {
771
+ this.sum.fill(0);
772
+ this.sumSq.fill(0);
773
+ this.start = s;
774
+ this.end = s;
775
+ while (this.end < e) {
776
+ this.addFrame(this.end, this.sum, this.sumSq);
777
+ this.end++;
778
+ }
779
+ }
780
+ }
781
+ };
782
+ async function searchTrackV1Guided(params) {
783
+ const tStart = nowMs2();
784
+ const options = params.options ?? {};
785
+ const hopSec = Math.max(5e-3, options.hopSec ?? 0.03);
786
+ const threshold = clamp013(options.threshold ?? 0.75);
787
+ const localContrastEnabled = options.localContrast?.enabled ?? true;
788
+ const backgroundScale = Math.max(1, options.localContrast?.backgroundScale ?? 3);
789
+ const qt0 = Math.min(params.queryRegion.t0, params.queryRegion.t1);
790
+ const qt1 = Math.max(params.queryRegion.t0, params.queryRegion.t1);
791
+ const windowSec = Math.max(1e-3, qt1 - qt0);
792
+ const minSpacingSec = Math.max(0, options.minCandidateSpacingSec ?? windowSec * 0.8);
793
+ const refinementEnabled = !!options.refinement?.enabled;
794
+ const refinementLabels = options.refinement?.labels ?? [];
795
+ const includeQueryAsPositive = options.refinement?.includeQueryAsPositive ?? true;
796
+ const modelDecision = decideModelKind({ enabled: refinementEnabled, labels: refinementLabels });
797
+ const baselineExplain = refinementEnabled ? {
798
+ kind: "baseline",
799
+ positives: modelDecision.positives.length,
800
+ negatives: modelDecision.negatives.length
801
+ } : { kind: "baseline", positives: 0, negatives: 0 };
802
+ const tPrep0 = nowMs2();
803
+ const timesFrames = params.mel.times;
804
+ const nFrames = timesFrames.length;
805
+ const trackDuration = Math.max(
806
+ nFrames ? timesFrames[nFrames - 1] ?? 0 : 0,
807
+ params.onsetEnvelope.times.length ? params.onsetEnvelope.times[params.onsetEnvelope.times.length - 1] ?? 0 : 0
808
+ );
809
+ const nWindows = Math.max(0, Math.floor((trackDuration - windowSec) / hopSec) + 1);
810
+ const times = new Float32Array(nWindows);
811
+ const scores = new Float32Array(nWindows);
812
+ if (nWindows === 0) {
813
+ const totalMs2 = nowMs2() - tStart;
814
+ return {
815
+ times,
816
+ scores,
817
+ candidates: [],
818
+ curveKind: "similarity",
819
+ model: baselineExplain,
820
+ meta: {
821
+ fingerprintMs: 0,
822
+ scanMs: 0,
823
+ modelMs: 0,
824
+ totalMs: totalMs2,
825
+ windowSec,
826
+ hopSec,
827
+ skippedWindows: 0,
828
+ scannedWindows: 0
829
+ }
830
+ };
831
+ }
832
+ const skipOverlap = options.skipWindowOverlap;
833
+ const shouldSkip = (t0, t1) => {
834
+ if (!skipOverlap) return false;
835
+ const s0 = skipOverlap.t0;
836
+ const s1 = skipOverlap.t1;
837
+ return t0 < s1 && t1 > s0;
838
+ };
839
+ const melDim = params.mel.melBands[0]?.length ?? 0;
840
+ const mfccFullDim = params.mfcc?.values[0]?.length ?? 0;
841
+ const mfccDim = Math.max(0, Math.min(12, mfccFullDim - 1));
842
+ const layout = makeFeatureVectorLayoutV1({ melDim, mfccDim, includeContrast: localContrastEnabled });
843
+ const melScale = new Float32Array(nFrames);
844
+ const melBands = params.mel.melBands;
845
+ for (let t = 0; t < nFrames; t++) {
846
+ if ((t & 2047) === 0 && options.isCancelled?.()) throw new Error("@octoseq/mir: cancelled");
847
+ const row = melBands[t];
848
+ if (!row) {
849
+ melScale[t] = 1;
850
+ continue;
851
+ }
852
+ let sumSq = 0;
853
+ for (let i = 0; i < melDim; i++) {
854
+ const x = row[i] ?? 0;
855
+ sumSq += x * x;
856
+ }
857
+ const n = Math.sqrt(sumSq);
858
+ melScale[t] = n > 1e-12 ? 1 / n : 1;
859
+ }
860
+ const mfccScale = mfccDim > 0 ? new Float32Array(nFrames) : null;
861
+ const mfccFrames = params.mfcc?.values ?? null;
862
+ if (mfccScale && mfccFrames) {
863
+ for (let t = 0; t < nFrames; t++) {
864
+ if ((t & 2047) === 0 && options.isCancelled?.()) throw new Error("@octoseq/mir: cancelled");
865
+ const row = mfccFrames[t];
866
+ if (!row) {
867
+ mfccScale[t] = 1;
868
+ continue;
869
+ }
870
+ let sumSq = 0;
871
+ for (let i = 0; i < mfccDim; i++) {
872
+ const x = row[i + 1] ?? 0;
873
+ sumSq += x * x;
874
+ }
875
+ const n = Math.sqrt(sumSq);
876
+ mfccScale[t] = n > 1e-12 ? 1 / n : 1;
877
+ }
878
+ }
879
+ const onsetValues = new Float32Array(nFrames);
880
+ const onsetSrc = params.onsetEnvelope.values;
881
+ for (let i = 0; i < nFrames; i++) onsetValues[i] = onsetSrc[i] ?? 0;
882
+ const onsetPrefix = new Float64Array(nFrames + 1);
883
+ onsetPrefix[0] = 0;
884
+ for (let i = 0; i < nFrames; i++) {
885
+ if ((i & 4095) === 0 && options.isCancelled?.()) throw new Error("@octoseq/mir: cancelled");
886
+ onsetPrefix[i + 1] = (onsetPrefix[i] ?? 0) + (onsetValues[i] ?? 0);
887
+ }
888
+ const onsetMax = buildSparseTableMax(onsetValues, options.isCancelled);
889
+ const onsetPeaks = peakPick(timesFrames, onsetValues, {
890
+ minIntervalSec: options.queryPeakPick?.minIntervalSec,
891
+ threshold: options.queryPeakPick?.threshold,
892
+ adaptive: options.queryPeakPick?.adaptiveFactor ? { method: "meanStd", factor: options.queryPeakPick.adaptiveFactor } : void 0,
893
+ strict: true
894
+ });
895
+ const isPeak = new Uint8Array(nFrames);
896
+ for (const p of onsetPeaks) {
897
+ const idx = p.index | 0;
898
+ if (idx >= 0 && idx < nFrames) isPeak[idx] = 1;
899
+ }
900
+ const peakPrefix = new Uint32Array(nFrames + 1);
901
+ for (let i = 0; i < nFrames; i++) peakPrefix[i + 1] = (peakPrefix[i] ?? 0) + (isPeak[i] ?? 0);
902
+ const fingerprintMs = nowMs2() - tPrep0;
903
+ const addMelFrame = (frame, sum, sumSq) => {
904
+ const row = melBands[frame];
905
+ const s = melScale[frame] ?? 1;
906
+ for (let i = 0; i < melDim; i++) {
907
+ const x = (row?.[i] ?? 0) * s;
908
+ sum[i] = (sum[i] ?? 0) + x;
909
+ sumSq[i] = (sumSq[i] ?? 0) + x * x;
910
+ }
911
+ };
912
+ const removeMelFrame = (frame, sum, sumSq) => {
913
+ const row = melBands[frame];
914
+ const s = melScale[frame] ?? 1;
915
+ for (let i = 0; i < melDim; i++) {
916
+ const x = (row?.[i] ?? 0) * s;
917
+ sum[i] = (sum[i] ?? 0) - x;
918
+ sumSq[i] = (sumSq[i] ?? 0) - x * x;
919
+ }
920
+ };
921
+ const addMfccFrame = (frame, sum, sumSq) => {
922
+ const row = mfccFrames?.[frame];
923
+ const s = mfccScale?.[frame] ?? 1;
924
+ for (let i = 0; i < mfccDim; i++) {
925
+ const x = (row?.[i + 1] ?? 0) * s;
926
+ sum[i] = (sum[i] ?? 0) + x;
927
+ sumSq[i] = (sumSq[i] ?? 0) + x * x;
928
+ }
929
+ };
930
+ const removeMfccFrame = (frame, sum, sumSq) => {
931
+ const row = mfccFrames?.[frame];
932
+ const s = mfccScale?.[frame] ?? 1;
933
+ for (let i = 0; i < mfccDim; i++) {
934
+ const x = (row?.[i + 1] ?? 0) * s;
935
+ sum[i] = (sum[i] ?? 0) - x;
936
+ sumSq[i] = (sumSq[i] ?? 0) - x * x;
937
+ }
938
+ };
939
+ const melFg = new SlidingMoments(melDim, addMelFrame, removeMelFrame);
940
+ const melBg = new SlidingMoments(melDim, addMelFrame, removeMelFrame);
941
+ const mfccFg = mfccDim > 0 ? new SlidingMoments(mfccDim, addMfccFrame, removeMfccFrame) : null;
942
+ const mfccBg = mfccDim > 0 ? new SlidingMoments(mfccDim, addMfccFrame, removeMfccFrame) : null;
943
+ const writeVectorFromState = (opts) => {
944
+ const out = opts.out;
945
+ out.fill(0);
946
+ const fgCount = Math.max(0, opts.fgEndIdx - opts.fgStartIdx);
947
+ const bgCount = Math.max(0, opts.bgEndIdx - opts.bgStartIdx);
948
+ const bgExCount = Math.max(0, bgCount - fgCount);
949
+ for (let i = 0; i < melDim; i++) {
950
+ const sum = melFg.sum[i] ?? 0;
951
+ const sumSq = melFg.sumSq[i] ?? 0;
952
+ const mean = fgCount > 0 ? sum / fgCount : 0;
953
+ const variance = fgCount > 0 ? Math.max(0, sumSq / fgCount - mean * mean) : 0;
954
+ out[layout.melMeanFg.offset + i] = mean;
955
+ out[layout.melVarianceFg.offset + i] = variance;
956
+ if (layout.melContrast) {
957
+ const bgSum = melBg.sum[i] ?? 0;
958
+ const bgMeanEx = bgExCount > 0 ? (bgSum - sum) / bgExCount : mean;
959
+ out[layout.melContrast.offset + i] = mean - bgMeanEx;
960
+ }
961
+ }
962
+ const fgOnsetSum = (onsetPrefix[opts.fgEndIdx] ?? 0) - (onsetPrefix[opts.fgStartIdx] ?? 0);
963
+ const fgOnsetMean = fgCount > 0 ? fgOnsetSum / fgCount : 0;
964
+ const fgOnsetMaxRaw = onsetMax.query(opts.fgStartIdx, opts.fgEndIdx);
965
+ const fgOnsetMax = Number.isFinite(fgOnsetMaxRaw) && fgOnsetMaxRaw !== -Infinity ? fgOnsetMaxRaw : 0;
966
+ const fgPeaks = (peakPrefix[opts.fgEndIdx] ?? 0) - (peakPrefix[opts.fgStartIdx] ?? 0);
967
+ const fgDur = Math.max(1e-6, opts.fgEndSec - opts.fgStartSec);
968
+ const fgPeakDensity = fgPeaks / fgDur;
969
+ out[layout.onsetFg.offset + 0] = fgOnsetMean;
970
+ out[layout.onsetFg.offset + 1] = fgOnsetMax;
971
+ out[layout.onsetFg.offset + 2] = fgPeakDensity;
972
+ if (layout.onsetContrast) {
973
+ const bgOnsetSum = (onsetPrefix[opts.bgEndIdx] ?? 0) - (onsetPrefix[opts.bgStartIdx] ?? 0);
974
+ const bgOnsetMeanEx = bgExCount > 0 ? (bgOnsetSum - fgOnsetSum) / bgExCount : fgOnsetMean;
975
+ const leftMax = onsetMax.query(opts.bgStartIdx, opts.fgStartIdx);
976
+ const rightMax = onsetMax.query(opts.fgEndIdx, opts.bgEndIdx);
977
+ const bgOnsetMaxEx = Math.max(
978
+ Number.isFinite(leftMax) && leftMax !== -Infinity ? leftMax : -Infinity,
979
+ Number.isFinite(rightMax) && rightMax !== -Infinity ? rightMax : -Infinity
980
+ );
981
+ const bgOnsetMaxExSafe = bgOnsetMaxEx === -Infinity ? fgOnsetMax : bgOnsetMaxEx;
982
+ const bgPeaks = (peakPrefix[opts.bgEndIdx] ?? 0) - (peakPrefix[opts.bgStartIdx] ?? 0);
983
+ const bgPeaksEx = Math.max(0, bgPeaks - fgPeaks);
984
+ const bgExDur = Math.max(1e-6, opts.bgEndSec - opts.bgStartSec - fgDur);
985
+ const bgPeakDensityEx = bgPeaksEx / bgExDur;
986
+ out[layout.onsetContrast.offset + 0] = fgOnsetMean - bgOnsetMeanEx;
987
+ out[layout.onsetContrast.offset + 1] = fgOnsetMax - bgOnsetMaxExSafe;
988
+ out[layout.onsetContrast.offset + 2] = fgPeakDensity - bgPeakDensityEx;
989
+ }
990
+ if (mfccDim > 0 && mfccFg && mfccBg && layout.mfccMeanFg && layout.mfccVarianceFg) {
991
+ for (let i = 0; i < mfccDim; i++) {
992
+ const sum = mfccFg.sum[i] ?? 0;
993
+ const sumSq = mfccFg.sumSq[i] ?? 0;
994
+ const mean = fgCount > 0 ? sum / fgCount : 0;
995
+ const variance = fgCount > 0 ? Math.max(0, sumSq / fgCount - mean * mean) : 0;
996
+ out[layout.mfccMeanFg.offset + i] = mean;
997
+ out[layout.mfccVarianceFg.offset + i] = variance;
998
+ if (layout.mfccMeanContrast && layout.mfccVarianceContrast) {
999
+ const bgSum = mfccBg.sum[i] ?? 0;
1000
+ const bgSumSq = mfccBg.sumSq[i] ?? 0;
1001
+ const bgMeanEx = bgExCount > 0 ? (bgSum - sum) / bgExCount : mean;
1002
+ const bgVarEx = bgExCount > 0 ? Math.max(0, (bgSumSq - sumSq) / bgExCount - bgMeanEx * bgMeanEx) : variance;
1003
+ out[layout.mfccMeanContrast.offset + i] = mean - bgMeanEx;
1004
+ out[layout.mfccVarianceContrast.offset + i] = variance - bgVarEx;
1005
+ }
1006
+ }
1007
+ }
1008
+ };
1009
+ const computeVectorForInterval = (t0, t1, out) => {
1010
+ const fgStartSec = Math.min(t0, t1);
1011
+ const fgEndSec = Math.max(t0, t1);
1012
+ const { bgStartSec, bgEndSec } = computeBackgroundWindow({
1013
+ fgStartSec,
1014
+ fgEndSec,
1015
+ trackDurationSec: trackDuration,
1016
+ backgroundScale
1017
+ });
1018
+ const fgStartIdx = advanceStartIndex(timesFrames, 0, fgStartSec);
1019
+ const fgEndIdx = advanceEndIndex(timesFrames, fgStartIdx, fgEndSec);
1020
+ const bgStartIdx = advanceStartIndex(timesFrames, 0, bgStartSec);
1021
+ const bgEndIdx = advanceEndIndex(timesFrames, bgStartIdx, bgEndSec);
1022
+ melFg.update(fgStartIdx, fgEndIdx);
1023
+ melBg.update(bgStartIdx, bgEndIdx);
1024
+ mfccFg?.update(fgStartIdx, fgEndIdx);
1025
+ mfccBg?.update(bgStartIdx, bgEndIdx);
1026
+ writeVectorFromState({
1027
+ fgStartIdx,
1028
+ fgEndIdx,
1029
+ bgStartIdx,
1030
+ bgEndIdx,
1031
+ fgStartSec,
1032
+ fgEndSec,
1033
+ bgStartSec,
1034
+ bgEndSec,
1035
+ out
1036
+ });
1037
+ };
1038
+ const queryVec = new Float32Array(layout.dim);
1039
+ computeVectorForInterval(qt0, qt1, queryVec);
1040
+ const resetSlidingState = () => {
1041
+ melFg.update(0, 0);
1042
+ melBg.update(0, 0);
1043
+ mfccFg?.update(0, 0);
1044
+ mfccBg?.update(0, 0);
1045
+ };
1046
+ const buildWindowVectorsPass = (onWindow) => {
1047
+ resetSlidingState();
1048
+ let fgStartIdx = 0;
1049
+ let fgEndIdx = 0;
1050
+ let bgStartIdx = 0;
1051
+ let bgEndIdx = 0;
1052
+ const vec = new Float32Array(layout.dim);
1053
+ for (let w = 0; w < nWindows; w++) {
1054
+ if ((w & 255) === 0 && options.isCancelled?.()) throw new Error("@octoseq/mir: cancelled");
1055
+ const t0 = w * hopSec;
1056
+ const t1 = t0 + windowSec;
1057
+ times[w] = t0;
1058
+ fgStartIdx = advanceStartIndex(timesFrames, fgStartIdx, t0);
1059
+ fgEndIdx = advanceEndIndex(timesFrames, fgEndIdx, t1);
1060
+ const { bgStartSec, bgEndSec } = computeBackgroundWindow({
1061
+ fgStartSec: t0,
1062
+ fgEndSec: t1,
1063
+ trackDurationSec: trackDuration,
1064
+ backgroundScale
1065
+ });
1066
+ bgStartIdx = advanceStartIndex(timesFrames, bgStartIdx, bgStartSec);
1067
+ bgEndIdx = advanceEndIndex(timesFrames, bgEndIdx, bgEndSec);
1068
+ melFg.update(fgStartIdx, fgEndIdx);
1069
+ melBg.update(bgStartIdx, bgEndIdx);
1070
+ mfccFg?.update(fgStartIdx, fgEndIdx);
1071
+ mfccBg?.update(bgStartIdx, bgEndIdx);
1072
+ writeVectorFromState({
1073
+ fgStartIdx,
1074
+ fgEndIdx,
1075
+ bgStartIdx,
1076
+ bgEndIdx,
1077
+ fgStartSec: t0,
1078
+ fgEndSec: t1,
1079
+ bgStartSec,
1080
+ bgEndSec,
1081
+ out: vec
1082
+ });
1083
+ onWindow(w, t0, t1, { start: bgStartSec, end: bgEndSec }, vec);
1084
+ }
1085
+ };
1086
+ let skippedWindows = 0;
1087
+ let scannedWindows = 0;
1088
+ const scanStartMs = nowMs2();
1089
+ let curveKind = "similarity";
1090
+ let modelExplain = baselineExplain;
1091
+ let modelMs = 0;
1092
+ let trainedModel = null;
1093
+ let zMean = null;
1094
+ let zInvStd = null;
1095
+ const runBaselineSimilarityScan = () => {
1096
+ skippedWindows = 0;
1097
+ scannedWindows = 0;
1098
+ buildWindowVectorsPass((w, t0, t1, _bg, vec) => {
1099
+ if (shouldSkip(t0, t1)) {
1100
+ scores[w] = 0;
1101
+ skippedWindows++;
1102
+ return;
1103
+ }
1104
+ scannedWindows++;
1105
+ scores[w] = cosineSimilarity01ByBlocks(queryVec, vec, layout, options.weights);
1106
+ });
1107
+ };
1108
+ if (modelDecision.kind === "baseline") {
1109
+ runBaselineSimilarityScan();
1110
+ } else {
1111
+ const tModel0 = nowMs2();
1112
+ curveKind = "confidence";
1113
+ try {
1114
+ const dim = layout.dim;
1115
+ const sum = new Float64Array(dim);
1116
+ const sumSq = new Float64Array(dim);
1117
+ buildWindowVectorsPass((_w, _t0, _t1, _bg, vec) => {
1118
+ for (let j = 0; j < dim; j++) {
1119
+ const x = vec[j] ?? 0;
1120
+ sum[j] = (sum[j] ?? 0) + x;
1121
+ sumSq[j] = (sumSq[j] ?? 0) + x * x;
1122
+ }
1123
+ });
1124
+ const mean = new Float32Array(dim);
1125
+ const invStd = new Float32Array(dim);
1126
+ const n = Math.max(1, nWindows);
1127
+ for (let j = 0; j < dim; j++) {
1128
+ const mu = (sum[j] ?? 0) / n;
1129
+ const ex2 = (sumSq[j] ?? 0) / n;
1130
+ const v = Math.max(0, ex2 - mu * mu);
1131
+ const std = Math.sqrt(v);
1132
+ mean[j] = mu;
1133
+ invStd[j] = std > 1e-6 ? 1 / std : 1;
1134
+ }
1135
+ zMean = mean;
1136
+ zInvStd = invStd;
1137
+ const positives = [];
1138
+ const negatives = [];
1139
+ const makeExample = (t0, t1) => {
1140
+ const v = new Float32Array(dim);
1141
+ computeVectorForInterval(t0, t1, v);
1142
+ zScoreInPlace(v, mean, invStd);
1143
+ return v;
1144
+ };
1145
+ for (const l of modelDecision.positives) positives.push(makeExample(l.t0, l.t1));
1146
+ for (const l of modelDecision.negatives) negatives.push(makeExample(l.t0, l.t1));
1147
+ if (includeQueryAsPositive) {
1148
+ const q = new Float32Array(dim);
1149
+ q.set(queryVec);
1150
+ zScoreInPlace(q, mean, invStd);
1151
+ positives.push(q);
1152
+ }
1153
+ trainedModel = modelDecision.kind === "logistic" ? trainLogisticModelV1({ positives, negatives, layout }) : buildPrototypeModelV1({ positives, layout });
1154
+ modelExplain = trainedModel.explain;
1155
+ skippedWindows = 0;
1156
+ scannedWindows = 0;
1157
+ buildWindowVectorsPass((w, t0, t1, _bg, vec) => {
1158
+ if (shouldSkip(t0, t1)) {
1159
+ scores[w] = 0;
1160
+ skippedWindows++;
1161
+ return;
1162
+ }
1163
+ scannedWindows++;
1164
+ zScoreInPlace(vec, mean, invStd);
1165
+ scores[w] = scoreWithModelV1(trainedModel, vec);
1166
+ });
1167
+ } catch (e) {
1168
+ if (e instanceof Error && e.message === "@octoseq/mir: cancelled") throw e;
1169
+ curveKind = "similarity";
1170
+ modelExplain = baselineExplain;
1171
+ trainedModel = null;
1172
+ zMean = null;
1173
+ zInvStd = null;
1174
+ runBaselineSimilarityScan();
1175
+ } finally {
1176
+ modelMs = nowMs2() - tModel0;
1177
+ }
1178
+ }
1179
+ const scanMs = nowMs2() - scanStartMs;
1180
+ const events = peakPick(times, scores, {
1181
+ threshold,
1182
+ minIntervalSec: minSpacingSec,
1183
+ strict: options.candidatePeakPick?.strict ?? true
1184
+ });
1185
+ const candidates = events.map((e) => {
1186
+ const windowStartSec = e.time;
1187
+ const windowEndSec = windowStartSec + windowSec;
1188
+ return {
1189
+ timeSec: e.time,
1190
+ score: e.strength,
1191
+ windowStartSec,
1192
+ windowEndSec
1193
+ };
1194
+ });
1195
+ if (trainedModel?.kind === "logistic" && zMean && zInvStd) {
1196
+ const tmp = new Float32Array(layout.dim);
1197
+ for (const c of candidates) {
1198
+ tmp.fill(0);
1199
+ computeVectorForInterval(c.windowStartSec, c.windowEndSec, tmp);
1200
+ zScoreInPlace(tmp, zMean, zInvStd);
1201
+ c.explain = {
1202
+ groupLogit: logitContributionsByGroupV1(trainedModel.w, trainedModel.b, tmp, layout)
1203
+ };
1204
+ }
1205
+ }
1206
+ const totalMs = nowMs2() - tStart;
1207
+ return {
1208
+ times,
1209
+ scores,
1210
+ candidates,
1211
+ curveKind,
1212
+ model: modelExplain,
1213
+ meta: {
1214
+ fingerprintMs,
1215
+ scanMs,
1216
+ modelMs,
1217
+ totalMs,
1218
+ windowSec,
1219
+ hopSec,
1220
+ skippedWindows,
1221
+ scannedWindows
1222
+ }
1223
+ };
1224
+ }
1225
+
1226
+ // src/index.ts
1227
+ var MIR_VERSION = "0.1.0";
1228
+ function helloMir(name = "world") {
1229
+ return `Hello, ${name} from @octoseq/mir v${MIR_VERSION}`;
1230
+ }
1231
+
1232
+ export { MIR_VERSION, MirGPU, clampDb, fingerprintToVectorV1, fingerprintV1, helloMir, minMax, normaliseForWaveform, searchTrackV1, searchTrackV1Guided, similarityFingerprintV1, spectrogramToDb };
1233
+ //# sourceMappingURL=index.js.map
1234
+ //# sourceMappingURL=index.js.map