@onda-lang/webaudio 0.6.0 → 0.7.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 CHANGED
@@ -90,9 +90,29 @@ After construction, the normal f32 render callback reuses cached Wasm-memory vie
90
90
  host-side allocation or memory growth. Full-block f32 inputs and outputs use typed-array bulk copies;
91
91
  segmented callbacks and other ABI scalar widths use preallocated typed views with conversion loops
92
92
  (i64 input conversion necessarily creates JavaScript `BigInt` values). External buffers are copied
93
- into Wasm with typed-array bulk operations during construction. Every declared external buffer must
94
- be supplied with nonempty data; the adapter rejects missing or empty bindings before creating the
95
- rendering node.
93
+ into Wasm with typed-array bulk operations during construction. Missing or `null` bindings install
94
+ neutral one-frame descriptors: reads return zero, writes are discarded, exact channel counts are
95
+ retained, and dynamic-channel buffers report one channel. Supplied bindings must still contain
96
+ nonempty, correctly shaped data.
97
+
98
+ Fixed buffer arrays may be supplied under their logical group name. Each slot is independent:
99
+
100
+ ```js
101
+ const processor = await createOndaAudioProcessor(context, artifact, {
102
+ buffers: {
103
+ impulse: { data: impulseSamples, channels: 1, sampleRate: 48_000 },
104
+ bank: [
105
+ null,
106
+ { data: secondSamples, channels: 1, sampleRate: 48_000 },
107
+ // Remaining slots are neutral.
108
+ ],
109
+ },
110
+ });
111
+ ```
112
+
113
+ Hosts may instead pass a flat array in physical descriptor order or key individual physical names
114
+ such as `"bank[1]"`. Logical group metadata determines the contiguous slot range; no sample data is
115
+ copied when Onda selects a slot while processing.
96
116
 
97
117
  Artifact descriptors and module exports are validated by the shared, compiler-free
98
118
  `@onda-lang/processor-abi` package before anything reaches the rendering thread.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onda-lang/webaudio",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Optional Web Audio adapter for Onda processor WebAssembly artifacts",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  "test": "node --test"
33
33
  },
