@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.
Files changed (47) hide show
  1. package/dist/chunk-HF3QHCRK.js +3234 -0
  2. package/dist/chunk-HF3QHCRK.js.map +1 -0
  3. package/dist/index.d.ts +2122 -47
  4. package/dist/index.js +3524 -39
  5. package/dist/index.js.map +1 -1
  6. package/dist/runMir-CnJQbBr8.d.ts +187 -0
  7. package/dist/runner/runMir.d.ts +2 -2
  8. package/dist/runner/runMir.js +1 -1
  9. package/dist/runner/workerProtocol.d.ts +8 -1
  10. package/dist/runner/workerProtocol.js.map +1 -1
  11. package/dist/types-DqH4umN8.d.ts +761 -0
  12. package/package.json +2 -2
  13. package/src/dsp/activity.ts +544 -0
  14. package/src/dsp/bandCqt.ts +662 -0
  15. package/src/dsp/bandEvents.ts +351 -0
  16. package/src/dsp/bandMask.ts +225 -0
  17. package/src/dsp/bandMir.ts +524 -0
  18. package/src/dsp/bandProposal.ts +552 -0
  19. package/src/dsp/beatCandidates.ts +299 -0
  20. package/src/dsp/cqt.ts +386 -0
  21. package/src/dsp/cqtSignals.ts +462 -0
  22. package/src/dsp/customSignalReduction.ts +841 -0
  23. package/src/dsp/eventToSignal.ts +531 -0
  24. package/src/dsp/frequencyBand.ts +956 -0
  25. package/src/dsp/mel.ts +56 -3
  26. package/src/dsp/musicalTime.ts +240 -0
  27. package/src/dsp/onset.ts +296 -30
  28. package/src/dsp/peakPicking.ts +519 -0
  29. package/src/dsp/phaseAlignment.ts +153 -0
  30. package/src/dsp/pitch.ts +289 -0
  31. package/src/dsp/resample.ts +44 -0
  32. package/src/dsp/signalTransforms.ts +660 -0
  33. package/src/dsp/silenceGating.ts +511 -0
  34. package/src/dsp/spectral.ts +124 -4
  35. package/src/dsp/tempoHypotheses.ts +395 -0
  36. package/src/gpu/bufferPool.ts +266 -0
  37. package/src/gpu/context.ts +30 -6
  38. package/src/gpu/helpers.ts +85 -0
  39. package/src/gpu/melProject.ts +83 -0
  40. package/src/index.ts +366 -5
  41. package/src/runner/runMir.ts +234 -2
  42. package/src/runner/workerProtocol.ts +9 -1
  43. package/src/types.ts +768 -3
  44. package/dist/chunk-DUWYCAVG.js +0 -1525
  45. package/dist/chunk-DUWYCAVG.js.map +0 -1
  46. package/dist/runMir-CSIBwNZ3.d.ts +0 -84
  47. package/dist/types-BE3py4fZ.d.ts +0 -83
package/src/types.ts CHANGED
@@ -39,21 +39,590 @@ export type MirEventsResult = {
39
39
  meta: MirRunMeta;
40
40
  };
41
41
 
