@onda-lang/webaudio 0.7.5 → 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/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.reset();
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 initialization");
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
- reset() {
353
- this.refreshMemoryCache();
354
- this.stateBytes.fill(0);
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
- this.executionFailed = false;
357
- this.checkExecutionStatus(
358
- this.exports.onda_init(this.paramsPtr, this.statePtr),
359
- "processor init",
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.executionFailed = true;
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,20 +583,22 @@ 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
- this.reset();
396
- const state = this.stateBytes;
397
- for (const entry of this.snapshotInfo) {
398
- const packedOffset = Number(entry.packed_snapshot_byte_offset);
399
- const physicalOffset = Number(entry.physical_state_byte_offset);
400
- const byteSize = Number(entry.byte_size);
401
- this.validateSnapshotEntry(entry, packedOffset, physicalOffset, byteSize);
402
- state.set(
403
- snapshot.subarray(packedOffset, packedOffset + byteSize),
404
- physicalOffset,
405
- );
406
- this.normalizeSnapshotIntegerRange(entry, state, physicalOffset, byteSize);
407
- }
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
+ });
408
602
  }
409
603
 
410
604
  normalizeSnapshotIntegerRange(entry, state, offset, byteSize) {
@@ -494,8 +688,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
494
688
  message.value,
495
689
  );
496
690
  this.postResponse(message, { type: "onda-ok", operation: message.type });
497
- } else if (message.type === "reset") {
498
- this.reset();
691
+ } else if (message.type === "init") {
692
+ this.init(message.mode);
499
693
  this.postResponse(message, { type: "onda-ok", operation: message.type });
500
694
  } else if (message.type === "event") {
501
695
  this.dispatchEvent(
@@ -525,6 +719,10 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
525
719
  } else if (message.type === "restore-snapshot") {
526
720
  this.restoreSnapshot(message.snapshot ?? message.bytes);
527
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);
528
726
  } else {
529
727
  throw new Error(`unknown Onda worklet operation '${String(message.type)}'`);
530
728
  }
@@ -539,6 +737,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
539
737
  }
540
738
 
541
739
  dispatchEvent(selector, values) {
740
+ this.requireInitialized("event dispatch");
542
741
  const eventId = Number.isInteger(selector)
543
742
  ? selector
544
743
  : this.eventInfo.findIndex((event) => event.name === selector);
@@ -598,18 +797,22 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
598
797
  if (typeof handler !== "function") {
599
798
  throw new Error(`missing WebAssembly export '${event.export}'`);
600
799
  }
601
- this.checkExecutionStatus(
602
- handler(
603
- this.eventPayloadPtr,
604
- this.paramsPtr,
605
- this.statePtr,
606
- this.bufferPointersPtr,
607
- this.bufferFramesPtr,
608
- this.bufferChannelsPtr,
609
- this.bufferSampleRatesPtr,
610
- ),
611
- `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,
612
810
  );
811
+ this.publishExecutionOutput(
812
+ EXECUTION_OPERATION_EVENT,
813
+ eventId,
814
+ );
815
+ this.checkExecutionStatus(status, `event '${event.name}'`);
613
816
  }
614
817
 
615
818
  eventValue(event, param, paramId, values) {
@@ -641,6 +844,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
641
844
  }
642
845
 
643
846
  readControlOutputs() {
847
+ this.requireInitialized("reading control outputs");
644
848
  return Object.fromEntries(
645
849
  this.controlOutputInfo.map((output) => [
646
850
  output.name,
@@ -652,6 +856,142 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
652
856
  );
653
857
  }
654
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
+
655
995
  bindInitialBuffers(options) {
656
996
  this.bufferInfo.forEach((buffer, bufferId) => {
657
997
  const supplied = this.initialBufferOption(options, buffer, bufferId);
@@ -804,7 +1144,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
804
1144
  }
805
1145
 
806
1146
  isFixedArray(info) {
807
- return info?.is_slice !== true && /\[[0-9]+\]$/.test(info?.type_repr ?? "");
1147
+ return info?.is_array
1148
+ ?? (info?.is_slice !== true && /\[[0-9]+\]$/.test(info?.type_repr ?? ""));
808
1149
  }
809
1150
 
810
1151
  decodeConstantScalar(scalar) {
@@ -1198,6 +1539,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
1198
1539
  }
1199
1540
 
1200
1541
  invokeProcessSegment(startFrame, frames, flags) {
1542
+ this.prepareExecutionOutput();
1201
1543
  return this.exports.onda_process(
1202
1544
  this.statePtr,
1203
1545
  this.paramsPtr,
@@ -1210,7 +1552,29 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
1210
1552
  this.bufferFramesPtr,
1211
1553
  this.bufferChannelsPtr,
1212
1554
  this.bufferSampleRatesPtr,
1555
+ this.executionOutputPtr,
1556
+ );
1557
+ }
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),
1213
1576
  );
1577
+ view.setUint32(this.executionOutputPtr + 8, 0, true);
1214
1578
  }
1215
1579
 
1216
1580
  clearOutputs(outputs) {
@@ -1219,12 +1583,13 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
1219
1583
  }
1220
1584
  }
1221
1585
 
1222
- process(inputs, outputs) {
1586
+ processPending(_inputs, outputs) {
1587
+ this.clearOutputs(outputs);
1588
+ return true;
1589
+ }
1590
+
1591
+ processInitialized(inputs, outputs) {
1223
1592
  this.refreshMemoryCache();
1224
- if (this.executionFailed) {
1225
- this.clearOutputs(outputs);
1226
- return true;
1227
- }
1228
1593
  const frames = this.audioFrameCount(inputs, outputs);
1229
1594
 
1230
1595
  let callbackOffset = 0;
@@ -1250,8 +1615,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
1250
1615
  segmentFrames,
1251
1616
  flags,
1252
1617
  );
1618
+ this.publishExecutionOutput(
1619
+ EXECUTION_OPERATION_PROCESS,
1620
+ 0,
1621
+ );
1253
1622
  if (status !== 0) {
1254
- this.executionFailed = true;
1623
+ this.invalidateState();
1255
1624
  this.clearOutputs(outputs);
1256
1625
  this.port.postMessage({
1257
1626
  type: "onda-error",