@octoseq/mir 0.1.0-main.4ecb074 → 0.1.0-main.5fdb072
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-HF3QHCRK.js +3234 -0
- package/dist/chunk-HF3QHCRK.js.map +1 -0
- package/dist/index.d.ts +2122 -47
- package/dist/index.js +3524 -39
- package/dist/index.js.map +1 -1
- package/dist/runMir-CnJQbBr8.d.ts +187 -0
- package/dist/runner/runMir.d.ts +2 -2
- package/dist/runner/runMir.js +1 -1
- package/dist/runner/workerProtocol.d.ts +8 -1
- package/dist/runner/workerProtocol.js.map +1 -1
- package/dist/types-DqH4umN8.d.ts +761 -0
- package/package.json +2 -2
- package/src/dsp/activity.ts +544 -0
- package/src/dsp/bandCqt.ts +662 -0
- package/src/dsp/bandEvents.ts +351 -0
- package/src/dsp/bandMask.ts +225 -0
- package/src/dsp/bandMir.ts +524 -0
- package/src/dsp/bandProposal.ts +552 -0
- package/src/dsp/beatCandidates.ts +299 -0
- package/src/dsp/cqt.ts +386 -0
- package/src/dsp/cqtSignals.ts +462 -0
- package/src/dsp/customSignalReduction.ts +841 -0
- package/src/dsp/eventToSignal.ts +531 -0
- package/src/dsp/frequencyBand.ts +956 -0
- package/src/dsp/mel.ts +56 -3
- package/src/dsp/musicalTime.ts +240 -0
- package/src/dsp/onset.ts +296 -30
- package/src/dsp/peakPicking.ts +519 -0
- package/src/dsp/phaseAlignment.ts +153 -0
- package/src/dsp/pitch.ts +289 -0
- package/src/dsp/resample.ts +44 -0
- package/src/dsp/signalTransforms.ts +660 -0
- package/src/dsp/silenceGating.ts +511 -0
- package/src/dsp/spectral.ts +124 -4
- package/src/dsp/tempoHypotheses.ts +395 -0
- package/src/gpu/bufferPool.ts +266 -0
- package/src/gpu/context.ts +30 -6
- package/src/gpu/helpers.ts +85 -0
- package/src/gpu/melProject.ts +83 -0
- package/src/index.ts +366 -5
- package/src/runner/runMir.ts +234 -2
- package/src/runner/workerProtocol.ts +9 -1
- package/src/types.ts +768 -3
- package/dist/chunk-DUWYCAVG.js +0 -1525
- package/dist/chunk-DUWYCAVG.js.map +0 -1
- package/dist/runMir-CSIBwNZ3.d.ts +0 -84
- package/dist/types-BE3py4fZ.d.ts +0 -83
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Band-Scoped MIR utilities for F3.
|
|
3
|
+
*
|
|
4
|
+
* These functions compute MIR features (amplitude envelope, onset strength,
|
|
5
|
+
* spectral flux) for frequency bands by applying spectral masks to an
|
|
6
|
+
* existing spectrogram.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Spectrogram } from "./spectrogram";
|
|
10
|
+
import type {
|
|
11
|
+
FrequencyBand,
|
|
12
|
+
MirRunMeta,
|
|
13
|
+
MirRunTimings,
|
|
14
|
+
BandMirFunctionId,
|
|
15
|
+
BandMirDiagnostics,
|
|
16
|
+
BandMir1DResult,
|
|
17
|
+
} from "../types";
|
|
18
|
+
import {
|
|
19
|
+
applyBandMaskToSpectrogram,
|
|
20
|
+
computeFrameAmplitude,
|
|
21
|
+
type BandMaskOptions,
|
|
22
|
+
} from "./bandMask";
|
|
23
|
+
|
|
24
|
+
// Re-export types for convenience
|
|
25
|
+
export type { BandMirFunctionId, BandMirDiagnostics, BandMir1DResult };
|
|
26
|
+
|
|
27
|
+
export type BandMirOptions = {
|
|
28
|
+
/** Soft edge width in Hz for mask transitions. Default: 0 */
|
|
29
|
+
edgeSmoothHz?: number;
|
|
30
|
+
/** Options for onset strength computation */
|
|
31
|
+
onset?: {
|
|
32
|
+
/** If true, log-compress magnitudes before differencing. */
|
|
33
|
+
useLog?: boolean;
|
|
34
|
+
/** Moving-average smoothing window length in milliseconds. 0 disables smoothing. */
|
|
35
|
+
smoothMs?: number;
|
|
36
|
+
/** How to convert temporal differences into novelty. */
|
|
37
|
+
diffMethod?: "rectified" | "abs";
|
|
38
|
+
};
|
|
39
|
+
/** Optional cancellation hook */
|
|
40
|
+
isCancelled?: () => boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ----------------------------
|
|
44
|
+
// Internal Helpers
|
|
45
|
+
// ----------------------------
|
|
46
|
+
|
|
47
|
+
function computeDiagnostics(
|
|
48
|
+
energyRetainedPerFrame: Float32Array
|
|
49
|
+
): BandMirDiagnostics {
|
|
50
|
+
const totalFrames = energyRetainedPerFrame.length;
|
|
51
|
+
let sum = 0;
|
|
52
|
+
let weakCount = 0;
|
|
53
|
+
let emptyCount = 0;
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < totalFrames; i++) {
|
|
56
|
+
const e = energyRetainedPerFrame[i] ?? 0;
|
|
57
|
+
sum += e;
|
|
58
|
+
if (e < 0.01) weakCount++;
|
|
59
|
+
if (e === 0) emptyCount++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const meanEnergyRetained = totalFrames > 0 ? sum / totalFrames : 0;
|
|
63
|
+
const warnings: string[] = [];
|
|
64
|
+
|
|
65
|
+
if (meanEnergyRetained < 0.01) {
|
|
66
|
+
warnings.push("Band contains very little energy - check frequency range");
|
|
67
|
+
}
|
|
68
|
+
if (emptyCount > totalFrames * 0.5) {
|
|
69
|
+
warnings.push("More than half of frames are empty - band may not be active for this audio");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
meanEnergyRetained,
|
|
74
|
+
weakFrameCount: weakCount,
|
|
75
|
+
emptyFrameCount: emptyCount,
|
|
76
|
+
totalFrames,
|
|
77
|
+
warnings,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createMeta(startMs: number): MirRunMeta {
|
|
82
|
+
const endMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
83
|
+
const timings: MirRunTimings = {
|
|
84
|
+
totalMs: endMs - startMs,
|
|
85
|
+
cpuMs: endMs - startMs,
|
|
86
|
+
gpuMs: 0,
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
backend: "cpu",
|
|
90
|
+
usedGpu: false,
|
|
91
|
+
timings,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function movingAverage(values: Float32Array, windowFrames: number): Float32Array {
|
|
96
|
+
if (windowFrames <= 1) return values;
|
|
97
|
+
|
|
98
|
+
const n = values.length;
|
|
99
|
+
const out = new Float32Array(n);
|
|
100
|
+
|
|
101
|
+
// Centered window.
|
|
102
|
+
const half = Math.floor(windowFrames / 2);
|
|
103
|
+
|
|
104
|
+
// Prefix sums for stable, bug-free O(n) moving average.
|
|
105
|
+
const prefix = new Float64Array(n + 1);
|
|
106
|
+
prefix[0] = 0;
|
|
107
|
+
for (let i = 0; i < n; i++) {
|
|
108
|
+
prefix[i + 1] = (prefix[i] ?? 0) + (values[i] ?? 0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < n; i++) {
|
|
112
|
+
const start = Math.max(0, i - half);
|
|
113
|
+
const end = Math.min(n, i + half + 1);
|
|
114
|
+
const sum = (prefix[end] ?? 0) - (prefix[start] ?? 0);
|
|
115
|
+
const count = Math.max(1, end - start);
|
|
116
|
+
out[i] = sum / count;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function logCompress(x: number): number {
|
|
123
|
+
// Stable compression without -Inf.
|
|
124
|
+
return Math.log1p(Math.max(0, x));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ----------------------------
|
|
128
|
+
// Band MIR Functions
|
|
129
|
+
// ----------------------------
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Compute amplitude envelope for a frequency band.
|
|
133
|
+
*
|
|
134
|
+
* Returns the sum of magnitudes per frame within the band's frequency range.
|
|
135
|
+
*
|
|
136
|
+
* @param spec - Source spectrogram
|
|
137
|
+
* @param band - Frequency band to analyze
|
|
138
|
+
* @param options - Computation options
|
|
139
|
+
* @returns Band MIR result with amplitude envelope
|
|
140
|
+
*/
|
|
141
|
+
export function bandAmplitudeEnvelope(
|
|
142
|
+
spec: Spectrogram,
|
|
143
|
+
band: FrequencyBand,
|
|
144
|
+
options?: BandMirOptions
|
|
145
|
+
): BandMir1DResult {
|
|
146
|
+
const startMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
147
|
+
|
|
148
|
+
// Apply band mask
|
|
149
|
+
const masked = applyBandMaskToSpectrogram(spec, band, {
|
|
150
|
+
edgeSmoothHz: options?.edgeSmoothHz,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const nFrames = masked.times.length;
|
|
154
|
+
const values = new Float32Array(nFrames);
|
|
155
|
+
|
|
156
|
+
for (let t = 0; t < nFrames; t++) {
|
|
157
|
+
const mags = masked.magnitudes[t];
|
|
158
|
+
if (mags) {
|
|
159
|
+
values[t] = computeFrameAmplitude(mags);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
kind: "bandMir1d",
|
|
165
|
+
bandId: band.id,
|
|
166
|
+
bandLabel: band.label,
|
|
167
|
+
fn: "bandAmplitudeEnvelope",
|
|
168
|
+
times: masked.times,
|
|
169
|
+
values,
|
|
170
|
+
meta: createMeta(startMs),
|
|
171
|
+
diagnostics: computeDiagnostics(masked.energyRetainedPerFrame),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Compute onset strength for a frequency band.
|
|
177
|
+
*
|
|
178
|
+
* Uses temporal differences of band-masked magnitudes.
|
|
179
|
+
* Adapted from onset.ts:onsetEnvelopeFromSpectrogram.
|
|
180
|
+
*
|
|
181
|
+
* @param spec - Source spectrogram
|
|
182
|
+
* @param band - Frequency band to analyze
|
|
183
|
+
* @param options - Computation options
|
|
184
|
+
* @returns Band MIR result with onset strength
|
|
185
|
+
*/
|
|
186
|
+
export function bandOnsetStrength(
|
|
187
|
+
spec: Spectrogram,
|
|
188
|
+
band: FrequencyBand,
|
|
189
|
+
options?: BandMirOptions
|
|
190
|
+
): BandMir1DResult {
|
|
191
|
+
const startMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
192
|
+
|
|
193
|
+
// Default onset options
|
|
194
|
+
const useLog = options?.onset?.useLog ?? false;
|
|
195
|
+
const smoothMs = options?.onset?.smoothMs ?? 30;
|
|
196
|
+
const diffMethod = options?.onset?.diffMethod ?? "rectified";
|
|
197
|
+
|
|
198
|
+
// Apply band mask
|
|
199
|
+
const masked = applyBandMaskToSpectrogram(spec, band, {
|
|
200
|
+
edgeSmoothHz: options?.edgeSmoothHz,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const nFrames = masked.times.length;
|
|
204
|
+
const nBins = (masked.fftSize >>> 1) + 1;
|
|
205
|
+
const out = new Float32Array(nFrames);
|
|
206
|
+
|
|
207
|
+
// First frame has no previous frame
|
|
208
|
+
out[0] = 0;
|
|
209
|
+
|
|
210
|
+
for (let t = 1; t < nFrames; t++) {
|
|
211
|
+
const cur = masked.magnitudes[t];
|
|
212
|
+
const prev = masked.magnitudes[t - 1];
|
|
213
|
+
|
|
214
|
+
if (!cur || !prev) {
|
|
215
|
+
out[t] = 0;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let sum = 0;
|
|
220
|
+
let binsWithData = 0;
|
|
221
|
+
|
|
222
|
+
for (let k = 0; k < nBins; k++) {
|
|
223
|
+
let a = cur[k] ?? 0;
|
|
224
|
+
let b = prev[k] ?? 0;
|
|
225
|
+
|
|
226
|
+
// Only count bins that have some energy
|
|
227
|
+
if (a > 0 || b > 0) {
|
|
228
|
+
binsWithData++;
|
|
229
|
+
|
|
230
|
+
if (useLog) {
|
|
231
|
+
a = logCompress(a);
|
|
232
|
+
b = logCompress(b);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const d = a - b;
|
|
236
|
+
sum += diffMethod === "abs" ? Math.abs(d) : Math.max(0, d);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Normalize by number of active bins to avoid scale depending on band width
|
|
241
|
+
out[t] = binsWithData > 0 ? sum / binsWithData : 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Apply smoothing if requested
|
|
245
|
+
const smoothMs_ = smoothMs;
|
|
246
|
+
if (smoothMs_ > 0 && nFrames >= 2) {
|
|
247
|
+
const dt = (masked.times[1] ?? 0) - (masked.times[0] ?? 0);
|
|
248
|
+
const windowFrames = Math.max(1, Math.round((smoothMs_ / 1000) / Math.max(1e-9, dt)));
|
|
249
|
+
return {
|
|
250
|
+
kind: "bandMir1d",
|
|
251
|
+
bandId: band.id,
|
|
252
|
+
bandLabel: band.label,
|
|
253
|
+
fn: "bandOnsetStrength",
|
|
254
|
+
times: masked.times,
|
|
255
|
+
values: movingAverage(out, windowFrames | 1),
|
|
256
|
+
meta: createMeta(startMs),
|
|
257
|
+
diagnostics: computeDiagnostics(masked.energyRetainedPerFrame),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
kind: "bandMir1d",
|
|
263
|
+
bandId: band.id,
|
|
264
|
+
bandLabel: band.label,
|
|
265
|
+
fn: "bandOnsetStrength",
|
|
266
|
+
times: masked.times,
|
|
267
|
+
values: out,
|
|
268
|
+
meta: createMeta(startMs),
|
|
269
|
+
diagnostics: computeDiagnostics(masked.energyRetainedPerFrame),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Compute spectral flux for a frequency band.
|
|
275
|
+
*
|
|
276
|
+
* Uses L1 distance between consecutive normalized band-masked spectra.
|
|
277
|
+
* Adapted from spectral.ts:spectralFlux.
|
|
278
|
+
*
|
|
279
|
+
* @param spec - Source spectrogram
|
|
280
|
+
* @param band - Frequency band to analyze
|
|
281
|
+
* @param options - Computation options
|
|
282
|
+
* @returns Band MIR result with spectral flux
|
|
283
|
+
*/
|
|
284
|
+
export function bandSpectralFlux(
|
|
285
|
+
spec: Spectrogram,
|
|
286
|
+
band: FrequencyBand,
|
|
287
|
+
options?: BandMirOptions
|
|
288
|
+
): BandMir1DResult {
|
|
289
|
+
const startMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
290
|
+
|
|
291
|
+
// Apply band mask
|
|
292
|
+
const masked = applyBandMaskToSpectrogram(spec, band, {
|
|
293
|
+
edgeSmoothHz: options?.edgeSmoothHz,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const nFrames = masked.times.length;
|
|
297
|
+
const nBins = (masked.fftSize >>> 1) + 1;
|
|
298
|
+
const out = new Float32Array(nFrames);
|
|
299
|
+
|
|
300
|
+
let prev: Float32Array | null = null;
|
|
301
|
+
|
|
302
|
+
for (let t = 0; t < nFrames; t++) {
|
|
303
|
+
const mags = masked.magnitudes[t];
|
|
304
|
+
|
|
305
|
+
if (!mags) {
|
|
306
|
+
out[t] = 0;
|
|
307
|
+
prev = null;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Normalize to reduce sensitivity to overall level
|
|
312
|
+
let sum = 0;
|
|
313
|
+
for (let k = 0; k < nBins; k++) sum += mags[k] ?? 0;
|
|
314
|
+
|
|
315
|
+
if (sum <= 0) {
|
|
316
|
+
out[t] = 0;
|
|
317
|
+
prev = null;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const cur = new Float32Array(nBins);
|
|
322
|
+
const inv = 1 / sum;
|
|
323
|
+
for (let k = 0; k < nBins; k++) cur[k] = (mags[k] ?? 0) * inv;
|
|
324
|
+
|
|
325
|
+
if (!prev) {
|
|
326
|
+
out[t] = 0;
|
|
327
|
+
prev = cur;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let flux = 0;
|
|
332
|
+
for (let k = 0; k < nBins; k++) {
|
|
333
|
+
const d = (cur[k] ?? 0) - (prev[k] ?? 0);
|
|
334
|
+
flux += Math.abs(d);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
out[t] = flux;
|
|
338
|
+
prev = cur;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
kind: "bandMir1d",
|
|
343
|
+
bandId: band.id,
|
|
344
|
+
bandLabel: band.label,
|
|
345
|
+
fn: "bandSpectralFlux",
|
|
346
|
+
times: masked.times,
|
|
347
|
+
values: out,
|
|
348
|
+
meta: createMeta(startMs),
|
|
349
|
+
diagnostics: computeDiagnostics(masked.energyRetainedPerFrame),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Compute spectral centroid for a frequency band.
|
|
355
|
+
*
|
|
356
|
+
* Returns the weighted average of frequency bins within the band (center of mass).
|
|
357
|
+
* Output is in Hz per frame.
|
|
358
|
+
*
|
|
359
|
+
* @param spec - Source spectrogram
|
|
360
|
+
* @param band - Frequency band to analyze
|
|
361
|
+
* @param options - Computation options
|
|
362
|
+
* @returns Band MIR result with spectral centroid in Hz
|
|
363
|
+
*/
|
|
364
|
+
export function bandSpectralCentroid(
|
|
365
|
+
spec: Spectrogram,
|
|
366
|
+
band: FrequencyBand,
|
|
367
|
+
options?: BandMirOptions
|
|
368
|
+
): BandMir1DResult {
|
|
369
|
+
const startMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
370
|
+
|
|
371
|
+
// Apply band mask
|
|
372
|
+
const masked = applyBandMaskToSpectrogram(spec, band, {
|
|
373
|
+
edgeSmoothHz: options?.edgeSmoothHz,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const nFrames = masked.times.length;
|
|
377
|
+
const nBins = (masked.fftSize >>> 1) + 1;
|
|
378
|
+
const binHz = masked.sampleRate / masked.fftSize;
|
|
379
|
+
const out = new Float32Array(nFrames);
|
|
380
|
+
|
|
381
|
+
for (let t = 0; t < nFrames; t++) {
|
|
382
|
+
const mags = masked.magnitudes[t];
|
|
383
|
+
|
|
384
|
+
if (!mags) {
|
|
385
|
+
out[t] = 0;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let num = 0;
|
|
390
|
+
let den = 0;
|
|
391
|
+
|
|
392
|
+
// Weighted average: centroid = Σ(f * m) / Σ(m)
|
|
393
|
+
for (let k = 0; k < nBins; k++) {
|
|
394
|
+
const m = mags[k] ?? 0;
|
|
395
|
+
if (m > 0) {
|
|
396
|
+
const f = k * binHz;
|
|
397
|
+
num += f * m;
|
|
398
|
+
den += m;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
out[t] = den > 0 ? num / den : 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
kind: "bandMir1d",
|
|
407
|
+
bandId: band.id,
|
|
408
|
+
bandLabel: band.label,
|
|
409
|
+
fn: "bandSpectralCentroid",
|
|
410
|
+
times: masked.times,
|
|
411
|
+
values: out,
|
|
412
|
+
meta: createMeta(startMs),
|
|
413
|
+
diagnostics: computeDiagnostics(masked.energyRetainedPerFrame),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ----------------------------
|
|
418
|
+
// Batch Runner
|
|
419
|
+
// ----------------------------
|
|
420
|
+
|
|
421
|
+
export type BandMirBatchRequest = {
|
|
422
|
+
bands: FrequencyBand[];
|
|
423
|
+
functions: BandMirFunctionId[];
|
|
424
|
+
/** Maximum number of bands to process concurrently. Default: 4 */
|
|
425
|
+
maxConcurrent?: number;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
export type BandMirBatchResult = {
|
|
429
|
+
/** Results keyed by bandId, each containing results for requested functions */
|
|
430
|
+
results: Map<string, BandMir1DResult[]>;
|
|
431
|
+
/** Total computation time in ms */
|
|
432
|
+
totalTimingMs: number;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Run band MIR analysis for multiple bands.
|
|
437
|
+
*
|
|
438
|
+
* Processes bands sequentially (web workers don't have real parallelism
|
|
439
|
+
* within a single thread). The maxConcurrent option is reserved for
|
|
440
|
+
* future multi-worker support.
|
|
441
|
+
*
|
|
442
|
+
* @param spec - Source spectrogram
|
|
443
|
+
* @param request - Batch request specifying bands and functions
|
|
444
|
+
* @param options - Computation options
|
|
445
|
+
* @returns Map of results by band ID
|
|
446
|
+
*/
|
|
447
|
+
export async function runBandMirBatch(
|
|
448
|
+
spec: Spectrogram,
|
|
449
|
+
request: BandMirBatchRequest,
|
|
450
|
+
options?: BandMirOptions
|
|
451
|
+
): Promise<BandMirBatchResult> {
|
|
452
|
+
const startMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
453
|
+
|
|
454
|
+
const results = new Map<string, BandMir1DResult[]>();
|
|
455
|
+
|
|
456
|
+
for (const band of request.bands) {
|
|
457
|
+
if (options?.isCancelled?.()) {
|
|
458
|
+
throw new Error("@octoseq/mir: cancelled");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!band.enabled) continue;
|
|
462
|
+
|
|
463
|
+
const bandResults: BandMir1DResult[] = [];
|
|
464
|
+
|
|
465
|
+
for (const fn of request.functions) {
|
|
466
|
+
if (options?.isCancelled?.()) {
|
|
467
|
+
throw new Error("@octoseq/mir: cancelled");
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let result: BandMir1DResult;
|
|
471
|
+
|
|
472
|
+
switch (fn) {
|
|
473
|
+
case "bandAmplitudeEnvelope":
|
|
474
|
+
result = bandAmplitudeEnvelope(spec, band, options);
|
|
475
|
+
break;
|
|
476
|
+
case "bandOnsetStrength":
|
|
477
|
+
result = bandOnsetStrength(spec, band, options);
|
|
478
|
+
break;
|
|
479
|
+
case "bandSpectralFlux":
|
|
480
|
+
result = bandSpectralFlux(spec, band, options);
|
|
481
|
+
break;
|
|
482
|
+
case "bandSpectralCentroid":
|
|
483
|
+
result = bandSpectralCentroid(spec, band, options);
|
|
484
|
+
break;
|
|
485
|
+
default:
|
|
486
|
+
// Exhaustive check
|
|
487
|
+
const _exhaustive: never = fn;
|
|
488
|
+
throw new Error(`Unknown band MIR function: ${_exhaustive}`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
bandResults.push(result);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
results.set(band.id, bandResults);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const endMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
results,
|
|
501
|
+
totalTimingMs: endMs - startMs,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get a human-readable label for a band MIR function.
|
|
507
|
+
*
|
|
508
|
+
* @param fn - Band MIR function ID
|
|
509
|
+
* @returns Human-readable label
|
|
510
|
+
*/
|
|
511
|
+
export function getBandMirFunctionLabel(fn: BandMirFunctionId): string {
|
|
512
|
+
switch (fn) {
|
|
513
|
+
case "bandAmplitudeEnvelope":
|
|
514
|
+
return "Amplitude Envelope";
|
|
515
|
+
case "bandOnsetStrength":
|
|
516
|
+
return "Onset Strength";
|
|
517
|
+
case "bandSpectralFlux":
|
|
518
|
+
return "Spectral Flux";
|
|
519
|
+
case "bandSpectralCentroid":
|
|
520
|
+
return "Spectral Centroid";
|
|
521
|
+
default:
|
|
522
|
+
return fn;
|
|
523
|
+
}
|
|
524
|
+
}
|