@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.
- package/dist/chunk-DUWYCAVG.js +1525 -0
- package/dist/chunk-DUWYCAVG.js.map +1 -0
- package/dist/index.d.ts +450 -0
- package/dist/index.js +1234 -0
- package/dist/index.js.map +1 -0
- package/dist/runMir-CSIBwNZ3.d.ts +84 -0
- package/dist/runner/runMir.d.ts +2 -0
- package/dist/runner/runMir.js +3 -0
- package/dist/runner/runMir.js.map +1 -0
- package/dist/runner/workerProtocol.d.ts +169 -0
- package/dist/runner/workerProtocol.js +11 -0
- package/dist/runner/workerProtocol.js.map +1 -0
- package/dist/types-BE3py4fZ.d.ts +83 -0
- package/package.json +55 -0
- package/src/dsp/fft.ts +22 -0
- package/src/dsp/fftBackend.ts +53 -0
- package/src/dsp/fftBackendFftjs.ts +60 -0
- package/src/dsp/hpss.ts +152 -0
- package/src/dsp/hpssGpu.ts +101 -0
- package/src/dsp/mel.ts +219 -0
- package/src/dsp/mfcc.ts +119 -0
- package/src/dsp/onset.ts +205 -0
- package/src/dsp/peakPick.ts +112 -0
- package/src/dsp/spectral.ts +95 -0
- package/src/dsp/spectrogram.ts +176 -0
- package/src/gpu/README.md +34 -0
- package/src/gpu/context.ts +44 -0
- package/src/gpu/helpers.ts +87 -0
- package/src/gpu/hpssMasks.ts +116 -0
- package/src/gpu/kernels/hpssMasks.wgsl.ts +137 -0
- package/src/gpu/kernels/melProject.wgsl.ts +48 -0
- package/src/gpu/kernels/onsetEnvelope.wgsl.ts +56 -0
- package/src/gpu/melProject.ts +98 -0
- package/src/gpu/onsetEnvelope.ts +81 -0
- package/src/gpu/webgpu.d.ts +176 -0
- package/src/index.ts +121 -0
- package/src/runner/runMir.ts +431 -0
- package/src/runner/workerProtocol.ts +189 -0
- package/src/search/featureVectorV1.ts +123 -0
- package/src/search/fingerprintV1.ts +230 -0
- package/src/search/refinedModelV1.ts +321 -0
- package/src/search/searchTrackV1.ts +206 -0
- package/src/search/searchTrackV1Guided.ts +863 -0
- package/src/search/similarity.ts +98 -0
- package/src/types.ts +105 -0
- package/src/util/display.ts +80 -0
- package/src/util/normalise.ts +58 -0
- package/src/util/stats.ts +25 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { MelSpectrogram } from "../dsp/mel";
|
|
2
|
+
import type { Features2D } from "../dsp/mfcc";
|
|
3
|
+
import { peakPick } from "../dsp/peakPick";
|
|
4
|
+
|
|
5
|
+
import { fingerprintV1 } from "./fingerprintV1";
|
|
6
|
+
import type { MirFingerprintVectorWeights } from "./similarity";
|
|
7
|
+
import { similarityFingerprintV1 } from "./similarity";
|
|
8
|
+
|
|
9
|
+
export type MirSearchCandidate = {
|
|
10
|
+
timeSec: number;
|
|
11
|
+
score: number;
|
|
12
|
+
windowStartSec: number;
|
|
13
|
+
windowEndSec: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type MirSearchResultV1 = {
|
|
17
|
+
times: Float32Array; // window start times
|
|
18
|
+
similarity: Float32Array; // [0,1]
|
|
19
|
+
candidates: MirSearchCandidate[];
|
|
20
|
+
meta: {
|
|
21
|
+
fingerprintMs: number;
|
|
22
|
+
scanMs: number;
|
|
23
|
+
totalMs: number;
|
|
24
|
+
windowSec: number;
|
|
25
|
+
hopSec: number;
|
|
26
|
+
skippedWindows: number;
|
|
27
|
+
scannedWindows: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type MirSearchOptionsV1 = {
|
|
32
|
+
/** Sliding window hop size in seconds. Default ~0.03s. */
|
|
33
|
+
hopSec?: number;
|
|
34
|
+
|
|
35
|
+
/** Similarity threshold for candidate detection. Default 0.75. */
|
|
36
|
+
threshold?: number;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Min spacing between candidates (seconds). Default is selectionDuration*0.8.
|
|
40
|
+
* Implemented via peakPick(minIntervalSec).
|
|
41
|
+
*/
|
|
42
|
+
minCandidateSpacingSec?: number;
|
|
43
|
+
|
|
44
|
+
/** If provided, windows overlapping [skipT0, skipT1] are skipped. */
|
|
45
|
+
skipWindowOverlap?: { t0: number; t1: number };
|
|
46
|
+
|
|
47
|
+
/** Optional weights for similarity vector blocks. */
|
|
48
|
+
weights?: MirFingerprintVectorWeights;
|
|
49
|
+
|
|
50
|
+
/** Peak-pick settings for query fingerprint peak density. */
|
|
51
|
+
queryPeakPick?: {
|
|
52
|
+
minIntervalSec?: number;
|
|
53
|
+
threshold?: number;
|
|
54
|
+
adaptiveFactor?: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Peak-pick settings for candidate detection on the similarity curve. */
|
|
58
|
+
candidatePeakPick?: {
|
|
59
|
+
strict?: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Cooperative cancellation hook (called frequently in scan loop). */
|
|
63
|
+
isCancelled?: () => boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function nowMs(): number {
|
|
67
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function clamp01(x: number): number {
|
|
71
|
+
if (x <= 0) return 0;
|
|
72
|
+
if (x >= 1) return 1;
|
|
73
|
+
return x;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// (no local helpers; keep this module minimal and lint-clean)
|
|
77
|
+
|
|
78
|
+
export async function searchTrackV1(params: {
|
|
79
|
+
queryRegion: { t0: number; t1: number };
|
|
80
|
+
|
|
81
|
+
mel: MelSpectrogram;
|
|
82
|
+
onsetEnvelope: { times: Float32Array; values: Float32Array };
|
|
83
|
+
mfcc?: Features2D;
|
|
84
|
+
|
|
85
|
+
options?: MirSearchOptionsV1;
|
|
86
|
+
}): Promise<MirSearchResultV1> {
|
|
87
|
+
const tStart = nowMs();
|
|
88
|
+
|
|
89
|
+
const options = params.options ?? {};
|
|
90
|
+
const hopSec = Math.max(0.005, options.hopSec ?? 0.03);
|
|
91
|
+
const threshold = clamp01(options.threshold ?? 0.75);
|
|
92
|
+
|
|
93
|
+
const qt0 = Math.min(params.queryRegion.t0, params.queryRegion.t1);
|
|
94
|
+
const qt1 = Math.max(params.queryRegion.t0, params.queryRegion.t1);
|
|
95
|
+
const windowSec = Math.max(1e-3, qt1 - qt0);
|
|
96
|
+
|
|
97
|
+
const minSpacingSec = Math.max(0, options.minCandidateSpacingSec ?? windowSec * 0.8);
|
|
98
|
+
|
|
99
|
+
// --- query fingerprint
|
|
100
|
+
const tFp0 = nowMs();
|
|
101
|
+
const queryFp = fingerprintV1({
|
|
102
|
+
t0: qt0,
|
|
103
|
+
t1: qt1,
|
|
104
|
+
mel: params.mel,
|
|
105
|
+
onsetEnvelope: params.onsetEnvelope,
|
|
106
|
+
mfcc: params.mfcc,
|
|
107
|
+
peakPick: options.queryPeakPick,
|
|
108
|
+
});
|
|
109
|
+
const fingerprintMs = nowMs() - tFp0;
|
|
110
|
+
|
|
111
|
+
// --- sliding scan
|
|
112
|
+
const scanStartMs = nowMs();
|
|
113
|
+
|
|
114
|
+
const trackDuration = Math.max(
|
|
115
|
+
params.mel.times.length ? (params.mel.times[params.mel.times.length - 1] ?? 0) : 0,
|
|
116
|
+
params.onsetEnvelope.times.length
|
|
117
|
+
? (params.onsetEnvelope.times[params.onsetEnvelope.times.length - 1] ?? 0)
|
|
118
|
+
: 0
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const nWindows = Math.max(0, Math.floor((trackDuration - windowSec) / hopSec) + 1);
|
|
122
|
+
const times = new Float32Array(nWindows);
|
|
123
|
+
const sim = new Float32Array(nWindows);
|
|
124
|
+
|
|
125
|
+
let skippedWindows = 0;
|
|
126
|
+
let scannedWindows = 0;
|
|
127
|
+
|
|
128
|
+
for (let w = 0; w < nWindows; w++) {
|
|
129
|
+
if (options.isCancelled?.()) {
|
|
130
|
+
throw new Error("@octoseq/mir: cancelled");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const t0 = w * hopSec;
|
|
134
|
+
const t1 = t0 + windowSec;
|
|
135
|
+
|
|
136
|
+
// Optionally skip windows overlapping the query itself.
|
|
137
|
+
if (options.skipWindowOverlap) {
|
|
138
|
+
const s0 = options.skipWindowOverlap.t0;
|
|
139
|
+
const s1 = options.skipWindowOverlap.t1;
|
|
140
|
+
const overlaps = t0 < s1 && t1 > s0;
|
|
141
|
+
if (overlaps) {
|
|
142
|
+
times[w] = t0;
|
|
143
|
+
sim[w] = 0;
|
|
144
|
+
skippedWindows++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
times[w] = t0;
|
|
150
|
+
|
|
151
|
+
// Compute window fingerprint using the same feature extraction logic.
|
|
152
|
+
// We avoid re-running spectrogram/mel computation; we only aggregate
|
|
153
|
+
// from existing mel/onset/mfcc time-aligned arrays.
|
|
154
|
+
const fp = fingerprintV1({
|
|
155
|
+
t0,
|
|
156
|
+
t1,
|
|
157
|
+
mel: params.mel,
|
|
158
|
+
onsetEnvelope: params.onsetEnvelope,
|
|
159
|
+
mfcc: params.mfcc,
|
|
160
|
+
peakPick: options.queryPeakPick,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const score = similarityFingerprintV1(queryFp, fp, options.weights);
|
|
164
|
+
sim[w] = clamp01(score);
|
|
165
|
+
scannedWindows++;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const scanMs = nowMs() - scanStartMs;
|
|
169
|
+
|
|
170
|
+
// --- candidate detection on similarity curve
|
|
171
|
+
// We use peakPick on (times, sim), with minIntervalSec enforcing spacing.
|
|
172
|
+
// We apply threshold as an absolute minimum peak height.
|
|
173
|
+
const events = peakPick(times, sim, {
|
|
174
|
+
threshold,
|
|
175
|
+
minIntervalSec: minSpacingSec,
|
|
176
|
+
strict: options.candidatePeakPick?.strict ?? true,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const candidates: MirSearchCandidate[] = events.map((e) => {
|
|
180
|
+
const windowStartSec = e.time;
|
|
181
|
+
const windowEndSec = windowStartSec + windowSec;
|
|
182
|
+
return {
|
|
183
|
+
timeSec: e.time,
|
|
184
|
+
score: e.strength,
|
|
185
|
+
windowStartSec,
|
|
186
|
+
windowEndSec,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const totalMs = nowMs() - tStart;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
times,
|
|
194
|
+
similarity: sim,
|
|
195
|
+
candidates,
|
|
196
|
+
meta: {
|
|
197
|
+
fingerprintMs,
|
|
198
|
+
scanMs,
|
|
199
|
+
totalMs,
|
|
200
|
+
windowSec,
|
|
201
|
+
hopSec,
|
|
202
|
+
skippedWindows,
|
|
203
|
+
scannedWindows,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|