42
- export type MirResult = Mir1DResult | Mir2DResult | MirEventsResult;
42
+ /**
43
+ * A beat candidate represents a plausible beat-like moment in the audio.
44
+ *
45
+ * These are sparse events (timestamps) that may or may not correspond to
46
+ * actual beats. They are not tempo-aligned and do not imply any BPM value.
47
+ *
48
+ * Beat candidates are intended to be:
49
+ * - Dense enough to include most true beats
50
+ * - Sparse enough to be computationally tractable
51
+ * - Inspectable in the UI for debugging
52
+ *
53
+ * Future milestones will cluster, align, and refine these candidates.
54
+ */
55
+ export type BeatCandidate = {
56
+ /** Time in seconds from track start. */
57
+ time: number;
58
+ /** Relative salience/confidence (0-1 normalized). Higher = more likely to be a beat. */
59
+ strength: number;
60
+ /** Source of this candidate (for debugging/inspection). */
61
+ source: BeatCandidateSource;
62
+ };
63
+
64
+ export type BeatCandidateSource =
65
+ | "onset_peak" // Derived from onset envelope peaks
66
+ | "flux_peak" // Derived from spectral flux peaks
67
+ | "combined"; // Derived from combined salience signal
68
+
69
+ export type BeatCandidatesResult = {
70
+ kind: "beatCandidates";
71
+ /** Frame times from the underlying analysis (for alignment). */
72
+ times: Float32Array;
73
+ /** The beat candidate events. */
74
+ candidates: BeatCandidate[];
75
+ /** Optional: the salience signal used for peak picking (for debugging). */
76
+ salience?: {
77
+ times: Float32Array;
78
+ values: Float32Array;
79
+ };
80
+ meta: MirRunMeta;
81
+ };
82
+
83
+ /**
84
+ * A tempo hypothesis represents a plausible BPM with confidence score.
85
+ *
86
+ * Hypotheses are derived from inter-onset intervals of beat candidates.
87
+ * They are grouped into harmonic families (e.g., 60, 120, 180 BPM) but
88
+ * not collapsed - each BPM is preserved as a separate hypothesis.
89
+ */
90
+ export type TempoHypothesis = {
91
+ /** Deterministic identifier for this hypothesis (e.g., "hyp-0"). */
92
+ id: string;
93
+ /** Tempo in beats per minute (0.1 BPM precision). */
94
+ bpm: number;
95
+ /** Confidence score normalized to [0, 1]. Higher = more likely. */
96
+ confidence: number;
97
+ /** Evidence metadata for debugging/inspection. */
98
+ evidence: TempoHypothesisEvidence;
99
+ /** Harmonic family ID - hypotheses in the same family are harmonically related. */
100
+ familyId: string;
101
+ /** Harmonic relationship to the family root (1.0 = root, 2.0 = double, 0.5 = half, etc.). */
102
+ harmonicRatio: number;
103
+ };
104
+
105
+ export type TempoHypothesisEvidence = {
106
+ /** Number of IOIs supporting this tempo. */
107
+ supportingIntervalCount: number;
108
+ /** Sum of weighted contributions (if strength-weighting enabled). */
109
+ weightedSupport: number;
110
+ /** Peak height in the histogram. */
111
+ peakHeight: number;
112
+ /** Histogram bin range [minBpm, maxBpm]. */
113
+ binRange: [number, number];
114
+ };
115
+
116
+ export type TempoHypothesesResult = {
117
+ kind: "tempoHypotheses";
118
+ /** Frame times from underlying analysis (for alignment). */
119
+ times: Float32Array;
120
+ /** Ordered list of tempo hypotheses (by confidence descending). */
121
+ hypotheses: TempoHypothesis[];
122
+ /** The number of beat candidates used as input. */
123
+ inputCandidateCount: number;
124
+ /** Histogram data for debugging/visualization. */
125
+ histogram?: {
126
+ bpmBins: Float32Array;
127
+ counts: Float32Array;
128
+ };
129
+ meta: MirRunMeta;
130
+ };
131
+
132
+ /**
133
+ * Diagnostics for activity signal computation.
134
+ * Provides information about the signal detection quality.
135
+ */
136
+ export type MirActivityDiagnostics = {
137
+ /** Estimated noise floor value (in log-energy scale). */
138
+ noiseFloor: number;
139
+ /** Enter threshold value (in log-energy scale). */
140
+ enterThreshold: number;
141
+ /** Exit threshold value (in log-energy scale). */
142
+ exitThreshold: number;
143
+ /** Total frames processed. */
144
+ totalFrames: number;
145
+ /** Number of frames marked as active. */
146
+ activeFrames: number;
147
+ /** Fraction of frames that are active (0-1). */
148
+ activeFraction: number;
149
+ };
150
+
151
+ /**
152
+ * Result of activity detection.
153
+ *
154
+ * Activity represents "signal present" vs "silence/noise floor".
155
+ * Use activityLevel for soft gating (0-1) or isActive for hard gating (boolean).
156
+ */
157
+ export type MirActivityResult = {
158
+ kind: "activity";
159
+ /** Frame times in seconds. */
160
+ times: Float32Array;
161
+ /** Activity level per frame (0-1), suitable for soft gating. */
162
+ activityLevel: Float32Array;
163
+ /** Binary activity flag per frame (0 or 1), suitable for hard gating. */
164
+ isActive: Uint8Array;
165
+ /** Suppression mask (1 = in post-silence suppression window). */
166
+ suppressMask: Uint8Array;
167
+ /** Execution metadata. */
168
+ meta: MirRunMeta;
169
+ /** Diagnostics about detection quality. */
170
+ diagnostics: MirActivityDiagnostics;
171
+ };
172
+
173
+ export type MirResult = Mir1DResult | Mir2DResult | MirEventsResult | BeatCandidatesResult | TempoHypothesesResult | MirActivityResult;
174
+
175
+ // ----------------------------
176
+ // Beat Grid Phase Alignment (B3)
177
+ // ----------------------------
178
+
179
+ /**
180
+ * A beat grid represents phase-aligned beats for a given BPM.
181
+ *
182
+ * Given a BPM and phase offset, the beat times are:
183
+ * beat[n] = phaseOffset + userNudge + n * (60 / bpm)
184
+ *
185
+ * Beat grids are hypotheses that can be confirmed or adjusted by the user.
186
+ */
187
+ export type BeatGrid = {
188
+ /** Unique identifier for this grid (derived from source hypothesis + phase). */
189
+ id: string;
190
+ /** BPM of the grid (from the source tempo hypothesis). */
191
+ bpm: number;
192
+ /** Phase offset in seconds from track start. First beat occurs at this time. */
193
+ phaseOffset: number;
194
+ /** Confidence score [0, 1] for this phase alignment. */
195
+ confidence: number;
196
+ /** The tempo hypothesis this grid was derived from. */
197
+ sourceHypothesisId: string;
198
+ /** Whether the user has locked this grid (prevents auto-updates). */
199
+ isLocked: boolean;
200
+ /** User adjustment to phase offset in seconds (additive). */
201
+ userNudge: number;
202
+ };
203
+
204
+ /**
205
+ * A phase hypothesis represents a candidate phase offset for a given BPM.
206
+ * Used during phase alignment scoring.
207
+ */
208
+ export type PhaseHypothesis = {
209
+ /** Index within the phase candidate list (0 to N-1). */
210
+ index: number;
211
+ /** Phase offset in seconds. */
212
+ phaseOffset: number;
213
+ /** Alignment score (sum of weighted matches, normalized). */
214
+ score: number;
215
+ /** Number of beat candidates matched within tolerance. */
216
+ matchCount: number;
217
+ /** Average offset error of matched candidates in seconds. */
218
+ avgOffsetError: number;
219
+ };
220
+
221
+ /**
222
+ * Configuration for beat grid phase alignment.
223
+ */
224
+ export type PhaseAlignmentConfig = {
225
+ /** Number of phase candidates to generate per beat period. Default: 16. */
226
+ phaseResolution?: number;
227
+ /** Tolerance in seconds for matching candidates to grid lines. Default: 0.05 (50ms). */
228
+ matchTolerance?: number;
229
+ /** Number of top phase hypotheses to keep. Default: 3. */
230
+ topK?: number;
231
+ /** Weight for systematic offset penalty (0-1). Default: 0.2. */
232
+ offsetPenaltyWeight?: number;
233
+ };
234
+
235
+ // ----------------------------
236
+ // Musical Time (B4)
237
+ // ----------------------------
238
+
239
+ /**
240
+ * Provenance metadata for a musical time segment.
241
+ * Tracks how the segment was created for audit and debugging.
242
+ */
243
+ export type MusicalTimeProvenance = {
244
+ /** How this segment was created. */
245
+ source: "promoted_from_hypothesis" | "manual_entry" | "imported";
246
+ /** Reference to the original TempoHypothesis (if promoted). */
247
+ sourceHypothesisId?: string;
248
+ /** ISO timestamp when the segment was created/promoted. */
249
+ promotedAt: string;
250
+ /** User nudge value preserved from promotion (for provenance). */
251
+ userNudge?: number;
252
+ };
253
+
254
+ /**
255
+ * A single segment of musical time with explicit boundaries.
256
+ *
257
+ * Beat times within a segment are derivable, not stored:
258
+ * beat[n] = phaseOffset + n * (60 / bpm)
259
+ *
260
+ * Segments are explicit, authored, and stable once committed.
261
+ * They never change unless explicitly edited or unlocked by the user.
262
+ */
263
+ export type MusicalTimeSegment = {
264
+ /** Unique identifier for this segment. */
265
+ id: string;
266
+ /** Tempo in beats per minute. */
267
+ bpm: number;
268
+ /** Phase offset in seconds - first beat time relative to segment start. */
269
+ phaseOffset: number;
270
+ /** Segment start boundary in seconds (inclusive). */
271
+ startTime: number;
272
+ /** Segment end boundary in seconds (exclusive). */
273
+ endTime: number;
274
+ /** Confidence score frozen at lock time (optional, for display). */
275
+ confidence?: number;
276
+ /** Provenance metadata. */
277
+ provenance: MusicalTimeProvenance;
278
+ };
279
+
280
+ /**
281
+ * The authoritative musical time structure for a track.
282
+ *
283
+ * Once authored, this becomes the source of truth for musical time.
284
+ * Rendering, scripts, and offline execution rely on this structure.
285
+ *
286
+ * Design constraints:
287
+ * - Musical time is explicitly authored, not inferred silently
288
+ * - Segments are non-overlapping and ordered by startTime
289
+ * - Beat times are derivable from segment properties
290
+ * - Gaps between segments are intentional (no musical time defined)
291
+ */
292
+ export type MusicalTimeStructure = {
293
+ /** Schema version for future migrations. */
294
+ version: 1;
295
+ /** Ordered list of musical time segments (by startTime ascending). */
296
+ segments: MusicalTimeSegment[];
297
+ /** ISO timestamp when the structure was created. */
298
+ createdAt: string;
299
+ /** ISO timestamp when the structure was last modified. */
300
+ modifiedAt: string;
301
+ };
302
+
303
+ /**
304
+ * Computed beat position at a given time.
305
+ *
306
+ * beat_position = beat_index + beat_phase
307
+ *
308
+ * This is a read-only computed value, not stored.
309
+ */
310
+ export type BeatPosition = {
311
+ /** The segment this position is within. */
312
+ segmentId: string;
313
+ /** Integer beat number from segment start (0, 1, 2, ...). */
314
+ beatIndex: number;
315
+ /** Phase within the current beat (0-1, exclusive). */
316
+ beatPhase: number;
317
+ /** Continuous beat position (beatIndex + beatPhase). */
318
+ beatPosition: number;
319
+ /** BPM of the containing segment. */
320
+ bpm: number;
321
+ };
322
+
323
+ // ----------------------------
324
+ // Frequency Bands (F1)
325
+ // ----------------------------
326
+
327
+ /**
328
+ * Time scope for a frequency band.
329
+ * - "global": Band applies to entire track
330
+ * - "sectioned": Band applies only to explicit start/end times
331
+ */
332
+ export type FrequencyBandTimeScope =
333
+ | { kind: "global" }
334
+ | { kind: "sectioned"; startTime: number; endTime: number };
335
+
336
+ /**
337
+ * A single time segment of a piecewise-linear frequency range.
338
+ *
339
+ * Segments define how the band's frequency boundaries vary over time.
340
+ * Between segment boundaries, linear interpolation is used.
341
+ *
342
+ * Invariants:
343
+ * - lowHzStart < highHzStart and lowHzEnd < highHzEnd
344
+ * - All frequency values >= 0
345
+ * - startTime < endTime
346
+ * - Segments don't overlap in time (within a band)
347
+ * - For sectioned bands, segments must fully cover the time scope
348
+ */
349
+ export type FrequencySegment = {
350
+ /** Start time of this segment in seconds (inclusive). */
351
+ startTime: number;
352
+ /** End time of this segment in seconds (exclusive). */
353
+ endTime: number;
354
+ /** Lower frequency bound in Hz at segment start. */
355
+ lowHzStart: number;
356
+ /** Upper frequency bound in Hz at segment start. */
357
+ highHzStart: number;
358
+ /** Lower frequency bound in Hz at segment end. */
359
+ lowHzEnd: number;
360
+ /** Upper frequency bound in Hz at segment end. */
361
+ highHzEnd: number;
362
+ };
363
+
364
+ /**
365
+ * Provenance metadata for a frequency band.
366
+ * Tracks how the band was created for audit and debugging.
367
+ */
368
+ export type FrequencyBandProvenance = {
369
+ /** How this band was created. */
370
+ source: "manual" | "imported" | "preset";
371
+ /** ISO timestamp when the band was created. */
372
+ createdAt: string;
373
+ /** Optional preset name if source is "preset". */
374
+ presetName?: string;
375
+ };
376
+
377
+ /**
378
+ * A frequency band definition.
379
+ *
380
+ * Bands define semantic frequency regions for band-isolated analysis
381
+ * (e.g., bass, mids, highs, or "kick-like", "snare-like").
382
+ *
383
+ * Bands can have constant or time-varying frequency boundaries via
384
+ * the piecewise-linear frequencyShape.
385
+ *
386
+ * Bands are immutable unless explicitly edited by the user.
387
+ */
388
+ export type FrequencyBand = {
389
+ /** Unique identifier for this band. */
390
+ id: string;
391
+ /** Human-readable label (editable). */
392
+ label: string;
393
+ /** The audio source this band belongs to ("mixdown" or stem ID). */
394
+ sourceId: string;
395
+ /** Whether the band is currently active for processing. */
396
+ enabled: boolean;
397
+ /** Time scope for this band. */
398
+ timeScope: FrequencyBandTimeScope;
399
+ /** Piecewise-linear frequency shape over time. */
400
+ frequencyShape: FrequencySegment[];
401
+ /** Stable sort order (not insertion order). */
402
+ sortOrder: number;
403
+ /** Provenance metadata. */
404
+ provenance: FrequencyBandProvenance;
405
+ };
406
+
407
+ /**
408
+ * The authoritative frequency band structure for a track.
409
+ *
410
+ * Once authored, this becomes the source of truth for frequency bands.
411
+ * Band-scoped MIR passes will rely on this structure.
412
+ *
413
+ * Design constraints:
414
+ * - Bands are explicitly authored, not inferred
415
+ * - Bands can overlap in frequency (intentional for semantic regions)
416
+ * - Each band's frequencyShape segments are non-overlapping and ordered
417
+ * - Bands are sorted by sortOrder for stable ordering
418
+ */
419
+ export type FrequencyBandStructure = {
420
+ /** Schema version for future migrations. */
421
+ version: 2;
422
+ /** Ordered list of frequency bands (by sortOrder). */
423
+ bands: FrequencyBand[];
424
+ /** ISO timestamp when the structure was created. */
425
+ createdAt: string;
426
+ /** ISO timestamp when the structure was last modified. */
427
+ modifiedAt: string;
428
+ };
429
+
430
+ /**
431
+ * Computed frequency bounds at a given time.
432
+ * Result of querying a band at a specific time point.
433
+ */
434
+ export type FrequencyBoundsAtTime = {
435
+ /** The band ID this belongs to. */
436
+ bandId: string;
437
+ /** Lower frequency in Hz at this time. */
438
+ lowHz: number;
439
+ /** Upper frequency in Hz at this time. */
440
+ highHz: number;
441
+ /** Whether the band is enabled. */
442
+ enabled: boolean;
443
+ };
444
+
445
+ /**
446
+ * A keyframe for UI display and editing.
447
+ *
448
+ * Keyframes are a UI abstraction over the segment model.
449
+ * Each segment boundary (start or end) can be represented as a keyframe.
450
+ * This makes it easier to display and edit time-varying frequency bands.
451
+ */
452
+ export type FrequencyKeyframe = {
453
+ /** Time in seconds. */
454
+ time: number;
455
+ /** Lower frequency bound in Hz at this time. */
456
+ lowHz: number;
457
+ /** Upper frequency bound in Hz at this time. */
458
+ highHz: number;
459
+ /** Index of the segment this keyframe belongs to. */
460
+ segmentIndex: number;
461
+ /** Whether this is the start or end of the segment. */
462
+ edge: "start" | "end";
463
+ };
464
+
465
+ // ----------------------------
466
+ // Band-Scoped MIR (F3)
467
+ // ----------------------------
468
+
469
+ /**
470
+ * Band MIR function identifiers (STFT-based).
471
+ */
472
+ export type BandMirFunctionId =
473
+ | "bandAmplitudeEnvelope"
474
+ | "bandOnsetStrength"
475
+ | "bandSpectralFlux"
476
+ | "bandSpectralCentroid";
43
477
 