34
34
  "dependencies": {
35
- "@onda-lang/processor-abi": "0.6.0"
35
+ "@onda-lang/processor-abi": "0.7.0"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
package/src/index.d.ts CHANGED
@@ -22,6 +22,10 @@ export interface OndaAudioProcessorOptions {
22
22
  workletUrl?: string | URL;
23
23
  /** Initial plain Onda parameter values, keyed or ordered by descriptor parameter. */
24
24
  params?: Record<string, unknown> | unknown[];
25
+ /**
26
+ * Initial external-buffer bindings keyed by physical name, grouped by logical array name, or
27
+ * ordered by physical descriptor slot. Missing and null slots use neutral one-frame storage.
28
+ */
25
29
  buffers?: Record<string, unknown> | unknown[];
26
30
  /** Preallocated capacity for dynamic event payloads. Defaults to 64 KiB. */
27
31
  eventPayloadCapacityBytes?: number;
package/src/worklet.js CHANGED
@@ -70,6 +70,9 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
70
70
  this.bufferInfo = Array.isArray(metadata.metadata?.buffers)
71
71
  ? metadata.metadata.buffers
72
72
  : [];
73
+ this.bufferArrayInfo = Array.isArray(metadata.metadata?.buffer_arrays)
74
+ ? metadata.metadata.buffer_arrays
75
+ : [];
73
76
  this.inputInfo = Array.isArray(metadata.metadata?.inputs)
74
77
  ? metadata.metadata.inputs
75
78
  : [];
@@ -607,11 +610,21 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
607
610
 
608
611
  bindInitialBuffers(options) {
609
612
  this.bufferInfo.forEach((buffer, bufferId) => {
610
- const supplied = Array.isArray(options)
611
- ? options[bufferId]
612
- : options[buffer.name];
613
- if (supplied === undefined) {
614
- throw new Error(`Onda buffer '${buffer.name}' is not bound`);
613
+ const supplied = this.initialBufferOption(options, buffer, bufferId);
614
+ const declaredChannels = Number(buffer.static_channels ?? 0);
615
+ const fallbackChannels = declaredChannels || 1;
616
+ if (supplied === undefined || supplied === null) {
617
+ const sampleRate = Math.fround(this.compileSampleRate);
618
+ this.writeBufferDescriptor(bufferId, 0, 1, fallbackChannels, sampleRate);
619
+ this.bufferBindings[bufferId] = {
620
+ ...buffer,
621
+ pointer: 0,
622
+ frames: 1,
623
+ channels: fallbackChannels,
624
+ sampleRate,
625
+ bound: false,
626
+ };
627
+ return;
615
628
  }
616
629
  const descriptor =
617
630
  Array.isArray(supplied) || ArrayBuffer.isView(supplied)
@@ -632,7 +645,6 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
632
645
  `Onda buffer '${buffer.name}' requires non-empty bound data`,
633
646
  );
634
647
  }
635
- const declaredChannels = Number(buffer.static_channels ?? 0);
636
648
  const channels = Number(descriptor.channels ?? declaredChannels);
637
649
  if (!Number.isInteger(channels) || channels <= 0) {
638
650
  throw new Error(`Onda buffer '${buffer.name}' requires channels > 0`);
@@ -655,25 +667,54 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
655
667
  const elementSize = this.scalarByteSize(buffer.scalar);
656
668
  const pointer = this.alloc(length * elementSize, elementSize);
657
669
  this.writeScalarValues(pointer, buffer.scalar, data, length);
658
- const view = this.memoryView();
659
- view.setUint32(this.bufferPointersPtr + bufferId * 4, pointer, true);
660
- view.setInt32(this.bufferFramesPtr + bufferId * 4, frames, true);
661
- view.setInt32(this.bufferChannelsPtr + bufferId * 4, channels, true);
662
- view.setFloat32(
663
- this.bufferSampleRatesPtr + bufferId * 4,
664
- sampleRate,
665
- true,
666
- );
670
+ this.writeBufferDescriptor(bufferId, pointer, frames, channels, sampleRate);
667
671
  this.bufferBindings[bufferId] = {
668
672
  ...buffer,
669
673
  pointer,
670
674
  frames,
671
675
  channels,
672
676
  sampleRate,
677
+ bound: true,
673
678
  };
674
679
  });
675
680
  }
676
681
 
682
+ writeBufferDescriptor(bufferId, pointer, frames, channels, sampleRate) {
683
+ const view = this.memoryView();
684
+ view.setUint32(this.bufferPointersPtr + bufferId * 4, pointer, true);
685
+ view.setInt32(this.bufferFramesPtr + bufferId * 4, frames, true);
686
+ view.setInt32(this.bufferChannelsPtr + bufferId * 4, channels, true);
687
+ view.setFloat32(this.bufferSampleRatesPtr + bufferId * 4, sampleRate, true);
688
+ }
689
+
690
+ initialBufferOption(options, buffer, bufferId) {
691
+ if (Array.isArray(options)) {
692
+ return options[bufferId];
693
+ }
694
+ if (!options || typeof options !== "object") {
695
+ throw new Error("processorOptions.buffers must be an array or object");
696
+ }
697
+ if (Object.prototype.hasOwnProperty.call(options, buffer.name)) {
698
+ return options[buffer.name];
699
+ }
700
+ const group = this.bufferArrayInfo.find((candidate) => {
701
+ const first = Number(candidate.first_buffer);
702
+ const len = Number(candidate.len);
703
+ return bufferId >= first && bufferId < first + len;
704
+ });
705
+ if (!group || !Object.prototype.hasOwnProperty.call(options, group.name)) {
706
+ return undefined;
707
+ }
708
+ const supplied = options[group.name];
709
+ if (supplied === undefined || supplied === null) {
710
+ return undefined;
711
+ }
712
+ if (!Array.isArray(supplied)) {
713
+ throw new Error(`Onda buffer array '${group.name}' must be an array`);
714
+ }
715
+ return supplied[bufferId - Number(group.first_buffer)];
716
+ }
717
+
677
718
  readBuffer(selector) {
678
719
  const bufferId = Number.isInteger(selector)
679
720
  ? selector