@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
@@ -0,0 +1,98 @@
1
+ import type { MirFingerprintV1 } from "./fingerprintV1";
2
+
3
+ export type MirFingerprintVectorWeights = {
4
+ /** Weight for mel(mean+variance) block. */
5
+ mel?: number;
6
+ /** Weight for transient/onset scalars block. */
7
+ transient?: number;
8
+ /** Weight for MFCC(mean+variance) block (if present). */
9
+ mfcc?: number;
10
+ };
11
+
12
+ function l2Norm(v: Float32Array): number {
13
+ let sum = 0;
14
+ for (let i = 0; i < v.length; i++) {
15
+ const x = v[i] ?? 0;
16
+ sum += x * x;
17
+ }
18
+ return Math.sqrt(sum);
19
+ }
20
+
21
+ function normaliseL2InPlace(v: Float32Array, eps = 1e-12): void {
22
+ const n = l2Norm(v);
23
+ const d = n > eps ? n : 1;
24
+ for (let i = 0; i < v.length; i++) v[i] = (v[i] ?? 0) / d;
25
+ }
26
+
27
+ function cosineSimilarity(a: Float32Array, b: Float32Array): number {
28
+ if (a.length !== b.length) throw new Error("@octoseq/mir: cosineSimilarity length mismatch");
29
+ let dot = 0;
30
+ let aa = 0;
31
+ let bb = 0;
32
+ for (let i = 0; i < a.length; i++) {
33
+ const x = a[i] ?? 0;
34
+ const y = b[i] ?? 0;
35
+ dot += x * y;
36
+ aa += x * x;
37
+ bb += y * y;
38
+ }
39
+ const denom = Math.sqrt(aa) * Math.sqrt(bb);
40
+ if (denom <= 0) return 0;
41
+
42
+ // Map cosine [-1,1] -> [0,1]. With our features it tends to be >= 0 anyway,
43
+ // but we keep the mapping deterministic and bounded.
44
+ const cos = dot / denom;
45
+ // Map cosine: clear negatives (penalize mismatch strongly), keep positives 0..1.
46
+ // Previous (cos+1)/2 was too lenient for "orthogonal" features.
47
+ return Math.max(0, cos);
48
+ }
49
+
50
+ function pushScaled(dst: number[], src: Float32Array, scale: number) {
51
+ for (let i = 0; i < src.length; i++) dst.push((src[i] ?? 0) * scale);
52
+ }
53
+
54
+ /**
55
+ * Convert a v1 fingerprint into a concatenated feature vector suitable for cosine similarity.
56
+ *
57
+ * Rules:
58
+ * - mel and mfcc blocks are L2-normalised separately (mean+var concatenated per-block)
59
+ * - transient scalars are treated as a small vector and L2-normalised too
60
+ * - blocks are then concatenated with optional weights applied per-block
61
+ */
62
+ export function fingerprintToVectorV1(fp: MirFingerprintV1, weights: MirFingerprintVectorWeights = {}): Float32Array {
63
+ const wMel = weights.mel ?? 1;
64
+ const wTrans = weights.transient ?? 1;
65
+ const wMfcc = weights.mfcc ?? 1;
66
+
67
+ // --- mel block
68
+ const melBlock = new Float32Array(fp.mel.mean.length + fp.mel.variance.length);
69
+ melBlock.set(fp.mel.mean, 0);
70
+ melBlock.set(fp.mel.variance, fp.mel.mean.length);
71
+ normaliseL2InPlace(melBlock);
72
+
73
+ // --- transient block
74
+ const transBlock = new Float32Array([fp.onset.mean, fp.onset.max, fp.onset.peakDensityHz]);
75
+ normaliseL2InPlace(transBlock);
76
+
77
+ // --- optional mfcc block
78
+ let mfccBlock: Float32Array | null = null;
79
+ if (fp.mfcc) {
80
+ mfccBlock = new Float32Array(fp.mfcc.mean.length + fp.mfcc.variance.length);
81
+ mfccBlock.set(fp.mfcc.mean, 0);
82
+ mfccBlock.set(fp.mfcc.variance, fp.mfcc.mean.length);
83
+ normaliseL2InPlace(mfccBlock);
84
+ }
85
+
86
+ const out: number[] = [];
87
+ pushScaled(out, melBlock, wMel);
88
+ pushScaled(out, transBlock, wTrans);
89
+ if (mfccBlock) pushScaled(out, mfccBlock, wMfcc);
90
+
91
+ return new Float32Array(out);
92
+ }
93
+
94
+ export function similarityFingerprintV1(a: MirFingerprintV1, b: MirFingerprintV1, weights: MirFingerprintVectorWeights = {}): number {
95
+ const va = fingerprintToVectorV1(a, weights);
96
+ const vb = fingerprintToVectorV1(b, weights);
97
+ return cosineSimilarity(va, vb);
98
+ }
package/src/types.ts ADDED
@@ -0,0 +1,105 @@
1
+ export type MirBackend = "cpu" | "gpu";
2
+
3
+ export type MirRunTimings = {
4
+ totalMs: number;
5
+ cpuMs?: number;
6
+ gpuMs?: number;
7
+ };
8
+
9
+ export type MirRunMeta = {
10
+ backend: MirBackend;
11
+ usedGpu: boolean;
12
+ timings: MirRunTimings;
13
+ };
14
+
15
+ export type Mir1DResult = {
16
+ kind: "1d";
17
+ times: Float32Array;
18
+ values: Float32Array;
19
+ meta: MirRunMeta;
20
+ };
21
+
22
+ export type Mir2DResult = {
23
+ kind: "2d";
24
+ times: Float32Array;
25
+ data: Float32Array[];
26
+ meta: MirRunMeta;
27
+ };
28
+
29
+ export type MirEvent = {
30
+ time: number;
31
+ strength: number;
32
+ index: number;
33
+ };
34
+
35
+ export type MirEventsResult = {
36
+ kind: "events";
37
+ times: Float32Array;
38
+ events: MirEvent[];
39
+ meta: MirRunMeta;
40
+ };
41
+
42
+ export type MirResult = Mir1DResult | Mir2DResult | MirEventsResult;
43
+
44
+ // (moved above)
45
+
46
+ export type MirFunctionId =
47
+ | "spectralCentroid"
48
+ | "spectralFlux"
49
+ | "melSpectrogram"
50
+ | "onsetEnvelope"
51
+ | "onsetPeaks"
52
+ | "hpssHarmonic"
53
+ | "hpssPercussive"
54
+ | "mfcc"
55
+ | "mfccDelta"
56
+ | "mfccDeltaDelta";
57
+
58
+ export type MirRunRequest = {
59
+ fn: MirFunctionId;
60
+ spectrogram?: {
61
+ fftSize: number;
62
+ hopSize: number;
63
+ window: "hann";
64
+ };
65
+ mel?: {
66
+ nMels: number;
67
+ fMin?: number;
68
+ fMax?: number;
69
+ };
70
+ backend?: MirBackend;
71
+
72
+ // Optional per-run config. Kept explicit (not a dynamic schema).
73
+ onset?: {
74
+ smoothMs?: number;
75
+ diffMethod?: "rectified" | "abs";
76
+ useLog?: boolean;
77
+ };
78
+ peakPick?: {
79
+ minIntervalSec?: number;
80
+ threshold?: number;
81
+ adaptiveFactor?: number;
82
+ };
83
+ hpss?: {
84
+ timeMedian?: number;
85
+ freqMedian?: number;
86
+ spectrogram?: {
87
+ fftSize: number;
88
+ hopSize: number;
89
+ window: "hann";
90
+ };
91
+ };
92
+ mfcc?: {
93
+ nCoeffs?: number;
94
+ spectrogram?: {
95
+ fftSize: number;
96
+ hopSize: number;
97
+ window: "hann";
98
+ };
99
+ };
100
+ };
101
+
102
+ export type MirAudioPayload = {
103
+ sampleRate: number;
104
+ mono: Float32Array;
105
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Display-only transforms for spectrogram-like (magnitude) data.
3
+ *
4
+ * These helpers are intentionally *not* used inside core MIR algorithms.
5
+ * They exist so applications can apply established visualisation practices
6
+ * (e.g. dB conversion, clamping) without mutating or rescaling the analysis
7
+ * outputs.
8
+ *
9
+ * Shape conventions:
10
+ * - 2D arrays are `[frame][bin]`.
11
+ */
12
+
13
+ export type Spectrogram2D = Float32Array[]; // [frame][bin]
14
+
15
+ export type SpectrogramToDbOptions = {
16
+ /**
17
+ * Optional floor (minimum) dB value applied during conversion.
18
+ * This is a display convenience only.
19
+ */
20
+ floorDb?: number;
21
+
22
+ /** Epsilon used to avoid log(0). Defaults to 1e-12. */
23
+ epsilon?: number;
24
+ };
25
+
26
+ /**
27
+ * Convert linear magnitudes to dB.
28
+ *
29
+ * Formula: `db = 20 * log10(max(eps, magnitude))`
30
+ *
31
+ * Notes:
32
+ * - This does *not* normalise or re-reference the values.
33
+ * - The input is not mutated.
34
+ * - Intended for visualisation only.
35
+ */
36
+ export function spectrogramToDb(magnitudes2d: Spectrogram2D, options: SpectrogramToDbOptions = {}): Spectrogram2D {
37
+ const eps = options.epsilon ?? 1e-12;
38
+ const floorDb = options.floorDb;
39
+
40
+ const out: Float32Array[] = new Array(magnitudes2d.length);
41
+
42
+ for (let t = 0; t < magnitudes2d.length; t++) {
43
+ const row = magnitudes2d[t] ?? new Float32Array(0);
44
+ const dbRow = new Float32Array(row.length);
45
+
46
+ for (let k = 0; k < row.length; k++) {
47
+ const mag = row[k] ?? 0;
48
+ const db = 20 * Math.log10(Math.max(eps, mag));
49
+ dbRow[k] = floorDb !== undefined ? Math.max(floorDb, db) : db;
50
+ }
51
+
52
+ out[t] = dbRow;
53
+ }
54
+
55
+ return out;
56
+ }
57
+
58
+ /**
59
+ * Clamp a dB-scaled 2D array to a fixed range.
60
+ *
61
+ * The input is not mutated.
62
+ * Intended for visualisation only.
63
+ */
64
+ export function clampDb(db2d: Spectrogram2D, minDb: number, maxDb: number): Spectrogram2D {
65
+ const out: Float32Array[] = new Array(db2d.length);
66
+
67
+ for (let t = 0; t < db2d.length; t++) {
68
+ const row = db2d[t] ?? new Float32Array(0);
69
+ const clamped = new Float32Array(row.length);
70
+
71
+ for (let k = 0; k < row.length; k++) {
72
+ const v = row[k] ?? 0;
73
+ clamped[k] = v < minDb ? minDb : v > maxDb ? maxDb : v;
74
+ }
75
+
76
+ out[t] = clamped;
77
+ }
78
+
79
+ return out;
80
+ }
@@ -0,0 +1,58 @@
1
+ export type NormaliseForWaveformOptions = {
2
+ min?: number;
3
+ max?: number;
4
+ center?: boolean;
5
+ };
6
+
7
+ /**
8
+ * Normalise a time-aligned feature array into a waveform-friendly range.
9
+ *
10
+ * Typical uses:
11
+ * - Map spectralFlux or centroid to [-1, 1] to re-use a waveform renderer.
12
+ *
13
+ * Defaults:
14
+ * - If `center` is true: range [-1, 1] (zero-centered)
15
+ * - Else: range [0, 1]
16
+ */
17
+ export function normaliseForWaveform(
18
+ data: Float32Array,
19
+ options: NormaliseForWaveformOptions = {}
20
+ ): Float32Array {
21
+ const center = options.center ?? false;
22
+
23
+ const targetMin = options.min ?? (center ? -1 : 0);
24
+ const targetMax = options.max ?? 1;
25
+
26
+ if (!Number.isFinite(targetMin) || !Number.isFinite(targetMax)) {
27
+ throw new Error("@octoseq/mir: normaliseForWaveform min/max must be finite");
28
+ }
29
+ if (targetMax === targetMin) {
30
+ throw new Error("@octoseq/mir: normaliseForWaveform max must differ from min");
31
+ }
32
+
33
+ let srcMin = Infinity;
34
+ let srcMax = -Infinity;
35
+ for (let i = 0; i < data.length; i++) {
36
+ const v = data[i] ?? 0;
37
+ if (v < srcMin) srcMin = v;
38
+ if (v > srcMax) srcMax = v;
39
+ }
40
+
41
+ // Degenerate or empty: return a constant line at the middle of the target range.
42
+ if (!Number.isFinite(srcMin) || !Number.isFinite(srcMax) || srcMax === srcMin) {
43
+ const out = new Float32Array(data.length);
44
+ const mid = (targetMin + targetMax) / 2;
45
+ out.fill(mid);
46
+ return out;
47
+ }
48
+
49
+ const out = new Float32Array(data.length);
50
+ const scale = (targetMax - targetMin) / (srcMax - srcMin);
51
+
52
+ for (let i = 0; i < data.length; i++) {
53
+ const v = data[i] ?? 0;
54
+ out[i] = targetMin + (v - srcMin) * scale;
55
+ }
56
+
57
+ return out;
58
+ }
@@ -0,0 +1,25 @@
1
+ export type MinMax = {
2
+ min: number;
3
+ max: number;
4
+ };
5
+
6
+ /**
7
+ * Compute min/max in a single pass without using spread / Math.min(...arr).
8
+ *
9
+ * Safe for very large arrays (millions of samples).
10
+ */
11
+ export function minMax(values: ArrayLike<number>): MinMax {
12
+ const n = values.length >>> 0;
13
+ if (n === 0) return { min: Infinity, max: -Infinity };
14
+
15
+ let min = Infinity;
16
+ let max = -Infinity;
17
+
18
+ for (let i = 0; i < n; i++) {
19
+ const v = values[i] ?? 0;
20
+ if (v < min) min = v;
21
+ if (v > max) max = v;
22
+ }
23
+
24
+ return { min, max };
25
+ }