@libraz/libsonare 1.8.1 → 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 (107) hide show
  1. package/README.md +5 -5
  2. package/dist/analysis.js +2 -0
  3. package/dist/analysis.js.map +1 -1
  4. package/dist/index.d.ts +8 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1927 -140
  7. package/dist/index.js.map +1 -1
  8. package/dist/instrument_types.d.ts +27 -3
  9. package/dist/instrument_types.d.ts.map +1 -1
  10. package/dist/mastering_core.d.ts +3 -2
  11. package/dist/mastering_core.d.ts.map +1 -1
  12. package/dist/mixer.d.ts +2 -2
  13. package/dist/project.d.ts +5 -4
  14. package/dist/project.d.ts.map +1 -1
  15. package/dist/project_class.d.ts +57 -3
  16. package/dist/project_class.d.ts.map +1 -1
  17. package/dist/project_internal.d.ts +35 -1
  18. package/dist/project_internal.d.ts.map +1 -1
  19. package/dist/project_types.d.ts +10 -1
  20. package/dist/project_types.d.ts.map +1 -1
  21. package/dist/public_types.d.ts +1 -0
  22. package/dist/public_types.d.ts.map +1 -1
  23. package/dist/public_types_mastering.d.ts +1 -1
  24. package/dist/public_types_mastering.d.ts.map +1 -1
  25. package/dist/public_types_vocal_edit.d.ts +250 -0
  26. package/dist/public_types_vocal_edit.d.ts.map +1 -0
  27. package/dist/public_types_vocal_project.d.ts +63 -0
  28. package/dist/public_types_vocal_project.d.ts.map +1 -0
  29. package/dist/realtime_engine.d.ts +57 -10
  30. package/dist/realtime_engine.d.ts.map +1 -1
  31. package/dist/repair_dereverb.d.ts +1 -0
  32. package/dist/repair_dereverb.d.ts.map +1 -1
  33. package/dist/repair_noise.d.ts +1 -0
  34. package/dist/repair_noise.d.ts.map +1 -1
  35. package/dist/sonare-analysis.wasm +0 -0
  36. package/dist/sonare.d.ts +53 -0
  37. package/dist/sonare.js +1 -1
  38. package/dist/sonare.wasm +0 -0
  39. package/dist/vocal_edit.d.ts +131 -0
  40. package/dist/vocal_edit.d.ts.map +1 -0
  41. package/dist/vocal_edit_worker.d.ts +51 -0
  42. package/dist/vocal_edit_worker.d.ts.map +1 -0
  43. package/dist/vocal_edit_worker.js +1462 -0
  44. package/dist/vocal_edit_worker.js.map +1 -0
  45. package/dist/vocal_edit_worker_client.d.ts +118 -0
  46. package/dist/vocal_edit_worker_client.d.ts.map +1 -0
  47. package/dist/vocal_edit_worker_protocol.d.ts +139 -0
  48. package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
  49. package/dist/vocal_project.d.ts +17 -0
  50. package/dist/vocal_project.d.ts.map +1 -0
  51. package/dist/web_midi.d.ts +4 -0
  52. package/dist/web_midi.d.ts.map +1 -1
  53. package/dist/worker.js.map +1 -1
  54. package/dist/worklet/engine-mixer-facade.d.ts +2 -1
  55. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  56. package/dist/worklet/engine-node.d.ts +8 -0
  57. package/dist/worklet/engine-node.d.ts.map +1 -1
  58. package/dist/worklet/engine-processor.d.ts +2 -1
  59. package/dist/worklet/engine-processor.d.ts.map +1 -1
  60. package/dist/worklet/engine-register.d.ts.map +1 -1
  61. package/dist/worklet/engine-strips.d.ts +4 -2
  62. package/dist/worklet/engine-strips.d.ts.map +1 -1
  63. package/dist/worklet/engine.d.ts +15 -3
  64. package/dist/worklet/engine.d.ts.map +1 -1
  65. package/dist/worklet/guards.d.ts +3 -1
  66. package/dist/worklet/guards.d.ts.map +1 -1
  67. package/dist/worklet/messages.d.ts +50 -5
  68. package/dist/worklet/messages.d.ts.map +1 -1
  69. package/dist/worklet/protocol.d.ts +10 -0
  70. package/dist/worklet/protocol.d.ts.map +1 -1
  71. package/dist/worklet.d.ts +1 -1
  72. package/dist/worklet.d.ts.map +1 -1
  73. package/dist/worklet.js +223 -16
  74. package/dist/worklet.js.map +1 -1
  75. package/package.json +6 -1
  76. package/src/index.ts +33 -0
  77. package/src/instrument_types.ts +29 -3
  78. package/src/mastering_core.ts +3 -2
  79. package/src/mixer.ts +2 -2
  80. package/src/project.ts +8 -1
  81. package/src/project_class.ts +136 -1
  82. package/src/project_internal.ts +90 -0
  83. package/src/project_types.ts +11 -1
  84. package/src/public_types.ts +1 -0
  85. package/src/public_types_mastering.ts +2 -0
  86. package/src/public_types_vocal_edit.ts +289 -0
  87. package/src/public_types_vocal_project.ts +69 -0
  88. package/src/realtime_engine.ts +86 -9
  89. package/src/repair_dereverb.ts +1 -0
  90. package/src/repair_noise.ts +1 -0
  91. package/src/sonare.js.d.ts +53 -0
  92. package/src/vocal_edit.ts +885 -0
  93. package/src/vocal_edit_worker.ts +832 -0
  94. package/src/vocal_edit_worker_client.ts +812 -0
  95. package/src/vocal_edit_worker_protocol.ts +206 -0
  96. package/src/vocal_project.ts +327 -0
  97. package/src/web_midi.ts +100 -5
  98. package/src/worklet/engine-mixer-facade.ts +2 -1
  99. package/src/worklet/engine-node.ts +47 -0
  100. package/src/worklet/engine-processor.ts +46 -1
  101. package/src/worklet/engine-register.ts +3 -0
  102. package/src/worklet/engine-strips.ts +31 -4
  103. package/src/worklet/engine.ts +27 -3
  104. package/src/worklet/guards.ts +26 -0
  105. package/src/worklet/messages.ts +59 -2
  106. package/src/worklet/protocol.ts +10 -0
  107. 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) {
@@ -764,11 +806,11 @@ function mergeNotes(request) {
764
806
  );
765
807
  }
