@onda-lang/webaudio 0.8.5 → 0.8.7
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 +21 -9
- package/api.md +78 -13
- package/package.json +2 -2
- package/src/index.d.ts +27 -0
- package/src/index.js +144 -22
- package/src/param-metadata.js +15 -0
- package/src/processor-constants.js +10 -0
- package/src/worklet.js +55 -29
package/README.md
CHANGED
|
@@ -57,8 +57,13 @@ write to the worklet. Boolean normalized values use the `0.5` threshold. For exa
|
|
|
57
57
|
```js
|
|
58
58
|
await processor.setParam("cutoff", 440); // exactly 440 Hz
|
|
59
59
|
await processor.setParamNormalized("cutoff", 0.5); // midpoint in its declared control scale
|
|
60
|
+
await processor.setParamElement("offsets", 1, 12); // one fixed-array element
|
|
61
|
+
await processor.setParamElementNormalized("offsets", 1, 0.5);
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
The element methods also accept scalar parameters with element index zero. Indexed names such as
|
|
65
|
+
`offsets[1]` remain available through `setParam()` and `setParamNormalized()`.
|
|
66
|
+
|
|
62
67
|
The same synchronous conversion helpers are re-exported for UI display and typed entry:
|
|
63
68
|
|
|
64
69
|
```js
|
|
@@ -128,7 +133,9 @@ Fixed arrays must have their declared length. Slice lengths may vary up to the c
|
|
|
128
133
|
payload capacity. `i64` leaves accept `bigint`, exact safe integers, or decimal integer strings and
|
|
129
134
|
reject values outside the signed 64-bit range. Missing top-level arguments use declared defaults;
|
|
130
135
|
structs and struct arrays have no event defaults and must be supplied in full. Input rejected during
|
|
131
|
-
worklet preflight leaves processor state usable and rejects the returned promise.
|
|
136
|
+
worklet preflight leaves processor state usable and rejects the returned promise. Unknown
|
|
137
|
+
nonnegative numeric event indices are successful no-ops, matching native dispatch; unknown names
|
|
138
|
+
and negative indices reject the promise.
|
|
132
139
|
|
|
133
140
|
## Prints
|
|
134
141
|
|
|
@@ -249,23 +256,28 @@ const processor = await createOndaAudioProcessorInitialized(context, artifact, {
|
|
|
249
256
|
Hosts may instead pass a flat array in physical descriptor order or key individual physical names
|
|
250
257
|
such as `"bank[1]"`. Logical group metadata determines the contiguous slot range; no sample data is
|
|
251
258
|
copied when Onda selects a slot while processing. Initial descriptors are installed before the
|
|
252
|
-
initialized
|
|
259
|
+
initialized factory awaits full initialization, so authored top-level and proc init code can
|
|
253
260
|
preprocess the supplied samples once before rendering begins.
|
|
254
261
|
|
|
255
262
|
Artifact descriptors and module exports are validated by the shared, compiler-free
|
|
256
263
|
`@onda-lang/processor-abi` package before anything reaches the rendering thread.
|
|
257
|
-
If generated init or event code returns a nonzero execution status, the adapter
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
If generated init or event code returns a nonzero execution status, the adapter rejects with
|
|
265
|
+
`OndaExecutionError`; its `operation` and `status` fields preserve the raw processor result. Event
|
|
266
|
+
input rejection preserves live state. The package re-exports the `PROCESSOR_EXECUTION_*` constants
|
|
267
|
+
for direct status comparisons. Runtime-safety failure invalidates the processor, so later callbacks
|
|
268
|
+
remain silent and stateful operations are rejected until full initialization or snapshot
|
|
269
|
+
restoration succeeds. Subscribe with `onExecutionError(listener)` to receive the same typed error
|
|
270
|
+
when a render-time process call fails; the failed callback emits silence.
|
|
261
271
|
`init(ONDA_INIT_PRESERVE_PINNED)` reruns generated initialization
|
|
262
272
|
while preserving pinned roots and task continuations; `init(ONDA_INIT_FULL)` initializes the
|
|
263
273
|
complete physical state and is required before processing an instance returned by
|
|
264
|
-
`createOndaAudioProcessor`. The initialized convenience
|
|
265
|
-
|
|
274
|
+
`createOndaAudioProcessor`. The initialized convenience factory installs its response and output
|
|
275
|
+
handling before requesting full initialization, then resolves only after initialization succeeds.
|
|
276
|
+
Neither mode allocates on the successful path, and a
|
|
266
277
|
failure returns the processor to its silent pending state until full initialization or snapshot
|
|
267
278
|
restore succeeds. Suspend or disconnect playback before initialization that performs substantial
|
|
268
|
-
work.
|
|
279
|
+
work. Snapshot restoration performs its internal full initialization silently and does not deliver
|
|
280
|
+
init prints or delegates.
|
|
269
281
|
|
|
270
282
|
Dynamic event storage is also allocated before rendering. Its default capacity is 64 KiB per
|
|
271
283
|
processor with dynamic events and can be changed explicitly:
|
package/api.md
CHANGED
|
@@ -121,8 +121,10 @@ versions rather than guessing layout compatibility.
|
|
|
121
121
|
`createParamControl(metadata)` validates one scalar parameter descriptor and returns a reusable
|
|
122
122
|
`OndaPreparedParamControl`. For an array, `createParamControl(metadata, index)` prepares one element;
|
|
123
123
|
`paramElementMetadata(metadata, index)` projects its scalar descriptor, default, and storage offset.
|
|
124
|
-
The Web Audio adapter
|
|
125
|
-
`
|
|
124
|
+
The Web Audio adapter exposes `setParamElement(nameOrIndex, element, value)` and
|
|
125
|
+
`setParamElementNormalized(nameOrIndex, element, normalized)` for individual scalar or fixed-array
|
|
126
|
+
elements. Indexed names such as `offsets[1]` remain accepted by `setParam` and
|
|
127
|
+
`setParamNormalized`; exact-length arrays use `setParam("offsets", values)`.
|
|
126
128
|
`createParamDomain(domain)` does the same from already-decoded values.
|
|
127
129
|
Prepared controls expose `constrainPlain`, `normalizedToPlain`, and `plainToNormalized` methods.
|
|
128
130
|
|
|
@@ -135,14 +137,17 @@ endpoints, apply linear, logarithmic, or curved scale, and clamp and snap to the
|
|
|
135
137
|
|
|
136
138
|
Complete wasm32 processor exports receive an optional execution-output descriptor containing
|
|
137
139
|
independently nullable delegate and print batches. Allocate all descriptors and storage before
|
|
138
|
-
real-time execution.
|
|
140
|
+
real-time execution. Their linear-memory regions must be mutually disjoint and must not overlap any
|
|
141
|
+
other region accessed by the processor call, matching the processor ABI's execution-output
|
|
142
|
+
contract.
|
|
139
143
|
|
|
140
144
|
`writeDelegateBatch` and `writePrintBatch` initialize reusable batch descriptors.
|
|
141
145
|
`writeExecutionOutput` connects their addresses. Call `resetExecutionOutput` immediately before
|
|
142
|
-
every generated init or process entry. Generated event entries reset
|
|
143
|
-
|
|
146
|
+
every generated init or process entry. Generated event entries reset before input preflight, so
|
|
147
|
+
rejected input returns empty batches. After a successful generated call,
|
|
144
148
|
`readDelegateBatch` or `readPrintBatch` validates the result counters.
|
|
145
|
-
`
|
|
149
|
+
`decodeDelegateBatch` validates and decodes a complete delegate batch in one call;
|
|
150
|
+
`decodeDelegateRecords` decodes an already-isolated storage region using `metadata.delegates`.
|
|
146
151
|
`decodePrintRecords` preserves primitive types and source sites; `formatPrintRecords` and
|
|
147
152
|
`formatPrintBatch` add canonical text formatting. Both decoded record types expose a `sequence` from
|
|
148
153
|
the host-reset call-local counter; merge that call's two arrays by this field when presenting one
|
|
@@ -184,27 +189,36 @@ The package root re-exports the common artifact validation and file helpers. The
|
|
|
184
189
|
- `compileOndaProcessorModule(artifact)` validates and compiles a reusable module off the render thread.
|
|
185
190
|
- `ondaAudioWorkletNodeOptions(artifact, options?)` builds low-level node options.
|
|
186
191
|
- `createOndaAudioProcessor(context, artifact, options?)` allocates an uninitialized adapter.
|
|
187
|
-
- `createOndaAudioProcessorInitialized(context, artifact, options?)` allocates
|
|
192
|
+
- `createOndaAudioProcessorInitialized(context, artifact, options?)` allocates it, requests full
|
|
193
|
+
initialization through the status-bearing control protocol, and resolves only on success.
|
|
188
194
|
- `flattenedAudioChannelCount(ports?)` totals declared physical channels with validation.
|
|
189
195
|
|
|
190
196
|
`OndaAudioProcessorOptions` accepts initial plain parameter values, external buffers, event,
|
|
191
197
|
delegate, and print capacities, an optional construction-time `onPrint` listener, a precompiled
|
|
192
198
|
module, custom node options, and an `AudioWorkletNode` constructor. Pass `onPrint` when using the
|
|
193
|
-
initialized
|
|
199
|
+
initialized factory if initialization output must be observed; registering a listener after
|
|
194
200
|
construction cannot replay output from an execution that has already completed. The artifact sample
|
|
195
201
|
rate must equal the context sample rate and it must expose at least one audio input or output.
|
|
196
202
|
Print and delegate delivery uses a bounded `SharedArrayBuffer` ring and requires cross-origin
|
|
197
203
|
isolation in browsers; ordinary audio processing remains available when shared memory is absent.
|
|
198
204
|
Low-level callers that manually pair the returned node options with `OndaAudioProcessor` must pass
|
|
199
|
-
|
|
205
|
+
the artifact metadata as the adapter constructor's second argument and
|
|
206
|
+
`processorOptions.executionOutputRing` as its fourth argument. Event encoding and numeric
|
|
207
|
+
unknown-index classification require that metadata.
|
|
200
208
|
|
|
201
209
|
### `OndaAudioProcessor`
|
|
202
210
|
|
|
203
211
|
The adapter exposes its `node` and validated `metadata`, plus these operations:
|
|
204
212
|
|
|
205
|
-
- `setParam(nameOrIndex, plain)` and `setParamNormalized(nameOrIndex, normalized)
|
|
206
|
-
|
|
207
|
-
- `
|
|
213
|
+
- `setParam(nameOrIndex, plain)` and `setParamNormalized(nameOrIndex, normalized)` for whole
|
|
214
|
+
parameters.
|
|
215
|
+
- `setParamElement(nameOrIndex, element, plain)` and
|
|
216
|
+
`setParamElementNormalized(nameOrIndex, element, normalized)` for one scalar or fixed-array
|
|
217
|
+
element.
|
|
218
|
+
- `trigger(nameOrIndex, values?)` for input events. Unknown nonnegative numeric indices are
|
|
219
|
+
successful no-ops, matching native dispatch; unknown names and negative indices are rejected.
|
|
220
|
+
- `onExecutionError(listener)`, `onDelegates(listener)`, and `onPrint(listener)`, each returning an
|
|
221
|
+
unsubscribe function.
|
|
208
222
|
- `init(mode)`, `snapshot()`, and `restoreSnapshot(bytes)`.
|
|
209
223
|
- `readControlOutputs()` and `readBuffer(nameOrIndex)`.
|
|
210
224
|
- `request(type, fields?, transfer?)` for adapter protocol extensions.
|
|
@@ -222,7 +236,24 @@ each init, event, or process segment.
|
|
|
222
236
|
|
|
223
237
|
`ONDA_INIT_FULL` clears and initializes all physical state. `ONDA_INIT_PRESERVE_PINNED` retains
|
|
224
238
|
pinned state according to the processor ABI. The package also re-exports the prepared and one-shot
|
|
225
|
-
parameter conversion helpers.
|
|
239
|
+
parameter conversion helpers. Snapshot restoration performs its internal full initialization
|
|
240
|
+
silently, without delivering init prints or delegates.
|
|
241
|
+
|
|
242
|
+
Raw WebAssembly exports use the same generated status values as native processor objects:
|
|
243
|
+
`PROCESSOR_EXECUTION_OK` (`0`), `PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE` (`1`), and event-only
|
|
244
|
+
`PROCESSOR_EXECUTION_INPUT_REJECTED` (`2`). The Web Audio adapter translates host-side validation
|
|
245
|
+
problems into rejected promises before generated execution. It also rejects init and event promises
|
|
246
|
+
when generated code returns a nonzero status: status `1` invalidates the live processor, while event
|
|
247
|
+
status `2` preserves it. These generated failures use `OndaExecutionError`, whose `operation` and
|
|
248
|
+
`status` fields retain the machine-readable failure. The Web Audio root re-exports all three
|
|
249
|
+
`PROCESSOR_EXECUTION_*` constants for direct comparison. Render-time process failure invalidates the
|
|
250
|
+
processor and silences subsequent callbacks. `onExecutionError(listener)` receives its typed
|
|
251
|
+
`OndaExecutionError`; requested init and event failures remain on their promise rejection path and
|
|
252
|
+
are not reported twice.
|
|
253
|
+
The raw `@onda-lang/processor-abi` package exposes `PROCESSOR_BEGIN_BLOCK`,
|
|
254
|
+
`PROCESSOR_END_BLOCK`, and `PROCESSOR_FULL_BLOCK` for native and direct WebAssembly hosts that use
|
|
255
|
+
segmented processing. Web Audio schedules render quanta internally and has no segmented-process
|
|
256
|
+
method.
|
|
226
257
|
|
|
227
258
|
`@onda-lang/webaudio/worklet` is the side-effect-only AudioWorklet registration module and has no
|
|
228
259
|
named exports.
|
|
@@ -269,8 +300,11 @@ PRINT_RECORD_HEADER_SIZE_BYTES
|
|
|
269
300
|
PROCESSOR_ABI_VERSION
|
|
270
301
|
PROCESSOR_ARTIFACT_FORMAT
|
|
271
302
|
PROCESSOR_ARTIFACT_FORMAT_VERSION
|
|
303
|
+
PROCESSOR_BEGIN_BLOCK
|
|
304
|
+
PROCESSOR_END_BLOCK
|
|
272
305
|
PROCESSOR_EXECUTION_OK
|
|
273
306
|
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE
|
|
307
|
+
PROCESSOR_FULL_BLOCK
|
|
274
308
|
PROCESSOR_INIT_FULL
|
|
275
309
|
PROCESSOR_INIT_PRESERVE_PINNED
|
|
276
310
|
PROCESSOR_SNAPSHOT_FORMAT_VERSION
|
|
@@ -279,6 +313,7 @@ createParamControl
|
|
|
279
313
|
paramElementMetadata
|
|
280
314
|
createParamDomain
|
|
281
315
|
createProcessorArtifactFiles
|
|
316
|
+
decodeDelegateBatch
|
|
282
317
|
decodeDelegateRecords
|
|
283
318
|
decodePrintRecords
|
|
284
319
|
formatPrintBatch
|
|
@@ -303,6 +338,7 @@ OndaPayloadSchema
|
|
|
303
338
|
OndaPayloadDefault
|
|
304
339
|
OndaPayloadValue
|
|
305
340
|
OndaPayloadSizes
|
|
341
|
+
OndaPayloadTensorMetadata
|
|
306
342
|
PayloadPlan
|
|
307
343
|
canonicalF32Number
|
|
308
344
|
EVENT_INPUT_SIZE_BYTES
|
|
@@ -317,12 +353,19 @@ OndaArtifactError
|
|
|
317
353
|
OndaBinaryenError
|
|
318
354
|
OndaBinaryenOptions
|
|
319
355
|
OndaProcessorArtifact
|
|
356
|
+
OndaProcessorInitMode
|
|
320
357
|
OndaProcessorMetadata
|
|
321
358
|
PROCESSOR_ABI_VERSION
|
|
322
359
|
PROCESSOR_ARTIFACT_FORMAT
|
|
323
360
|
PROCESSOR_ARTIFACT_FORMAT_VERSION
|
|
361
|
+
PROCESSOR_BEGIN_BLOCK
|
|
362
|
+
PROCESSOR_END_BLOCK
|
|
363
|
+
PROCESSOR_EXECUTION_INPUT_REJECTED
|
|
324
364
|
PROCESSOR_EXECUTION_OK
|
|
325
365
|
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE
|
|
366
|
+
PROCESSOR_FULL_BLOCK
|
|
367
|
+
PROCESSOR_INIT_FULL
|
|
368
|
+
PROCESSOR_INIT_PRESERVE_PINNED
|
|
326
369
|
PROCESSOR_SNAPSHOT_FORMAT_VERSION
|
|
327
370
|
SUPPORTED_MIR_SCHEMA_VERSION
|
|
328
371
|
compileTrustedMir
|
|
@@ -363,6 +406,7 @@ OndaLspAnalysisOptions
|
|
|
363
406
|
OndaLspMessage
|
|
364
407
|
OndaMaterializedProjectFile
|
|
365
408
|
OndaProcessorArtifact
|
|
409
|
+
OndaProcessorInitMode
|
|
366
410
|
OndaProcessorMetadata
|
|
367
411
|
OndaProjectBufferInfo
|
|
368
412
|
OndaProjectCapabilities
|
|
@@ -380,8 +424,14 @@ OndaWorkerLike
|
|
|
380
424
|
PROCESSOR_ABI_VERSION
|
|
381
425
|
PROCESSOR_ARTIFACT_FORMAT
|
|
382
426
|
PROCESSOR_ARTIFACT_FORMAT_VERSION
|
|
427
|
+
PROCESSOR_BEGIN_BLOCK
|
|
428
|
+
PROCESSOR_END_BLOCK
|
|
429
|
+
PROCESSOR_EXECUTION_INPUT_REJECTED
|
|
383
430
|
PROCESSOR_EXECUTION_OK
|
|
384
431
|
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE
|
|
432
|
+
PROCESSOR_FULL_BLOCK
|
|
433
|
+
PROCESSOR_INIT_FULL
|
|
434
|
+
PROCESSOR_INIT_PRESERVE_PINNED
|
|
385
435
|
PROCESSOR_SNAPSHOT_FORMAT_VERSION
|
|
386
436
|
WorkerCompilerOptions
|
|
387
437
|
createCompiler
|
|
@@ -405,11 +455,16 @@ OndaAudioProcessor
|
|
|
405
455
|
OndaAudioProcessorOptions
|
|
406
456
|
OndaAudioPrintBatch
|
|
407
457
|
OndaAudioPrintListener
|
|
458
|
+
OndaExecutionError
|
|
459
|
+
OndaExecutionErrorListener
|
|
408
460
|
OndaInitMode
|
|
409
461
|
OndaParamDomain
|
|
410
462
|
OndaPreparedParamControl
|
|
411
463
|
OndaProcessorArtifact
|
|
412
464
|
OndaProcessorMetadata
|
|
465
|
+
PROCESSOR_EXECUTION_INPUT_REJECTED
|
|
466
|
+
PROCESSOR_EXECUTION_OK
|
|
467
|
+
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE
|
|
413
468
|
compileOndaProcessorModule
|
|
414
469
|
constrainParamPlain
|
|
415
470
|
createOndaAudioProcessor
|
|
@@ -432,11 +487,21 @@ are rejected. Missing event arguments use declared constant defaults.
|
|
|
432
487
|
Supply `i64` as `bigint`, an exact safe integer, or a decimal string. `decode(bytes)` returns named
|
|
433
488
|
nested values with `bigint` for `i64`. Plans own immutable schema snapshots.
|
|
434
489
|
|
|
490
|
+
`tensorMetadata` exposes the schema's flattened SoA contract without reparsing JSON. Each immutable
|
|
491
|
+
record contains `path`, `encoding`, fixed `shape`, `parameterIndex`, `isSlice`, and
|
|
492
|
+
`fixedElementCount`. Tuple path components use `__0`, `__1`, and so on. A slice's runtime tensor
|
|
493
|
+
length is its logical length times `fixedElementCount`; the dynamic outer axis is therefore not
|
|
494
|
+
repeated in `shape`.
|
|
495
|
+
|
|
435
496
|
`requiredWorkspace(bytes)` preflights packed little-endian input. `sizes(lengths)` reports wire
|
|
436
497
|
and aligned workspace sizes without allocating payload storage. Low-level wasm hosts provision
|
|
437
498
|
both regions and a 16-byte descriptor, then call `writeEventInput` before invoking the event.
|
|
438
499
|
Rejected input returns `PROCESSOR_EXECUTION_INPUT_REJECTED` (2) and leaves processor state usable.
|
|
439
500
|
`OndaAudioProcessor.trigger` performs logical encoding before transferring bytes to the worklet.
|
|
501
|
+
Web hosts intentionally have no unchecked tensor-view event entry: JavaScript values must cross the
|
|
502
|
+
main-thread/worklet and Wasm linear-memory boundaries, so native borrowed addresses cannot be
|
|
503
|
+
preserved end to end. Web event dispatch remains the packed, checked `onda_event_N` path and uses
|
|
504
|
+
its preallocated workspace.
|
|
440
505
|
Decoded `f32` leaves use `canonicalF32Number(value)`: the returned JavaScript number has the
|
|
441
506
|
shortest decimal representation that round-trips to the same `f32`, so JSON and controls do not
|
|
442
507
|
expose an irrelevant widened binary tail.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onda-lang/webaudio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
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.
|
|
37
|
+
"@onda-lang/processor-abi": "0.8.7"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
package/src/index.d.ts
CHANGED
|
@@ -3,6 +3,21 @@ export const ONDA_INIT_PRESERVE_PINNED: 0;
|
|
|
3
3
|
export const ONDA_INIT_FULL: 1;
|
|
4
4
|
export type OndaInitMode = 0 | 1;
|
|
5
5
|
|
|
6
|
+
export {
|
|
7
|
+
PROCESSOR_EXECUTION_INPUT_REJECTED,
|
|
8
|
+
PROCESSOR_EXECUTION_OK,
|
|
9
|
+
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE,
|
|
10
|
+
} from "@onda-lang/processor-abi";
|
|
11
|
+
|
|
12
|
+
/** A nonzero status returned by generated processor execution. */
|
|
13
|
+
export class OndaExecutionError extends Error {
|
|
14
|
+
constructor(message: string, operation: string, status: number);
|
|
15
|
+
readonly operation: string;
|
|
16
|
+
readonly status: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type OndaExecutionErrorListener = (error: OndaExecutionError) => void;
|
|
20
|
+
|
|
6
21
|
export type {
|
|
7
22
|
OndaParamDomain,
|
|
8
23
|
OndaPreparedParamControl,
|
|
@@ -88,6 +103,7 @@ export function createOndaAudioProcessor(
|
|
|
88
103
|
artifact: OndaProcessorArtifact,
|
|
89
104
|
options?: OndaAudioProcessorOptions,
|
|
90
105
|
): Promise<OndaAudioProcessor>;
|
|
106
|
+
/** Resolves after full initialization succeeds; generated failures reject with OndaExecutionError. */
|
|
91
107
|
export function createOndaAudioProcessorInitialized(
|
|
92
108
|
context: BaseAudioContext,
|
|
93
109
|
artifact: OndaProcessorArtifact,
|
|
@@ -110,9 +126,20 @@ export class OndaAudioProcessor {
|
|
|
110
126
|
request(type: string, fields?: Record<string, unknown>, transfer?: Transferable[]): Promise<any>;
|
|
111
127
|
/** Set a plain Onda value; ranged scalar values are clamped and snapped. */
|
|
112
128
|
setParam(param: string | number, value: unknown): Promise<any>;
|
|
129
|
+
/** Set one scalar or fixed-array parameter element without modifying its siblings. */
|
|
130
|
+
setParamElement(param: string | number, element: number, value: unknown): Promise<any>;
|
|
113
131
|
/** Map a host value in [0, 1] through the descriptor and set the resulting plain value. */
|
|
114
132
|
setParamNormalized(param: string | number, value: number): Promise<any>;
|
|
133
|
+
/** Normalized form of setParamElement(). */
|
|
134
|
+
setParamElementNormalized(
|
|
135
|
+
param: string | number,
|
|
136
|
+
element: number,
|
|
137
|
+
value: number,
|
|
138
|
+
): Promise<any>;
|
|
139
|
+
/** Unknown nonnegative numeric event indices are successful no-ops; unknown names are rejected. */
|
|
115
140
|
trigger(event: string | number, values?: Record<string, unknown> | unknown[]): Promise<any>;
|
|
141
|
+
/** Subscribe to asynchronous render-time generated failures. */
|
|
142
|
+
onExecutionError(listener: OndaExecutionErrorListener): () => boolean;
|
|
116
143
|
/**
|
|
117
144
|
* Subscribe to batches decoded after generated execution. Collection is active only while at
|
|
118
145
|
* least one listener is registered. overflowCount reports configured-capacity loss and
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { paramAddress } from "./param-metadata.js";
|
|
1
|
+
import { paramAddress, paramElementAddress } from "./param-metadata.js";
|
|
2
2
|
import {
|
|
3
3
|
PayloadPlan,
|
|
4
4
|
createParamControl,
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
drainExecutionOutputRing,
|
|
19
19
|
openExecutionOutputRing,
|
|
20
20
|
} from "./execution-output-ring.js";
|
|
21
|
+
import {
|
|
22
|
+
PROCESSOR_INIT_FULL,
|
|
23
|
+
PROCESSOR_INIT_PRESERVE_PINNED,
|
|
24
|
+
} from "./processor-constants.js";
|
|
21
25
|
|
|
22
26
|
export {
|
|
23
27
|
createParamDomain,
|
|
@@ -25,11 +29,23 @@ export {
|
|
|
25
29
|
constrainParamPlain,
|
|
26
30
|
paramNormalizedToPlain,
|
|
27
31
|
paramPlainToNormalized,
|
|
32
|
+
PROCESSOR_EXECUTION_INPUT_REJECTED,
|
|
33
|
+
PROCESSOR_EXECUTION_OK,
|
|
34
|
+
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE,
|
|
28
35
|
} from "@onda-lang/processor-abi";
|
|
29
36
|
|
|
30
37
|
export const ONDA_AUDIO_WORKLET_PROCESSOR_NAME = "onda-wasm-processor";
|
|
31
|
-
export const ONDA_INIT_PRESERVE_PINNED =
|
|
32
|
-
export const ONDA_INIT_FULL =
|
|
38
|
+
export const ONDA_INIT_PRESERVE_PINNED = PROCESSOR_INIT_PRESERVE_PINNED;
|
|
39
|
+
export const ONDA_INIT_FULL = PROCESSOR_INIT_FULL;
|
|
40
|
+
|
|
41
|
+
export class OndaExecutionError extends Error {
|
|
42
|
+
constructor(message, operation, status) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "OndaExecutionError";
|
|
45
|
+
this.operation = operation;
|
|
46
|
+
this.status = status;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
33
49
|
|
|
34
50
|
const registrationByContext = new WeakMap();
|
|
35
51
|
const DEFAULT_DELEGATE_CAPACITY_BYTES = 64 * 1024;
|
|
@@ -171,6 +187,11 @@ function paramInfoFor(params, selector, cache = null) {
|
|
|
171
187
|
return element === null ? info : paramElementInfo(info, element, cache);
|
|
172
188
|
}
|
|
173
189
|
|
|
190
|
+
function paramElementInfoFor(params, selector, element, cache = null) {
|
|
191
|
+
const address = paramElementAddress(params, selector, element);
|
|
192
|
+
return paramElementInfo(address.info, address.element, cache);
|
|
193
|
+
}
|
|
194
|
+
|
|
174
195
|
function preparedParamControl(info, cache = null) {
|
|
175
196
|
if (info.type_repr !== info.scalar) return null;
|
|
176
197
|
if (info.scalar !== "bool" && info.param_control === null) return null;
|
|
@@ -263,7 +284,7 @@ async function createOndaAudioProcessorImpl(context, artifact, options, initiali
|
|
|
263
284
|
}
|
|
264
285
|
const nodeOptions = audioWorkletNodeOptionsFromValidated(
|
|
265
286
|
validated,
|
|
266
|
-
{ ...options, compiledModule, initialize },
|
|
287
|
+
{ ...options, compiledModule, initialize: false },
|
|
267
288
|
false,
|
|
268
289
|
);
|
|
269
290
|
const node = new NodeConstructor(
|
|
@@ -271,12 +292,32 @@ async function createOndaAudioProcessorImpl(context, artifact, options, initiali
|
|
|
271
292
|
ONDA_AUDIO_WORKLET_PROCESSOR_NAME,
|
|
272
293
|
nodeOptions,
|
|
273
294
|
);
|
|
274
|
-
|
|
295
|
+
const processor = new OndaAudioProcessor(
|
|
275
296
|
node,
|
|
276
297
|
validated.metadata,
|
|
277
298
|
options.onPrint,
|
|
278
299
|
nodeOptions.processorOptions.executionOutputRing,
|
|
279
300
|
);
|
|
301
|
+
if (!initialize) return processor;
|
|
302
|
+
try {
|
|
303
|
+
await processor.init(ONDA_INIT_FULL);
|
|
304
|
+
return processor;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
// Initialization publishes diagnostic prints before reporting its status. Consume any
|
|
307
|
+
// already-published records while the construction-time listener is still installed.
|
|
308
|
+
try {
|
|
309
|
+
processor.drainExecutionOutput();
|
|
310
|
+
} catch {
|
|
311
|
+
// Preserve the initialization failure if consuming its diagnostics also fails.
|
|
312
|
+
}
|
|
313
|
+
processor.close(error);
|
|
314
|
+
try {
|
|
315
|
+
node.disconnect?.();
|
|
316
|
+
} catch {
|
|
317
|
+
// Preserve the initialization failure if node teardown also fails.
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
280
321
|
}
|
|
281
322
|
|
|
282
323
|
export async function compileOndaProcessorModule(artifact) {
|
|
@@ -312,6 +353,7 @@ export class OndaAudioProcessor {
|
|
|
312
353
|
this.paramElements = new WeakMap();
|
|
313
354
|
this.nextRequestId = 1;
|
|
314
355
|
this.pending = new Map();
|
|
356
|
+
this.executionErrorListeners = new Set();
|
|
315
357
|
this.delegateListeners = new Set();
|
|
316
358
|
this.delegateSubscriptionId = 0;
|
|
317
359
|
this.printListeners = new Set(
|
|
@@ -333,15 +375,22 @@ export class OndaAudioProcessor {
|
|
|
333
375
|
this.closeReason = null;
|
|
334
376
|
this.handleMessage = (event) => {
|
|
335
377
|
const message = event.data ?? {};
|
|
336
|
-
|
|
378
|
+
const error = message.type === "onda-error"
|
|
379
|
+
? Number.isInteger(message.status)
|
|
380
|
+
? new OndaExecutionError(message.error, message.operation, message.status)
|
|
381
|
+
: new Error(message.error)
|
|
382
|
+
: null;
|
|
383
|
+
if (message.requestId === undefined) {
|
|
384
|
+
if (error instanceof OndaExecutionError) {
|
|
385
|
+
this.notifyListeners(this.executionErrorListeners, error);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
337
389
|
const pending = this.pending.get(message.requestId);
|
|
338
390
|
if (!pending) return;
|
|
339
391
|
this.pending.delete(message.requestId);
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
} else {
|
|
343
|
-
pending.resolve(message);
|
|
344
|
-
}
|
|
392
|
+
if (error) pending.reject(error);
|
|
393
|
+
else pending.resolve(message);
|
|
345
394
|
};
|
|
346
395
|
node.port.addEventListener("message", this.handleMessage);
|
|
347
396
|
node.port.start?.();
|
|
@@ -375,7 +424,7 @@ export class OndaAudioProcessor {
|
|
|
375
424
|
if (result.async) await result.value;
|
|
376
425
|
}
|
|
377
426
|
};
|
|
378
|
-
void wait().catch((error) => this.
|
|
427
|
+
void wait().catch((error) => this.reportListenerError(error));
|
|
379
428
|
}
|
|
380
429
|
|
|
381
430
|
stopExecutionOutputDrain() {
|
|
@@ -398,18 +447,18 @@ export class OndaAudioProcessor {
|
|
|
398
447
|
);
|
|
399
448
|
}
|
|
400
449
|
|
|
401
|
-
|
|
450
|
+
reportListenerError(error) {
|
|
402
451
|
queueMicrotask(() => {
|
|
403
452
|
throw error;
|
|
404
453
|
});
|
|
405
454
|
}
|
|
406
455
|
|
|
407
|
-
|
|
456
|
+
notifyListeners(listeners, value) {
|
|
408
457
|
for (const listener of listeners) {
|
|
409
458
|
try {
|
|
410
|
-
listener(
|
|
459
|
+
listener(value);
|
|
411
460
|
} catch (error) {
|
|
412
|
-
this.
|
|
461
|
+
this.reportListenerError(error);
|
|
413
462
|
}
|
|
414
463
|
}
|
|
415
464
|
}
|
|
@@ -422,7 +471,7 @@ export class OndaAudioProcessor {
|
|
|
422
471
|
try {
|
|
423
472
|
this.consumeExecutionOutput(entry);
|
|
424
473
|
} catch (error) {
|
|
425
|
-
this.
|
|
474
|
+
this.reportListenerError(error);
|
|
426
475
|
}
|
|
427
476
|
},
|
|
428
477
|
);
|
|
@@ -540,7 +589,7 @@ export class OndaAudioProcessor {
|
|
|
540
589
|
entries: batchRecords.map((record) => record.entry),
|
|
541
590
|
...meta,
|
|
542
591
|
};
|
|
543
|
-
this.
|
|
592
|
+
this.notifyListeners(this.printListeners, batch);
|
|
544
593
|
} else {
|
|
545
594
|
const meta = delegate ?? { overflowCount: 0, transportDropCount: 0 };
|
|
546
595
|
delegate = null;
|
|
@@ -550,7 +599,7 @@ export class OndaAudioProcessor {
|
|
|
550
599
|
occurrences: batchRecords.map((record) => record.occurrence),
|
|
551
600
|
...meta,
|
|
552
601
|
};
|
|
553
|
-
this.
|
|
602
|
+
this.notifyListeners(this.delegateListeners, batch);
|
|
554
603
|
}
|
|
555
604
|
cursor = end;
|
|
556
605
|
}
|
|
@@ -562,7 +611,7 @@ export class OndaAudioProcessor {
|
|
|
562
611
|
entries: [],
|
|
563
612
|
...print,
|
|
564
613
|
};
|
|
565
|
-
this.
|
|
614
|
+
this.notifyListeners(this.printListeners, batch);
|
|
566
615
|
}
|
|
567
616
|
if (
|
|
568
617
|
delegate
|
|
@@ -574,7 +623,7 @@ export class OndaAudioProcessor {
|
|
|
574
623
|
occurrences: [],
|
|
575
624
|
...delegate,
|
|
576
625
|
};
|
|
577
|
-
this.
|
|
626
|
+
this.notifyListeners(this.delegateListeners, batch);
|
|
578
627
|
}
|
|
579
628
|
}
|
|
580
629
|
|
|
@@ -609,6 +658,29 @@ export class OndaAudioProcessor {
|
|
|
609
658
|
}
|
|
610
659
|
}
|
|
611
660
|
|
|
661
|
+
setParamElement(param, element, value) {
|
|
662
|
+
try {
|
|
663
|
+
if (!Array.isArray(this.paramInfo)) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
"setParamElement requires processor metadata; construct the adapter with createOndaAudioProcessor()",
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const info = paramElementInfoFor(
|
|
669
|
+
this.paramInfo,
|
|
670
|
+
param,
|
|
671
|
+
element,
|
|
672
|
+
this.paramElements,
|
|
673
|
+
);
|
|
674
|
+
return this.request("set-param", {
|
|
675
|
+
param,
|
|
676
|
+
element,
|
|
677
|
+
value: constrainParamValue(info, value, this.paramControls, this.paramElements),
|
|
678
|
+
});
|
|
679
|
+
} catch (error) {
|
|
680
|
+
return Promise.reject(error);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
612
684
|
setParamNormalized(param, value) {
|
|
613
685
|
try {
|
|
614
686
|
if (!Array.isArray(this.paramInfo)) {
|
|
@@ -630,11 +702,51 @@ export class OndaAudioProcessor {
|
|
|
630
702
|
}
|
|
631
703
|
}
|
|
632
704
|
|
|
705
|
+
setParamElementNormalized(param, element, value) {
|
|
706
|
+
try {
|
|
707
|
+
if (!Array.isArray(this.paramInfo)) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
"setParamElementNormalized requires processor metadata; construct the adapter with createOndaAudioProcessor()",
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
const info = paramElementInfoFor(
|
|
713
|
+
this.paramInfo,
|
|
714
|
+
param,
|
|
715
|
+
element,
|
|
716
|
+
this.paramElements,
|
|
717
|
+
);
|
|
718
|
+
const control = preparedParamControl(info, this.paramControls);
|
|
719
|
+
if (!control) {
|
|
720
|
+
throw new Error(`Onda parameter '${info.name}' has no scalar host-control domain`);
|
|
721
|
+
}
|
|
722
|
+
return this.request("set-param", {
|
|
723
|
+
param,
|
|
724
|
+
element,
|
|
725
|
+
value: control.normalizedToPlain(value),
|
|
726
|
+
});
|
|
727
|
+
} catch (error) {
|
|
728
|
+
return Promise.reject(error);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
633
732
|
trigger(event, values = {}) {
|
|
634
733
|
try {
|
|
635
734
|
this.assertOpen();
|
|
636
735
|
const events = this.metadata?.metadata?.events;
|
|
637
|
-
|
|
736
|
+
if (Number.isInteger(event)) {
|
|
737
|
+
if (!Array.isArray(events)) {
|
|
738
|
+
throw new Error(
|
|
739
|
+
"event triggers require processor metadata; construct the adapter with createOndaAudioProcessor()",
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
if (event >= events.length) {
|
|
743
|
+
const payload = new Uint8Array();
|
|
744
|
+
return this.request("event", { event, payload }, [payload.buffer]);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
const info = Number.isInteger(event)
|
|
748
|
+
? events?.[event]
|
|
749
|
+
: events?.find((entry) => entry.name === event);
|
|
638
750
|
if (!info) throw new Error(`unknown Onda event '${String(event)}'`);
|
|
639
751
|
let plan = this.eventPlans.get(info);
|
|
640
752
|
if (!plan) { plan = new PayloadPlan(info.schema); this.eventPlans.set(info, plan); }
|
|
@@ -643,6 +755,15 @@ export class OndaAudioProcessor {
|
|
|
643
755
|
} catch (error) { return Promise.reject(error); }
|
|
644
756
|
}
|
|
645
757
|
|
|
758
|
+
onExecutionError(listener) {
|
|
759
|
+
this.assertOpen();
|
|
760
|
+
if (typeof listener !== "function") {
|
|
761
|
+
throw new TypeError("execution-error listener must be a function");
|
|
762
|
+
}
|
|
763
|
+
this.executionErrorListeners.add(listener);
|
|
764
|
+
return () => this.executionErrorListeners.delete(listener);
|
|
765
|
+
}
|
|
766
|
+
|
|
646
767
|
onDelegates(listener) {
|
|
647
768
|
this.assertOpen();
|
|
648
769
|
if (typeof listener !== "function") {
|
|
@@ -775,6 +896,7 @@ export class OndaAudioProcessor {
|
|
|
775
896
|
this.stopExecutionOutputDrain();
|
|
776
897
|
for (const pending of this.pending.values()) pending.reject(this.closeReason);
|
|
777
898
|
this.pending.clear();
|
|
899
|
+
this.executionErrorListeners.clear();
|
|
778
900
|
this.delegateListeners.clear();
|
|
779
901
|
this.printListeners.clear();
|
|
780
902
|
}
|
package/src/param-metadata.js
CHANGED
|
@@ -16,3 +16,18 @@ export function paramAddress(params, selector) {
|
|
|
16
16
|
}
|
|
17
17
|
throw new Error(`unknown Onda parameter '${String(selector)}'`);
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
export function paramElementAddress(params, selector, element) {
|
|
21
|
+
const address = paramAddress(params, selector);
|
|
22
|
+
if (address.element !== null) {
|
|
23
|
+
throw new Error("an explicit element write requires a parameter name or index");
|
|
24
|
+
}
|
|
25
|
+
if (
|
|
26
|
+
!Number.isSafeInteger(element)
|
|
27
|
+
|| element < 0
|
|
28
|
+
|| element >= address.info.array_len
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(`Onda parameter '${address.info.name}' element index is out of bounds`);
|
|
31
|
+
}
|
|
32
|
+
return { info: address.info, element };
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Keep the AudioWorklet dependency graph relative-only so the published module can be loaded
|
|
2
|
+
// directly. The adapter tests verify these processor ABI values against @onda-lang/processor-abi.
|
|
3
|
+
export const PROCESSOR_EXECUTION_OK = 0;
|
|
4
|
+
export const PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE = 1;
|
|
5
|
+
export const PROCESSOR_EXECUTION_INPUT_REJECTED = 2;
|
|
6
|
+
export const PROCESSOR_BEGIN_BLOCK = 1 << 0;
|
|
7
|
+
export const PROCESSOR_END_BLOCK = 1 << 1;
|
|
8
|
+
export const PROCESSOR_FULL_BLOCK = PROCESSOR_BEGIN_BLOCK | PROCESSOR_END_BLOCK;
|
|
9
|
+
export const PROCESSOR_INIT_PRESERVE_PINNED = 0;
|
|
10
|
+
export const PROCESSOR_INIT_FULL = 1;
|
package/src/worklet.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { paramAddress } from "./param-metadata.js";
|
|
1
|
+
import { paramAddress, paramElementAddress } from "./param-metadata.js";
|
|
2
2
|
import {
|
|
3
3
|
EXECUTION_OPERATION_EVENT,
|
|
4
4
|
EXECUTION_OPERATION_INIT,
|
|
@@ -7,16 +7,25 @@ import {
|
|
|
7
7
|
openExecutionOutputRing,
|
|
8
8
|
writeExecutionOutputRing,
|
|
9
9
|
} from "./execution-output-ring.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import {
|
|
11
|
+
PROCESSOR_BEGIN_BLOCK,
|
|
12
|
+
PROCESSOR_END_BLOCK,
|
|
13
|
+
PROCESSOR_EXECUTION_OK,
|
|
14
|
+
PROCESSOR_EXECUTION_INPUT_REJECTED,
|
|
15
|
+
PROCESSOR_INIT_FULL,
|
|
16
|
+
PROCESSOR_INIT_PRESERVE_PINNED,
|
|
17
|
+
} from "./processor-constants.js";
|
|
16
18
|
const ONDA_AUDIO_WORKLET_PROCESSOR_NAME = "onda-wasm-processor";
|
|
17
19
|
const DEFAULT_EVENT_PAYLOAD_CAPACITY_BYTES = 64 * 1024;
|
|
18
20
|
const DEFAULT_DELEGATE_CAPACITY_BYTES = 64 * 1024;
|
|
19
21
|
const DEFAULT_PRINT_CAPACITY_BYTES = 64 * 1024;
|
|
22
|
+
|
|
23
|
+
class ProcessorExecutionError extends Error {
|
|
24
|
+
constructor(message, status) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.status = status;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
20
29
|
const DELEGATE_BATCH_SIZE_BYTES = 20;
|
|
21
30
|
const PRINT_BATCH_SIZE_BYTES = 20;
|
|
22
31
|
const EXECUTION_OUTPUT_SIZE_BYTES = 12;
|
|
@@ -289,7 +298,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
289
298
|
this.refreshMemoryCache(true);
|
|
290
299
|
this.invalidateState();
|
|
291
300
|
if (processorOptions.initialize === true) {
|
|
292
|
-
this.init(
|
|
301
|
+
this.init(PROCESSOR_INIT_FULL);
|
|
293
302
|
}
|
|
294
303
|
this.allocationLocked = true;
|
|
295
304
|
this.port.onmessage = (event) => this.handleMessage(event.data ?? {});
|
|
@@ -485,8 +494,10 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
485
494
|
}
|
|
486
495
|
}
|
|
487
496
|
|
|
488
|
-
setParam(selector, value) {
|
|
489
|
-
const { info: param, element } =
|
|
497
|
+
setParam(selector, value, explicitElement = null) {
|
|
498
|
+
const { info: param, element } = explicitElement === null
|
|
499
|
+
? paramAddress(this.paramInfo, selector)
|
|
500
|
+
: paramElementAddress(this.paramInfo, selector, explicitElement);
|
|
490
501
|
if (value === undefined) {
|
|
491
502
|
throw new Error(`Onda parameter '${param.name}' requires a value`);
|
|
492
503
|
}
|
|
@@ -517,10 +528,10 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
517
528
|
}
|
|
518
529
|
|
|
519
530
|
init(mode) {
|
|
520
|
-
if (mode !==
|
|
531
|
+
if (mode !== PROCESSOR_INIT_PRESERVE_PINNED && mode !== PROCESSOR_INIT_FULL) {
|
|
521
532
|
throw new Error(`invalid Onda init mode '${String(mode)}'`);
|
|
522
533
|
}
|
|
523
|
-
if (mode ===
|
|
534
|
+
if (mode === PROCESSOR_INIT_PRESERVE_PINNED && !this.initialized) {
|
|
524
535
|
throw new Error("full initialization is required before preserving pinned state");
|
|
525
536
|
}
|
|
526
537
|
this.runInitialization(mode);
|
|
@@ -538,7 +549,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
538
549
|
this.blockCursor = blockCursor;
|
|
539
550
|
}
|
|
540
551
|
|
|
541
|
-
runInitialization(mode, afterInitialize) {
|
|
552
|
+
runInitialization(mode, afterInitialize, collectOutput = true) {
|
|
542
553
|
// Reinitializing state does not create a compile-block boundary. Retain
|
|
543
554
|
// the host-side position so the next process segment cannot synthesize an
|
|
544
555
|
// extra BEGIN_BLOCK or postpone the matching END_BLOCK.
|
|
@@ -548,7 +559,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
548
559
|
// leaves the processor on the silent pending path.
|
|
549
560
|
this.invalidateState();
|
|
550
561
|
this.refreshMemoryCache();
|
|
551
|
-
this.prepareExecutionOutput();
|
|
562
|
+
if (collectOutput) this.prepareExecutionOutput();
|
|
552
563
|
const status = this.exports.onda_processor_init(
|
|
553
564
|
this.paramsPtr,
|
|
554
565
|
this.statePtr,
|
|
@@ -557,13 +568,15 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
557
568
|
this.bufferFramesPtr,
|
|
558
569
|
this.bufferChannelsPtr,
|
|
559
570
|
this.bufferSampleRatesPtr,
|
|
560
|
-
this.executionOutputPtr,
|
|
571
|
+
collectOutput ? this.executionOutputPtr : 0,
|
|
561
572
|
);
|
|
562
573
|
// Generated failures retain diagnostic prints but clear delegates.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
574
|
+
if (collectOutput) {
|
|
575
|
+
this.publishExecutionOutput(
|
|
576
|
+
EXECUTION_OPERATION_INIT,
|
|
577
|
+
0,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
567
580
|
this.checkExecutionStatus(status, "processor init");
|
|
568
581
|
afterInitialize?.();
|
|
569
582
|
this.commitInitializedState(blockCursor);
|
|
@@ -576,9 +589,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
576
589
|
}
|
|
577
590
|
|
|
578
591
|
checkExecutionStatus(status, operation) {
|
|
579
|
-
if (status ===
|
|
592
|
+
if (status === PROCESSOR_EXECUTION_OK) return;
|
|
580
593
|
this.invalidateState();
|
|
581
|
-
throw new
|
|
594
|
+
throw new ProcessorExecutionError(
|
|
595
|
+
`${operation} failed with Onda execution status ${String(status)}`,
|
|
596
|
+
status,
|
|
597
|
+
);
|
|
582
598
|
}
|
|
583
599
|
|
|
584
600
|
createSnapshot() {
|
|
@@ -609,7 +625,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
609
625
|
// The ABI restore base is a fresh post-init image, so scratch and
|
|
610
626
|
// control-mirror state never leak across a restore. Initialization and
|
|
611
627
|
// overlay form one lifecycle transition: neither partial result is ready.
|
|
612
|
-
this.runInitialization(
|
|
628
|
+
this.runInitialization(PROCESSOR_INIT_FULL, () => {
|
|
613
629
|
const state = this.stateBytes;
|
|
614
630
|
for (const entry of this.snapshotInfo) {
|
|
615
631
|
const packedOffset = Number(entry.packed_snapshot_byte_offset);
|
|
@@ -622,7 +638,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
622
638
|
);
|
|
623
639
|
this.normalizeSnapshotIntegerRange(entry, state, physicalOffset, byteSize);
|
|
624
640
|
}
|
|
625
|
-
});
|
|
641
|
+
}, false);
|
|
626
642
|
}
|
|
627
643
|
|
|
628
644
|
normalizeSnapshotIntegerRange(entry, state, offset, byteSize) {
|
|
@@ -710,6 +726,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
710
726
|
this.setParam(
|
|
711
727
|
message.param ?? message.name ?? message.index,
|
|
712
728
|
message.value,
|
|
729
|
+
message.element ?? null,
|
|
713
730
|
);
|
|
714
731
|
this.postResponse(message, { type: "onda-ok", operation: message.type });
|
|
715
732
|
} else if (message.type === "init") {
|
|
@@ -751,17 +768,20 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
751
768
|
throw new Error(`unknown Onda worklet operation '${String(message.type)}'`);
|
|
752
769
|
}
|
|
753
770
|
} catch (error) {
|
|
754
|
-
|
|
771
|
+
const response = {
|
|
755
772
|
type: "onda-error",
|
|
756
773
|
operation: message.type ?? "unknown",
|
|
757
774
|
requestId: message.requestId,
|
|
758
775
|
error: String(error && error.message ? error.message : error),
|
|
759
|
-
}
|
|
776
|
+
};
|
|
777
|
+
if (Number.isInteger(error?.status)) response.status = error.status;
|
|
778
|
+
this.port.postMessage(response);
|
|
760
779
|
}
|
|
761
780
|
}
|
|
762
781
|
|
|
763
782
|
dispatchEvent(selector, payload) {
|
|
764
783
|
this.requireInitialized("event dispatch");
|
|
784
|
+
if (Number.isInteger(selector) && selector >= this.eventInfo.length) return;
|
|
765
785
|
const eventId = Number.isInteger(selector)
|
|
766
786
|
? selector
|
|
767
787
|
: this.eventInfo.findIndex((event) => event.name === selector);
|
|
@@ -794,7 +814,12 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
794
814
|
this.bufferSampleRatesPtr,
|
|
795
815
|
this.executionOutputPtr,
|
|
796
816
|
);
|
|
797
|
-
if (status === PROCESSOR_EXECUTION_INPUT_REJECTED)
|
|
817
|
+
if (status === PROCESSOR_EXECUTION_INPUT_REJECTED) {
|
|
818
|
+
throw new ProcessorExecutionError(
|
|
819
|
+
`event '${event.name}' input rejected: invalid payload or insufficient workspace`,
|
|
820
|
+
status,
|
|
821
|
+
);
|
|
822
|
+
}
|
|
798
823
|
this.publishExecutionOutput(
|
|
799
824
|
EXECUTION_OPERATION_EVENT,
|
|
800
825
|
eventId,
|
|
@@ -1559,8 +1584,8 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1559
1584
|
this.blockSize - startFrame,
|
|
1560
1585
|
);
|
|
1561
1586
|
const endsBlock = startFrame + segmentFrames === this.blockSize;
|
|
1562
|
-
const flags = (startFrame === 0 ?
|
|
1563
|
-
| (endsBlock ?
|
|
1587
|
+
const flags = (startFrame === 0 ? PROCESSOR_BEGIN_BLOCK : 0)
|
|
1588
|
+
| (endsBlock ? PROCESSOR_END_BLOCK : 0);
|
|
1564
1589
|
|
|
1565
1590
|
this.marshalInputSegment(
|
|
1566
1591
|
inputs,
|
|
@@ -1578,12 +1603,13 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1578
1603
|
EXECUTION_OPERATION_PROCESS,
|
|
1579
1604
|
0,
|
|
1580
1605
|
);
|
|
1581
|
-
if (status !==
|
|
1606
|
+
if (status !== PROCESSOR_EXECUTION_OK) {
|
|
1582
1607
|
this.invalidateState();
|
|
1583
1608
|
this.clearOutputs(outputs);
|
|
1584
1609
|
this.port.postMessage({
|
|
1585
1610
|
type: "onda-error",
|
|
1586
1611
|
operation: "process",
|
|
1612
|
+
status,
|
|
1587
1613
|
error: `processor process failed with Onda execution status ${String(status)}`,
|
|
1588
1614
|
});
|
|
1589
1615
|
return true;
|