@libraz/libsonare 1.8.0 → 1.8.2

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 (138) hide show
  1. package/README.md +5 -5
  2. package/dist/_effects_common.d.ts +2 -1
  3. package/dist/_effects_common.d.ts.map +1 -1
  4. package/dist/analysis.js +3 -0
  5. package/dist/analysis.js.map +1 -1
  6. package/dist/effects_mastering.d.ts +2 -2
  7. package/dist/effects_mastering.d.ts.map +1 -1
  8. package/dist/effects_note_ops.d.ts +55 -30
  9. package/dist/effects_note_ops.d.ts.map +1 -1
  10. package/dist/effects_timepitch.d.ts +17 -12
  11. package/dist/effects_timepitch.d.ts.map +1 -1
  12. package/dist/feature_decompose.d.ts +1 -1
  13. package/dist/feature_decompose.d.ts.map +1 -1
  14. package/dist/index.d.ts +11 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2098 -155
  17. package/dist/index.js.map +1 -1
  18. package/dist/instrument_types.d.ts +27 -3
  19. package/dist/instrument_types.d.ts.map +1 -1
  20. package/dist/mastering_core.d.ts +30 -3
  21. package/dist/mastering_core.d.ts.map +1 -1
  22. package/dist/mixer.d.ts +2 -2
  23. package/dist/mixer.d.ts.map +1 -1
  24. package/dist/project.d.ts +5 -4
  25. package/dist/project.d.ts.map +1 -1
  26. package/dist/project_class.d.ts +57 -3
  27. package/dist/project_class.d.ts.map +1 -1
  28. package/dist/project_internal.d.ts +35 -1
  29. package/dist/project_internal.d.ts.map +1 -1
  30. package/dist/project_types.d.ts +17 -6
  31. package/dist/project_types.d.ts.map +1 -1
  32. package/dist/public_types.d.ts +1 -0
  33. package/dist/public_types.d.ts.map +1 -1
  34. package/dist/public_types_mastering.d.ts +19 -1
  35. package/dist/public_types_mastering.d.ts.map +1 -1
  36. package/dist/public_types_playback.d.ts +1 -0
  37. package/dist/public_types_playback.d.ts.map +1 -1
  38. package/dist/public_types_spectral.d.ts +5 -5
  39. package/dist/public_types_spectral.d.ts.map +1 -1
  40. package/dist/public_types_vocal_edit.d.ts +250 -0
  41. package/dist/public_types_vocal_edit.d.ts.map +1 -0
  42. package/dist/public_types_vocal_project.d.ts +63 -0
  43. package/dist/public_types_vocal_project.d.ts.map +1 -0
  44. package/dist/realtime_engine.d.ts +106 -16
  45. package/dist/realtime_engine.d.ts.map +1 -1
  46. package/dist/repair_dereverb.d.ts +1 -0
  47. package/dist/repair_dereverb.d.ts.map +1 -1
  48. package/dist/repair_noise.d.ts +1 -0
  49. package/dist/repair_noise.d.ts.map +1 -1
  50. package/dist/sonare-analysis.wasm +0 -0
  51. package/dist/sonare.d.ts +89 -0
  52. package/dist/sonare.js +1 -1
  53. package/dist/sonare.wasm +0 -0
  54. package/dist/streaming_processors.d.ts +5 -0
  55. package/dist/streaming_processors.d.ts.map +1 -1
  56. package/dist/vocal_edit.d.ts +131 -0
  57. package/dist/vocal_edit.d.ts.map +1 -0
  58. package/dist/vocal_edit_worker.d.ts +51 -0
  59. package/dist/vocal_edit_worker.d.ts.map +1 -0
  60. package/dist/vocal_edit_worker.js +1462 -0
  61. package/dist/vocal_edit_worker.js.map +1 -0
  62. package/dist/vocal_edit_worker_client.d.ts +118 -0
  63. package/dist/vocal_edit_worker_client.d.ts.map +1 -0
  64. package/dist/vocal_edit_worker_protocol.d.ts +139 -0
  65. package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
  66. package/dist/vocal_project.d.ts +17 -0
  67. package/dist/vocal_project.d.ts.map +1 -0
  68. package/dist/web_midi.d.ts +4 -0
  69. package/dist/web_midi.d.ts.map +1 -1
  70. package/dist/worker.js.map +1 -1
  71. package/dist/worklet/engine-mixer-facade.d.ts +42 -5
  72. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  73. package/dist/worklet/engine-node.d.ts +10 -0
  74. package/dist/worklet/engine-node.d.ts.map +1 -1
  75. package/dist/worklet/engine-processor.d.ts +6 -1
  76. package/dist/worklet/engine-processor.d.ts.map +1 -1
  77. package/dist/worklet/engine-register.d.ts.map +1 -1
  78. package/dist/worklet/engine-strips.d.ts +8 -4
  79. package/dist/worklet/engine-strips.d.ts.map +1 -1
  80. package/dist/worklet/engine-sync.d.ts +4 -2
  81. package/dist/worklet/engine-sync.d.ts.map +1 -1
  82. package/dist/worklet/engine.d.ts +42 -9
  83. package/dist/worklet/engine.d.ts.map +1 -1
  84. package/dist/worklet/guards.d.ts +10 -2
  85. package/dist/worklet/guards.d.ts.map +1 -1
  86. package/dist/worklet/messages.d.ts +54 -9
  87. package/dist/worklet/messages.d.ts.map +1 -1
  88. package/dist/worklet/mixer-processor.d.ts +2 -2
  89. package/dist/worklet/mixer-processor.d.ts.map +1 -1
  90. package/dist/worklet/protocol.d.ts +20 -2
  91. package/dist/worklet/protocol.d.ts.map +1 -1
  92. package/dist/worklet.d.ts +1 -1
  93. package/dist/worklet.d.ts.map +1 -1
  94. package/dist/worklet.js +808 -204
  95. package/dist/worklet.js.map +1 -1
  96. package/package.json +6 -1
  97. package/src/_effects_common.ts +36 -6
  98. package/src/effects_mastering.ts +4 -0
  99. package/src/effects_note_ops.ts +92 -44
  100. package/src/effects_timepitch.ts +42 -31
  101. package/src/feature_decompose.ts +2 -2
  102. package/src/index.ts +39 -0
  103. package/src/instrument_types.ts +29 -3
  104. package/src/mastering_core.ts +98 -2
  105. package/src/mixer.ts +41 -21
  106. package/src/project.ts +8 -1
  107. package/src/project_class.ts +136 -1
  108. package/src/project_internal.ts +90 -0
  109. package/src/project_types.ts +18 -6
  110. package/src/public_types.ts +1 -0
  111. package/src/public_types_mastering.ts +23 -0
  112. package/src/public_types_playback.ts +1 -0
  113. package/src/public_types_spectral.ts +5 -5
  114. package/src/public_types_vocal_edit.ts +289 -0
  115. package/src/public_types_vocal_project.ts +69 -0
  116. package/src/realtime_engine.ts +157 -24
  117. package/src/repair_dereverb.ts +1 -0
  118. package/src/repair_noise.ts +1 -0
  119. package/src/sonare.js.d.ts +89 -0
  120. package/src/streaming_processors.ts +8 -0
  121. package/src/vocal_edit.ts +885 -0
  122. package/src/vocal_edit_worker.ts +832 -0
  123. package/src/vocal_edit_worker_client.ts +812 -0
  124. package/src/vocal_edit_worker_protocol.ts +206 -0
  125. package/src/vocal_project.ts +327 -0
  126. package/src/web_midi.ts +100 -5
  127. package/src/worklet/engine-mixer-facade.ts +422 -87
  128. package/src/worklet/engine-node.ts +69 -4
  129. package/src/worklet/engine-processor.ts +129 -34
  130. package/src/worklet/engine-register.ts +3 -0
  131. package/src/worklet/engine-strips.ts +36 -14
  132. package/src/worklet/engine-sync.ts +6 -1
  133. package/src/worklet/engine.ts +76 -40
  134. package/src/worklet/guards.ts +81 -0
  135. package/src/worklet/messages.ts +63 -4
  136. package/src/worklet/mixer-processor.ts +10 -3
  137. package/src/worklet/protocol.ts +39 -8
  138. package/src/worklet.ts +2 -0
package/dist/index.js CHANGED
@@ -177,8 +177,8 @@ function wrapModuleErrors(raw) {
177
177
  }
178
178
  });
179
179
  }
