@libraz/libsonare 1.8.0 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +5 -5
  2. package/dist/_effects_common.d.ts +2 -1
  3. package/dist/_effects_common.d.ts.map +1 -1
  4. package/dist/analysis.js +3 -0
  5. package/dist/analysis.js.map +1 -1
  6. package/dist/effects_mastering.d.ts +2 -2
  7. package/dist/effects_mastering.d.ts.map +1 -1
  8. package/dist/effects_note_ops.d.ts +55 -30
  9. package/dist/effects_note_ops.d.ts.map +1 -1
  10. package/dist/effects_timepitch.d.ts +17 -12
  11. package/dist/effects_timepitch.d.ts.map +1 -1
  12. package/dist/feature_decompose.d.ts +1 -1
  13. package/dist/feature_decompose.d.ts.map +1 -1
  14. package/dist/index.d.ts +11 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2098 -155
  17. package/dist/index.js.map +1 -1
  18. package/dist/instrument_types.d.ts +27 -3
  19. package/dist/instrument_types.d.ts.map +1 -1
  20. package/dist/mastering_core.d.ts +30 -3
  21. package/dist/mastering_core.d.ts.map +1 -1
  22. package/dist/mixer.d.ts +2 -2
  23. package/dist/mixer.d.ts.map +1 -1
  24. package/dist/project.d.ts +5 -4
  25. package/dist/project.d.ts.map +1 -1
  26. package/dist/project_class.d.ts +57 -3
  27. package/dist/project_class.d.ts.map +1 -1
  28. package/dist/project_internal.d.ts +35 -1
  29. package/dist/project_internal.d.ts.map +1 -1
  30. package/dist/project_types.d.ts +17 -6
  31. package/dist/project_types.d.ts.map +1 -1
  32. package/dist/public_types.d.ts +1 -0
  33. package/dist/public_types.d.ts.map +1 -1
  34. package/dist/public_types_mastering.d.ts +19 -1
  35. package/dist/public_types_mastering.d.ts.map +1 -1
  36. package/dist/public_types_playback.d.ts +1 -0
  37. package/dist/public_types_playback.d.ts.map +1 -1
  38. package/dist/public_types_spectral.d.ts +5 -5
  39. package/dist/public_types_spectral.d.ts.map +1 -1
  40. package/dist/public_types_vocal_edit.d.ts +250 -0
  41. package/dist/public_types_vocal_edit.d.ts.map +1 -0
  42. package/dist/public_types_vocal_project.d.ts +63 -0
  43. package/dist/public_types_vocal_project.d.ts.map +1 -0
  44. package/dist/realtime_engine.d.ts +106 -16
  45. package/dist/realtime_engine.d.ts.map +1 -1
  46. package/dist/repair_dereverb.d.ts +1 -0
  47. package/dist/repair_dereverb.d.ts.map +1 -1
  48. package/dist/repair_noise.d.ts +1 -0
  49. package/dist/repair_noise.d.ts.map +1 -1
  50. package/dist/sonare-analysis.wasm +0 -0
  51. package/dist/sonare.d.ts +89 -0
  52. package/dist/sonare.js +1 -1
  53. package/dist/sonare.wasm +0 -0
  54. package/dist/streaming_processors.d.ts +5 -0
  55. package/dist/streaming_processors.d.ts.map +1 -1
  56. package/dist/vocal_edit.d.ts +131 -0
  57. package/dist/vocal_edit.d.ts.map +1 -0
  58. package/dist/vocal_edit_worker.d.ts +51 -0
  59. package/dist/vocal_edit_worker.d.ts.map +1 -0
  60. package/dist/vocal_edit_worker.js +1462 -0
  61. package/dist/vocal_edit_worker.js.map +1 -0
  62. package/dist/vocal_edit_worker_client.d.ts +118 -0
  63. package/dist/vocal_edit_worker_client.d.ts.map +1 -0
  64. package/dist/vocal_edit_worker_protocol.d.ts +139 -0
  65. package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
  66. package/dist/vocal_project.d.ts +17 -0
  67. package/dist/vocal_project.d.ts.map +1 -0
  68. package/dist/web_midi.d.ts +4 -0
  69. package/dist/web_midi.d.ts.map +1 -1
  70. package/dist/worker.js.map +1 -1
  71. package/dist/worklet/engine-mixer-facade.d.ts +42 -5
  72. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
  73. package/dist/worklet/engine-node.d.ts +10 -0
  74. package/dist/worklet/engine-node.d.ts.map +1 -1
  75. package/dist/worklet/engine-processor.d.ts +6 -1
  76. package/dist/worklet/engine-processor.d.ts.map +1 -1
  77. package/dist/worklet/engine-register.d.ts.map +1 -1
  78. package/dist/worklet/engine-strips.d.ts +8 -4
  79. package/dist/worklet/engine-strips.d.ts.map +1 -1
  80. package/dist/worklet/engine-sync.d.ts +4 -2
  81. package/dist/worklet/engine-sync.d.ts.map +1 -1
  82. package/dist/worklet/engine.d.ts +42 -9
  83. package/dist/worklet/engine.d.ts.map +1 -1
  84. package/dist/worklet/guards.d.ts +10 -2
  85. package/dist/worklet/guards.d.ts.map +1 -1
  86. package/dist/worklet/messages.d.ts +54 -9
  87. package/dist/worklet/messages.d.ts.map +1 -1
  88. package/dist/worklet/mixer-processor.d.ts +2 -2
  89. package/dist/worklet/mixer-processor.d.ts.map +1 -1
  90. package/dist/worklet/protocol.d.ts +20 -2
  91. package/dist/worklet/protocol.d.ts.map +1 -1
  92. package/dist/worklet.d.ts +1 -1
  93. package/dist/worklet.d.ts.map +1 -1
  94. package/dist/worklet.js +808 -204
  95. package/dist/worklet.js.map +1 -1
  96. package/package.json +6 -1
  97. package/src/_effects_common.ts +36 -6
  98. package/src/effects_mastering.ts +4 -0
  99. package/src/effects_note_ops.ts +92 -44
  100. package/src/effects_timepitch.ts +42 -31
  101. package/src/feature_decompose.ts +2 -2
  102. package/src/index.ts +39 -0
  103. package/src/instrument_types.ts +29 -3
  104. package/src/mastering_core.ts +98 -2
  105. package/src/mixer.ts +41 -21
  106. package/src/project.ts +8 -1
  107. package/src/project_class.ts +136 -1
  108. package/src/project_internal.ts +90 -0
  109. package/src/project_types.ts +18 -6
  110. package/src/public_types.ts +1 -0
  111. package/src/public_types_mastering.ts +23 -0
  112. package/src/public_types_playback.ts +1 -0
  113. package/src/public_types_spectral.ts +5 -5
  114. package/src/public_types_vocal_edit.ts +289 -0
  115. package/src/public_types_vocal_project.ts +69 -0
  116. package/src/realtime_engine.ts +157 -24
  117. package/src/repair_dereverb.ts +1 -0
  118. package/src/repair_noise.ts +1 -0
  119. package/src/sonare.js.d.ts +89 -0
  120. package/src/streaming_processors.ts +8 -0
  121. package/src/vocal_edit.ts +885 -0
  122. package/src/vocal_edit_worker.ts +832 -0
  123. package/src/vocal_edit_worker_client.ts +812 -0
  124. package/src/vocal_edit_worker_protocol.ts +206 -0
  125. package/src/vocal_project.ts +327 -0
  126. package/src/web_midi.ts +100 -5
  127. package/src/worklet/engine-mixer-facade.ts +422 -87
  128. package/src/worklet/engine-node.ts +69 -4
  129. package/src/worklet/engine-processor.ts +129 -34
  130. package/src/worklet/engine-register.ts +3 -0
  131. package/src/worklet/engine-strips.ts +36 -14
  132. package/src/worklet/engine-sync.ts +6 -1
  133. package/src/worklet/engine.ts +76 -40
  134. package/src/worklet/guards.ts +81 -0
  135. package/src/worklet/messages.ts +63 -4
  136. package/src/worklet/mixer-processor.ts +10 -3
  137. package/src/worklet/protocol.ts +39 -8
  138. package/src/worklet.ts +2 -0
