@onda-lang/webaudio 0.8.2 → 0.8.3

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
@@ -75,10 +75,12 @@ const sliderValueFor440Hz = paramPlainToNormalized(cutoff, 440);
75
75
  ```
76
76
 
77
77
  The helpers preserve exact endpoints, clamp and snap consistently with adapter writes, and reject
78
- arrays or numeric parameters without a host-control domain. `scale`, `curve`, `unit`, `step_repr`,
78
+ numeric parameters without a host-control domain. Select array elements with indexed names such
79
+ as `offsets[0]`, or supply an exact-length array to `setParam("offsets", values)`. `scale`, `curve`, `unit`, `step_repr`,
79
80
  and `step_count` remain available on each parameter's descriptor metadata for control construction
80
81
  and formatting. For repeated UI conversion, `createParamControl(param)` prepares and validates the
81
- descriptor once and returns bound conversion methods.
82
+ descriptor once and returns bound conversion methods; pass an element index as its second argument
83
+ for an array.
82
84
 
83
85
  Controlled `i64` domains use the descriptor's exact binary64 integer range. Full-width unranged
84
86
  `i64` values continue to use `bigint` when written directly.
package/api.md CHANGED
@@ -119,7 +119,11 @@ versions rather than guessing layout compatibility.
119
119
  ### Parameter controls
120
120
 
121
121
  `createParamControl(metadata)` validates one scalar parameter descriptor and returns a reusable
122
- `OndaPreparedParamControl`. `createParamDomain(domain)` does the same from already-decoded values.
122
+ `OndaPreparedParamControl`. For an array, `createParamControl(metadata, index)` prepares one element;
123
+ `paramElementMetadata(metadata, index)` projects its scalar descriptor, default, and storage offset.
124
+ The Web Audio adapter accepts indexed names such as `offsets[1]` in `setParam` and
125
+ `setParamNormalized`, and exact-length arrays in `setParam("offsets", values)`.
126
+ `createParamDomain(domain)` does the same from already-decoded values.
123
127
  Prepared controls expose `constrainPlain`, `normalizedToPlain`, and `plainToNormalized` methods.
124
128
 
125
129
  The one-shot `constrainParamPlain`, `paramNormalizedToPlain`, and `paramPlainToNormalized`
@@ -271,6 +275,7 @@ PROCESSOR_INIT_PRESERVE_PINNED
271
275
  PROCESSOR_SNAPSHOT_FORMAT_VERSION
272
276
  constrainParamPlain
273
277
  createParamControl
278
+ paramElementMetadata
274
279
  createParamDomain
275
280
  createProcessorArtifactFiles
276
281
  decodeDelegateRecords
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onda-lang/webaudio",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "type": "module",
5
5
  "description": "Optional Web Audio adapter for Onda processor WebAssembly artifacts",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "prepack": "node ../../scripts/stage-web-api-docs.mjs"
35
35
  },
36
36
  "dependencies": {
37
- "@onda-lang/processor-abi": "0.8.2"
37
+ "@onda-lang/processor-abi": "0.8.3"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
package/src/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { paramAddress } from "./param-metadata.js";
1
2
  import {
2
3
  createParamControl,
4
+ paramElementMetadata,
3
5
  decodeDelegateRecords,
4
6
  formatPrintRecords,
5
7
  validateProcessorArtifact,
@@ -152,18 +154,24 @@ function configuredExecutionOutputCapacity(
152
154
  return capacity;
153
155
  }
154
156
 
155
- function paramInfoFor(paramInfo, selector) {
156
- const info = Number.isInteger(selector)
157
- ? paramInfo[selector]
158
- : paramInfo.find((candidate) => candidate.name === selector);
159
- if (!info) {
160
- throw new Error(`unknown Onda parameter '${String(selector)}'`);
157
+ function paramElementInfo(info, element, cache = null) {
158
+ if (!cache) return paramElementMetadata(info, element);
159
+ let elements = cache.get(info);
160
+ if (!elements) {
161
+ elements = new Map();
162
+ cache.set(info, elements);
161
163
  }
162
- return info;
164
+ if (!elements.has(element)) elements.set(element, paramElementMetadata(info, element));
165
+ return elements.get(element);
166
+ }
167
+
168
+ function paramInfoFor(params, selector, cache = null) {
169
+ const { info, element } = paramAddress(params, selector);
170
+ return element === null ? info : paramElementInfo(info, element, cache);
163
171
  }
164
172
 
165
173
  function preparedParamControl(info, cache = null) {
166
- if (Number(info.array_len) !== 1) return null;
174
+ if (info.type_repr !== info.scalar) return null;
167
175
  if (info.scalar !== "bool" && info.param_control === null) return null;
168
176
  let control = cache?.get(info);
169
177
  if (!control) {
@@ -173,7 +181,15 @@ function preparedParamControl(info, cache = null) {
173
181
  return control;
174
182
  }
175
183
 
176
- function constrainParamValue(info, value, cache = null) {
184
+ function constrainParamValue(info, value, cache = null, elements = null) {
185
+ if (info.type_repr !== info.scalar) {
186
+ if ((!Array.isArray(value) && !ArrayBuffer.isView(value)) || value.length !== info.array_len) {
187
+ throw new Error(`Onda parameter '${info.name}' requires exactly ${info.array_len} values`);
188
+ }
189
+ return Array.from(value, (item, index) => constrainParamValue(
190
+ paramElementInfo(info, index, elements), item, cache, elements,
191
+ ));
192
+ }
177
193
  return preparedParamControl(info, cache)?.constrainPlain(value) ?? value;
178
194
  }
179
195
 
@@ -291,6 +307,7 @@ export class OndaAudioProcessor {
291
307
  this.metadata = metadata;
292
308
  this.paramInfo = metadata?.metadata?.params ?? null;
293
309
  this.paramControls = new WeakMap();
310
+ this.paramElements = new WeakMap();
294
311
  this.nextRequestId = 1;
295
312
  this.pending = new Map();
296
313
  this.delegateListeners = new Set();
@@ -580,10 +597,10 @@ export class OndaAudioProcessor {
580
597
  if (!Array.isArray(this.paramInfo)) {
581
598
  return this.request("set-param", { param, value });
582
599
  }
583
- const info = paramInfoFor(this.paramInfo ?? [], param);
600
+ const info = paramInfoFor(this.paramInfo ?? [], param, this.paramElements);
584
601
  return this.request("set-param", {
585
602
  param,
586
- value: constrainParamValue(info, value, this.paramControls),
603
+ value: constrainParamValue(info, value, this.paramControls, this.paramElements),
587
604
  });
588
605
  } catch (error) {
589
606
  return Promise.reject(error);
@@ -597,7 +614,7 @@ export class OndaAudioProcessor {
597
614
  "setParamNormalized requires processor metadata; construct the adapter with createOndaAudioProcessor()",
598
615
  );
599
616
  }
600
- const info = paramInfoFor(this.paramInfo, param);
617
+ const info = paramInfoFor(this.paramInfo, param, this.paramElements);
601
618
  const control = preparedParamControl(info, this.paramControls);
602
619
  if (!control) {
603
620
  throw new Error(`Onda parameter '${info.name}' has no scalar host-control domain`);
@@ -0,0 +1,18 @@
1
+ // Shared by the adapter and worklet; keep this module independent of package
2
+ // imports so worklets also load from a plain static HTTP directory.
3
+ export function paramAddress(params, selector) {
4
+ const direct = Number.isInteger(selector) ? params[selector] : params.find((param) => param.name === selector);
5
+ if (direct) return { info: direct, element: null };
6
+ if (typeof selector === "string") {
7
+ const match = /^(.*)\[(0|[1-9][0-9]*)\]$/.exec(selector);
8
+ const root = match && params.find((param) => param.name === match[1] && param.type_repr !== param.scalar);
9
+ if (root) {
10
+ const element = Number(match[2]);
11
+ if (!Number.isSafeInteger(element) || element >= root.array_len) {
12
+ throw new Error(`Onda parameter '${root.name}' element index is out of bounds`);
13
+ }
14
+ return { info: root, element };
15
+ }
16
+ }
17
+ throw new Error(`unknown Onda parameter '${String(selector)}'`);
18
+ }
package/src/worklet.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { paramAddress } from "./param-metadata.js";
1
2
  import {
2
3
  EXECUTION_OPERATION_EVENT,
3
4
  EXECUTION_OPERATION_INIT,
@@ -477,13 +478,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
477
478
  }
478
479
 
479
480
  setParam(selector, value) {
480
- const paramId = Number.isInteger(selector)
481
- ? selector
482
- : this.paramInfo.findIndex((param) => param.name === selector);
483
- const param = this.paramInfo[paramId];
484
- if (!param) {
485
- throw new Error(`unknown Onda parameter '${String(selector)}'`);
486
- }
481
+ const { info: param, element } = paramAddress(this.paramInfo, selector);
487
482
  if (value === undefined) {
488
483
  throw new Error(`Onda parameter '${param.name}' requires a value`);
489
484
  }
@@ -502,6 +497,10 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
502
497
  ) {
503
498
  throw new Error(`Onda parameter '${param.name}' has invalid storage metadata`);
504
499
  }
500
+ if (element !== null) {
501
+ this.writeScalar(this.paramsPtr + offset + element * this.scalarByteSize(param.scalar), param.scalar, value, this.memoryView());
502
+ return;
503
+ }
505
504
  this.writeStorage(
506
505
  this.paramsPtr + offset,
507
506
  param,