@libraz/libsonare 1.5.5 → 1.6.0

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 (75) hide show
  1. package/README.md +109 -20
  2. package/dist/analysis.d.ts +5730 -0
  3. package/dist/analysis.js +2464 -0
  4. package/dist/analysis.js.map +1 -0
  5. package/dist/index.d.ts +759 -35
  6. package/dist/index.js +904 -209
  7. package/dist/index.js.map +1 -1
  8. package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
  9. package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
  10. package/dist/sonare-analysis.js +2 -0
  11. package/dist/sonare-analysis.wasm +0 -0
  12. package/dist/sonare.js +1 -1
  13. package/dist/sonare.wasm +0 -0
  14. package/dist/worker.d.ts +65 -0
  15. package/dist/worker.js +707 -0
  16. package/dist/worker.js.map +1 -0
  17. package/dist/worklet.d.ts +574 -86
  18. package/dist/worklet.js +1634 -235
  19. package/dist/worklet.js.map +1 -1
  20. package/package.json +23 -11
  21. package/src/_chain_config.ts +5 -3
  22. package/src/analysis.ts +137 -0
  23. package/src/analysis_helpers.ts +6 -0
  24. package/src/clip_page_streamer.ts +62 -8
  25. package/src/codes.ts +51 -64
  26. package/src/effects_mastering.ts +1 -0
  27. package/src/errors.ts +1 -0
  28. package/src/feature_core.ts +140 -2
  29. package/src/feature_music.ts +17 -2
  30. package/src/feature_pitch.ts +70 -1
  31. package/src/feature_spectral.ts +231 -5
  32. package/src/feature_spectrogram.ts +159 -0
  33. package/src/features.ts +16 -1
  34. package/src/index.ts +101 -7
  35. package/src/mastering_chain.ts +20 -8
  36. package/src/mastering_core.ts +5 -0
  37. package/src/mastering_dynamics.ts +7 -1
  38. package/src/metering.ts +6 -1
  39. package/src/opfs_clip_pages.ts +26 -1
  40. package/src/project.ts +7 -0
  41. package/src/project_class.ts +62 -8
  42. package/src/project_internal.ts +24 -29
  43. package/src/project_types.ts +69 -10
  44. package/src/public_types.ts +68 -0
  45. package/src/public_types_mastering.ts +22 -0
  46. package/src/public_types_mixing.ts +11 -2
  47. package/src/public_types_music.ts +9 -0
  48. package/src/public_types_spectral.ts +33 -0
  49. package/src/quick_analysis.ts +25 -7
  50. package/src/realtime_engine.ts +153 -3
  51. package/src/realtime_voice_changer.ts +51 -77
  52. package/src/sonare-analysis.js.d.ts +8 -0
  53. package/src/sonare.js.d.ts +276 -6
  54. package/src/streaming_processors.ts +15 -0
  55. package/src/web_midi.ts +36 -4
  56. package/src/worker.ts +196 -0
  57. package/src/worker_client.ts +352 -0
  58. package/src/worker_protocol.ts +59 -0
  59. package/src/worklet/engine-automation.ts +2 -1
  60. package/src/worklet/engine-capture-facade.ts +13 -4
  61. package/src/worklet/engine-clips.ts +12 -3
  62. package/src/worklet/engine-node.ts +212 -4
  63. package/src/worklet/engine-offline.ts +7 -2
  64. package/src/worklet/engine-parameter-facade.ts +26 -5
  65. package/src/worklet/engine-processor.ts +383 -42
  66. package/src/worklet/engine-register.ts +8 -0
  67. package/src/worklet/engine-strips.ts +17 -1
  68. package/src/worklet/engine-sync.ts +2 -6
  69. package/src/worklet/engine-tempo-facade.ts +8 -6
  70. package/src/worklet/engine.ts +380 -13
  71. package/src/worklet/guards.ts +22 -1
  72. package/src/worklet/messages.ts +125 -7
  73. package/src/worklet/protocol.ts +301 -17
  74. package/src/worklet/voice-changer-processor.ts +12 -15
  75. package/src/worklet.ts +20 -1
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
8
8
  ErrorCode2[ErrorCode2["OutOfMemory"] = 5] = "OutOfMemory";
9
9
  ErrorCode2[ErrorCode2["NotSupported"] = 6] = "NotSupported";
10
10
  ErrorCode2[ErrorCode2["InvalidState"] = 7] = "InvalidState";
11
+ ErrorCode2[ErrorCode2["Cancelled"] = 8] = "Cancelled";
11
12
  ErrorCode2[ErrorCode2["Unknown"] = 99] = "Unknown";
12
13
  return ErrorCode2;
13
14
  })(ErrorCode || {});
@@ -405,6 +406,8 @@ function flattenChainConfig(config) {
405
406
  out[path] = value;
406
407
  } else if (value !== null && typeof value === "object") {
407
408
  walk(value, path);
409
+ } else if (value !== void 0) {
410
+ throw new TypeError(`Mastering override '${path}' must be a number or boolean.`);
408
411
  }
409
412
  }
410
413
  };
@@ -446,12 +449,14 @@ function masterAudioStereoRequest(requestOrLeft, right, sampleRate, preset, over
446
449
  }