44
- // (moved above)
478
+ /**
479
+ * Band CQT function identifiers (CQT-based).
480
+ */
481
+ export type BandCqtFunctionId =
482
+ | "bandCqtHarmonicEnergy"
483
+ | "bandCqtBassPitchMotion"
484
+ | "bandCqtTonalStability";
485
+
486
+ /**
487
+ * Band event function identifiers (derived from 1D signals).
488
+ */
489
+ export type BandEventFunctionId =
490
+ | "bandOnsetPeaks"
491
+ | "bandBeatCandidates";
492
+
493
+ /**
494
+ * Diagnostics for band MIR computation.
495
+ * Provides information about energy retention and potential issues.
496
+ */
497
+ export type BandMirDiagnostics = {
498
+ /** Average energy retention across all frames (0-1). */
499
+ meanEnergyRetained: number;
500
+ /** Number of frames with < 1% energy (weak band). */
501
+ weakFrameCount: number;
502
+ /** Number of frames with 0 energy (empty band). */
503
+ emptyFrameCount: number;
504
+ /** Total frames processed. */
505
+ totalFrames: number;
506
+ /** Warning messages (informational, not blocking). */
507
+ warnings: string[];
508
+ };
509
+
510
+ /**
511
+ * Result of a band-scoped MIR computation (STFT-based).
512
+ */
513
+ export type BandMir1DResult = {
514
+ kind: "bandMir1d";
515
+ /** ID of the band this result is for. */
516
+ bandId: string;
517
+ /** Label of the band (for display). */
518
+ bandLabel: string;
519
+ /** The MIR function that produced this result. */
520
+ fn: BandMirFunctionId;
521
+ /** Frame times aligned to spectrogram timebase. */
522
+ times: Float32Array;
523
+ /** Signal values per frame. */
524
+ values: Float32Array;
525
+ /** Execution metadata. */
526
+ meta: MirRunMeta;
527
+ /** Diagnostics about energy retention and potential issues. */
528
+ diagnostics: BandMirDiagnostics;
529
+ };
530
+
531
+ /**
532
+ * Result of a band-scoped CQT computation.
533
+ */
534
+ export type BandCqt1DResult = {
535
+ kind: "bandCqt1d";
536
+ /** ID of the band this result is for. */
537
+ bandId: string;
538
+ /** Label of the band (for display). */
539
+ bandLabel: string;
540
+ /** The CQT function that produced this result. */
541
+ fn: BandCqtFunctionId;
542
+ /** Frame times aligned to CQT timebase. */
543
+ times: Float32Array;
544
+ /** Signal values per frame. */
545
+ values: Float32Array;
546
+ /** Execution metadata. */
547
+ meta: MirRunMeta;
548
+ /** Diagnostics about energy retention and potential issues. */
549
+ diagnostics: BandMirDiagnostics;
550
+ };
551
+
552
+ /**
553
+ * A band event (onset peak or beat candidate within a band).
554
+ */
555
+ export type BandMirEvent = {
556
+ /** Time in seconds. */
557
+ time: number;
558
+ /** Relative weight/strength (0-1 normalized). */
559
+ weight: number;
560
+ /** Optional beat position if beat grid exists. */
561
+ beatPosition?: number;
562
+ /** Optional beat phase if beat grid exists. */
563
+ beatPhase?: number;
564
+ };
565
+
566
+ /**
567
+ * Diagnostics for band event extraction.
568
+ */
569
+ export type BandEventDiagnostics = {
570
+ /** Total number of events extracted. */
571
+ eventCount: number;
572
+ /** Events per second (density). */
573
+ eventsPerSecond: number;
574
+ /** Warning messages (e.g., too sparse or too dense). */
575
+ warnings: string[];
576
+ };
577
+
578
+ /**
579
+ * Result of band event extraction.
580
+ */
581
+ export type BandEventsResult = {
582
+ kind: "bandEvents";
583
+ /** ID of the band this result is for. */
584
+ bandId: string;
585
+ /** Label of the band (for display). */
586
+ bandLabel: string;
587
+ /** The event function that produced this result. */
588
+ fn: BandEventFunctionId;
589
+ /** Extracted events. */
590
+ events: BandMirEvent[];
591
+ /** Source signal used for extraction (for debugging). */
592
+ sourceSignal?: {
593
+ fn: BandMirFunctionId;
594
+ times: Float32Array;
595
+ values: Float32Array;
596
+ };
597
+ /** Execution metadata. */
598
+ meta: MirRunMeta;
599
+ /** Diagnostics about extraction quality. */
600
+ diagnostics: BandEventDiagnostics;
601
+ };
45
602
 