180
- function setSonareModule(module2) {
181
- wrappedModule = wrapModuleErrors(module2);
180
+ function setSonareModule(module3) {
181
+ wrappedModule = wrapModuleErrors(module3);
182
182
  }
183
183
  function getSonareModule() {
184
184
  if (!wrappedModule) {
@@ -518,6 +518,19 @@ function assertInterleavedSamples(fnName, samples, channels, validate) {
518
518
  }
519
519
 
520
520
  // src/project_internal.ts
521
+ var projectTimelineHandles = /* @__PURE__ */ new WeakMap();
522
+ function registerProjectTimeline(owner, native) {
523
+ projectTimelineHandles.set(owner, native);
524
+ }
525
+ function unregisterProjectTimeline(owner) {
526
+ projectTimelineHandles.delete(owner);
527
+ }
528
+ function projectTimelineNativeId(owner) {
529
+ if (owner === null || typeof owner !== "object" && typeof owner !== "function") {
530
+ return void 0;
531
+ }
532
+ return projectTimelineHandles.get(owner)?.id;
533
+ }
521
534
  function normalizeSynthInstrument(patch) {
522
535
  if (patch === null || typeof patch !== "object") {
523
536
  return patch;
@@ -534,6 +547,35 @@ function normalizeSynthInstrument(patch) {
534
547
  }
535
548
  return { ...rest, sampleBankId: sampleBank.nativeId };
536
549
  }
550
+ function partRigInsertsJson(inserts) {
551
+ if (inserts === void 0 || inserts === null) {
552
+ return void 0;
553
+ }
554
+ if (!Array.isArray(inserts)) {
555
+ throw new TypeError("inserts must be an array");
556
+ }
557
+ return JSON.stringify(
558
+ inserts.map((insert) => {
559
+ const { processor, params } = insert;
560
+ return params === void 0 ? { processor } : { processor, params: typeof params === "string" ? params : JSON.stringify(params) };
561
+ })
562
+ );
563
+ }
564
+ function partRigFromNative(native) {
565
+ if (native === null) {
566
+ return null;
567
+ }
568
+ if (native.insertsJson === null) {
569
+ return { mode: native.mode };
570
+ }
571
+ const inserts = JSON.parse(native.insertsJson).map(
572
+ ({ processor, params }) => ({
573
+ processor,
574
+ params: JSON.parse(params)
575
+ })
576
+ );
577
+ return { mode: native.mode, inserts };
578
+ }
537
579
  function projectModule() {
538
580
  const candidate = getSonareModule();
539
581
  if (typeof candidate.projectAbiVersion !== "function" || candidate.Project === void 0) {
@@ -624,13 +666,30 @@ function alignTakeToReference(request) {
624
666
  }
625
667
 
626
668
  // src/_effects_common.ts
627
- function toVoicedFloat32(voiced) {
669
+ function toVoicedFloat32(fnName, voiced) {
670
+ if (!(voiced instanceof Int32Array || voiced instanceof Uint8Array || voiced instanceof Float32Array || Array.isArray(voiced))) {
671
+ throw new TypeError(
672
+ `${fnName}: voiced must be an Int32Array, Uint8Array, Float32Array, number[], or boolean[]`
673
+ );
674
+ }
628
675
  const out = new Float32Array(voiced.length);
629
676
  for (let index = 0; index < voiced.length; index += 1) {
630
- out[index] = voiced[index] ? 1 : 0;
677
+ const value = voiced[index];
678
+ if (typeof value !== "number" && typeof value !== "boolean") {
679
+ throw new TypeError(`${fnName}: voiced array entries must be numbers or booleans`);
680
+ }
681
+ out[index] = value ? 1 : 0;
631
682
  }
632
683
  return out;
633
684
  }
685
+ function assertPitchTrackLengths(fnName, f0Hz, voiced, voicedProb) {
686
+ if (voiced != null && voiced.length !== f0Hz.length) {
687
+ throw new RangeError(`${fnName}: voiced must have the same length as f0Hz`);
688
+ }
689
+ if (voiced == null && voicedProb != null && voicedProb.length !== f0Hz.length) {
690
+ throw new RangeError(`${fnName}: voicedProb must have the same length as f0Hz`);
691
+ }
692
+ }
634
693
 
635
694
  // src/effects_note_ops.ts
636
695
  function requireModule() {
@@ -639,13 +698,9 @@ function requireModule() {
639
698
  function assertNoteTrack(fnName, request) {
640
699
  assertSamples(fnName, request.samples, request.validate !== false);
641
700
  assertSampleRate(fnName, request.sampleRate);
642
- if (request.voiced && request.voiced.length !== request.f0Hz.length) {
643
- throw new RangeError(`${fnName}: voiced length must match f0Hz length`);
644
- }
645
- if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
646
- throw new RangeError(`${fnName}: voicedProb length must match f0Hz length`);
647
- }
648
- return request.voiced ? toVoicedFloat32(request.voiced) : void 0;
701
+ const voicedF32 = request.voiced == null ? void 0 : toVoicedFloat32(fnName, request.voiced);
702
+ assertPitchTrackLengths(fnName, request.f0Hz, request.voiced, request.voicedProb);
703
+ return voicedF32;
649
704
  }
650
705
  function noteStretch(samples, sampleRate = 22050, options = {}) {
651
706
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
@@ -675,20 +730,46 @@ function extractNotes(request) {
675
730
  request.samples,
676
731
  request.sampleRate,
677
732
  request.f0Hz,
678
- request.voicedProb,
733
+ request.voiced == null ? request.voicedProb ?? void 0 : void 0,
679
734
  voicedF32,
680
735
  request.frameRate,
681
736
  request
682
737
  );
683
738
  }
684
739
  function renderNotes(request) {
685
- assertSamples("renderNotes", request.samples, request.validate !== false);
686
740
  assertSampleRate("renderNotes", request.sampleRate);
687
- return requireModule().renderNotes(request.samples, request.sampleRate, request.notes, request);
741
+ if (!Array.isArray(request.notes)) {
742
+ throw new TypeError("renderNotes: notes must be an array");
743
+ }
744
+ const f0Hz = request.f0Hz;
745
+ if (f0Hz !== void 0 && !(f0Hz instanceof Float32Array)) {
746
+ throw new TypeError("renderNotes: f0Hz must be a Float32Array");
747
+ }
748
+ if (f0Hz !== void 0) {
749
+ assertFiniteScalar("renderNotes", request.frameRate, "frameRate");
750
+ }
751
+ const { voiced: publicVoiced, ...withoutVoiced } = request;
752
+ const voiced = publicVoiced == null ? void 0 : toVoicedFloat32("renderNotes", publicVoiced);
753
+ if (publicVoiced != null && f0Hz === void 0) {
754
+ throw new RangeError("renderNotes: voiced requires f0Hz");
755
+ }
756
+ if (f0Hz !== void 0) {
757
+ assertPitchTrackLengths("renderNotes", f0Hz, publicVoiced);
758
+ }
759
+ assertSamples("renderNotes", request.samples, request.validate !== false);
760
+ const options = { ...withoutVoiced, voiced };
761
+ return requireModule().renderNotes(request.samples, request.sampleRate, request.notes, options);
688
762
  }
689
763
  function decomposeNotePitch(request) {
764
+ if (!(request.f0Hz instanceof Float32Array)) {
765
+ throw new TypeError("decomposeNotePitch: f0Hz must be a Float32Array");
766
+ }
767
+ assertFiniteScalar("decomposeNotePitch", request.frameRate, "frameRate");
768
+ const voiced = request.voiced == null ? void 0 : toVoicedFloat32("decomposeNotePitch", request.voiced);
769
+ assertPitchTrackLengths("decomposeNotePitch", request.f0Hz, request.voiced);
690
770
  return requireModule().decomposeNotePitch(
691
771
  request.f0Hz,
772
+ voiced,
692
773
  request.frameRate,
693
774
  request.medianHz,
694
775
  request.vibratoCutoffHz ?? 0
@@ -700,7 +781,7 @@ function splitNote(request) {
700
781
  request.samples,
701
782
  request.sampleRate,
702
783
  request.f0Hz,
703
- request.voicedProb,
784
+ request.voiced == null ? request.voicedProb ?? void 0 : void 0,
704
785
  voicedF32,
705
786
  request.frameRate,
706
787
  request.notes,
@@ -715,7 +796,7 @@ function mergeNotes(request) {
715
796
  request.samples,
716
797
  request.sampleRate,
717
798
  request.f0Hz,
718
- request.voicedProb,
799
+ request.voiced == null ? request.voicedProb ?? void 0 : void 0,
719
800
  voicedF32,
720
801
  request.frameRate,
721
802
  request.notes,
@@ -725,11 +806,11 @@ function mergeNotes(request) {
725
806
  );
726
807
  }
727
808
  function noteTargetsFromSmf(request) {
728
- const module2 = requireModule();
729
- if (typeof module2.noteTargetsFromSmf !== "function") {
809
+ const module3 = requireModule();
810
+ if (typeof module3.noteTargetsFromSmf !== "function") {
730
811
  throw new Error("libsonare was built without arrangement support");
731
812
  }
732
- return module2.noteTargetsFromSmf(request.data, request.trackIndex ?? 0);
813
+ return module3.noteTargetsFromSmf(request.data, request.trackIndex ?? 0);
733
814
  }
734
815
  function assignNoteTargets(request) {
735
816
  return requireModule().assignNoteTargets(
@@ -912,13 +993,13 @@ function pitchCorrectToMidiTimevarying(samples, f0Hz, targetMidi, sampleRate = 2
912
993
  ...options
913
994
  } : samples;
914
995
  assertSamples("pitchCorrectToMidiTimevarying", request.samples, request.validate !== false);
915
- if (request.voiced && request.voiced.length !== request.f0Hz.length) {
916
- throw new RangeError("pitchCorrectToMidiTimevarying: voiced length must match f0Hz length");
917
- }
918
- if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
919
- throw new RangeError("pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length");
920
- }
921
- const voicedF32 = request.voiced ? toVoicedFloat32(request.voiced) : void 0;
996
+ const voicedF32 = request.voiced == null ? void 0 : toVoicedFloat32("pitchCorrectToMidiTimevarying", request.voiced);
997
+ assertPitchTrackLengths(
998
+ "pitchCorrectToMidiTimevarying",
999
+ request.f0Hz,
1000
+ request.voiced,
1001
+ request.voicedProb
1002
+ );
922
1003
  return requireModule5().pitchCorrectToMidiTimevarying(
923
1004
  request.samples,
924
1005
  request.sampleRate ?? 22050,
@@ -926,21 +1007,23 @@ function pitchCorrectToMidiTimevarying(samples, f0Hz, targetMidi, sampleRate = 2
926
1007
  request.targetMidi,
927
1008
  request.hopLength ?? 512,
928
1009
  voicedF32,
929
- request.voicedProb
1010
+ request.voiced == null ? request.voicedProb ?? void 0 : void 0
930
1011
  );
931
1012
  }
932
1013
  function pitchCorrectTimevarying(samples, f0Hz, sampleRate = 22050, hopLength = 512, options = {}) {
933
1014
  const request = samples instanceof Float32Array ? { samples, f0Hz, sampleRate, hopLength, ...options } : samples;
934
1015
  assertSamples("pitchCorrectTimevarying", request.samples, request.validate !== false);
935
- if (request.voiced && request.voiced.length !== request.f0Hz.length) {
936
- throw new RangeError("pitchCorrectTimevarying: voiced length must match f0Hz length");
937
- }
938
- if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
939
- throw new RangeError("pitchCorrectTimevarying: voicedProb length must match f0Hz length");
940
- }
1016
+ const voicedF32 = request.voiced == null ? void 0 : toVoicedFloat32("pitchCorrectTimevarying", request.voiced);
1017
+ assertPitchTrackLengths(
1018
+ "pitchCorrectTimevarying",
1019
+ request.f0Hz,
1020
+ request.voiced,
1021
+ request.voicedProb
1022
+ );
941
1023
  const nativeOptions = {
942
1024
  ...request,
943
- voiced: request.voiced ? toVoicedFloat32(request.voiced) : void 0
1025
+ voiced: voicedF32,
1026
+ voicedProb: request.voiced == null ? request.voicedProb ?? void 0 : void 0
944
1027
  };
945
1028
  return requireModule5().pitchCorrectTimevarying(
946
1029
  request.samples,
@@ -1369,6 +1452,32 @@ function masteringPairProcess(processorName, source, reference, sampleRate = 220
1369
1452
  request.params ?? {}
1370
1453
  );
1371
1454
  }
1455
+ function masteringPairProcessStereo(processorName, sourceLeft, sourceRight, referenceLeft, referenceRight, sampleRate = 22050, params = {}) {
1456
+ const request = typeof processorName === "string" ? {
1457
+ processorName,
1458
+ sourceLeft,
1459
+ sourceRight,
1460
+ referenceLeft,
1461
+ referenceRight,
1462
+ sampleRate,
1463
+ params
1464
+ } : processorName;
1465
+ if (request.sourceLeft.length !== request.sourceRight.length) {
1466
+ throw new Error("Source left and right channel lengths must match.");
1467
+ }
1468
+ if (request.referenceLeft.length !== request.referenceRight.length) {
1469
+ throw new Error("Reference left and right channel lengths must match.");
1470
+ }
1471
+ return requireModule8().masteringPairProcessStereo(
1472
+ request.processorName,
1473
+ request.sourceLeft,
1474
+ request.sourceRight,
1475
+ request.referenceLeft,
1476
+ request.referenceRight,
1477
+ request.sampleRate ?? 22050,
1478
+ request.params ?? {}
1479
+ );
1480
+ }
1372
1481
  function masteringPairAnalyze(analysisName, source, reference, sampleRate = 22050, params = {}) {
1373
1482
  const request = typeof analysisName === "string" ? {
1374
1483
  analysisName,
@@ -1392,6 +1501,21 @@ function masteringAbMatchLoudness(request) {
1392
1501
  request.sampleRate ?? 22050
1393
1502
  );
1394
1503
  }
1504
+ function masteringAbMatchLoudnessStereo(request) {
1505
+ if (request.sourceLeft.length !== request.sourceRight.length) {
1506
+ throw new Error("Source left and right channel lengths must match.");
1507
+ }
1508
+ if (request.referenceLeft.length !== request.referenceRight.length) {
1509
+ throw new Error("Reference left and right channel lengths must match.");
1510
+ }
1511
+ return requireModule8().masteringAbMatchLoudnessStereo(
1512
+ request.sourceLeft,
1513
+ request.sourceRight,
1514
+ request.referenceLeft,
1515
+ request.referenceRight,
1516
+ request.sampleRate ?? 22050
1517
+ );
1518
+ }
1395
1519
  function masteringStereoAnalyze(analysisName, left, right, sampleRate = 22050, params = {}) {
1396
1520
  const request = typeof analysisName === "string" ? {
1397
1521
  analysisName,
@@ -2029,7 +2153,7 @@ function decomposeWithInit(s, nFeatures = 0, nFrames = 0, nComponents = 0, nIter
2029
2153
  return requireModule16().decomposeWithInit(s, nFeatures, nFrames, nComponents, nIter, beta, init2);
2030
2154
  }
2031
2155
  function decomposeStems(request) {
2032
- return requireModule16().decomposeStems(request.samples, request.sampleRate, {
2156
+ return requireModule16().decomposeStems(request.samples, request.sampleRate ?? 22050, {
2033
2157
  nComponents: request.nComponents,
2034
2158
  nFft: request.nFft,
2035
2159
  hopLength: request.hopLength,
@@ -3923,29 +4047,29 @@ function detectAcoustic(samples, sampleRate = 48e3, options = {}) {
3923
4047
  return result;
3924
4048
  }
3925
4049
  function synthesizeRir(options = {}) {
3926
- const module2 = requireModule25();
3927
- if (typeof module2.synthesizeRir !== "function") {
4050
+ const module3 = requireModule25();
4051
+ if (typeof module3.synthesizeRir !== "function") {
3928
4052
  throw new Error("libsonare was built without acoustic-simulation support");
3929
4053
  }
3930
- return module2.synthesizeRir(options);
4054
+ return module3.synthesizeRir(options);
3931
4055
  }
3932
4056
  function estimateRoom(samples, sampleRate = 48e3, options = {}) {
3933
- const module2 = requireModule25();
3934
- if (typeof module2.estimateRoom !== "function") {
4057
+ const module3 = requireModule25();
4058
+ if (typeof module3.estimateRoom !== "function") {
3935
4059
  throw new Error("libsonare was built without acoustic-simulation support");
3936
4060
  }
3937
4061
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
3938
4062
  validateAnalysisInput("estimateRoom", request.samples, request.sampleRate ?? 48e3, request);
3939
- return module2.estimateRoom(request.samples, request.sampleRate ?? 48e3, request);
4063
+ return module3.estimateRoom(request.samples, request.sampleRate ?? 48e3, request);
3940
4064
  }
3941
4065
  function roomMorph(samples, sampleRate, options = {}) {
3942
- const module2 = requireModule25();
3943
- if (typeof module2.roomMorph !== "function") {
4066
+ const module3 = requireModule25();
4067
+ if (typeof module3.roomMorph !== "function") {
3944
4068
  throw new Error("libsonare was built without acoustic-simulation support");
3945
4069
  }
3946
4070
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
3947
4071
  validateAnalysisInput("roomMorph", request.samples, request.sampleRate, request);
3948
- return module2.roomMorph(request.samples, request.sampleRate, request);
4072
+ return module3.roomMorph(request.samples, request.sampleRate, request);
3949
4073
  }
3950
4074
  function analyzeWithProgress(samples, sampleRate = 22050, onProgress, options) {
3951
4075
  const request = samples instanceof Float32Array ? { samples, sampleRate, onProgress, options } : samples;
@@ -4828,8 +4952,8 @@ function suggestJson(fnName, request, sceneOnly) {
4828
4952
  const { left, right, ids, names } = planarTracks(request.tracks);
4829
4953
  const sampleRate = request.sampleRate;
4830
4954
  const params = request.options ?? {};
4831
- const module2 = requireModule26();
4832
- return sceneOnly ? module2.mixingAssistantSuggestSceneJson(left, right, ids, names, sampleRate, params) : module2.mixingAssistantSuggest(left, right, ids, names, sampleRate, params);
4955
+ const module3 = requireModule26();
4956
+ return sceneOnly ? module3.mixingAssistantSuggestSceneJson(left, right, ids, names, sampleRate, params) : module3.mixingAssistantSuggest(left, right, ids, names, sampleRate, params);
4833
4957
  }
4834
4958
  function suggestMixScene(request) {
4835
4959
  return JSON.parse(suggestJson("suggestMixScene", request, false));
@@ -5015,8 +5139,8 @@ var PolyphonicAnalysis = class {
5015
5139
  constructor(request) {
5016
5140
  assertSamples("analyzePolyphonic", request.samples, request.validate !== false);
5017
5141
  assertSampleRate("analyzePolyphonic", request.sampleRate);
5018
- const module2 = getSonareModule();
5019
- this.native = module2.createPolyphonicAnalysis(
5142
+ const module3 = getSonareModule();
5143
+ this.native = module3.createPolyphonicAnalysis(
5020
5144
  request.samples,
5021
5145
  request.sampleRate,
5022
5146
  request
@@ -5292,9 +5416,261 @@ var CONTROLLER_AXES = [
5292
5416
  "vibrato-depth"
5293
5417
  ];
5294
5418
  var ARTICULATIONS = ["poly", "mono-retrigger", "mono-legato"];
5419
+ var PART_RIG_MODES = ["bank", "none", "chain"];
5420
+ var PART_RIG_ALL_PARTS = 255;
5295
5421
  var MPE_DIMENSIONS = ["bend", "pressure", "timbre"];
5296
5422
  var NOTE_TRACKINGS = ["last", "lowest", "highest", "all"];
5297
5423
 
5424
+ // src/vocal_project.ts
5425
+ var UINT32_MAX = 4294967295;
5426
+ var PROJECT_ID_MAX = UINT32_MAX - 1;
5427
+ var UINT64_MAX = "18446744073709551615";
5428
+ function requireObject(value, field) {
5429
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
5430
+ throw new TypeError(`${field} must be an object`);
5431
+ }
5432
+ return value;
5433
+ }
5434
+ function uint32(value, field, allowZero = true) {
5435
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > PROJECT_ID_MAX) {
5436
+ throw new RangeError(`${field} must be a uint32 project id`);
5437
+ }
5438
+ return value;
5439
+ }
5440
+ function nativeUint32(value, field) {
5441
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > UINT32_MAX) {
5442
+ throw new RangeError(`${field} must be a native uint32`);
5443
+ }
5444
+ return value;
5445
+ }
5446
+ function nativeInt64(value, field) {
5447
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) {
5448
+ throw new RangeError(`${field} must be a safe integer`);
5449
+ }
5450
+ return value;
5451
+ }
5452
+ function positiveSampleCount(value, field) {
5453
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
5454
+ throw new RangeError(`${field} must be a positive safe integer`);
5455
+ }
5456
+ return value;
5457
+ }
5458
+ function finite(value, field) {
5459
+ if (typeof value !== "number" || !Number.isFinite(value)) {
5460
+ throw new RangeError(`${field} must be finite`);
5461
+ }
5462
+ return value;
5463
+ }
5464
+ function decimalUint64(value, field) {
5465
+ if (typeof value !== "string" || !/^(0|[1-9]\d*)$/.test(value)) {
5466
+ throw new RangeError(`${field} must be a canonical decimal uint64 string`);
5467
+ }
5468
+ if (value.length > UINT64_MAX.length || value.length === UINT64_MAX.length && value > UINT64_MAX) {
5469
+ throw new RangeError(`${field} must fit uint64`);
5470
+ }
5471
+ return value;
5472
+ }
5473
+ function hexSha256(value, field) {
5474
+ if (typeof value !== "string" || !/^[0-9a-fA-F]{64}$/.test(value)) {
5475
+ throw new TypeError(`${field} must be 64 hexadecimal characters`);
5476
+ }
5477
+ return value.toLowerCase();
5478
+ }
5479
+ function finiteMono(value, field) {
5480
+ if (!(value instanceof Float32Array) || value.length === 0) {
5481
+ throw new TypeError(`${field} must be a non-empty Float32Array`);
5482
+ }
5483
+ for (let index = 0; index < value.length; index += 1) {
5484
+ if (!Number.isFinite(value[index])) {
5485
+ throw new RangeError(`${field} must contain only finite samples`);
5486
+ }
5487
+ }
5488
+ return new Float32Array(value);
5489
+ }
5490
+ function sve1(value) {
5491
+ if (!(value instanceof Uint8Array) || value.length === 0) {
5492
+ throw new TypeError("sve1 must be a non-empty Uint8Array");
5493
+ }
5494
+ return new Uint8Array(value);
5495
+ }
5496
+ function copyToken(value) {
5497
+ const source = requireObject(value, "renderToken");
5498
+ return {
5499
+ sessionEpoch: decimalUint64(source.sessionEpoch, "renderToken.sessionEpoch"),
5500
+ revision: decimalUint64(source.revision, "renderToken.revision"),
5501
+ draftId: decimalUint64(source.draftId, "renderToken.draftId"),
5502
+ generation: decimalUint64(source.generation, "renderToken.generation"),
5503
+ requestId: decimalUint64(source.requestId, "renderToken.requestId"),
5504
+ profileId: uint32(source.profileId, "renderToken.profileId")
5505
+ };
5506
+ }
5507
+ function copyApplyRequest(request) {
5508
+ const source = requireObject(request, "request");
5509
+ const renderedMono = finiteMono(source.renderedMono, "renderedMono");
5510
+ const expectedSourceSampleCount = positiveSampleCount(
5511
+ source.expectedSourceSampleCount,
5512
+ "expectedSourceSampleCount"
5513
+ );
5514
+ if (renderedMono.length !== expectedSourceSampleCount) {
5515
+ throw new RangeError(
5516
+ `renderedMono length must equal expectedSourceSampleCount (${renderedMono.length} != ${expectedSourceSampleCount})`
5517
+ );
5518
+ }
5519
+ const expectedSourceSampleRate = finite(
5520
+ source.expectedSourceSampleRate,
5521
+ "expectedSourceSampleRate"
5522
+ );
5523
+ assertSampleRate("Project.applyVocalEdit", expectedSourceSampleRate);
5524
+ const renderedSampleRate = finite(source.renderedSampleRate, "renderedSampleRate");
5525
+ assertSampleRate("Project.applyVocalEdit", renderedSampleRate, "renderedSampleRate");
5526
+ if (renderedSampleRate !== expectedSourceSampleRate) {
5527
+ throw new RangeError("renderedSampleRate must equal expectedSourceSampleRate");
5528
+ }
5529
+ const renderedStartSample = source.renderedStartSample ?? 0;
5530
+ if (renderedStartSample !== 0) {
5531
+ throw new RangeError("renderedStartSample must be zero for the v1 Project vocal API");
5532
+ }
5533
+ const renderToken = copyToken(source.renderToken);
5534
+ if (renderToken.draftId !== "0" || renderToken.generation !== "0") {
5535
+ throw new RangeError("renderToken must be a committed session token");
5536
+ }
5537
+ return {
5538
+ clipId: uint32(source.clipId, "clipId", false),
5539
+ takeId: uint32(source.takeId ?? 0, "takeId"),
5540
+ expectedSourceId: uint32(source.expectedSourceId, "expectedSourceId", false),
5541
+ expectedSourceSampleRate,
5542
+ expectedSourceSampleCount,
5543
+ expectedSourceSha256: hexSha256(source.expectedSourceSha256, "expectedSourceSha256"),
5544
+ expectedClipLengthPpq: finite(source.expectedClipLengthPpq, "expectedClipLengthPpq"),
5545
+ expectedSourceOffsetPpq: finite(source.expectedSourceOffsetPpq, "expectedSourceOffsetPpq"),
5546
+ renderedMono,
5547
+ renderedSampleRate,
5548
+ renderedStartSample: 0,
5549
+ renderToken,
5550
+ sve1: sve1(source.sve1)
5551
+ };
5552
+ }
5553
+ function copyApplyResult(value) {
5554
+ const source = requireObject(value, "applyVocalEdit result");
5555
+ return {
5556
+ clipId: uint32(source.clipId, "result.clipId", false),
5557
+ takeId: uint32(source.takeId, "result.takeId"),
5558
+ originalSourceId: uint32(source.originalSourceId, "result.originalSourceId", false),
5559
+ derivedSourceId: uint32(source.derivedSourceId, "result.derivedSourceId", false),
5560
+ committedRevision: decimalUint64(source.committedRevision, "result.committedRevision"),
5561
+ profileId: uint32(source.profileId, "result.profileId"),
5562
+ derivedSourceSha256: hexSha256(source.derivedSourceSha256, "result.derivedSourceSha256"),
5563
+ sidecarKey: typeof source.sidecarKey === "string" ? source.sidecarKey : (() => {
5564
+ throw new TypeError("result.sidecarKey must be a string");
5565
+ })()
5566
+ };
5567
+ }
5568
+ function copyDependency(value) {
5569
+ const source = requireObject(value, "vocal edit dependency");
5570
+ const reason = nativeUint32(source.reason, "dependency.reason");
5571
+ return {
5572
+ clipId: nativeUint32(source.clipId, "dependency.clipId"),
5573
+ takeId: nativeUint32(source.takeId, "dependency.takeId"),
5574
+ originalSourceId: nativeUint32(source.originalSourceId, "dependency.originalSourceId"),
5575
+ derivedSourceId: nativeUint32(source.derivedSourceId, "dependency.derivedSourceId"),
5576
+ sourceSampleRate: nativeUint32(source.sourceSampleRate, "dependency.sourceSampleRate"),
5577
+ profileId: nativeUint32(source.profileId, "dependency.profileId"),
5578
+ sourceSampleCount: nativeInt64(source.sourceSampleCount, "dependency.sourceSampleCount"),
5579
+ committedRevision: decimalUint64(source.committedRevision, "dependency.committedRevision"),
5580
+ originalSourceSha256: hexSha256(source.originalSourceSha256, "dependency.originalSourceSha256"),
5581
+ derivedSourceSha256: hexSha256(source.derivedSourceSha256, "dependency.derivedSourceSha256"),
5582
+ originalPcmAvailable: source.originalPcmAvailable === true,
5583
+ derivedPcmAvailable: source.derivedPcmAvailable === true,
5584
+ reason,
5585
+ sidecarKey: typeof source.sidecarKey === "string" ? source.sidecarKey : (() => {
5586
+ throw new TypeError("dependency.sidecarKey must be a string");
5587
+ })()
5588
+ };
5589
+ }
5590
+ function copyRehydrateItem(value) {
5591
+ const source = requireObject(value, "vocal rehydrate item");
5592
+ const status = source.status;
5593
+ if (typeof status !== "number" || !Number.isSafeInteger(status) || status < 0 || status > 2) {
5594
+ throw new RangeError("rehydrate.status must be one of 0, 1, or 2");
5595
+ }
5596
+ const reason = nativeUint32(source.reason, "rehydrate.reason");
5597
+ return {
5598
+ clipId: nativeUint32(source.clipId, "rehydrate.clipId"),
5599
+ takeId: nativeUint32(source.takeId, "rehydrate.takeId"),
5600
+ derivedSourceId: nativeUint32(source.derivedSourceId, "rehydrate.derivedSourceId"),
5601
+ status,
5602
+ reason
5603
+ };
5604
+ }
5605
+ function copyOriginalSources(originals) {
5606
+ if (!Array.isArray(originals)) {
5607
+ throw new TypeError("originals must be an array");
5608
+ }
5609
+ const seen = /* @__PURE__ */ new Set();
5610
+ return originals.map((original, index) => {
5611
+ const source = requireObject(original, `originals[${index}]`);
5612
+ const sourceId = uint32(source.sourceId, `originals[${index}].sourceId`, false);
5613
+ if (seen.has(sourceId)) {
5614
+ throw new RangeError(`originals contains duplicate sourceId ${sourceId}`);
5615
+ }
5616
+ seen.add(sourceId);
5617
+ const sampleRate = finite(source.sampleRate, `originals[${index}].sampleRate`);
5618
+ assertSampleRate("Project.rehydrateVocalEdits", sampleRate, `originals[${index}].sampleRate`);
5619
+ return {
5620
+ sourceId,
5621
+ mono: finiteMono(source.mono, `originals[${index}].mono`),
5622
+ sampleRate
5623
+ };
5624
+ });
5625
+ }
5626
+ function projectApplyVocalEdit(native, request) {
5627
+ const normalized = copyApplyRequest(request);
5628
+ return copyApplyResult(native.applyVocalEdit(normalized));
5629
+ }
5630
+ function projectGetVocalEditDependencies(native) {
5631
+ const value = native.getVocalEditDependencies();
5632
+ if (!Array.isArray(value)) {
5633
+ throw new TypeError("native vocal dependencies must be an array");
5634
+ }
5635
+ return value.map(copyDependency);
5636
+ }
5637
+ function projectRehydrateVocalEdits(native, originals, cancel) {
5638
+ if (cancel !== void 0 && typeof cancel !== "function") {
5639
+ throw new TypeError("cancel must be a function");
5640
+ }
5641
+ const normalized = copyOriginalSources(originals);
5642
+ let callbackFailed = false;
5643
+ let callbackFailure;
5644
+ const guardedCancel = cancel === void 0 ? void 0 : () => {
5645
+ try {
5646
+ return cancel();
5647
+ } catch (error) {
5648
+ callbackFailed = true;
5649
+ callbackFailure = error;
5650
+ return true;
5651
+ }
5652
+ };
5653
+ let value;
5654
+ try {
5655
+ value = native.rehydrateVocalEdits(normalized, guardedCancel);
5656
+ } catch (error) {
5657
+ if (callbackFailed) {
5658
+ throw callbackFailure;
5659
+ }
5660
+ throw error;
5661
+ }
5662
+ if (callbackFailed) {
5663
+ throw callbackFailure;
5664
+ }
5665
+ if (value !== null && typeof value === "object" && "cancelFailure" in value) {
5666
+ throw value.cancelFailure;
5667
+ }
5668
+ if (!Array.isArray(value)) {
5669
+ throw new TypeError("native vocal rehydrate result must be an array");
5670
+ }
5671
+ return value.map(copyRehydrateItem);
5672
+ }
5673
+
5298
5674
  // src/project_class.ts
5299
5675
  function normalizeMidiFxBakeRequest(clipIdOrRequest, configJson) {
5300
5676
  if (typeof clipIdOrRequest === "number") {
@@ -5363,11 +5739,57 @@ function midi2Byte(fnName, value, argName) {
5363
5739
  assertBoundedInteger(fnName, value, argName, 0, 255);
5364
5740
  return value;
5365
5741
  }
5742
+ var ProjectTimeline = class {
5743
+ /** @internal */
5744
+ constructor(native) {
5745
+ this.native = native;
5746
+ registerProjectTimeline(this, native);
5747
+ }
5748
+ /**
5749
+ * Identity an engine names this timeline by, so no raw pointer crosses into JS.
5750
+ *
5751
+ * @internal
5752
+ */
5753
+ get nativeId() {
5754
+ if (this.native === null) {
5755
+ throw new TypeError("ProjectTimeline is disposed");
5756
+ }
5757
+ return this.native.id;
5758
+ }
5759
+ /** Release the underlying WASM object. Idempotent. */
5760
+ dispose() {
5761
+ if (this.native === null) {
5762
+ return;
5763
+ }
5764
+ unregisterProjectTimeline(this);
5765
+ this.native.delete();
5766
+ this.native = null;
5767
+ }
5768
+ };
5366
5769
  var Project = class _Project {
5367
5770
  constructor() {
5368
5771
  this.released = false;
5772
+ this.deleteRequested = false;
5773
+ this.activeVocalRehydrateCalls = 0;
5369
5774
  this.native = new (projectModule()).Project();
5370
5775
  }
5776
+ applyVocalEdit(request) {
5777
+ return projectApplyVocalEdit(this.native, request);
5778
+ }
5779
+ getVocalEditDependencies() {
5780
+ return projectGetVocalEditDependencies(this.native);
5781
+ }
5782
+ rehydrateVocalEdits(originals, cancel) {
5783
+ this.activeVocalRehydrateCalls += 1;
5784
+ try {
5785
+ return projectRehydrateVocalEdits(this.native, originals, cancel);
5786
+ } finally {
5787
+ this.activeVocalRehydrateCalls -= 1;
5788
+ if (this.activeVocalRehydrateCalls === 0 && this.deleteRequested) {
5789
+ this.native.delete();
5790
+ }
5791
+ }
5792
+ }
5371
5793
  /** Create a new empty project. */
5372
5794
  static create() {
5373
5795
  return new _Project();
@@ -5736,6 +6158,26 @@ var Project = class _Project {
5736
6158
  setTrackGain(trackId, gain) {
5737
6159
  this.native.setTrackGain(trackId, gain);
5738
6160
  }
6161
+ /**
6162
+ * Set the rig of one part (0-15), or of the destination's default when `part`
6163
+ * is {@link PART_RIG_ALL_PARTS}, on MIDI destination `destinationId`, via an
6164
+ * undoable edit. `'chain'` takes 1-8 `inserts` and is the only mode that does;
6165
+ * a destination no track references is stored and read back all the same.
6166
+ * Refused: a malformed insert document (invalid format), an out-of-range part,
6167
+ * an unknown mode or processor, or inserts on a non-chain mode (invalid
6168
+ * parameter), and an insert chain in a build without mastering.
6169
+ */
6170
+ setPartRig(destinationId, part, mode, inserts) {
6171
+ this.native.setPartRig(destinationId, part, mode, partRigInsertsJson(inserts));
6172
+ }
6173
+ /** The stored rig for `(destinationId, part)`, or null when none is stored. */
6174
+ getPartRig(destinationId, part) {
6175
+ return partRigFromNative(this.native.getPartRig(destinationId, part));
6176
+ }
6177
+ /** Remove the stored rig for `(destinationId, part)` via an undoable edit (no-op when absent). */
6178
+ clearPartRig(destinationId, part) {
6179
+ this.native.clearPartRig(destinationId, part);
6180
+ }
5739
6181
  /** Set a track's mute flag via an undoable edit (a muted track is silent). */
5740
6182
  setTrackMute(trackId, mute) {
5741
6183
  this.native.setTrackMute(trackId, mute);
@@ -5941,6 +6383,16 @@ var Project = class _Project {
5941
6383
  compile() {
5942
6384
  return this.native.compile();
5943
6385
  }
6386
+ /**
6387
+ * Compile the project and keep the resulting timeline. The result is
6388
+ * {@link compile}'s plus `timeline`: a {@link ProjectTimeline} to install with
6389
+ * `RealtimeEngine.applyProjectTimeline`, or `null` when an error diagnostic
6390
+ * means nothing compiled.
6391
+ */
6392
+ compileTimeline() {
6393
+ const { timeline, ...result } = this.native.compileTimeline();
6394
+ return { ...result, timeline: timeline === null ? null : new ProjectTimeline(timeline) };
6395
+ }
5944
6396
  /**
5945
6397
  * Compile + render the project offline to interleaved float audio. MIDI
5946
6398
  * tracks render silently here (no instrument is bound) — use
@@ -6313,12 +6765,20 @@ var Project = class _Project {
6313
6765
  lastBounceCompileResult() {
6314
6766
  return this.native.lastBounceCompileResult();
6315
6767
  }
6316
- /** Release the underlying WASM object. Idempotent, as the Node facade is. */
6768
+ /**
6769
+ * Release the underlying WASM object. Idempotent, as the Node facade is.
6770
+ * Called from a rehydrate cancel callback, the release happens when the
6771
+ * outermost {@link rehydrateVocalEdits} call returns.
6772
+ */
6317
6773
  delete() {
6318
6774
  if (this.released) {
6319
6775
  return;
6320
6776
  }
6321
6777
  this.released = true;
6778
+ if (this.activeVocalRehydrateCalls !== 0) {
6779
+ this.deleteRequested = true;
6780
+ return;
6781
+ }
6322
6782
  this.native.delete();
6323
6783
  }
6324
6784
  /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
@@ -6418,14 +6878,14 @@ function engineCapabilities() {
6418
6878
  var RealtimeEngine = class _RealtimeEngine {
6419
6879
  constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
6420
6880
  this.released = false;
6421
- const module2 = getSonareModule();
6881
+ const module3 = getSonareModule();
6422
6882
  const capabilities2 = engineCapabilities();
6423
6883
  if (!capabilities2.abiCompatible) {
6424
6884
  throw new Error(
6425
6885
  `Engine ABI mismatch: wasm=${capabilities2.engineAbiVersion}, expected=${capabilities2.expectedEngineAbiVersion}`
6426
6886
  );
6427
6887
  }
6428
- this.native = new module2.RealtimeEngine(
6888
+ this.native = new module3.RealtimeEngine(
6429
6889
  sampleRate,
6430
6890
  maxBlockSize,
6431
6891
  commandCapacity,
@@ -6629,6 +7089,17 @@ var RealtimeEngine = class _RealtimeEngine {
6629
7089
  setArticulation(destinationId, channel, articulation) {
6630
7090
  this.native.setArticulation(destinationId, channel, articulation);
6631
7091
  }
7092
+ /**
7093
+ * Set the rig of one part (0-15), or of the destination's default when `part`
7094
+ * is `PART_RIG_ALL_PARTS`, on the instrument bound to `destinationId`. A
7095
+ * direct call, not a stored edit: a destination with no instrument is an
7096
+ * invalid parameter, and an instrument without part rigs (a builtin, a
7097
+ * host-callback instrument) is refused as not implemented rather than
7098
+ * succeeding quietly. `'chain'` takes 1-8 `inserts`; no other mode does.
7099
+ */
7100
+ setPartRig(destinationId, part, mode, inserts) {
7101
+ this.native.setPartRig(destinationId, part, mode, partRigInsertsJson(inserts));
7102
+ }
6632
7103
  /**
6633
7104
  * Read back {@link setArticulation} as the canonical name. An ordinal this
6634
7105
  * build cannot spell is handed back as the number, the way every other enum
@@ -6674,7 +7145,10 @@ var RealtimeEngine = class _RealtimeEngine {
6674
7145
  /**
6675
7146
  * Route a destination's (track lane's) MIDI to the external output queue
6676
7147
  * instead of the internal instrument rack, so the track plays an external
6677
- * device. Clearing it restores internal-synth playback.
7148
+ * device. Clearing it restores internal-synth playback. Control-thread only.
7149
+ * The change takes effect at the next processed block; switching a
7150
+ * destination's route first releases its notes and resets its controllers
7151
+ * through the old route, and drops its pending MIDI-FX events. Single writer.
6678
7152
  */
6679
7153
  setMidiDestinationExternal(destinationId, external) {
6680
7154
  this.native.setMidiDestinationExternal(destinationId, external);
@@ -6828,8 +7302,11 @@ var RealtimeEngine = class _RealtimeEngine {
6828
7302
  /**
6829
7303
  * Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
6830
7304
  * the full message including the leading 0xF0 and trailing 0xF7 (1..512
6831
- * bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Mirrors
6832
- * the Node/Python/C-ABI `pushMidiSysex`.
7305
+ * bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Throws
7306
+ * `InvalidParameter` when the destination instrument cannot prepare
7307
+ * the SysEx (retrying cannot help), and `OutOfMemory` when the payload
7308
+ * slots or the command queue are full (retry after a processed block).
7309
+ * Mirrors the Node/Python/C-ABI `pushMidiSysex`.
6833
7310
  */
6834
7311
  pushMidiSysex(destinationId, data, renderFrame = -1) {
6835
7312
  this.native.pushMidiSysex(destinationId, data, renderFrame);
@@ -6852,12 +7329,30 @@ var RealtimeEngine = class _RealtimeEngine {
6852
7329
  getTransportState() {
6853
7330
  return this.native.getTransportState();
6854
7331
  }
7332
+ /** Queues an integrated-loudness reset; short-term and momentary windows are retained. */
7333
+ resetMasterLoudnessMeter(renderFrame = -1) {
7334
+ this.native.resetMasterLoudnessMeter(renderFrame);
7335
+ }
7336
+ /** Reads the immutable factory value for a resolved insert parameter id. */
7337
+ insertParameterConstructedValue(paramId) {
7338
+ return this.native.insertParameterConstructedValue(paramId);
7339
+ }
6855
7340
  play(renderFrame = -1) {
6856
7341
  this.native.play(renderFrame);
6857
7342
  }
7343
+ /**
7344
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
7345
+ * centred pitch bend on every channel played since the last reset, so
7346
+ * controller values set before a loop region are not restored at the wrap.
7347
+ */
6858
7348
  stop(renderFrame = -1) {
6859
7349
  this.native.stop(renderFrame);
6860
7350
  }
7351
+ /**
7352
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
7353
+ * centred pitch bend on every channel played since the last reset, so
7354
+ * controller values set before a loop region are not restored at the wrap.
7355
+ */
6861
7356
  seekSample(timelineSample, renderFrame = -1) {
6862
7357
  this.native.seekSample(timelineSample, renderFrame);
6863
7358
  }
@@ -6882,6 +7377,11 @@ var RealtimeEngine = class _RealtimeEngine {
6882
7377
  applyCommandsDueNowPreservingFuture() {
6883
7378
  this.native.applyCommandsDueNowPreservingFuture();
6884
7379
  }
7380
+ /**
7381
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
7382
+ * centred pitch bend on every channel played since the last reset, so
7383
+ * controller values set before a loop region are not restored at the wrap.
7384
+ */
6885
7385
  seekPpq(ppq, renderFrame = -1) {
6886
7386
  this.native.seekPpq(ppq, renderFrame);
6887
7387
  }
@@ -6901,6 +7401,11 @@ var RealtimeEngine = class _RealtimeEngine {
6901
7401
  sampleAtPpq(ppq) {
6902
7402
  return Number(this.native.sampleAtPpq(ppq));
6903
7403
  }
7404
+ /**
7405
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
7406
+ * centred pitch bend on every channel played since the last reset, so
7407
+ * controller values set before a loop region are not restored at the wrap.
7408
+ */
6904
7409
  setLoop(startPpq, endPpq, enabled = true) {
6905
7410
  this.native.setLoop(startPpq, endPpq, enabled);
6906
7411
  }
@@ -6995,25 +7500,56 @@ var RealtimeEngine = class _RealtimeEngine {
6995
7500
  if (typeof lane === "number") {
6996
7501
  return { trackId: lane };
6997
7502
  }
6998
- if (!lane.sends) {
6999
- return lane;
7000
- }
7001
- return { ...lane, sends: _RealtimeEngine.normalizeSends(lane.sends) };
7503
+ return {
7504
+ ...lane,
7505
+ sends: lane.sends ? _RealtimeEngine.normalizeSends(lane.sends) : void 0
7506
+ };
7002
7507
  })
7003
7508
  );
7004
7509
  }
7005
7510
  /**
7006
7511
  * Keys one insert of a lane strip from another lane's post-strip audio
7007
7512
  * (ducking/sidechainRouter inserts). sourceTrackId 0 removes the binding.
7513
+ * Lanes are processed in key order and the key is delay-compensated to the
7514
+ * destination strip input, so the result depends on neither the lane order
7515
+ * nor the block size. Throws, leaving the bindings unchanged, for a lane
7516
+ * keying itself, a binding that would close a cycle over the lane bindings,
7517
+ * a full binding table and an alignment past the delay ceiling. Not callable
7518
+ * while a block is being processed.
7008
7519
  */
7009
7520
  setLaneSidechain(trackId, insertIndex, sourceTrackId) {
7010
7521
  this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
7011
7522
  }
7523
+ /**
7524
+ * Installs a compiled project timeline into this stopped engine, all or
7525
+ * nothing: tempo and time-signature segments, markers, track lanes, track
7526
+ * automation, audio and MIDI clips, and the per-track strips of the project's
7527
+ * mixer scene are replaced in full (a later low-level setter on those domains
7528
+ * is overwritten by the next apply). Instruments, buses, the master strip,
7529
+ * metronome, loop and capture are not touched; bind instruments separately.
7530
+ *
7531
+ * Throws while the transport is playing, for a timeline the engine cannot
7532
+ * hold, and for a scene strip routed from several tracks. A validation
7533
+ * failure leaves the engine unchanged. The engine keeps its own reference, so
7534
+ * {@link ProjectTimeline.dispose} may follow immediately.
7535
+ *
7536
+ * Also throws on an unprepared engine and on a timeline whose sample rate
7537
+ * differs from the engine's. Once a timeline is applied, a later prepare at
7538
+ * another rate is refused until the timeline's clips are cleared.
7539
+ */
7540
+ applyProjectTimeline(timeline) {
7541
+ const timelineId = projectTimelineNativeId(timeline);
7542
+ if (timelineId === void 0) {
7543
+ throw new TypeError("timeline must be a ProjectTimeline instance");
7544
+ }
7545
+ this.native.applyProjectTimeline(timelineId);
7546
+ }
7012
7547
  setTrackBuses(buses) {
7013
7548
  this.native.setTrackBuses(
7014
- Array.isArray(buses) ? buses.map(
7015
- (bus) => bus.sends ? { ...bus, sends: _RealtimeEngine.normalizeSends(bus.sends) } : bus
7016
- ) : buses
7549
+ Array.isArray(buses) ? buses.map((bus) => ({
7550
+ ...bus,
7551
+ sends: bus.sends ? _RealtimeEngine.normalizeSends(bus.sends) : void 0
7552
+ })) : buses
7017
7553
  );
7018
7554
  }
7019
7555
  /**
@@ -7167,6 +7703,16 @@ var RealtimeEngine = class _RealtimeEngine {
7167
7703
  * stages of the offline mastering chain (`repair.*`, `loudness`, and the
7168
7704
  * match stages) have no insert form and no automation id: they buffer the
7169
7705
  * entire signal by construction and do not run on the realtime path.
7706
+ *
7707
+ * The returned id uses the track's current positional lane selector. When
7708
+ * `setTrackLanes` successfully changes lane order or membership, the engine
7709
+ * remaps already queued and published track automation by track id, but it
7710
+ * cannot update a numeric id retained by the caller. Re-resolve every track
7711
+ * insert id after such a topology change before passing it to
7712
+ * `setAutomationLane`, `setParameter`, or `setParameterSmoothed`. Use
7713
+ * `setTrackStripInsertParamByName` when the operation needs a stable track
7714
+ * identity. Master and bus insert ids are separate and are not invalidated by
7715
+ * track-lane changes.
7170
7716
  */
7171
7717
  resolveTrackInsertAutomationId(trackId, insertIndex, paramName) {
7172
7718
  return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
@@ -7199,12 +7745,9 @@ var RealtimeEngine = class _RealtimeEngine {
7199
7745
  * return `-1`: they resize voice pools or swap DSP topology, which is not
7200
7746
  * audio-thread safe. Rebind the instrument with a new patch instead.
7201
7747
  *
7202
- * `gain`, `busDrive`, `cutoffHz`, `resonanceQ`, `envToCutoffCents`,
7203
- * `lfoToPitchCents` and `pitchOffsetCents` reach voices that are already
7204
- * sounding from the next block; the rest are cached into per-voice state at
7205
- * note-on and take effect from the next note, so a lane that moves one of
7206
- * them under a held note looks inert until the next one speaks — that is the
7207
- * behaviour, not a dropped write.
7748
+ * All automatable parameters of a NativeSynth reach already-sounding voices
7749
+ * from the next processed block. Voices using GM program patches are
7750
+ * unaffected; parameters apply to NativeSynth's own patch only.
7208
7751
  *
7209
7752
  * The id survives an unbind/rebind of the same destination and applies
7210
7753
  * nothing while that destination is unbound.
@@ -7228,6 +7771,16 @@ var RealtimeEngine = class _RealtimeEngine {
7228
7771
  setTrackStripDualPan(trackId, leftPan, rightPan) {
7229
7772
  this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
7230
7773
  }
7774
+ /**
7775
+ * Sets a track lane strip's surround placement in realtime (glitch-free; a
7776
+ * constant-power 5 ms one-pole glide). Used when the destination has more than
7777
+ * two channels. Omitted fields default to a centered point source
7778
+ * (`distance` 1); a `distance` of 0 or less is treated as 1. Throws for an
7779
+ * unknown track, a track without a lane strip, or a non-finite field.
7780
+ */
7781
+ setTrackStripSurroundPan(trackId, pan) {
7782
+ this.native.setTrackStripSurroundPan(trackId, pan);
7783
+ }
7231
7784
  /**
7232
7785
  * Sets a bus strip's output pan position in realtime (glitch-free). Throws
7233
7786
  * for an unknown bus or one wider than stereo.
@@ -7248,8 +7801,9 @@ var RealtimeEngine = class _RealtimeEngine {
7248
7801
  this.native.setBusStripDualPan(busId, leftPan, rightPan);
7249
7802
  }
7250
7803
  /**
7251
- * Sets a track lane strip's inter-channel alignment delay (whole samples).
7252
- * Adjusts strip latency, so PDC and reported graph latency are refreshed.
7804
+ * Sets a track lane strip's alignment delay (whole samples), moving this lane
7805
+ * later relative to every other lane. It is not latency: PDC does not
7806
+ * compensate it and the reported graph latency is unchanged.
7253
7807
  */
7254
7808
  setTrackStripChannelDelaySamples(trackId, delaySamples) {
7255
7809
  this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
@@ -7493,6 +8047,12 @@ var RealtimeEngine = class _RealtimeEngine {
7493
8047
  meterScratchRenderFrame() {
7494
8048
  return Number(this.native.meterScratchRenderFrame());
7495
8049
  }
8050
+ meterScratchInputPeakDbL() {
8051
+ return this.native.meterScratchInputPeakDbL();
8052
+ }
8053
+ meterScratchInputPeakDbR() {
8054
+ return this.native.meterScratchInputPeakDbR();
8055
+ }
7496
8056
  meterScratchValue(field) {
7497
8057
  return this.native.meterScratchValue(field);
7498
8058
  }
@@ -7502,14 +8062,24 @@ var RealtimeEngine = class _RealtimeEngine {
7502
8062
  /**
7503
8063
  * Drains pending meter telemetry as per-plane (wide) records for a surround
7504
8064
  * target. Use this for a surround mix target; {@link drainMeterTelemetry}
7505
- * stays the stereo fast path. The two share one queue — call only one per
7506
- * target. The live AudioWorklet path owns the queue via the stereo drain, so
7507
- * this wide drain is for an offline (non-worklet) engine instance; per-plane
8065
+ * stays the stereo fast path. The two share one queue and each consumes every
8066
+ * target's records, so an engine uses only one of them. The live AudioWorklet
8067
+ * path owns the queue via the stereo drain, so this wide drain is for an
8068
+ * offline (non-worklet) engine instance; per-plane
7508
8069
  * surround meters are not delivered over the live worklet meter ring.
7509
8070
  */
7510
8071
  drainMeterTelemetryWide(maxRecords = 1024) {
7511
8072
  return this.native.drainMeterTelemetryWide(maxRecords);
7512
8073
  }
8074
+ /**
8075
+ * Per-insert gain reduction in dB (each <= 0) of a meter target's strip from the
8076
+ * last rendered block, in combined pre-to-post insert order. `targetId` uses the
8077
+ * meter-record encoding (0 master, 1..32 lanes, 33..40 buses; 0xFFFF monitor
8078
+ * yields an empty array). Throws for an id outside that range.
8079
+ */
8080
+ meterTargetInsertGainReduction(targetId) {
8081
+ return this.native.meterTargetInsertGainReduction(targetId);
8082
+ }
7513
8083
  /**
7514
8084
  * Enables per-target spectrum + vectorscope capture. @param intervalFrames is
7515
8085
  * the minimum render-frame gap between snapshots (0 disables). @param bandCount
@@ -7623,9 +8193,9 @@ var StreamAnalyzer = class {
7623
8193
  throw new TypeError("outputFormat must be the integer 0 (Float32)");
7624
8194
  }
7625
8195
  const window = config.window === void 0 ? void 0 : resolveOrdinalInRange(config.window, 0, 3, "stream analyzer window");
7626
- const module2 = getSonareModule();
8196
+ const module3 = getSonareModule();
7627
8197
  const defaults = streamAnalyzerConfigDefaults();
7628
- this.analyzer = new module2.StreamAnalyzer(
8198
+ this.analyzer = new module3.StreamAnalyzer(
7629
8199
  config.sampleRate ?? defaults.sampleRate,
7630
8200
  config.nFft ?? defaults.nFft,
7631
8201
  config.hopLength ?? defaults.hopLength,
@@ -7885,8 +8455,8 @@ var Mixer = class _Mixer {
7885
8455
  * @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
7886
8456
  */
7887
8457
  static fromSceneJson(json, sampleRate = 48e3, blockSize = 512) {
7888
- const module2 = getSonareModule();
7889
- return new _Mixer(module2.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
8458
+ const module3 = getSonareModule();
8459
+ return new _Mixer(module3.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
7890
8460
  }
7891
8461
  /**
7892
8462
  * Rebuild and compile the routing graph without resetting its absolute
@@ -7942,47 +8512,60 @@ var Mixer = class _Mixer {
7942
8512
  * after {@link delete}.
7943
8513
  */
7944
8514
  createRealtimeBuffer() {
7945
- const stripCount = this.stripCount();
7946
- let leftInputs = [];
7947
- let rightInputs = [];
8515
+ const leftInputs = [];
8516
+ const rightInputs = [];
7948
8517
  let outLeft = this.mixer.outputLeftView();
7949
8518
  let outRight = this.mixer.outputRightView();
8519
+ let acquiredStripCount = -1;
8520
+ const viewsDetached = () => outLeft.byteLength === 0 || outRight.byteLength === 0;
7950
8521
  const acquire = () => {
7951
- leftInputs = [];
7952
- rightInputs = [];
7953
- for (let index = 0; index < stripCount; index++) {
8522
+ const stripCount = this.stripCount();
8523
+ const detached = viewsDetached();
8524
+ if (detached) {
8525
+ leftInputs.length = 0;
8526
+ rightInputs.length = 0;
8527
+ } else {
8528
+ leftInputs.length = Math.min(leftInputs.length, stripCount);
8529
+ rightInputs.length = Math.min(rightInputs.length, stripCount);
8530
+ }
8531
+ for (let index = leftInputs.length; index < stripCount; index++) {
7954
8532
  leftInputs.push(this.mixer.inputLeftView(index));
8533
+ }
8534
+ for (let index = rightInputs.length; index < stripCount; index++) {
7955
8535
  rightInputs.push(this.mixer.inputRightView(index));
7956
8536
  }
7957
- outLeft = this.mixer.outputLeftView();
7958
- outRight = this.mixer.outputRightView();
8537
+ if (detached) {
8538
+ outLeft = this.mixer.outputLeftView();
8539
+ outRight = this.mixer.outputRightView();
8540
+ }
8541
+ acquiredStripCount = stripCount;
7959
8542
  };
7960
8543
  acquire();
7961
- const reacquireIfDetached = () => {
7962
- if (outLeft.byteLength === 0 || (leftInputs[0]?.byteLength ?? 1) === 0) {
8544
+ const acquireIfNeeded = () => {
8545
+ if (acquiredStripCount !== this.stripCount() || viewsDetached()) {
7963
8546
  acquire();
7964
8547
  }
7965
8548
  };
7966
8549
  return {
7967
8550
  get leftInputs() {
7968
- reacquireIfDetached();
8551
+ acquireIfNeeded();
7969
8552
  return leftInputs;
7970
8553
  },
7971
8554
  get rightInputs() {
7972
- reacquireIfDetached();
8555
+ acquireIfNeeded();
7973
8556
  return rightInputs;
7974
8557
  },
7975
8558
  get outLeft() {
7976
- reacquireIfDetached();
8559
+ acquireIfNeeded();
7977
8560
  return outLeft;
7978
8561
  },
7979
8562
  get outRight() {
7980
- reacquireIfDetached();
8563
+ acquireIfNeeded();
7981
8564
  return outRight;
7982
8565
  },
7983
- process: (numSamples = outLeft.length) => {
7984
- reacquireIfDetached();
7985
- this.mixer.processPreparedStereo(numSamples);
8566
+ process: (numSamples) => {
8567
+ acquireIfNeeded();
8568
+ this.mixer.processPreparedStereo(numSamples ?? outLeft.length);
7986
8569
  }
7987
8570
  };
7988
8571
  }
@@ -8200,8 +8783,8 @@ var Mixer = class _Mixer {
8200
8783
  this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
8201
8784
  }
8202
8785
  /**
8203
- * Set a per-strip channel delay in samples. This changes the strip's reported
8204
- * latency; recompile to re-run latency compensation.
8786
+ * Set a per-strip channel delay in samples, moving the strip later relative to
8787
+ * the others. It is not latency: latency compensation does not undo it.
8205
8788
  */
8206
8789
  setChannelDelaySamples(stripIndex, delaySamples) {
8207
8790
  this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
@@ -8455,8 +9038,8 @@ var RealtimeVoiceChanger = class {
8455
9038
  */
8456
9039
  constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
8457
9040
  this.released = false;
8458
- const module2 = getSonareModule();
8459
- this.changer = module2.createRealtimeVoiceChanger(config);
9041
+ const module3 = getSonareModule();
9042
+ this.changer = module3.createRealtimeVoiceChanger(config);
8460
9043
  if (sampleRate !== void 0) {
8461
9044
  let prepared = false;
8462
9045
  try {
@@ -8732,9 +9315,9 @@ var EQ_PHASE_MODES = {
8732
9315
  var StreamingMasteringChain = class {
8733
9316
  constructor(config) {
8734
9317
  this.released = false;
8735
- const module2 = getSonareModule();
9318
+ const module3 = getSonareModule();
8736
9319
  const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
8737
- this.chain = module2.createStreamingMasteringChain({
9320
+ this.chain = module3.createStreamingMasteringChain({
8738
9321
  __flatParams: flattenChainConfig(chainConfig),
8739
9322
  loudnessStaticGainDb,
8740
9323
  loudnessStaticGainPeakDb
@@ -8782,6 +9365,13 @@ var StreamingMasteringChain = class {
8782
9365
  reset() {
8783
9366
  this.chain.reset();
8784
9367
  }
9368
+ /**
9369
+ * Set one realtime-safe full-chain parameter at a serialized block boundary.
9370
+ * Structural, disabled-stage and unknown parameters are rejected.
9371
+ */
9372
+ setParameter(key, value) {
9373
+ this.chain.setParameter(key, value);
9374
+ }
8785
9375
  /** Total reported latency in samples across all active processors. */
8786
9376
  latencySamples() {
8787
9377
  return this.chain.latencySamples();
@@ -8863,8 +9453,8 @@ var StreamingMasteringChain = class {
8863
9453
  var StreamingEqualizer = class {
8864
9454
  constructor(config = {}) {
8865
9455
  this.released = false;
8866
- const module2 = getSonareModule();
8867
- this.eq = module2.createEqualizer(config);
9456
+ const module3 = getSonareModule();
9457
+ this.eq = module3.createEqualizer(config);
8868
9458
  }
8869
9459
  /**
8870
9460
  * Configure the band at `index` (0..23). Omitted fields use C++ defaults.
@@ -9033,8 +9623,8 @@ var StreamingEqualizer = class {
9033
9623
  var StreamingRetune = class {
9034
9624
  constructor(config = {}) {
9035
9625
  this.released = false;
9036
- const module2 = getSonareModule();
9037
- this.retune = module2.createStreamingRetune(config);
9626
+ const module3 = getSonareModule();
9627
+ this.retune = module3.createStreamingRetune(config);
9038
9628
  }
9039
9629
  /**
9040
9630
  * Allocate and initialize native state for the given sample rate and maximum
@@ -9098,28 +9688,1292 @@ function transcribe(request) {
9098
9688
  );
9099
9689
  }
9100
9690
 
9101
- // src/web_midi.ts
9102
- function isWebMidiAvailable() {
9103
- return typeof globalThis.navigator?.requestMIDIAccess === "function";
9691
+ // src/vocal_edit.ts
9692
+ var vocalErrorFields = [
9693
+ "reason",
9694
+ "field",
9695
+ "expected",
9696
+ "actual",
9697
+ "expectedText",
9698
+ "actualText"
9699
+ ];
9700
+ var cachedModuleSource = null;
9701
+ var cachedVocalModule = null;
9702
+ function addVocalErrorDetail(error, detail) {
9703
+ if (error === null || typeof error !== "object") {
9704
+ return;
9705
+ }
9706
+ const candidate = error;
9707
+ if (candidate.name !== "SonareError" || typeof candidate.code !== "number") {
9708
+ return;
9709
+ }
9710
+ for (const field of vocalErrorFields) {
9711
+ Object.defineProperty(candidate, field, {
9712
+ configurable: true,
9713
+ enumerable: true,
9714
+ value: detail[field],
9715
+ writable: false
9716
+ });
9717
+ }
9104
9718
  }
9105
- async function bindWebMidi(engine, options = {}) {
9106
- const navigatorWithMidi = globalThis.navigator;
9107
- if (typeof navigatorWithMidi?.requestMIDIAccess !== "function") {
9108
- throw new Error("Web MIDI is not available in this environment");
9719
+ function module() {
9720
+ const source = getSonareModule();
9721
+ if (source === cachedModuleSource && cachedVocalModule !== null) {
9722
+ return cachedVocalModule;
9109
9723
  }
9110
- const group = options.group ?? 0;
9111
- assertNibble("bindWebMidi", group, "group");
9112
- const destinationId = options.destinationId ?? 0;
9113
- const selectedIds = new Set(options.inputIds ?? []);
9114
- const access = await navigatorWithMidi.requestMIDIAccess({
9115
- sysex: options.sysex ?? false,
9116
- software: options.software ?? true
9724
+ const detailReader = source.vocalEditLastErrorDetail;
9725
+ if (typeof detailReader !== "function") {
9726
+ cachedModuleSource = source;
9727
+ cachedVocalModule = source;
9728
+ return source;
9729
+ }
9730
+ const functionCache = /* @__PURE__ */ new Map();
9731
+ const wrapped = new Proxy(source, {
9732
+ get(target, property, receiver) {
9733
+ const member = Reflect.get(target, property, receiver);
9734
+ if (property === "vocalEditLastErrorDetail" || typeof property !== "string" || !property.startsWith("vocalEdit") || typeof member !== "function") {
9735
+ return member;
9736
+ }
9737
+ const cached = functionCache.get(property);
9738
+ if (cached !== void 0) {
9739
+ return cached;
9740
+ }
9741
+ const wrappedMember = (...args) => {
9742
+ let beforeSequence;
9743
+ try {
9744
+ beforeSequence = detailReader.call(target).sequence;
9745
+ } catch {
9746
+ }
9747
+ try {
9748
+ return Reflect.apply(member, target, args);
9749
+ } catch (error) {
9750
+ if (beforeSequence !== void 0) {
9751
+ try {
9752
+ const detail = detailReader.call(target);
9753
+ if (detail.sequence !== beforeSequence) {
9754
+ addVocalErrorDetail(error, detail);
9755
+ }
9756
+ } catch {
9757
+ }
9758
+ }
9759
+ throw error;
9760
+ }
9761
+ };
9762
+ functionCache.set(property, wrappedMember);
9763
+ return wrappedMember;
9764
+ }
9117
9765
  });
9118
- for (const binding of options.ccBindings ?? []) {
9119
- engine.bindMidiCc(binding.channel, binding.controller, binding.paramId, binding.options);
9766
+ cachedModuleSource = source;
9767
+ cachedVocalModule = wrapped;
9768
+ return wrapped;
9769
+ }
9770
+ function requireHandle(handle, subject) {
9771
+ if (!Number.isSafeInteger(handle) || handle <= 0) {
9772
+ throw new TypeError(`${subject} has already been disposed`);
9120
9773
  }
9121
- engine.setMidiInputSource(destinationId);
9122
- const bound = /* @__PURE__ */ new Map();
9774
+ }
9775
+ function requireFinite(value, field) {
9776
+ if (!Number.isFinite(value)) {
9777
+ throw new RangeError(`${field} must be finite`);
9778
+ }
9779
+ return value;
9780
+ }
9781
+ function requireSample(value, field) {
9782
+ requireFinite(value, field);
9783
+ if (!Number.isSafeInteger(value) || value < 0) {
9784
+ throw new RangeError(`${field} must be a non-negative safe integer`);
9785
+ }
9786
+ return value;
9787
+ }
9788
+ function requireUint32(value, field) {
9789
+ requireSample(value, field);
9790
+ if (value > 4294967295) {
9791
+ throw new RangeError(`${field} must fit uint32`);
9792
+ }
9793
+ return value;
9794
+ }
9795
+ function requireSampleRate(value) {
9796
+ requireSample(value, "sampleRate");
9797
+ if (value < 8e3 || value > 384e3) {
9798
+ throw new RangeError("sampleRate must be an integer in [8000, 384000]");
9799
+ }
9800
+ return value;
9801
+ }
9802
+ function requireToken(value, field, fallback = "0") {
9803
+ const token = value ?? fallback;
9804
+ if (typeof token !== "string" || !/^(0|[1-9]\d*)$/.test(token)) {
9805
+ throw new RangeError(`${field} must be a canonical decimal uint64 string`);
9806
+ }
9807
+ if (token.length > 20 || token.length === 20 && token > "18446744073709551615") {
9808
+ throw new RangeError(`${field} must fit uint64`);
9809
+ }
9810
+ return token;
9811
+ }
9812
+ function resolveRevision(argument, fallback) {
9813
+ const value = typeof argument === "string" ? argument : argument?.expectedRevision;
9814
+ return requireToken(value, "expectedRevision", fallback);
9815
+ }
9816
+ function requireRange(range) {
9817
+ if (!range || typeof range !== "object") {
9818
+ throw new TypeError("range must be an object");
9819
+ }
9820
+ const startSample = requireSample(range.startSample, "range.startSample");
9821
+ const endSample = requireSample(range.endSample, "range.endSample");
9822
+ if (endSample < startSample) {
9823
+ throw new RangeError("range.endSample must be >= startSample");
9824
+ }
9825
+ return { startSample, endSample };
9826
+ }
9827
+ function copyToken2(token) {
9828
+ return {
9829
+ sessionEpoch: requireToken(token.sessionEpoch, "sessionEpoch"),
9830
+ revision: requireToken(token.revision, "revision"),
9831
+ draftId: requireToken(token.draftId, "draftId"),
9832
+ generation: requireToken(token.generation, "generation"),
9833
+ requestId: requireToken(token.requestId, "requestId"),
9834
+ profileId: token.profileId
9835
+ };
9836
+ }
9837
+ function copyRange(range) {
9838
+ return { startSample: range.startSample, endSample: range.endSample };
9839
+ }
9840
+ function copyNotes(value) {
9841
+ const notes = Array.from(value.notes, (note) => ({
9842
+ ...note,
9843
+ amplitude: new Float32Array(note.amplitude),
9844
+ edit: {
9845
+ ...note.edit,
9846
+ pitch: {
9847
+ ...note.edit.pitch,
9848
+ target: note.edit.pitch.target.mode === "curve" ? {
9849
+ mode: "curve",
9850
+ points: note.edit.pitch.target.points.map((point) => ({ ...point }))
9851
+ } : { ...note.edit.pitch.target }
9852
+ },
9853
+ amplitudeEnvelope: Array.from(note.edit.amplitudeEnvelope),
9854
+ formant: { ...note.edit.formant }
9855
+ }
9856
+ }));
9857
+ return {
9858
+ notes,
9859
+ transitions: Array.from(value.transitions, (transition) => ({ ...transition }))
9860
+ };
9861
+ }
9862
+ function copyAnalysis(value) {
9863
+ return {
9864
+ frameOriginSample: value.frameOriginSample,
9865
+ samplesPerFrame: value.samplesPerFrame,
9866
+ frameLengthSamples: value.frameLengthSamples,
9867
+ f0Hz: new Float32Array(value.f0Hz),
9868
+ voiced: new Uint8Array(value.voiced),
9869
+ algorithmId: value.algorithmId,
9870
+ algorithmVersion: value.algorithmVersion,
9871
+ sourceLengthSamples: value.sourceLengthSamples,
9872
+ sampleRate: value.sampleRate,
9873
+ sourceSha256: value.sourceSha256,
9874
+ analysisSha256: value.analysisSha256,
9875
+ fminHz: value.fminHz,
9876
+ fmaxHz: value.fmaxHz,
9877
+ yinThreshold: value.yinThreshold,
9878
+ voicedThreshold: value.voicedThreshold,
9879
+ centered: value.centered,
9880
+ segmentationThresholdCents: value.segmentationThresholdCents,
9881
+ minNoteMs: value.minNoteMs,
9882
+ referenceHz: value.referenceHz
9883
+ };
9884
+ }
9885
+ function copyPitch(value) {
9886
+ return {
9887
+ sourceSamples: new Float64Array(value.sourceSamples),
9888
+ measuredMidi: new Float64Array(value.measuredMidi),
9889
+ targetMidi: new Float64Array(value.targetMidi),
9890
+ effectiveMidi: new Float64Array(value.effectiveMidi),
9891
+ voiced: new Uint8Array(value.voiced),
9892
+ hasTarget: new Uint8Array(value.hasTarget)
9893
+ };
9894
+ }
9895
+ function copyRender(value) {
9896
+ return {
9897
+ samples: new Float32Array(value.samples),
9898
+ startSample: value.startSample,
9899
+ token: copyToken2(value.token),
9900
+ processedRanges: Array.from(value.processedRanges, copyRange),
9901
+ cacheHitUnits: requireToken(value.cacheHitUnits, "cacheHitUnits"),
9902
+ dryPassedFrames: requireToken(value.dryPassedFrames, "dryPassedFrames"),
9903
+ limitedCorrectionFrames: requireToken(value.limitedCorrectionFrames, "limitedCorrectionFrames")
9904
+ };
9905
+ }
9906
+ function validateSessionLimits(limits) {
9907
+ if (limits === void 0) {
9908
+ return;
9909
+ }
9910
+ if (limits.maxHistoryBytes !== void 0) {
9911
+ requireToken(limits.maxHistoryBytes, "limits.maxHistoryBytes");
9912
+ }
9913
+ if (limits.maxCacheBytes !== void 0) {
9914
+ requireToken(limits.maxCacheBytes, "limits.maxCacheBytes");
9915
+ }
9916
+ }
9917
+ function copyEditResult(value) {
9918
+ return {
9919
+ token: copyToken2(value.token),
9920
+ dirtyRanges: Array.from(value.dirtyRanges, copyRange),
9921
+ idChanges: Array.from(value.idChanges, (change) => ({
9922
+ operationIndex: change.operationIndex,
9923
+ retiredId: change.retiredId,
9924
+ newIds: Array.from(change.newIds)
9925
+ }))
9926
+ };
9927
+ }
9928
+ function validateCreate(request) {
9929
+ if (!request || typeof request !== "object") {
9930
+ throw new TypeError("request must be an object");
9931
+ }
9932
+ if (!(request.samples instanceof Float32Array) || request.samples.length === 0) {
9933
+ throw new TypeError("samples must be a non-empty Float32Array");
9934
+ }
9935
+ for (const sample of request.samples) {
9936
+ requireFinite(sample, "samples");
9937
+ }
9938
+ requireSampleRate(request.sampleRate);
9939
+ if (request.outputLengthSamples !== void 0) {
9940
+ requireSample(request.outputLengthSamples, "outputLengthSamples");
9941
+ }
9942
+ validateSessionLimits(request.limits);
9943
+ if (request.analysis) {
9944
+ const analysis = request.analysis;
9945
+ if (analysis.algorithmId !== void 0) {
9946
+ if (typeof analysis.algorithmId !== "string") {
9947
+ throw new TypeError("analysis.algorithmId must be a string");
9948
+ }
9949
+ if (analysis.algorithmId.includes("\0")) {
9950
+ throw new RangeError("analysis.algorithmId must not contain NUL");
9951
+ }
9952
+ }
9953
+ requireFinite(analysis.frameOriginSample, "analysis.frameOriginSample");
9954
+ requireFinite(analysis.samplesPerFrame, "analysis.samplesPerFrame");
9955
+ if (analysis.samplesPerFrame <= 0) {
9956
+ throw new RangeError("analysis.samplesPerFrame must be positive");
9957
+ }
9958
+ requireSample(analysis.frameLengthSamples, "analysis.frameLengthSamples");
9959
+ if (analysis.frameLengthSamples === 0) {
9960
+ throw new RangeError("analysis.frameLengthSamples must be positive");
9961
+ }
9962
+ if (!(analysis.f0Hz instanceof Float32Array) || !(analysis.voiced instanceof Uint8Array)) {
9963
+ throw new TypeError("analysis arrays must be Float32Array and Uint8Array");
9964
+ }
9965
+ if (analysis.f0Hz.length !== analysis.voiced.length) {
9966
+ throw new RangeError("analysis arrays must have equal lengths");
9967
+ }
9968
+ }
9969
+ }
9970
+ function normalizeRequest(request, outputLengthSamples) {
9971
+ const range = request.range === void 0 ? { startSample: 0, endSample: requireSample(outputLengthSamples, "outputLengthSamples") } : requireRange(request.range);
9972
+ const requestId = requireToken(request.requestId, "requestId");
9973
+ return { range, requestId };
9974
+ }
9975
+ function vocalEditAvailable() {
9976
+ const raw = module();
9977
+ return typeof raw.vocalEditAvailable === "function" && raw.vocalEditAvailable() !== 0;
9978
+ }
9979
+ function vocalEditApiVersion() {
9980
+ const raw = module();
9981
+ return typeof raw.vocalEditApiVersion === "function" ? raw.vocalEditApiVersion() : 0;
9982
+ }
9983
+ function createVocalEditSession(request) {
9984
+ validateCreate(request);
9985
+ if (!vocalEditAvailable()) {
9986
+ throw new Error("vocal edit is unavailable in this WASM build");
9987
+ }
9988
+ return new VocalEditSession(
9989
+ module().vocalEditSessionCreate(request.samples, request.sampleRate, request)
9990
+ );
9991
+ }
9992
+ function restoreVocalEditSession(request) {
9993
+ if (!(request.samples instanceof Float32Array) || request.samples.length === 0) {
9994
+ throw new TypeError("samples must be a non-empty Float32Array");
9995
+ }
9996
+ if (!(request.state instanceof Uint8Array)) {
9997
+ throw new TypeError("state must be a Uint8Array");
9998
+ }
9999
+ for (const sample of request.samples) {
10000
+ requireFinite(sample, "samples");
10001
+ }
10002
+ requireSampleRate(request.sampleRate);
10003
+ validateSessionLimits(request.limits);
10004
+ if (!vocalEditAvailable()) {
10005
+ throw new Error("vocal edit is unavailable in this WASM build");
10006
+ }
10007
+ return new VocalEditSession(
10008
+ module().vocalEditSessionRestore(request.samples, request.sampleRate, request.state, request)
10009
+ );
10010
+ }
10011
+ var VocalEditSession = class {
10012
+ /** @internal Instances are created by {@link createVocalEditSession} and {@link restoreVocalEditSession}. */
10013
+ constructor(handle) {
10014
+ this.drafts = /* @__PURE__ */ new Set();
10015
+ this.disposed = false;
10016
+ requireHandle(handle, "session");
10017
+ this.handle = handle;
10018
+ }
10019
+ dispose() {
10020
+ if (this.disposed) {
10021
+ return;
10022
+ }
10023
+ for (const draft of this.drafts) {
10024
+ draft.dispose();
10025
+ }
10026
+ this.drafts.clear();
10027
+ module().vocalEditSessionDestroy(this.handle);
10028
+ this.handle = 0;
10029
+ this.disposed = true;
10030
+ }
10031
+ delete() {
10032
+ this.dispose();
10033
+ }
10034
+ destroy() {
10035
+ this.dispose();
10036
+ }
10037
+ native() {
10038
+ if (this.disposed) {
10039
+ throw new TypeError("session has already been disposed");
10040
+ }
10041
+ requireHandle(this.handle, "session");
10042
+ return this.handle;
10043
+ }
10044
+ token() {
10045
+ return copyToken2(module().vocalEditSessionToken(this.native()));
10046
+ }
10047
+ revision() {
10048
+ return this.token().revision;
10049
+ }
10050
+ notes() {
10051
+ return copyNotes(module().vocalEditSessionNotes(this.native()));
10052
+ }
10053
+ analysis() {
10054
+ return copyAnalysis(module().vocalEditSessionAnalysis(this.native()));
10055
+ }
10056
+ capabilities() {
10057
+ return { ...module().vocalEditSessionCapabilities(this.native()) };
10058
+ }
10059
+ outputLengthSamples() {
10060
+ return requireSample(
10061
+ module().vocalEditSessionOutputLength(this.native()),
10062
+ "outputLengthSamples"
10063
+ );
10064
+ }
10065
+ history() {
10066
+ const value = module().vocalEditSessionHistory(this.native());
10067
+ return { canUndo: value.canUndo === true, canRedo: value.canRedo === true };
10068
+ }
10069
+ beginEdit(options = {}) {
10070
+ const draft = new VocalEditDraft(
10071
+ module().vocalEditSessionBeginEdit(this.native(), resolveRevision(options, this.revision())),
10072
+ this
10073
+ );
10074
+ this.drafts.add(draft);
10075
+ return draft;
10076
+ }
10077
+ undo(options = {}) {
10078
+ return copyEditResult(
10079
+ module().vocalEditSessionApplyHistory(
10080
+ this.native(),
10081
+ resolveRevision(options, this.revision()),
10082
+ false
10083
+ )
10084
+ );
10085
+ }
10086
+ redo(options = {}) {
10087
+ return copyEditResult(
10088
+ module().vocalEditSessionApplyHistory(
10089
+ this.native(),
10090
+ resolveRevision(options, this.revision()),
10091
+ true
10092
+ )
10093
+ );
10094
+ }
10095
+ evaluatePitch(noteId) {
10096
+ return copyPitch(
10097
+ module().vocalEditEvaluatePitch(this.native(), requireUint32(noteId, "noteId"), false)
10098
+ );
10099
+ }
10100
+ sourceSampleToDestinationSample(noteId, sourceSample) {
10101
+ requireFinite(sourceSample, "sourceSample");
10102
+ return module().vocalEditMapCoordinate(
10103
+ this.native(),
10104
+ requireUint32(noteId, "noteId"),
10105
+ sourceSample,
10106
+ false,
10107
+ false
10108
+ );
10109
+ }
10110
+ destinationSampleToSourceSample(noteId, destinationSample) {
10111
+ requireFinite(destinationSample, "destinationSample");
10112
+ return module().vocalEditMapCoordinate(
10113
+ this.native(),
10114
+ requireUint32(noteId, "noteId"),
10115
+ destinationSample,
10116
+ false,
10117
+ true
10118
+ );
10119
+ }
10120
+ captureRenderSnapshot() {
10121
+ return new VocalRenderSnapshot(module().vocalEditCaptureSnapshot(this.native(), false));
10122
+ }
10123
+ exportState() {
10124
+ return new Uint8Array(module().vocalEditSessionExportState(this.native()));
10125
+ }
10126
+ /** @internal Used by the draft to remove itself from the owner set. */
10127
+ _forgetDraft(draft) {
10128
+ this.drafts.delete(draft);
10129
+ }
10130
+ };
10131
+ var VocalEditDraft = class {
10132
+ /** @internal Instances are created by {@link VocalEditSession.beginEdit}. */
10133
+ constructor(handle, owner) {
10134
+ this.disposed = false;
10135
+ requireHandle(handle, "draft");
10136
+ this.handle = handle;
10137
+ this.owner = owner;
10138
+ }
10139
+ native() {
10140
+ if (this.disposed) {
10141
+ throw new TypeError("draft has already been disposed");
10142
+ }
10143
+ requireHandle(this.handle, "draft");
10144
+ return this.handle;
10145
+ }
10146
+ token() {
10147
+ return copyToken2(module().vocalEditDraftToken(this.native()));
10148
+ }
10149
+ notes() {
10150
+ return copyNotes(module().vocalEditDraftNotes(this.native()));
10151
+ }
10152
+ apply(request) {
10153
+ if (request.expectedGeneration === void 0) {
10154
+ throw new TypeError("expectedGeneration is required");
10155
+ }
10156
+ const generation = requireToken(request.expectedGeneration, "expectedGeneration");
10157
+ if (!Array.isArray(request.operations)) {
10158
+ throw new TypeError("operations must be an array");
10159
+ }
10160
+ return copyEditResult(
10161
+ module().vocalEditDraftApply(this.native(), generation, request.operations)
10162
+ );
10163
+ }
10164
+ commit(options = {}) {
10165
+ const result = copyEditResult(
10166
+ module().vocalEditDraftCommit(this.native(), resolveRevision(options, this.owner.revision()))
10167
+ );
10168
+ this.dispose();
10169
+ return result;
10170
+ }
10171
+ cancel() {
10172
+ if (this.disposed) {
10173
+ return;
10174
+ }
10175
+ module().vocalEditDraftCancel(this.native());
10176
+ this.dispose();
10177
+ }
10178
+ evaluatePitch(noteId) {
10179
+ return copyPitch(
10180
+ module().vocalEditEvaluatePitch(this.native(), requireUint32(noteId, "noteId"), true)
10181
+ );
10182
+ }
10183
+ sourceSampleToDestinationSample(noteId, sourceSample) {
10184
+ requireFinite(sourceSample, "sourceSample");
10185
+ return module().vocalEditMapCoordinate(
10186
+ this.native(),
10187
+ requireUint32(noteId, "noteId"),
10188
+ sourceSample,
10189
+ true,
10190
+ false
10191
+ );
10192
+ }
10193
+ destinationSampleToSourceSample(noteId, destinationSample) {
10194
+ requireFinite(destinationSample, "destinationSample");
10195
+ return module().vocalEditMapCoordinate(
10196
+ this.native(),
10197
+ requireUint32(noteId, "noteId"),
10198
+ destinationSample,
10199
+ true,
10200
+ true
10201
+ );
10202
+ }
10203
+ captureRenderSnapshot() {
10204
+ return new VocalRenderSnapshot(module().vocalEditCaptureSnapshot(this.native(), true));
10205
+ }
10206
+ dispose() {
10207
+ if (this.disposed) {
10208
+ return;
10209
+ }
10210
+ module().vocalEditDraftDestroy(this.handle);
10211
+ this.handle = 0;
10212
+ this.disposed = true;
10213
+ this.owner._forgetDraft(this);
10214
+ }
10215
+ delete() {
10216
+ this.dispose();
10217
+ }
10218
+ destroy() {
10219
+ this.dispose();
10220
+ }
10221
+ };
10222
+ var VocalRenderSnapshot = class {
10223
+ /** @internal Instances are created by a session or draft. */
10224
+ constructor(handle) {
10225
+ this.disposed = false;
10226
+ requireHandle(handle, "snapshot");
10227
+ this.handle = handle;
10228
+ }
10229
+ native() {
10230
+ if (this.disposed) {
10231
+ throw new TypeError("snapshot has already been disposed");
10232
+ }
10233
+ requireHandle(this.handle, "snapshot");
10234
+ return this.handle;
10235
+ }
10236
+ outputLengthSamples() {
10237
+ return requireSample(
10238
+ module().vocalEditSnapshotOutputLength(this.native()),
10239
+ "outputLengthSamples"
10240
+ );
10241
+ }
10242
+ render(request) {
10243
+ const handle = this.native();
10244
+ if (request.signal?.aborted) {
10245
+ throw new DOMException("The render was aborted", "AbortError");
10246
+ }
10247
+ const normalized = normalizeRequest(request, this.outputLengthSamples());
10248
+ return copyRender(
10249
+ module().vocalEditSnapshotRender(
10250
+ handle,
10251
+ normalized.range.startSample,
10252
+ normalized.range.endSample,
10253
+ normalized.requestId
10254
+ )
10255
+ );
10256
+ }
10257
+ beginRenderJob(request) {
10258
+ const handle = this.native();
10259
+ if (request.signal?.aborted) {
10260
+ throw new DOMException("The render was aborted", "AbortError");
10261
+ }
10262
+ const normalized = normalizeRequest(request, this.outputLengthSamples());
10263
+ return new VocalRenderJob(
10264
+ module().vocalEditRenderJobBegin(
10265
+ handle,
10266
+ normalized.range.startSample,
10267
+ normalized.range.endSample,
10268
+ normalized.requestId
10269
+ )
10270
+ );
10271
+ }
10272
+ /**
10273
+ * Render one native work unit per macrotask so `request.signal` can stop the
10274
+ * job between units. A cancelled job publishes no partial PCM.
10275
+ */
10276
+ renderAsync(request) {
10277
+ this.native();
10278
+ const signal = request.signal;
10279
+ if (signal?.aborted) {
10280
+ return Promise.reject(new DOMException("The render was aborted", "AbortError"));
10281
+ }
10282
+ const job = this.beginRenderJob(request);
10283
+ return (async () => {
10284
+ try {
10285
+ while (true) {
10286
+ if (signal?.aborted) {
10287
+ job.abort();
10288
+ throw new DOMException("The render was aborted", "AbortError");
10289
+ }
10290
+ if (job.next().complete) {
10291
+ return job.finalize();
10292
+ }
10293
+ await new Promise((resolve) => setTimeout(resolve, 0));
10294
+ }
10295
+ } catch (error) {
10296
+ job.abort();
10297
+ throw error;
10298
+ } finally {
10299
+ job.dispose();
10300
+ }
10301
+ })();
10302
+ }
10303
+ dispose() {
10304
+ if (this.disposed) {
10305
+ return;
10306
+ }
10307
+ module().vocalEditSnapshotDestroy(this.handle);
10308
+ this.handle = 0;
10309
+ this.disposed = true;
10310
+ }
10311
+ delete() {
10312
+ this.dispose();
10313
+ }
10314
+ destroy() {
10315
+ this.dispose();
10316
+ }
10317
+ };
10318
+ var VocalRenderJob = class {
10319
+ /** @internal Instances are created by {@link VocalRenderSnapshot.beginRenderJob}. */
10320
+ constructor(handle) {
10321
+ this.disposed = false;
10322
+ requireHandle(handle, "render job");
10323
+ this.handle = handle;
10324
+ }
10325
+ native() {
10326
+ if (this.disposed) {
10327
+ throw new TypeError("render job has already been disposed");
10328
+ }
10329
+ requireHandle(this.handle, "render job");
10330
+ return this.handle;
10331
+ }
10332
+ next() {
10333
+ return { complete: module().vocalEditRenderJobNext(this.native()) };
10334
+ }
10335
+ finalize() {
10336
+ const result = copyRender(module().vocalEditRenderJobFinalize(this.native()));
10337
+ this.dispose();
10338
+ return result;
10339
+ }
10340
+ abort() {
10341
+ if (!this.disposed) {
10342
+ module().vocalEditRenderJobAbort(this.handle);
10343
+ }
10344
+ }
10345
+ dispose() {
10346
+ if (this.disposed) {
10347
+ return;
10348
+ }
10349
+ module().vocalEditRenderJobDestroy(this.handle);
10350
+ this.handle = 0;
10351
+ this.disposed = true;
10352
+ }
10353
+ delete() {
10354
+ this.dispose();
10355
+ }
10356
+ destroy() {
10357
+ this.dispose();
10358
+ }
10359
+ };
10360
+
10361
+ // src/vocal_edit_worker_protocol.ts
10362
+ function stateTokenEquals(a, b) {
10363
+ if (!a || !b) {
10364
+ return a === b;
10365
+ }
10366
+ return a.sessionEpoch === b.sessionEpoch && a.revision === b.revision && a.draftId === b.draftId && a.generation === b.generation && a.profileId === b.profileId;
10367
+ }
10368
+ function compareUint64(a, b) {
10369
+ const left = a.replace(/^0+(?=\d)/, "");
10370
+ const right = b.replace(/^0+(?=\d)/, "");
10371
+ if (left.length !== right.length) {
10372
+ return left.length < right.length ? -1 : 1;
10373
+ }
10374
+ if (left === right) {
10375
+ return 0;
10376
+ }
10377
+ return left < right ? -1 : 1;
10378
+ }
10379
+
10380
+ // src/vocal_edit_worker_client.ts
10381
+ var VocalEditWorkerStaleResultError = class extends Error {
10382
+ constructor(message = "A vocal edit Worker result is stale") {
10383
+ super(message);
10384
+ this.name = "StaleResultError";
10385
+ }
10386
+ };
10387
+ var VocalEditWorkerTask = class {
10388
+ constructor(result, cancelRequest) {
10389
+ this.result = result;
10390
+ this.cancelRequest = cancelRequest;
10391
+ }
10392
+ cancel() {
10393
+ this.cancelRequest();
10394
+ }
10395
+ // biome-ignore lint/suspicious/noThenProperty: PromiseLike intentionally exposes then.
10396
+ then(onfulfilled, onrejected) {
10397
+ return this.result.then(onfulfilled, onrejected);
10398
+ }
10399
+ catch(onrejected) {
10400
+ return this.result.catch(onrejected);
10401
+ }
10402
+ finally(onfinally) {
10403
+ return this.result.finally(onfinally);
10404
+ }
10405
+ };
10406
+ function resolveRevision2(argument, fallback) {
10407
+ const value = typeof argument === "string" ? argument : argument?.expectedRevision;
10408
+ return value ?? fallback;
10409
+ }
10410
+ function incrementDecimal(value) {
10411
+ if (compareUint64(value, "18446744073709551615") >= 0) {
10412
+ throw new RangeError("vocal Worker intent sequence exhausted uint64");
10413
+ }
10414
+ const digits = value.split("").map((digit) => digit.charCodeAt(0) - 48);
10415
+ let carry = 1;
10416
+ for (let i = digits.length - 1; i >= 0; i--) {
10417
+ const next = digits[i] + carry;
10418
+ digits[i] = next % 10;
10419
+ carry = Math.floor(next / 10);
10420
+ }
10421
+ if (carry !== 0) {
10422
+ digits.unshift(carry);
10423
+ }
10424
+ return digits.join("");
10425
+ }
10426
+ function cancellationFlag() {
10427
+ if (typeof SharedArrayBuffer === "undefined") {
10428
+ return void 0;
10429
+ }
10430
+ return new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
10431
+ }
10432
+ function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
10433
+ if (value instanceof Float32Array) {
10434
+ const next = copy ? value.slice() : value;
10435
+ if (next.buffer instanceof ArrayBuffer && !transferred.has(next.buffer)) {
10436
+ transferred.add(next.buffer);
10437
+ transfers.push(next.buffer);
10438
+ }
10439
+ return next;
10440
+ }
10441
+ if (value instanceof Uint8Array) {
10442
+ const next = copy ? value.slice() : value;
10443
+ if (next.buffer instanceof ArrayBuffer && !transferred.has(next.buffer)) {
10444
+ transferred.add(next.buffer);
10445
+ transfers.push(next.buffer);
10446
+ }
10447
+ return next;
10448
+ }
10449
+ if (Array.isArray(value)) {
10450
+ return value.map((item) => cloneForWorker(item, copy, transfers, transferred));
10451
+ }
10452
+ if (value !== null && typeof value === "object") {
10453
+ return Object.fromEntries(
10454
+ Object.entries(value).map(([key, item]) => [
10455
+ key,
10456
+ cloneForWorker(item, copy, transfers, transferred)
10457
+ ])
10458
+ );
10459
+ }
10460
+ return value;
10461
+ }
10462
+ function cloneRequest(request, copy, transfers) {
10463
+ return cloneForWorker(request, copy, transfers);
10464
+ }
10465
+ function workerError(message) {
10466
+ if (message.error.name === "StaleResultError") {
10467
+ return new VocalEditWorkerStaleResultError(message.error.message);
10468
+ }
10469
+ const { code, codeName } = message.error;
10470
+ const error = message.error.name === "SonareError" && typeof code === "number" ? new SonareError(code, codeName ?? "", message.error.message) : new Error(message.error.message);
10471
+ error.name = message.error.name;
10472
+ for (const key of [
10473
+ "code",
10474
+ "reason",
10475
+ "field",
10476
+ "expected",
10477
+ "actual",
10478
+ "expectedText",
10479
+ "actualText"
10480
+ ]) {
10481
+ const value = message.error[key];
10482
+ if (value !== void 0) {
10483
+ Object.defineProperty(error, key, { value, enumerable: true, configurable: true });
10484
+ }
10485
+ }
10486
+ return error;
10487
+ }
10488
+ function sameSessionToken(a, b) {
10489
+ return a.sessionEpoch === b.sessionEpoch && a.profileId === b.profileId;
10490
+ }
10491
+ function nextSessionId(counter) {
10492
+ return `vocal-${counter.toString(36)}`;
10493
+ }
10494
+ function abortError() {
10495
+ return new DOMException("The operation was aborted", "AbortError");
10496
+ }
10497
+ var VocalEditWorkerClient = class {
10498
+ constructor(options = {}) {
10499
+ this.pending = /* @__PURE__ */ new Map();
10500
+ this.latestIntent = /* @__PURE__ */ new Map();
10501
+ this.sessions = /* @__PURE__ */ new Set();
10502
+ this.nextId = 1;
10503
+ this.nextSession = 1;
10504
+ this.closed = false;
10505
+ this.usesEventTarget = false;
10506
+ this.onMessage = (event) => {
10507
+ const message = event.data;
10508
+ if (message.type === "sonare:vocal-progress") {
10509
+ return;
10510
+ }
10511
+ const pending = this.pending.get(message.id);
10512
+ if (!pending) {
10513
+ return;
10514
+ }
10515
+ this.pending.delete(message.id);
10516
+ if (pending.abortListener && pending.signal) {
10517
+ pending.signal.removeEventListener("abort", pending.abortListener);
10518
+ }
10519
+ if (!pending.preview && pending.session && message.token && pending.baseToken && sameSessionToken(message.token, pending.baseToken)) {
10520
+ pending.session.updateToken(message.token);
10521
+ }
10522
+ const latest = this.latestIntent.get(pending.sessionId) ?? pending.intent;
10523
+ if (compareUint64(pending.intent, latest) < 0) {
10524
+ pending.reject(new VocalEditWorkerStaleResultError());
10525
+ return;
10526
+ }
10527
+ if (message.token && pending.baseToken) {
10528
+ const tokenMatches = pending.preview && pending.session ? stateTokenEquals(message.token, pending.session.latestToken()) : sameSessionToken(message.token, pending.baseToken);
10529
+ if (!tokenMatches) {
10530
+ pending.reject(new VocalEditWorkerStaleResultError());
10531
+ return;
10532
+ }
10533
+ }
10534
+ if (pending.preview && !message.token) {
10535
+ pending.reject(new VocalEditWorkerStaleResultError());
10536
+ return;
10537
+ }
10538
+ if (message.type === "sonare:vocal-error") {
10539
+ pending.reject(workerError(message));
10540
+ return;
10541
+ }
10542
+ pending.resolve({ result: message.result, token: message.token });
10543
+ };
10544
+ this.onError = (event) => {
10545
+ const error = new Error(event.message || "Vocal edit Worker failed");
10546
+ for (const pending of this.pending.values()) {
10547
+ pending.abortListener && pending.signal?.removeEventListener("abort", pending.abortListener);
10548
+ pending.reject(error);
10549
+ }
10550
+ this.pending.clear();
10551
+ };
10552
+ this.onNodeMessage = (data) => {
10553
+ this.onMessage({ data });
10554
+ };
10555
+ this.onNodeError = (error) => {
10556
+ this.onError({ message: error instanceof Error ? error.message : String(error) });
10557
+ };
10558
+ this.ownsWorker = options.worker === void 0 || options.terminateWorkerOnDispose === true;
10559
+ if (options.worker) {
10560
+ this.worker = options.worker;
10561
+ } else {
10562
+ if (!options.workerFactory && typeof Worker === "undefined") {
10563
+ throw new Error("VocalEditWorkerClient requires a Worker implementation");
10564
+ }
10565
+ const url = options.workerUrl === void 0 ? new URL("./vocal_edit_worker.js", import.meta.url) : new URL(options.workerUrl, import.meta.url);
10566
+ this.worker = options.workerFactory?.(url) ?? new Worker(url, { type: "module", name: "sonare-vocal-edit" });
10567
+ }
10568
+ if (this.worker.addEventListener) {
10569
+ this.usesEventTarget = true;
10570
+ this.worker.addEventListener("message", this.onMessage);
10571
+ this.worker.addEventListener("error", this.onError);
10572
+ } else if (this.worker.on) {
10573
+ this.worker.on("message", this.onNodeMessage);
10574
+ this.worker.on("error", this.onNodeError);
10575
+ } else {
10576
+ throw new TypeError("VocalEditWorkerClient requires Worker event listeners");
10577
+ }
10578
+ }
10579
+ create(request, options = {}) {
10580
+ return this.openSession({ kind: "create", request }, options);
10581
+ }
10582
+ restore(request, options = {}) {
10583
+ return this.openSession({ kind: "restore", request }, options);
10584
+ }
10585
+ postDisposeMessage(sessionId, clientIntentSequence) {
10586
+ try {
10587
+ this.worker.postMessage({
10588
+ type: "sonare:vocal-dispose",
10589
+ id: this.nextId++,
10590
+ sessionId,
10591
+ clientIntentSequence
10592
+ });
10593
+ } catch {
10594
+ }
10595
+ }
10596
+ postCancelMessage(id, pending) {
10597
+ if (pending.cancelFlag) {
10598
+ Atomics.store(pending.cancelFlag, 0, 1);
10599
+ }
10600
+ try {
10601
+ this.worker.postMessage({
10602
+ type: "sonare:vocal-cancel",
10603
+ id,
10604
+ sessionId: pending.sessionId,
10605
+ clientIntentSequence: pending.intent
10606
+ });
10607
+ } catch {
10608
+ }
10609
+ }
10610
+ dispose() {
10611
+ if (this.closed) {
10612
+ return;
10613
+ }
10614
+ const pendingEntries = [...this.pending.entries()];
10615
+ const sessionIntents = /* @__PURE__ */ new Map();
10616
+ for (const session of this.sessions) {
10617
+ sessionIntents.set(session.sessionId, this.latestIntent.get(session.sessionId) ?? "0");
10618
+ }
10619
+ for (const [, pending] of pendingEntries) {
10620
+ if (!sessionIntents.has(pending.sessionId)) {
10621
+ sessionIntents.set(pending.sessionId, pending.intent);
10622
+ }
10623
+ }
10624
+ this.closed = true;
10625
+ for (const [id, pending] of pendingEntries) {
10626
+ this.postCancelMessage(id, pending);
10627
+ }
10628
+ for (const [sessionId, intent] of sessionIntents) {
10629
+ this.postDisposeMessage(sessionId, intent);
10630
+ }
10631
+ if (this.usesEventTarget) {
10632
+ this.worker.removeEventListener?.("message", this.onMessage);
10633
+ this.worker.removeEventListener?.("error", this.onError);
10634
+ } else {
10635
+ this.worker.off?.("message", this.onNodeMessage);
10636
+ this.worker.off?.("error", this.onNodeError);
10637
+ }
10638
+ for (const [, pending] of pendingEntries) {
10639
+ pending.abortListener && pending.signal?.removeEventListener("abort", pending.abortListener);
10640
+ pending.reject(new Error("VocalEditWorkerClient was disposed"));
10641
+ }
10642
+ this.pending.clear();
10643
+ this.sessions.clear();
10644
+ this.latestIntent.clear();
10645
+ if (this.ownsWorker) {
10646
+ this.worker.terminate();
10647
+ }
10648
+ }
10649
+ /** @internal Called by VocalEditWorkerSession. */
10650
+ disposeSession(session) {
10651
+ const task = this.call({
10652
+ type: "sonare:vocal-dispose",
10653
+ id: 0,
10654
+ sessionId: session.sessionId,
10655
+ clientIntentSequence: this.intent(session.sessionId)
10656
+ });
10657
+ const result = task.result.then(() => {
10658
+ this.sessions.delete(session);
10659
+ this.latestIntent.delete(session.sessionId);
10660
+ return null;
10661
+ });
10662
+ return new VocalEditWorkerTask(result, () => task.cancel());
10663
+ }
10664
+ /** @internal */
10665
+ callMutation(session, mutation, options = {}) {
10666
+ const message = {
10667
+ type: "sonare:vocal-mutate",
10668
+ id: 0,
10669
+ sessionId: session.sessionId,
10670
+ clientIntentSequence: this.intent(session.sessionId),
10671
+ baseToken: session.currentToken(),
10672
+ mutation
10673
+ };
10674
+ const task = this.call(message, { copy: options.copy }, session);
10675
+ const result = task.result.then((response) => response.result);
10676
+ return new VocalEditWorkerTask(result, () => {
10677
+ });
10678
+ }
10679
+ /** @internal */
10680
+ callPreview(session, request, options = {}) {
10681
+ const signal = request.signal;
10682
+ const { signal: _ignoredSignal, ...portableRequest } = request;
10683
+ const message = {
10684
+ type: "sonare:vocal-preview",
10685
+ id: 0,
10686
+ sessionId: session.sessionId,
10687
+ clientIntentSequence: this.intent(session.sessionId),
10688
+ baseToken: session.currentToken(),
10689
+ request: portableRequest
10690
+ };
10691
+ const task = this.call(message, { signal, copy: options.copy }, session);
10692
+ const result = task.result.then((response) => response.result);
10693
+ return new VocalEditWorkerTask(result, () => task.cancel());
10694
+ }
10695
+ intent(sessionId) {
10696
+ const next = incrementDecimal(this.latestIntent.get(sessionId) ?? "0");
10697
+ this.latestIntent.set(sessionId, next);
10698
+ return next;
10699
+ }
10700
+ openSession(request, options) {
10701
+ const sessionId = nextSessionId(this.nextSession++);
10702
+ let cancelled = false;
10703
+ const task = this.callCreate(sessionId, request, options);
10704
+ const result = task.result.then((value) => {
10705
+ const session = new VocalEditWorkerSession(this, sessionId, value.token, value);
10706
+ if (cancelled || options.signal?.aborted) {
10707
+ if (!this.closed) {
10708
+ session.dispose().result.catch(() => {
10709
+ });
10710
+ }
10711
+ throw abortError();
10712
+ }
10713
+ this.sessions.add(session);
10714
+ return session;
10715
+ });
10716
+ return new VocalEditWorkerTask(result, () => {
10717
+ cancelled = true;
10718
+ task.cancel();
10719
+ });
10720
+ }
10721
+ callCreate(sessionId, request, options) {
10722
+ const message = {
10723
+ type: "sonare:vocal-create",
10724
+ id: 0,
10725
+ sessionId,
10726
+ clientIntentSequence: this.intent(sessionId),
10727
+ request
10728
+ };
10729
+ const task = this.call(message, options);
10730
+ const result = task.result.then((response) => response.result);
10731
+ return new VocalEditWorkerTask(result, () => task.cancel());
10732
+ }
10733
+ call(input, options = {}, session) {
10734
+ if (this.closed) {
10735
+ throw new Error("VocalEditWorkerClient was disposed");
10736
+ }
10737
+ const id = this.nextId++;
10738
+ const transfers = [];
10739
+ const message = cloneRequest(
10740
+ { ...input, id },
10741
+ options.copy === true,
10742
+ transfers
10743
+ );
10744
+ const cancelFlag = cancellationFlag();
10745
+ if (cancelFlag) {
10746
+ message.cancelBuffer = cancelFlag.buffer;
10747
+ }
10748
+ const result = new Promise((resolve, reject) => {
10749
+ if (options.signal?.aborted) {
10750
+ reject(abortError());
10751
+ return;
10752
+ }
10753
+ const pending = {
10754
+ resolve: (value) => resolve(value),
10755
+ reject,
10756
+ sessionId: message.sessionId,
10757
+ intent: message.clientIntentSequence,
10758
+ baseToken: message.type === "sonare:vocal-mutate" || message.type === "sonare:vocal-preview" ? message.baseToken : void 0,
10759
+ preview: message.type === "sonare:vocal-preview",
10760
+ session,
10761
+ cancelFlag,
10762
+ signal: options.signal
10763
+ };
10764
+ if (options.signal) {
10765
+ pending.abortListener = () => {
10766
+ if (cancelFlag) {
10767
+ Atomics.store(cancelFlag, 0, 1);
10768
+ }
10769
+ this.worker.postMessage({
10770
+ type: "sonare:vocal-cancel",
10771
+ id,
10772
+ sessionId: message.sessionId,
10773
+ clientIntentSequence: message.clientIntentSequence
10774
+ });
10775
+ };
10776
+ options.signal.addEventListener("abort", pending.abortListener, { once: true });
10777
+ }
10778
+ this.pending.set(id, pending);
10779
+ });
10780
+ if (!options.signal?.aborted) {
10781
+ try {
10782
+ this.worker.postMessage(message, transfers);
10783
+ } catch (error) {
10784
+ const pending = this.pending.get(id);
10785
+ if (pending) {
10786
+ this.pending.delete(id);
10787
+ if (pending.abortListener && pending.signal) {
10788
+ pending.signal.removeEventListener("abort", pending.abortListener);
10789
+ }
10790
+ pending.reject(error);
10791
+ }
10792
+ }
10793
+ }
10794
+ return new VocalEditWorkerTask(result, () => {
10795
+ const pending = this.pending.get(id);
10796
+ if (!pending) {
10797
+ return;
10798
+ }
10799
+ if (pending.cancelFlag) {
10800
+ Atomics.store(pending.cancelFlag, 0, 1);
10801
+ }
10802
+ this.worker.postMessage({
10803
+ type: "sonare:vocal-cancel",
10804
+ id,
10805
+ sessionId: pending.sessionId,
10806
+ clientIntentSequence: pending.intent
10807
+ });
10808
+ });
10809
+ }
10810
+ };
10811
+ var VocalEditWorkerSession = class {
10812
+ /** @internal Created by VocalEditWorkerClient.create/restore. */
10813
+ constructor(client, sessionId, token, created) {
10814
+ this.disposed = false;
10815
+ this.client = client;
10816
+ this.sessionId = sessionId;
10817
+ this.tokenValue = token;
10818
+ this.created = created;
10819
+ }
10820
+ currentToken() {
10821
+ this.requireAlive();
10822
+ return this.tokenValue;
10823
+ }
10824
+ updateToken(token) {
10825
+ this.tokenValue = token;
10826
+ }
10827
+ /** @internal The adopted token, readable after dispose for reply checks. */
10828
+ latestToken() {
10829
+ return this.tokenValue;
10830
+ }
10831
+ token() {
10832
+ this.requireAlive();
10833
+ return { ...this.tokenValue };
10834
+ }
10835
+ /** The adopted revision; like {@link token}, it follows replies already received. */
10836
+ revision() {
10837
+ return this.token().revision;
10838
+ }
10839
+ notes() {
10840
+ this.requireAlive();
10841
+ return this.client.callMutation(this, { kind: "notes" });
10842
+ }
10843
+ analysis() {
10844
+ this.requireAlive();
10845
+ return this.client.callMutation(this, { kind: "analysis" });
10846
+ }
10847
+ capabilities() {
10848
+ this.requireAlive();
10849
+ return this.client.callMutation(this, { kind: "capabilities" });
10850
+ }
10851
+ outputLengthSamples() {
10852
+ this.requireAlive();
10853
+ return this.client.callMutation(this, { kind: "outputLength" });
10854
+ }
10855
+ history() {
10856
+ this.requireAlive();
10857
+ return this.client.callMutation(this, { kind: "history" });
10858
+ }
10859
+ /** Token of the open draft; rejects when no draft is open. */
10860
+ draftToken() {
10861
+ this.requireAlive();
10862
+ return this.client.callMutation(this, { kind: "draftToken" });
10863
+ }
10864
+ evaluatePitch(noteId, options = {}) {
10865
+ this.requireAlive();
10866
+ return this.client.callMutation(this, {
10867
+ kind: "evaluatePitch",
10868
+ noteId,
10869
+ draft: options.draft === true
10870
+ });
10871
+ }
10872
+ sourceSampleToDestinationSample(noteId, sourceSample, options = {}) {
10873
+ this.requireAlive();
10874
+ return this.client.callMutation(this, {
10875
+ kind: "mapCoordinate",
10876
+ noteId,
10877
+ sample: sourceSample,
10878
+ inverse: false,
10879
+ draft: options.draft === true
10880
+ });
10881
+ }
10882
+ destinationSampleToSourceSample(noteId, destinationSample, options = {}) {
10883
+ this.requireAlive();
10884
+ return this.client.callMutation(this, {
10885
+ kind: "mapCoordinate",
10886
+ noteId,
10887
+ sample: destinationSample,
10888
+ inverse: true,
10889
+ draft: options.draft === true
10890
+ });
10891
+ }
10892
+ beginEdit(options = {}) {
10893
+ this.requireAlive();
10894
+ return this.client.callMutation(this, {
10895
+ kind: "beginEdit",
10896
+ expectedRevision: resolveRevision2(options, this.tokenValue.revision)
10897
+ });
10898
+ }
10899
+ apply(request, options = {}) {
10900
+ this.requireAlive();
10901
+ if (request.expectedGeneration === void 0) {
10902
+ throw new TypeError("expectedGeneration is required");
10903
+ }
10904
+ return this.client.callMutation(this, { kind: "apply", request }, options);
10905
+ }
10906
+ commit(options = {}) {
10907
+ this.requireAlive();
10908
+ return this.client.callMutation(this, {
10909
+ kind: "commit",
10910
+ expectedRevision: resolveRevision2(options, this.tokenValue.revision)
10911
+ });
10912
+ }
10913
+ cancel() {
10914
+ this.requireAlive();
10915
+ return this.client.callMutation(this, { kind: "cancel" });
10916
+ }
10917
+ undo(options = {}) {
10918
+ this.requireAlive();
10919
+ return this.client.callMutation(this, {
10920
+ kind: "undo",
10921
+ expectedRevision: resolveRevision2(options, this.tokenValue.revision)
10922
+ });
10923
+ }
10924
+ redo(options = {}) {
10925
+ this.requireAlive();
10926
+ return this.client.callMutation(this, {
10927
+ kind: "redo",
10928
+ expectedRevision: resolveRevision2(options, this.tokenValue.revision)
10929
+ });
10930
+ }
10931
+ exportState() {
10932
+ this.requireAlive();
10933
+ return this.client.callMutation(this, { kind: "exportState" });
10934
+ }
10935
+ preview(request, options = {}) {
10936
+ this.requireAlive();
10937
+ return this.client.callPreview(this, request, options);
10938
+ }
10939
+ /** Releases the Worker session and any open draft; later calls on it are rejected. */
10940
+ dispose() {
10941
+ if (this.disposed) {
10942
+ return new VocalEditWorkerTask(Promise.resolve(null), () => {
10943
+ });
10944
+ }
10945
+ this.disposed = true;
10946
+ return this.client.disposeSession(this);
10947
+ }
10948
+ requireAlive() {
10949
+ if (this.disposed) {
10950
+ throw new TypeError("vocal Worker session has already been disposed");
10951
+ }
10952
+ }
10953
+ };
10954
+
10955
+ // src/web_midi.ts
10956
+ function isWebMidiAvailable() {
10957
+ return typeof globalThis.navigator?.requestMIDIAccess === "function";
10958
+ }
10959
+ async function bindWebMidi(engine, options = {}) {
10960
+ const navigatorWithMidi = globalThis.navigator;
10961
+ if (typeof navigatorWithMidi?.requestMIDIAccess !== "function") {
10962
+ throw new Error("Web MIDI is not available in this environment");
10963
+ }
10964
+ const group = options.group ?? 0;
10965
+ assertNibble("bindWebMidi", group, "group");
10966
+ const destinationId = options.destinationId ?? 0;
10967
+ const selectedIds = new Set(options.inputIds ?? []);
10968
+ const access = await navigatorWithMidi.requestMIDIAccess({
10969
+ sysex: options.sysex ?? false,
10970
+ software: options.software ?? true
10971
+ });
10972
+ for (const binding of options.ccBindings ?? []) {
10973
+ engine.bindMidiCc(binding.channel, binding.controller, binding.paramId, binding.options);
10974
+ }
10975
+ engine.setMidiInputSource(destinationId);
10976
+ const bound = /* @__PURE__ */ new Map();
9123
10977
  let closed = false;
9124
10978
  const shouldBind = (input) => input.state !== "disconnected" && (selectedIds.size === 0 || selectedIds.has(input.id));
9125
10979
  const snapshotInputs = () => Array.from(iterInputs(access), ([id, input]) => ({
@@ -9222,8 +11076,8 @@ function dispatchMidiMessage(engine, event, group, runningStatus, timestampToSam
9222
11076
  return 0;
9223
11077
  }
9224
11078
  const first = data[0];
9225
- if (first > 255) {
9226
- dispatchUmpMessage(
11079
+ if (first > 255 || data instanceof Uint32Array) {
11080
+ dispatchUmpMessages(
9227
11081
  engine,
9228
11082
  data,
9229
11083
  timestampToSamples?.(event.receivedTime ?? event.timeStamp ?? 0) ?? 0
@@ -9246,8 +11100,12 @@ function dispatchMidiMessage(engine, event, group, runningStatus, timestampToSam
9246
11100
  return status >= 248 ? runningStatus : 0;
9247
11101
  }
9248
11102
  const a = readU7(data, offset);
9249
- const b = readU7(data, offset + 1);
9250
- if (a < 0 || b < 0) {
11103
+ if (a < 0) {
11104
+ return status;
11105
+ }
11106
+ const oneDataByteMessage = message === 192 || message === 208;
11107
+ const b = oneDataByteMessage ? -1 : readU7(data, offset + 1);
11108
+ if (!oneDataByteMessage && b < 0) {
9251
11109
  return status;
9252
11110
  }
9253
11111
  const portTimeSamples = timestampToSamples ? timestampToSamples(event.receivedTime ?? event.timeStamp ?? 0) : 0;
@@ -9261,13 +11119,42 @@ function dispatchMidiMessage(engine, event, group, runningStatus, timestampToSam
9261
11119
  }
9262
11120
  } else if (message === 176 && b >= 0) {
9263
11121
  engine.pushMidiInputCc(group, channel, a, b, portTimeSamples);
11122
+ } else if (message === 160 && b >= 0) {
11123
+ engine.pushMidiInputPolyPressure?.(group, channel, a, b, portTimeSamples);
11124
+ } else if (message === 208) {
11125
+ engine.pushMidiInputChannelPressure?.(group, channel, a, portTimeSamples);
11126
+ } else if (message === 224 && b >= 0) {
11127
+ engine.pushMidiInputPitchBend?.(group, channel, a | b << 7, portTimeSamples);
9264
11128
  }
9265
11129
  return status;
9266
11130
  }
11131
+ function dispatchUmpMessages(engine, words, portTimeSamples) {
11132
+ let offset = 0;
11133
+ while (offset < words.length) {
11134
+ const word0 = words[offset] >>> 0;
11135
+ const wordCount = umpWordCount(word0 >>> 28);
11136
+ if (words.length - offset < wordCount) {
11137
+ return;
11138
+ }
11139
+ const packet = new Uint32Array(wordCount);
11140
+ for (let index = 0; index < wordCount; index++) {
11141
+ packet[index] = words[offset + index] >>> 0;
11142
+ }
11143
+ dispatchUmpMessage(engine, packet, portTimeSamples);
11144
+ offset += wordCount;
11145
+ }
11146
+ }
9267
11147
  function dispatchUmpMessage(engine, words, portTimeSamples) {
9268
11148
  const word0 = words[0] >>> 0;
9269
11149
  const messageType = word0 >>> 28;
9270
11150
  const group = word0 >>> 24 & 15;
11151
+ if (messageType === 3 || messageType === 5) {
11152
+ return;
11153
+ }
11154
+ if (engine.pushMidiInputUmp) {
11155
+ engine.pushMidiInputUmp(words, portTimeSamples);
11156
+ return;
11157
+ }
9271
11158
  if (messageType === 2) {
9272
11159
  const status = word0 >>> 16 & 255;
9273
11160
  const message = status & 240;
@@ -9284,6 +11171,12 @@ function dispatchUmpMessage(engine, words, portTimeSamples) {
9284
11171
  }
9285
11172
  } else if (message === 176) {
9286
11173
  engine.pushMidiInputCc(group, channel, a, b, portTimeSamples);
11174
+ } else if (message === 160) {
11175
+ engine.pushMidiInputPolyPressure?.(group, channel, a, b, portTimeSamples);
11176
+ } else if (message === 208) {
11177
+ engine.pushMidiInputChannelPressure?.(group, channel, a, portTimeSamples);
11178
+ } else if (message === 224) {
11179
+ engine.pushMidiInputPitchBend?.(group, channel, a | b << 7, portTimeSamples);
9287
11180
  }
9288
11181
  return;
9289
11182
  }
@@ -9299,7 +11192,40 @@ function dispatchUmpMessage(engine, words, portTimeSamples) {
9299
11192
  engine.pushMidiInputNoteOn(group, channel, data1, velocity, portTimeSamples);
9300
11193
  } else if (status === 11) {
9301
11194
  engine.pushMidiInputCc(group, channel, data1, word1 >>> 25 & 127, portTimeSamples);
9302
- }
11195
+ } else if (status === 10) {
11196
+ engine.pushMidiInputPolyPressure?.(
11197
+ group,
11198
+ channel,
11199
+ data1,
11200
+ word1 >>> 25 & 127,
11201
+ portTimeSamples
11202
+ );
11203
+ } else if (status === 13) {
11204
+ engine.pushMidiInputChannelPressure?.(group, channel, word1 >>> 25 & 127, portTimeSamples);
11205
+ } else if (status === 14) {
11206
+ engine.pushMidiInputPitchBend?.(group, channel, word1 >>> 18, portTimeSamples);
11207
+ }
11208
+ }
11209
+ }
11210
+ function umpWordCount(messageType) {
11211
+ switch (messageType & 15) {
11212
+ case 0:
11213
+ case 1:
11214
+ case 2:
11215
+ case 6:
11216
+ case 7:
11217
+ return 1;
11218
+ case 3:
11219
+ case 4:
11220
+ case 8:
11221
+ case 9:
11222
+ case 10:
11223
+ return 2;
11224
+ case 11:
11225
+ case 12:
11226
+ return 3;
11227
+ default:
11228
+ return 4;
9303
11229
  }
9304
11230
  }
9305
11231
  function readU7(data, index) {
@@ -9336,7 +11262,7 @@ var OfflineWorkerTask = class {
9336
11262
  return this.result.finally(onfinally);
9337
11263
  }
9338
11264
  };
9339
- function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
11265
+ function cloneForWorker2(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
9340
11266
  if (value instanceof Float32Array) {
9341
11267
  const samples = copy ? value.slice() : value;
9342
11268
  const buffer = samples.buffer;
@@ -9352,25 +11278,25 @@ function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ ne
9352
11278
  return samples;
9353
11279
  }
9354
11280
  if (Array.isArray(value)) {
9355
- return value.map((item) => cloneForWorker(item, copy, transfers, transferred));
11281
+ return value.map((item) => cloneForWorker2(item, copy, transfers, transferred));
9356
11282
  }
9357
11283
  if (value !== null && typeof value === "object") {
9358
11284
  return Object.fromEntries(
9359
11285
  Object.entries(value).map(([key, item]) => [
9360
11286
  key,
9361
- cloneForWorker(item, copy, transfers, transferred)
11287
+ cloneForWorker2(item, copy, transfers, transferred)
9362
11288
  ])
9363
11289
  );
9364
11290
  }
9365
11291
  return value;
9366
11292
  }
9367
- function cancellationFlag() {
11293
+ function cancellationFlag2() {
9368
11294
  if (typeof SharedArrayBuffer === "undefined") {
9369
11295
  return void 0;
9370
11296
  }
9371
11297
  return new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
9372
11298
  }
9373
- function workerError(message) {
11299
+ function workerError2(message) {
9374
11300
  if (message.error.code !== void 0) {
9375
11301
  return new SonareError(
9376
11302
  message.error.code,
@@ -9403,7 +11329,7 @@ var OfflineWorkerClient = class {
9403
11329
  pending.resolve(message.result);
9404
11330
  return;
9405
11331
  }
9406
- pending.reject(workerError(message));
11332
+ pending.reject(workerError2(message));
9407
11333
  };
9408
11334
  this.onError = (event) => {
9409
11335
  const error = new Error(event.message || "Offline Worker failed");
@@ -9492,8 +11418,8 @@ var OfflineWorkerClient = class {
9492
11418
  }
9493
11419
  const id = this.nextId++;
9494
11420
  const transfers = [];
9495
- const preparedRequest = cloneForWorker(request, options.copy === true, transfers);
9496
- const cancelFlag = cancellationFlag();
11421
+ const preparedRequest = cloneForWorker2(request, options.copy === true, transfers);
11422
+ const cancelFlag = cancellationFlag2();
9497
11423
  const result = new Promise((resolve, reject) => {
9498
11424
  this.pending.set(id, {
9499
11425
  resolve: (value) => resolve(value),
@@ -9525,10 +11451,10 @@ var OfflineWorkerClient = class {
9525
11451
  };
9526
11452
 
9527
11453
  // src/index.ts
9528
- var module = null;
11454
+ var module2 = null;
9529
11455
  var initPromise = null;
9530
11456
  async function init(options) {
9531
- if (module) {
11457
+ if (module2) {
9532
11458
  return;
9533
11459
  }
9534
11460
  if (initPromise) {
@@ -9537,8 +11463,8 @@ async function init(options) {
9537
11463
  initPromise = (async () => {
9538
11464
  try {
9539
11465
  const createModule = options?.moduleFactory ?? (await import("./sonare.js")).default;
9540
- module = await createModule(options);
9541
- setSonareModule(module);
11466
+ module2 = await createModule(options);
11467
+ setSonareModule(module2);
9542
11468
  } catch (error) {
9543
11469
  initPromise = null;
9544
11470
  throw error;
@@ -9547,43 +11473,43 @@ async function init(options) {
9547
11473
  return initPromise;
9548
11474
  }
9549
11475
  function isInitialized() {
9550
- return module !== null;
11476
+ return module2 !== null;
9551
11477
  }
9552
11478
  function version() {
9553
- if (!module) {
11479
+ if (!module2) {
9554
11480
  throw new Error("Module not initialized. Call init() first.");
9555
11481
  }
9556
- return module.version();
11482
+ return module2.version();
9557
11483
  }
9558
11484
  function capabilities() {
9559
- if (!module) {
11485
+ if (!module2) {
9560
11486
  throw new Error("Module not initialized. Call init() first.");
9561
11487
  }
9562
- return module.capabilities();
11488
+ return module2.capabilities();
9563
11489
  }
9564
11490
  function capabilityCatalog() {
9565
- if (!module) {
11491
+ if (!module2) {
9566
11492
  throw new Error("Module not initialized. Call init() first.");
9567
11493
  }
9568
- return JSON.parse(module.capabilityCatalog());
11494
+ return JSON.parse(module2.capabilityCatalog());
9569
11495
  }
9570
11496
  function abiVersion() {
9571
- if (!module) {
11497
+ if (!module2) {
9572
11498
  throw new Error("Module not initialized. Call init() first.");
9573
11499
  }
9574
- return module.abiVersion();
11500
+ return module2.abiVersion();
9575
11501
  }
9576
11502
  function engineAbiVersion() {
9577
- if (!module) {
11503
+ if (!module2) {
9578
11504
  throw new Error("Module not initialized. Call init() first.");
9579
11505
  }
9580
- return module.engineAbiVersion();
11506
+ return module2.engineAbiVersion();
9581
11507
  }
9582
11508
  function voiceChangerAbiVersion() {
9583
- if (!module) {
11509
+ if (!module2) {
9584
11510
  throw new Error("Module not initialized. Call init() first.");
9585
11511
  }
9586
- return module.voiceChangerAbiVersion();
11512
+ return module2.voiceChangerAbiVersion();
9587
11513
  }
9588
11514
  var VOICE_PRESET_ORDINALS = [
9589
11515
  "neutral-monitor",
@@ -9607,19 +11533,19 @@ function resolveVoicePresetOrdinal(preset) {
9607
11533
  return ordinal;
9608
11534
  }
9609
11535
  function voiceCharacterPresetId(preset) {
9610
- if (!module) {
11536
+ if (!module2) {
9611
11537
  throw new Error("Module not initialized. Call init() first.");
9612
11538
  }
9613
11539
  if (typeof preset === "number" && (!Number.isSafeInteger(preset) || preset < 0 || preset >= VOICE_PRESET_ORDINALS.length)) {
9614
11540
  return null;
9615
11541
  }
9616
- return module.voiceCharacterPresetId(resolveVoicePresetOrdinal(preset));
11542
+ return module2.voiceCharacterPresetId(resolveVoicePresetOrdinal(preset));
9617
11543
  }
9618
11544
  function realtimeVoiceChangerPresetConfig(preset) {
9619
- if (!module) {
11545
+ if (!module2) {
9620
11546
  throw new Error("Module not initialized. Call init() first.");
9621
11547
  }
9622
- return module.realtimeVoiceChangerPresetConfig(resolveVoicePresetOrdinal(preset));
11548
+ return module2.realtimeVoiceChangerPresetConfig(resolveVoicePresetOrdinal(preset));
9623
11549
  }
9624
11550
  export {
9625
11551
  ARTICULATIONS,
@@ -9643,6 +11569,8 @@ export {
9643
11569
  NOTE_TRACKINGS,
9644
11570
  OfflineWorkerClient,
9645
11571
  OfflineWorkerTask,
11572
+ PART_RIG_ALL_PARTS,
11573
+ PART_RIG_MODES,
9646
11574
  PROJECT_AUTOMATION_TARGET_OPAQUE,
9647
11575
  PROJECT_AUTOMATION_TARGET_TRACK_FADER_DB,
9648
11576
  PROJECT_AUTOMATION_TARGET_TRACK_PAN,
@@ -9652,6 +11580,7 @@ export {
9652
11580
  PlaybackRenderer,
9653
11581
  PolyphonicAnalysis,
9654
11582
  Project,
11583
+ ProjectTimeline,
9655
11584
  RealtimeEngine,
9656
11585
  RealtimeVoiceChanger,
9657
11586
  SAMPLE_KEY_TRACKS,
@@ -9671,6 +11600,14 @@ export {
9671
11600
  StreamingEqualizer,
9672
11601
  StreamingMasteringChain,
9673
11602
  StreamingRetune,
11603
+ VocalEditDraft,
11604
+ VocalEditSession,
11605
+ VocalEditWorkerClient,
11606
+ VocalEditWorkerSession,
11607
+ VocalEditWorkerStaleResultError,
11608
+ VocalEditWorkerTask,
11609
+ VocalRenderJob,
11610
+ VocalRenderSnapshot,
9674
11611
  abiVersion,
9675
11612
  alignTakeToReference,
9676
11613
  amplitudeToDb,
@@ -9702,6 +11639,7 @@ export {
9702
11639
  cqtToAudio,
9703
11640
  createOpfsClipPageProvider,
9704
11641
  createOpfsClipPageWorker,
11642
+ createVocalEditSession,
9705
11643
  cyclicTempogram,
9706
11644
  dbToAmplitude,
9707
11645
  dbToPower,
@@ -9756,6 +11694,7 @@ export {
9756
11694
  masterAudioWithProgress,
9757
11695
  mastering,
9758
11696
  masteringAbMatchLoudness,
11697
+ masteringAbMatchLoudnessStereo,
9759
11698
  masteringAmpPresetCatalog,
9760
11699
  masteringAssistantSuggest,
9761
11700
  masteringAssistantSuggestChain,
@@ -9777,6 +11716,7 @@ export {
9777
11716
  masteringPairAnalysisNames,
9778
11717
  masteringPairAnalyze,
9779
11718
  masteringPairProcess,
11719
+ masteringPairProcessStereo,
9780
11720
  masteringPairProcessorNames,
9781
11721
  masteringPlatformNames,
9782
11722
  masteringPresetNames,
@@ -9890,6 +11830,7 @@ export {
9890
11830
  renderPercussiveEvents,
9891
11831
  renderPlayback,
9892
11832
  resample,
11833
+ restoreVocalEditSession,
9893
11834
  rmsEnergy,
9894
11835
  roomMorph,
9895
11836
  samplesToFrames,
@@ -9939,6 +11880,8 @@ export {
9939
11880
  validateRealtimeVoiceChangerPresetJson,
9940
11881
  vectorNormalize,
9941
11882
  version,
11883
+ vocalEditApiVersion,
11884
+ vocalEditAvailable,
9942
11885
  voiceChange,
9943
11886
  voiceChangeRealtime,
9944
11887
  voiceChangerAbiVersion,