@onda-lang/webaudio 0.7.5 → 0.8.1
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 +126 -14
- package/api.md +407 -0
- package/package.json +5 -3
- package/src/execution-output-ring.js +234 -0
- package/src/index.d.ts +75 -3
- package/src/index.js +472 -10
- package/src/worklet.js +433 -47
package/src/worklet.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXECUTION_OPERATION_EVENT,
|
|
3
|
+
EXECUTION_OPERATION_INIT,
|
|
4
|
+
EXECUTION_OPERATION_PROCESS,
|
|
5
|
+
EXECUTION_OPERATION_TRANSPORT,
|
|
6
|
+
openExecutionOutputRing,
|
|
7
|
+
writeExecutionOutputRing,
|
|
8
|
+
} from "./execution-output-ring.js";
|
|
9
|
+
|
|
1
10
|
const ONDA_PROCESS_BEGIN_BLOCK = 1 << 0;
|
|
2
11
|
const ONDA_PROCESS_END_BLOCK = 1 << 1;
|
|
12
|
+
const ONDA_INIT_PRESERVE_PINNED = 0;
|
|
13
|
+
const ONDA_INIT_FULL = 1;
|
|
3
14
|
const ONDA_AUDIO_WORKLET_PROCESSOR_NAME = "onda-wasm-processor";
|
|
4
15
|
const DEFAULT_EVENT_PAYLOAD_CAPACITY_BYTES = 64 * 1024;
|
|
16
|
+
const DEFAULT_DELEGATE_CAPACITY_BYTES = 64 * 1024;
|
|
17
|
+
const DEFAULT_PRINT_CAPACITY_BYTES = 64 * 1024;
|
|
18
|
+
const DELEGATE_BATCH_SIZE_BYTES = 20;
|
|
19
|
+
const PRINT_BATCH_SIZE_BYTES = 20;
|
|
20
|
+
const EXECUTION_OUTPUT_SIZE_BYTES = 12;
|
|
5
21
|
const HOST_LITTLE_ENDIAN = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
|
|
6
22
|
|
|
23
|
+
function exactI64(value) {
|
|
24
|
+
let integer;
|
|
25
|
+
if (typeof value === "bigint") {
|
|
26
|
+
integer = value;
|
|
27
|
+
} else if (typeof value === "number" && Number.isSafeInteger(value)) {
|
|
28
|
+
integer = BigInt(value);
|
|
29
|
+
} else if (typeof value === "string" && /^-?[0-9]+$/.test(value)) {
|
|
30
|
+
integer = BigInt(value);
|
|
31
|
+
} else {
|
|
32
|
+
throw new TypeError("i64 value must be a bigint, safe integer, or decimal integer string");
|
|
33
|
+
}
|
|
34
|
+
if (BigInt.asIntN(64, integer) !== integer) {
|
|
35
|
+
throw new RangeError(`i64 value '${integer}' is outside the signed 64-bit range`);
|
|
36
|
+
}
|
|
37
|
+
return integer;
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
8
41
|
constructor(options) {
|
|
9
42
|
super();
|
|
@@ -47,6 +80,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
47
80
|
this.stateBytes = null;
|
|
48
81
|
this.inputViews = [];
|
|
49
82
|
this.outputViews = [];
|
|
83
|
+
this.delegateRecordView = null;
|
|
84
|
+
this.printRecordView = null;
|
|
50
85
|
|
|
51
86
|
const heapBase = Number(
|
|
52
87
|
this.exports.__heap_base?.value ?? this.exports.__heap_base ?? 0,
|
|
@@ -64,6 +99,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
64
99
|
this.eventInfo = Array.isArray(metadata.metadata?.events)
|
|
65
100
|
? metadata.metadata.events
|
|
66
101
|
: [];
|
|
102
|
+
this.delegateInfo = Array.isArray(metadata.metadata?.delegates)
|
|
103
|
+
? metadata.metadata.delegates
|
|
104
|
+
: [];
|
|
105
|
+
this.logSiteInfo = Array.isArray(metadata.metadata?.log_sites)
|
|
106
|
+
? metadata.metadata.log_sites
|
|
107
|
+
: [];
|
|
67
108
|
this.controlOutputInfo = Array.isArray(metadata.metadata?.control_outputs)
|
|
68
109
|
? metadata.metadata.control_outputs
|
|
69
110
|
: [];
|
|
@@ -115,7 +156,6 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
115
156
|
this.outputPtrs = [];
|
|
116
157
|
this.outputCapacityFrames = 0;
|
|
117
158
|
this.blockCursor = 0;
|
|
118
|
-
this.executionFailed = false;
|
|
119
159
|
|
|
120
160
|
const paramBytes = Number(metadata.runtime?.param_size_bytes ?? 0);
|
|
121
161
|
if (!Number.isInteger(paramBytes) || paramBytes < 0) {
|
|
@@ -148,13 +188,101 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
148
188
|
this.eventPayloadPtr = this.eventPayloadCapacity
|
|
149
189
|
? this.alloc(this.eventPayloadCapacity, 8)
|
|
150
190
|
: 0;
|
|
191
|
+
this.delegateCapacity = this.configuredDelegateCapacity(
|
|
192
|
+
processorOptions.delegateCapacityBytes,
|
|
193
|
+
);
|
|
194
|
+
this.delegateStoragePtr = this.delegateInfo.length && this.delegateCapacity
|
|
195
|
+
? this.alloc(this.delegateCapacity, 8)
|
|
196
|
+
: 0;
|
|
197
|
+
this.delegateBatchPtr = this.delegateInfo.length
|
|
198
|
+
? this.alloc(DELEGATE_BATCH_SIZE_BYTES, 4)
|
|
199
|
+
: 0;
|
|
200
|
+
if (this.delegateBatchPtr) {
|
|
201
|
+
const view = new DataView(this.memory.buffer);
|
|
202
|
+
view.setUint32(this.delegateBatchPtr, this.delegateStoragePtr, true);
|
|
203
|
+
view.setUint32(this.delegateBatchPtr + 4, this.delegateCapacity, true);
|
|
204
|
+
view.setUint32(this.delegateBatchPtr + 8, 0, true);
|
|
205
|
+
view.setUint32(this.delegateBatchPtr + 12, 0, true);
|
|
206
|
+
view.setUint32(this.delegateBatchPtr + 16, 0, true);
|
|
207
|
+
}
|
|
208
|
+
this.printCapacity = this.configuredPrintCapacity(
|
|
209
|
+
processorOptions.printCapacityBytes,
|
|
210
|
+
);
|
|
211
|
+
this.printStoragePtr = this.logSiteInfo.length && this.printCapacity
|
|
212
|
+
? this.alloc(this.printCapacity, 8)
|
|
213
|
+
: 0;
|
|
214
|
+
this.printBatchPtr = this.logSiteInfo.length
|
|
215
|
+
? this.alloc(PRINT_BATCH_SIZE_BYTES, 4)
|
|
216
|
+
: 0;
|
|
217
|
+
if (this.printBatchPtr) {
|
|
218
|
+
const view = new DataView(this.memory.buffer);
|
|
219
|
+
view.setUint32(this.printBatchPtr, this.printStoragePtr, true);
|
|
220
|
+
view.setUint32(this.printBatchPtr + 4, this.printCapacity, true);
|
|
221
|
+
view.setUint32(this.printBatchPtr + 8, 0, true);
|
|
222
|
+
view.setUint32(this.printBatchPtr + 12, 0, true);
|
|
223
|
+
view.setUint32(this.printBatchPtr + 16, 0, true);
|
|
224
|
+
}
|
|
225
|
+
this.printCollectionEnabled = processorOptions.printCollectionEnabled === true
|
|
226
|
+
&& this.printStoragePtr !== 0;
|
|
227
|
+
this.printSubscriptionId = Number.isSafeInteger(processorOptions.printSubscriptionId)
|
|
228
|
+
? processorOptions.printSubscriptionId
|
|
229
|
+
: 0;
|
|
230
|
+
this.executionOutputPtr = this.delegateBatchPtr || this.printBatchPtr
|
|
231
|
+
? this.alloc(EXECUTION_OUTPUT_SIZE_BYTES, 4)
|
|
232
|
+
: 0;
|
|
233
|
+
if (this.executionOutputPtr) {
|
|
234
|
+
const view = new DataView(this.memory.buffer);
|
|
235
|
+
view.setUint32(this.executionOutputPtr, 0, true);
|
|
236
|
+
view.setUint32(
|
|
237
|
+
this.executionOutputPtr + 4,
|
|
238
|
+
this.printCollectionEnabled ? this.printBatchPtr : 0,
|
|
239
|
+
true,
|
|
240
|
+
);
|
|
241
|
+
view.setUint32(this.executionOutputPtr + 8, 0, true);
|
|
242
|
+
}
|
|
243
|
+
this.delegateCollectionEnabled = false;
|
|
244
|
+
this.delegateSubscriptionId = 0;
|
|
245
|
+
this.executionOutputRing = processorOptions.executionOutputRing === undefined
|
|
246
|
+
|| processorOptions.executionOutputRing === null
|
|
247
|
+
? null
|
|
248
|
+
: openExecutionOutputRing(processorOptions.executionOutputRing);
|
|
249
|
+
if (
|
|
250
|
+
this.executionOutputRing
|
|
251
|
+
&& (
|
|
252
|
+
this.executionOutputRing.delegateCapacity !== this.delegateCapacity
|
|
253
|
+
|| this.executionOutputRing.printCapacity !== this.printCapacity
|
|
254
|
+
)
|
|
255
|
+
) {
|
|
256
|
+
throw new Error("execution-output ring capacities do not match processor storage");
|
|
257
|
+
}
|
|
258
|
+
this.pendingDelegateDrops = 0;
|
|
259
|
+
this.pendingDelegateOverflow = 0;
|
|
260
|
+
this.pendingPrintDrops = 0;
|
|
261
|
+
this.pendingPrintOverflow = 0;
|
|
262
|
+
this.executionOutputEntry = {
|
|
263
|
+
operation: 0,
|
|
264
|
+
operationIndex: 0,
|
|
265
|
+
delegateSubscriptionId: 0,
|
|
266
|
+
delegateUsed: 0,
|
|
267
|
+
delegateRecordCount: 0,
|
|
268
|
+
delegateOverflowCount: 0,
|
|
269
|
+
delegateTransportDropCount: 0,
|
|
270
|
+
printSubscriptionId: 0,
|
|
271
|
+
printUsed: 0,
|
|
272
|
+
printRecordCount: 0,
|
|
273
|
+
printOverflowCount: 0,
|
|
274
|
+
printTransportDropCount: 0,
|
|
275
|
+
};
|
|
151
276
|
this.writeParamDefaults();
|
|
152
277
|
this.writeInitialParams(processorOptions.params ?? {});
|
|
153
278
|
this.ensureInputCapacity(this.blockSize);
|
|
154
279
|
this.ensureOutputCapacity(this.blockSize);
|
|
155
280
|
this.viewsReady = true;
|
|
156
281
|
this.refreshMemoryCache(true);
|
|
157
|
-
this.
|
|
282
|
+
this.invalidateState();
|
|
283
|
+
if (processorOptions.initialize === true) {
|
|
284
|
+
this.init(ONDA_INIT_FULL);
|
|
285
|
+
}
|
|
158
286
|
this.allocationLocked = true;
|
|
159
287
|
this.port.onmessage = (event) => this.handleMessage(event.data ?? {});
|
|
160
288
|
}
|
|
@@ -175,7 +303,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
175
303
|
|
|
176
304
|
alloc(size, align) {
|
|
177
305
|
if (this.allocationLocked) {
|
|
178
|
-
throw new Error("Onda worklet memory allocation is locked after
|
|
306
|
+
throw new Error("Onda worklet memory allocation is locked after construction");
|
|
179
307
|
}
|
|
180
308
|
const ptr = this.alignUp(this.heap, align);
|
|
181
309
|
const next = ptr + size;
|
|
@@ -212,6 +340,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
212
340
|
this.outputCapacityFrames,
|
|
213
341
|
);
|
|
214
342
|
});
|
|
343
|
+
this.delegateRecordView = this.delegateStoragePtr
|
|
344
|
+
? new Uint8Array(buffer, this.delegateStoragePtr, this.delegateCapacity)
|
|
345
|
+
: null;
|
|
346
|
+
this.printRecordView = this.printStoragePtr
|
|
347
|
+
? new Uint8Array(buffer, this.printStoragePtr, this.printCapacity)
|
|
348
|
+
: null;
|
|
215
349
|
}
|
|
216
350
|
|
|
217
351
|
memoryView() {
|
|
@@ -255,6 +389,32 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
255
389
|
return capacity;
|
|
256
390
|
}
|
|
257
391
|
|
|
392
|
+
configuredDelegateCapacity(configuredCapacity) {
|
|
393
|
+
const capacity = configuredCapacity
|
|
394
|
+
?? (this.delegateInfo.length ? DEFAULT_DELEGATE_CAPACITY_BYTES : 0);
|
|
395
|
+
if (
|
|
396
|
+
!Number.isSafeInteger(capacity)
|
|
397
|
+
|| capacity < 0
|
|
398
|
+
|| capacity > 0x7fff_ffff
|
|
399
|
+
) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
"delegate capacity must be an integer from 0 through 2147483647 bytes",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
return capacity;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
configuredPrintCapacity(configuredCapacity) {
|
|
408
|
+
const capacity = configuredCapacity
|
|
409
|
+
?? (this.logSiteInfo.length ? DEFAULT_PRINT_CAPACITY_BYTES : 0);
|
|
410
|
+
if (!Number.isSafeInteger(capacity) || capacity < 0 || capacity > 0x7fff_ffff) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
"print capacity must be an integer from 0 through 2147483647 bytes",
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
return capacity;
|
|
416
|
+
}
|
|
417
|
+
|
|
258
418
|
flattenAudioChannels(ports, kind) {
|
|
259
419
|
const channels = [];
|
|
260
420
|
for (const port of ports) {
|
|
@@ -349,24 +509,73 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
349
509
|
);
|
|
350
510
|
}
|
|
351
511
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
512
|
+
init(mode) {
|
|
513
|
+
if (mode !== ONDA_INIT_PRESERVE_PINNED && mode !== ONDA_INIT_FULL) {
|
|
514
|
+
throw new Error(`invalid Onda init mode '${String(mode)}'`);
|
|
515
|
+
}
|
|
516
|
+
if (mode === ONDA_INIT_PRESERVE_PINNED && !this.initialized) {
|
|
517
|
+
throw new Error("full initialization is required before preserving pinned state");
|
|
518
|
+
}
|
|
519
|
+
this.runInitialization(mode);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
invalidateState() {
|
|
523
|
+
this.initialized = false;
|
|
524
|
+
this.process = this.processPending;
|
|
355
525
|
this.blockCursor = 0;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
commitInitializedState(blockCursor) {
|
|
529
|
+
this.initialized = true;
|
|
530
|
+
this.process = this.processInitialized;
|
|
531
|
+
this.blockCursor = blockCursor;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
runInitialization(mode, afterInitialize) {
|
|
535
|
+
// Reinitializing state does not create a compile-block boundary. Retain
|
|
536
|
+
// the host-side position so the next process segment cannot synthesize an
|
|
537
|
+
// extra BEGIN_BLOCK or postpone the matching END_BLOCK.
|
|
538
|
+
const blockCursor = this.initialized ? this.blockCursor : 0;
|
|
539
|
+
// Generated initialization mutates the live image in place. Stop exposing
|
|
540
|
+
// it before entering Wasm so a failure, including one in snapshot overlay,
|
|
541
|
+
// leaves the processor on the silent pending path.
|
|
542
|
+
this.invalidateState();
|
|
543
|
+
this.refreshMemoryCache();
|
|
544
|
+
this.prepareExecutionOutput();
|
|
545
|
+
const status = this.exports.onda_processor_init(
|
|
546
|
+
this.paramsPtr,
|
|
547
|
+
this.statePtr,
|
|
548
|
+
mode,
|
|
549
|
+
this.bufferPointersPtr,
|
|
550
|
+
this.bufferFramesPtr,
|
|
551
|
+
this.bufferChannelsPtr,
|
|
552
|
+
this.bufferSampleRatesPtr,
|
|
553
|
+
this.executionOutputPtr,
|
|
360
554
|
);
|
|
555
|
+
// Generated failures retain diagnostic prints but clear delegates.
|
|
556
|
+
this.publishExecutionOutput(
|
|
557
|
+
EXECUTION_OPERATION_INIT,
|
|
558
|
+
0,
|
|
559
|
+
);
|
|
560
|
+
this.checkExecutionStatus(status, "processor init");
|
|
561
|
+
afterInitialize?.();
|
|
562
|
+
this.commitInitializedState(blockCursor);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
requireInitialized(operation) {
|
|
566
|
+
if (!this.initialized) {
|
|
567
|
+
throw new Error(`full initialization is required before ${operation}`);
|
|
568
|
+
}
|
|
361
569
|
}
|
|
362
570
|
|
|
363
571
|
checkExecutionStatus(status, operation) {
|
|
364
572
|
if (status === 0) return;
|
|
365
|
-
this.
|
|
573
|
+
this.invalidateState();
|
|
366
574
|
throw new Error(`${operation} failed with Onda execution status ${String(status)}`);
|
|
367
575
|
}
|
|
368
576
|
|
|
369
577
|
createSnapshot() {
|
|
578
|
+
this.requireInitialized("snapshot");
|
|
370
579
|
const snapshot = new Uint8Array(this.snapshotSizeBytes);
|
|
371
580
|
this.refreshMemoryCache();
|
|
372
581
|
const state = this.stateBytes;
|
|
@@ -391,20 +600,22 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
391
600
|
);
|
|
392
601
|
}
|
|
393
602
|
// The ABI restore base is a fresh post-init image, so scratch and
|
|
394
|
-
// control-mirror state never leak across a restore.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
603
|
+
// control-mirror state never leak across a restore. Initialization and
|
|
604
|
+
// overlay form one lifecycle transition: neither partial result is ready.
|
|
605
|
+
this.runInitialization(ONDA_INIT_FULL, () => {
|
|
606
|
+
const state = this.stateBytes;
|
|
607
|
+
for (const entry of this.snapshotInfo) {
|
|
608
|
+
const packedOffset = Number(entry.packed_snapshot_byte_offset);
|
|
609
|
+
const physicalOffset = Number(entry.physical_state_byte_offset);
|
|
610
|
+
const byteSize = Number(entry.byte_size);
|
|
611
|
+
this.validateSnapshotEntry(entry, packedOffset, physicalOffset, byteSize);
|
|
612
|
+
state.set(
|
|
613
|
+
snapshot.subarray(packedOffset, packedOffset + byteSize),
|
|
614
|
+
physicalOffset,
|
|
615
|
+
);
|
|
616
|
+
this.normalizeSnapshotIntegerRange(entry, state, physicalOffset, byteSize);
|
|
617
|
+
}
|
|
618
|
+
});
|
|
408
619
|
}
|
|
409
620
|
|
|
410
621
|
normalizeSnapshotIntegerRange(entry, state, offset, byteSize) {
|
|
@@ -494,8 +705,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
494
705
|
message.value,
|
|
495
706
|
);
|
|
496
707
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
497
|
-
} else if (message.type === "
|
|
498
|
-
this.
|
|
708
|
+
} else if (message.type === "init") {
|
|
709
|
+
this.init(message.mode);
|
|
499
710
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
500
711
|
} else if (message.type === "event") {
|
|
501
712
|
this.dispatchEvent(
|
|
@@ -525,6 +736,10 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
525
736
|
} else if (message.type === "restore-snapshot") {
|
|
526
737
|
this.restoreSnapshot(message.snapshot ?? message.bytes);
|
|
527
738
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
739
|
+
} else if (message.type === "delegate-subscription") {
|
|
740
|
+
this.setDelegateSubscription(message.enabled, message.subscriptionId);
|
|
741
|
+
} else if (message.type === "print-subscription") {
|
|
742
|
+
this.setPrintSubscription(message.enabled, message.subscriptionId);
|
|
528
743
|
} else {
|
|
529
744
|
throw new Error(`unknown Onda worklet operation '${String(message.type)}'`);
|
|
530
745
|
}
|
|
@@ -539,6 +754,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
539
754
|
}
|
|
540
755
|
|
|
541
756
|
dispatchEvent(selector, values) {
|
|
757
|
+
this.requireInitialized("event dispatch");
|
|
542
758
|
const eventId = Number.isInteger(selector)
|
|
543
759
|
? selector
|
|
544
760
|
: this.eventInfo.findIndex((event) => event.name === selector);
|
|
@@ -598,18 +814,22 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
598
814
|
if (typeof handler !== "function") {
|
|
599
815
|
throw new Error(`missing WebAssembly export '${event.export}'`);
|
|
600
816
|
}
|
|
601
|
-
this.
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
`event '${event.name}'`,
|
|
817
|
+
this.prepareExecutionOutput();
|
|
818
|
+
const status = handler(
|
|
819
|
+
this.eventPayloadPtr,
|
|
820
|
+
this.paramsPtr,
|
|
821
|
+
this.statePtr,
|
|
822
|
+
this.bufferPointersPtr,
|
|
823
|
+
this.bufferFramesPtr,
|
|
824
|
+
this.bufferChannelsPtr,
|
|
825
|
+
this.bufferSampleRatesPtr,
|
|
826
|
+
this.executionOutputPtr,
|
|
612
827
|
);
|
|
828
|
+
this.publishExecutionOutput(
|
|
829
|
+
EXECUTION_OPERATION_EVENT,
|
|
830
|
+
eventId,
|
|
831
|
+
);
|
|
832
|
+
this.checkExecutionStatus(status, `event '${event.name}'`);
|
|
613
833
|
}
|
|
614
834
|
|
|
615
835
|
eventValue(event, param, paramId, values) {
|
|
@@ -641,6 +861,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
641
861
|
}
|
|
642
862
|
|
|
643
863
|
readControlOutputs() {
|
|
864
|
+
this.requireInitialized("reading control outputs");
|
|
644
865
|
return Object.fromEntries(
|
|
645
866
|
this.controlOutputInfo.map((output) => [
|
|
646
867
|
output.name,
|
|
@@ -652,6 +873,142 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
652
873
|
);
|
|
653
874
|
}
|
|
654
875
|
|
|
876
|
+
saturatedAdd(lhs, rhs) {
|
|
877
|
+
return Math.min(0xffff_ffff, lhs + rhs);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
publishExecutionOutput(operation, operationIndex) {
|
|
881
|
+
if (!this.executionOutputRing) return;
|
|
882
|
+
if (
|
|
883
|
+
!this.delegateCollectionEnabled
|
|
884
|
+
&& !this.printCollectionEnabled
|
|
885
|
+
&& !this.pendingDelegateDrops
|
|
886
|
+
&& !this.pendingDelegateOverflow
|
|
887
|
+
&& !this.pendingPrintDrops
|
|
888
|
+
&& !this.pendingPrintOverflow
|
|
889
|
+
) return;
|
|
890
|
+
const view = this.memoryView();
|
|
891
|
+
const delegateUsed = this.delegateCollectionEnabled && this.delegateBatchPtr
|
|
892
|
+
? view.getUint32(this.delegateBatchPtr + 8, true)
|
|
893
|
+
: 0;
|
|
894
|
+
const delegateRecordCount = this.delegateCollectionEnabled && this.delegateBatchPtr
|
|
895
|
+
? view.getUint32(this.delegateBatchPtr + 12, true)
|
|
896
|
+
: 0;
|
|
897
|
+
const delegateOverflowCount = this.delegateCollectionEnabled && this.delegateBatchPtr
|
|
898
|
+
? view.getUint32(this.delegateBatchPtr + 16, true)
|
|
899
|
+
: 0;
|
|
900
|
+
const printUsed = this.printCollectionEnabled && this.printBatchPtr
|
|
901
|
+
? view.getUint32(this.printBatchPtr + 8, true)
|
|
902
|
+
: 0;
|
|
903
|
+
const printRecordCount = this.printCollectionEnabled && this.printBatchPtr
|
|
904
|
+
? view.getUint32(this.printBatchPtr + 12, true)
|
|
905
|
+
: 0;
|
|
906
|
+
const printOverflowCount = this.printCollectionEnabled && this.printBatchPtr
|
|
907
|
+
? view.getUint32(this.printBatchPtr + 16, true)
|
|
908
|
+
: 0;
|
|
909
|
+
if (delegateUsed > this.delegateCapacity) {
|
|
910
|
+
throw new Error("processor returned an invalid delegate byte count");
|
|
911
|
+
}
|
|
912
|
+
if (printUsed > this.printCapacity) {
|
|
913
|
+
throw new Error("processor returned an invalid print byte count");
|
|
914
|
+
}
|
|
915
|
+
const hasCurrent = delegateUsed
|
|
916
|
+
|| delegateRecordCount
|
|
917
|
+
|| delegateOverflowCount
|
|
918
|
+
|| printUsed
|
|
919
|
+
|| printRecordCount
|
|
920
|
+
|| printOverflowCount;
|
|
921
|
+
const hasPending = this.pendingDelegateDrops
|
|
922
|
+
|| this.pendingDelegateOverflow
|
|
923
|
+
|| this.pendingPrintDrops
|
|
924
|
+
|| this.pendingPrintOverflow;
|
|
925
|
+
if (!hasCurrent && !hasPending) return;
|
|
926
|
+
|
|
927
|
+
const entry = this.executionOutputEntry;
|
|
928
|
+
entry.operation = hasCurrent ? operation : EXECUTION_OPERATION_TRANSPORT;
|
|
929
|
+
entry.operationIndex = hasCurrent ? operationIndex : 0;
|
|
930
|
+
entry.delegateSubscriptionId = this.delegateSubscriptionId;
|
|
931
|
+
entry.delegateUsed = delegateUsed;
|
|
932
|
+
entry.delegateRecordCount = delegateRecordCount;
|
|
933
|
+
entry.delegateOverflowCount = this.saturatedAdd(
|
|
934
|
+
this.pendingDelegateOverflow,
|
|
935
|
+
delegateOverflowCount,
|
|
936
|
+
);
|
|
937
|
+
entry.delegateTransportDropCount = this.pendingDelegateDrops;
|
|
938
|
+
entry.printSubscriptionId = this.printSubscriptionId;
|
|
939
|
+
entry.printUsed = printUsed;
|
|
940
|
+
entry.printRecordCount = printRecordCount;
|
|
941
|
+
entry.printOverflowCount = this.saturatedAdd(
|
|
942
|
+
this.pendingPrintOverflow,
|
|
943
|
+
printOverflowCount,
|
|
944
|
+
);
|
|
945
|
+
entry.printTransportDropCount = this.pendingPrintDrops;
|
|
946
|
+
if (writeExecutionOutputRing(
|
|
947
|
+
this.executionOutputRing,
|
|
948
|
+
entry,
|
|
949
|
+
this.delegateRecordView,
|
|
950
|
+
this.printRecordView,
|
|
951
|
+
)) {
|
|
952
|
+
this.pendingDelegateDrops = 0;
|
|
953
|
+
this.pendingDelegateOverflow = 0;
|
|
954
|
+
this.pendingPrintDrops = 0;
|
|
955
|
+
this.pendingPrintOverflow = 0;
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
this.pendingDelegateDrops = this.saturatedAdd(
|
|
959
|
+
this.pendingDelegateDrops,
|
|
960
|
+
delegateRecordCount,
|
|
961
|
+
);
|
|
962
|
+
this.pendingDelegateOverflow = this.saturatedAdd(
|
|
963
|
+
this.pendingDelegateOverflow,
|
|
964
|
+
delegateOverflowCount,
|
|
965
|
+
);
|
|
966
|
+
this.pendingPrintDrops = this.saturatedAdd(
|
|
967
|
+
this.pendingPrintDrops,
|
|
968
|
+
printRecordCount,
|
|
969
|
+
);
|
|
970
|
+
this.pendingPrintOverflow = this.saturatedAdd(
|
|
971
|
+
this.pendingPrintOverflow,
|
|
972
|
+
printOverflowCount,
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
setDelegateSubscription(enabled, subscriptionId) {
|
|
977
|
+
this.delegateCollectionEnabled = enabled === true && this.delegateStoragePtr !== 0;
|
|
978
|
+
this.delegateSubscriptionId = Number.isSafeInteger(subscriptionId)
|
|
979
|
+
? subscriptionId
|
|
980
|
+
: 0;
|
|
981
|
+
this.pendingDelegateDrops = 0;
|
|
982
|
+
this.pendingDelegateOverflow = 0;
|
|
983
|
+
if (this.delegateBatchPtr) {
|
|
984
|
+
const view = this.memoryView();
|
|
985
|
+
this.resetRecordBatch(view, this.delegateBatchPtr);
|
|
986
|
+
view.setUint32(
|
|
987
|
+
this.executionOutputPtr,
|
|
988
|
+
this.delegateCollectionEnabled ? this.delegateBatchPtr : 0,
|
|
989
|
+
true,
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
setPrintSubscription(enabled, subscriptionId) {
|
|
995
|
+
this.printCollectionEnabled = enabled === true && this.printStoragePtr !== 0;
|
|
996
|
+
this.printSubscriptionId = Number.isSafeInteger(subscriptionId)
|
|
997
|
+
? subscriptionId
|
|
998
|
+
: 0;
|
|
999
|
+
this.pendingPrintDrops = 0;
|
|
1000
|
+
this.pendingPrintOverflow = 0;
|
|
1001
|
+
if (this.printBatchPtr) {
|
|
1002
|
+
const view = this.memoryView();
|
|
1003
|
+
this.resetRecordBatch(view, this.printBatchPtr);
|
|
1004
|
+
view.setUint32(
|
|
1005
|
+
this.executionOutputPtr + 4,
|
|
1006
|
+
this.printCollectionEnabled ? this.printBatchPtr : 0,
|
|
1007
|
+
true,
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
655
1012
|
bindInitialBuffers(options) {
|
|
656
1013
|
this.bufferInfo.forEach((buffer, bufferId) => {
|
|
657
1014
|
const supplied = this.initialBufferOption(options, buffer, bufferId);
|
|
@@ -804,7 +1161,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
804
1161
|
}
|
|
805
1162
|
|
|
806
1163
|
isFixedArray(info) {
|
|
807
|
-
return info?.
|
|
1164
|
+
return info?.is_array
|
|
1165
|
+
?? (info?.is_slice !== true && /\[[0-9]+\]$/.test(info?.type_repr ?? ""));
|
|
808
1166
|
}
|
|
809
1167
|
|
|
810
1168
|
decodeConstantScalar(scalar) {
|
|
@@ -889,7 +1247,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
889
1247
|
}
|
|
890
1248
|
} else if (scalar === "i64") {
|
|
891
1249
|
for (let index = 0; index < length; index += 1) {
|
|
892
|
-
target[index] =
|
|
1250
|
+
target[index] = exactI64(values[index]);
|
|
893
1251
|
}
|
|
894
1252
|
} else {
|
|
895
1253
|
target.set(values);
|
|
@@ -921,7 +1279,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
921
1279
|
writeScalar(address, scalar, value, view = this.memoryView()) {
|
|
922
1280
|
if (scalar === "bool") view.setUint8(address, value ? 1 : 0);
|
|
923
1281
|
else if (scalar === "i32") view.setInt32(address, Number(value), true);
|
|
924
|
-
else if (scalar === "i64") view.setBigInt64(address,
|
|
1282
|
+
else if (scalar === "i64") view.setBigInt64(address, exactI64(value), true);
|
|
925
1283
|
else if (scalar === "f32") view.setFloat32(address, Number(value), true);
|
|
926
1284
|
else if (scalar === "f64") view.setFloat64(address, Number(value), true);
|
|
927
1285
|
else throw new Error(`unsupported ABI scalar '${String(scalar)}'`);
|
|
@@ -1198,6 +1556,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1198
1556
|
}
|
|
1199
1557
|
|
|
1200
1558
|
invokeProcessSegment(startFrame, frames, flags) {
|
|
1559
|
+
this.prepareExecutionOutput();
|
|
1201
1560
|
return this.exports.onda_process(
|
|
1202
1561
|
this.statePtr,
|
|
1203
1562
|
this.paramsPtr,
|
|
@@ -1210,21 +1569,44 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1210
1569
|
this.bufferFramesPtr,
|
|
1211
1570
|
this.bufferChannelsPtr,
|
|
1212
1571
|
this.bufferSampleRatesPtr,
|
|
1572
|
+
this.executionOutputPtr,
|
|
1213
1573
|
);
|
|
1214
1574
|
}
|
|
1215
1575
|
|
|
1576
|
+
resetRecordBatch(view, batchPtr) {
|
|
1577
|
+
if (!batchPtr) return;
|
|
1578
|
+
view.setUint32(batchPtr + 8, 0, true);
|
|
1579
|
+
view.setUint32(batchPtr + 12, 0, true);
|
|
1580
|
+
view.setUint32(batchPtr + 16, 0, true);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
prepareExecutionOutput() {
|
|
1584
|
+
if (!this.executionOutputPtr) return;
|
|
1585
|
+
const view = this.memoryView();
|
|
1586
|
+
this.resetRecordBatch(
|
|
1587
|
+
view,
|
|
1588
|
+
view.getUint32(this.executionOutputPtr, true),
|
|
1589
|
+
);
|
|
1590
|
+
this.resetRecordBatch(
|
|
1591
|
+
view,
|
|
1592
|
+
view.getUint32(this.executionOutputPtr + 4, true),
|
|
1593
|
+
);
|
|
1594
|
+
view.setUint32(this.executionOutputPtr + 8, 0, true);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1216
1597
|
clearOutputs(outputs) {
|
|
1217
1598
|
for (const bus of outputs) {
|
|
1218
1599
|
for (const channel of bus) channel.fill(0);
|
|
1219
1600
|
}
|
|
1220
1601
|
}
|
|
1221
1602
|
|
|
1222
|
-
|
|
1603
|
+
processPending(_inputs, outputs) {
|
|
1604
|
+
this.clearOutputs(outputs);
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
processInitialized(inputs, outputs) {
|
|
1223
1609
|
this.refreshMemoryCache();
|
|
1224
|
-
if (this.executionFailed) {
|
|
1225
|
-
this.clearOutputs(outputs);
|
|
1226
|
-
return true;
|
|
1227
|
-
}
|
|
1228
1610
|
const frames = this.audioFrameCount(inputs, outputs);
|
|
1229
1611
|
|
|
1230
1612
|
let callbackOffset = 0;
|
|
@@ -1250,8 +1632,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1250
1632
|
segmentFrames,
|
|
1251
1633
|
flags,
|
|
1252
1634
|
);
|
|
1635
|
+
this.publishExecutionOutput(
|
|
1636
|
+
EXECUTION_OPERATION_PROCESS,
|
|
1637
|
+
0,
|
|
1638
|
+
);
|
|
1253
1639
|
if (status !== 0) {
|
|
1254
|
-
this.
|
|
1640
|
+
this.invalidateState();
|
|
1255
1641
|
this.clearOutputs(outputs);
|
|
1256
1642
|
this.port.postMessage({
|
|
1257
1643
|
type: "onda-error",
|