@libraz/libsonare 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2078 @@
1
+ // src/index.ts
2
+ var EXPECTED_ENGINE_ABI_VERSION = 2;
3
+ function automationCurveCode(curve) {
4
+ switch (curve) {
5
+ case "linear":
6
+ return 0;
7
+ case "exponential":
8
+ return 1;
9
+ case "hold":
10
+ return 2;
11
+ case "s-curve":
12
+ return 3;
13
+ default:
14
+ throw new Error(`Invalid automation curve: ${curve}`);
15
+ }
16
+ }
17
+ function panLawCode(panLaw) {
18
+ if (typeof panLaw === "number") {
19
+ return panLaw;
20
+ }
21
+ switch (panLaw) {
22
+ case "const4.5dB":
23
+ return 1;
24
+ case "const6dB":
25
+ return 2;
26
+ case "linear0dB":
27
+ return 3;
28
+ default:
29
+ return 0;
30
+ }
31
+ }
32
+ function meterTapCode(tap) {
33
+ return tap === "preFader" || tap === 0 ? 0 : 1;
34
+ }
35
+ function sendTimingCode(timing) {
36
+ return timing === "preFader" || timing === 0 ? 0 : 1;
37
+ }
38
+ var module = null;
39
+ var initPromise = null;
40
+ async function init(options) {
41
+ if (module) {
42
+ return;
43
+ }
44
+ if (initPromise) {
45
+ return initPromise;
46
+ }
47
+ initPromise = (async () => {
48
+ try {
49
+ const createModule = (await import("./sonare.js")).default;
50
+ module = await createModule(options);
51
+ } catch (error) {
52
+ initPromise = null;
53
+ throw error;
54
+ }
55
+ })();
56
+ return initPromise;
57
+ }
58
+ function isInitialized() {
59
+ return module !== null;
60
+ }
61
+ function engineAbiVersion() {
62
+ if (!module) {
63
+ throw new Error("Module not initialized. Call init() first.");
64
+ }
65
+ return module.engineAbiVersion();
66
+ }
67
+ function engineCapabilities() {
68
+ const abiVersion = engineAbiVersion();
69
+ const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === "function";
70
+ const atomics = typeof globalThis.Atomics === "object";
71
+ const audioWorklet = typeof AudioWorkletNode !== "undefined" || typeof globalThis.AudioWorkletProcessor !== "undefined";
72
+ return {
73
+ engineAbiVersion: abiVersion,
74
+ expectedEngineAbiVersion: EXPECTED_ENGINE_ABI_VERSION,
75
+ abiCompatible: abiVersion === EXPECTED_ENGINE_ABI_VERSION,
76
+ sharedArrayBuffer,
77
+ atomics,
78
+ audioWorklet,
79
+ mode: sharedArrayBuffer && atomics ? "sab" : "postMessage"
80
+ };
81
+ }
82
+ var RealtimeEngine = class {
83
+ constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
84
+ if (!module) {
85
+ throw new Error("Module not initialized. Call init() first.");
86
+ }
87
+ const capabilities = engineCapabilities();
88
+ if (!capabilities.abiCompatible) {
89
+ throw new Error(
90
+ `Engine ABI mismatch: wasm=${capabilities.engineAbiVersion}, expected=${capabilities.expectedEngineAbiVersion}`
91
+ );
92
+ }
93
+ this.native = new module.RealtimeEngine(
94
+ sampleRate,
95
+ maxBlockSize,
96
+ commandCapacity,
97
+ telemetryCapacity
98
+ );
99
+ }
100
+ prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024) {
101
+ this.native.prepare(sampleRate, maxBlockSize, commandCapacity, telemetryCapacity);
102
+ }
103
+ /** Queue a sample-accurate parameter change (engine kSetParam). */
104
+ setParameter(paramId, value, renderFrame = -1) {
105
+ this.native.setParameter(paramId, value, renderFrame);
106
+ }
107
+ /** Queue a smoothed parameter change (engine kSetParamSmoothed). */
108
+ setParameterSmoothed(paramId, value, renderFrame = -1) {
109
+ this.native.setParameterSmoothed(paramId, value, renderFrame);
110
+ }
111
+ /** Read back the current transport state snapshot. */
112
+ getTransportState() {
113
+ return this.native.getTransportState();
114
+ }
115
+ play(renderFrame = -1) {
116
+ this.native.play(renderFrame);
117
+ }
118
+ stop(renderFrame = -1) {
119
+ this.native.stop(renderFrame);
120
+ }
121
+ seekSample(timelineSample, renderFrame = -1) {
122
+ this.native.seekSample(timelineSample, renderFrame);
123
+ }
124
+ seekPpq(ppq, renderFrame = -1) {
125
+ this.native.seekPpq(ppq, renderFrame);
126
+ }
127
+ setTempo(bpm) {
128
+ this.native.setTempo(bpm);
129
+ }
130
+ setTimeSignature(numerator, denominator) {
131
+ this.native.setTimeSignature(numerator, denominator);
132
+ }
133
+ setLoop(startPpq, endPpq, enabled = true) {
134
+ this.native.setLoop(startPpq, endPpq, enabled);
135
+ }
136
+ addParameter(info) {
137
+ this.native.addParameter(info);
138
+ }
139
+ parameterCount() {
140
+ return this.native.parameterCount();
141
+ }
142
+ parameterInfoByIndex(index) {
143
+ return this.native.parameterInfoByIndex(index);
144
+ }
145
+ parameterInfo(id) {
146
+ return this.native.parameterInfo(id);
147
+ }
148
+ setAutomationLane(paramId, points) {
149
+ this.native.setAutomationLane(paramId, points);
150
+ }
151
+ automationLaneCount() {
152
+ return this.native.automationLaneCount();
153
+ }
154
+ setMarkers(markers) {
155
+ this.native.setMarkers(markers);
156
+ }
157
+ markerCount() {
158
+ return this.native.markerCount();
159
+ }
160
+ markerByIndex(index) {
161
+ return this.native.markerByIndex(index);
162
+ }
163
+ marker(id) {
164
+ return this.native.marker(id);
165
+ }
166
+ seekMarker(markerId, renderFrame = -1) {
167
+ this.native.seekMarker(markerId, renderFrame);
168
+ }
169
+ setLoopFromMarkers(startMarkerId, endMarkerId) {
170
+ this.native.setLoopFromMarkers(startMarkerId, endMarkerId);
171
+ }
172
+ setMetronome(config) {
173
+ this.native.setMetronome(config);
174
+ }
175
+ metronome() {
176
+ return this.native.metronome();
177
+ }
178
+ countInEndSample(startSample, bars) {
179
+ return Number(this.native.countInEndSample(startSample, bars));
180
+ }
181
+ setGraph(spec) {
182
+ this.native.setGraph(spec);
183
+ }
184
+ graphNodeCount() {
185
+ return this.native.graphNodeCount();
186
+ }
187
+ graphConnectionCount() {
188
+ return this.native.graphConnectionCount();
189
+ }
190
+ setClips(clips) {
191
+ this.native.setClips(clips);
192
+ }
193
+ clipCount() {
194
+ return this.native.clipCount();
195
+ }
196
+ setCaptureBuffer(numChannels, capacityFrames) {
197
+ this.native.setCaptureBuffer(numChannels, capacityFrames);
198
+ }
199
+ armCapture(armed = true) {
200
+ this.native.armCapture(armed);
201
+ }
202
+ setCapturePunch(startSample, endSample, enabled = true) {
203
+ this.native.setCapturePunch(startSample, endSample, enabled);
204
+ }
205
+ resetCapture() {
206
+ this.native.resetCapture();
207
+ }
208
+ captureStatus() {
209
+ return this.native.captureStatus();
210
+ }
211
+ capturedAudio() {
212
+ return this.native.capturedAudio();
213
+ }
214
+ process(channels) {
215
+ return this.native.process(channels);
216
+ }
217
+ processWithMonitor(channels) {
218
+ return this.native.processWithMonitor(channels);
219
+ }
220
+ renderOffline(channels, blockSize = 128) {
221
+ return this.native.renderOffline(channels, blockSize);
222
+ }
223
+ bounceOffline(options) {
224
+ return this.native.bounceOffline(options);
225
+ }
226
+ freezeOffline(options) {
227
+ return this.native.freezeOffline(options);
228
+ }
229
+ drainTelemetry(maxRecords = 1024) {
230
+ return this.native.drainTelemetry(maxRecords);
231
+ }
232
+ drainMeterTelemetry(maxRecords = 1024) {
233
+ return this.native.drainMeterTelemetry(maxRecords);
234
+ }
235
+ destroy() {
236
+ this.native.delete();
237
+ }
238
+ };
239
+ var Mixer = class _Mixer {
240
+ constructor(mixer) {
241
+ this.mixer = mixer;
242
+ }
243
+ /**
244
+ * Build a mixer from a scene JSON string.
245
+ *
246
+ * @param json - Scene JSON (strips, buses, sends, connections, inserts)
247
+ * @param sampleRate - Sample rate in Hz (default: 48000)
248
+ * @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
249
+ */
250
+ static fromSceneJson(json, sampleRate = 48e3, blockSize = 512) {
251
+ if (!module) {
252
+ throw new Error("Module not initialized. Call init() first.");
253
+ }
254
+ return new _Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize));
255
+ }
256
+ /** Rebuild and compile the routing graph from the current scene topology. */
257
+ compile() {
258
+ this.mixer.compile();
259
+ }
260
+ /**
261
+ * Mix one block of per-strip stereo audio into the stereo master.
262
+ *
263
+ * @param leftChannels - `leftChannels[i]` is the left channel of strip `i`
264
+ * @param rightChannels - `rightChannels[i]` is the right channel of strip `i`
265
+ * @returns Mixed stereo master (`left`, `right`, `sampleRate`)
266
+ */
267
+ processStereo(leftChannels, rightChannels) {
268
+ if (leftChannels.length !== rightChannels.length) {
269
+ throw new Error("leftChannels and rightChannels must have the same length.");
270
+ }
271
+ return this.mixer.processStereo(leftChannels, rightChannels);
272
+ }
273
+ /**
274
+ * Mix one block into caller-owned output arrays.
275
+ *
276
+ * This avoids allocating the result object and result `Float32Array`s. It is
277
+ * intended for realtime bridges such as AudioWorklet; the input channel count
278
+ * must match the scene strip count and all arrays must have the same length.
279
+ */
280
+ processStereoInto(leftChannels, rightChannels, outLeft, outRight) {
281
+ if (leftChannels.length !== rightChannels.length) {
282
+ throw new Error("leftChannels and rightChannels must have the same length.");
283
+ }
284
+ if (outLeft.length !== outRight.length) {
285
+ throw new Error("outLeft and outRight must have the same length.");
286
+ }
287
+ this.mixer.processStereoInto(leftChannels, rightChannels, outLeft, outRight);
288
+ }
289
+ /**
290
+ * Create reusable WASM-heap input/output views for realtime-style processing.
291
+ *
292
+ * Fill `leftInputs[i]` / `rightInputs[i]`, call `process()`, then read
293
+ * `outLeft` / `outRight`. The views are owned by this mixer and become invalid
294
+ * after {@link delete}.
295
+ */
296
+ createRealtimeBuffer() {
297
+ const stripCount = this.stripCount();
298
+ const leftInputs = [];
299
+ const rightInputs = [];
300
+ for (let index = 0; index < stripCount; index++) {
301
+ leftInputs.push(this.mixer.inputLeftView(index));
302
+ rightInputs.push(this.mixer.inputRightView(index));
303
+ }
304
+ const outLeft = this.mixer.outputLeftView();
305
+ const outRight = this.mixer.outputRightView();
306
+ return {
307
+ leftInputs,
308
+ rightInputs,
309
+ outLeft,
310
+ outRight,
311
+ process: (numSamples = outLeft.length) => this.mixer.processPreparedStereo(numSamples)
312
+ };
313
+ }
314
+ /** Number of strips in the mixer (e.g. strips loaded from the scene). */
315
+ stripCount() {
316
+ return this.mixer.stripCount();
317
+ }
318
+ /**
319
+ * Schedule sample-accurate insert-parameter automation on a strip's insert.
320
+ *
321
+ * @param stripIndex - Strip index in `[0, stripCount())`
322
+ * @param insertIndex - Index into the strip's combined insert sequence
323
+ * (`[pre-inserts... post-inserts...]`)
324
+ * @param paramId - Processor-specific parameter id
325
+ * @param samplePos - Absolute samples from the start of processing (the mixer
326
+ * advances an internal position from 0 on the first {@link processStereo}
327
+ * call; recompiling resets it to 0)
328
+ * @param value - Target parameter value
329
+ * @param curve - Interpolation curve (default: `'linear'`)
330
+ * @throws If the strip index is out of range or the schedule call fails
331
+ * (unknown curve, out-of-range insert index, or full event lane)
332
+ */
333
+ scheduleInsertAutomation(stripIndex, insertIndex, paramId, samplePos, value, curve = "linear") {
334
+ this.mixer.scheduleInsertAutomation(
335
+ stripIndex,
336
+ insertIndex,
337
+ paramId,
338
+ samplePos,
339
+ value,
340
+ automationCurveCode(curve)
341
+ );
342
+ }
343
+ /**
344
+ * Resolve a strip's index in `[0, stripCount())` from its scene id, or `null`
345
+ * when no strip with that id exists (matches the Node binding's `number | null`).
346
+ */
347
+ stripById(id) {
348
+ const index = this.mixer.stripById(id);
349
+ return index < 0 ? null : index;
350
+ }
351
+ /**
352
+ * Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
353
+ * `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
354
+ * {@link compile} (or {@link processStereo}) to rebuild.
355
+ */
356
+ addBus(id, role = "aux") {
357
+ this.mixer.addBus(id, role);
358
+ }
359
+ /** Remove a bus by id. Marks the routing graph dirty. */
360
+ removeBus(id) {
361
+ this.mixer.removeBus(id);
362
+ }
363
+ /** Number of buses in the mixer topology. */
364
+ busCount() {
365
+ return this.mixer.busCount();
366
+ }
367
+ /**
368
+ * Add a VCA group with the given gain offset (dB). `members` is a list of
369
+ * strip ids governed by the group (may be empty).
370
+ */
371
+ addVcaGroup(id, gainDb = 0, members = []) {
372
+ this.mixer.addVcaGroup(id, gainDb, members);
373
+ }
374
+ /** Remove a VCA group by id. */
375
+ removeVcaGroup(id) {
376
+ this.mixer.removeVcaGroup(id);
377
+ }
378
+ /** Number of VCA groups in the mixer topology. */
379
+ vcaGroupCount() {
380
+ return this.mixer.vcaGroupCount();
381
+ }
382
+ /**
383
+ * Set a strip's solo state. Takes effect on the next process without a
384
+ * graph recompile.
385
+ */
386
+ setSoloed(stripIndex, soloed) {
387
+ this.mixer.setSoloed(stripIndex, soloed);
388
+ }
389
+ /**
390
+ * Mark a strip solo-safe so it is never implied-muted by another strip's
391
+ * solo. Takes effect on the next process without a graph recompile.
392
+ */
393
+ setSoloSafe(stripIndex, soloSafe) {
394
+ this.mixer.setSoloSafe(stripIndex, soloSafe);
395
+ }
396
+ /** Invert the polarity of the left and/or right channel of a strip. */
397
+ setPolarityInvert(stripIndex, invertLeft, invertRight) {
398
+ this.mixer.setPolarityInvert(stripIndex, invertLeft, invertRight);
399
+ }
400
+ /** Set the strip's pan law. */
401
+ setPanLaw(stripIndex, panLaw) {
402
+ this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
403
+ }
404
+ /**
405
+ * Set a per-strip channel delay in samples. This changes the strip's reported
406
+ * latency; recompile to re-run latency compensation.
407
+ */
408
+ setChannelDelaySamples(stripIndex, delaySamples) {
409
+ this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
410
+ }
411
+ /** Set the strip's live VCA gain offset in dB (not persisted to the scene). */
412
+ setVcaOffsetDb(stripIndex, offsetDb) {
413
+ this.mixer.setVcaOffsetDb(stripIndex, offsetDb);
414
+ }
415
+ /** Set independent left/right pan positions (dual-pan mode). */
416
+ setDualPan(stripIndex, leftPan, rightPan) {
417
+ this.mixer.setDualPan(stripIndex, leftPan, rightPan);
418
+ }
419
+ /**
420
+ * Add a send to a strip after construction.
421
+ *
422
+ * @param stripIndex - Strip index in `[0, stripCount())`
423
+ * @param id - Send id
424
+ * @param destinationBusId - Destination bus id
425
+ * @param sendDb - Initial send level in dB
426
+ * @param timing - `'preFader'` or `'postFader'` (default: `'postFader'`)
427
+ * @returns The new send's index
428
+ */
429
+ addSend(stripIndex, id, destinationBusId, sendDb, timing = "postFader") {
430
+ return this.mixer.addSend(stripIndex, id, destinationBusId, sendDb, sendTimingCode(timing));
431
+ }
432
+ /** Set the send level (in dB) for an existing send by index. */
433
+ setSendDb(stripIndex, sendIndex, sendDb) {
434
+ this.mixer.setSendDb(stripIndex, sendIndex, sendDb);
435
+ }
436
+ /**
437
+ * Read a strip's meter snapshot at the given tap point.
438
+ *
439
+ * @param stripIndex - Strip index in `[0, stripCount())`
440
+ * @param tap - `'preFader'` or `'postFader'` (default: `'postFader'`)
441
+ */
442
+ meterTap(stripIndex, tap = "postFader") {
443
+ return this.mixer.meterTap(stripIndex, meterTapCode(tap));
444
+ }
445
+ /**
446
+ * Read a strip's meter snapshot. Alias of {@link meterTap}, provided for
447
+ * cross-binding (Node/Python) parity.
448
+ *
449
+ * @param stripIndex - Strip index in `[0, stripCount())`
450
+ * @param tap - `'preFader'` or `'postFader'` (default: `'postFader'`)
451
+ */
452
+ stripMeter(stripIndex, tap = "postFader") {
453
+ return this.mixer.stripMeter(stripIndex, meterTapCode(tap));
454
+ }
455
+ /**
456
+ * Schedule sample-accurate fader automation on a strip.
457
+ *
458
+ * @param stripIndex - Strip index in `[0, stripCount())`
459
+ * @param samplePos - Absolute samples from the start of processing
460
+ * @param faderDb - Target fader level in dB
461
+ * @param curve - Interpolation curve (default: `'linear'`)
462
+ */
463
+ scheduleFaderAutomation(stripIndex, samplePos, faderDb, curve = "linear") {
464
+ this.mixer.scheduleFaderAutomation(stripIndex, samplePos, faderDb, automationCurveCode(curve));
465
+ }
466
+ /**
467
+ * Schedule sample-accurate pan automation on a strip.
468
+ *
469
+ * @param stripIndex - Strip index in `[0, stripCount())`
470
+ * @param samplePos - Absolute samples from the start of processing
471
+ * @param pan - Target pan position
472
+ * @param curve - Interpolation curve (default: `'linear'`)
473
+ */
474
+ schedulePanAutomation(stripIndex, samplePos, pan, curve = "linear") {
475
+ this.mixer.schedulePanAutomation(stripIndex, samplePos, pan, automationCurveCode(curve));
476
+ }
477
+ /**
478
+ * Schedule sample-accurate width automation on a strip.
479
+ *
480
+ * @param stripIndex - Strip index in `[0, stripCount())`
481
+ * @param samplePos - Absolute samples from the start of processing
482
+ * @param width - Target stereo width
483
+ * @param curve - Interpolation curve (default: `'linear'`)
484
+ */
485
+ scheduleWidthAutomation(stripIndex, samplePos, width, curve = "linear") {
486
+ this.mixer.scheduleWidthAutomation(stripIndex, samplePos, width, automationCurveCode(curve));
487
+ }
488
+ /**
489
+ * Schedule sample-accurate send-level automation on a strip's send.
490
+ *
491
+ * @param stripIndex - Strip index in `[0, stripCount())`
492
+ * @param sendIndex - Send index in the strip's add order
493
+ * @param samplePos - Absolute samples from the start of processing
494
+ * @param db - Target send level in dB
495
+ * @param curve - Interpolation curve (default: `'linear'`)
496
+ */
497
+ scheduleSendAutomation(stripIndex, sendIndex, samplePos, db, curve = "linear") {
498
+ this.mixer.scheduleSendAutomation(
499
+ stripIndex,
500
+ sendIndex,
501
+ samplePos,
502
+ db,
503
+ automationCurveCode(curve)
504
+ );
505
+ }
506
+ /**
507
+ * Read up to `maxPoints` of a strip's most recent goniometer samples
508
+ * (oldest to newest).
509
+ */
510
+ readGoniometerLatest(stripIndex, maxPoints) {
511
+ return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
512
+ }
513
+ /** Serialize the current scene (strips, buses, sends, connections) to JSON. */
514
+ toSceneJson() {
515
+ return this.mixer.toSceneJson();
516
+ }
517
+ /** Release the underlying WASM object. Safe to call only once. */
518
+ delete() {
519
+ this.mixer.delete();
520
+ }
521
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
522
+ destroy() {
523
+ this.delete();
524
+ }
525
+ };
526
+
527
+ // src/worklet.ts
528
+ var SONARE_METER_RING_HEADER_INTS = 4;
529
+ var SONARE_METER_RING_RECORD_FLOATS = 6;
530
+ var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
531
+ var SONARE_ENGINE_RING_HEADER_INTS = 5;
532
+ var SONARE_ENGINE_COMMAND_RECORD_BYTES = 32;
533
+ var SONARE_ENGINE_TELEMETRY_RECORD_BYTES = 48;
534
+ var SonareEngineCommandType = /* @__PURE__ */ ((SonareEngineCommandType2) => {
535
+ SonareEngineCommandType2[SonareEngineCommandType2["SetParam"] = 0] = "SetParam";
536
+ SonareEngineCommandType2[SonareEngineCommandType2["SetParamSmoothed"] = 1] = "SetParamSmoothed";
537
+ SonareEngineCommandType2[SonareEngineCommandType2["TransportPlay"] = 2] = "TransportPlay";
538
+ SonareEngineCommandType2[SonareEngineCommandType2["TransportStop"] = 3] = "TransportStop";
539
+ SonareEngineCommandType2[SonareEngineCommandType2["TransportSeekSample"] = 4] = "TransportSeekSample";
540
+ SonareEngineCommandType2[SonareEngineCommandType2["TransportSeekPpq"] = 5] = "TransportSeekPpq";
541
+ SonareEngineCommandType2[SonareEngineCommandType2["SetTempoMap"] = 6] = "SetTempoMap";
542
+ SonareEngineCommandType2[SonareEngineCommandType2["SetLoop"] = 7] = "SetLoop";
543
+ SonareEngineCommandType2[SonareEngineCommandType2["SwapGraph"] = 8] = "SwapGraph";
544
+ SonareEngineCommandType2[SonareEngineCommandType2["SwapAutomation"] = 9] = "SwapAutomation";
545
+ SonareEngineCommandType2[SonareEngineCommandType2["SetSoloMute"] = 10] = "SetSoloMute";
546
+ SonareEngineCommandType2[SonareEngineCommandType2["AddClip"] = 11] = "AddClip";
547
+ SonareEngineCommandType2[SonareEngineCommandType2["RemoveClip"] = 12] = "RemoveClip";
548
+ SonareEngineCommandType2[SonareEngineCommandType2["ArmRecord"] = 13] = "ArmRecord";
549
+ SonareEngineCommandType2[SonareEngineCommandType2["Punch"] = 14] = "Punch";
550
+ SonareEngineCommandType2[SonareEngineCommandType2["SetMetronome"] = 15] = "SetMetronome";
551
+ SonareEngineCommandType2[SonareEngineCommandType2["SetMarker"] = 16] = "SetMarker";
552
+ SonareEngineCommandType2[SonareEngineCommandType2["SeekMarker"] = 17] = "SeekMarker";
553
+ return SonareEngineCommandType2;
554
+ })(SonareEngineCommandType || {});
555
+ var SonareEngineTelemetryType = /* @__PURE__ */ ((SonareEngineTelemetryType2) => {
556
+ SonareEngineTelemetryType2[SonareEngineTelemetryType2["ProcessBlock"] = 0] = "ProcessBlock";
557
+ SonareEngineTelemetryType2[SonareEngineTelemetryType2["Error"] = 1] = "Error";
558
+ return SonareEngineTelemetryType2;
559
+ })(SonareEngineTelemetryType || {});
560
+ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2) => {
561
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["None"] = 0] = "None";
562
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["CommandQueueOverflow"] = 1] = "CommandQueueOverflow";
563
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["PendingCommandOverflow"] = 2] = "PendingCommandOverflow";
564
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["BoundaryOverflow"] = 3] = "BoundaryOverflow";
565
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["TelemetryOverflow"] = 4] = "TelemetryOverflow";
566
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["CaptureOverflow"] = 5] = "CaptureOverflow";
567
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["MaxBlockExceeded"] = 6] = "MaxBlockExceeded";
568
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["UnknownTarget"] = 7] = "UnknownTarget";
569
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["NonRealtimeSafeParameter"] = 8] = "NonRealtimeSafeParameter";
570
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["NotPrepared"] = 9] = "NotPrepared";
571
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["NonQueueableCommand"] = 10] = "NonQueueableCommand";
572
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["AutomationBindTargetOverflow"] = 11] = "AutomationBindTargetOverflow";
573
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["StaleAutomationLanes"] = 12] = "StaleAutomationLanes";
574
+ SonareEngineTelemetryError2[SonareEngineTelemetryError2["SmoothedParameterCapacity"] = 13] = "SmoothedParameterCapacity";
575
+ return SonareEngineTelemetryError2;
576
+ })(SonareEngineTelemetryError || {});
577
+ function toDb(value) {
578
+ return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
579
+ }
580
+ function isRecord(value) {
581
+ return typeof value === "object" && value !== null;
582
+ }
583
+ function isWorkletMessage(value) {
584
+ if (!isRecord(value) || typeof value.type !== "string") {
585
+ return false;
586
+ }
587
+ return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
588
+ }
589
+ function isEngineCommandRecord(value) {
590
+ return isRecord(value) && typeof value.type === "number";
591
+ }
592
+ function isEngineTelemetryRecord(value) {
593
+ return isRecord(value) && typeof value.type === "number" && typeof value.error === "number" && typeof value.renderFrame === "number" && typeof value.timelineSample === "number" && typeof value.audibleTimelineSample === "number" && typeof value.graphLatencySamplesQ8 === "number" && typeof value.value === "number";
594
+ }
595
+ function isMeterSnapshot(value) {
596
+ return isRecord(value) && value.type === "meter" && typeof value.frame === "number" && typeof value.peakDbL === "number" && typeof value.peakDbR === "number" && typeof value.rmsDbL === "number" && typeof value.rmsDbR === "number" && typeof value.correlation === "number";
597
+ }
598
+ function sonareMeterRingBufferByteLength(capacity) {
599
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
600
+ return SONARE_METER_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_METER_RING_RECORD_FLOATS * Float32Array.BYTES_PER_ELEMENT;
601
+ }
602
+ function createSonareMeterRingBuffer(capacity = 128) {
603
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
604
+ const sharedBuffer = new SharedArrayBuffer(sonareMeterRingBufferByteLength(clampedCapacity));
605
+ const ring = meterRingFromSharedBuffer(sharedBuffer, clampedCapacity);
606
+ Atomics.store(ring.header, 0, 0);
607
+ Atomics.store(ring.header, 1, clampedCapacity);
608
+ Atomics.store(ring.header, 2, SONARE_METER_RING_RECORD_FLOATS);
609
+ Atomics.store(ring.header, 3, 0);
610
+ return { sharedBuffer, header: ring.header, records: ring.records, capacity: ring.capacity };
611
+ }
612
+ function readSonareMeterRingBuffer(ring, readIndex = 0) {
613
+ const writeIndex = Atomics.load(ring.header, 0);
614
+ const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
615
+ const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
616
+ const meters = [];
617
+ for (let index = firstReadable; index < writeIndex; index++) {
618
+ const offset = index % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
619
+ meters.push({
620
+ type: "meter",
621
+ frame: ring.records[offset],
622
+ peakDbL: ring.records[offset + 1],
623
+ peakDbR: ring.records[offset + 2],
624
+ rmsDbL: ring.records[offset + 3],
625
+ rmsDbR: ring.records[offset + 4],
626
+ correlation: ring.records[offset + 5]
627
+ });
628
+ }
629
+ return { nextReadIndex: writeIndex, meters };
630
+ }
631
+ function sonareSpectrumRingBufferByteLength(capacity, bands = 16) {
632
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
633
+ const clampedBands = Math.max(1, Math.floor(bands));
634
+ return SONARE_SPECTRUM_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * (2 + clampedBands) * Float32Array.BYTES_PER_ELEMENT;
635
+ }
636
+ function createSonareSpectrumRingBuffer(capacity = 128, bands = 16) {
637
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
638
+ const clampedBands = Math.max(1, Math.floor(bands));
639
+ const sharedBuffer = new SharedArrayBuffer(
640
+ sonareSpectrumRingBufferByteLength(clampedCapacity, clampedBands)
641
+ );
642
+ const ring = spectrumRingFromSharedBuffer(sharedBuffer, clampedCapacity, clampedBands);
643
+ Atomics.store(ring.header, 0, 0);
644
+ Atomics.store(ring.header, 1, clampedCapacity);
645
+ Atomics.store(ring.header, 2, ring.recordFloats);
646
+ Atomics.store(ring.header, 3, clampedBands);
647
+ Atomics.store(ring.header, 4, 0);
648
+ return {
649
+ sharedBuffer,
650
+ header: ring.header,
651
+ records: ring.records,
652
+ capacity: ring.capacity,
653
+ bands: ring.bands
654
+ };
655
+ }
656
+ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
657
+ const writeIndex = Atomics.load(ring.header, 0);
658
+ const recordFloats = Atomics.load(ring.header, 2) || 2 + ring.bands;
659
+ const bands = Atomics.load(ring.header, 3) || ring.bands;
660
+ const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
661
+ const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
662
+ const spectra = [];
663
+ for (let index = firstReadable; index < writeIndex; index++) {
664
+ const offset = index % ring.capacity * recordFloats;
665
+ const values = new Float32Array(bands);
666
+ values.set(ring.records.subarray(offset + 2, offset + 2 + bands));
667
+ spectra.push({ type: "spectrum", frame: ring.records[offset], bands: values });
668
+ }
669
+ return { nextReadIndex: writeIndex, spectra };
670
+ }
671
+ function sonareEngineCommandRingBufferByteLength(capacity) {
672
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
673
+ return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_COMMAND_RECORD_BYTES;
674
+ }
675
+ function sonareEngineTelemetryRingBufferByteLength(capacity) {
676
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
677
+ return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_TELEMETRY_RECORD_BYTES;
678
+ }
679
+ function createSonareEngineCommandRingBuffer(capacity = 128) {
680
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
681
+ const sharedBuffer = new SharedArrayBuffer(
682
+ sonareEngineCommandRingBufferByteLength(clampedCapacity)
683
+ );
684
+ const ring = engineRingFromSharedBuffer(
685
+ sharedBuffer,
686
+ SONARE_ENGINE_COMMAND_RECORD_BYTES,
687
+ clampedCapacity
688
+ );
689
+ return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
690
+ }
691
+ function createSonareEngineTelemetryRingBuffer(capacity = 128) {
692
+ const clampedCapacity = Math.max(1, Math.floor(capacity));
693
+ const sharedBuffer = new SharedArrayBuffer(
694
+ sonareEngineTelemetryRingBufferByteLength(clampedCapacity)
695
+ );
696
+ const ring = engineRingFromSharedBuffer(
697
+ sharedBuffer,
698
+ SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
699
+ clampedCapacity
700
+ );
701
+ return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
702
+ }
703
+ function pushSonareEngineCommandRingBuffer(ring, command) {
704
+ const writeIndex = Atomics.load(ring.header, 0);
705
+ const readIndex = Atomics.load(ring.header, 1);
706
+ if (writeIndex - readIndex >= ring.capacity) {
707
+ Atomics.add(ring.header, 4, 1);
708
+ return false;
709
+ }
710
+ writeEngineCommandRecord(
711
+ ring.view,
712
+ recordOffset(writeIndex, ring.capacity, SONARE_ENGINE_COMMAND_RECORD_BYTES),
713
+ command
714
+ );
715
+ Atomics.store(ring.header, 0, writeIndex + 1);
716
+ return true;
717
+ }
718
+ function popSonareEngineCommandRingBuffer(ring) {
719
+ const readIndex = Atomics.load(ring.header, 1);
720
+ const writeIndex = Atomics.load(ring.header, 0);
721
+ if (readIndex >= writeIndex) {
722
+ return null;
723
+ }
724
+ const command = readEngineCommandRecord(
725
+ ring.view,
726
+ recordOffset(readIndex, ring.capacity, SONARE_ENGINE_COMMAND_RECORD_BYTES)
727
+ );
728
+ Atomics.store(ring.header, 1, readIndex + 1);
729
+ return command;
730
+ }
731
+ function writeSonareEngineTelemetryRingBuffer(ring, telemetry) {
732
+ const writeIndex = Atomics.load(ring.header, 0);
733
+ writeEngineTelemetryRecord(
734
+ ring.view,
735
+ recordOffset(writeIndex, ring.capacity, SONARE_ENGINE_TELEMETRY_RECORD_BYTES),
736
+ telemetry
737
+ );
738
+ Atomics.store(ring.header, 0, writeIndex + 1);
739
+ if (writeIndex + 1 > ring.capacity) {
740
+ Atomics.store(ring.header, 4, writeIndex + 1 - ring.capacity);
741
+ }
742
+ }
743
+ function readSonareEngineTelemetryRingBuffer(ring, readIndex = 0) {
744
+ const writeIndex = Atomics.load(ring.header, 0);
745
+ const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
746
+ const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
747
+ const telemetry = [];
748
+ for (let index = firstReadable; index < writeIndex; index++) {
749
+ telemetry.push(
750
+ readEngineTelemetryRecord(
751
+ ring.view,
752
+ recordOffset(index, ring.capacity, SONARE_ENGINE_TELEMETRY_RECORD_BYTES)
753
+ )
754
+ );
755
+ }
756
+ return { nextReadIndex: writeIndex, telemetry };
757
+ }
758
+ function meterRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
759
+ const headerBytes = SONARE_METER_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
760
+ const header = new Int32Array(sharedBuffer, 0, SONARE_METER_RING_HEADER_INTS);
761
+ const existingCapacity = Atomics.load(header, 1);
762
+ const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
763
+ const minBytes = sonareMeterRingBufferByteLength(capacity);
764
+ if (sharedBuffer.byteLength < minBytes) {
765
+ throw new Error("meterSharedBuffer is too small for the requested ring capacity.");
766
+ }
767
+ Atomics.store(header, 1, capacity);
768
+ Atomics.store(header, 2, SONARE_METER_RING_RECORD_FLOATS);
769
+ return {
770
+ header,
771
+ records: new Float32Array(
772
+ sharedBuffer,
773
+ headerBytes,
774
+ capacity * SONARE_METER_RING_RECORD_FLOATS
775
+ ),
776
+ capacity
777
+ };
778
+ }
779
+ function spectrumRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBands) {
780
+ const headerBytes = SONARE_SPECTRUM_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
781
+ const header = new Int32Array(sharedBuffer, 0, SONARE_SPECTRUM_RING_HEADER_INTS);
782
+ const existingCapacity = Atomics.load(header, 1);
783
+ const existingBands = Atomics.load(header, 3);
784
+ const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
785
+ const bands = Math.max(1, Math.floor(existingBands || fallbackBands || 16));
786
+ const recordFloats = 2 + bands;
787
+ const minBytes = sonareSpectrumRingBufferByteLength(capacity, bands);
788
+ if (sharedBuffer.byteLength < minBytes) {
789
+ throw new Error("spectrumSharedBuffer is too small for the requested ring capacity.");
790
+ }
791
+ Atomics.store(header, 1, capacity);
792
+ Atomics.store(header, 2, recordFloats);
793
+ Atomics.store(header, 3, bands);
794
+ return {
795
+ header,
796
+ records: new Float32Array(sharedBuffer, headerBytes, capacity * recordFloats),
797
+ capacity,
798
+ bands,
799
+ recordFloats
800
+ };
801
+ }
802
+ function engineRingFromSharedBuffer(sharedBuffer, recordBytes, fallbackCapacity) {
803
+ const headerBytes = SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
804
+ const header = new Int32Array(sharedBuffer, 0, SONARE_ENGINE_RING_HEADER_INTS);
805
+ const existingCapacity = Atomics.load(header, 2);
806
+ const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
807
+ const minBytes = headerBytes + capacity * recordBytes;
808
+ if (sharedBuffer.byteLength < minBytes) {
809
+ throw new Error("engine SharedArrayBuffer is too small for the requested ring capacity.");
810
+ }
811
+ Atomics.store(header, 2, capacity);
812
+ Atomics.store(header, 3, recordBytes);
813
+ return {
814
+ header,
815
+ view: new DataView(sharedBuffer, headerBytes, capacity * recordBytes),
816
+ capacity
817
+ };
818
+ }
819
+ function recordOffset(index, capacity, recordBytes) {
820
+ return index % capacity * recordBytes;
821
+ }
822
+ function toBigInt64(value, fallback) {
823
+ if (typeof value === "bigint") {
824
+ return value;
825
+ }
826
+ if (typeof value === "number") {
827
+ return BigInt(Math.trunc(value));
828
+ }
829
+ return fallback;
830
+ }
831
+ function writeEngineCommandRecord(view, offset, command) {
832
+ view.setUint32(offset, command.type, true);
833
+ view.setUint32(offset + 4, command.targetId ?? 0, true);
834
+ view.setBigInt64(offset + 8, toBigInt64(command.sampleTime, -1n), true);
835
+ view.setFloat32(offset + 16, command.argFloat ?? 0, true);
836
+ view.setUint32(offset + 20, 0, true);
837
+ view.setBigInt64(offset + 24, toBigInt64(command.argInt, 0n), true);
838
+ }
839
+ function readEngineCommandRecord(view, offset) {
840
+ return {
841
+ type: view.getUint32(offset, true),
842
+ targetId: view.getUint32(offset + 4, true),
843
+ sampleTime: Number(view.getBigInt64(offset + 8, true)),
844
+ argFloat: view.getFloat32(offset + 16, true),
845
+ argInt: Number(view.getBigInt64(offset + 24, true))
846
+ };
847
+ }
848
+ function writeEngineTelemetryRecord(view, offset, telemetry) {
849
+ view.setUint32(offset, telemetry.type, true);
850
+ view.setUint32(offset + 4, telemetry.error, true);
851
+ view.setBigInt64(offset + 8, BigInt(Math.trunc(telemetry.renderFrame)), true);
852
+ view.setBigInt64(offset + 16, BigInt(Math.trunc(telemetry.timelineSample)), true);
853
+ view.setBigInt64(offset + 24, BigInt(Math.trunc(telemetry.audibleTimelineSample)), true);
854
+ view.setInt32(offset + 32, telemetry.graphLatencySamplesQ8, true);
855
+ view.setUint32(offset + 36, telemetry.value, true);
856
+ view.setBigInt64(offset + 40, 0n, true);
857
+ }
858
+ function readEngineTelemetryRecord(view, offset) {
859
+ return {
860
+ type: view.getUint32(offset, true),
861
+ error: view.getUint32(offset + 4, true),
862
+ renderFrame: Number(view.getBigInt64(offset + 8, true)),
863
+ timelineSample: Number(view.getBigInt64(offset + 16, true)),
864
+ audibleTimelineSample: Number(view.getBigInt64(offset + 24, true)),
865
+ graphLatencySamplesQ8: view.getInt32(offset + 32, true),
866
+ value: view.getUint32(offset + 36, true)
867
+ };
868
+ }
869
+ function telemetryFromEngine(telemetry) {
870
+ return {
871
+ type: telemetry.type,
872
+ error: telemetry.error,
873
+ renderFrame: telemetry.renderFrame,
874
+ timelineSample: telemetry.timelineSample,
875
+ audibleTimelineSample: telemetry.audibleTimelineSample,
876
+ graphLatencySamplesQ8: telemetry.graphLatencySamplesQ8,
877
+ value: telemetry.value
878
+ };
879
+ }
880
+ function meterFromEngine(meter) {
881
+ return {
882
+ type: "meter",
883
+ frame: meter.renderFrame,
884
+ peakDbL: meter.peakDbL,
885
+ peakDbR: meter.peakDbR,
886
+ rmsDbL: meter.rmsDbL,
887
+ rmsDbR: meter.rmsDbR,
888
+ correlation: meter.correlation
889
+ };
890
+ }
891
+ function magnitudeToDb(value) {
892
+ return value > 1e-12 ? 20 * Math.log10(value) : -120;
893
+ }
894
+ var SonareWorkletProcessor = class {
895
+ constructor(options, transport) {
896
+ this.closed = false;
897
+ this.processedFrames = 0;
898
+ this.lastMeterFrame = 0;
899
+ this.lastSpectrumFrame = 0;
900
+ if (!options.sceneJson) {
901
+ throw new Error("sceneJson is required.");
902
+ }
903
+ this.sampleRate = options.sampleRate ?? 48e3;
904
+ this.blockSize = options.blockSize ?? 128;
905
+ this.meterIntervalFrames = Math.max(0, Math.floor(options.meterIntervalFrames ?? 2048));
906
+ this.spectrumIntervalFrames = Math.max(0, Math.floor(options.spectrumIntervalFrames ?? 0));
907
+ this.transport = transport;
908
+ this.meterIntervalFrames = Math.max(0, Math.floor(options.meterIntervalFrames ?? 2048));
909
+ this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
910
+ this.spectrumRing = options.spectrumSharedBuffer ? spectrumRingFromSharedBuffer(
911
+ options.spectrumSharedBuffer,
912
+ options.spectrumRingCapacity,
913
+ options.spectrumBands
914
+ ) : void 0;
915
+ const spectrumBandCount = this.spectrumRing?.bands ?? Math.max(1, options.spectrumBands ?? 16);
916
+ this.spectrumBands = new Float32Array(spectrumBandCount);
917
+ this.mixer = Mixer.fromSceneJson(options.sceneJson, this.sampleRate, this.blockSize);
918
+ this.mixer.compile();
919
+ const sceneStripCount = this.mixer.stripCount();
920
+ const stripCount = options.stripCount ?? sceneStripCount;
921
+ if (stripCount !== sceneStripCount) {
922
+ throw new Error("stripCount must match the scene strip count.");
923
+ }
924
+ this.realtime = this.mixer.createRealtimeBuffer();
925
+ }
926
+ process(inputs, outputs) {
927
+ if (this.closed) {
928
+ return false;
929
+ }
930
+ const output = outputs[0];
931
+ const leftOut = output?.[0];
932
+ const rightOut = output?.[1];
933
+ if (!leftOut) {
934
+ return true;
935
+ }
936
+ const frames = leftOut.length;
937
+ if (frames !== this.blockSize) {
938
+ return false;
939
+ }
940
+ for (let strip = 0; strip < this.realtime.leftInputs.length; strip++) {
941
+ const input = inputs[strip];
942
+ const left = input?.[0];
943
+ const right = input?.[1];
944
+ const leftTarget = this.realtime.leftInputs[strip];
945
+ const rightTarget = this.realtime.rightInputs[strip];
946
+ if (left && left.length === frames) {
947
+ leftTarget.set(left);
948
+ if (right && right.length === frames) {
949
+ rightTarget.set(right);
950
+ } else {
951
+ rightTarget.set(left);
952
+ }
953
+ } else {
954
+ leftTarget.fill(0);
955
+ rightTarget.fill(0);
956
+ }
957
+ }
958
+ this.realtime.process(frames);
959
+ leftOut.set(this.realtime.outLeft);
960
+ if (rightOut) {
961
+ rightOut.set(this.realtime.outRight);
962
+ }
963
+ this.processedFrames += frames;
964
+ this.publishMeter(this.realtime.outLeft, this.realtime.outRight);
965
+ this.publishSpectrum(this.realtime.outLeft, this.realtime.outRight);
966
+ return true;
967
+ }
968
+ receiveMessage(message) {
969
+ if (this.closed) {
970
+ return;
971
+ }
972
+ if (message.type === "destroy") {
973
+ this.destroy();
974
+ return;
975
+ }
976
+ if (message.type === "setMeterInterval") {
977
+ this.meterIntervalFrames = Math.max(0, Math.floor(message.frames));
978
+ return;
979
+ }
980
+ if (message.type === "scheduleInsertAutomation") {
981
+ this.mixer.scheduleInsertAutomation(
982
+ message.stripIndex,
983
+ message.insertIndex,
984
+ message.paramId,
985
+ message.samplePos ?? this.processedFrames,
986
+ message.value,
987
+ message.curve ?? "linear"
988
+ );
989
+ }
990
+ }
991
+ destroy() {
992
+ if (!this.closed) {
993
+ this.mixer.delete();
994
+ this.closed = true;
995
+ }
996
+ }
997
+ publishMeter(left, right) {
998
+ if (!this.transport || this.meterIntervalFrames <= 0) {
999
+ return;
1000
+ }
1001
+ if (this.processedFrames - this.lastMeterFrame < this.meterIntervalFrames) {
1002
+ return;
1003
+ }
1004
+ this.lastMeterFrame = this.processedFrames;
1005
+ let peakL = 0;
1006
+ let peakR = 0;
1007
+ let sumL = 0;
1008
+ let sumR = 0;
1009
+ let sumLR = 0;
1010
+ for (let i = 0; i < left.length; i++) {
1011
+ const l = left[i] ?? 0;
1012
+ const r = right[i] ?? 0;
1013
+ const absL = Math.abs(l);
1014
+ const absR = Math.abs(r);
1015
+ if (absL > peakL) {
1016
+ peakL = absL;
1017
+ }
1018
+ if (absR > peakR) {
1019
+ peakR = absR;
1020
+ }
1021
+ sumL += l * l;
1022
+ sumR += r * r;
1023
+ sumLR += l * r;
1024
+ }
1025
+ const rmsL = Math.sqrt(sumL / Math.max(1, left.length));
1026
+ const rmsR = Math.sqrt(sumR / Math.max(1, right.length));
1027
+ const denominator = Math.sqrt(sumL * sumR);
1028
+ const meter = {
1029
+ type: "meter",
1030
+ frame: this.processedFrames,
1031
+ peakDbL: toDb(peakL),
1032
+ peakDbR: toDb(peakR),
1033
+ rmsDbL: toDb(rmsL),
1034
+ rmsDbR: toDb(rmsR),
1035
+ correlation: denominator > 0 ? sumLR / denominator : 0
1036
+ };
1037
+ this.transport.onMeter?.(meter);
1038
+ if (this.meterRing) {
1039
+ this.writeMeterRing(meter);
1040
+ } else {
1041
+ this.transport.postMessage?.(meter);
1042
+ }
1043
+ }
1044
+ writeMeterRing(meter) {
1045
+ const ring = this.meterRing;
1046
+ if (!ring) {
1047
+ return;
1048
+ }
1049
+ const writeIndex = Atomics.load(ring.header, 0);
1050
+ const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
1051
+ ring.records[offset] = meter.frame;
1052
+ ring.records[offset + 1] = meter.peakDbL;
1053
+ ring.records[offset + 2] = meter.peakDbR;
1054
+ ring.records[offset + 3] = meter.rmsDbL;
1055
+ ring.records[offset + 4] = meter.rmsDbR;
1056
+ ring.records[offset + 5] = meter.correlation;
1057
+ Atomics.store(ring.header, 0, writeIndex + 1);
1058
+ if (writeIndex + 1 > ring.capacity) {
1059
+ Atomics.store(ring.header, 3, writeIndex + 1 - ring.capacity);
1060
+ }
1061
+ }
1062
+ publishSpectrum(left, right) {
1063
+ if (this.spectrumIntervalFrames <= 0) {
1064
+ return;
1065
+ }
1066
+ if (this.processedFrames - this.lastSpectrumFrame < this.spectrumIntervalFrames) {
1067
+ return;
1068
+ }
1069
+ this.lastSpectrumFrame = this.processedFrames;
1070
+ this.computeSpectrum(left, right);
1071
+ if (this.spectrumRing) {
1072
+ this.writeSpectrumRing(this.processedFrames, this.spectrumBands);
1073
+ return;
1074
+ }
1075
+ const spectrum = {
1076
+ type: "spectrum",
1077
+ frame: this.processedFrames,
1078
+ bands: new Float32Array(this.spectrumBands)
1079
+ };
1080
+ this.transport?.onSpectrum?.(spectrum);
1081
+ this.transport?.postMessage?.(spectrum);
1082
+ }
1083
+ computeSpectrum(left, right) {
1084
+ const n = Math.max(1, Math.min(left.length, right.length));
1085
+ for (let band = 0; band < this.spectrumBands.length; band++) {
1086
+ const bin = band + 1;
1087
+ let real = 0;
1088
+ let imag = 0;
1089
+ for (let i = 0; i < n; i++) {
1090
+ const sample = 0.5 * ((left[i] ?? 0) + (right[i] ?? 0));
1091
+ const phase = -2 * Math.PI * bin * i / n;
1092
+ real += sample * Math.cos(phase);
1093
+ imag += sample * Math.sin(phase);
1094
+ }
1095
+ this.spectrumBands[band] = magnitudeToDb(2 * Math.hypot(real, imag) / n);
1096
+ }
1097
+ }
1098
+ writeSpectrumRing(frame, bands) {
1099
+ const ring = this.spectrumRing;
1100
+ if (!ring) {
1101
+ return;
1102
+ }
1103
+ const writeIndex = Atomics.load(ring.header, 0);
1104
+ const offset = writeIndex % ring.capacity * ring.recordFloats;
1105
+ ring.records[offset] = frame;
1106
+ ring.records[offset + 1] = bands.length;
1107
+ ring.records.set(bands.subarray(0, ring.bands), offset + 2);
1108
+ Atomics.store(ring.header, 0, writeIndex + 1);
1109
+ if (writeIndex + 1 > ring.capacity) {
1110
+ Atomics.store(ring.header, 4, writeIndex + 1 - ring.capacity);
1111
+ }
1112
+ }
1113
+ };
1114
+ var SonareRealtimeEngineWorkletProcessor = class {
1115
+ constructor(options = {}, transport) {
1116
+ this.closed = false;
1117
+ this.lastMeterFrame = Number.NEGATIVE_INFINITY;
1118
+ this.sampleRate = options.sampleRate ?? 48e3;
1119
+ this.blockSize = options.blockSize ?? 128;
1120
+ this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
1121
+ this.runtimeTarget = options.runtimeTarget ?? "embind";
1122
+ if (this.runtimeTarget === "sonare-rt") {
1123
+ throw new Error(
1124
+ 'sonare-rt runtime is provided by the dedicated Emscripten AudioWorklet module; use SonareRealtimeEngineNode.create({ runtimeTarget: "sonare-rt", moduleUrl: ... }) to load it.'
1125
+ );
1126
+ }
1127
+ this.transport = transport;
1128
+ this.meterIntervalFrames = Math.max(0, Math.floor(options.meterIntervalFrames ?? 2048));
1129
+ this.commandRing = options.commandSharedBuffer ? this.commandRingFromSharedBuffer(options.commandSharedBuffer, options.commandRingCapacity) : void 0;
1130
+ this.telemetryRing = options.telemetrySharedBuffer ? this.telemetryRingFromSharedBuffer(
1131
+ options.telemetrySharedBuffer,
1132
+ options.telemetryRingCapacity
1133
+ ) : void 0;
1134
+ this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
1135
+ }
1136
+ process(inputs, outputs) {
1137
+ if (this.closed) {
1138
+ return false;
1139
+ }
1140
+ const output = outputs[0];
1141
+ const firstOutput = output?.[0];
1142
+ if (!firstOutput) {
1143
+ return true;
1144
+ }
1145
+ const frames = firstOutput.length;
1146
+ if (frames > this.blockSize) {
1147
+ for (const channel of output ?? []) {
1148
+ channel.fill(0);
1149
+ }
1150
+ this.publishTelemetry();
1151
+ return true;
1152
+ }
1153
+ this.drainCommands();
1154
+ const channels = [];
1155
+ const input = inputs[0];
1156
+ for (let ch = 0; ch < this.channelCount; ch++) {
1157
+ const source = input?.[ch];
1158
+ const channel = new Float32Array(frames);
1159
+ if (source && source.length === frames) {
1160
+ channel.set(source);
1161
+ }
1162
+ channels.push(channel);
1163
+ }
1164
+ const processed = this.engine.process(channels);
1165
+ for (let ch = 0; ch < output.length; ch++) {
1166
+ const target = output[ch];
1167
+ const source = processed[ch] ?? processed[0];
1168
+ if (source) {
1169
+ target.set(source.subarray(0, target.length));
1170
+ } else {
1171
+ target.fill(0);
1172
+ }
1173
+ }
1174
+ this.publishTelemetry();
1175
+ this.publishMeters();
1176
+ return true;
1177
+ }
1178
+ receiveCommand(command) {
1179
+ if (!this.closed) {
1180
+ this.applyCommand(command);
1181
+ }
1182
+ }
1183
+ destroy() {
1184
+ if (!this.closed) {
1185
+ this.engine.destroy();
1186
+ this.closed = true;
1187
+ }
1188
+ }
1189
+ drainCommands() {
1190
+ if (!this.commandRing) {
1191
+ return;
1192
+ }
1193
+ for (let i = 0; i < 64; i++) {
1194
+ const command = popSonareEngineCommandRingBuffer(this.commandRing);
1195
+ if (!command) {
1196
+ return;
1197
+ }
1198
+ this.applyCommand(command);
1199
+ }
1200
+ }
1201
+ applyCommand(command) {
1202
+ const sampleTime = Number(command.sampleTime ?? -1);
1203
+ switch (command.type) {
1204
+ case 2 /* TransportPlay */:
1205
+ this.engine.play(sampleTime);
1206
+ break;
1207
+ case 3 /* TransportStop */:
1208
+ this.engine.stop(sampleTime);
1209
+ break;
1210
+ case 4 /* TransportSeekSample */:
1211
+ this.engine.seekSample(Number(command.argInt ?? 0), sampleTime);
1212
+ break;
1213
+ case 5 /* TransportSeekPpq */:
1214
+ this.engine.seekPpq(Number(command.argFloat ?? 0), sampleTime);
1215
+ break;
1216
+ case 6 /* SetTempoMap */:
1217
+ this.engine.setTempo(Number(command.argFloat ?? 120));
1218
+ break;
1219
+ case 7 /* SetLoop */:
1220
+ this.engine.setLoop(
1221
+ Number(command.argFloat ?? 0),
1222
+ Number(command.argInt ?? 0) / 1e6,
1223
+ command.targetId !== 0
1224
+ );
1225
+ break;
1226
+ case 13 /* ArmRecord */:
1227
+ this.engine.armCapture(Boolean(command.argInt));
1228
+ break;
1229
+ case 14 /* Punch */:
1230
+ this.engine.setCapturePunch(
1231
+ Number(command.argInt ?? 0),
1232
+ Math.max(0, Math.round(Number(command.argFloat ?? 0) * this.sampleRate)),
1233
+ true
1234
+ );
1235
+ break;
1236
+ case 15 /* SetMetronome */:
1237
+ this.engine.setMetronome({
1238
+ enabled: Boolean(command.argInt),
1239
+ beatGain: 0.25,
1240
+ accentGain: 0.75,
1241
+ clickSamples: 64
1242
+ });
1243
+ break;
1244
+ default:
1245
+ this.publishTelemetryRecord({
1246
+ type: 1 /* Error */,
1247
+ error: 7 /* UnknownTarget */,
1248
+ renderFrame: 0,
1249
+ timelineSample: 0,
1250
+ audibleTimelineSample: 0,
1251
+ graphLatencySamplesQ8: 0,
1252
+ value: Number(command.type)
1253
+ });
1254
+ break;
1255
+ }
1256
+ }
1257
+ publishTelemetry() {
1258
+ for (const item of this.engine.drainTelemetry(64)) {
1259
+ this.publishTelemetryRecord(telemetryFromEngine(item));
1260
+ }
1261
+ }
1262
+ publishTelemetryRecord(record) {
1263
+ if (this.telemetryRing) {
1264
+ writeSonareEngineTelemetryRingBuffer(this.telemetryRing, record);
1265
+ return;
1266
+ }
1267
+ this.transport?.postMessage?.(record);
1268
+ }
1269
+ publishMeters() {
1270
+ if (!this.transport || this.meterIntervalFrames <= 0) {
1271
+ return;
1272
+ }
1273
+ for (const item of this.engine.drainMeterTelemetry(64)) {
1274
+ const meter = meterFromEngine(item);
1275
+ if (meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
1276
+ continue;
1277
+ }
1278
+ this.lastMeterFrame = meter.frame;
1279
+ this.transport.onMeter?.(meter);
1280
+ this.transport.postMessage?.(meter);
1281
+ }
1282
+ }
1283
+ commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
1284
+ const ring = engineRingFromSharedBuffer(
1285
+ sharedBuffer,
1286
+ SONARE_ENGINE_COMMAND_RECORD_BYTES,
1287
+ fallbackCapacity
1288
+ );
1289
+ return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
1290
+ }
1291
+ telemetryRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
1292
+ const ring = engineRingFromSharedBuffer(
1293
+ sharedBuffer,
1294
+ SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
1295
+ fallbackCapacity
1296
+ );
1297
+ return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
1298
+ }
1299
+ };
1300
+ var SonareRtRealtimeEngineRuntime = class {
1301
+ constructor(options) {
1302
+ this.closed = false;
1303
+ this.module = options.module;
1304
+ this.memory = options.memory;
1305
+ this.sampleRate = options.sampleRate ?? 48e3;
1306
+ this.blockSize = options.blockSize ?? 128;
1307
+ this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
1308
+ this.commandRing = options.commandSharedBuffer ? this.commandRingFromSharedBuffer(options.commandSharedBuffer, options.commandRingCapacity) : void 0;
1309
+ this.telemetryRing = options.telemetrySharedBuffer ? this.telemetryRingFromSharedBuffer(
1310
+ options.telemetrySharedBuffer,
1311
+ options.telemetryRingCapacity
1312
+ ) : void 0;
1313
+ this.engine = this.module._sonare_rt_engine_create();
1314
+ if (this.engine <= 0) {
1315
+ throw new Error("failed to create sonare-rt engine");
1316
+ }
1317
+ if (this.module._sonare_rt_engine_prepare(
1318
+ this.engine,
1319
+ this.sampleRate,
1320
+ this.blockSize,
1321
+ 1024,
1322
+ 1024
1323
+ ) !== 1) {
1324
+ this.module._sonare_rt_engine_destroy(this.engine);
1325
+ throw new Error("failed to prepare sonare-rt engine");
1326
+ }
1327
+ this.channelPointerTable = this.module._malloc(
1328
+ this.channelCount * Uint32Array.BYTES_PER_ELEMENT
1329
+ );
1330
+ this.channelBuffers = [];
1331
+ for (let ch = 0; ch < this.channelCount; ch++) {
1332
+ this.channelBuffers.push(
1333
+ this.module._malloc(this.blockSize * Float32Array.BYTES_PER_ELEMENT)
1334
+ );
1335
+ }
1336
+ this.telemetryIntsPtr = this.module._malloc(64 * 4 * Int32Array.BYTES_PER_ELEMENT);
1337
+ this.telemetryFramesPtr = this.module._malloc(64 * 3 * Float64Array.BYTES_PER_ELEMENT);
1338
+ this.writeChannelPointers();
1339
+ }
1340
+ process(inputs, outputs) {
1341
+ if (this.closed) {
1342
+ return false;
1343
+ }
1344
+ const output = outputs[0];
1345
+ const firstOutput = output?.[0];
1346
+ if (!firstOutput) {
1347
+ return true;
1348
+ }
1349
+ const frames = firstOutput.length;
1350
+ if (frames > this.blockSize) {
1351
+ for (const channel of output) {
1352
+ channel.fill(0);
1353
+ }
1354
+ return true;
1355
+ }
1356
+ this.drainCommands();
1357
+ const heap = new Float32Array(this.memory.buffer);
1358
+ const input = inputs[0];
1359
+ for (let ch = 0; ch < this.channelCount; ch++) {
1360
+ const ptr = this.channelBuffers[ch] ?? this.channelBuffers[0];
1361
+ const offset = ptr >> 2;
1362
+ const source = input?.[ch];
1363
+ if (source && source.length === frames) {
1364
+ heap.set(source, offset);
1365
+ } else {
1366
+ heap.fill(0, offset, offset + frames);
1367
+ }
1368
+ }
1369
+ this.module._sonare_rt_engine_process(
1370
+ this.engine,
1371
+ this.channelPointerTable,
1372
+ this.channelCount,
1373
+ frames
1374
+ );
1375
+ for (let ch = 0; ch < output.length; ch++) {
1376
+ const target = output[ch];
1377
+ const ptr = this.channelBuffers[ch] ?? this.channelBuffers[0];
1378
+ target.set(heap.subarray(ptr >> 2, (ptr >> 2) + target.length));
1379
+ }
1380
+ this.publishTelemetry();
1381
+ return true;
1382
+ }
1383
+ destroy() {
1384
+ if (this.closed) {
1385
+ return;
1386
+ }
1387
+ this.module._free(this.telemetryFramesPtr);
1388
+ this.module._free(this.telemetryIntsPtr);
1389
+ for (const ptr of this.channelBuffers) {
1390
+ this.module._free(ptr);
1391
+ }
1392
+ this.module._free(this.channelPointerTable);
1393
+ this.module._sonare_rt_engine_destroy(this.engine);
1394
+ this.closed = true;
1395
+ }
1396
+ writeChannelPointers() {
1397
+ const pointers = new Uint32Array(this.memory.buffer);
1398
+ const offset = this.channelPointerTable >> 2;
1399
+ for (let ch = 0; ch < this.channelBuffers.length; ch++) {
1400
+ pointers[offset + ch] = this.channelBuffers[ch];
1401
+ }
1402
+ }
1403
+ drainCommands() {
1404
+ if (!this.commandRing) {
1405
+ return;
1406
+ }
1407
+ for (let i = 0; i < 64; i++) {
1408
+ const command = popSonareEngineCommandRingBuffer(this.commandRing);
1409
+ if (!command) {
1410
+ return;
1411
+ }
1412
+ this.applyCommand(command);
1413
+ }
1414
+ }
1415
+ applyCommand(command) {
1416
+ const sampleTime = toBigInt64(command.sampleTime, -1n);
1417
+ switch (command.type) {
1418
+ case 2 /* TransportPlay */:
1419
+ this.module._sonare_rt_engine_play(this.engine, sampleTime);
1420
+ break;
1421
+ case 3 /* TransportStop */:
1422
+ this.module._sonare_rt_engine_stop(this.engine, sampleTime);
1423
+ break;
1424
+ case 4 /* TransportSeekSample */:
1425
+ this.module._sonare_rt_engine_seek_sample(
1426
+ this.engine,
1427
+ toBigInt64(command.argInt, 0n),
1428
+ sampleTime
1429
+ );
1430
+ break;
1431
+ case 5 /* TransportSeekPpq */:
1432
+ this.module._sonare_rt_engine_seek_ppq(
1433
+ this.engine,
1434
+ Number(command.argFloat ?? 0),
1435
+ sampleTime
1436
+ );
1437
+ break;
1438
+ case 6 /* SetTempoMap */:
1439
+ this.module._sonare_rt_engine_set_tempo(this.engine, Number(command.argFloat ?? 120));
1440
+ break;
1441
+ case 7 /* SetLoop */:
1442
+ this.module._sonare_rt_engine_set_loop(
1443
+ this.engine,
1444
+ Number(command.argFloat ?? 0),
1445
+ Number(command.argInt ?? 0) / 1e6,
1446
+ command.targetId ? 1 : 0
1447
+ );
1448
+ break;
1449
+ case 13 /* ArmRecord */:
1450
+ this.module._sonare_rt_engine_set_capture_armed(this.engine, command.argInt ? 1 : 0);
1451
+ break;
1452
+ case 14 /* Punch */:
1453
+ this.module._sonare_rt_engine_set_capture_punch(
1454
+ this.engine,
1455
+ toBigInt64(command.argInt, 0n),
1456
+ BigInt(Math.trunc(Number(command.argFloat ?? 0) * this.sampleRate)),
1457
+ 1
1458
+ );
1459
+ break;
1460
+ case 15 /* SetMetronome */:
1461
+ this.module._sonare_rt_engine_set_metronome_enabled(
1462
+ this.engine,
1463
+ command.argInt ? 1 : 0,
1464
+ 0.25,
1465
+ 0.75,
1466
+ 64
1467
+ );
1468
+ break;
1469
+ case 17 /* SeekMarker */:
1470
+ this.module._sonare_rt_engine_seek_marker(
1471
+ this.engine,
1472
+ Math.trunc(command.targetId ?? 0),
1473
+ sampleTime
1474
+ );
1475
+ break;
1476
+ default:
1477
+ if (this.telemetryRing) {
1478
+ writeSonareEngineTelemetryRingBuffer(this.telemetryRing, {
1479
+ type: 1 /* Error */,
1480
+ error: 7 /* UnknownTarget */,
1481
+ renderFrame: 0,
1482
+ timelineSample: 0,
1483
+ audibleTimelineSample: 0,
1484
+ graphLatencySamplesQ8: 0,
1485
+ value: Number(command.type)
1486
+ });
1487
+ }
1488
+ break;
1489
+ }
1490
+ }
1491
+ publishTelemetry() {
1492
+ if (!this.telemetryRing) {
1493
+ this.module._sonare_rt_engine_drain_telemetry(
1494
+ this.engine,
1495
+ this.telemetryIntsPtr,
1496
+ this.telemetryFramesPtr,
1497
+ 64
1498
+ );
1499
+ return;
1500
+ }
1501
+ const count = this.module._sonare_rt_engine_drain_telemetry(
1502
+ this.engine,
1503
+ this.telemetryIntsPtr,
1504
+ this.telemetryFramesPtr,
1505
+ 64
1506
+ );
1507
+ const ints = new Int32Array(this.memory.buffer);
1508
+ const frames = new Float64Array(this.memory.buffer);
1509
+ const intBase = this.telemetryIntsPtr >> 2;
1510
+ const frameBase = this.telemetryFramesPtr >> 3;
1511
+ for (let i = 0; i < count; i++) {
1512
+ writeSonareEngineTelemetryRingBuffer(this.telemetryRing, {
1513
+ type: ints[intBase + i * 4],
1514
+ error: ints[intBase + i * 4 + 1],
1515
+ renderFrame: frames[frameBase + i * 3],
1516
+ timelineSample: frames[frameBase + i * 3 + 1],
1517
+ audibleTimelineSample: frames[frameBase + i * 3 + 2],
1518
+ graphLatencySamplesQ8: ints[intBase + i * 4 + 2],
1519
+ value: ints[intBase + i * 4 + 3]
1520
+ });
1521
+ }
1522
+ }
1523
+ commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
1524
+ const ring = engineRingFromSharedBuffer(
1525
+ sharedBuffer,
1526
+ SONARE_ENGINE_COMMAND_RECORD_BYTES,
1527
+ fallbackCapacity
1528
+ );
1529
+ return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
1530
+ }
1531
+ telemetryRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
1532
+ const ring = engineRingFromSharedBuffer(
1533
+ sharedBuffer,
1534
+ SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
1535
+ fallbackCapacity
1536
+ );
1537
+ return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
1538
+ }
1539
+ };
1540
+ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
1541
+ constructor(node, capabilities, commandRing, telemetryRing) {
1542
+ this.telemetryReadIndex = 0;
1543
+ this.telemetryListeners = /* @__PURE__ */ new Set();
1544
+ this.meterListeners = /* @__PURE__ */ new Set();
1545
+ this.destroyed = false;
1546
+ this.node = node;
1547
+ this.capabilities = capabilities;
1548
+ this.commandRing = commandRing;
1549
+ this.telemetryRing = telemetryRing;
1550
+ this.ready = new Promise((resolve, reject) => {
1551
+ this.resolveReady = resolve;
1552
+ this.rejectReady = reject;
1553
+ });
1554
+ if (capabilities.runtimeTarget !== "sonare-rt") {
1555
+ this.resolveReady();
1556
+ }
1557
+ this.node.port.onmessage = (event) => {
1558
+ if (isEngineTelemetryRecord(event.data)) {
1559
+ this.emitTelemetry(event.data);
1560
+ } else if (isMeterSnapshot(event.data)) {
1561
+ this.emitMeter(event.data);
1562
+ } else if (isRecord(event.data) && event.data.type === "ready") {
1563
+ this.resolveReady();
1564
+ } else if (isRecord(event.data) && event.data.type === "error") {
1565
+ this.rejectReady(new Error(String(event.data.message ?? "AudioWorklet error")));
1566
+ }
1567
+ };
1568
+ }
1569
+ static async create(context, options = {}) {
1570
+ const runtimeTarget = options.runtimeTarget ?? "embind";
1571
+ const processorName = options.processorName ?? "sonare-realtime-engine-processor";
1572
+ const moduleUrl = options.moduleUrl;
1573
+ if (moduleUrl && context.audioWorklet?.addModule) {
1574
+ await context.audioWorklet.addModule(moduleUrl);
1575
+ }
1576
+ const detectedCapabilities = options.engineAbiVersion !== void 0 ? {
1577
+ engineAbiVersion: options.engineAbiVersion,
1578
+ expectedEngineAbiVersion: options.expectedEngineAbiVersion ?? options.engineAbiVersion,
1579
+ abiCompatible: options.engineAbiVersion === (options.expectedEngineAbiVersion ?? options.engineAbiVersion)
1580
+ } : runtimeTarget === "embind" ? engineCapabilities() : void 0;
1581
+ if (options.requireAbiCompatible !== false && detectedCapabilities?.abiCompatible === false) {
1582
+ throw new Error(
1583
+ `Engine ABI mismatch: wasm=${detectedCapabilities.engineAbiVersion}, expected=${detectedCapabilities.expectedEngineAbiVersion}`
1584
+ );
1585
+ }
1586
+ const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === "function";
1587
+ const atomics = typeof globalThis.Atomics === "object";
1588
+ const audioWorklet = typeof AudioWorkletNode !== "undefined" || !!options.nodeFactory;
1589
+ const degradedReason = options.mode !== "postMessage" && (!sharedArrayBuffer || !atomics) ? "SharedArrayBuffer or Atomics unavailable; using postMessage transport." : void 0;
1590
+ const mode = options.mode === "postMessage" || !sharedArrayBuffer || !atomics ? "postMessage" : "sab";
1591
+ if (options.mode === "sab" && mode !== "sab") {
1592
+ throw new Error(
1593
+ "SharedArrayBuffer mode requested but SharedArrayBuffer/Atomics are unavailable."
1594
+ );
1595
+ }
1596
+ const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128) : void 0;
1597
+ const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128) : void 0;
1598
+ const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
1599
+ const processorOptions = {
1600
+ runtimeTarget,
1601
+ rtModuleUrl: options.rtModuleUrl,
1602
+ rtWasmBinary: options.rtWasmBinary,
1603
+ sampleRate: options.sampleRate ?? context.sampleRate,
1604
+ blockSize: options.blockSize,
1605
+ channelCount,
1606
+ commandSharedBuffer: commandRing?.sharedBuffer,
1607
+ commandRingCapacity: commandRing?.capacity,
1608
+ telemetrySharedBuffer: telemetryRing?.sharedBuffer,
1609
+ telemetryRingCapacity: telemetryRing?.capacity
1610
+ };
1611
+ const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
1612
+ const node = factory(context, processorName, {
1613
+ numberOfInputs: 1,
1614
+ numberOfOutputs: 1,
1615
+ outputChannelCount: [channelCount],
1616
+ processorOptions
1617
+ });
1618
+ return new _SonareRealtimeEngineNode(
1619
+ node,
1620
+ {
1621
+ mode,
1622
+ runtimeTarget,
1623
+ sharedArrayBuffer,
1624
+ atomics,
1625
+ audioWorklet,
1626
+ engineAbiVersion: detectedCapabilities?.engineAbiVersion,
1627
+ expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
1628
+ abiCompatible: detectedCapabilities?.abiCompatible,
1629
+ degradedReason
1630
+ },
1631
+ commandRing,
1632
+ telemetryRing
1633
+ );
1634
+ }
1635
+ play(sampleTime = -1) {
1636
+ return this.sendCommand({ type: 2 /* TransportPlay */, sampleTime });
1637
+ }
1638
+ stop(sampleTime = -1) {
1639
+ return this.sendCommand({ type: 3 /* TransportStop */, sampleTime });
1640
+ }
1641
+ seekSample(timelineSample, sampleTime = -1) {
1642
+ return this.sendCommand({
1643
+ type: 4 /* TransportSeekSample */,
1644
+ sampleTime,
1645
+ argInt: timelineSample
1646
+ });
1647
+ }
1648
+ seekPpq(ppq, sampleTime = -1) {
1649
+ return this.sendCommand({
1650
+ type: 5 /* TransportSeekPpq */,
1651
+ sampleTime,
1652
+ argFloat: ppq
1653
+ });
1654
+ }
1655
+ sendCommand(command) {
1656
+ if (this.destroyed) {
1657
+ return false;
1658
+ }
1659
+ if (this.commandRing) {
1660
+ return pushSonareEngineCommandRingBuffer(this.commandRing, command);
1661
+ }
1662
+ this.node.port.postMessage(command);
1663
+ return true;
1664
+ }
1665
+ pollTelemetry() {
1666
+ if (!this.telemetryRing) {
1667
+ return [];
1668
+ }
1669
+ const read = readSonareEngineTelemetryRingBuffer(this.telemetryRing, this.telemetryReadIndex);
1670
+ this.telemetryReadIndex = read.nextReadIndex;
1671
+ for (const telemetry of read.telemetry) {
1672
+ this.emitTelemetry(telemetry);
1673
+ }
1674
+ return read.telemetry;
1675
+ }
1676
+ onTelemetry(callback) {
1677
+ this.telemetryListeners.add(callback);
1678
+ return () => {
1679
+ this.telemetryListeners.delete(callback);
1680
+ };
1681
+ }
1682
+ onMeter(callback) {
1683
+ this.meterListeners.add(callback);
1684
+ return () => {
1685
+ this.meterListeners.delete(callback);
1686
+ };
1687
+ }
1688
+ destroy() {
1689
+ if (this.destroyed) {
1690
+ return;
1691
+ }
1692
+ this.destroyed = true;
1693
+ this.node.port.postMessage({ type: 3 /* TransportStop */, sampleTime: -1 });
1694
+ this.node.disconnect();
1695
+ this.telemetryListeners.clear();
1696
+ this.meterListeners.clear();
1697
+ }
1698
+ emitTelemetry(telemetry) {
1699
+ for (const listener of this.telemetryListeners) {
1700
+ listener(telemetry);
1701
+ }
1702
+ }
1703
+ emitMeter(meter) {
1704
+ for (const listener of this.meterListeners) {
1705
+ listener(meter);
1706
+ }
1707
+ }
1708
+ };
1709
+ var SonareEngine = class _SonareEngine {
1710
+ constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
1711
+ this.automationLanes = /* @__PURE__ */ new Map();
1712
+ this.clips = /* @__PURE__ */ new Map();
1713
+ this.markers = /* @__PURE__ */ new Map();
1714
+ this.nextClipId = 1;
1715
+ this.nextMarkerId = 1;
1716
+ this.destroyed = false;
1717
+ this.context = context;
1718
+ this.realtimeNode = realtimeNode;
1719
+ this.offlineEngine = offlineEngine;
1720
+ this.node = realtimeNode.node;
1721
+ this.capabilities = realtimeNode.capabilities;
1722
+ this.sampleRate = sampleRate;
1723
+ this.offlineBlockSize = offlineBlockSize;
1724
+ this.offlineChannelCount = offlineChannelCount;
1725
+ this.transport = {
1726
+ play: (sampleTime = -1) => this.realtimeNode.play(sampleTime),
1727
+ stop: (sampleTime = -1) => this.realtimeNode.stop(sampleTime),
1728
+ seekPpq: (ppq, sampleTime = -1) => {
1729
+ this.offlineEngine.seekPpq(ppq, sampleTime);
1730
+ return this.realtimeNode.seekPpq(ppq, sampleTime);
1731
+ },
1732
+ seekSeconds: (seconds, sampleTime = -1) => {
1733
+ const timelineSample = Math.max(0, Math.round(seconds * this.sampleRate));
1734
+ this.offlineEngine.seekSample(timelineSample, sampleTime);
1735
+ return this.realtimeNode.seekSample(timelineSample, sampleTime);
1736
+ },
1737
+ setTempo: (bpm) => this.setTempo(bpm),
1738
+ setLoop: (startPpq, endPpq, enabled = true) => this.setLoop(startPpq, endPpq, enabled)
1739
+ };
1740
+ }
1741
+ static async create(context, options = {}) {
1742
+ const sampleRate = options.sampleRate ?? context.sampleRate;
1743
+ const blockSize = options.offlineBlockSize ?? options.blockSize ?? 128;
1744
+ const channelCount = Math.max(
1745
+ 1,
1746
+ Math.floor(options.offlineChannelCount ?? options.channelCount ?? 2)
1747
+ );
1748
+ const realtimeNode = await SonareRealtimeEngineNode.create(context, options);
1749
+ const offlineEngine = options.offlineEngine ?? new RealtimeEngine(sampleRate, blockSize);
1750
+ return new _SonareEngine(
1751
+ context,
1752
+ realtimeNode,
1753
+ offlineEngine,
1754
+ sampleRate,
1755
+ blockSize,
1756
+ channelCount
1757
+ );
1758
+ }
1759
+ async suspend() {
1760
+ if (this.destroyed) {
1761
+ return;
1762
+ }
1763
+ await this.context.suspend?.();
1764
+ }
1765
+ async resume() {
1766
+ if (this.destroyed) {
1767
+ return;
1768
+ }
1769
+ await this.context.resume?.();
1770
+ }
1771
+ setTempo(bpm) {
1772
+ this.offlineEngine.setTempo(bpm);
1773
+ this.realtimeNode.sendCommand({
1774
+ type: 6 /* SetTempoMap */,
1775
+ sampleTime: -1,
1776
+ argFloat: bpm
1777
+ });
1778
+ }
1779
+ setLoop(startPpq, endPpq, enabled = true) {
1780
+ this.offlineEngine.setLoop(startPpq, endPpq, enabled);
1781
+ return this.realtimeNode.sendCommand({
1782
+ type: 7 /* SetLoop */,
1783
+ targetId: enabled ? 1 : 0,
1784
+ sampleTime: -1,
1785
+ argFloat: startPpq,
1786
+ argInt: Math.round(endPpq * 1e6)
1787
+ });
1788
+ }
1789
+ setParam(nodeId, param, value) {
1790
+ void nodeId;
1791
+ void param;
1792
+ void value;
1793
+ return false;
1794
+ }
1795
+ scheduleParam(nodeId, param, ppq, value, curve = "linear") {
1796
+ const paramId = this.resolveParamId(nodeId, param);
1797
+ const lane = this.automationLanes.get(paramId) ?? [];
1798
+ lane.push({ ppq, value, curveToNext: this.curveCode(curve) });
1799
+ lane.sort((a, b) => a.ppq - b.ppq);
1800
+ this.automationLanes.set(paramId, lane);
1801
+ this.offlineEngine.setAutomationLane(paramId, lane);
1802
+ }
1803
+ addAutomationPoint(laneId, ppq, value, curve = "linear") {
1804
+ this.scheduleParam("", laneId, ppq, value, curve);
1805
+ }
1806
+ listParameters() {
1807
+ const parameters = [];
1808
+ for (let index = 0; index < this.offlineEngine.parameterCount(); index++) {
1809
+ parameters.push(this.offlineEngine.parameterInfoByIndex(index));
1810
+ }
1811
+ return parameters;
1812
+ }
1813
+ setSoloMute(target, solo, mute) {
1814
+ void target;
1815
+ void solo;
1816
+ void mute;
1817
+ return false;
1818
+ }
1819
+ addClip(trackId, buffer, startPpq, opts = {}) {
1820
+ const id = opts.id ?? this.nextClipId++;
1821
+ const clip = {
1822
+ ...opts,
1823
+ id,
1824
+ channels: buffer,
1825
+ startPpq
1826
+ };
1827
+ this.clips.set(id, clip);
1828
+ this.syncClips();
1829
+ void trackId;
1830
+ return id;
1831
+ }
1832
+ removeClip(clipId) {
1833
+ this.clips.delete(clipId);
1834
+ this.syncClips();
1835
+ }
1836
+ armRecord(trackId, enabled) {
1837
+ this.offlineEngine.armCapture(enabled);
1838
+ return this.realtimeNode.sendCommand({
1839
+ type: 13 /* ArmRecord */,
1840
+ targetId: this.resolveTargetId(trackId),
1841
+ sampleTime: -1,
1842
+ argInt: enabled ? 1 : 0
1843
+ });
1844
+ }
1845
+ punch(inPpq, outPpq) {
1846
+ const inSample = this.ppqToApproxSample(inPpq);
1847
+ const outSample = this.ppqToApproxSample(outPpq);
1848
+ this.offlineEngine.setCapturePunch(inSample, outSample, true);
1849
+ return this.realtimeNode.sendCommand({
1850
+ type: 14 /* Punch */,
1851
+ sampleTime: -1,
1852
+ argInt: inSample,
1853
+ argFloat: outPpq
1854
+ });
1855
+ }
1856
+ setMetronome(opts) {
1857
+ this.offlineEngine.setMetronome(opts);
1858
+ this.realtimeNode.sendCommand({
1859
+ type: 15 /* SetMetronome */,
1860
+ sampleTime: -1,
1861
+ argInt: opts.enabled ? 1 : 0
1862
+ });
1863
+ }
1864
+ addMarker(ppq, name = "") {
1865
+ const id = this.nextMarkerId++;
1866
+ this.markers.set(id, { id, ppq, name });
1867
+ this.syncMarkers();
1868
+ return id;
1869
+ }
1870
+ seekMarker(markerId) {
1871
+ this.offlineEngine.seekMarker(markerId);
1872
+ return false;
1873
+ }
1874
+ async renderOffline(totalFrames) {
1875
+ const frames = Math.max(0, Math.floor(totalFrames));
1876
+ const inputs = [];
1877
+ for (let ch = 0; ch < this.offlineChannelCount; ch++) {
1878
+ inputs.push(new Float32Array(frames));
1879
+ }
1880
+ return this.offlineEngine.renderOffline(inputs, this.offlineBlockSize);
1881
+ }
1882
+ onMeter(callback) {
1883
+ return this.realtimeNode.onMeter(callback);
1884
+ }
1885
+ onTelemetry(callback) {
1886
+ return this.realtimeNode.onTelemetry(callback);
1887
+ }
1888
+ pollTelemetry() {
1889
+ return this.realtimeNode.pollTelemetry();
1890
+ }
1891
+ destroy() {
1892
+ if (this.destroyed) {
1893
+ return;
1894
+ }
1895
+ this.destroyed = true;
1896
+ this.transport.stop();
1897
+ this.realtimeNode.pollTelemetry();
1898
+ this.realtimeNode.destroy();
1899
+ this.offlineEngine.destroy();
1900
+ }
1901
+ syncClips() {
1902
+ this.offlineEngine.setClips(Array.from(this.clips.values()));
1903
+ }
1904
+ syncMarkers() {
1905
+ this.offlineEngine.setMarkers(Array.from(this.markers.values()).sort((a, b) => a.ppq - b.ppq));
1906
+ }
1907
+ resolveParamId(nodeId, param) {
1908
+ if (typeof param === "number") {
1909
+ return param;
1910
+ }
1911
+ const byName = this.listParameters().find((info) => info.name === param);
1912
+ if (byName) {
1913
+ return byName.id;
1914
+ }
1915
+ return this.resolveTargetId(param || nodeId);
1916
+ }
1917
+ resolveTargetId(target) {
1918
+ if (typeof target === "number") {
1919
+ return target;
1920
+ }
1921
+ const parsed = Number.parseInt(target, 10);
1922
+ return Number.isFinite(parsed) ? parsed : 0;
1923
+ }
1924
+ curveCode(curve) {
1925
+ if (typeof curve === "number") {
1926
+ return curve;
1927
+ }
1928
+ return curve === "exponential" ? 1 : 0;
1929
+ }
1930
+ ppqToApproxSample(ppq) {
1931
+ return Math.max(0, Math.round(ppq * 60 / 120 * this.sampleRate));
1932
+ }
1933
+ };
1934
+ function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
1935
+ const scope = globalThis;
1936
+ if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
1937
+ throw new Error("AudioWorkletProcessor is not available in this context.");
1938
+ }
1939
+ const Base = scope.AudioWorkletProcessor;
1940
+ class RegisteredSonareWorkletProcessor extends Base {
1941
+ constructor(options) {
1942
+ super();
1943
+ const port = this.port;
1944
+ this.bridge = new SonareWorkletProcessor(options?.processorOptions ?? { sceneJson: "" }, {
1945
+ postMessage: (message) => port?.postMessage?.(message)
1946
+ });
1947
+ const onMessage = (event) => {
1948
+ if (isWorkletMessage(event.data)) {
1949
+ this.bridge.receiveMessage(event.data);
1950
+ }
1951
+ };
1952
+ if (port?.addEventListener) {
1953
+ port.addEventListener("message", onMessage);
1954
+ port.start?.();
1955
+ } else if (port) {
1956
+ port.onmessage = onMessage;
1957
+ }
1958
+ }
1959
+ process(inputs, outputs) {
1960
+ return this.bridge.process(inputs, outputs);
1961
+ }
1962
+ }
1963
+ scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
1964
+ }
1965
+ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-engine-processor") {
1966
+ const scope = globalThis;
1967
+ if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
1968
+ throw new Error("AudioWorkletProcessor is not available in this context.");
1969
+ }
1970
+ const Base = scope.AudioWorkletProcessor;
1971
+ class RegisteredSonareRealtimeEngineWorkletProcessor extends Base {
1972
+ constructor(options) {
1973
+ super();
1974
+ const port = this.port;
1975
+ const processorOptions = options?.processorOptions ?? {};
1976
+ if (processorOptions.runtimeTarget === "sonare-rt") {
1977
+ void this.initializeSonareRt(processorOptions, port);
1978
+ } else {
1979
+ this.bridge = new SonareRealtimeEngineWorkletProcessor(processorOptions, {
1980
+ postMessage: (message) => port?.postMessage?.(message),
1981
+ onMeter: (meter) => port?.postMessage?.(meter)
1982
+ });
1983
+ }
1984
+ const onMessage = (event) => {
1985
+ if (isEngineCommandRecord(event.data)) {
1986
+ this.bridge?.receiveCommand(event.data);
1987
+ }
1988
+ };
1989
+ if (port?.addEventListener) {
1990
+ port.addEventListener("message", onMessage);
1991
+ port.start?.();
1992
+ } else if (port) {
1993
+ port.onmessage = onMessage;
1994
+ }
1995
+ }
1996
+ process(inputs, outputs) {
1997
+ if (this.rtBridge) {
1998
+ return this.rtBridge.process(inputs, outputs);
1999
+ }
2000
+ if (this.bridge) {
2001
+ return this.bridge.process(inputs, outputs);
2002
+ }
2003
+ const output = outputs[0];
2004
+ for (const channel of output ?? []) {
2005
+ channel.fill(0);
2006
+ }
2007
+ return true;
2008
+ }
2009
+ async initializeSonareRt(options, port) {
2010
+ try {
2011
+ if (!options.rtModuleUrl) {
2012
+ throw new Error("rtModuleUrl is required for sonare-rt AudioWorklet runtime.");
2013
+ }
2014
+ const memory = new WebAssembly.Memory({ initial: 1024, maximum: 1024, shared: true });
2015
+ const globalFactory = globalThis.SonareRtModuleFactory;
2016
+ const moduleFactory = globalFactory ? { default: globalFactory } : await import(options.rtModuleUrl);
2017
+ const module2 = await moduleFactory.default({
2018
+ wasmMemory: memory,
2019
+ wasmBinary: options.rtWasmBinary,
2020
+ locateFile: (path) => options.rtModuleUrl.replace(/[^/]*$/, path)
2021
+ });
2022
+ this.rtBridge = new SonareRtRealtimeEngineRuntime({
2023
+ module: module2,
2024
+ memory,
2025
+ sampleRate: options.sampleRate,
2026
+ blockSize: options.blockSize,
2027
+ channelCount: options.channelCount,
2028
+ commandSharedBuffer: options.commandSharedBuffer,
2029
+ commandRingCapacity: options.commandRingCapacity,
2030
+ telemetrySharedBuffer: options.telemetrySharedBuffer,
2031
+ telemetryRingCapacity: options.telemetryRingCapacity
2032
+ });
2033
+ port?.postMessage?.({ type: "ready", runtimeTarget: "sonare-rt" });
2034
+ } catch (error) {
2035
+ port?.postMessage?.({
2036
+ type: "error",
2037
+ message: error instanceof Error ? error.message : String(error)
2038
+ });
2039
+ }
2040
+ }
2041
+ }
2042
+ scope.registerProcessor(name, RegisteredSonareRealtimeEngineWorkletProcessor);
2043
+ }
2044
+ export {
2045
+ SONARE_ENGINE_COMMAND_RECORD_BYTES,
2046
+ SONARE_ENGINE_RING_HEADER_INTS,
2047
+ SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
2048
+ SONARE_METER_RING_HEADER_INTS,
2049
+ SONARE_METER_RING_RECORD_FLOATS,
2050
+ SONARE_SPECTRUM_RING_HEADER_INTS,
2051
+ SonareEngine,
2052
+ SonareEngineCommandType,
2053
+ SonareEngineTelemetryError,
2054
+ SonareEngineTelemetryType,
2055
+ SonareRealtimeEngineNode,
2056
+ SonareRealtimeEngineWorkletProcessor,
2057
+ SonareRtRealtimeEngineRuntime,
2058
+ SonareWorkletProcessor,
2059
+ createSonareEngineCommandRingBuffer,
2060
+ createSonareEngineTelemetryRingBuffer,
2061
+ createSonareMeterRingBuffer,
2062
+ createSonareSpectrumRingBuffer,
2063
+ init,
2064
+ isInitialized,
2065
+ popSonareEngineCommandRingBuffer,
2066
+ pushSonareEngineCommandRingBuffer,
2067
+ readSonareEngineTelemetryRingBuffer,
2068
+ readSonareMeterRingBuffer,
2069
+ readSonareSpectrumRingBuffer,
2070
+ registerSonareRealtimeEngineWorkletProcessor,
2071
+ registerSonareWorkletProcessor,
2072
+ sonareEngineCommandRingBufferByteLength,
2073
+ sonareEngineTelemetryRingBufferByteLength,
2074
+ sonareMeterRingBufferByteLength,
2075
+ sonareSpectrumRingBufferByteLength,
2076
+ writeSonareEngineTelemetryRingBuffer
2077
+ };
2078
+ //# sourceMappingURL=worklet.js.map