@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
@@ -1,13 +1,19 @@
1
+ import { detectBeatCandidates } from "../dsp/beatCandidates";
2
+ import { generateTempoHypotheses } from "../dsp/tempoHypotheses";
1
3
  import { melSpectrogram, type MelConfig, type MelSpectrogram } from "../dsp/mel";
2
4
  import { mfcc, delta, deltaDelta } from "../dsp/mfcc";
3
5
  import { onsetEnvelopeFromMel, onsetEnvelopeFromMelGpu } from "../dsp/onset";
4
6
  import { peakPick } from "../dsp/peakPick";
5
7
  import { hpss } from "../dsp/hpss";
6
8
  import { hpssGpu } from "../dsp/hpssGpu";
7
- import { spectralCentroid, spectralFlux } from "../dsp/spectral";
9
+ import { amplitudeEnvelope, spectralCentroid, spectralFlux } from "../dsp/spectral";
8
10
  import { spectrogram, type AudioBufferLike, type Spectrogram, type SpectrogramConfig } from "../dsp/spectrogram";
11
+ import { cqtSpectrogram, withCqtDefaults } from "../dsp/cqt";
12
+ import { harmonicEnergy, bassPitchMotion, tonalStability } from "../dsp/cqtSignals";
13
+ import { pitchF0, pitchConfidence } from "../dsp/pitch";
14
+ import { computeActivityFromMel } from "../dsp/activity";
9
15
  import type { MirGPU } from "../gpu/context";
10
- import type { MirAudioPayload, MirBackend, MirResult, MirRunRequest } from "../types";
16
+ import type { CqtConfig, MirAudioPayload, MirBackend, MirResult, MirRunRequest } from "../types";
11
17
 