@@ -0,0 +1,1462 @@
1
+ // src/errors.ts
2
+ var SonareError = class extends Error {
3
+ constructor(code, codeName, message) {
4
+ super(message);
5
+ this.name = "SonareError";
6
+ this.code = code;
7
+ this.codeName = codeName;
8
+ }
9
+ /**
10
+ * Brand-based `instanceof`: an error that carries the shape narrows here even
11
+ * when it is not literally an instance of this class. That is not a
12
+ * hypothetical — an error posted from the analysis worker arrives as a
13
+ * structured clone with its prototype gone, which a prototype-based
14
+ * `instanceof` would silently miss. Delegates to {@link isSonareError} so the
15
+ * two never disagree.
16
+ */
17
+ static [Symbol.hasInstance](value) {
18
+ return isSonareError(value);
19
+ }
20
+ };
21
+ function isSonareError(value) {
22
+ return value instanceof Error && value.name === "SonareError" && typeof value.code === "number";
23
+ }
24
+
25
+ // src/module_state.ts
26
+ var wrappedModule = null;
27
+ function nativeExceptionPtr(error) {
28
+ if (typeof error === "number") {
29
+ return error;
30
+ }
31
+ if (error !== null && typeof error === "object") {
32
+ const ptr = error.excPtr;
33
+ if (typeof ptr === "number") {
34
+ return ptr;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+ function makeSonareError(raw, thrown) {
40
+ let code = 99 /* Unknown */;
41
+ let codeName = "Unknown";
42
+ let message = `libsonare native exception (${thrown})`;
43
+ try {
44
+ const info = raw.sonareExceptionInfo?.(thrown);
45
+ if (info) {
46
+ code = info.code ?? code;
47
+ codeName = info.codeName ?? codeName;
48
+ message = info.message || message;
49
+ }
50
+ } catch {
51
+ } finally {
52
+ try {
53
+ raw.sonareReleaseException(thrown);
54
+ } catch {
55
+ }
56
+ }
57
+ return new SonareError(code, codeName, message);
58
+ }
59
+ function wrapModuleErrors(raw) {
60
+ const cache = /* @__PURE__ */ new Map();
61
+ const objectCache = /* @__PURE__ */ new WeakMap();
62
+ const convert = (error) => {
63
+ const ptr = nativeExceptionPtr(error);
64
+ if (ptr !== null) {
65
+ throw makeSonareError(raw, ptr);
66
+ }
67
+ throw error;
68
+ };
69
+ const wrapNativeObject = (value) => {
70
+ if (value === null || typeof value !== "object") {
71
+ return value;
72
+ }
73
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof Promise) {
74
+ return value;
75
+ }
76
+ const proto = Object.getPrototypeOf(value);
77
+ if (Array.isArray(value) || proto === Object.prototype || proto === null) {
78
+ return value;
79
+ }
80
+ const objectValue = value;
81
+ const cached = objectCache.get(objectValue);
82
+ if (cached) {
83
+ return cached;
84
+ }
85
+ const methodCache = /* @__PURE__ */ new Map();
86
+ const wrapped = new Proxy(objectValue, {
87
+ get(target, prop, receiver) {
88
+ const member = Reflect.get(target, prop, receiver);
89
+ if (typeof member !== "function") {
90
+ return member;
91
+ }
92
+ const cachedMethod = methodCache.get(prop);
93
+ if (cachedMethod) {
94
+ return cachedMethod;
95
+ }
96
+ const method = member;
97
+ const wrappedMethod = (...args) => {
98
+ try {
99
+ return wrapNativeObject(Reflect.apply(method, target, args));
100
+ } catch (error) {
101
+ return convert(error);
102
+ }
103
+ };
104
+ methodCache.set(prop, wrappedMethod);
105
+ return wrappedMethod;
106
+ }
107
+ });
108
+ objectCache.set(objectValue, wrapped);
109
+ return wrapped;
110
+ };
111
+ const wrapFunction = (value) => {
112
+ const fnCache = /* @__PURE__ */ new Map();
113
+ return new Proxy(value, {
114
+ get(target, prop, receiver) {
115
+ const member = Reflect.get(target, prop, receiver);
116
+ if (typeof member !== "function") {
117
+ return member;
118
+ }
119
+ const cachedMember = fnCache.get(prop);
120
+ if (cachedMember) {
121
+ return cachedMember;
122
+ }
123
+ const fn = member;
124
+ const wrappedMember = (...args) => {
125
+ try {
126
+ return wrapNativeObject(Reflect.apply(fn, target, args));
127
+ } catch (error) {
128
+ return convert(error);
129
+ }
130
+ };
131
+ fnCache.set(prop, wrappedMember);
132
+ return wrappedMember;
133
+ },
134
+ apply(t, thisArg, args) {
135
+ try {
136
+ return wrapNativeObject(Reflect.apply(t, thisArg, args));
137
+ } catch (error) {
138
+ return convert(error);
139
+ }
140
+ },
141
+ construct(t, args, newTarget) {
142
+ try {
143
+ return wrapNativeObject(Reflect.construct(t, args, newTarget));
144
+ } catch (error) {
145
+ return convert(error);
146
+ }
147
+ }
148
+ });
149
+ };
150
+ return new Proxy(raw, {
151
+ get(target, prop, receiver) {
152
+ const value = Reflect.get(target, prop, receiver);
153
+ if (typeof value !== "function") {
154
+ return value;
155
+ }
156
+ const cached = cache.get(prop);
157
+ if (cached) {
158
+ return cached;
159
+ }
160
+ const wrapped = wrapFunction(value);
161
+ cache.set(prop, wrapped);
162
+ return wrapped;
163
+ }
164
+ });
165
+ }
166
+ function setSonareModule(module3) {
167
+ wrappedModule = wrapModuleErrors(module3);
168
+ }
169
+ function getSonareModule() {
170
+ if (!wrappedModule) {
171
+ throw new Error("Module not initialized. Call init() first.");
172
+ }
173
+ return wrappedModule;
174
+ }
175
+
176
+ // src/vocal_edit.ts
177
+ var vocalErrorFields = [
178
+ "reason",
179
+ "field",
180
+ "expected",
181
+ "actual",
182
+ "expectedText",
183
+ "actualText"
184
+ ];
185
+ var cachedModuleSource = null;
186
+ var cachedVocalModule = null;
187
+ function addVocalErrorDetail(error, detail) {
188
+ if (error === null || typeof error !== "object") {
189
+ return;
190
+ }
191
+ const candidate = error;
192
+ if (candidate.name !== "SonareError" || typeof candidate.code !== "number") {
193
+ return;
194
+ }
195
+ for (const field of vocalErrorFields) {
196
+ Object.defineProperty(candidate, field, {
197
+ configurable: true,
198
+ enumerable: true,
199
+ value: detail[field],
200
+ writable: false
201
+ });
202
+ }
203
+ }
204
+ function module() {
205
+ const source = getSonareModule();
206
+ if (source === cachedModuleSource && cachedVocalModule !== null) {
207
+ return cachedVocalModule;
208
+ }
209
+ const detailReader = source.vocalEditLastErrorDetail;
210
+ if (typeof detailReader !== "function") {
211
+ cachedModuleSource = source;
212
+ cachedVocalModule = source;
213
+ return source;
214
+ }
215
+ const functionCache = /* @__PURE__ */ new Map();
216
+ const wrapped = new Proxy(source, {
217
+ get(target, property, receiver) {
218
+ const member = Reflect.get(target, property, receiver);
219
+ if (property === "vocalEditLastErrorDetail" || typeof property !== "string" || !property.startsWith("vocalEdit") || typeof member !== "function") {
220
+ return member;
221
+ }
222
+ const cached = functionCache.get(property);
223
+ if (cached !== void 0) {
224
+ return cached;
225
+ }
226
+ const wrappedMember = (...args) => {
227
+ let beforeSequence;
228
+ try {
229
+ beforeSequence = detailReader.call(target).sequence;
230
+ } catch {
231
+ }
232
+ try {
233
+ return Reflect.apply(member, target, args);
234
+ } catch (error) {
235
+ if (beforeSequence !== void 0) {
236
+ try {
237
+ const detail = detailReader.call(target);
238
+ if (detail.sequence !== beforeSequence) {
239
+ addVocalErrorDetail(error, detail);
240
+ }
241
+ } catch {
242
+ }
243
+ }
244
+ throw error;
245
+ }
246
+ };
247
+ functionCache.set(property, wrappedMember);
248
+ return wrappedMember;
249
+ }
250
+ });
251
+ cachedModuleSource = source;
252
+ cachedVocalModule = wrapped;
253
+ return wrapped;
254
+ }
255
+ function requireHandle(handle, subject) {
256
+ if (!Number.isSafeInteger(handle) || handle <= 0) {
257
+ throw new TypeError(`${subject} has already been disposed`);
258
+ }
259
+ }
260
+ function requireFinite(value, field) {
261
+ if (!Number.isFinite(value)) {
262
+ throw new RangeError(`${field} must be finite`);
263
+ }
264
+ return value;
265
+ }
266
+ function requireSample(value, field) {
267
+ requireFinite(value, field);
268
+ if (!Number.isSafeInteger(value) || value < 0) {
269
+ throw new RangeError(`${field} must be a non-negative safe integer`);
270
+ }
271
+ return value;
272
+ }
273
+ function requireUint32(value, field) {
274
+ requireSample(value, field);
275
+ if (value > 4294967295) {
276
+ throw new RangeError(`${field} must fit uint32`);
277
+ }
278
+ return value;
279
+ }
280
+ function requireSampleRate(value) {
281
+ requireSample(value, "sampleRate");
282
+ if (value < 8e3 || value > 384e3) {
283
+ throw new RangeError("sampleRate must be an integer in [8000, 384000]");
284
+ }
285
+ return value;
286
+ }
287
+ function requireToken(value, field, fallback = "0") {
288
+ const token = value ?? fallback;
289
+ if (typeof token !== "string" || !/^(0|[1-9]\d*)$/.test(token)) {
290
+ throw new RangeError(`${field} must be a canonical decimal uint64 string`);
291
+ }
292
+ if (token.length > 20 || token.length === 20 && token > "18446744073709551615") {
293
+ throw new RangeError(`${field} must fit uint64`);
294
+ }
295
+ return token;
296
+ }
297
+ function resolveRevision(argument, fallback) {
298
+ const value = typeof argument === "string" ? argument : argument?.expectedRevision;
299
+ return requireToken(value, "expectedRevision", fallback);
300
+ }
301
+ function requireRange(range) {
302
+ if (!range || typeof range !== "object") {
303
+ throw new TypeError("range must be an object");
304
+ }
305
+ const startSample = requireSample(range.startSample, "range.startSample");
306
+ const endSample = requireSample(range.endSample, "range.endSample");
307
+ if (endSample < startSample) {
308
+ throw new RangeError("range.endSample must be >= startSample");
309
+ }
310
+ return { startSample, endSample };
311
+ }
312
+ function copyToken(token) {
313
+ return {
314
+ sessionEpoch: requireToken(token.sessionEpoch, "sessionEpoch"),
315
+ revision: requireToken(token.revision, "revision"),
316
+ draftId: requireToken(token.draftId, "draftId"),
317
+ generation: requireToken(token.generation, "generation"),
318
+ requestId: requireToken(token.requestId, "requestId"),
319
+ profileId: token.profileId
320
+ };
321
+ }
322
+ function copyRange(range) {
323
+ return { startSample: range.startSample, endSample: range.endSample };
324
+ }
325
+ function copyNotes(value) {
326
+ const notes = Array.from(value.notes, (note) => ({
327
+ ...note,
328
+ amplitude: new Float32Array(note.amplitude),
329
+ edit: {
330
+ ...note.edit,
331
+ pitch: {
332
+ ...note.edit.pitch,
333
+ target: note.edit.pitch.target.mode === "curve" ? {
334
+ mode: "curve",
335
+ points: note.edit.pitch.target.points.map((point) => ({ ...point }))
336
+ } : { ...note.edit.pitch.target }
337
+ },
338
+ amplitudeEnvelope: Array.from(note.edit.amplitudeEnvelope),
339
+ formant: { ...note.edit.formant }
340
+ }
341
+ }));
342
+ return {
343
+ notes,
344
+ transitions: Array.from(value.transitions, (transition) => ({ ...transition }))
345
+ };
346
+ }
347
+ function copyAnalysis(value) {
348
+ return {
349
+ frameOriginSample: value.frameOriginSample,
350
+ samplesPerFrame: value.samplesPerFrame,
351
+ frameLengthSamples: value.frameLengthSamples,
352
+ f0Hz: new Float32Array(value.f0Hz),
353
+ voiced: new Uint8Array(value.voiced),
354
+ algorithmId: value.algorithmId,
355
+ algorithmVersion: value.algorithmVersion,
356
+ sourceLengthSamples: value.sourceLengthSamples,
357
+ sampleRate: value.sampleRate,
358
+ sourceSha256: value.sourceSha256,
359
+ analysisSha256: value.analysisSha256,
360
+ fminHz: value.fminHz,
361
+ fmaxHz: value.fmaxHz,
362
+ yinThreshold: value.yinThreshold,
363
+ voicedThreshold: value.voicedThreshold,
364
+ centered: value.centered,
365
+ segmentationThresholdCents: value.segmentationThresholdCents,
366
+ minNoteMs: value.minNoteMs,
367
+ referenceHz: value.referenceHz
368
+ };
369
+ }
370
+ function copyPitch(value) {
371
+ return {
372
+ sourceSamples: new Float64Array(value.sourceSamples),
373
+ measuredMidi: new Float64Array(value.measuredMidi),
374
+ targetMidi: new Float64Array(value.targetMidi),
375
+ effectiveMidi: new Float64Array(value.effectiveMidi),
376
+ voiced: new Uint8Array(value.voiced),
377
+ hasTarget: new Uint8Array(value.hasTarget)
378
+ };
379
+ }
380
+ function copyRender(value) {
381
+ return {
382
+ samples: new Float32Array(value.samples),
383
+ startSample: value.startSample,
384
+ token: copyToken(value.token),
385
+ processedRanges: Array.from(value.processedRanges, copyRange),
386
+ cacheHitUnits: requireToken(value.cacheHitUnits, "cacheHitUnits"),
387
+ dryPassedFrames: requireToken(value.dryPassedFrames, "dryPassedFrames"),
388
+ limitedCorrectionFrames: requireToken(value.limitedCorrectionFrames, "limitedCorrectionFrames")
389
+ };
390
+ }
391
+ function validateSessionLimits(limits) {
392
+ if (limits === void 0) {
393
+ return;
394
+ }
395
+ if (limits.maxHistoryBytes !== void 0) {
396
+ requireToken(limits.maxHistoryBytes, "limits.maxHistoryBytes");
397
+ }
398
+ if (limits.maxCacheBytes !== void 0) {
399
+ requireToken(limits.maxCacheBytes, "limits.maxCacheBytes");
400
+ }
401
+ }
402
+ function copyEditResult(value) {
403
+ return {
404
+ token: copyToken(value.token),
405
+ dirtyRanges: Array.from(value.dirtyRanges, copyRange),
406
+ idChanges: Array.from(value.idChanges, (change) => ({
407
+ operationIndex: change.operationIndex,
408
+ retiredId: change.retiredId,
409
+ newIds: Array.from(change.newIds)
410
+ }))
411
+ };
412
+ }
413
+ function validateCreate(request) {
414
+ if (!request || typeof request !== "object") {
415
+ throw new TypeError("request must be an object");
416
+ }
417
+ if (!(request.samples instanceof Float32Array) || request.samples.length === 0) {
418
+ throw new TypeError("samples must be a non-empty Float32Array");
419
+ }
420
+ for (const sample of request.samples) {
421
+ requireFinite(sample, "samples");
422
+ }
423
+ requireSampleRate(request.sampleRate);
424
+ if (request.outputLengthSamples !== void 0) {
425
+ requireSample(request.outputLengthSamples, "outputLengthSamples");
426
+ }
427
+ validateSessionLimits(request.limits);
428
+ if (request.analysis) {
429
+ const analysis = request.analysis;
430
+ if (analysis.algorithmId !== void 0) {
431
+ if (typeof analysis.algorithmId !== "string") {
432
+ throw new TypeError("analysis.algorithmId must be a string");
433
+ }
434
+ if (analysis.algorithmId.includes("\0")) {
435
+ throw new RangeError("analysis.algorithmId must not contain NUL");
436
+ }
437
+ }
438
+ requireFinite(analysis.frameOriginSample, "analysis.frameOriginSample");
439
+ requireFinite(analysis.samplesPerFrame, "analysis.samplesPerFrame");
440
+ if (analysis.samplesPerFrame <= 0) {
441
+ throw new RangeError("analysis.samplesPerFrame must be positive");
442
+ }
443
+ requireSample(analysis.frameLengthSamples, "analysis.frameLengthSamples");
444
+ if (analysis.frameLengthSamples === 0) {
445
+ throw new RangeError("analysis.frameLengthSamples must be positive");
446
+ }
447
+ if (!(analysis.f0Hz instanceof Float32Array) || !(analysis.voiced instanceof Uint8Array)) {
448
+ throw new TypeError("analysis arrays must be Float32Array and Uint8Array");
449
+ }
450
+ if (analysis.f0Hz.length !== analysis.voiced.length) {
451
+ throw new RangeError("analysis arrays must have equal lengths");
452
+ }
453
+ }
454
+ }
455
+ function normalizeRequest(request, outputLengthSamples) {
456
+ const range = request.range === void 0 ? { startSample: 0, endSample: requireSample(outputLengthSamples, "outputLengthSamples") } : requireRange(request.range);
457
+ const requestId = requireToken(request.requestId, "requestId");
458
+ return { range, requestId };
459
+ }
460
+ function vocalEditAvailable() {
461
+ const raw = module();
462
+ return typeof raw.vocalEditAvailable === "function" && raw.vocalEditAvailable() !== 0;
463
+ }
464
+ function createVocalEditSession(request) {
465
+ validateCreate(request);
466
+ if (!vocalEditAvailable()) {
467
+ throw new Error("vocal edit is unavailable in this WASM build");
468
+ }
469
+ return new VocalEditSession(
470
+ module().vocalEditSessionCreate(request.samples, request.sampleRate, request)
471
+ );
472
+ }
473
+ function restoreVocalEditSession(request) {
474
+ if (!(request.samples instanceof Float32Array) || request.samples.length === 0) {
475
+ throw new TypeError("samples must be a non-empty Float32Array");
476
+ }
477
+ if (!(request.state instanceof Uint8Array)) {
478
+ throw new TypeError("state must be a Uint8Array");
479
+ }
480
+ for (const sample of request.samples) {
481
+ requireFinite(sample, "samples");
482
+ }
483
+ requireSampleRate(request.sampleRate);
484
+ validateSessionLimits(request.limits);
485
+ if (!vocalEditAvailable()) {
486
+ throw new Error("vocal edit is unavailable in this WASM build");
487
+ }
488
+ return new VocalEditSession(
489
+ module().vocalEditSessionRestore(request.samples, request.sampleRate, request.state, request)
490
+ );
491
+ }
492
+ var VocalEditSession = class {
493
+ /** @internal Instances are created by {@link createVocalEditSession} and {@link restoreVocalEditSession}. */
494
+ constructor(handle) {
495
+ this.drafts = /* @__PURE__ */ new Set();
496
+ this.disposed = false;
497
+ requireHandle(handle, "session");
498
+ this.handle = handle;
499
+ }
500
+ dispose() {
501
+ if (this.disposed) {
502
+ return;
503
+ }
504
+ for (const draft of this.drafts) {
505
+ draft.dispose();
506
+ }
507
+ this.drafts.clear();
508
+ module().vocalEditSessionDestroy(this.handle);
509
+ this.handle = 0;
510
+ this.disposed = true;
511
+ }
512
+ delete() {
513
+ this.dispose();
514
+ }
515
+ destroy() {
516
+ this.dispose();
517
+ }
518
+ native() {
519
+ if (this.disposed) {
520
+ throw new TypeError("session has already been disposed");
521
+ }
522
+ requireHandle(this.handle, "session");
523
+ return this.handle;
524
+ }
525
+ token() {
526
+ return copyToken(module().vocalEditSessionToken(this.native()));
527
+ }
528
+ revision() {
529
+ return this.token().revision;
530
+ }
531
+ notes() {
532
+ return copyNotes(module().vocalEditSessionNotes(this.native()));
533
+ }
534
+ analysis() {
535
+ return copyAnalysis(module().vocalEditSessionAnalysis(this.native()));
536
+ }
537
+ capabilities() {
538
+ return { ...module().vocalEditSessionCapabilities(this.native()) };
539
+ }
540
+ outputLengthSamples() {
541
+ return requireSample(
542
+ module().vocalEditSessionOutputLength(this.native()),
543
+ "outputLengthSamples"
544
+ );
545
+ }
546
+ history() {
547
+ const value = module().vocalEditSessionHistory(this.native());
548
+ return { canUndo: value.canUndo === true, canRedo: value.canRedo === true };
549
+ }
550
+ beginEdit(options = {}) {
551
+ const draft = new VocalEditDraft(
552
+ module().vocalEditSessionBeginEdit(this.native(), resolveRevision(options, this.revision())),
553
+ this
554
+ );
555
+ this.drafts.add(draft);
556
+ return draft;
557
+ }
558
+ undo(options = {}) {
559
+ return copyEditResult(
560
+ module().vocalEditSessionApplyHistory(
561
+ this.native(),
562
+ resolveRevision(options, this.revision()),
563
+ false
564
+ )
565
+ );
566
+ }
567
+ redo(options = {}) {
568
+ return copyEditResult(
569
+ module().vocalEditSessionApplyHistory(
570
+ this.native(),
571
+ resolveRevision(options, this.revision()),
572
+ true
573
+ )
574
+ );
575
+ }
576
+ evaluatePitch(noteId) {
577
+ return copyPitch(
578
+ module().vocalEditEvaluatePitch(this.native(), requireUint32(noteId, "noteId"), false)
579
+ );
580
+ }
581
+ sourceSampleToDestinationSample(noteId, sourceSample) {
582
+ requireFinite(sourceSample, "sourceSample");
583
+ return module().vocalEditMapCoordinate(
584
+ this.native(),
585
+ requireUint32(noteId, "noteId"),
586
+ sourceSample,
587
+ false,
588
+ false
589
+ );
590
+ }
591
+ destinationSampleToSourceSample(noteId, destinationSample) {
592
+ requireFinite(destinationSample, "destinationSample");
593
+ return module().vocalEditMapCoordinate(
594
+ this.native(),
595
+ requireUint32(noteId, "noteId"),
596
+ destinationSample,
597
+ false,
598
+ true
599
+ );
600
+ }
601
+ captureRenderSnapshot() {
602
+ return new VocalRenderSnapshot(module().vocalEditCaptureSnapshot(this.native(), false));
603
+ }
604
+ exportState() {
605
+ return new Uint8Array(module().vocalEditSessionExportState(this.native()));
606
+ }
607
+ /** @internal Used by the draft to remove itself from the owner set. */
608
+ _forgetDraft(draft) {
609
+ this.drafts.delete(draft);
610
+ }
611
+ };
612
+ var VocalEditDraft = class {
613
+ /** @internal Instances are created by {@link VocalEditSession.beginEdit}. */
614
+ constructor(handle, owner) {
615
+ this.disposed = false;
616
+ requireHandle(handle, "draft");
617
+ this.handle = handle;
618
+ this.owner = owner;
619
+ }
620
+ native() {
621
+ if (this.disposed) {
622
+ throw new TypeError("draft has already been disposed");
623
+ }
624
+ requireHandle(this.handle, "draft");
625
+ return this.handle;
626
+ }
627
+ token() {
628
+ return copyToken(module().vocalEditDraftToken(this.native()));
629
+ }
630
+ notes() {
631
+ return copyNotes(module().vocalEditDraftNotes(this.native()));
632
+ }
633
+ apply(request) {
634
+ if (request.expectedGeneration === void 0) {
635
+ throw new TypeError("expectedGeneration is required");
636
+ }
637
+ const generation = requireToken(request.expectedGeneration, "expectedGeneration");
638
+ if (!Array.isArray(request.operations)) {
639
+ throw new TypeError("operations must be an array");
640
+ }
641
+ return copyEditResult(
642
+ module().vocalEditDraftApply(this.native(), generation, request.operations)
643
+ );
644
+ }
645
+ commit(options = {}) {
646
+ const result = copyEditResult(
647
+ module().vocalEditDraftCommit(this.native(), resolveRevision(options, this.owner.revision()))
648
+ );
649
+ this.dispose();
650
+ return result;
651
+ }
652
+ cancel() {
653
+ if (this.disposed) {
654
+ return;
655
+ }
656
+ module().vocalEditDraftCancel(this.native());
657
+ this.dispose();
658
+ }
659
+ evaluatePitch(noteId) {
660
+ return copyPitch(
661
+ module().vocalEditEvaluatePitch(this.native(), requireUint32(noteId, "noteId"), true)
662
+ );
663
+ }
664
+ sourceSampleToDestinationSample(noteId, sourceSample) {
665
+ requireFinite(sourceSample, "sourceSample");
666
+ return module().vocalEditMapCoordinate(
667
+ this.native(),
668
+ requireUint32(noteId, "noteId"),
669
+ sourceSample,
670
+ true,
671
+ false
672
+ );
673
+ }
674
+ destinationSampleToSourceSample(noteId, destinationSample) {
675
+ requireFinite(destinationSample, "destinationSample");
676
+ return module().vocalEditMapCoordinate(
677
+ this.native(),
678
+ requireUint32(noteId, "noteId"),
679
+ destinationSample,
680
+ true,
681
+ true
682
+ );
683
+ }
684
+ captureRenderSnapshot() {
685
+ return new VocalRenderSnapshot(module().vocalEditCaptureSnapshot(this.native(), true));
686
+ }
687
+ dispose() {
688
+ if (this.disposed) {
689
+ return;
690
+ }
691
+ module().vocalEditDraftDestroy(this.handle);
692
+ this.handle = 0;
693
+ this.disposed = true;
694
+ this.owner._forgetDraft(this);
695
+ }
696
+ delete() {
697
+ this.dispose();
698
+ }
699
+ destroy() {
700
+ this.dispose();
701
+ }
702
+ };
703
+ var VocalRenderSnapshot = class {
704
+ /** @internal Instances are created by a session or draft. */
705
+ constructor(handle) {
706
+ this.disposed = false;
707
+ requireHandle(handle, "snapshot");
708
+ this.handle = handle;
709
+ }
710
+ native() {
711
+ if (this.disposed) {
712
+ throw new TypeError("snapshot has already been disposed");
713
+ }
714
+ requireHandle(this.handle, "snapshot");
715
+ return this.handle;
716
+ }
717
+ outputLengthSamples() {
718
+ return requireSample(
719
+ module().vocalEditSnapshotOutputLength(this.native()),
720
+ "outputLengthSamples"
721
+ );
722
+ }
723
+ render(request) {
724
+ const handle = this.native();
725
+ if (request.signal?.aborted) {
726
+ throw new DOMException("The render was aborted", "AbortError");
727
+ }
728
+ const normalized = normalizeRequest(request, this.outputLengthSamples());
729
+ return copyRender(
730
+ module().vocalEditSnapshotRender(
731
+ handle,
732
+ normalized.range.startSample,
733
+ normalized.range.endSample,
734
+ normalized.requestId
735
+ )
736
+ );
737
+ }
738
+ beginRenderJob(request) {
739
+ const handle = this.native();
740
+ if (request.signal?.aborted) {
741
+ throw new DOMException("The render was aborted", "AbortError");
742
+ }
743
+ const normalized = normalizeRequest(request, this.outputLengthSamples());
744
+ return new VocalRenderJob(
745
+ module().vocalEditRenderJobBegin(
746
+ handle,
747
+ normalized.range.startSample,
748
+ normalized.range.endSample,
749
+ normalized.requestId
750
+ )
751
+ );
752
+ }
753
+ /**
754
+ * Render one native work unit per macrotask so `request.signal` can stop the
755
+ * job between units. A cancelled job publishes no partial PCM.
756
+ */
757
+ renderAsync(request) {
758
+ this.native();
759
+ const signal = request.signal;
760
+ if (signal?.aborted) {
761
+ return Promise.reject(new DOMException("The render was aborted", "AbortError"));
762
+ }
763
+ const job = this.beginRenderJob(request);
764
+ return (async () => {
765
+ try {
766
+ while (true) {
767
+ if (signal?.aborted) {
768
+ job.abort();
769
+ throw new DOMException("The render was aborted", "AbortError");
770
+ }
771
+ if (job.next().complete) {
772
+ return job.finalize();
773
+ }
774
+ await new Promise((resolve) => setTimeout(resolve, 0));
775
+ }
776
+ } catch (error) {
777
+ job.abort();
778
+ throw error;
779
+ } finally {
780
+ job.dispose();
781
+ }
782
+ })();
783
+ }
784
+ dispose() {
785
+ if (this.disposed) {
786
+ return;
787
+ }
788
+ module().vocalEditSnapshotDestroy(this.handle);
789
+ this.handle = 0;
790
+ this.disposed = true;
791
+ }
792
+ delete() {
793
+ this.dispose();
794
+ }
795
+ destroy() {
796
+ this.dispose();
797
+ }
798
+ };
799
+ var VocalRenderJob = class {
800
+ /** @internal Instances are created by {@link VocalRenderSnapshot.beginRenderJob}. */
801
+ constructor(handle) {
802
+ this.disposed = false;
803
+ requireHandle(handle, "render job");
804
+ this.handle = handle;
805
+ }
806
+ native() {
807
+ if (this.disposed) {
808
+ throw new TypeError("render job has already been disposed");
809
+ }
810
+ requireHandle(this.handle, "render job");
811
+ return this.handle;
812
+ }
813
+ next() {
814
+ return { complete: module().vocalEditRenderJobNext(this.native()) };
815
+ }
816
+ finalize() {
817
+ const result = copyRender(module().vocalEditRenderJobFinalize(this.native()));
818
+ this.dispose();
819
+ return result;
820
+ }
821
+ abort() {
822
+ if (!this.disposed) {
823
+ module().vocalEditRenderJobAbort(this.handle);
824
+ }
825
+ }
826
+ dispose() {
827
+ if (this.disposed) {
828
+ return;
829
+ }
830
+ module().vocalEditRenderJobDestroy(this.handle);
831
+ this.handle = 0;
832
+ this.disposed = true;
833
+ }
834
+ delete() {
835
+ this.dispose();
836
+ }
837
+ destroy() {
838
+ this.dispose();
839
+ }
840
+ };
841
+
842
+ // src/vocal_edit_worker_protocol.ts
843
+ function compareUint64(a, b) {
844
+ const left = a.replace(/^0+(?=\d)/, "");
845
+ const right = b.replace(/^0+(?=\d)/, "");
846
+ if (left.length !== right.length) {
847
+ return left.length < right.length ? -1 : 1;
848
+ }
849
+ if (left === right) {
850
+ return 0;
851
+ }
852
+ return left < right ? -1 : 1;
853
+ }
854
+
855
+ // src/index.ts
856
+ var module2 = null;
857
+ var initPromise = null;
858
+ async function init(options) {
859
+ if (module2) {
860
+ return;
861
+ }
862
+ if (initPromise) {
863
+ return initPromise;
864
+ }
865
+ initPromise = (async () => {
866
+ try {
867
+ const createModule = options?.moduleFactory ?? (await import("./sonare.js")).default;
868
+ module2 = await createModule(options);
869
+ setSonareModule(module2);
870
+ } catch (error) {
871
+ initPromise = null;
872
+ throw error;
873
+ }
874
+ })();
875
+ return initPromise;
876
+ }
877
+
878
+ // src/vocal_edit_worker.ts
879
+ var productionFactory = {
880
+ create: createVocalEditSession,
881
+ restore: restoreVocalEditSession
882
+ };
883
+ function errorPayload(error) {
884
+ const candidate = error;
885
+ return {
886
+ name: candidate.name ?? "Error",
887
+ message: candidate.message ?? String(error),
888
+ ...candidate.code === void 0 ? {} : { code: candidate.code },
889
+ ...candidate.codeName === void 0 ? {} : { codeName: candidate.codeName },
890
+ ...candidate.reason === void 0 ? {} : { reason: candidate.reason },
891
+ ...candidate.field === void 0 ? {} : { field: candidate.field },
892
+ ...candidate.expected === void 0 ? {} : { expected: candidate.expected },
893
+ ...candidate.actual === void 0 ? {} : { actual: candidate.actual },
894
+ ...candidate.expectedText === void 0 ? {} : { expectedText: candidate.expectedText },
895
+ ...candidate.actualText === void 0 ? {} : { actualText: candidate.actualText }
896
+ };
897
+ }
898
+ function transferables(value) {
899
+ const result = [];
900
+ const seen = /* @__PURE__ */ new Set();
901
+ const visit = (item) => {
902
+ if (item === null || item === void 0 || typeof item !== "object") {
903
+ return;
904
+ }
905
+ if (ArrayBuffer.isView(item)) {
906
+ const buffer = item.buffer;
907
+ if (buffer instanceof ArrayBuffer && !seen.has(buffer)) {
908
+ seen.add(buffer);
909
+ result.push(buffer);
910
+ }
911
+ return;
912
+ }
913
+ if (item instanceof ArrayBuffer) {
914
+ if (!seen.has(item)) {
915
+ seen.add(item);
916
+ result.push(item);
917
+ }
918
+ return;
919
+ }
920
+ if (Array.isArray(item)) {
921
+ item.forEach(visit);
922
+ return;
923
+ }
924
+ Object.values(item).forEach(visit);
925
+ };
926
+ visit(value);
927
+ return result;
928
+ }
929
+ function cancelledError() {
930
+ const error = new Error("Vocal render cancelled");
931
+ error.name = "AbortError";
932
+ return error;
933
+ }
934
+ function isCancelled(active) {
935
+ return active.cancelled || active.cancelBuffer !== void 0 && Atomics.load(new Int32Array(active.cancelBuffer), 0) !== 0;
936
+ }
937
+ function nextMacrotask() {
938
+ return new Promise((resolve) => setTimeout(resolve, 0));
939
+ }
940
+ function resultToken(value, session) {
941
+ const candidate = value;
942
+ return candidate?.token ?? currentToken(session);
943
+ }
944
+ function currentToken(session) {
945
+ return session.draft?.token() ?? session.value.token();
946
+ }
947
+ function postResult(endpoint2, id, sessionId, clientIntentSequence, result, token) {
948
+ endpoint2.postMessage(
949
+ {
950
+ type: "sonare:vocal-result",
951
+ id,
952
+ sessionId,
953
+ clientIntentSequence,
954
+ result,
955
+ ...token ? { token } : {}
956
+ },
957
+ transferables(result)
958
+ );
959
+ }
960
+ function postError(endpoint2, id, sessionId, clientIntentSequence, error, token) {
961
+ endpoint2.postMessage({
962
+ type: "sonare:vocal-error",
963
+ id,
964
+ sessionId,
965
+ clientIntentSequence,
966
+ error: errorPayload(error),
967
+ ...token ? { token } : {}
968
+ });
969
+ }
970
+ function rejectQueuedCommands(endpoint2, session, error) {
971
+ for (const queued of session.queue) {
972
+ postError(
973
+ endpoint2,
974
+ queued.message.id,
975
+ queued.message.sessionId,
976
+ queued.message.clientIntentSequence,
977
+ error,
978
+ currentToken(session)
979
+ );
980
+ }
981
+ session.queue = [];
982
+ }
983
+ function ensureSameEpoch(message, session) {
984
+ if (message.baseToken && message.baseToken.sessionEpoch !== currentToken(session).sessionEpoch) {
985
+ const error = new Error("The vocal edit request targets another session epoch");
986
+ error.name = "StaleResultError";
987
+ throw error;
988
+ }
989
+ }
990
+ function draftRequired(session) {
991
+ if (!session.draft) {
992
+ throw new Error("No vocal edit draft is open");
993
+ }
994
+ return session.draft;
995
+ }
996
+ function readTarget(session, draft) {
997
+ return draft === true ? draftRequired(session) : session.value;
998
+ }
999
+ async function runPreview(message, session, active, endpoint2) {
1000
+ const snapshot = session.draft ? session.draft.captureRenderSnapshot() : session.value.captureRenderSnapshot();
1001
+ let job;
1002
+ try {
1003
+ job = snapshot.beginRenderJob({ ...message.request, signal: void 0 });
1004
+ while (true) {
1005
+ if (isCancelled(active)) {
1006
+ job.abort();
1007
+ throw cancelledError();
1008
+ }
1009
+ const progress = job.next();
1010
+ endpoint2.postMessage({
1011
+ type: "sonare:vocal-progress",
1012
+ id: message.id,
1013
+ sessionId: message.sessionId,
1014
+ clientIntentSequence: message.clientIntentSequence,
1015
+ complete: progress.complete,
1016
+ token: currentToken(session)
1017
+ });
1018
+ if (progress.complete) {
1019
+ return job.finalize();
1020
+ }
1021
+ await nextMacrotask();
1022
+ }
1023
+ } finally {
1024
+ job?.dispose();
1025
+ snapshot.dispose();
1026
+ }
1027
+ }
1028
+ async function execute(command, session, endpoint2) {
1029
+ const message = command.message;
1030
+ const active = command.active;
1031
+ session.running = active;
1032
+ try {
1033
+ ensureSameEpoch(message, session);
1034
+ let result;
1035
+ if (message.type === "sonare:vocal-preview") {
1036
+ result = await runPreview(message, session, active, endpoint2);
1037
+ } else {
1038
+ const mutation = message.mutation;
1039
+ switch (mutation.kind) {
1040
+ case "beginEdit":
1041
+ if (session.draft) {
1042
+ throw new Error("A vocal edit draft is already open");
1043
+ }
1044
+ session.draft = session.value.beginEdit(mutation.expectedRevision);
1045
+ result = session.draft.token();
1046
+ break;
1047
+ case "apply":
1048
+ result = draftRequired(session).apply(mutation.request);
1049
+ break;
1050
+ case "commit":
1051
+ result = draftRequired(session).commit(mutation.expectedRevision);
1052
+ session.draft?.dispose();
1053
+ session.draft = void 0;
1054
+ break;
1055
+ case "cancel":
1056
+ draftRequired(session).cancel();
1057
+ session.draft?.dispose();
1058
+ session.draft = void 0;
1059
+ result = null;
1060
+ break;
1061
+ case "undo":
1062
+ if (session.draft) {
1063
+ throw new Error("Cancel or commit the vocal draft before undo");
1064
+ }
1065
+ result = session.value.undo(mutation.expectedRevision);
1066
+ break;
1067
+ case "redo":
1068
+ if (session.draft) {
1069
+ throw new Error("Cancel or commit the vocal draft before redo");
1070
+ }
1071
+ result = session.value.redo(mutation.expectedRevision);
1072
+ break;
1073
+ case "notes":
1074
+ result = session.draft ? session.draft.notes() : session.value.notes();
1075
+ break;
1076
+ case "analysis":
1077
+ result = session.value.analysis();
1078
+ break;
1079
+ case "capabilities":
1080
+ result = session.value.capabilities();
1081
+ break;
1082
+ case "exportState":
1083
+ if (session.draft) {
1084
+ throw new Error("Commit or cancel the vocal draft before export");
1085
+ }
1086
+ result = { data: session.value.exportState() };
1087
+ break;
1088
+ case "outputLength":
1089
+ result = session.value.outputLengthSamples();
1090
+ break;
1091
+ case "history":
1092
+ result = session.value.history();
1093
+ break;
1094
+ case "draftToken":
1095
+ result = draftRequired(session).token();
1096
+ break;
1097
+ case "evaluatePitch":
1098
+ result = readTarget(session, mutation.draft).evaluatePitch(mutation.noteId);
1099
+ break;
1100
+ case "mapCoordinate": {
1101
+ const target = readTarget(session, mutation.draft);
1102
+ result = mutation.inverse ? target.destinationSampleToSourceSample(mutation.noteId, mutation.sample) : target.sourceSampleToDestinationSample(mutation.noteId, mutation.sample);
1103
+ break;
1104
+ }
1105
+ }
1106
+ }
1107
+ if (message.type === "sonare:vocal-preview" && isCancelled(active)) {
1108
+ throw cancelledError();
1109
+ }
1110
+ postResult(
1111
+ endpoint2,
1112
+ message.id,
1113
+ message.sessionId,
1114
+ message.clientIntentSequence,
1115
+ result,
1116
+ resultToken(result, session)
1117
+ );
1118
+ } catch (error) {
1119
+ postError(
1120
+ endpoint2,
1121
+ message.id,
1122
+ message.sessionId,
1123
+ message.clientIntentSequence,
1124
+ error,
1125
+ currentToken(session)
1126
+ );
1127
+ } finally {
1128
+ session.running = void 0;
1129
+ }
1130
+ }
1131
+ async function drain(session, endpoint2) {
1132
+ if (session.draining) {
1133
+ return;
1134
+ }
1135
+ session.draining = true;
1136
+ try {
1137
+ while (session.queue.length !== 0 && !session.disposing) {
1138
+ const command = session.queue.shift();
1139
+ await execute(command, session, endpoint2);
1140
+ }
1141
+ } finally {
1142
+ session.draining = false;
1143
+ }
1144
+ }
1145
+ function queueCommand(endpoint2, sessions, message) {
1146
+ const session = sessions.get(message.sessionId);
1147
+ if (!session || session.disposing) {
1148
+ postError(
1149
+ endpoint2,
1150
+ message.id,
1151
+ message.sessionId,
1152
+ message.clientIntentSequence,
1153
+ new Error("Unknown vocal session")
1154
+ );
1155
+ return;
1156
+ }
1157
+ if (compareUint64(message.clientIntentSequence, session.latestIntent) <= 0) {
1158
+ const error = new Error("A stale vocal client intent was rejected");
1159
+ error.name = "StaleResultError";
1160
+ postError(
1161
+ endpoint2,
1162
+ message.id,
1163
+ message.sessionId,
1164
+ message.clientIntentSequence,
1165
+ error,
1166
+ currentToken(session)
1167
+ );
1168
+ return;
1169
+ }
1170
+ session.latestIntent = message.clientIntentSequence;
1171
+ if (message.type === "sonare:vocal-preview") {
1172
+ const stale = session.queue.filter(
1173
+ (command) => command.message.type === "sonare:vocal-preview"
1174
+ );
1175
+ session.queue = session.queue.filter(
1176
+ (command) => command.message.type !== "sonare:vocal-preview"
1177
+ );
1178
+ for (const command of stale) {
1179
+ postError(
1180
+ endpoint2,
1181
+ command.message.id,
1182
+ command.message.sessionId,
1183
+ command.message.clientIntentSequence,
1184
+ Object.assign(new Error("Preview superseded"), { name: "StaleResultError" }),
1185
+ currentToken(session)
1186
+ );
1187
+ }
1188
+ }
1189
+ session.queue.push({
1190
+ message,
1191
+ active: {
1192
+ id: message.id,
1193
+ cancelled: false,
1194
+ preview: message.type === "sonare:vocal-preview",
1195
+ cancelBuffer: message.cancelBuffer
1196
+ }
1197
+ });
1198
+ void drain(session, endpoint2);
1199
+ }
1200
+ var UINT64_MAX = "18446744073709551615";
1201
+ var mutationKinds = /* @__PURE__ */ new Set([
1202
+ "beginEdit",
1203
+ "apply",
1204
+ "commit",
1205
+ "cancel",
1206
+ "undo",
1207
+ "redo",
1208
+ "notes",
1209
+ "analysis",
1210
+ "capabilities",
1211
+ "exportState",
1212
+ "outputLength",
1213
+ "history",
1214
+ "draftToken",
1215
+ "evaluatePitch",
1216
+ "mapCoordinate"
1217
+ ]);
1218
+ function malformed(message) {
1219
+ return new TypeError(`malformed vocal Worker message: ${message}`);
1220
+ }
1221
+ function isRecord(value) {
1222
+ return value !== null && typeof value === "object";
1223
+ }
1224
+ function isDecimalUint64(value) {
1225
+ return typeof value === "string" && /^(0|[1-9]\d*)$/.test(value) && compareUint64(value, UINT64_MAX) <= 0;
1226
+ }
1227
+ function optionalDecimal(value, field) {
1228
+ if (value !== void 0 && !isDecimalUint64(value)) {
1229
+ throw malformed(`${field} must be a decimal uint64 string`);
1230
+ }
1231
+ }
1232
+ function replyId(data) {
1233
+ if (!isRecord(data) || data.type === "sonare:vocal-cancel") {
1234
+ return void 0;
1235
+ }
1236
+ return Number.isSafeInteger(data.id) && data.id > 0 ? data.id : void 0;
1237
+ }
1238
+ function validateRequest(data) {
1239
+ if (!isRecord(data)) {
1240
+ throw malformed("message must be an object");
1241
+ }
1242
+ if (!Number.isSafeInteger(data.id) || data.id <= 0) {
1243
+ throw malformed("id must be a positive safe integer");
1244
+ }
1245
+ if (typeof data.sessionId !== "string" || data.sessionId.length === 0) {
1246
+ throw malformed("sessionId must be a non-empty string");
1247
+ }
1248
+ if (!isDecimalUint64(data.clientIntentSequence)) {
1249
+ throw malformed("clientIntentSequence must be a decimal uint64 string");
1250
+ }
1251
+ if (data.cancelBuffer !== void 0 && !(typeof SharedArrayBuffer !== "undefined" && data.cancelBuffer instanceof SharedArrayBuffer && data.cancelBuffer.byteLength >= Int32Array.BYTES_PER_ELEMENT)) {
1252
+ throw malformed("cancelBuffer must be a SharedArrayBuffer");
1253
+ }
1254
+ if (data.baseToken !== void 0 && !(isRecord(data.baseToken) && isDecimalUint64(data.baseToken.sessionEpoch))) {
1255
+ throw malformed("baseToken must be a state token");
1256
+ }
1257
+ switch (data.type) {
1258
+ case "sonare:vocal-create": {
1259
+ const request = data.request;
1260
+ if (!isRecord(request) || request.kind !== "create" && request.kind !== "restore" || !isRecord(request.request)) {
1261
+ throw malformed("create request must name create or restore with a request object");
1262
+ }
1263
+ break;
1264
+ }
1265
+ case "sonare:vocal-mutate": {
1266
+ const mutation = data.mutation;
1267
+ if (!isRecord(mutation) || typeof mutation.kind !== "string" || !mutationKinds.has(mutation.kind)) {
1268
+ throw malformed("mutation.kind is not a vocal Worker mutation");
1269
+ }
1270
+ if (mutation.kind === "apply" && !isRecord(mutation.request)) {
1271
+ throw malformed("apply mutation must carry a request object");
1272
+ }
1273
+ if (mutation.kind === "commit" && !isDecimalUint64(mutation.expectedRevision)) {
1274
+ throw malformed("commit expectedRevision must be a decimal uint64 string");
1275
+ }
1276
+ optionalDecimal(mutation.expectedRevision, "expectedRevision");
1277
+ break;
1278
+ }
1279
+ case "sonare:vocal-preview":
1280
+ if (!isRecord(data.request)) {
1281
+ throw malformed("preview request must be an object");
1282
+ }
1283
+ break;
1284
+ case "sonare:vocal-dispose":
1285
+ case "sonare:vocal-cancel":
1286
+ break;
1287
+ default:
1288
+ throw malformed("unknown message type");
1289
+ }
1290
+ return data;
1291
+ }
1292
+ function installVocalEditWorkerEndpoint(endpoint2, dependencies = {}) {
1293
+ const sessions = /* @__PURE__ */ new Map();
1294
+ const creating = /* @__PURE__ */ new Map();
1295
+ const factory = dependencies.factory ?? productionFactory;
1296
+ const initialize = dependencies.initialize ?? init;
1297
+ const cancel = (message) => {
1298
+ const pendingCreate = creating.get(message.id);
1299
+ if (pendingCreate) {
1300
+ pendingCreate.cancelled = true;
1301
+ return;
1302
+ }
1303
+ const session = sessions.get(message.sessionId);
1304
+ if (!session) {
1305
+ return;
1306
+ }
1307
+ const running = session.running;
1308
+ if (running?.id === message.id && running.preview) {
1309
+ running.cancelled = true;
1310
+ return;
1311
+ }
1312
+ const queuedIndex = session.queue.findIndex(
1313
+ (command) => command.message.id === message.id && command.message.type === "sonare:vocal-preview"
1314
+ );
1315
+ if (queuedIndex >= 0) {
1316
+ const [queued] = session.queue.splice(queuedIndex, 1);
1317
+ postError(
1318
+ endpoint2,
1319
+ queued.message.id,
1320
+ queued.message.sessionId,
1321
+ queued.message.clientIntentSequence,
1322
+ cancelledError(),
1323
+ currentToken(session)
1324
+ );
1325
+ }
1326
+ };
1327
+ const create = async (message) => {
1328
+ const active = {
1329
+ id: message.id,
1330
+ cancelled: false,
1331
+ preview: false,
1332
+ cancelBuffer: message.cancelBuffer
1333
+ };
1334
+ creating.set(message.id, active);
1335
+ let value;
1336
+ try {
1337
+ await initialize();
1338
+ if (isCancelled(active)) {
1339
+ throw cancelledError();
1340
+ }
1341
+ if (sessions.has(message.sessionId)) {
1342
+ throw new Error("Vocal session id is already in use");
1343
+ }
1344
+ value = message.request.kind === "create" ? factory.create(message.request.request) : factory.restore(message.request.request);
1345
+ if (isCancelled(active)) {
1346
+ throw cancelledError();
1347
+ }
1348
+ const result = {
1349
+ sessionId: message.sessionId,
1350
+ token: value.token(),
1351
+ notes: value.notes(),
1352
+ analysis: value.analysis(),
1353
+ capabilities: value.capabilities()
1354
+ };
1355
+ sessions.set(message.sessionId, {
1356
+ value,
1357
+ latestIntent: message.clientIntentSequence,
1358
+ queue: [],
1359
+ running: void 0,
1360
+ draining: false,
1361
+ disposing: false
1362
+ });
1363
+ value = void 0;
1364
+ postResult(
1365
+ endpoint2,
1366
+ message.id,
1367
+ message.sessionId,
1368
+ message.clientIntentSequence,
1369
+ result,
1370
+ result.token
1371
+ );
1372
+ } catch (error) {
1373
+ value?.dispose();
1374
+ postError(endpoint2, message.id, message.sessionId, message.clientIntentSequence, error);
1375
+ } finally {
1376
+ creating.delete(message.id);
1377
+ }
1378
+ };
1379
+ const dispose = (message) => {
1380
+ const session = sessions.get(message.sessionId);
1381
+ if (!session) {
1382
+ postResult(endpoint2, message.id, message.sessionId, message.clientIntentSequence, null);
1383
+ return;
1384
+ }
1385
+ if (session.disposing) {
1386
+ void session.released?.then(
1387
+ () => postResult(endpoint2, message.id, message.sessionId, message.clientIntentSequence, null)
1388
+ );
1389
+ return;
1390
+ }
1391
+ session.disposing = true;
1392
+ const active = session.running;
1393
+ if (active) {
1394
+ active.cancelled = true;
1395
+ }
1396
+ rejectQueuedCommands(endpoint2, session, new Error("Vocal session disposed"));
1397
+ session.released = (async () => {
1398
+ while (session.draining) {
1399
+ await nextMacrotask();
1400
+ }
1401
+ session.draft?.dispose();
1402
+ session.value.dispose();
1403
+ sessions.delete(message.sessionId);
1404
+ })();
1405
+ void session.released.then(
1406
+ () => postResult(endpoint2, message.id, message.sessionId, message.clientIntentSequence, null)
1407
+ );
1408
+ };
1409
+ endpoint2.addEventListener("message", (event) => {
1410
+ const data = event.data;
1411
+ try {
1412
+ const message = validateRequest(data);
1413
+ switch (message.type) {
1414
+ case "sonare:vocal-cancel":
1415
+ cancel(message);
1416
+ return;
1417
+ case "sonare:vocal-create":
1418
+ void create(message);
1419
+ return;
1420
+ case "sonare:vocal-dispose":
1421
+ dispose(message);
1422
+ return;
1423
+ default:
1424
+ queueCommand(endpoint2, sessions, message);
1425
+ }
1426
+ } catch (error) {
1427
+ const id = replyId(data);
1428
+ if (id === void 0) {
1429
+ return;
1430
+ }
1431
+ const record = data;
1432
+ try {
1433
+ postError(
1434
+ endpoint2,
1435
+ id,
1436
+ typeof record.sessionId === "string" ? record.sessionId : "",
1437
+ isDecimalUint64(record.clientIntentSequence) ? record.clientIntentSequence : "0",
1438
+ error
1439
+ );
1440
+ } catch {
1441
+ }
1442
+ }
1443
+ });
1444
+ }
1445
+ function browserEndpoint() {
1446
+ const scope = globalThis;
1447
+ if (scope.document !== void 0 || !scope.postMessage || !scope.addEventListener) {
1448
+ return null;
1449
+ }
1450
+ return {
1451
+ postMessage: (message, transfer) => scope.postMessage?.(message, transfer),
1452
+ addEventListener: (type, listener) => scope.addEventListener?.(type, listener)
1453
+ };
1454
+ }
1455
+ var endpoint = browserEndpoint();
1456
+ if (endpoint) {
1457
+ installVocalEditWorkerEndpoint(endpoint);
1458
+ }
1459
+ export {
1460
+ installVocalEditWorkerEndpoint
1461
+ };
1462
+ //# sourceMappingURL=vocal_edit_worker.js.map