447
450
  function masteringChain(samples, sampleRate = 22050, config = {}, onProgress) {
448
451
  const request = samples instanceof Float32Array ? { samples, sampleRate, config, onProgress } : samples;
449
- if (request.onProgress) {
452
+ if (request.onProgress || request.cancel) {
450
453
  return requireModule3().masteringChainWithProgress(
451
454
  request.samples,
452
455
  request.sampleRate ?? 22050,
453
456
  canonicalChainConfig(request.config ?? {}),
454
- request.onProgress
457
+ request.onProgress ?? (() => {
458
+ }),
459
+ request.cancel ?? (() => false)
455
460
  );
456
461
  }
457
462
  return requireModule3().masteringChain(
@@ -465,13 +470,15 @@ function masteringChainStereo(left, right, sampleRate = 22050, config = {}, onPr
465
470
  if (request.left.length !== request.right.length) {
466
471
  throw new Error("Stereo channel lengths must match.");
467
472
  }
468
- if (request.onProgress) {
473
+ if (request.onProgress || request.cancel) {
469
474
  return requireModule3().masteringChainStereoWithProgress(
470
475
  request.left,
471
476
  request.right,
472
477
  request.sampleRate ?? 22050,
473
478
  canonicalChainConfig(request.config ?? {}),
474
- request.onProgress
479
+ request.onProgress ?? (() => {
480
+ }),
481
+ request.cancel ?? (() => false)
475
482
  );
476
483
  }
477
484
  return requireModule3().masteringChainStereo(
@@ -490,7 +497,8 @@ function masteringChainWithProgress(samples, sampleRate = 22050, config = {}, on
490
497
  request.samples,
491
498
  request.sampleRate ?? 22050,
492
499
  canonicalChainConfig(request.config ?? {}),
493
- request.onProgress
500
+ request.onProgress,
501
+ request.cancel ?? (() => false)
494
502
  );
495
503
  }
496
504
  function masteringChainStereoWithProgress(left, right, sampleRate = 22050, config = {}, onProgress) {
@@ -506,7 +514,8 @@ function masteringChainStereoWithProgress(left, right, sampleRate = 22050, confi
506
514
  request.right,
507
515
  request.sampleRate ?? 22050,
508
516
  canonicalChainConfig(request.config ?? {}),
509
- request.onProgress
517
+ request.onProgress,
518
+ request.cancel ?? (() => false)
510
519
  );
511
520
  }
512
521
  function masteringPresetNames() {
@@ -515,13 +524,15 @@ function masteringPresetNames() {
515
524
  function masterAudio(samples, sampleRate = 22050, presetName = "pop", overrides = {}, onProgress) {
516
525
  const request = masterAudioRequest(samples, sampleRate, presetName, overrides, onProgress);
517
526
  const flat = flattenChainConfig(request.overrides ?? {});
518
- if (request.onProgress) {
527
+ if (request.onProgress || request.cancel) {
519
528
  return requireModule3().masterAudioWithProgress(
520
529
  request.preset ?? "pop",
521
530
  request.samples,
522
531
  request.sampleRate ?? 22050,
523
532
  flat,
524
- request.onProgress
533
+ request.onProgress ?? (() => {
534
+ }),
535
+ request.cancel ?? (() => false)
525
536
  );
526
537
  }
527
538
  return requireModule3().masterAudio(
@@ -544,14 +555,16 @@ function masterAudioStereo(left, right = void 0, sampleRate = 22050, presetName
544
555
  if (request.left.length !== request.right.length) {
545
556
  throw new Error("Stereo channel lengths must match.");
546
557
  }
547
- if (request.onProgress) {
558
+ if (request.onProgress || request.cancel) {
548
559
  return requireModule3().masterAudioStereoWithProgress(
549
560
  request.preset ?? "pop",
550
561
  request.left,
551
562
  request.right,
552
563
  request.sampleRate ?? 22050,
553
564
  flat,
554
- request.onProgress
565
+ request.onProgress ?? (() => {
566
+ }),
567
+ request.cancel ?? (() => false)
555
568
  );
556
569
  }
557
570
  return requireModule3().masterAudioStereo(
@@ -572,7 +585,8 @@ function masterAudioWithProgress(samples, sampleRate = 22050, presetName = "pop"
572
585
  request.samples,
573
586
  request.sampleRate ?? 22050,
574
587
  flattenChainConfig(request.overrides ?? {}),
575
- request.onProgress
588
+ request.onProgress,
589
+ request.cancel ?? (() => false)
576
590
  );
577
591
  }
578
592
  function masterAudioStereoWithProgress(left, right = void 0, sampleRate = 22050, presetName = "pop", overrides = null, onProgress) {
@@ -596,7 +610,8 @@ function masterAudioStereoWithProgress(left, right = void 0, sampleRate = 22050,
596
610
  request.right,
597
611
  request.sampleRate ?? 22050,
598
612
  flattenChainConfig(request.overrides ?? {}),
599
- request.onProgress
613
+ request.onProgress,
614
+ request.cancel ?? (() => false)
600
615
  );
601
616
  }
602
617
 
@@ -759,7 +774,10 @@ function masteringDynamicsCompressor(samples, sampleRate, options = {}) {
759
774
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
760
775
  assertSamples("masteringDynamicsCompressor", request.samples, request.validate !== false);
761
776
  const detector = typeof request.detector === "string" ? COMPRESSOR_DETECTOR_MAP[request.detector] : request.detector;
762
- const opts = { ...request };
777
+ const opts = Object.assign(
778
+ Object.create(Object.getPrototypeOf(request)),
779
+ request
780
+ );
763
781
  if (detector !== void 0) {
764
782
  opts.detector = detector;
765
783
  }
@@ -848,6 +866,36 @@ function mixStereo(leftChannels, rightChannels, sampleRate = 48e3, options = {})
848
866
  function requireModule8() {
849
867
  return getSonareModule();
850
868
  }
869
+ function tone(frequency = 440, sampleRate = 22050, duration = 1, phase = 0, amplitude = 1) {
870
+ const request = typeof frequency === "number" ? { frequency, sampleRate, duration, phase, amplitude } : frequency;
871
+ return requireModule8().tone(
872
+ request.frequency ?? 440,
873
+ request.sampleRate ?? 22050,
874
+ request.duration ?? 1,
875
+ request.phase ?? 0,
876
+ request.amplitude ?? 1
877
+ );
878
+ }
879
+ function chirp(fmin = 440, fmax = 880, sampleRate = 22050, duration = 1, linear = true) {
880
+ const request = typeof fmin === "number" ? { fmin, fmax, sampleRate, duration, linear } : fmin;
881
+ return requireModule8().chirp(
882
+ request.fmin ?? 440,
883
+ request.fmax ?? 880,
884
+ request.sampleRate ?? 22050,
885
+ request.duration ?? 1,
886
+ request.linear ?? true
887
+ );
888
+ }
889
+ function clicks(times, sampleRate = 22050, length = 0, frequency = 1e3, clickDuration = 0.1) {
890
+ const request = times instanceof Float32Array ? { times, sampleRate, length, frequency, clickDuration } : times;
891
+ return requireModule8().clicks(
892
+ request.times,
893
+ request.sampleRate ?? 22050,
894
+ request.length ?? 0,
895
+ request.frequency ?? 1e3,
896
+ request.clickDuration ?? 0.1
897
+ );
898
+ }
851
899
  function hzToMel(hz) {
852
900
  return requireModule8().hzToMel(hz);
853
901
  }
@@ -944,6 +992,12 @@ function fixFrames(frames, xMin = 0, xMax = -1, pad = true) {
944
992
  }
945
993
  return requireModule8().fixFrames(frames, xMin, xMax, pad);
946
994
  }
995
+ function onsetBacktrack(events, energy) {
996
+ if (!(events instanceof Int32Array)) {
997
+ return onsetBacktrack(events.events, events.energy);
998
+ }
999
+ return requireModule8().onsetBacktrack(events, energy);
1000
+ }
947
1001
  function peakPick(values, preMax, postMax, preAvg, postAvg, delta, wait) {
948
1002
  if (!(values instanceof Float32Array)) {
949
1003
  const r = values;
@@ -988,12 +1042,28 @@ function tonnetz(chromagram, nChroma, nFrames) {
988
1042
  }
989
1043
  return requireModule8().tonnetz(chromagram, nChroma, nFrames);
990
1044
  }
991
- function tempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, mode = "autocorrelation") {
1045
+ function tempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, mode = "autocorrelation", center = true, norm = true) {
992
1046
  if (!(onsetEnvelope2 instanceof Float32Array)) {
993
1047
  const r = onsetEnvelope2;
994
- return tempogram(r.onsetEnvelope, r.sampleRate, r.hopLength, r.winLength, r.mode);
1048
+ return tempogram(
1049
+ r.onsetEnvelope,
1050
+ r.sampleRate,
1051
+ r.hopLength,
1052
+ r.winLength,
1053
+ r.mode,
1054
+ r.center,
1055
+ r.norm
1056
+ );
995
1057
  }
996
- return requireModule8().tempogram(onsetEnvelope2, sampleRate, hopLength, winLength, mode);
1058
+ return requireModule8().tempogram(
1059
+ onsetEnvelope2,
1060
+ sampleRate,
1061
+ hopLength,
1062
+ winLength,
1063
+ mode,
1064
+ center,
1065
+ norm
1066
+ );
997
1067
  }
998
1068
  function cyclicTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, bpmMin = 60, nBins = 60) {
999
1069
  if (!(onsetEnvelope2 instanceof Float32Array)) {
@@ -1327,7 +1397,7 @@ function onsetStrengthMulti(samples, sampleRate = 22050, nFft = 2048, hopLength
1327
1397
  validatePositiveIntegers("onsetStrengthMulti", { nFft, hopLength, nMels, nBands });
1328
1398
  return requireModule9().onsetStrengthMulti(samples, sampleRate, nFft, hopLength, nMels, nBands);
1329
1399
  }
1330
- function fourierTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, options = {}) {
1400
+ function fourierTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, winLength = 384, center = true, norm = true, options = {}) {
1331
1401
  if (!(onsetEnvelope2 instanceof Float32Array)) {
1332
1402
  const request = onsetEnvelope2;
1333
1403
  return fourierTempogram(
@@ -1335,13 +1405,22 @@ function fourierTempogram(onsetEnvelope2, sampleRate = 22050, hopLength = 512, w
1335
1405
  request.sampleRate,
1336
1406
  request.hopLength,
1337
1407
  request.winLength,
1408
+ request.center,
1409
+ request.norm,
1338
1410
  request
1339
1411
  );
1340
1412
  }
1341
1413
  assertSampleRate("fourierTempogram", sampleRate);
1342
1414
  assertSamples("fourierTempogram", onsetEnvelope2, options.validate !== false, "onsetEnvelope");
1343
1415
  validatePositiveIntegers("fourierTempogram", { hopLength, winLength });
1344
- return requireModule9().fourierTempogram(onsetEnvelope2, sampleRate, hopLength, winLength);
1416
+ return requireModule9().fourierTempogram(
1417
+ onsetEnvelope2,
1418
+ sampleRate,
1419
+ hopLength,
1420
+ winLength,
1421
+ center,
1422
+ norm
1423
+ );
1345
1424
  }
1346
1425
  function tempogramRatio(tempogramData, winLength = 384, sampleRate = 22050, hopLength = 512, factors, options = {}) {
1347
1426
  if (!(tempogramData instanceof Float32Array)) {
@@ -1392,6 +1471,21 @@ function shortTermLufs(samples, sampleRate = 22050, options = {}) {
1392
1471
  function requireModule10() {
1393
1472
  return getSonareModule();
1394
1473
  }
1474
+ function piptrack(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, fmin = 150, fmax = 4e3, threshold = 0.1) {
1475
+ if (!(samples instanceof Float32Array)) {
1476
+ const request = samples;
1477
+ return piptrack(
1478
+ request.samples,
1479
+ request.sampleRate,
1480
+ request.nFft,
1481
+ request.hopLength,
1482
+ request.fmin,
1483
+ request.fmax,
1484
+ request.threshold
1485
+ );
1486
+ }
1487
+ return requireModule10().piptrack(samples, sampleRate, nFft, hopLength, fmin, fmax, threshold);
1488
+ }
1395
1489
  function pitchYin(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512, fmin = 65, fmax = 2093, threshold = 0.1, fillNa = false) {
1396
1490
  if (!(samples instanceof Float32Array)) {
1397
1491
  const request = samples;
@@ -1442,6 +1536,13 @@ function pitchPyin(samples, sampleRate = 22050, frameLength = 2048, hopLength =
1442
1536
  fillNa
1443
1537
  );
1444
1538
  }
1539
+ function noteSegments(request) {
1540
+ return requireModule10().noteSegments(request.f0Hz, request.voicedProb, request.frameRate, {
1541
+ segmentationThresholdCents: request.segmentationThresholdCents,
1542
+ minNoteMs: request.minNoteMs,
1543
+ referenceHz: request.referenceHz
1544
+ });
1545
+ }
1445
1546
 
1446
1547
  // src/feature_resample.ts
1447
1548
  function requireModule11() {
@@ -1458,6 +1559,15 @@ function resample(samples, srcSr, targetSr) {
1458
1559
  function requireModule12() {
1459
1560
  return getSonareModule();
1460
1561
  }
1562
+ function validateSegmentMatrix(fnName, data, rows, cols, dataName) {
1563
+ assertPositiveInteger(fnName, rows, "rows");
1564
+ assertPositiveInteger(fnName, cols, "cols");
1565
+ assertSamples(fnName, data, true, dataName);
1566
+ const expected = rows * cols;
1567
+ if (!Number.isSafeInteger(expected) || data.length !== expected) {
1568
+ throw new RangeError(`${fnName}: ${dataName} length must equal rows * cols`);
1569
+ }
1570
+ }
1461
1571
  function spectralCentroid(samples, sampleRate = 22050, nFft = 2048, hopLength = 512) {
1462
1572
  if (!(samples instanceof Float32Array)) {
1463
1573
  return spectralCentroid(samples.samples, samples.sampleRate, samples.nFft, samples.hopLength);
@@ -1602,11 +1712,17 @@ function ebur128LoudnessRange(samples, sampleRate = 22050) {
1602
1712
  }
1603
1713
  return requireModule12().ebur128LoudnessRange(samples, sampleRate);
1604
1714
  }
1605
- function spectralBandwidth(samples, sampleRate = 22050, nFft = 2048, hopLength = 512) {
1715
+ function spectralBandwidth(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, p = 2) {
1606
1716
  if (!(samples instanceof Float32Array)) {
1607
- return spectralBandwidth(samples.samples, samples.sampleRate, samples.nFft, samples.hopLength);
1717
+ return spectralBandwidth(
1718
+ samples.samples,
1719
+ samples.sampleRate,
1720
+ samples.nFft,
1721
+ samples.hopLength,
1722
+ samples.p
1723
+ );
1608
1724
  }
1609
- return requireModule12().spectralBandwidth(samples, sampleRate, nFft, hopLength);
1725
+ return requireModule12().spectralBandwidth(samples, sampleRate, nFft, hopLength, p);
1610
1726
  }
1611
1727
  function spectralRolloff(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, rollPercent = 0.85) {
1612
1728
  if (!(samples instanceof Float32Array)) {
@@ -1626,6 +1742,18 @@ function spectralFlatness(samples, sampleRate = 22050, nFft = 2048, hopLength =
1626
1742
  }
1627
1743
  return requireModule12().spectralFlatness(samples, sampleRate, nFft, hopLength);
1628
1744
  }
1745
+ function spectralFlux(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, lag = 1) {
1746
+ if (!(samples instanceof Float32Array)) {
1747
+ return spectralFlux(
1748
+ samples.samples,
1749
+ samples.sampleRate,
1750
+ samples.nFft,
1751
+ samples.hopLength,
1752
+ samples.lag
1753
+ );
1754
+ }
1755
+ return requireModule12().spectralFlux(samples, sampleRate, nFft, hopLength, lag);
1756
+ }
1629
1757
  function zeroCrossingRate(samples, sampleRate = 22050, frameLength = 2048, hopLength = 512) {
1630
1758
  if (!(samples instanceof Float32Array)) {
1631
1759
  return zeroCrossingRate(
@@ -1643,6 +1771,107 @@ function rmsEnergy(samples, sampleRate = 22050, frameLength = 2048, hopLength =
1643
1771
  }
1644
1772
  return requireModule12().rmsEnergy(samples, sampleRate, frameLength, hopLength);
1645
1773
  }
1774
+ function segmentCrossSimilarity(request) {
1775
+ validateSegmentMatrix("segmentCrossSimilarity", request.x, request.xRows, request.xCols, "x");
1776
+ validateSegmentMatrix("segmentCrossSimilarity", request.y, request.yRows, request.yCols, "y");
1777
+ if (request.xRows !== request.yRows) {
1778
+ throw new RangeError("segmentCrossSimilarity: feature dimensions must match");
1779
+ }
1780
+ assertNonNegativeInteger("segmentCrossSimilarity", request.k ?? 0, "k");
1781
+ return requireModule12().segmentCrossSimilarity(
1782
+ request.x,
1783
+ request.xRows,
1784
+ request.xCols,
1785
+ request.y,
1786
+ request.yRows,
1787
+ request.yCols,
1788
+ request.k ?? 0,
1789
+ request.metric ?? "cosine",
1790
+ request.mode ?? "connectivity"
1791
+ );
1792
+ }
1793
+ function segmentRecurrenceMatrix(request) {
1794
+ validateSegmentMatrix(
1795
+ "segmentRecurrenceMatrix",
1796
+ request.data,
1797
+ request.rows,
1798
+ request.cols,
1799
+ "data"
1800
+ );
1801
+ assertNonNegativeInteger("segmentRecurrenceMatrix", request.k ?? 0, "k");
1802
+ assertNonNegativeInteger("segmentRecurrenceMatrix", request.width ?? 1, "width");
1803
+ return requireModule12().segmentRecurrenceMatrix(
1804
+ request.data,
1805
+ request.rows,
1806
+ request.cols,
1807
+ request.k ?? 0,
1808
+ request.width ?? 1,
1809
+ request.sym ?? false,
1810
+ request.metric ?? "euclidean",
1811
+ request.mode ?? "connectivity"
1812
+ );
1813
+ }
1814
+ function segmentRecurrenceToLag(request) {
1815
+ validateSegmentMatrix(
1816
+ "segmentRecurrenceToLag",
1817
+ request.recurrence,
1818
+ request.n,
1819
+ request.n,
1820
+ "recurrence"
1821
+ );
1822
+ return requireModule12().segmentRecurrenceToLag(
1823
+ request.recurrence,
1824
+ request.n,
1825
+ request.pad ?? false
1826
+ );
1827
+ }
1828
+ function segmentLagToRecurrence(request) {
1829
+ validateSegmentMatrix("segmentLagToRecurrence", request.lag, request.rows, request.lags, "lag");
1830
+ return requireModule12().segmentLagToRecurrence(request.lag, request.rows, request.lags);
1831
+ }
1832
+ function segmentSubsegment(request) {
1833
+ validateSegmentMatrix("segmentSubsegment", request.data, request.rows, request.cols, "data");
1834
+ assertPositiveInteger("segmentSubsegment", request.nSegments ?? 4, "nSegments");
1835
+ return requireModule12().segmentSubsegment(
1836
+ request.data,
1837
+ request.rows,
1838
+ request.cols,
1839
+ request.boundaries,
1840
+ request.nSegments ?? 4
1841
+ );
1842
+ }
1843
+ function segmentAgglomerative(request) {
1844
+ validateSegmentMatrix("segmentAgglomerative", request.data, request.rows, request.cols, "data");
1845
+ assertPositiveInteger("segmentAgglomerative", request.k, "k");
1846
+ return requireModule12().segmentAgglomerative(
1847
+ request.data,
1848
+ request.rows,
1849
+ request.cols,
1850
+ request.k,
1851
+ request.linkage ?? "average"
1852
+ );
1853
+ }
1854
+ function segmentPathEnhance(request) {
1855
+ validateSegmentMatrix(
1856
+ "segmentPathEnhance",
1857
+ request.recurrence,
1858
+ request.n,
1859
+ request.n,
1860
+ "recurrence"
1861
+ );
1862
+ assertPositiveInteger("segmentPathEnhance", request.win, "win");
1863
+ assertPositiveInteger("segmentPathEnhance", request.maxRatio ?? 2, "maxRatio");
1864
+ assertNonNegativeInteger("segmentPathEnhance", request.minRatio ?? 0, "minRatio");
1865
+ assertPositiveInteger("segmentPathEnhance", request.nFilters ?? 7, "nFilters");
1866
+ return requireModule12().segmentPathEnhance(
1867
+ request.recurrence,
1868
+ request.n,
1869
+ request.win,
1870
+ request.maxRatio ?? 2,
1871
+ request.minRatio ?? 0,
1872
+ request.nFilters ?? 7
1873
+ );
1874
+ }
1646
1875
 
1647
1876
  // src/feature_spectrogram.ts
1648
1877
  function requireModule13() {
@@ -1821,6 +2050,53 @@ function mfcc(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, nMels =
1821
2050
  lifter
1822
2051
  );
1823
2052
  }
2053
+ function melDelta(features, nFeatures, nFrames, width = 9) {
2054
+ const request = features instanceof Float32Array ? { features, nFeatures: nFeatures ?? 0, nFrames: nFrames ?? 0, width } : features;
2055
+ assertPositiveInteger("melDelta", request.nFeatures, "nFeatures");
2056
+ assertPositiveInteger("melDelta", request.nFrames, "nFrames");
2057
+ assertPositiveInteger("melDelta", request.width ?? 9, "width");
2058
+ if ((request.width ?? 9) < 3 || (request.width ?? 9) % 2 === 0) {
2059
+ throw new RangeError("melDelta: width must be an odd integer of at least 3");
2060
+ }
2061
+ if (request.features.length !== request.nFeatures * request.nFrames) {
2062
+ throw new RangeError("melDelta: feature matrix length must equal nFeatures * nFrames");
2063
+ }
2064
+ return requireModule13().melDelta(
2065
+ request.features,
2066
+ request.nFeatures,
2067
+ request.nFrames,
2068
+ request.width ?? 9
2069
+ );
2070
+ }
2071
+ function reassignedSpectrogram(samples, sampleRate = 22050, nFft = 2048, hopLength = 512, refPower = 1e-6, fillNan = false) {
2072
+ if (!(samples instanceof Float32Array)) {
2073
+ const request = samples;
2074
+ return reassignedSpectrogram(
2075
+ request.samples,
2076
+ request.sampleRate,
2077
+ request.nFft,
2078
+ request.hopLength,
2079
+ request.refPower,
2080
+ request.fillNan
2081
+ );
2082
+ }
2083
+ assertSamples("reassignedSpectrogram", samples, true);
2084
+ assertSampleRate("reassignedSpectrogram", sampleRate);
2085
+ assertPositiveInteger("reassignedSpectrogram", nFft, "nFft");
2086
+ assertPositiveInteger("reassignedSpectrogram", hopLength, "hopLength");
2087
+ assertFiniteScalar("reassignedSpectrogram", refPower, "refPower");
2088
+ if (refPower < 0) {
2089
+ throw new RangeError("reassignedSpectrogram: refPower must be non-negative");
2090
+ }
2091
+ return requireModule13().reassignedSpectrogram(
2092
+ samples,
2093
+ sampleRate,
2094
+ nFft,
2095
+ hopLength,
2096
+ refPower,
2097
+ fillNan
2098
+ );
2099
+ }
1824
2100
  function melToStft(melPower, nMels = 0, nFrames = 0, sampleRate = 22050, nFft = 2048, fmin = 0, fmax = 0, htk = false, options = {}) {
1825
2101
  if (!(melPower instanceof Float32Array)) {
1826
2102
  const request = melPower;
@@ -1876,6 +2152,35 @@ function melToAudio(melPower, nMels = 0, nFrames = 0, sampleRate = 22050, nFft =
1876
2152
  htk
1877
2153
  );
1878
2154
  }
2155
+ function griffinLim(magnitude, nBins = 0, nFrames = 0, sampleRate = 22050, nFft = 2048, hopLength = 512, nIter = 32, momentum = 0.99, options = {}) {
2156
+ if (!(magnitude instanceof Float32Array)) {
2157
+ const request = magnitude;
2158
+ return griffinLim(
2159
+ request.magnitude,
2160
+ request.nBins,
2161
+ request.nFrames,
2162
+ request.sampleRate,
2163
+ request.nFft,
2164
+ request.hopLength,
2165
+ request.nIter,
2166
+ request.momentum,
2167
+ request
2168
+ );
2169
+ }
2170
+ assertSampleRate("griffinLim", sampleRate);
2171
+ validateMatrix("griffinLim", magnitude, nBins, nFrames, "magnitude", "nBins", options);
2172
+ validatePositiveIntegers2("griffinLim", { nFft, hopLength, nIter });
2173
+ return requireModule13().griffinLim(
2174
+ magnitude,
2175
+ nBins,
2176
+ nFrames,
2177
+ sampleRate,
2178
+ nFft,
2179
+ hopLength,
2180
+ nIter,
2181
+ momentum
2182
+ );
2183
+ }
1879
2184
  function mfccToMel(mfccCoefficients, nMfcc = 0, nFrames = 0, nMels = 128, lifter = 0, options = {}) {
1880
2185
  if (!(mfccCoefficients instanceof Float32Array)) {
1881
2186
  const request = mfccCoefficients;
@@ -2134,6 +2439,11 @@ function convertAnalysisResult(wasm) {
2134
2439
  return {
2135
2440
  bpm: wasm.bpm,
2136
2441
  bpmConfidence: wasm.bpmConfidence,
2442
+ bpmCandidates: wasm.bpmCandidates.map((candidate) => ({
2443
+ value: candidate.value,
2444
+ confidence: candidate.confidence,
2445
+ relation: candidate.relation
2446
+ })),
2137
2447
  key: {
2138
2448
  root: wasm.key.root,
2139
2449
  mode: wasm.key.mode,
@@ -2142,6 +2452,7 @@ function convertAnalysisResult(wasm) {
2142
2452
  shortName: wasm.key.shortName
2143
2453
  },
2144
2454
  timeSignature: wasm.timeSignature,
2455
+ timeSignatureCandidates: wasm.timeSignatureCandidates,
2145
2456
  beatTimes,
2146
2457
  beats: wasm.beats,
2147
2458
  chords: wasm.chords.map((c) => ({
@@ -2232,7 +2543,7 @@ function detectKeyCandidates(samples, sampleRate = 22050, options = {}) {
2232
2543
  function detectOnsets(samples, sampleRate = 22050, options = {}) {
2233
2544
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
2234
2545
  validateAnalysisInput("detectOnsets", request.samples, request.sampleRate ?? 22050, request);
2235
- return requireModule14().detectOnsets(request.samples, request.sampleRate ?? 22050);
2546
+ return requireModule14().detectOnsets(request.samples, request.sampleRate ?? 22050, request);
2236
2547
  }
2237
2548
  function detectBeats(samples, sampleRate = 22050, options = {}) {
2238
2549
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
@@ -2364,7 +2675,9 @@ function analyzeWithProgress(samples, sampleRate = 22050, onProgress) {
2364
2675
  const result = requireModule14().analyzeWithProgress(
2365
2676
  request.samples,
2366
2677
  request.sampleRate ?? 22050,
2367
- request.onProgress
2678
+ request.onProgress ?? (() => {
2679
+ }),
2680
+ request.cancel ?? (() => false)
2368
2681
  );
2369
2682
  return convertAnalysisResult(result);
2370
2683
  }
@@ -2788,7 +3101,12 @@ self.onmessage = async (event) => {
2788
3101
  `;
2789
3102
  function createOpfsClipPageWorker() {
2790
3103
  const blob = new Blob([opfsClipPageWorkerSource], { type: "text/javascript" });
2791
- return new Worker(URL.createObjectURL(blob));
3104
+ const url = URL.createObjectURL(blob);
3105
+ try {
3106
+ return new Worker(url);
3107
+ } finally {
3108
+ URL.revokeObjectURL(url);
3109
+ }
2792
3110
  }
2793
3111
  function createOpfsClipPageProvider(engine, options) {
2794
3112
  if (options.numChannels <= 0 || options.numSamples <= 0 || options.pageFrames <= 0) {
@@ -2828,6 +3146,7 @@ function createOpfsClipPageProvider(engine, options) {
2828
3146
  }
2829
3147
  try {
2830
3148
  provider.supply(response.pageIndex, channels);
3149
+ options.onPageSupplied?.(response.pageIndex, channels);
2831
3150
  } catch {
2832
3151
  entry.resolve(false);
2833
3152
  return;
@@ -2875,6 +3194,13 @@ function createOpfsClipPageProvider(engine, options) {
2875
3194
  supplyRequest(request) {
2876
3195
  return supplyPage(Math.floor(request.sample / options.pageFrames));
2877
3196
  },
3197
+ clearPage(pageIndex) {
3198
+ if (closed) {
3199
+ return;
3200
+ }
3201
+ provider.clear(pageIndex);
3202
+ options.onPageCleared?.(pageIndex);
3203
+ },
2878
3204
  close() {
2879
3205
  if (closed) {
2880
3206
  return;
@@ -2886,6 +3212,7 @@ function createOpfsClipPageProvider(engine, options) {
2886
3212
  }
2887
3213
  pending.clear();
2888
3214
  provider.destroy();
3215
+ options.onClose?.();
2889
3216
  if (ownsWorker) {
2890
3217
  worker.terminate();
2891
3218
  }
@@ -2963,7 +3290,10 @@ var ClipPageStreamer = class {
2963
3290
  if (!state) {
2964
3291
  continue;
2965
3292
  }
2966
- const page = Math.floor(request.sample / state.source.pageFrames);
3293
+ const page = request.pageIndex !== void 0 ? request.pageIndex : Math.floor((request.sample ?? Number.NaN) / state.source.pageFrames);
3294
+ if (!Number.isInteger(page) || page < 0 || page > state.lastPage) {
3295
+ continue;
3296
+ }
2967
3297
  frontiers.set(request.clipId, page);
2968
3298
  }
2969
3299
  const fetches = [];
@@ -2998,7 +3328,7 @@ var ClipPageStreamer = class {
2998
3328
  const high = Math.min(state.lastPage, frontier + this.readAheadPages);
2999
3329
  for (const page of state.resident.keys()) {
3000
3330
  if (page < low || page > high) {
3001
- state.source.binding.provider.clear(page);
3331
+ this.clearPage(state, page);
3002
3332
  state.resident.delete(page);
3003
3333
  }
3004
3334
  }
@@ -3013,7 +3343,7 @@ var ClipPageStreamer = class {
3013
3343
  state.source.binding.supplyPage(pageIndex).then(
3014
3344
  (ok) => {
3015
3345
  if (state.generation !== generation) {
3016
- state.source.binding.provider.clear(pageIndex);
3346
+ this.clearPage(state, pageIndex);
3017
3347
  } else if (!ok && state.resident.get(pageIndex) === generation) {
3018
3348
  state.resident.delete(pageIndex);
3019
3349
  }
@@ -3034,12 +3364,28 @@ var ClipPageStreamer = class {
3034
3364
  state.generation += 1;
3035
3365
  state.lastFrontier = null;
3036
3366
  for (const page of state.resident.keys()) {
3037
- state.source.binding.provider.clear(page);
3367
+ this.clearPage(state, page);
3038
3368
  }
3039
3369
  state.resident.clear();
3040
3370
  }
3371
+ clearPage(state, pageIndex) {
3372
+ if (state.source.binding.clearPage) {
3373
+ state.source.binding.clearPage(pageIndex);
3374
+ } else {
3375
+ state.source.binding.provider.clear(pageIndex);
3376
+ }
3377
+ }
3041
3378
  };
3042
- async function attachOpfsClipStream(streamer, engine, options) {
3379
+ async function attachOpfsClipStream(streamerOrEngine, engineOrOptions, maybeOptions) {
3380
+ if (!(streamerOrEngine instanceof ClipPageStreamer)) {
3381
+ return streamerOrEngine.attachOpfsClipStream(engineOrOptions);
3382
+ }
3383
+ const streamer = streamerOrEngine;
3384
+ const engine = engineOrOptions;
3385
+ const options = maybeOptions;
3386
+ if (!options) {
3387
+ throw new Error("attachOpfsClipStream requires options.");
3388
+ }
3043
3389
  const { clipId, primePages = 1, ...providerOptions } = options;
3044
3390
  const binding = createOpfsClipPageProvider(engine, providerOptions);
3045
3391
  const lastPage = Math.ceil(providerOptions.numSamples / providerOptions.pageFrames) - 1;
@@ -3095,7 +3441,11 @@ async function bindMicrophoneInput(context, engine, options = {}) {
3095
3441
  function assertOversampleFactor(fnName, factor) {
3096
3442
  const normalized = factor === 0 ? 4 : factor;
3097
3443
  if (!Number.isInteger(normalized) || normalized < 1 || normalized > 16 || (normalized & normalized - 1) !== 0) {
3098
- throw new RangeError(`${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`);
3444
+ throw new SonareError(
3445
+ 4 /* InvalidParameter */,
3446
+ "InvalidParameter",
3447
+ `${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`
3448
+ );
3099
3449
  }
3100
3450
  }
3101
3451
  function requireModule15() {
@@ -3275,6 +3625,61 @@ function waveformPeakPyramid(samples, channels, options = {}) {
3275
3625
  return requireModule15().waveformPeakPyramid(request.samples, request.channels, levels);
3276
3626
  }
3277
3627
 
3628
+ // src/codes.ts
3629
+ function resolveEnumOrdinal(value, values, enumName) {
3630
+ if (typeof value === "number") {
3631
+ if (!Number.isSafeInteger(value) || !Object.values(values).includes(value)) {
3632
+ throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
3633
+ }
3634
+ return value;
3635
+ }
3636
+ if (typeof value === "string") {
3637
+ const ordinal = values[value];
3638
+ if (ordinal !== void 0) {
3639
+ return ordinal;
3640
+ }
3641
+ }
3642
+ throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
3643
+ }
3644
+ var AUTOMATION_CURVE_VALUES = {
3645
+ linear: 0,
3646
+ exponential: 1,
3647
+ hold: 2,
3648
+ "s-curve": 3
3649
+ };
3650
+ var PAN_LAW_VALUES = {
3651
+ const3dB: 0,
3652
+ "const4.5dB": 1,
3653
+ const6dB: 2,
3654
+ linear0dB: 3
3655
+ };
3656
+ var PAN_MODE_VALUES = {
3657
+ balance: 0,
3658
+ pan: 0,
3659
+ stereopan: 1,
3660
+ "stereo-pan": 1,
3661
+ dualpan: 2,
3662
+ "dual-pan": 2
3663
+ };
3664
+ var METER_TAP_VALUES = { preFader: 0, postFader: 1 };
3665
+ var SEND_TIMING_VALUES = { postFader: 0, preFader: 1 };
3666
+ function automationCurveCode(curve) {
3667
+ return resolveEnumOrdinal(curve, AUTOMATION_CURVE_VALUES, "automation curve");
3668
+ }
3669
+ function panLawCode(panLaw) {
3670
+ return resolveEnumOrdinal(panLaw, PAN_LAW_VALUES, "pan law");
3671
+ }
3672
+ function panModeCode(panMode) {
3673
+ const normalized = typeof panMode === "string" ? panMode.replace(/_/g, "-").toLowerCase() : panMode;
3674
+ return resolveEnumOrdinal(normalized, PAN_MODE_VALUES, "pan mode");
3675
+ }
3676
+ function meterTapCode(tap) {
3677
+ return resolveEnumOrdinal(tap, METER_TAP_VALUES, "meter tap");
3678
+ }
3679
+ function sendTimingCode(timing) {
3680
+ return resolveEnumOrdinal(timing, SEND_TIMING_VALUES, "send timing");
3681
+ }
3682
+
3278
3683
  // src/project_internal.ts
3279
3684
  function projectModule() {
3280
3685
  const candidate = getSonareModule();
@@ -3341,37 +3746,17 @@ function assertProjectMidiEvents(fnName, events) {
3341
3746
  });
3342
3747
  }
3343
3748
  function projectTrackKindValue(kind) {
3344
- if (kind === void 0 || kind === "audio") {
3345
- return 0;
3346
- }
3347
- if (kind === "midi") {
3348
- return 1;
3349
- }
3350
- if (kind === "aux") {
3351
- return 2;
3352
- }
3353
- return kind;
3749
+ return resolveEnumOrdinal(kind ?? "audio", { audio: 0, midi: 1, aux: 2 }, "project track kind");
3354
3750
  }
3355
3751
  function projectWarpModeValue(mode) {
3356
- if (mode === void 0 || mode === "off") {
3357
- return 0;
3358
- }
3359
- if (mode === "repitch") {
3360
- return 1;
3361
- }
3362
- if (mode === "tempo-sync") {
3363
- return 2;
3364
- }
3365
- return mode;
3752
+ return resolveEnumOrdinal(
3753
+ mode ?? "off",
3754
+ { off: 0, repitch: 1, "tempo-sync": 2 },
3755
+ "project warp mode"
3756
+ );
3366
3757
  }
3367
3758
  function projectLoopModeValue(mode) {
3368
- if (mode === void 0 || mode === "off") {
3369
- return 0;
3370
- }
3371
- if (mode === "loop") {
3372
- return 1;
3373
- }
3374
- return mode;
3759
+ return resolveEnumOrdinal(mode ?? "off", { off: 0, loop: 1 }, "project loop mode");
3375
3760
  }
3376
3761
 
3377
3762
  // src/project_class.ts
@@ -3544,6 +3929,16 @@ var Project = class _Project {
3544
3929
  addClip(desc) {
3545
3930
  return this.native.addClip(desc);
3546
3931
  }
3932
+ /** Import host-separated PCM through the normal audio-track/clip path. */
3933
+ importExternalStems(request) {
3934
+ return this.native.importExternalStems({
3935
+ ...request,
3936
+ stems: request.stems.map((stem) => ({
3937
+ ...stem,
3938
+ layout: stem.layout === "mono" ? 1 : stem.layout === "stereo" ? 2 : stem.layout
3939
+ }))
3940
+ });
3941
+ }
3547
3942
  /** Split captured loop-recording audio into takes and add one clip. */
3548
3943
  addLoopRecordingTakes(desc) {
3549
3944
  return this.native.addLoopRecordingTakes(desc);
@@ -3588,7 +3983,11 @@ var Project = class _Project {
3588
3983
  * Route a track's MIDI to host-instrument `destinationId` (0 = default). The
3589
3984
  * compiler stamps every MIDI clip on the track with this id so the engine
3590
3985
  * dispatches its events to the instrument registered for that destination.
3591
- * Routes through an undoable edit command.
3986
+ * Routes through an undoable edit command. Builtin, NativeSynth, and SF2
3987
+ * instruments retain source-track provenance inside a shared destination
3988
+ * voice pool. With only zero-latency instruments bound, live lanes and
3989
+ * channel-strip bounces remain aligned. Configure one live lane per source
3990
+ * track that needs strip processing.
3592
3991
  */
3593
3992
  setTrackMidiDestination(trackId, destinationId) {
3594
3993
  this.native.setTrackMidiDestination(trackId, destinationId);
@@ -3825,6 +4224,14 @@ var Project = class _Project {
3825
4224
  setClipFade(clipId, fadeIn = {}, fadeOut = {}) {
3826
4225
  this.native.setClipFade(clipId, fadeIn, fadeOut);
3827
4226
  }
4227
+ /** Audio source ids that need decoded PCM after deserialization. */
4228
+ unresolvedAudioSourceIds() {
4229
+ return this.native.unresolvedAudioSourceIds();
4230
+ }
4231
+ /** Register decoded interleaved PCM for an existing audio source (undoable). */
4232
+ setSourceAudio(sourceId, audio, channels, sampleRate) {
4233
+ this.native.setSourceAudio(sourceId, audio, channels, sampleRate);
4234
+ }
3828
4235
  /** Replace a clip's take list and active take id (undoable). */
3829
4236
  setClipTakes(clipId, takes, activeTakeId = 0) {
3830
4237
  this.native.setClipTakes(clipId, takes, activeTakeId);
@@ -3866,23 +4273,23 @@ var Project = class _Project {
3866
4273
  setTrackRoute(trackId, channelStripRef, outputTarget) {
3867
4274
  this.native.setTrackRoute(trackId, channelStripRef ?? "", outputTarget ?? "");
3868
4275
  }
3869
- /** Append an automation lane to a track; returns the lane index (undoable). */
4276
+ /** Append an automation lane; returns its stable target parameter id (undoable). */
3870
4277
  addAutomationLane(trackId, desc) {
3871
4278
  return this.native.addAutomationLane(trackId, {
3872
4279
  targetParamId: desc.targetParamId,
3873
4280
  points: desc.points
3874
4281
  });
3875
4282
  }
3876
- /** Replace an existing automation lane in place (undoable). */
3877
- editAutomationLane(trackId, laneIndex, desc) {
3878
- this.native.editAutomationLane(trackId, laneIndex, {
4283
+ /** Replace the lane identified by its stable target parameter id (undoable). */
4284
+ editAutomationLane(trackId, targetParamId, desc) {
4285
+ this.native.editAutomationLane(trackId, targetParamId, {
3879
4286
  targetParamId: desc.targetParamId,
3880
4287
  points: desc.points
3881
4288
  });
3882
4289
  }
3883
- /** Remove an automation lane from a track (undoable). */
3884
- removeAutomationLane(trackId, laneIndex) {
3885
- this.native.removeAutomationLane(trackId, laneIndex);
4290
+ /** Remove the lane identified by its stable target parameter id (undoable). */
4291
+ removeAutomationLane(trackId, targetParamId) {
4292
+ this.native.removeAutomationLane(trackId, targetParamId);
3886
4293
  }
3887
4294
  /** Replace the project's key annotation stream (undoable). */
3888
4295
  annotateKeys(keys) {
@@ -3946,6 +4353,18 @@ var Project = class _Project {
3946
4353
  markerByIndex(index) {
3947
4354
  return this.native.markerByIndex(index);
3948
4355
  }
4356
+ /** Read a stored project track by 0-based index. */
4357
+ trackByIndex(index) {
4358
+ return this.native.trackByIndex(index);
4359
+ }
4360
+ /** Read a stored project clip by 0-based index. */
4361
+ clipByIndex(index) {
4362
+ return this.native.clipByIndex(index);
4363
+ }
4364
+ /** Read a stored project source by 0-based index. */
4365
+ sourceByIndex(index) {
4366
+ return this.native.sourceByIndex(index);
4367
+ }
3949
4368
  /** Number of markers in the project. */
3950
4369
  markerCount() {
3951
4370
  return this.native.markerCount();
@@ -4019,6 +4438,7 @@ var MarkerKind = {
4019
4438
  cuePoint: 3,
4020
4439
  keySignature: 4
4021
4440
  };
4441
+ var BUILTIN_SYNTH_WAVEFORMS = ["sine", "saw", "sawtooth", "square", "triangle"];
4022
4442
  var SYNTH_ENGINE_MODES = [
4023
4443
  "default",
4024
4444
  "subtractive",
@@ -4081,82 +4501,6 @@ var SYNTH_MOD_DESTINATIONS = [
4081
4501
  "pan-units"
4082
4502
  ];
4083
4503
 
4084
- // src/codes.ts
4085
- function automationCurveCode(curve) {
4086
- switch (curve) {
4087
- case "linear":
4088
- return 0;
4089
- case "exponential":
4090
- return 1;
4091
- case "hold":
4092
- return 2;
4093
- case "s-curve":
4094
- return 3;
4095
- default:
4096
- throw new Error(`Invalid automation curve: ${curve}`);
4097
- }
4098
- }
4099
- function panLawCode(panLaw) {
4100
- if (typeof panLaw === "number") {
4101
- return panLaw;
4102
- }
4103
- switch (panLaw) {
4104
- case "const3dB":
4105
- return 0;
4106
- case "const4.5dB":
4107
- return 1;
4108
- case "const6dB":
4109
- return 2;
4110
- case "linear0dB":
4111
- return 3;
4112
- default:
4113
- throw new Error(`Invalid pan law: ${panLaw}`);
4114
- }
4115
- }
4116
- function panModeCode(panMode) {
4117
- if (typeof panMode === "number") {
4118
- return panMode;
4119
- }
4120
- switch (panMode) {
4121
- case "balance":
4122
- return 0;
4123
- case "stereoPan":
4124
- case "stereo-pan":
4125
- return 1;
4126
- case "dualPan":
4127
- case "dual-pan":
4128
- return 2;
4129
- default:
4130
- throw new Error(`Invalid pan mode: ${panMode}`);
4131
- }
4132
- }
4133
- function meterTapCode(tap) {
4134
- if (typeof tap === "number") {
4135
- return tap;
4136
- }
4137
- switch (tap) {
4138
- case "preFader":
4139
- return 0;
4140
- case "postFader":
4141
- return 1;
4142
- default:
4143
- throw new Error(`Invalid meter tap: ${tap}`);
4144
- }
4145
- }
4146
- function sendTimingCode(timing) {
4147
- if (typeof timing === "number") {
4148
- return timing;
4149
- }
4150
- switch (timing) {
4151
- case "postFader":
4152
- return 0;
4153
- case "preFader":
4154
- return 1;
4155
- default:
4156
- throw new Error(`Invalid send timing: ${timing}`);
4157
- }
4158
- }
4159
-
4160
4504
  // src/realtime_engine.ts
4161
4505
  var EXPECTED_ENGINE_ABI_VERSION = 3;
4162
4506
  function engineCapabilities() {
@@ -4175,23 +4519,30 @@ function engineCapabilities() {
4175
4519
  };
4176
4520
  }
4177
4521
  var RealtimeEngine = class {
4178
- constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
4522
+ constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
4179
4523
  const module2 = getSonareModule();
4180
- const capabilities = engineCapabilities();
4181
- if (!capabilities.abiCompatible) {
4524
+ const capabilities2 = engineCapabilities();
4525
+ if (!capabilities2.abiCompatible) {
4182
4526
  throw new Error(
4183
- `Engine ABI mismatch: wasm=${capabilities.engineAbiVersion}, expected=${capabilities.expectedEngineAbiVersion}`
4527
+ `Engine ABI mismatch: wasm=${capabilities2.engineAbiVersion}, expected=${capabilities2.expectedEngineAbiVersion}`
4184
4528
  );
4185
4529
  }
4186
4530
  this.native = new module2.RealtimeEngine(
4187
4531
  sampleRate,
4188
4532
  maxBlockSize,
4189
4533
  commandCapacity,
4190
- telemetryCapacity
4534
+ telemetryCapacity,
4535
+ maxChannels
4191
4536
  );
4192
4537
  }
4193
- prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024) {
4194
- this.native.prepare(sampleRate, maxBlockSize, commandCapacity, telemetryCapacity);
4538
+ prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
4539
+ this.native.prepareWithChannels(
4540
+ sampleRate,
4541
+ maxBlockSize,
4542
+ commandCapacity,
4543
+ telemetryCapacity,
4544
+ maxChannels
4545
+ );
4195
4546
  }
4196
4547
  /** Queue a sample-accurate parameter change (engine kSetParam). */
4197
4548
  setParameter(paramId, value, renderFrame = -1) {
@@ -4317,6 +4668,9 @@ var RealtimeEngine = class {
4317
4668
  externalMidiDroppedCount() {
4318
4669
  return this.native.externalMidiDroppedCount();
4319
4670
  }
4671
+ externalMidiPendingCount() {
4672
+ return this.native.externalMidiPendingCount();
4673
+ }
4320
4674
  /**
4321
4675
  * Drain queued external-MIDI events, already lowered to MIDI 1.0 byte
4322
4676
  * messages ready to write to a Web MIDI output port. Call once per audio
@@ -4327,6 +4681,25 @@ var RealtimeEngine = class {
4327
4681
  drainExternalMidi(maxRecords = 1024) {
4328
4682
  return this.native.drainExternalMidi(maxRecords);
4329
4683
  }
4684
+ /** Scalar, allocation-free external-MIDI drain for AudioWorklet SAB output. */
4685
+ popExternalMidiToScratch() {
4686
+ return this.native.popExternalMidiToScratch();
4687
+ }
4688
+ externalMidiScratchDestinationId() {
4689
+ return this.native.externalMidiScratchDestinationId();
4690
+ }
4691
+ externalMidiScratchRenderFrame() {
4692
+ return this.native.externalMidiScratchRenderFrame();
4693
+ }
4694
+ externalMidiScratchByteWord() {
4695
+ return this.native.externalMidiScratchByteWord();
4696
+ }
4697
+ externalMidiScratchByteCount() {
4698
+ return this.native.externalMidiScratchByteCount();
4699
+ }
4700
+ consumeExternalMidiScratch() {
4701
+ this.native.consumeExternalMidiScratch();
4702
+ }
4330
4703
  pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
4331
4704
  this.native.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
4332
4705
  }
@@ -4351,6 +4724,10 @@ var RealtimeEngine = class {
4351
4724
  pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
4352
4725
  this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
4353
4726
  }
4727
+ /** Queue one immediate MIDI 1.0 channel-voice UMP word for a destination. */
4728
+ pushMidiUmp(destinationId, word0, renderFrame = -1) {
4729
+ this.native.pushMidiUmp(destinationId, word0, renderFrame);
4730
+ }
4354
4731
  /**
4355
4732
  * Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
4356
4733
  * the full message including the leading 0xF0 and trailing 0xF7 (1..512
@@ -4396,6 +4773,10 @@ var RealtimeEngine = class {
4396
4773
  settleParameters() {
4397
4774
  this.native.settleParameters();
4398
4775
  }
4776
+ /** Drains queued commands on an offline/control-only engine immediately. */
4777
+ flushControlCommands() {
4778
+ this.native.flushControlCommands();
4779
+ }
4399
4780
  seekPpq(ppq, renderFrame = -1) {
4400
4781
  this.native.seekPpq(ppq, renderFrame);
4401
4782
  }
@@ -4650,6 +5031,23 @@ var RealtimeEngine = class {
4650
5031
  popClipPageRequest() {
4651
5032
  return this.native.popClipPageRequest();
4652
5033
  }
5034
+ /**
5035
+ * Moves one native request into the binding's persistent scalar scratch.
5036
+ * This avoids creating an embind JS object in AudioWorklet process().
5037
+ */
5038
+ popClipPageRequestToScratch() {
5039
+ return this.native.popClipPageRequestToScratch();
5040
+ }
5041
+ clipPageRequestScratchClipId() {
5042
+ return this.native.clipPageRequestScratchClipId();
5043
+ }
5044
+ clipPageRequestScratchSample() {
5045
+ return this.native.clipPageRequestScratchSample();
5046
+ }
5047
+ /** Cumulative page misses dropped because the native bounded request queue was full. */
5048
+ clipPageRequestOverflowCount() {
5049
+ return this.native.clipPageRequestOverflowCount();
5050
+ }
4653
5051
  setCaptureBuffer(numChannels, capacityFrames) {
4654
5052
  this.native.setCaptureBuffer(numChannels, capacityFrames);
4655
5053
  }
@@ -4678,6 +5076,10 @@ var RealtimeEngine = class {
4678
5076
  capturedAudio() {
4679
5077
  return this.native.capturedAudio();
4680
5078
  }
5079
+ /**
5080
+ * Renders in place, adding engine output to `channels`. Zero each plane first
5081
+ * when it contains no upstream input.
5082
+ */
4681
5083
  process(channels) {
4682
5084
  return this.native.process(channels);
4683
5085
  }
@@ -4700,6 +5102,7 @@ var RealtimeEngine = class {
4700
5102
  }
4701
5103
  /**
4702
5104
  * Runs the engine in place over the prepared per-channel scratch buffers.
5105
+ * Zero each active span first when it contains no upstream input.
4703
5106
  * Allocation-free: safe to call on the AudioWorklet render thread after
4704
5107
  * `prepareChannels`.
4705
5108
  */
@@ -4721,6 +5124,42 @@ var RealtimeEngine = class {
4721
5124
  drainTelemetry(maxRecords = 1024) {
4722
5125
  return this.native.drainTelemetry(maxRecords);
4723
5126
  }
5127
+ popTelemetryToScratch() {
5128
+ return this.native.popTelemetryToScratch();
5129
+ }
5130
+ telemetryScratchType() {
5131
+ return this.native.telemetryScratchType();
5132
+ }
5133
+ telemetryScratchError() {
5134
+ return this.native.telemetryScratchError();
5135
+ }
5136
+ telemetryScratchRenderFrame() {
5137
+ return Number(this.native.telemetryScratchRenderFrame());
5138
+ }
5139
+ telemetryScratchTimelineSample() {
5140
+ return Number(this.native.telemetryScratchTimelineSample());
5141
+ }
5142
+ telemetryScratchAudibleTimelineSample() {
5143
+ return Number(this.native.telemetryScratchAudibleTimelineSample());
5144
+ }
5145
+ telemetryScratchGraphLatencySamplesQ8() {
5146
+ return this.native.telemetryScratchGraphLatencySamplesQ8();
5147
+ }
5148
+ telemetryScratchValue() {
5149
+ return this.native.telemetryScratchValue();
5150
+ }
5151
+ popMeterTelemetryToScratch() {
5152
+ return this.native.popMeterTelemetryToScratch();
5153
+ }
5154
+ meterScratchTargetId() {
5155
+ return this.native.meterScratchTargetId();
5156
+ }
5157
+ meterScratchRenderFrame() {
5158
+ return Number(this.native.meterScratchRenderFrame());
5159
+ }
5160
+ meterScratchValue(field) {
5161
+ return this.native.meterScratchValue(field);
5162
+ }
4724
5163
  drainMeterTelemetry(maxRecords = 1024) {
4725
5164
  return this.native.drainMeterTelemetry(maxRecords);
4726
5165
  }
@@ -4748,6 +5187,30 @@ var RealtimeEngine = class {
4748
5187
  drainScopeTelemetry(maxRecords = 1024) {
4749
5188
  return this.native.drainScopeTelemetry(maxRecords);
4750
5189
  }
5190
+ popScopeTelemetryToScratch() {
5191
+ return this.native.popScopeTelemetryToScratch();
5192
+ }
5193
+ scopeScratchTargetId() {
5194
+ return this.native.scopeScratchTargetId();
5195
+ }
5196
+ scopeScratchRenderFrame() {
5197
+ return Number(this.native.scopeScratchRenderFrame());
5198
+ }
5199
+ scopeScratchBandCount() {
5200
+ return this.native.scopeScratchBandCount();
5201
+ }
5202
+ scopeScratchBand(index) {
5203
+ return this.native.scopeScratchBand(index);
5204
+ }
5205
+ scopeScratchPointCount() {
5206
+ return this.native.scopeScratchPointCount();
5207
+ }
5208
+ scopeScratchPointLeft(index) {
5209
+ return this.native.scopeScratchPointLeft(index);
5210
+ }
5211
+ scopeScratchPointRight(index) {
5212
+ return this.native.scopeScratchPointRight(index);
5213
+ }
4751
5214
  destroy() {
4752
5215
  this.native.delete();
4753
5216
  }
@@ -5449,65 +5912,18 @@ var Mixer = class _Mixer {
5449
5912
  };
5450
5913
 
5451
5914
  // src/realtime_voice_changer.ts
5452
- function isFlatVoiceChangerPod(config) {
5453
- return typeof config === "object" && config !== null && "retuneSemitones" in config;
5454
- }
5455
- function flatVoiceChangerPodToNested(pod) {
5456
- return {
5457
- inputGainDb: pod.inputGainDb,
5458
- outputGainDb: pod.outputGainDb,
5459
- wetMix: pod.wetMix,
5460
- retune: { semitones: pod.retuneSemitones, mix: pod.retuneMix, grainSize: pod.retuneGrainSize },
5461
- formant: {
5462
- factor: pod.formantFactor,
5463
- amount: pod.formantAmount,
5464
- body: pod.formantBody,
5465
- brightness: pod.formantBrightness,
5466
- nasal: pod.formantNasal
5467
- },
5468
- eq: {
5469
- highpassHz: pod.eqHighpassHz,
5470
- bodyDb: pod.eqBodyDb,
5471
- presenceDb: pod.eqPresenceDb,
5472
- airDb: pod.eqAirDb
5473
- },
5474
- gate: {
5475
- thresholdDb: pod.gateThresholdDb,
5476
- attackMs: pod.gateAttackMs,
5477
- releaseMs: pod.gateReleaseMs,
5478
- rangeDb: pod.gateRangeDb
5479
- },
5480
- compressor: {
5481
- thresholdDb: pod.compressorThresholdDb,
5482
- ratio: pod.compressorRatio,
5483
- attackMs: pod.compressorAttackMs,
5484
- releaseMs: pod.compressorReleaseMs,
5485
- makeupGainDb: pod.compressorMakeupGainDb
5486
- },
5487
- deesser: {
5488
- frequencyHz: pod.deesserFrequencyHz,
5489
- thresholdDb: pod.deesserThresholdDb,
5490
- ratio: pod.deesserRatio,
5491
- rangeDb: pod.deesserRangeDb
5492
- },
5493
- reverb: {
5494
- mix: pod.reverbMix,
5495
- timeMs: pod.reverbTimeMs,
5496
- damping: pod.reverbDamping,
5497
- seed: pod.reverbSeed
5498
- },
5499
- limiter: {
5500
- ceilingDb: pod.limiterCeilingDb,
5501
- releaseMs: pod.limiterReleaseMs,
5502
- enableIspLimiter: pod.limiterEnableIspLimiter,
5503
- ispCeilingDbtp: pod.limiterIspCeilingDbtp
5504
- }
5505
- };
5506
- }
5507
5915
  var RealtimeVoiceChanger = class {
5508
- constructor(config = "neutral-monitor") {
5916
+ /**
5917
+ * Creates a voice changer. Supplying `sampleRate` prepares it immediately,
5918
+ * matching the Node and Python constructors; omitting it preserves the
5919
+ * explicit {@link prepare} lifecycle for callers that configure later.
5920
+ */
5921
+ constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
5509
5922
  const module2 = getSonareModule();
5510
5923
  this.changer = module2.createRealtimeVoiceChanger(config);
5924
+ if (sampleRate !== void 0) {
5925
+ this.changer.prepare(sampleRate, maxBlockSize, channels);
5926
+ }
5511
5927
  }
5512
5928
  prepare(sampleRate, maxBlockSize = 128, channels = 1) {
5513
5929
  this.changer.prepare(sampleRate, maxBlockSize, channels);
@@ -5516,8 +5932,15 @@ var RealtimeVoiceChanger = class {
5516
5932
  this.changer.reset();
5517
5933
  }
5518
5934
  setConfig(config) {
5519
- const resolved = isFlatVoiceChangerPod(config) ? flatVoiceChangerPodToNested(config) : config;
5520
- this.changer.setConfig(resolved);
5935
+ this.changer.setConfig(config);
5936
+ }
5937
+ /**
5938
+ * Apply a flat, pre-normalized config without JSON serialization. Intended
5939
+ * for AudioWorklet control messages whose sender prepared the POD on the
5940
+ * main thread.
5941
+ */
5942
+ setPodConfig(config) {
5943
+ this.changer.setPodConfig(config);
5521
5944
  }
5522
5945
  configJson() {
5523
5946
  return this.changer.configJson();
@@ -5525,6 +5948,13 @@ var RealtimeVoiceChanger = class {
5525
5948
  latencySamples() {
5526
5949
  return this.changer.latencySamples();
5527
5950
  }
5951
+ /**
5952
+ * Monotonically increases whenever {@link prepare} can replace the native
5953
+ * scratch buffers. Cached WASM heap views must be reacquired after it changes.
5954
+ */
5955
+ bufferGeneration() {
5956
+ return this.changer.bufferGeneration();
5957
+ }
5528
5958
  processMono(samples) {
5529
5959
  return this.changer.processMono(samples);
5530
5960
  }
@@ -5601,10 +6031,12 @@ var RealtimeVoiceChanger = class {
5601
6031
  createRealtimeMonoBuffer(numSamples) {
5602
6032
  let input = this.getMonoInputBuffer(numSamples);
5603
6033
  let output = this.getMonoOutputBuffer(numSamples);
6034
+ let generation = this.bufferGeneration();
5604
6035
  const reacquireIfDetached = () => {
5605
- if (input.byteLength === 0 || output.byteLength === 0) {
6036
+ if (generation !== this.bufferGeneration() || input.byteLength === 0 || output.byteLength === 0) {
5606
6037
  input = this.getMonoInputBuffer(numSamples);
5607
6038
  output = this.getMonoOutputBuffer(numSamples);
6039
+ generation = this.bufferGeneration();
5608
6040
  }
5609
6041
  };
5610
6042
  return {
@@ -5626,10 +6058,12 @@ var RealtimeVoiceChanger = class {
5626
6058
  createRealtimeInterleavedBuffer(numFrames, numChannels) {
5627
6059
  let input = this.getInterleavedInputBuffer(numFrames, numChannels);
5628
6060
  let output = this.getInterleavedOutputBuffer(numFrames, numChannels);
6061
+ let generation = this.bufferGeneration();
5629
6062
  const reacquireIfDetached = () => {
5630
- if (input.byteLength === 0 || output.byteLength === 0) {
6063
+ if (generation !== this.bufferGeneration() || input.byteLength === 0 || output.byteLength === 0) {
5631
6064
  input = this.getInterleavedInputBuffer(numFrames, numChannels);
5632
6065
  output = this.getInterleavedOutputBuffer(numFrames, numChannels);
6066
+ generation = this.bufferGeneration();
5633
6067
  }
5634
6068
  };
5635
6069
  return {
@@ -5656,15 +6090,17 @@ var RealtimeVoiceChanger = class {
5656
6090
  */
5657
6091
  createRealtimePlanarBuffer(numFrames, numChannels) {
5658
6092
  let channels = [];
6093
+ let generation = this.bufferGeneration();
5659
6094
  const acquire = () => {
5660
6095
  channels = [];
5661
6096
  for (let ch = 0; ch < numChannels; ch++) {
5662
6097
  channels.push(this.getPlanarChannelBuffer(ch, numFrames));
5663
6098
  }
6099
+ generation = this.bufferGeneration();
5664
6100
  };
5665
6101
  acquire();
5666
6102
  const reacquireIfDetached = () => {
5667
- if ((channels[0]?.byteLength ?? 0) === 0) {
6103
+ if (generation !== this.bufferGeneration() || (channels[0]?.byteLength ?? 0) === 0) {
5668
6104
  acquire();
5669
6105
  }
5670
6106
  };
@@ -5740,6 +6176,19 @@ var StreamingMasteringChain = class {
5740
6176
  }
5741
6177
  return this.chain.processStereo(left, right);
5742
6178
  }
6179
+ /**
6180
+ * Emit delayed audio and finite processor tails after the final mono block.
6181
+ * Call until this returns an empty array. The initial `latencySamples()`
6182
+ * samples of the concatenated stream are delayed and should be discarded for
6183
+ * time-aligned output.
6184
+ */
6185
+ flushMono() {
6186
+ return this.chain.flushMono();
6187
+ }
6188
+ /** Stereo counterpart of {@link flushMono}. */
6189
+ flushStereo() {
6190
+ return this.chain.flushStereo();
6191
+ }
5743
6192
  /** Reset all processor state without rebuilding. */
5744
6193
  reset() {
5745
6194
  this.chain.reset();
@@ -6131,6 +6580,214 @@ function iterInputs(access) {
6131
6580
  return access.inputs instanceof Map ? access.inputs.entries() : access.inputs;
6132
6581
  }
6133
6582
 
6583
+ // src/worker_client.ts
6584
+ var OfflineWorkerTask = class {
6585
+ constructor(result, cancelRequest) {
6586
+ this.result = result;
6587
+ this.cancelRequest = cancelRequest;
6588
+ }
6589
+ cancel() {
6590
+ this.cancelRequest();
6591
+ }
6592
+ // biome-ignore lint/suspicious/noThenProperty: this intentionally implements PromiseLike so callers can await a task and cancel it.
6593
+ then(onfulfilled, onrejected) {
6594
+ return this.result.then(onfulfilled, onrejected);
6595
+ }
6596
+ catch(onrejected) {
6597
+ return this.result.catch(onrejected);
6598
+ }
6599
+ finally(onfinally) {
6600
+ return this.result.finally(onfinally);
6601
+ }
6602
+ };
6603
+ function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
6604
+ if (value instanceof Float32Array) {
6605
+ const samples = copy ? value.slice() : value;
6606
+ const buffer = samples.buffer;
6607
+ if (!(buffer instanceof ArrayBuffer)) {
6608
+ throw new TypeError(
6609
+ "OfflineWorkerClient only transfers Float32Array values backed by ArrayBuffer"
6610
+ );
6611
+ }
6612
+ if (!transferred.has(buffer)) {
6613
+ transferred.add(buffer);
6614
+ transfers.push(buffer);
6615
+ }
6616
+ return samples;
6617
+ }
6618
+ if (Array.isArray(value)) {
6619
+ return value.map((item) => cloneForWorker(item, copy, transfers, transferred));
6620
+ }
6621
+ if (value !== null && typeof value === "object") {
6622
+ return Object.fromEntries(
6623
+ Object.entries(value).map(([key, item]) => [
6624
+ key,
6625
+ cloneForWorker(item, copy, transfers, transferred)
6626
+ ])
6627
+ );
6628
+ }
6629
+ return value;
6630
+ }
6631
+ function cancellationFlag() {
6632
+ if (typeof SharedArrayBuffer === "undefined") {
6633
+ return void 0;
6634
+ }
6635
+ return new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
6636
+ }
6637
+ function workerError(message) {
6638
+ if (message.error.code !== void 0) {
6639
+ return new SonareError(
6640
+ message.error.code,
6641
+ message.error.codeName ?? "Unknown",
6642
+ message.error.message
6643
+ );
6644
+ }
6645
+ const error = new Error(message.error.message);
6646
+ error.name = message.error.name;
6647
+ return error;
6648
+ }
6649
+ var OfflineWorkerClient = class {
6650
+ constructor(options = {}) {
6651
+ this.pending = /* @__PURE__ */ new Map();
6652
+ this.nextId = 1;
6653
+ this.closed = false;
6654
+ this.usesEventTarget = false;
6655
+ this.onMessage = (event) => {
6656
+ const message = event.data;
6657
+ const pending = this.pending.get(message.id);
6658
+ if (!pending) {
6659
+ return;
6660
+ }
6661
+ if (message.type === "sonare:offline-progress") {
6662
+ pending.onProgress?.({ progress: message.progress, stage: message.stage });
6663
+ return;
6664
+ }
6665
+ this.pending.delete(message.id);
6666
+ if (message.type === "sonare:offline-result") {
6667
+ pending.resolve(message.result);
6668
+ return;
6669
+ }
6670
+ pending.reject(workerError(message));
6671
+ };
6672
+ this.onError = (event) => {
6673
+ const error = new Error(event.message || "Offline Worker failed");
6674
+ for (const { reject } of this.pending.values()) {
6675
+ reject(error);
6676
+ }
6677
+ this.pending.clear();
6678
+ };
6679
+ this.onNodeMessage = (data) => {
6680
+ this.onMessage({ data });
6681
+ };
6682
+ this.onNodeError = (error) => {
6683
+ this.onError(
6684
+ error instanceof Error ? { message: error.message } : { message: String(error) }
6685
+ );
6686
+ };
6687
+ this.ownsWorker = options.worker === void 0 || options.terminateWorkerOnDispose === true;
6688
+ if (options.worker) {
6689
+ this.worker = options.worker;
6690
+ } else {
6691
+ if (!options.workerFactory && typeof Worker === "undefined") {
6692
+ throw new Error("OfflineWorkerClient requires a browser Worker implementation");
6693
+ }
6694
+ const url = options.workerUrl === void 0 ? new URL("./worker.js", import.meta.url) : new URL(options.workerUrl, import.meta.url);
6695
+ this.worker = options.workerFactory?.(url) ?? new Worker(url, { type: "module", name: "sonare-offline" });
6696
+ }
6697
+ if (this.worker.addEventListener) {
6698
+ this.usesEventTarget = true;
6699
+ this.worker.addEventListener("message", this.onMessage);
6700
+ this.worker.addEventListener("error", this.onError);
6701
+ } else if (this.worker.on) {
6702
+ this.worker.on("message", this.onNodeMessage);
6703
+ this.worker.on("error", this.onNodeError);
6704
+ } else {
6705
+ throw new TypeError("OfflineWorkerClient requires Worker event listeners");
6706
+ }
6707
+ }
6708
+ /** Dispatch full music analysis to the Worker. */
6709
+ analyze(request, options) {
6710
+ return this.call("analyze", request, options);
6711
+ }
6712
+ /** Dispatch BPM detection to the Worker. */
6713
+ detectBpm(request, options) {
6714
+ return this.call("detectBpm", request, options);
6715
+ }
6716
+ /** Dispatch key detection to the Worker. */
6717
+ detectKey(request, options) {
6718
+ return this.call("detectKey", request, options);
6719
+ }
6720
+ /** Dispatch chord detection to the Worker. */
6721
+ detectChords(request, options) {
6722
+ return this.call("detectChords", request, options);
6723
+ }
6724
+ /** Dispatch mono preset mastering to the Worker. */
6725
+ masterAudio(request, options) {
6726
+ return this.call("masterAudio", request, options);
6727
+ }
6728
+ /** Dispatch stereo preset mastering to the Worker. */
6729
+ masterAudioStereo(request, options) {
6730
+ return this.call("masterAudioStereo", request, options);
6731
+ }
6732
+ /** Stop accepting calls, reject outstanding work, and release the Worker if owned. */
6733
+ dispose() {
6734
+ if (this.closed) {
6735
+ return;
6736
+ }
6737
+ this.closed = true;
6738
+ if (this.usesEventTarget) {
6739
+ this.worker.removeEventListener?.("message", this.onMessage);
6740
+ this.worker.removeEventListener?.("error", this.onError);
6741
+ } else {
6742
+ this.worker.off?.("message", this.onNodeMessage);
6743
+ this.worker.off?.("error", this.onNodeError);
6744
+ }
6745
+ for (const { reject } of this.pending.values()) {
6746
+ reject(new Error("OfflineWorkerClient was disposed"));
6747
+ }
6748
+ this.pending.clear();
6749
+ if (this.ownsWorker) {
6750
+ this.worker.terminate();
6751
+ }
6752
+ }
6753
+ call(operation, request, options = {}) {
6754
+ if (this.closed) {
6755
+ throw new Error("OfflineWorkerClient was disposed");
6756
+ }
6757
+ const id = this.nextId++;
6758
+ const transfers = [];
6759
+ const preparedRequest = cloneForWorker(request, options.copy === true, transfers);
6760
+ const cancelFlag = cancellationFlag();
6761
+ const result = new Promise((resolve, reject) => {
6762
+ this.pending.set(id, {
6763
+ resolve: (value) => resolve(value),
6764
+ reject,
6765
+ onProgress: options.onProgress,
6766
+ cancelFlag
6767
+ });
6768
+ });
6769
+ this.worker.postMessage(
6770
+ {
6771
+ type: "sonare:offline-run",
6772
+ id,
6773
+ operation,
6774
+ request: preparedRequest,
6775
+ ...cancelFlag ? { cancelBuffer: cancelFlag.buffer } : {}
6776
+ },
6777
+ transfers
6778
+ );
6779
+ return new OfflineWorkerTask(result, () => {
6780
+ if (!this.pending.has(id)) {
6781
+ return;
6782
+ }
6783
+ if (cancelFlag) {
6784
+ Atomics.store(cancelFlag, 0, 1);
6785
+ }
6786
+ this.worker.postMessage({ type: "sonare:offline-cancel", id });
6787
+ });
6788
+ }
6789
+ };
6790
+
6134
6791
  // src/index.ts
6135
6792
  var module = null;
6136
6793
  var initPromise = null;
@@ -6162,6 +6819,18 @@ function version() {
6162
6819
  }
6163
6820
  return module.version();
6164
6821
  }
6822
+ function capabilities() {
6823
+ if (!module) {
6824
+ throw new Error("Module not initialized. Call init() first.");
6825
+ }
6826
+ return module.capabilities();
6827
+ }
6828
+ function capabilityCatalog() {
6829
+ if (!module) {
6830
+ throw new Error("Module not initialized. Call init() first.");
6831
+ }
6832
+ return JSON.parse(module.capabilityCatalog());
6833
+ }
6165
6834
  function abiVersion() {
6166
6835
  if (!module) {
6167
6836
  throw new Error("Module not initialized. Call init() first.");
@@ -6190,6 +6859,9 @@ var VOICE_PRESET_ORDINALS = [
6190
6859
  ];
6191
6860
  function resolveVoicePresetOrdinal(preset) {
6192
6861
  if (typeof preset === "number") {
6862
+ if (!Number.isSafeInteger(preset) || preset < 0 || preset >= VOICE_PRESET_ORDINALS.length) {
6863
+ throw new RangeError(`Unknown voice-character preset ordinal: ${String(preset)}`);
6864
+ }
6193
6865
  return preset;
6194
6866
  }
6195
6867
  const ordinal = VOICE_PRESET_ORDINALS.indexOf(preset);
@@ -6212,7 +6884,9 @@ function realtimeVoiceChangerPresetConfig(preset) {
6212
6884
  }
6213
6885
  export {
6214
6886
  Audio,
6887
+ BUILTIN_SYNTH_WAVEFORMS,
6215
6888
  ChordQuality,
6889
+ ClipPageProvider,
6216
6890
  ClipPageStreamer,
6217
6891
  EXPECTED_ENGINE_ABI_VERSION,
6218
6892
  EXPECTED_PROJECT_ABI_VERSION,
@@ -6221,6 +6895,8 @@ export {
6221
6895
  MarkerKind,
6222
6896
  Mixer,
6223
6897
  Mode,
6898
+ OfflineWorkerClient,
6899
+ OfflineWorkerTask,
6224
6900
  PitchClass as Pitch,
6225
6901
  PitchClass,
6226
6902
  Project,
@@ -6254,10 +6930,14 @@ export {
6254
6930
  bassChroma,
6255
6931
  bindMicrophoneInput,
6256
6932
  bindWebMidi,
6933
+ capabilities,
6934
+ capabilityCatalog,
6935
+ chirp,
6257
6936
  chordFunctionalAnalysis,
6258
6937
  chroma,
6259
6938
  chromaCens,
6260
6939
  chromaCqt,
6940
+ clicks,
6261
6941
  cqt,
6262
6942
  cqtToAudio,
6263
6943
  createOpfsClipPageProvider,
@@ -6287,6 +6967,7 @@ export {
6287
6967
  frameSignal,
6288
6968
  framesToSamples,
6289
6969
  framesToTime,
6970
+ griffinLim,
6290
6971
  harmonic,
6291
6972
  hasFfmpegSupport,
6292
6973
  hpss,
@@ -6337,6 +7018,7 @@ export {
6337
7018
  masteringStereoAnalysisNames,
6338
7019
  masteringStereoAnalyze,
6339
7020
  masteringStreamingPreview,
7021
+ melDelta,
6340
7022
  melSpectrogram,
6341
7023
  melToAudio,
6342
7024
  melToHz,
@@ -6369,8 +7051,10 @@ export {
6369
7051
  nnlsChroma,
6370
7052
  normalize,
6371
7053
  noteMove,
7054
+ noteSegments,
6372
7055
  noteStretch,
6373
7056
  noteToHz,
7057
+ onsetBacktrack,
6374
7058
  onsetEnvelope,
6375
7059
  onsetStrengthMulti,
6376
7060
  opfsClipPageWorkerSource,
@@ -6379,6 +7063,7 @@ export {
6379
7063
  peakPick,
6380
7064
  percussive,
6381
7065
  phaseVocoder,
7066
+ piptrack,
6382
7067
  pitchCorrectTimevarying,
6383
7068
  pitchCorrectToMidi,
6384
7069
  pitchCorrectToMidiTimevarying,
@@ -6395,6 +7080,7 @@ export {
6395
7080
  realtimeVoiceChangerPresetConfig,
6396
7081
  realtimeVoiceChangerPresetJson,
6397
7082
  realtimeVoiceChangerPresetNames,
7083
+ reassignedSpectrogram,
6398
7084
  remix,
6399
7085
  resample,
6400
7086
  rmsEnergy,
@@ -6403,12 +7089,20 @@ export {
6403
7089
  scaleCorrectionSemitones,
6404
7090
  scalePitchClassEnabled,
6405
7091
  scaleQuantizeMidi,
7092
+ segmentAgglomerative,
7093
+ segmentCrossSimilarity,
7094
+ segmentLagToRecurrence,
7095
+ segmentPathEnhance,
7096
+ segmentRecurrenceMatrix,
7097
+ segmentRecurrenceToLag,
7098
+ segmentSubsegment,
6406
7099
  shortTermLufs,
6407
7100
  spectralBandwidth,
6408
7101
  spectralCentroid,
6409
7102
  spectralContrast,
6410
7103
  spectralEdit,
6411
7104
  spectralFlatness,
7105
+ spectralFlux,
6412
7106
  spectralRolloff,
6413
7107
  splitSilence,
6414
7108
  stft,
@@ -6422,6 +7116,7 @@ export {
6422
7116
  tempogramRatio,
6423
7117
  timeStretch,
6424
7118
  timeToFrames,
7119
+ tone,
6425
7120
  tonnetz,
6426
7121
  trim,
6427
7122
  trimSilence,