@onda-lang/webaudio 0.7.4 → 0.8.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 +124 -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 +456 -43
package/src/worklet.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
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
|
|
|
7
23
|
class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
@@ -47,6 +63,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
47
63
|
this.stateBytes = null;
|
|
48
64
|
this.inputViews = [];
|
|
49
65
|
this.outputViews = [];
|
|
66
|
+
this.delegateRecordView = null;
|
|
67
|
+
this.printRecordView = null;
|
|
50
68
|
|
|
51
69
|
const heapBase = Number(
|
|
52
70
|
this.exports.__heap_base?.value ?? this.exports.__heap_base ?? 0,
|
|
@@ -64,6 +82,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
64
82
|
this.eventInfo = Array.isArray(metadata.metadata?.events)
|
|
65
83
|
? metadata.metadata.events
|
|
66
84
|
: [];
|
|
85
|
+
this.delegateInfo = Array.isArray(metadata.metadata?.delegates)
|
|
86
|
+
? metadata.metadata.delegates
|
|
87
|
+
: [];
|
|
88
|
+
this.logSiteInfo = Array.isArray(metadata.metadata?.log_sites)
|
|
89
|
+
? metadata.metadata.log_sites
|
|
90
|
+
: [];
|
|
67
91
|
this.controlOutputInfo = Array.isArray(metadata.metadata?.control_outputs)
|
|
68
92
|
? metadata.metadata.control_outputs
|
|
69
93
|
: [];
|
|
@@ -115,7 +139,6 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
115
139
|
this.outputPtrs = [];
|
|
116
140
|
this.outputCapacityFrames = 0;
|
|
117
141
|
this.blockCursor = 0;
|
|
118
|
-
this.executionFailed = false;
|
|
119
142
|
|
|
120
143
|
const paramBytes = Number(metadata.runtime?.param_size_bytes ?? 0);
|
|
121
144
|
if (!Number.isInteger(paramBytes) || paramBytes < 0) {
|
|
@@ -148,13 +171,101 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
148
171
|
this.eventPayloadPtr = this.eventPayloadCapacity
|
|
149
172
|
? this.alloc(this.eventPayloadCapacity, 8)
|
|
150
173
|
: 0;
|
|
174
|
+
this.delegateCapacity = this.configuredDelegateCapacity(
|
|
175
|
+
processorOptions.delegateCapacityBytes,
|
|
176
|
+
);
|
|
177
|
+
this.delegateStoragePtr = this.delegateInfo.length && this.delegateCapacity
|
|
178
|
+
? this.alloc(this.delegateCapacity, 8)
|
|
179
|
+
: 0;
|
|
180
|
+
this.delegateBatchPtr = this.delegateInfo.length
|
|
181
|
+
? this.alloc(DELEGATE_BATCH_SIZE_BYTES, 4)
|
|
182
|
+
: 0;
|
|
183
|
+
if (this.delegateBatchPtr) {
|
|
184
|
+
const view = new DataView(this.memory.buffer);
|
|
185
|
+
view.setUint32(this.delegateBatchPtr, this.delegateStoragePtr, true);
|
|
186
|
+
view.setUint32(this.delegateBatchPtr + 4, this.delegateCapacity, true);
|
|
187
|
+
view.setUint32(this.delegateBatchPtr + 8, 0, true);
|
|
188
|
+
view.setUint32(this.delegateBatchPtr + 12, 0, true);
|
|
189
|
+
view.setUint32(this.delegateBatchPtr + 16, 0, true);
|
|
190
|
+
}
|
|
191
|
+
this.printCapacity = this.configuredPrintCapacity(
|
|
192
|
+
processorOptions.printCapacityBytes,
|
|
193
|
+
);
|
|
194
|
+
this.printStoragePtr = this.logSiteInfo.length && this.printCapacity
|
|
195
|
+
? this.alloc(this.printCapacity, 8)
|
|
196
|
+
: 0;
|
|
197
|
+
this.printBatchPtr = this.logSiteInfo.length
|
|
198
|
+
? this.alloc(PRINT_BATCH_SIZE_BYTES, 4)
|
|
199
|
+
: 0;
|
|
200
|
+
if (this.printBatchPtr) {
|
|
201
|
+
const view = new DataView(this.memory.buffer);
|
|
202
|
+
view.setUint32(this.printBatchPtr, this.printStoragePtr, true);
|
|
203
|
+
view.setUint32(this.printBatchPtr + 4, this.printCapacity, true);
|
|
204
|
+
view.setUint32(this.printBatchPtr + 8, 0, true);
|
|
205
|
+
view.setUint32(this.printBatchPtr + 12, 0, true);
|
|
206
|
+
view.setUint32(this.printBatchPtr + 16, 0, true);
|
|
207
|
+
}
|
|
208
|
+
this.printCollectionEnabled = processorOptions.printCollectionEnabled === true
|
|
209
|
+
&& this.printStoragePtr !== 0;
|
|
210
|
+
this.printSubscriptionId = Number.isSafeInteger(processorOptions.printSubscriptionId)
|
|
211
|
+
? processorOptions.printSubscriptionId
|
|
212
|
+
: 0;
|
|
213
|
+
this.executionOutputPtr = this.delegateBatchPtr || this.printBatchPtr
|
|
214
|
+
? this.alloc(EXECUTION_OUTPUT_SIZE_BYTES, 4)
|
|
215
|
+
: 0;
|
|
216
|
+
if (this.executionOutputPtr) {
|
|
217
|
+
const view = new DataView(this.memory.buffer);
|
|
218
|
+
view.setUint32(this.executionOutputPtr, 0, true);
|
|
219
|
+
view.setUint32(
|
|
220
|
+
this.executionOutputPtr + 4,
|
|
221
|
+
this.printCollectionEnabled ? this.printBatchPtr : 0,
|
|
222
|
+
true,
|
|
223
|
+
);
|
|
224
|
+
view.setUint32(this.executionOutputPtr + 8, 0, true);
|
|
225
|
+
}
|
|
226
|
+
this.delegateCollectionEnabled = false;
|
|
227
|
+
this.delegateSubscriptionId = 0;
|
|
228
|
+
this.executionOutputRing = processorOptions.executionOutputRing === undefined
|
|
229
|
+
|| processorOptions.executionOutputRing === null
|
|
230
|
+
? null
|
|
231
|
+
: openExecutionOutputRing(processorOptions.executionOutputRing);
|
|
232
|
+
if (
|
|
233
|
+
this.executionOutputRing
|
|
234
|
+
&& (
|
|
235
|
+
this.executionOutputRing.delegateCapacity !== this.delegateCapacity
|
|
236
|
+
|| this.executionOutputRing.printCapacity !== this.printCapacity
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
throw new Error("execution-output ring capacities do not match processor storage");
|
|
240
|
+
}
|
|
241
|
+
this.pendingDelegateDrops = 0;
|
|
242
|
+
this.pendingDelegateOverflow = 0;
|
|
243
|
+
this.pendingPrintDrops = 0;
|
|
244
|
+
this.pendingPrintOverflow = 0;
|
|
245
|
+
this.executionOutputEntry = {
|
|
246
|
+
operation: 0,
|
|
247
|
+
operationIndex: 0,
|
|
248
|
+
delegateSubscriptionId: 0,
|
|
249
|
+
delegateUsed: 0,
|
|
250
|
+
delegateRecordCount: 0,
|
|
251
|
+
delegateOverflowCount: 0,
|
|
252
|
+
delegateTransportDropCount: 0,
|
|
253
|
+
printSubscriptionId: 0,
|
|
254
|
+
printUsed: 0,
|
|
255
|
+
printRecordCount: 0,
|
|
256
|
+
printOverflowCount: 0,
|
|
257
|
+
printTransportDropCount: 0,
|
|
258
|
+
};
|
|
151
259
|
this.writeParamDefaults();
|
|
152
260
|
this.writeInitialParams(processorOptions.params ?? {});
|
|
153
261
|
this.ensureInputCapacity(this.blockSize);
|
|
154
262
|
this.ensureOutputCapacity(this.blockSize);
|
|
155
263
|
this.viewsReady = true;
|
|
156
264
|
this.refreshMemoryCache(true);
|
|
157
|
-
this.
|
|
265
|
+
this.invalidateState();
|
|
266
|
+
if (processorOptions.initialize === true) {
|
|
267
|
+
this.init(ONDA_INIT_FULL);
|
|
268
|
+
}
|
|
158
269
|
this.allocationLocked = true;
|
|
159
270
|
this.port.onmessage = (event) => this.handleMessage(event.data ?? {});
|
|
160
271
|
}
|
|
@@ -175,7 +286,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
175
286
|
|
|
176
287
|
alloc(size, align) {
|
|
177
288
|
if (this.allocationLocked) {
|
|
178
|
-
throw new Error("Onda worklet memory allocation is locked after
|
|
289
|
+
throw new Error("Onda worklet memory allocation is locked after construction");
|
|
179
290
|
}
|
|
180
291
|
const ptr = this.alignUp(this.heap, align);
|
|
181
292
|
const next = ptr + size;
|
|
@@ -212,6 +323,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
212
323
|
this.outputCapacityFrames,
|
|
213
324
|
);
|
|
214
325
|
});
|
|
326
|
+
this.delegateRecordView = this.delegateStoragePtr
|
|
327
|
+
? new Uint8Array(buffer, this.delegateStoragePtr, this.delegateCapacity)
|
|
328
|
+
: null;
|
|
329
|
+
this.printRecordView = this.printStoragePtr
|
|
330
|
+
? new Uint8Array(buffer, this.printStoragePtr, this.printCapacity)
|
|
331
|
+
: null;
|
|
215
332
|
}
|
|
216
333
|
|
|
217
334
|
memoryView() {
|
|
@@ -255,6 +372,32 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
255
372
|
return capacity;
|
|
256
373
|
}
|
|
257
374
|
|
|
375
|
+
configuredDelegateCapacity(configuredCapacity) {
|
|
376
|
+
const capacity = configuredCapacity
|
|
377
|
+
?? (this.delegateInfo.length ? DEFAULT_DELEGATE_CAPACITY_BYTES : 0);
|
|
378
|
+
if (
|
|
379
|
+
!Number.isSafeInteger(capacity)
|
|
380
|
+
|| capacity < 0
|
|
381
|
+
|| capacity > 0x7fff_ffff
|
|
382
|
+
) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
"delegate capacity must be an integer from 0 through 2147483647 bytes",
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return capacity;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
configuredPrintCapacity(configuredCapacity) {
|
|
391
|
+
const capacity = configuredCapacity
|
|
392
|
+
?? (this.logSiteInfo.length ? DEFAULT_PRINT_CAPACITY_BYTES : 0);
|
|
393
|
+
if (!Number.isSafeInteger(capacity) || capacity < 0 || capacity > 0x7fff_ffff) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"print capacity must be an integer from 0 through 2147483647 bytes",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return capacity;
|
|
399
|
+
}
|
|
400
|
+
|
|
258
401
|
flattenAudioChannels(ports, kind) {
|
|
259
402
|
const channels = [];
|
|
260
403
|
for (const port of ports) {
|
|
@@ -349,24 +492,73 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
349
492
|
);
|
|
350
493
|
}
|
|
351
494
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
495
|
+
init(mode) {
|
|
496
|
+
if (mode !== ONDA_INIT_PRESERVE_PINNED && mode !== ONDA_INIT_FULL) {
|
|
497
|
+
throw new Error(`invalid Onda init mode '${String(mode)}'`);
|
|
498
|
+
}
|
|
499
|
+
if (mode === ONDA_INIT_PRESERVE_PINNED && !this.initialized) {
|
|
500
|
+
throw new Error("full initialization is required before preserving pinned state");
|
|
501
|
+
}
|
|
502
|
+
this.runInitialization(mode);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
invalidateState() {
|
|
506
|
+
this.initialized = false;
|
|
507
|
+
this.process = this.processPending;
|
|
355
508
|
this.blockCursor = 0;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
commitInitializedState(blockCursor) {
|
|
512
|
+
this.initialized = true;
|
|
513
|
+
this.process = this.processInitialized;
|
|
514
|
+
this.blockCursor = blockCursor;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
runInitialization(mode, afterInitialize) {
|
|
518
|
+
// Reinitializing state does not create a compile-block boundary. Retain
|
|
519
|
+
// the host-side position so the next process segment cannot synthesize an
|
|
520
|
+
// extra BEGIN_BLOCK or postpone the matching END_BLOCK.
|
|
521
|
+
const blockCursor = this.initialized ? this.blockCursor : 0;
|
|
522
|
+
// Generated initialization mutates the live image in place. Stop exposing
|
|
523
|
+
// it before entering Wasm so a failure, including one in snapshot overlay,
|
|
524
|
+
// leaves the processor on the silent pending path.
|
|
525
|
+
this.invalidateState();
|
|
526
|
+
this.refreshMemoryCache();
|
|
527
|
+
this.prepareExecutionOutput();
|
|
528
|
+
const status = this.exports.onda_processor_init(
|
|
529
|
+
this.paramsPtr,
|
|
530
|
+
this.statePtr,
|
|
531
|
+
mode,
|
|
532
|
+
this.bufferPointersPtr,
|
|
533
|
+
this.bufferFramesPtr,
|
|
534
|
+
this.bufferChannelsPtr,
|
|
535
|
+
this.bufferSampleRatesPtr,
|
|
536
|
+
this.executionOutputPtr,
|
|
360
537
|
);
|
|
538
|
+
// Generated failures retain diagnostic prints but clear delegates.
|
|
539
|
+
this.publishExecutionOutput(
|
|
540
|
+
EXECUTION_OPERATION_INIT,
|
|
541
|
+
0,
|
|
542
|
+
);
|
|
543
|
+
this.checkExecutionStatus(status, "processor init");
|
|
544
|
+
afterInitialize?.();
|
|
545
|
+
this.commitInitializedState(blockCursor);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
requireInitialized(operation) {
|
|
549
|
+
if (!this.initialized) {
|
|
550
|
+
throw new Error(`full initialization is required before ${operation}`);
|
|
551
|
+
}
|
|
361
552
|
}
|
|
362
553
|
|
|
363
554
|
checkExecutionStatus(status, operation) {
|
|
364
555
|
if (status === 0) return;
|
|
365
|
-
this.
|
|
556
|
+
this.invalidateState();
|
|
366
557
|
throw new Error(`${operation} failed with Onda execution status ${String(status)}`);
|
|
367
558
|
}
|
|
368
559
|
|
|
369
560
|
createSnapshot() {
|
|
561
|
+
this.requireInitialized("snapshot");
|
|
370
562
|
const snapshot = new Uint8Array(this.snapshotSizeBytes);
|
|
371
563
|
this.refreshMemoryCache();
|
|
372
564
|
const state = this.stateBytes;
|
|
@@ -391,19 +583,65 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
391
583
|
);
|
|
392
584
|
}
|
|
393
585
|
// 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
|
-
|
|
586
|
+
// control-mirror state never leak across a restore. Initialization and
|
|
587
|
+
// overlay form one lifecycle transition: neither partial result is ready.
|
|
588
|
+
this.runInitialization(ONDA_INIT_FULL, () => {
|
|
589
|
+
const state = this.stateBytes;
|
|
590
|
+
for (const entry of this.snapshotInfo) {
|
|
591
|
+
const packedOffset = Number(entry.packed_snapshot_byte_offset);
|
|
592
|
+
const physicalOffset = Number(entry.physical_state_byte_offset);
|
|
593
|
+
const byteSize = Number(entry.byte_size);
|
|
594
|
+
this.validateSnapshotEntry(entry, packedOffset, physicalOffset, byteSize);
|
|
595
|
+
state.set(
|
|
596
|
+
snapshot.subarray(packedOffset, packedOffset + byteSize),
|
|
597
|
+
physicalOffset,
|
|
598
|
+
);
|
|
599
|
+
this.normalizeSnapshotIntegerRange(entry, state, physicalOffset, byteSize);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
normalizeSnapshotIntegerRange(entry, state, offset, byteSize) {
|
|
605
|
+
const range = entry.integer_range;
|
|
606
|
+
if (range === null || range === undefined) return;
|
|
607
|
+
if (entry.array_len !== 1 || (entry.scalar !== "i32" && entry.scalar !== "i64")) {
|
|
608
|
+
throw new Error(`state '${String(entry.name)}' has an invalid integer range`);
|
|
609
|
+
}
|
|
610
|
+
const mode = range.mode;
|
|
611
|
+
if (mode !== "clamp" && mode !== "wrap") {
|
|
612
|
+
throw new Error(`state '${String(entry.name)}' has an invalid integer range mode`);
|
|
613
|
+
}
|
|
614
|
+
const view = new DataView(state.buffer, state.byteOffset + offset, byteSize);
|
|
615
|
+
if (entry.scalar === "i32") {
|
|
616
|
+
const min = Number(range.min?.value);
|
|
617
|
+
const max = Number(range.max?.value);
|
|
618
|
+
if (!Number.isInteger(min) || !Number.isInteger(max) || min > max || byteSize !== 4) {
|
|
619
|
+
throw new Error(`state '${String(entry.name)}' has invalid i32 range bounds`);
|
|
620
|
+
}
|
|
621
|
+
const value = view.getInt32(0, true);
|
|
622
|
+
const normalized = mode === "clamp"
|
|
623
|
+
? Math.min(Math.max(value, min), max)
|
|
624
|
+
: min + (((value - min) % (max - min + 1)) + (max - min + 1)) % (max - min + 1);
|
|
625
|
+
view.setInt32(0, normalized, true);
|
|
626
|
+
return;
|
|
406
627
|
}
|
|
628
|
+
let min;
|
|
629
|
+
let max;
|
|
630
|
+
try {
|
|
631
|
+
min = BigInt(range.min?.value);
|
|
632
|
+
max = BigInt(range.max?.value);
|
|
633
|
+
} catch {
|
|
634
|
+
throw new Error(`state '${String(entry.name)}' has invalid i64 range bounds`);
|
|
635
|
+
}
|
|
636
|
+
if (min > max || byteSize !== 8) {
|
|
637
|
+
throw new Error(`state '${String(entry.name)}' has invalid i64 range bounds`);
|
|
638
|
+
}
|
|
639
|
+
const value = view.getBigInt64(0, true);
|
|
640
|
+
const width = max - min + 1n;
|
|
641
|
+
const normalized = mode === "clamp"
|
|
642
|
+
? (value < min ? min : (value > max ? max : value))
|
|
643
|
+
: min + ((value - min) % width + width) % width;
|
|
644
|
+
view.setBigInt64(0, normalized, true);
|
|
407
645
|
}
|
|
408
646
|
|
|
409
647
|
snapshotBytes(value) {
|
|
@@ -450,8 +688,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
450
688
|
message.value,
|
|
451
689
|
);
|
|
452
690
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
453
|
-
} else if (message.type === "
|
|
454
|
-
this.
|
|
691
|
+
} else if (message.type === "init") {
|
|
692
|
+
this.init(message.mode);
|
|
455
693
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
456
694
|
} else if (message.type === "event") {
|
|
457
695
|
this.dispatchEvent(
|
|
@@ -481,6 +719,10 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
481
719
|
} else if (message.type === "restore-snapshot") {
|
|
482
720
|
this.restoreSnapshot(message.snapshot ?? message.bytes);
|
|
483
721
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
722
|
+
} else if (message.type === "delegate-subscription") {
|
|
723
|
+
this.setDelegateSubscription(message.enabled, message.subscriptionId);
|
|
724
|
+
} else if (message.type === "print-subscription") {
|
|
725
|
+
this.setPrintSubscription(message.enabled, message.subscriptionId);
|
|
484
726
|
} else {
|
|
485
727
|
throw new Error(`unknown Onda worklet operation '${String(message.type)}'`);
|
|
486
728
|
}
|
|
@@ -495,6 +737,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
495
737
|
}
|
|
496
738
|
|
|
497
739
|
dispatchEvent(selector, values) {
|
|
740
|
+
this.requireInitialized("event dispatch");
|
|
498
741
|
const eventId = Number.isInteger(selector)
|
|
499
742
|
? selector
|
|
500
743
|
: this.eventInfo.findIndex((event) => event.name === selector);
|
|
@@ -554,18 +797,22 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
554
797
|
if (typeof handler !== "function") {
|
|
555
798
|
throw new Error(`missing WebAssembly export '${event.export}'`);
|
|
556
799
|
}
|
|
557
|
-
this.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
`event '${event.name}'`,
|
|
800
|
+
this.prepareExecutionOutput();
|
|
801
|
+
const status = handler(
|
|
802
|
+
this.eventPayloadPtr,
|
|
803
|
+
this.paramsPtr,
|
|
804
|
+
this.statePtr,
|
|
805
|
+
this.bufferPointersPtr,
|
|
806
|
+
this.bufferFramesPtr,
|
|
807
|
+
this.bufferChannelsPtr,
|
|
808
|
+
this.bufferSampleRatesPtr,
|
|
809
|
+
this.executionOutputPtr,
|
|
568
810
|
);
|
|
811
|
+
this.publishExecutionOutput(
|
|
812
|
+
EXECUTION_OPERATION_EVENT,
|
|
813
|
+
eventId,
|
|
814
|
+
);
|
|
815
|
+
this.checkExecutionStatus(status, `event '${event.name}'`);
|
|
569
816
|
}
|
|
570
817
|
|
|
571
818
|
eventValue(event, param, paramId, values) {
|
|
@@ -597,6 +844,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
597
844
|
}
|
|
598
845
|
|
|
599
846
|
readControlOutputs() {
|
|
847
|
+
this.requireInitialized("reading control outputs");
|
|
600
848
|
return Object.fromEntries(
|
|
601
849
|
this.controlOutputInfo.map((output) => [
|
|
602
850
|
output.name,
|
|
@@ -608,6 +856,142 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
608
856
|
);
|
|
609
857
|
}
|
|
610
858
|
|
|
859
|
+
saturatedAdd(lhs, rhs) {
|
|
860
|
+
return Math.min(0xffff_ffff, lhs + rhs);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
publishExecutionOutput(operation, operationIndex) {
|
|
864
|
+
if (!this.executionOutputRing) return;
|
|
865
|
+
if (
|
|
866
|
+
!this.delegateCollectionEnabled
|
|
867
|
+
&& !this.printCollectionEnabled
|
|
868
|
+
&& !this.pendingDelegateDrops
|
|
869
|
+
&& !this.pendingDelegateOverflow
|
|
870
|
+
&& !this.pendingPrintDrops
|
|
871
|
+
&& !this.pendingPrintOverflow
|
|
872
|
+
) return;
|
|
873
|
+
const view = this.memoryView();
|
|
874
|
+
const delegateUsed = this.delegateCollectionEnabled && this.delegateBatchPtr
|
|
875
|
+
? view.getUint32(this.delegateBatchPtr + 8, true)
|
|
876
|
+
: 0;
|
|
877
|
+
const delegateRecordCount = this.delegateCollectionEnabled && this.delegateBatchPtr
|
|
878
|
+
? view.getUint32(this.delegateBatchPtr + 12, true)
|
|
879
|
+
: 0;
|
|
880
|
+
const delegateOverflowCount = this.delegateCollectionEnabled && this.delegateBatchPtr
|
|
881
|
+
? view.getUint32(this.delegateBatchPtr + 16, true)
|
|
882
|
+
: 0;
|
|
883
|
+
const printUsed = this.printCollectionEnabled && this.printBatchPtr
|
|
884
|
+
? view.getUint32(this.printBatchPtr + 8, true)
|
|
885
|
+
: 0;
|
|
886
|
+
const printRecordCount = this.printCollectionEnabled && this.printBatchPtr
|
|
887
|
+
? view.getUint32(this.printBatchPtr + 12, true)
|
|
888
|
+
: 0;
|
|
889
|
+
const printOverflowCount = this.printCollectionEnabled && this.printBatchPtr
|
|
890
|
+
? view.getUint32(this.printBatchPtr + 16, true)
|
|
891
|
+
: 0;
|
|
892
|
+
if (delegateUsed > this.delegateCapacity) {
|
|
893
|
+
throw new Error("processor returned an invalid delegate byte count");
|
|
894
|
+
}
|
|
895
|
+
if (printUsed > this.printCapacity) {
|
|
896
|
+
throw new Error("processor returned an invalid print byte count");
|
|
897
|
+
}
|
|
898
|
+
const hasCurrent = delegateUsed
|
|
899
|
+
|| delegateRecordCount
|
|
900
|
+
|| delegateOverflowCount
|
|
901
|
+
|| printUsed
|
|
902
|
+
|| printRecordCount
|
|
903
|
+
|| printOverflowCount;
|
|
904
|
+
const hasPending = this.pendingDelegateDrops
|
|
905
|
+
|| this.pendingDelegateOverflow
|
|
906
|
+
|| this.pendingPrintDrops
|
|
907
|
+
|| this.pendingPrintOverflow;
|
|
908
|
+
if (!hasCurrent && !hasPending) return;
|
|
909
|
+
|
|
910
|
+
const entry = this.executionOutputEntry;
|
|
911
|
+
entry.operation = hasCurrent ? operation : EXECUTION_OPERATION_TRANSPORT;
|
|
912
|
+
entry.operationIndex = hasCurrent ? operationIndex : 0;
|
|
913
|
+
entry.delegateSubscriptionId = this.delegateSubscriptionId;
|
|
914
|
+
entry.delegateUsed = delegateUsed;
|
|
915
|
+
entry.delegateRecordCount = delegateRecordCount;
|
|
916
|
+
entry.delegateOverflowCount = this.saturatedAdd(
|
|
917
|
+
this.pendingDelegateOverflow,
|
|
918
|
+
delegateOverflowCount,
|
|
919
|
+
);
|
|
920
|
+
entry.delegateTransportDropCount = this.pendingDelegateDrops;
|
|
921
|
+
entry.printSubscriptionId = this.printSubscriptionId;
|
|
922
|
+
entry.printUsed = printUsed;
|
|
923
|
+
entry.printRecordCount = printRecordCount;
|
|
924
|
+
entry.printOverflowCount = this.saturatedAdd(
|
|
925
|
+
this.pendingPrintOverflow,
|
|
926
|
+
printOverflowCount,
|
|
927
|
+
);
|
|
928
|
+
entry.printTransportDropCount = this.pendingPrintDrops;
|
|
929
|
+
if (writeExecutionOutputRing(
|
|
930
|
+
this.executionOutputRing,
|
|
931
|
+
entry,
|
|
932
|
+
this.delegateRecordView,
|
|
933
|
+
this.printRecordView,
|
|
934
|
+
)) {
|
|
935
|
+
this.pendingDelegateDrops = 0;
|
|
936
|
+
this.pendingDelegateOverflow = 0;
|
|
937
|
+
this.pendingPrintDrops = 0;
|
|
938
|
+
this.pendingPrintOverflow = 0;
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
this.pendingDelegateDrops = this.saturatedAdd(
|
|
942
|
+
this.pendingDelegateDrops,
|
|
943
|
+
delegateRecordCount,
|
|
944
|
+
);
|
|
945
|
+
this.pendingDelegateOverflow = this.saturatedAdd(
|
|
946
|
+
this.pendingDelegateOverflow,
|
|
947
|
+
delegateOverflowCount,
|
|
948
|
+
);
|
|
949
|
+
this.pendingPrintDrops = this.saturatedAdd(
|
|
950
|
+
this.pendingPrintDrops,
|
|
951
|
+
printRecordCount,
|
|
952
|
+
);
|
|
953
|
+
this.pendingPrintOverflow = this.saturatedAdd(
|
|
954
|
+
this.pendingPrintOverflow,
|
|
955
|
+
printOverflowCount,
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
setDelegateSubscription(enabled, subscriptionId) {
|
|
960
|
+
this.delegateCollectionEnabled = enabled === true && this.delegateStoragePtr !== 0;
|
|
961
|
+
this.delegateSubscriptionId = Number.isSafeInteger(subscriptionId)
|
|
962
|
+
? subscriptionId
|
|
963
|
+
: 0;
|
|
964
|
+
this.pendingDelegateDrops = 0;
|
|
965
|
+
this.pendingDelegateOverflow = 0;
|
|
966
|
+
if (this.delegateBatchPtr) {
|
|
967
|
+
const view = this.memoryView();
|
|
968
|
+
this.resetRecordBatch(view, this.delegateBatchPtr);
|
|
969
|
+
view.setUint32(
|
|
970
|
+
this.executionOutputPtr,
|
|
971
|
+
this.delegateCollectionEnabled ? this.delegateBatchPtr : 0,
|
|
972
|
+
true,
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
setPrintSubscription(enabled, subscriptionId) {
|
|
978
|
+
this.printCollectionEnabled = enabled === true && this.printStoragePtr !== 0;
|
|
979
|
+
this.printSubscriptionId = Number.isSafeInteger(subscriptionId)
|
|
980
|
+
? subscriptionId
|
|
981
|
+
: 0;
|
|
982
|
+
this.pendingPrintDrops = 0;
|
|
983
|
+
this.pendingPrintOverflow = 0;
|
|
984
|
+
if (this.printBatchPtr) {
|
|
985
|
+
const view = this.memoryView();
|
|
986
|
+
this.resetRecordBatch(view, this.printBatchPtr);
|
|
987
|
+
view.setUint32(
|
|
988
|
+
this.executionOutputPtr + 4,
|
|
989
|
+
this.printCollectionEnabled ? this.printBatchPtr : 0,
|
|
990
|
+
true,
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
611
995
|
bindInitialBuffers(options) {
|
|
612
996
|
this.bufferInfo.forEach((buffer, bufferId) => {
|
|
613
997
|
const supplied = this.initialBufferOption(options, buffer, bufferId);
|
|
@@ -760,7 +1144,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
760
1144
|
}
|
|
761
1145
|
|
|
762
1146
|
isFixedArray(info) {
|
|
763
|
-
return info?.
|
|
1147
|
+
return info?.is_array
|
|
1148
|
+
?? (info?.is_slice !== true && /\[[0-9]+\]$/.test(info?.type_repr ?? ""));
|
|
764
1149
|
}
|
|
765
1150
|
|
|
766
1151
|
decodeConstantScalar(scalar) {
|
|
@@ -1154,6 +1539,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1154
1539
|
}
|
|
1155
1540
|
|
|
1156
1541
|
invokeProcessSegment(startFrame, frames, flags) {
|
|
1542
|
+
this.prepareExecutionOutput();
|
|
1157
1543
|
return this.exports.onda_process(
|
|
1158
1544
|
this.statePtr,
|
|
1159
1545
|
this.paramsPtr,
|
|
@@ -1166,21 +1552,44 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1166
1552
|
this.bufferFramesPtr,
|
|
1167
1553
|
this.bufferChannelsPtr,
|
|
1168
1554
|
this.bufferSampleRatesPtr,
|
|
1555
|
+
this.executionOutputPtr,
|
|
1169
1556
|
);
|
|
1170
1557
|
}
|
|
1171
1558
|
|
|
1559
|
+
resetRecordBatch(view, batchPtr) {
|
|
1560
|
+
if (!batchPtr) return;
|
|
1561
|
+
view.setUint32(batchPtr + 8, 0, true);
|
|
1562
|
+
view.setUint32(batchPtr + 12, 0, true);
|
|
1563
|
+
view.setUint32(batchPtr + 16, 0, true);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
prepareExecutionOutput() {
|
|
1567
|
+
if (!this.executionOutputPtr) return;
|
|
1568
|
+
const view = this.memoryView();
|
|
1569
|
+
this.resetRecordBatch(
|
|
1570
|
+
view,
|
|
1571
|
+
view.getUint32(this.executionOutputPtr, true),
|
|
1572
|
+
);
|
|
1573
|
+
this.resetRecordBatch(
|
|
1574
|
+
view,
|
|
1575
|
+
view.getUint32(this.executionOutputPtr + 4, true),
|
|
1576
|
+
);
|
|
1577
|
+
view.setUint32(this.executionOutputPtr + 8, 0, true);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1172
1580
|
clearOutputs(outputs) {
|
|
1173
1581
|
for (const bus of outputs) {
|
|
1174
1582
|
for (const channel of bus) channel.fill(0);
|
|
1175
1583
|
}
|
|
1176
1584
|
}
|
|
1177
1585
|
|
|
1178
|
-
|
|
1586
|
+
processPending(_inputs, outputs) {
|
|
1587
|
+
this.clearOutputs(outputs);
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
processInitialized(inputs, outputs) {
|
|
1179
1592
|
this.refreshMemoryCache();
|
|
1180
|
-
if (this.executionFailed) {
|
|
1181
|
-
this.clearOutputs(outputs);
|
|
1182
|
-
return true;
|
|
1183
|
-
}
|
|
1184
1593
|
const frames = this.audioFrameCount(inputs, outputs);
|
|
1185
1594
|
|
|
1186
1595
|
let callbackOffset = 0;
|
|
@@ -1206,8 +1615,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1206
1615
|
segmentFrames,
|
|
1207
1616
|
flags,
|
|
1208
1617
|
);
|
|
1618
|
+
this.publishExecutionOutput(
|
|
1619
|
+
EXECUTION_OPERATION_PROCESS,
|
|
1620
|
+
0,
|
|
1621
|
+
);
|
|
1209
1622
|
if (status !== 0) {
|
|
1210
|
-
this.
|
|
1623
|
+
this.invalidateState();
|
|
1211
1624
|
this.clearOutputs(outputs);
|
|
1212
1625
|
this.port.postMessage({
|
|
1213
1626
|
type: "onda-error",
|