@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.
- package/README.md +164 -2
- package/dist/index.d.ts +1122 -18
- package/dist/index.js +1124 -14
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +2 -0
- package/dist/sonare-rt.js +2 -0
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +447 -0
- package/dist/worklet.js +2078 -0
- package/dist/worklet.js.map +1 -0
- package/package.json +14 -4
- package/src/index.ts +1895 -63
- package/src/public_types.ts +451 -0
- package/src/sonare-rt.d.ts +93 -0
- package/src/sonare.js.d.ts +710 -2
- package/src/stream_types.ts +35 -0
- package/src/wasm_types.ts +695 -2
- package/src/worklet.ts +2140 -0
package/dist/worklet.js
ADDED
|
@@ -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
|