766
808
  function noteTargetsFromSmf(request) {
767
- const module2 = requireModule();
768
- if (typeof module2.noteTargetsFromSmf !== "function") {
809
+ const module3 = requireModule();
810
+ if (typeof module3.noteTargetsFromSmf !== "function") {
769
811
  throw new Error("libsonare was built without arrangement support");
770
812
  }
771
- return module2.noteTargetsFromSmf(request.data, request.trackIndex ?? 0);
813
+ return module3.noteTargetsFromSmf(request.data, request.trackIndex ?? 0);
772
814
  }
773
815
  function assignNoteTargets(request) {
774
816
  return requireModule().assignNoteTargets(
@@ -4005,29 +4047,29 @@ function detectAcoustic(samples, sampleRate = 48e3, options = {}) {
4005
4047
  return result;
4006
4048
  }
4007
4049
  function synthesizeRir(options = {}) {
4008
- const module2 = requireModule25();
4009
- if (typeof module2.synthesizeRir !== "function") {
4050
+ const module3 = requireModule25();
4051
+ if (typeof module3.synthesizeRir !== "function") {
4010
4052
  throw new Error("libsonare was built without acoustic-simulation support");
4011
4053
  }
4012
- return module2.synthesizeRir(options);
4054
+ return module3.synthesizeRir(options);
4013
4055
  }
4014
4056
  function estimateRoom(samples, sampleRate = 48e3, options = {}) {
4015
- const module2 = requireModule25();
4016
- if (typeof module2.estimateRoom !== "function") {
4057
+ const module3 = requireModule25();
4058
+ if (typeof module3.estimateRoom !== "function") {
4017
4059
  throw new Error("libsonare was built without acoustic-simulation support");
4018
4060
  }
4019
4061
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
4020
4062
  validateAnalysisInput("estimateRoom", request.samples, request.sampleRate ?? 48e3, request);
4021
- return module2.estimateRoom(request.samples, request.sampleRate ?? 48e3, request);
4063
+ return module3.estimateRoom(request.samples, request.sampleRate ?? 48e3, request);
4022
4064
  }
4023
4065
  function roomMorph(samples, sampleRate, options = {}) {
4024
- const module2 = requireModule25();
4025
- if (typeof module2.roomMorph !== "function") {
4066
+ const module3 = requireModule25();
4067
+ if (typeof module3.roomMorph !== "function") {
4026
4068
  throw new Error("libsonare was built without acoustic-simulation support");
4027
4069
  }
4028
4070
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
4029
4071
  validateAnalysisInput("roomMorph", request.samples, request.sampleRate, request);
4030
- return module2.roomMorph(request.samples, request.sampleRate, request);
4072
+ return module3.roomMorph(request.samples, request.sampleRate, request);
4031
4073
  }
4032
4074
  function analyzeWithProgress(samples, sampleRate = 22050, onProgress, options) {
4033
4075
  const request = samples instanceof Float32Array ? { samples, sampleRate, onProgress, options } : samples;
@@ -4910,8 +4952,8 @@ function suggestJson(fnName, request, sceneOnly) {
4910
4952
  const { left, right, ids, names } = planarTracks(request.tracks);
4911
4953
  const sampleRate = request.sampleRate;
4912
4954
  const params = request.options ?? {};
4913
- const module2 = requireModule26();
4914
- 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);
4915
4957
  }
4916
4958
  function suggestMixScene(request) {
4917
4959
  return JSON.parse(suggestJson("suggestMixScene", request, false));
@@ -5097,8 +5139,8 @@ var PolyphonicAnalysis = class {
5097
5139
  constructor(request) {
5098
5140
  assertSamples("analyzePolyphonic", request.samples, request.validate !== false);
5099
5141
  assertSampleRate("analyzePolyphonic", request.sampleRate);
5100
- const module2 = getSonareModule();
5101
- this.native = module2.createPolyphonicAnalysis(
5142
+ const module3 = getSonareModule();
5143
+ this.native = module3.createPolyphonicAnalysis(
5102
5144
  request.samples,
5103
5145
  request.sampleRate,
5104
5146
  request
@@ -5374,9 +5416,261 @@ var CONTROLLER_AXES = [
5374
5416
  "vibrato-depth"
5375
5417
  ];
5376
5418
  var ARTICULATIONS = ["poly", "mono-retrigger", "mono-legato"];
5419
+ var PART_RIG_MODES = ["bank", "none", "chain"];
5420
+ var PART_RIG_ALL_PARTS = 255;
5377
5421
  var MPE_DIMENSIONS = ["bend", "pressure", "timbre"];
5378
5422
  var NOTE_TRACKINGS = ["last", "lowest", "highest", "all"];
5379
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
+
5380
5674
  // src/project_class.ts
5381
5675
  function normalizeMidiFxBakeRequest(clipIdOrRequest, configJson) {
5382
5676
  if (typeof clipIdOrRequest === "number") {
@@ -5445,11 +5739,57 @@ function midi2Byte(fnName, value, argName) {
5445
5739
  assertBoundedInteger(fnName, value, argName, 0, 255);
5446
5740
  return value;
5447
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
+ };
5448
5769
  var Project = class _Project {
5449
5770
  constructor() {
5450
5771
  this.released = false;
5772
+ this.deleteRequested = false;
5773
+ this.activeVocalRehydrateCalls = 0;
5451
5774
  this.native = new (projectModule()).Project();
5452
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
+ }
5453
5793
  /** Create a new empty project. */
5454
5794
  static create() {
5455
5795
  return new _Project();
@@ -5818,6 +6158,26 @@ var Project = class _Project {
5818
6158
  setTrackGain(trackId, gain) {
5819
6159
  this.native.setTrackGain(trackId, gain);
5820
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
+ }
5821
6181
  /** Set a track's mute flag via an undoable edit (a muted track is silent). */
5822
6182
  setTrackMute(trackId, mute) {
5823
6183
  this.native.setTrackMute(trackId, mute);
@@ -6023,6 +6383,16 @@ var Project = class _Project {
6023
6383
  compile() {
6024
6384
  return this.native.compile();
6025
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
+ }
6026
6396
  /**
6027
6397
  * Compile + render the project offline to interleaved float audio. MIDI
6028
6398
  * tracks render silently here (no instrument is bound) — use
@@ -6395,12 +6765,20 @@ var Project = class _Project {
6395
6765
  lastBounceCompileResult() {
6396
6766
  return this.native.lastBounceCompileResult();
6397
6767
  }
6398
- /** 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
+ */
6399
6773
  delete() {
6400
6774
  if (this.released) {
6401
6775
  return;
6402
6776
  }
6403
6777
  this.released = true;
6778
+ if (this.activeVocalRehydrateCalls !== 0) {
6779
+ this.deleteRequested = true;
6780
+ return;
6781
+ }
6404
6782
  this.native.delete();
6405
6783
  }
6406
6784
  /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
@@ -6500,14 +6878,14 @@ function engineCapabilities() {
6500
6878
  var RealtimeEngine = class _RealtimeEngine {
6501
6879
  constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
6502
6880
  this.released = false;
6503
- const module2 = getSonareModule();
6881
+ const module3 = getSonareModule();
6504
6882
  const capabilities2 = engineCapabilities();
6505
6883
  if (!capabilities2.abiCompatible) {
6506
6884
  throw new Error(
6507
6885
  `Engine ABI mismatch: wasm=${capabilities2.engineAbiVersion}, expected=${capabilities2.expectedEngineAbiVersion}`
6508
6886
  );
6509
6887
  }
6510
- this.native = new module2.RealtimeEngine(
6888
+ this.native = new module3.RealtimeEngine(
6511
6889
  sampleRate,
6512
6890
  maxBlockSize,
6513
6891
  commandCapacity,
@@ -6711,6 +7089,17 @@ var RealtimeEngine = class _RealtimeEngine {
6711
7089
  setArticulation(destinationId, channel, articulation) {
6712
7090
  this.native.setArticulation(destinationId, channel, articulation);
6713
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
+ }
6714
7103
  /**
6715
7104
  * Read back {@link setArticulation} as the canonical name. An ordinal this
6716
7105
  * build cannot spell is handed back as the number, the way every other enum
@@ -7121,10 +7510,40 @@ var RealtimeEngine = class _RealtimeEngine {
7121
7510
  /**
7122
7511
  * Keys one insert of a lane strip from another lane's post-strip audio
7123
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.
7124
7519
  */
7125
7520
  setLaneSidechain(trackId, insertIndex, sourceTrackId) {
7126
7521
  this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
7127
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
+ }
7128
7547
  setTrackBuses(buses) {
7129
7548
  this.native.setTrackBuses(
7130
7549
  Array.isArray(buses) ? buses.map((bus) => ({
@@ -7326,12 +7745,9 @@ var RealtimeEngine = class _RealtimeEngine {
7326
7745
  * return `-1`: they resize voice pools or swap DSP topology, which is not
7327
7746
  * audio-thread safe. Rebind the instrument with a new patch instead.
7328
7747
  *
7329
- * `gain`, `busDrive`, `cutoffHz`, `resonanceQ`, `envToCutoffCents`,
7330
- * `lfoToPitchCents` and `pitchOffsetCents` reach voices that are already
7331
- * sounding from the next block; the rest are cached into per-voice state at
7332
- * note-on and take effect from the next note, so a lane that moves one of
7333
- * them under a held note looks inert until the next one speaks — that is the
7334
- * 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.
7335
7751
  *
7336
7752
  * The id survives an unbind/rebind of the same destination and applies
7337
7753
  * nothing while that destination is unbound.
@@ -7355,6 +7771,16 @@ var RealtimeEngine = class _RealtimeEngine {
7355
7771
  setTrackStripDualPan(trackId, leftPan, rightPan) {
7356
7772
  this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
7357
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
+ }
7358
7784
  /**
7359
7785
  * Sets a bus strip's output pan position in realtime (glitch-free). Throws
7360
7786
  * for an unknown bus or one wider than stereo.
@@ -7375,8 +7801,9 @@ var RealtimeEngine = class _RealtimeEngine {
7375
7801
  this.native.setBusStripDualPan(busId, leftPan, rightPan);
7376
7802
  }
7377
7803
  /**
7378
- * Sets a track lane strip's inter-channel alignment delay (whole samples).
7379
- * 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.
7380
7807
  */
7381
7808
  setTrackStripChannelDelaySamples(trackId, delaySamples) {
7382
7809
  this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
@@ -7644,6 +8071,15 @@ var RealtimeEngine = class _RealtimeEngine {
7644
8071
  drainMeterTelemetryWide(maxRecords = 1024) {
7645
8072
  return this.native.drainMeterTelemetryWide(maxRecords);
7646
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
+ }
7647
8083
  /**
7648
8084
  * Enables per-target spectrum + vectorscope capture. @param intervalFrames is
7649
8085
  * the minimum render-frame gap between snapshots (0 disables). @param bandCount
@@ -7757,9 +8193,9 @@ var StreamAnalyzer = class {
7757
8193
  throw new TypeError("outputFormat must be the integer 0 (Float32)");
7758
8194
  }
7759
8195
  const window = config.window === void 0 ? void 0 : resolveOrdinalInRange(config.window, 0, 3, "stream analyzer window");
7760
- const module2 = getSonareModule();
8196
+ const module3 = getSonareModule();
7761
8197
  const defaults = streamAnalyzerConfigDefaults();
7762
- this.analyzer = new module2.StreamAnalyzer(
8198
+ this.analyzer = new module3.StreamAnalyzer(
7763
8199
  config.sampleRate ?? defaults.sampleRate,
7764
8200
  config.nFft ?? defaults.nFft,
7765
8201
  config.hopLength ?? defaults.hopLength,
@@ -8019,8 +8455,8 @@ var Mixer = class _Mixer {
8019
8455
  * @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
8020
8456
  */
8021
8457
  static fromSceneJson(json, sampleRate = 48e3, blockSize = 512) {
8022
- const module2 = getSonareModule();
8023
- return new _Mixer(module2.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
8458
+ const module3 = getSonareModule();
8459
+ return new _Mixer(module3.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
8024
8460
  }
8025
8461
  /**
8026
8462
  * Rebuild and compile the routing graph without resetting its absolute
@@ -8347,8 +8783,8 @@ var Mixer = class _Mixer {
8347
8783
  this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
8348
8784
  }
8349
8785
  /**
8350
- * Set a per-strip channel delay in samples. This changes the strip's reported
8351
- * 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.
8352
8788
  */
8353
8789
  setChannelDelaySamples(stripIndex, delaySamples) {
8354
8790
  this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
@@ -8602,8 +9038,8 @@ var RealtimeVoiceChanger = class {
8602
9038
  */
8603
9039
  constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
8604
9040
  this.released = false;
8605
- const module2 = getSonareModule();
8606
- this.changer = module2.createRealtimeVoiceChanger(config);
9041
+ const module3 = getSonareModule();
9042
+ this.changer = module3.createRealtimeVoiceChanger(config);
8607
9043
  if (sampleRate !== void 0) {
8608
9044
  let prepared = false;
8609
9045
  try {
@@ -8879,9 +9315,9 @@ var EQ_PHASE_MODES = {
8879
9315
  var StreamingMasteringChain = class {
8880
9316
  constructor(config) {
8881
9317
  this.released = false;
8882
- const module2 = getSonareModule();
9318
+ const module3 = getSonareModule();
8883
9319
  const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
8884
- this.chain = module2.createStreamingMasteringChain({
9320
+ this.chain = module3.createStreamingMasteringChain({
8885
9321
  __flatParams: flattenChainConfig(chainConfig),
8886
9322
  loudnessStaticGainDb,
8887
9323
  loudnessStaticGainPeakDb
@@ -9017,8 +9453,8 @@ var StreamingMasteringChain = class {
9017
9453
  var StreamingEqualizer = class {
9018
9454
  constructor(config = {}) {
9019
9455
  this.released = false;
9020
- const module2 = getSonareModule();
9021
- this.eq = module2.createEqualizer(config);
9456
+ const module3 = getSonareModule();
9457
+ this.eq = module3.createEqualizer(config);
9022
9458
  }
9023
9459
  /**
9024
9460
  * Configure the band at `index` (0..23). Omitted fields use C++ defaults.
@@ -9187,8 +9623,8 @@ var StreamingEqualizer = class {
9187
9623
  var StreamingRetune = class {
9188
9624
  constructor(config = {}) {
9189
9625
  this.released = false;
9190
- const module2 = getSonareModule();
9191
- this.retune = module2.createStreamingRetune(config);
9626
+ const module3 = getSonareModule();
9627
+ this.retune = module3.createStreamingRetune(config);
9192
9628
  }
9193
9629
  /**
9194
9630
  * Allocate and initialize native state for the given sample rate and maximum
@@ -9252,77 +9688,1341 @@ function transcribe(request) {
9252
9688
  );
9253
9689
  }
9254
9690
 
9255
- // src/web_midi.ts
9256
- function isWebMidiAvailable() {
9257
- 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
+ }
9258
9718
  }
9259
- async function bindWebMidi(engine, options = {}) {
9260
- const navigatorWithMidi = globalThis.navigator;
9261
- if (typeof navigatorWithMidi?.requestMIDIAccess !== "function") {
9262
- 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;
9263
9723
  }
9264
- const group = options.group ?? 0;
9265
- assertNibble("bindWebMidi", group, "group");
9266
- const destinationId = options.destinationId ?? 0;
9267
- const selectedIds = new Set(options.inputIds ?? []);
9268
- const access = await navigatorWithMidi.requestMIDIAccess({
9269
- sysex: options.sysex ?? false,
9270
- software: options.software ?? true
9271
- });
9272
- for (const binding of options.ccBindings ?? []) {
9273
- engine.bindMidiCc(binding.channel, binding.controller, binding.paramId, binding.options);
9724
+ const detailReader = source.vocalEditLastErrorDetail;
9725
+ if (typeof detailReader !== "function") {
9726
+ cachedModuleSource = source;
9727
+ cachedVocalModule = source;
9728
+ return source;
9274
9729
  }
9275
- engine.setMidiInputSource(destinationId);
9276
- const bound = /* @__PURE__ */ new Map();
9277
- let closed = false;
9278
- const shouldBind = (input) => input.state !== "disconnected" && (selectedIds.size === 0 || selectedIds.has(input.id));
9279
- const snapshotInputs = () => Array.from(iterInputs(access), ([id, input]) => ({
9280
- id,
9281
- name: input.name ?? "",
9282
- manufacturer: input.manufacturer ?? "",
9283
- state: input.state ?? "connected"
9284
- }));
9285
- const notify = () => options.onInputsChanged?.(snapshotInputs());
9286
- const bindInput = (input) => {
9287
- if (bound.has(input.id) || !shouldBind(input)) {
9288
- return;
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;
9289
9764
  }
9290
- const entry = {
9291
- input,
9292
- listener: (event) => {
9293
- entry.runningStatus = dispatchMidiMessage(
9294
- engine,
9295
- event,
9296
- group,
9297
- entry.runningStatus,
9298
- options.timestampToSamples
9299
- );
9765
+ });
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`);
9773
+ }
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 }
9300
9852
  },
9301
- runningStatus: 0
9302
- };
9303
- const listener = entry.listener;
9304
- if (input.addEventListener) {
9305
- input.addEventListener("midimessage", listener);
9306
- } else {
9307
- input.onmidimessage = listener;
9853
+ amplitudeEnvelope: Array.from(note.edit.amplitudeEnvelope),
9854
+ formant: { ...note.edit.formant }
9308
9855
  }
9309
- bound.set(input.id, entry);
9856
+ }));
9857
+ return {
9858
+ notes,
9859
+ transitions: Array.from(value.transitions, (transition) => ({ ...transition }))
9310
9860
  };
9311
- const unbindInput = (input) => {
9312
- const entry = bound.get(input.id);
9313
- if (!entry) {
9314
- return;
9315
- }
9316
- if (entry.input.removeEventListener) {
9317
- entry.input.removeEventListener("midimessage", entry.listener);
9318
- } else if (entry.input.onmidimessage === entry.listener) {
9319
- entry.input.onmidimessage = null;
9320
- }
9321
- bound.delete(input.id);
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
9322
9883
  };
9323
- const refreshInputs = () => {
9324
- for (const [, entry] of bound) {
9325
- if (!shouldBind(entry.input)) {
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();
10977
+ let closed = false;
10978
+ const shouldBind = (input) => input.state !== "disconnected" && (selectedIds.size === 0 || selectedIds.has(input.id));
10979
+ const snapshotInputs = () => Array.from(iterInputs(access), ([id, input]) => ({
10980
+ id,
10981
+ name: input.name ?? "",
10982
+ manufacturer: input.manufacturer ?? "",
10983
+ state: input.state ?? "connected"
10984
+ }));
10985
+ const notify = () => options.onInputsChanged?.(snapshotInputs());
10986
+ const bindInput = (input) => {
10987
+ if (bound.has(input.id) || !shouldBind(input)) {
10988
+ return;
10989
+ }
10990
+ const entry = {
10991
+ input,
10992
+ listener: (event) => {
10993
+ entry.runningStatus = dispatchMidiMessage(
10994
+ engine,
10995
+ event,
10996
+ group,
10997
+ entry.runningStatus,
10998
+ options.timestampToSamples
10999
+ );
11000
+ },
11001
+ runningStatus: 0
11002
+ };
11003
+ const listener = entry.listener;
11004
+ if (input.addEventListener) {
11005
+ input.addEventListener("midimessage", listener);
11006
+ } else {
11007
+ input.onmidimessage = listener;
11008
+ }
11009
+ bound.set(input.id, entry);
11010
+ };
11011
+ const unbindInput = (input) => {
11012
+ const entry = bound.get(input.id);
11013
+ if (!entry) {
11014
+ return;
11015
+ }
11016
+ if (entry.input.removeEventListener) {
11017
+ entry.input.removeEventListener("midimessage", entry.listener);
11018
+ } else if (entry.input.onmidimessage === entry.listener) {
11019
+ entry.input.onmidimessage = null;
11020
+ }
11021
+ bound.delete(input.id);
11022
+ };
11023
+ const refreshInputs = () => {
11024
+ for (const [, entry] of bound) {
11025
+ if (!shouldBind(entry.input)) {
9326
11026
  unbindInput(entry.input);
9327
11027
  }
9328
11028
  }
@@ -9376,8 +11076,8 @@ function dispatchMidiMessage(engine, event, group, runningStatus, timestampToSam
9376
11076
  return 0;
9377
11077
  }
9378
11078
  const first = data[0];
9379
- if (first > 255) {
9380
- dispatchUmpMessage(
11079
+ if (first > 255 || data instanceof Uint32Array) {
11080
+ dispatchUmpMessages(
9381
11081
  engine,
9382
11082
  data,
9383
11083
  timestampToSamples?.(event.receivedTime ?? event.timeStamp ?? 0) ?? 0
@@ -9400,8 +11100,12 @@ function dispatchMidiMessage(engine, event, group, runningStatus, timestampToSam
9400
11100
  return status >= 248 ? runningStatus : 0;
9401
11101
  }
9402
11102
  const a = readU7(data, offset);
9403
- const b = readU7(data, offset + 1);
9404
- 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) {
9405
11109
  return status;
9406
11110
  }
9407
11111
  const portTimeSamples = timestampToSamples ? timestampToSamples(event.receivedTime ?? event.timeStamp ?? 0) : 0;
@@ -9415,13 +11119,42 @@ function dispatchMidiMessage(engine, event, group, runningStatus, timestampToSam
9415
11119
  }
9416
11120
  } else if (message === 176 && b >= 0) {
9417
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);
9418
11128
  }
9419
11129
  return status;
9420
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
+ }
9421
11147
  function dispatchUmpMessage(engine, words, portTimeSamples) {
9422
11148
  const word0 = words[0] >>> 0;
9423
11149
  const messageType = word0 >>> 28;
9424
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
+ }
9425
11158
  if (messageType === 2) {
9426
11159
  const status = word0 >>> 16 & 255;
9427
11160
  const message = status & 240;
@@ -9438,6 +11171,12 @@ function dispatchUmpMessage(engine, words, portTimeSamples) {
9438
11171
  }
9439
11172
  } else if (message === 176) {
9440
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);
9441
11180
  }
9442
11181
  return;
9443
11182
  }
@@ -9453,7 +11192,40 @@ function dispatchUmpMessage(engine, words, portTimeSamples) {
9453
11192
  engine.pushMidiInputNoteOn(group, channel, data1, velocity, portTimeSamples);
9454
11193
  } else if (status === 11) {
9455
11194
  engine.pushMidiInputCc(group, channel, data1, word1 >>> 25 & 127, portTimeSamples);
9456
- }
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;
9457
11229
  }
9458
11230
  }
9459
11231
  function readU7(data, index) {
@@ -9490,7 +11262,7 @@ var OfflineWorkerTask = class {
9490
11262
  return this.result.finally(onfinally);
9491
11263
  }
9492
11264
  };
9493
- function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
11265
+ function cloneForWorker2(value, copy, transfers, transferred = /* @__PURE__ */ new Set()) {
9494
11266
  if (value instanceof Float32Array) {
9495
11267
  const samples = copy ? value.slice() : value;
9496
11268
  const buffer = samples.buffer;
@@ -9506,25 +11278,25 @@ function cloneForWorker(value, copy, transfers, transferred = /* @__PURE__ */ ne
9506
11278
  return samples;
9507
11279
  }
9508
11280
  if (Array.isArray(value)) {
9509
- return value.map((item) => cloneForWorker(item, copy, transfers, transferred));
11281
+ return value.map((item) => cloneForWorker2(item, copy, transfers, transferred));
9510
11282
  }
9511
11283
  if (value !== null && typeof value === "object") {
9512
11284
  return Object.fromEntries(
9513
11285
  Object.entries(value).map(([key, item]) => [
9514
11286
  key,
9515
- cloneForWorker(item, copy, transfers, transferred)
11287
+ cloneForWorker2(item, copy, transfers, transferred)
9516
11288
  ])
9517
11289
  );
9518
11290
  }
9519
11291
  return value;
9520
11292
  }
9521
- function cancellationFlag() {
11293
+ function cancellationFlag2() {
9522
11294
  if (typeof SharedArrayBuffer === "undefined") {
9523
11295
  return void 0;
9524
11296
  }
9525
11297
  return new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
9526
11298
  }
9527
- function workerError(message) {
11299
+ function workerError2(message) {
9528
11300
  if (message.error.code !== void 0) {
9529
11301
  return new SonareError(
9530
11302
  message.error.code,
@@ -9557,7 +11329,7 @@ var OfflineWorkerClient = class {
9557
11329
  pending.resolve(message.result);
9558
11330
  return;
9559
11331
  }
9560
- pending.reject(workerError(message));
11332
+ pending.reject(workerError2(message));
9561
11333
  };
9562
11334
  this.onError = (event) => {
9563
11335
  const error = new Error(event.message || "Offline Worker failed");
@@ -9646,8 +11418,8 @@ var OfflineWorkerClient = class {
9646
11418
  }
9647
11419
  const id = this.nextId++;
9648
11420
  const transfers = [];
9649
- const preparedRequest = cloneForWorker(request, options.copy === true, transfers);
9650
- const cancelFlag = cancellationFlag();
11421
+ const preparedRequest = cloneForWorker2(request, options.copy === true, transfers);
11422
+ const cancelFlag = cancellationFlag2();
9651
11423
  const result = new Promise((resolve, reject) => {
9652
11424
  this.pending.set(id, {
9653
11425
  resolve: (value) => resolve(value),
@@ -9679,10 +11451,10 @@ var OfflineWorkerClient = class {
9679
11451
  };
9680
11452
 
9681
11453
  // src/index.ts
9682
- var module = null;
11454
+ var module2 = null;
9683
11455
  var initPromise = null;
9684
11456
  async function init(options) {
9685
- if (module) {
11457
+ if (module2) {
9686
11458
  return;
9687
11459
  }
9688
11460
  if (initPromise) {
@@ -9691,8 +11463,8 @@ async function init(options) {
9691
11463
  initPromise = (async () => {
9692
11464
  try {
9693
11465
  const createModule = options?.moduleFactory ?? (await import("./sonare.js")).default;
9694
- module = await createModule(options);
9695
- setSonareModule(module);
11466
+ module2 = await createModule(options);
11467
+ setSonareModule(module2);
9696
11468
  } catch (error) {
9697
11469
  initPromise = null;
9698
11470
  throw error;
@@ -9701,43 +11473,43 @@ async function init(options) {
9701
11473
  return initPromise;
9702
11474
  }
9703
11475
  function isInitialized() {
9704
- return module !== null;
11476
+ return module2 !== null;
9705
11477
  }
9706
11478
  function version() {
9707
- if (!module) {
11479
+ if (!module2) {
9708
11480
  throw new Error("Module not initialized. Call init() first.");
9709
11481
  }
9710
- return module.version();
11482
+ return module2.version();
9711
11483
  }
9712
11484
  function capabilities() {
9713
- if (!module) {
11485
+ if (!module2) {
9714
11486
  throw new Error("Module not initialized. Call init() first.");
9715
11487
  }
9716
- return module.capabilities();
11488
+ return module2.capabilities();
9717
11489
  }
9718
11490
  function capabilityCatalog() {
9719
- if (!module) {
11491
+ if (!module2) {
9720
11492
  throw new Error("Module not initialized. Call init() first.");
9721
11493
  }
9722
- return JSON.parse(module.capabilityCatalog());
11494
+ return JSON.parse(module2.capabilityCatalog());
9723
11495
  }
9724
11496
  function abiVersion() {
9725
- if (!module) {
11497
+ if (!module2) {
9726
11498
  throw new Error("Module not initialized. Call init() first.");
9727
11499
  }
9728
- return module.abiVersion();
11500
+ return module2.abiVersion();
9729
11501
  }
9730
11502
  function engineAbiVersion() {
9731
- if (!module) {
11503
+ if (!module2) {
9732
11504
  throw new Error("Module not initialized. Call init() first.");
9733
11505
  }
9734
- return module.engineAbiVersion();
11506
+ return module2.engineAbiVersion();
9735
11507
  }
9736
11508
  function voiceChangerAbiVersion() {
9737
- if (!module) {
11509
+ if (!module2) {
9738
11510
  throw new Error("Module not initialized. Call init() first.");
9739
11511
  }
9740
- return module.voiceChangerAbiVersion();
11512
+ return module2.voiceChangerAbiVersion();
9741
11513
  }
9742
11514
  var VOICE_PRESET_ORDINALS = [
9743
11515
  "neutral-monitor",
@@ -9761,19 +11533,19 @@ function resolveVoicePresetOrdinal(preset) {
9761
11533
  return ordinal;
9762
11534
  }
9763
11535
  function voiceCharacterPresetId(preset) {
9764
- if (!module) {
11536
+ if (!module2) {
9765
11537
  throw new Error("Module not initialized. Call init() first.");
9766
11538
  }
9767
11539
  if (typeof preset === "number" && (!Number.isSafeInteger(preset) || preset < 0 || preset >= VOICE_PRESET_ORDINALS.length)) {
9768
11540
  return null;
9769
11541
  }
9770
- return module.voiceCharacterPresetId(resolveVoicePresetOrdinal(preset));
11542
+ return module2.voiceCharacterPresetId(resolveVoicePresetOrdinal(preset));
9771
11543
  }
9772
11544
  function realtimeVoiceChangerPresetConfig(preset) {
9773
- if (!module) {
11545
+ if (!module2) {
9774
11546
  throw new Error("Module not initialized. Call init() first.");
9775
11547
  }
9776
- return module.realtimeVoiceChangerPresetConfig(resolveVoicePresetOrdinal(preset));
11548
+ return module2.realtimeVoiceChangerPresetConfig(resolveVoicePresetOrdinal(preset));
9777
11549
  }
9778
11550
  export {
9779
11551
  ARTICULATIONS,
@@ -9797,6 +11569,8 @@ export {
9797
11569
  NOTE_TRACKINGS,
9798
11570
  OfflineWorkerClient,
9799
11571
  OfflineWorkerTask,
11572
+ PART_RIG_ALL_PARTS,
11573
+ PART_RIG_MODES,
9800
11574
  PROJECT_AUTOMATION_TARGET_OPAQUE,
9801
11575
  PROJECT_AUTOMATION_TARGET_TRACK_FADER_DB,
9802
11576
  PROJECT_AUTOMATION_TARGET_TRACK_PAN,
@@ -9806,6 +11580,7 @@ export {
9806
11580
  PlaybackRenderer,
9807
11581
  PolyphonicAnalysis,
9808
11582
  Project,
11583
+ ProjectTimeline,
9809
11584
  RealtimeEngine,
9810
11585
  RealtimeVoiceChanger,
9811
11586
  SAMPLE_KEY_TRACKS,
@@ -9825,6 +11600,14 @@ export {
9825
11600
  StreamingEqualizer,
9826
11601
  StreamingMasteringChain,
9827
11602
  StreamingRetune,
11603
+ VocalEditDraft,
11604
+ VocalEditSession,
11605
+ VocalEditWorkerClient,
11606
+ VocalEditWorkerSession,
11607
+ VocalEditWorkerStaleResultError,
11608
+ VocalEditWorkerTask,
11609
+ VocalRenderJob,
11610
+ VocalRenderSnapshot,
9828
11611
  abiVersion,
9829
11612
  alignTakeToReference,
9830
11613
  amplitudeToDb,
@@ -9856,6 +11639,7 @@ export {
9856
11639
  cqtToAudio,
9857
11640
  createOpfsClipPageProvider,
9858
11641
  createOpfsClipPageWorker,
11642
+ createVocalEditSession,
9859
11643
  cyclicTempogram,
9860
11644
  dbToAmplitude,
9861
11645
  dbToPower,
@@ -10046,6 +11830,7 @@ export {
10046
11830
  renderPercussiveEvents,
10047
11831
  renderPlayback,
10048
11832
  resample,
11833
+ restoreVocalEditSession,
10049
11834
  rmsEnergy,
10050
11835
  roomMorph,
10051
11836
  samplesToFrames,
@@ -10095,6 +11880,8 @@ export {
10095
11880
  validateRealtimeVoiceChangerPresetJson,
10096
11881
  vectorNormalize,
10097
11882
  version,
11883
+ vocalEditApiVersion,
11884
+ vocalEditAvailable,
10098
11885
  voiceChange,
10099
11886
  voiceChangeRealtime,
10100
11887
  voiceChangerAbiVersion,