46
603
  export type MirFunctionId =
604
+ | "amplitudeEnvelope"
47
605
  | "spectralCentroid"
48
606
  | "spectralFlux"
49
607
  | "melSpectrogram"
50
608
  | "onsetEnvelope"
51
609
  | "onsetPeaks"
610
+ | "beatCandidates"
611
+ | "tempoHypotheses"
52
612
  | "hpssHarmonic"
53
613
  | "hpssPercussive"
54
614
  | "mfcc"
55
615
  | "mfccDelta"
56
- | "mfccDeltaDelta";
616
+ | "mfccDeltaDelta"
617
+ // CQT-derived signals (F5)
618
+ | "cqtHarmonicEnergy"
619
+ | "cqtBassPitchMotion"
620
+ | "cqtTonalStability"
621
+ // Pitch detection (P1)
622
+ | "pitchF0"
623
+ | "pitchConfidence"
624
+ // Activity detection
625
+ | "activity";
57
626
 
58
627
  export type MirRunRequest = {
59
628
  fn: MirFunctionId;
@@ -97,9 +666,205 @@ export type MirRunRequest = {
97
666
  window: "hann";
98
667
  };
99
668
  };
669
+ beatCandidates?: {
670
+ /** Minimum inter-candidate interval in seconds. Default: 0.1 (100ms). */
671
+ minIntervalSec?: number;
672
+ /** Threshold factor for peak detection. Lower = more candidates. Default: 0.5. */
673
+ thresholdFactor?: number;
674
+ /** Smoothing window for salience signal in ms. Default: 50. */
675
+ smoothMs?: number;
676
+ /** Whether to include the salience signal in output (for debugging). */
677
+ includeSalience?: boolean;
678
+ };
679
+ tempoHypotheses?: {
680
+ /** Minimum BPM to consider. Default: 24. */
681
+ minBpm?: number;
682
+ /** Maximum BPM to consider. Default: 300. */
683
+ maxBpm?: number;
684
+ /** Histogram bin size in BPM. Default: 1.0. */
685
+ binSizeBpm?: number;
686
+ /** Maximum number of hypotheses to return. Default: 10. */
687
+ maxHypotheses?: number;
688
+ /** Minimum confidence threshold (0-1). Default: 0.05. */
689
+ minConfidence?: number;
690
+ /** Weight IOIs by beat candidate strength. Default: true. */
691
+ weightByStrength?: boolean;
692
+ /** Include histogram in output for debugging. Default: false. */
693
+ includeHistogram?: boolean;
694
+ };
695
+
696
+ // CQT configuration (F5)
697
+ cqt?: {
698
+ /** Number of bins per octave. Default: 24 (quarter-tone). */
699
+ binsPerOctave?: number;
700
+ /** Minimum frequency in Hz. Default: 32.7 Hz (C1). */
701
+ fMin?: number;
702
+ /** Maximum frequency in Hz. Default: 8372 Hz (C9). */
703
+ fMax?: number;
704
+ /** Hop size in samples. Auto-computed if not specified. */
705
+ hopSize?: number;
706
+ };
707
+
708
+ // Pitch detection configuration (P1)
709
+ pitch?: {
710
+ /** Minimum frequency in Hz. Default: 50. */
711
+ fMinHz?: number;
712
+ /** Maximum frequency in Hz. Default: 1000. */
713
+ fMaxHz?: number;
714
+ /** YIN threshold for voiced detection (0-1). Default: 0.15. */
715
+ threshold?: number;
716
+ };
717
+
718
+ // Activity detection configuration
719
+ activity?: {
720
+ /** Percentile for noise floor estimation (0-100). Default: 10. */
721
+ energyPercentile?: number;
722
+ /** dB margin above noise floor to enter active state. Default: 6. */
723
+ enterMargin?: number;
724
+ /** dB margin above noise floor to exit active state. Default: 3. */
725
+ exitMargin?: number;
726
+ /** Hangover duration in ms to prevent rapid toggling. Default: 50. */
727
+ hangoverMs?: number;
728
+ /** Minimum active duration in ms. Default: 30. */
729
+ minActiveMs?: number;
730
+ /** Smoothing window in ms for activity level. Default: 20. */
731
+ smoothMs?: number;
732
+ };
100
733
  };