12
18
  function nowMs(): number {
13
19
  return typeof performance !== "undefined" ? performance.now() : Date.now();
@@ -84,6 +90,29 @@ export async function runMir(
84
90
  window: "hann",
85
91
  };
86
92
 
93
+ // Amplitude envelope: compute directly from raw audio (no spectrogram needed)
94
+ if (request.fn === "amplitudeEnvelope") {
95
+ const cpuStart = nowMs();
96
+ const result = amplitudeEnvelope(audio.mono, audio.sampleRate, {
97
+ hopSize: specConfig.hopSize,
98
+ windowSize: specConfig.fftSize,
99
+ });
100
+ const cpuEnd = nowMs();
101
+ return {
102
+ kind: "1d",
103
+ times: result.times,
104
+ values: result.values,
105
+ meta: {
106
+ backend: "cpu",
107
+ usedGpu: false,
108
+ timings: {
109
+ totalMs: cpuEnd - t0,
110
+ cpuMs: cpuEnd - cpuStart,
111
+ },
112
+ },
113
+ };
114
+ }
115
+
87
116
  // CPU: spectrogram + centroid/flux are CPU-only today.
88
117
  const cpuStart = nowMs();
89
118
  const spec: Spectrogram = await spectrogram(asAudioBufferLike(audio), specConfig, undefined, {
@@ -285,6 +314,76 @@ export async function runMir(
285
314
  };
286
315
  }
287
316
 
317
+ if (request.fn === "beatCandidates") {
318
+ // Beat candidate detection requires both mel spectrogram and raw spectrogram.
319
+ const { mel, cpuExtraMs: melCpuMs } = await computeMel(false);
320
+
321
+ const beatOpts = request.beatCandidates ?? {};
322
+ const result = detectBeatCandidates(mel, spec, {
323
+ minIntervalSec: beatOpts.minIntervalSec,
324
+ thresholdFactor: beatOpts.thresholdFactor,
325
+ smoothMs: beatOpts.smoothMs,
326
+ });
327
+
328
+ const end = nowMs();
329
+ return {
330
+ kind: "beatCandidates",
331
+ times: result.salience.times,
332
+ candidates: result.candidates,
333
+ salience: beatOpts.includeSalience ? result.salience : undefined,
334
+ meta: {
335
+ backend: "cpu",
336
+ usedGpu: false,
337
+ timings: {
338
+ totalMs: end - t0,
339
+ cpuMs: cpuAfterSpec - cpuStart + melCpuMs,
340
+ },
341
+ },
342
+ };
343
+ }
344
+
345
+ if (request.fn === "tempoHypotheses") {
346
+ // Tempo hypothesis generation requires beat candidates.
347
+ // We compute them internally (could accept pre-computed in future).
348
+ const { mel, cpuExtraMs: melCpuMs } = await computeMel(false);
349
+
350
+ const beatOpts = request.beatCandidates ?? {};
351
+ const beatResult = detectBeatCandidates(mel, spec, {
352
+ minIntervalSec: beatOpts.minIntervalSec,
353
+ thresholdFactor: beatOpts.thresholdFactor,
354
+ smoothMs: beatOpts.smoothMs,
355
+ });
356
+
357
+ const tempoStart = nowMs();
358
+ const tempoOpts = request.tempoHypotheses ?? {};
359
+ const result = generateTempoHypotheses(beatResult.candidates, {
360
+ minBpm: tempoOpts.minBpm,
361
+ maxBpm: tempoOpts.maxBpm,
362
+ binSizeBpm: tempoOpts.binSizeBpm,
363
+ maxHypotheses: tempoOpts.maxHypotheses,
364
+ minConfidence: tempoOpts.minConfidence,
365
+ weightByStrength: tempoOpts.weightByStrength,
366
+ includeHistogram: tempoOpts.includeHistogram,
367
+ });
368
+
369
+ const end = nowMs();
370
+ return {
371
+ kind: "tempoHypotheses",
372
+ times: spec.times,
373
+ hypotheses: result.hypotheses,
374
+ inputCandidateCount: result.inputCandidateCount,
375
+ histogram: result.histogram,
376
+ meta: {
377
+ backend: "cpu",
378
+ usedGpu: false,
379
+ timings: {
380
+ totalMs: end - t0,
381
+ cpuMs: cpuAfterSpec - cpuStart + melCpuMs + (end - tempoStart),
382
+ },
383
+ },
384
+ };
385
+ }
386
+
288
387
  if (request.fn === "hpssHarmonic" || request.fn === "hpssPercussive") {
289
388
  // HPSS may use a custom spectrogram config
290
389
  const hpssSpecConfig = options.hpss?.spectrogram ?? specConfig;
@@ -410,6 +509,139 @@ export async function runMir(
410
509
  };
411
510
  }
412
511
 
512
+ // ----------------------------
513
+ // CQT-derived signals (F5)
514
+ // ----------------------------
515
+
516
+ if (request.fn === "cqtHarmonicEnergy" || request.fn === "cqtBassPitchMotion" || request.fn === "cqtTonalStability") {
517
+ // CQT signals bypass the STFT we computed above and compute their own CQT.
518
+ // This is intentional: CQT has different frequency resolution requirements.
519
+ const cqtStart = nowMs();
520
+
521
+ const cqtConfig: CqtConfig = withCqtDefaults(request.cqt);
522
+ const cqt = await cqtSpectrogram(asAudioBufferLike(audio), cqtConfig, {
523
+ isCancelled: options.isCancelled,
524
+ });
525
+
526
+ const cqtEnd = nowMs();
527
+
528
+ // Compute the requested signal
529
+ let signal;
530
+ if (request.fn === "cqtHarmonicEnergy") {
531
+ signal = harmonicEnergy(cqt);
532
+ } else if (request.fn === "cqtBassPitchMotion") {
533
+ signal = bassPitchMotion(cqt);
534
+ } else {
535
+ signal = tonalStability(cqt);
536
+ }
537
+
538
+ const end = nowMs();
539
+
540
+ return {
541
+ kind: "1d",
542
+ times: signal.times,
543
+ values: signal.values,
544
+ meta: {
545
+ backend: "cpu",
546
+ usedGpu: false,
547
+ timings: {
548
+ totalMs: end - t0,
549
+ cpuMs: (cqtEnd - cqtStart) + (end - cqtEnd),
550
+ },
551
+ },
552
+ };
553
+ }
554
+
555
+ // ----------------------------
556
+ // Pitch detection (P1)
557
+ // ----------------------------
558
+
559
+ if (request.fn === "pitchF0" || request.fn === "pitchConfidence") {
560
+ // Pitch detection works directly on raw audio samples (like amplitudeEnvelope).
561
+ // Uses YIN algorithm for monophonic pitch estimation.
562
+ const pitchStart = nowMs();
563
+
564
+ const pitchConfig = {
565
+ fMinHz: request.pitch?.fMinHz,
566
+ fMaxHz: request.pitch?.fMaxHz,
567
+ threshold: request.pitch?.threshold,
568
+ hopSize: specConfig.hopSize,
569
+ windowSize: specConfig.fftSize,
570
+ };
571
+
572
+ const result = request.fn === "pitchF0"
573
+ ? pitchF0(audio.mono, audio.sampleRate, pitchConfig)
574
+ : pitchConfidence(audio.mono, audio.sampleRate, pitchConfig);
575
+
576
+ const end = nowMs();
577
+
578
+ return {
579
+ kind: "1d",
580
+ times: result.times,
581
+ values: result.values,
582
+ meta: {
583
+ backend: "cpu",
584
+ usedGpu: false,
585
+ timings: {
586
+ totalMs: end - t0,
587
+ cpuMs: end - pitchStart,
588
+ },
589
+ },
590
+ };
591
+ }
592
+
593
+ // ----------------------------
594
+ // Activity detection
595
+ // ----------------------------
596
+
597
+ if (request.fn === "activity") {
598
+ // Activity detection requires mel spectrogram.
599
+ const { mel, cpuExtraMs: melCpuMs } = await computeMel(false);
600
+ const activityStart = nowMs();
601
+
602
+ const activityConfig = {
603
+ energyPercentile: request.activity?.energyPercentile,
604
+ enterMargin: request.activity?.enterMargin,
605
+ exitMargin: request.activity?.exitMargin,
606
+ hangoverMs: request.activity?.hangoverMs,
607
+ minActiveMs: request.activity?.minActiveMs,
608
+ smoothMs: request.activity?.smoothMs,
609
+ };
610
+
611
+ const activity = computeActivityFromMel(mel, activityConfig);
612
+ const end = nowMs();
613
+
614
+ // Compute diagnostics
615
+ let activeCount = 0;
616
+ for (let i = 0; i < activity.isActive.length; i++) {
617
+ if (activity.isActive[i]) activeCount++;
618
+ }
619
+
620
+ return {
621
+ kind: "activity",
622
+ times: activity.times,
623
+ activityLevel: activity.activityLevel,
624
+ isActive: activity.isActive,
625
+ suppressMask: activity.suppressMask,
626
+ meta: {
627
+ backend: "cpu",
628
+ usedGpu: false,
629
+ timings: {
630
+ totalMs: end - t0,
631
+ cpuMs: cpuAfterSpec - cpuStart + melCpuMs + (end - activityStart),
632
+ },
633
+ },
634
+ diagnostics: {
635
+ noiseFloor: activity.diagnostics.noiseFloor,
636
+ enterThreshold: activity.diagnostics.enterThreshold,
637
+ exitThreshold: activity.diagnostics.exitThreshold,
638
+ totalFrames: activity.times.length,
639
+ activeFrames: activeCount,
640
+ activeFraction: activity.times.length > 0 ? activeCount / activity.times.length : 0,
641
+ },
642
+ };
643
+ }
644
+
413
645
  // Fallback: keep old behaviour (melSpectrogram) if unknown fn.
414
646
  const { mel, usedGpu, gpuMs, cpuExtraMs } = await computeMel(backend === "gpu");
415
647
  const end = nowMs();
@@ -1,4 +1,4 @@
1
- import type { MirAudioPayload, MirResult, MirRunRequest } from "../types";
1
+ import type { BeatCandidate, MirAudioPayload, MirResult, MirRunRequest, TempoHypothesis } from "../types";
2
2
 
3
3
  export type MirWorkerInitMessage = {
4
4
  type: "INIT";
@@ -93,6 +93,14 @@ export type MirWorkerResultMessage = {
93
93
  values?: ArrayBufferLike;
94
94
  data2d?: ArrayBufferLike[];
95
95
  events?: Array<{ time: number; strength: number; index: number }>;
96
+ candidates?: BeatCandidate[];
97
+ // For tempoHypotheses
98
+ hypotheses?: TempoHypothesis[];
99
+ inputCandidateCount?: number;
100
+ histogram?: {
101
+ bpmBins: ArrayBufferLike;
102
+ counts: ArrayBufferLike;
103
+ };
96
104
  meta: MirResult["meta"];
97
105
  };
98
106
  };