101
734
 
102
735
  export type MirAudioPayload = {
103
736
  sampleRate: number;
104
737
  mono: Float32Array;
105
738
  };
739
+
740
+ // ----------------------------
741
+ // Constant-Q Transform (F5)
742
+ // ----------------------------
743
+
744
+ /**
745
+ * Configuration for CQT (Constant-Q Transform) computation.
746
+ *
747
+ * CQT provides log-frequency resolution aligned to musical pitch ratios.
748
+ * It is an internal spectral view, not a user-facing spectrogram.
749
+ */
750
+ export type CqtConfig = {
751
+ /** Number of bins per octave. Default: 24 (quarter-tone resolution). */
752
+ binsPerOctave: number;
753
+ /** Minimum frequency in Hz. Default: 32.7 Hz (C1). */
754
+ fMin: number;
755
+ /** Maximum frequency in Hz. Default: 8372 Hz (C9). */
756
+ fMax: number;
757
+ /** Hop size in samples. If not specified, auto-computed based on resolution. */
758
+ hopSize?: number;
759
+ };
760
+
761
+ /**
762
+ * Result of CQT computation.
763
+ *
764
+ * CQT produces a time-frequency representation with logarithmic frequency spacing.
765
+ * Each bin corresponds to a specific frequency based on the musical pitch scale.
766
+ */
767
+ export type CqtSpectrogram = {
768
+ /** Sample rate of source audio. */
769
+ sampleRate: number;
770
+ /** Configuration used for this computation. */
771
+ config: CqtConfig;
772
+ /** Time axis (frame centers in seconds). */
773
+ times: Float32Array;
774
+ /** CQT magnitudes [frame][bin], log-frequency ordered from fMin to fMax. */
775
+ magnitudes: Float32Array[];
776
+ /** Number of octaves covered. */
777
+ nOctaves: number;
778
+ /** Number of bins per octave. */
779
+ binsPerOctave: number;
780
+ /** Center frequency of each bin in Hz. */
781
+ binFrequencies: Float32Array;
782
+ };
783
+
784
+ /**
785
+ * Identifier for CQT-derived 1D signals.
786
+ */
787
+ export type CqtSignalId = "harmonicEnergy" | "bassPitchMotion" | "tonalStability";
788
+
789
+ /**
790
+ * Result of a CQT-derived 1D signal computation.
791
+ */
792
+ export type CqtSignalResult = {
793
+ kind: "cqt1d";
794
+ signalId: CqtSignalId;
795
+ times: Float32Array;
796
+ values: Float32Array;
797
+ meta: MirRunMeta;
798
+ };
799
+
800
+ // ----------------------------
801
+ // Band Proposals (F5)
802
+ // ----------------------------
803
+
804
+ /**
805
+ * Source algorithm that generated a band proposal.
806
+ */
807
+ export type BandProposalSource =
808
+ /** Persistent spectral peak detected across time. */
809
+ | "spectral_peak"
810
+ /** Frequency region with distinct onset pattern. */
811
+ | "onset_band"
812
+ /** Concentrated energy cluster in frequency region. */
813
+ | "energy_cluster"
814
+ /** Detected harmonic series structure. */
815
+ | "harmonic_structure"
816
+ /** High harmonic energy from CQT analysis. */
817
+ | "cqt_harmonic"
818
+ /** Significant bass pitch motion from CQT analysis. */
819
+ | "cqt_bass_motion"
820
+ /** Low tonal stability region from CQT analysis. */
821
+ | "cqt_tonal_instability";
822
+
823
+ /**
824
+ * A band proposal is an ephemeral suggestion for a frequency band.
825
+ *
826
+ * Proposals are generated by automated analysis and presented to the user
827
+ * for review. They are never auto-persisted - explicit user action
828
+ * (promotion) is required to convert them to real FrequencyBands.
829
+ *
830
+ * Design principle: Automation is advisory, not authoritative.
831
+ */
832
+ export type BandProposal = {
833
+ /** Unique ephemeral identifier for this proposal. */
834
+ id: string;
835
+ /** Proposed frequency band (same structure as FrequencyBand). */
836
+ band: FrequencyBand;
837
+ /** Salience score [0, 1] indicating how "interesting" this band is. */
838
+ salience: number;
839
+ /** Human-readable reason for this proposal. */
840
+ reason: string;
841
+ /** Algorithm that generated this proposal. */
842
+ source: BandProposalSource;
843
+ /** ISO timestamp when this proposal was generated. */
844
+ generatedAt: string;
845
+ };
846
+
847
+ /**
848
+ * Configuration for band proposal generation.
849
+ */
850
+ export type BandProposalConfig = {
851
+ /** Maximum number of proposals to generate. Default: 8. */
852
+ maxProposals?: number;
853
+ /** Minimum salience threshold for proposals [0, 1]. Default: 0.3. */
854
+ minSalience?: number;
855
+ /** Minimum separation in octaves between proposals. Default: 0.5. */
856
+ minSeparationOctaves?: number;
857
+ /** Minimum band width (Hz). Prevents implausibly narrow proposals. Default: 20. */
858
+ minBandwidthHz?: number;
859
+ /** Time window for analysis in seconds (0 = full track). Default: 0. */
860
+ analysisWindow?: number;
861
+ };
862
+
863
+ /**
864
+ * Result of band proposal generation.
865
+ */
866
+ export type BandProposalResult = {
867
+ kind: "bandProposals";
868
+ proposals: BandProposal[];
869
+ meta: MirRunMeta;
870
+ };