@onda-lang/webaudio 0.5.4 → 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 +67 -3
- package/package.json +2 -2
- package/src/index.d.ts +27 -3
- package/src/index.js +95 -4
- package/src/worklet.js +105 -26
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ const processor = await createOndaAudioProcessor(audioContext, artifact, {
|
|
|
13
13
|
});
|
|
14
14
|
processor.node.connect(audioContext.destination);
|
|
15
15
|
await processor.setParam("gain", 0.75);
|
|
16
|
+
await processor.setParamNormalized("cutoff", 0.5);
|
|
16
17
|
const snapshot = await processor.snapshot();
|
|
17
18
|
```
|
|
18
19
|
|
|
@@ -21,6 +22,46 @@ metadata, marshals declared scalar widths, schedules arbitrary render quanta acr
|
|
|
21
22
|
blocks, and provides request/response helpers for parameters, events, buffers, control outputs,
|
|
22
23
|
reset, and portable snapshots.
|
|
23
24
|
|
|
25
|
+
## Parameters
|
|
26
|
+
|
|
27
|
+
`params` passed during construction and values passed to `setParam()` are plain Onda values. The
|
|
28
|
+
adapter clamps ranged scalar values and snaps stepped domains before posting them; the real-time
|
|
29
|
+
worklet only writes the resulting canonical value in the declared scalar representation. Unknown
|
|
30
|
+
initial parameter names or indices are rejected before the worklet node is constructed.
|
|
31
|
+
|
|
32
|
+
`setParamNormalized()` accepts a host value in `[0, 1]`. The adapter uses the artifact descriptor's
|
|
33
|
+
linear or logarithmic scale and step metadata to convert it to a plain value before posting the
|
|
34
|
+
write to the worklet. Boolean normalized values use the `0.5` threshold. For example:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
await processor.setParam("cutoff", 440); // exactly 440 Hz
|
|
38
|
+
await processor.setParamNormalized("cutoff", 0.5); // midpoint in its declared control scale
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The same synchronous conversion helpers are re-exported for UI display and typed entry:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import {
|
|
45
|
+
paramNormalizedToPlain,
|
|
46
|
+
paramPlainToNormalized,
|
|
47
|
+
} from "@onda-lang/webaudio";
|
|
48
|
+
|
|
49
|
+
const cutoff = processor.metadata.metadata.params.find(
|
|
50
|
+
(param) => param.name === "cutoff",
|
|
51
|
+
);
|
|
52
|
+
const displayValue = paramNormalizedToPlain(cutoff, sliderValue);
|
|
53
|
+
const sliderValueFor440Hz = paramPlainToNormalized(cutoff, 440);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The helpers preserve exact endpoints, clamp and snap consistently with adapter writes, and reject
|
|
57
|
+
arrays or numeric parameters without a host-control domain. `scale`, `curve`, `unit`, `step_repr`,
|
|
58
|
+
and `step_count` remain available on each parameter's descriptor metadata for control construction
|
|
59
|
+
and formatting. For repeated UI conversion, `createParamControl(param)` prepares and validates the
|
|
60
|
+
descriptor once and returns bound conversion methods.
|
|
61
|
+
|
|
62
|
+
Controlled `i64` domains use the descriptor's exact binary64 integer range. Full-width unranged
|
|
63
|
+
`i64` values continue to use `bigint` when written directly.
|
|
64
|
+
|
|
24
65
|
The artifact must be compiled for exactly `audioContext.sampleRate`; the adapter rejects a mismatch
|
|
25
66
|
before registering the node so sample-rate-derived language semantics cannot silently drift. A Web
|
|
26
67
|
Audio processor must expose at least one audio input or output, because an empty callback surface
|
|
@@ -49,12 +90,35 @@ After construction, the normal f32 render callback reuses cached Wasm-memory vie
|
|
|
49
90
|
host-side allocation or memory growth. Full-block f32 inputs and outputs use typed-array bulk copies;
|
|
50
91
|
segmented callbacks and other ABI scalar widths use preallocated typed views with conversion loops
|
|
51
92
|
(i64 input conversion necessarily creates JavaScript `BigInt` values). External buffers are copied
|
|
52
|
-
into Wasm with typed-array bulk operations during construction.
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
55
116
|
|
|
56
117
|
Artifact descriptors and module exports are validated by the shared, compiler-free
|
|
57
118
|
`@onda-lang/processor-abi` package before anything reaches the rendering thread.
|
|
119
|
+
If generated init, event, or process code returns a nonzero execution status, the adapter reports
|
|
120
|
+
an `onda-error`, latches the failed state, and emits silence without re-entering processing. A
|
|
121
|
+
successful reset clears the latch after reinitializing state.
|
|
58
122
|
|
|
59
123
|
Dynamic event storage is also allocated before rendering. Its default capacity is 64 KiB per
|
|
60
124
|
processor with dynamic events and can be changed explicitly:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onda-lang/webaudio",
|
|
3
|
-
"version": "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.
|
|
35
|
+
"@onda-lang/processor-abi": "0.7.0"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
package/src/index.d.ts
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
export const ONDA_AUDIO_WORKLET_PROCESSOR_NAME: "onda-wasm-processor";
|
|
2
2
|
|
|
3
|
-
export type {
|
|
4
|
-
|
|
3
|
+
export type {
|
|
4
|
+
OndaParamDomain,
|
|
5
|
+
OndaPreparedParamControl,
|
|
6
|
+
OndaProcessorArtifact,
|
|
7
|
+
OndaProcessorMetadata,
|
|
8
|
+
} from "@onda-lang/processor-abi";
|
|
9
|
+
export {
|
|
10
|
+
createParamDomain,
|
|
11
|
+
createParamControl,
|
|
12
|
+
constrainParamPlain,
|
|
13
|
+
paramNormalizedToPlain,
|
|
14
|
+
paramPlainToNormalized,
|
|
15
|
+
} from "@onda-lang/processor-abi";
|
|
16
|
+
import type {
|
|
17
|
+
OndaProcessorArtifact,
|
|
18
|
+
OndaProcessorMetadata,
|
|
19
|
+
} from "@onda-lang/processor-abi";
|
|
5
20
|
|
|
6
21
|
export interface OndaAudioProcessorOptions {
|
|
7
22
|
workletUrl?: string | URL;
|
|
23
|
+
/** Initial plain Onda parameter values, keyed or ordered by descriptor parameter. */
|
|
8
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
|
+
*/
|
|
9
29
|
buffers?: Record<string, unknown> | unknown[];
|
|
10
30
|
/** Preallocated capacity for dynamic event payloads. Defaults to 64 KiB. */
|
|
11
31
|
eventPayloadCapacityBytes?: number;
|
|
@@ -34,10 +54,14 @@ export function compileOndaProcessorModule(
|
|
|
34
54
|
): Promise<WebAssembly.Module>;
|
|
35
55
|
|
|
36
56
|
export class OndaAudioProcessor {
|
|
37
|
-
constructor(node: AudioWorkletNode);
|
|
57
|
+
constructor(node: AudioWorkletNode, metadata?: OndaProcessorMetadata | null);
|
|
38
58
|
readonly node: AudioWorkletNode;
|
|
59
|
+
readonly metadata: OndaProcessorMetadata | null;
|
|
39
60
|
request(type: string, fields?: Record<string, unknown>, transfer?: Transferable[]): Promise<any>;
|
|
61
|
+
/** Set a plain Onda value; ranged scalar values are clamped and snapped. */
|
|
40
62
|
setParam(param: string | number, value: unknown): Promise<any>;
|
|
63
|
+
/** Map a host value in [0, 1] through the descriptor and set the resulting plain value. */
|
|
64
|
+
setParamNormalized(param: string | number, value: number): Promise<any>;
|
|
41
65
|
trigger(event: string | number, values?: Record<string, unknown> | unknown[]): Promise<any>;
|
|
42
66
|
reset(): Promise<any>;
|
|
43
67
|
snapshot(): Promise<Uint8Array>;
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createParamControl,
|
|
2
3
|
validateProcessorArtifact,
|
|
3
4
|
validateProcessorModule,
|
|
4
5
|
} from "@onda-lang/processor-abi";
|
|
5
6
|
|
|
7
|
+
export {
|
|
8
|
+
createParamDomain,
|
|
9
|
+
createParamControl,
|
|
10
|
+
constrainParamPlain,
|
|
11
|
+
paramNormalizedToPlain,
|
|
12
|
+
paramPlainToNormalized,
|
|
13
|
+
} from "@onda-lang/processor-abi";
|
|
14
|
+
|
|
6
15
|
export const ONDA_AUDIO_WORKLET_PROCESSOR_NAME = "onda-wasm-processor";
|
|
7
16
|
|
|
8
17
|
const registrationByContext = new WeakMap();
|
|
@@ -65,7 +74,10 @@ function audioWorkletNodeOptionsFromValidated(
|
|
|
65
74
|
? { wasmBytes: wasm }
|
|
66
75
|
: { wasmModule: options.compiledModule }),
|
|
67
76
|
metadata,
|
|
68
|
-
params:
|
|
77
|
+
params: constrainInitialParamValues(
|
|
78
|
+
metadata.metadata.params,
|
|
79
|
+
options.params ?? {},
|
|
80
|
+
),
|
|
69
81
|
buffers: options.buffers ?? {},
|
|
70
82
|
eventPayloadCapacityBytes: options.eventPayloadCapacityBytes,
|
|
71
83
|
},
|
|
@@ -78,6 +90,50 @@ function audioWorkletNodeOptionsFromValidated(
|
|
|
78
90
|
return nodeOptions;
|
|
79
91
|
}
|
|
80
92
|
|
|
93
|
+
function paramInfoFor(paramInfo, selector) {
|
|
94
|
+
const info = Number.isInteger(selector)
|
|
95
|
+
? paramInfo[selector]
|
|
96
|
+
: paramInfo.find((candidate) => candidate.name === selector);
|
|
97
|
+
if (!info) {
|
|
98
|
+
throw new Error(`unknown Onda parameter '${String(selector)}'`);
|
|
99
|
+
}
|
|
100
|
+
return info;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function preparedParamControl(info, cache = null) {
|
|
104
|
+
if (Number(info.array_len) !== 1) return null;
|
|
105
|
+
if (info.scalar !== "bool" && info.param_control === null) return null;
|
|
106
|
+
let control = cache?.get(info);
|
|
107
|
+
if (!control) {
|
|
108
|
+
control = createParamControl(info);
|
|
109
|
+
cache?.set(info, control);
|
|
110
|
+
}
|
|
111
|
+
return control;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function constrainParamValue(info, value, cache = null) {
|
|
115
|
+
return preparedParamControl(info, cache)?.constrainPlain(value) ?? value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function constrainInitialParamValues(paramInfo, values) {
|
|
119
|
+
if (Array.isArray(values)) {
|
|
120
|
+
return values.map((value, index) => (
|
|
121
|
+
value === undefined
|
|
122
|
+
? value
|
|
123
|
+
: constrainParamValue(paramInfoFor(paramInfo, index), value)
|
|
124
|
+
));
|
|
125
|
+
}
|
|
126
|
+
if (values && typeof values === "object") {
|
|
127
|
+
return Object.fromEntries(
|
|
128
|
+
Object.entries(values).map(([name, value]) => [
|
|
129
|
+
name,
|
|
130
|
+
constrainParamValue(paramInfoFor(paramInfo, name), value),
|
|
131
|
+
]),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
throw new Error("params must be an array or object");
|
|
135
|
+
}
|
|
136
|
+
|
|
81
137
|
export async function registerOndaAudioWorklet(
|
|
82
138
|
context,
|
|
83
139
|
workletUrl = new URL("./worklet.js", import.meta.url),
|
|
@@ -123,7 +179,7 @@ export async function createOndaAudioProcessor(context, artifact, options = {})
|
|
|
123
179
|
false,
|
|
124
180
|
),
|
|
125
181
|
);
|
|
126
|
-
return new OndaAudioProcessor(node);
|
|
182
|
+
return new OndaAudioProcessor(node, validated.metadata);
|
|
127
183
|
}
|
|
128
184
|
|
|
129
185
|
export async function compileOndaProcessorModule(artifact) {
|
|
@@ -139,8 +195,11 @@ async function compileValidatedProcessorModule({ wasm, metadata }) {
|
|
|
139
195
|
}
|
|
140
196
|
|
|
141
197
|
export class OndaAudioProcessor {
|
|
142
|
-
constructor(node) {
|
|
198
|
+
constructor(node, metadata = null) {
|
|
143
199
|
this.node = node;
|
|
200
|
+
this.metadata = metadata;
|
|
201
|
+
this.paramInfo = metadata?.metadata?.params ?? null;
|
|
202
|
+
this.paramControls = new WeakMap();
|
|
144
203
|
this.nextRequestId = 1;
|
|
145
204
|
this.pending = new Map();
|
|
146
205
|
this.handleMessage = (event) => {
|
|
@@ -173,7 +232,39 @@ export class OndaAudioProcessor {
|
|
|
173
232
|
}
|
|
174
233
|
|
|
175
234
|
setParam(param, value) {
|
|
176
|
-
|
|
235
|
+
try {
|
|
236
|
+
if (!Array.isArray(this.paramInfo)) {
|
|
237
|
+
return this.request("set-param", { param, value });
|
|
238
|
+
}
|
|
239
|
+
const info = paramInfoFor(this.paramInfo ?? [], param);
|
|
240
|
+
return this.request("set-param", {
|
|
241
|
+
param,
|
|
242
|
+
value: constrainParamValue(info, value, this.paramControls),
|
|
243
|
+
});
|
|
244
|
+
} catch (error) {
|
|
245
|
+
return Promise.reject(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setParamNormalized(param, value) {
|
|
250
|
+
try {
|
|
251
|
+
if (!Array.isArray(this.paramInfo)) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
"setParamNormalized requires processor metadata; construct the adapter with createOndaAudioProcessor()",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const info = paramInfoFor(this.paramInfo, param);
|
|
257
|
+
const control = preparedParamControl(info, this.paramControls);
|
|
258
|
+
if (!control) {
|
|
259
|
+
throw new Error(`Onda parameter '${info.name}' has no scalar host-control domain`);
|
|
260
|
+
}
|
|
261
|
+
return this.request("set-param", {
|
|
262
|
+
param,
|
|
263
|
+
value: control.normalizedToPlain(value),
|
|
264
|
+
});
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return Promise.reject(error);
|
|
267
|
+
}
|
|
177
268
|
}
|
|
178
269
|
|
|
179
270
|
trigger(event, values = {}) {
|
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
|
: [];
|
|
@@ -112,6 +115,7 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
112
115
|
this.outputPtrs = [];
|
|
113
116
|
this.outputCapacityFrames = 0;
|
|
114
117
|
this.blockCursor = 0;
|
|
118
|
+
this.executionFailed = false;
|
|
115
119
|
|
|
116
120
|
const paramBytes = Number(metadata.runtime?.param_size_bytes ?? 0);
|
|
117
121
|
if (!Number.isInteger(paramBytes) || paramBytes < 0) {
|
|
@@ -338,14 +342,28 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
338
342
|
) {
|
|
339
343
|
throw new Error(`Onda parameter '${param.name}' has invalid storage metadata`);
|
|
340
344
|
}
|
|
341
|
-
this.writeStorage(
|
|
345
|
+
this.writeStorage(
|
|
346
|
+
this.paramsPtr + offset,
|
|
347
|
+
param,
|
|
348
|
+
value,
|
|
349
|
+
);
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
reset() {
|
|
345
353
|
this.refreshMemoryCache();
|
|
346
354
|
this.stateBytes.fill(0);
|
|
347
355
|
this.blockCursor = 0;
|
|
348
|
-
this.
|
|
356
|
+
this.executionFailed = false;
|
|
357
|
+
this.checkExecutionStatus(
|
|
358
|
+
this.exports.onda_init(this.paramsPtr, this.statePtr),
|
|
359
|
+
"processor init",
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
checkExecutionStatus(status, operation) {
|
|
364
|
+
if (status === 0) return;
|
|
365
|
+
this.executionFailed = true;
|
|
366
|
+
throw new Error(`${operation} failed with Onda execution status ${String(status)}`);
|
|
349
367
|
}
|
|
350
368
|
|
|
351
369
|
createSnapshot() {
|
|
@@ -536,14 +554,17 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
536
554
|
if (typeof handler !== "function") {
|
|
537
555
|
throw new Error(`missing WebAssembly export '${event.export}'`);
|
|
538
556
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
557
|
+
this.checkExecutionStatus(
|
|
558
|
+
handler(
|
|
559
|
+
this.eventPayloadPtr,
|
|
560
|
+
this.paramsPtr,
|
|
561
|
+
this.statePtr,
|
|
562
|
+
this.bufferPointersPtr,
|
|
563
|
+
this.bufferFramesPtr,
|
|
564
|
+
this.bufferChannelsPtr,
|
|
565
|
+
this.bufferSampleRatesPtr,
|
|
566
|
+
),
|
|
567
|
+
`event '${event.name}'`,
|
|
547
568
|
);
|
|
548
569
|
}
|
|
549
570
|
|
|
@@ -589,11 +610,21 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
589
610
|
|
|
590
611
|
bindInitialBuffers(options) {
|
|
591
612
|
this.bufferInfo.forEach((buffer, bufferId) => {
|
|
592
|
-
const supplied =
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (supplied === undefined) {
|
|
596
|
-
|
|
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;
|
|
597
628
|
}
|
|
598
629
|
const descriptor =
|
|
599
630
|
Array.isArray(supplied) || ArrayBuffer.isView(supplied)
|
|
@@ -614,7 +645,6 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
614
645
|
`Onda buffer '${buffer.name}' requires non-empty bound data`,
|
|
615
646
|
);
|
|
616
647
|
}
|
|
617
|
-
const declaredChannels = Number(buffer.static_channels ?? 0);
|
|
618
648
|
const channels = Number(descriptor.channels ?? declaredChannels);
|
|
619
649
|
if (!Number.isInteger(channels) || channels <= 0) {
|
|
620
650
|
throw new Error(`Onda buffer '${buffer.name}' requires channels > 0`);
|
|
@@ -637,25 +667,54 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
637
667
|
const elementSize = this.scalarByteSize(buffer.scalar);
|
|
638
668
|
const pointer = this.alloc(length * elementSize, elementSize);
|
|
639
669
|
this.writeScalarValues(pointer, buffer.scalar, data, length);
|
|
640
|
-
|
|
641
|
-
view.setUint32(this.bufferPointersPtr + bufferId * 4, pointer, true);
|
|
642
|
-
view.setInt32(this.bufferFramesPtr + bufferId * 4, frames, true);
|
|
643
|
-
view.setInt32(this.bufferChannelsPtr + bufferId * 4, channels, true);
|
|
644
|
-
view.setFloat32(
|
|
645
|
-
this.bufferSampleRatesPtr + bufferId * 4,
|
|
646
|
-
sampleRate,
|
|
647
|
-
true,
|
|
648
|
-
);
|
|
670
|
+
this.writeBufferDescriptor(bufferId, pointer, frames, channels, sampleRate);
|
|
649
671
|
this.bufferBindings[bufferId] = {
|
|
650
672
|
...buffer,
|
|
651
673
|
pointer,
|
|
652
674
|
frames,
|
|
653
675
|
channels,
|
|
654
676
|
sampleRate,
|
|
677
|
+
bound: true,
|
|
655
678
|
};
|
|
656
679
|
});
|
|
657
680
|
}
|
|
658
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
|
+
|
|
659
718
|
readBuffer(selector) {
|
|
660
719
|
const bufferId = Number.isInteger(selector)
|
|
661
720
|
? selector
|
|
@@ -1110,8 +1169,18 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1110
1169
|
);
|
|
1111
1170
|
}
|
|
1112
1171
|
|
|
1172
|
+
clearOutputs(outputs) {
|
|
1173
|
+
for (const bus of outputs) {
|
|
1174
|
+
for (const channel of bus) channel.fill(0);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1113
1178
|
process(inputs, outputs) {
|
|
1114
1179
|
this.refreshMemoryCache();
|
|
1180
|
+
if (this.executionFailed) {
|
|
1181
|
+
this.clearOutputs(outputs);
|
|
1182
|
+
return true;
|
|
1183
|
+
}
|
|
1115
1184
|
const frames = this.audioFrameCount(inputs, outputs);
|
|
1116
1185
|
|
|
1117
1186
|
let callbackOffset = 0;
|
|
@@ -1132,11 +1201,21 @@ class OndaWasmProcessor extends AudioWorkletProcessor {
|
|
|
1132
1201
|
startFrame,
|
|
1133
1202
|
segmentFrames,
|
|
1134
1203
|
);
|
|
1135
|
-
this.invokeProcessSegment(
|
|
1204
|
+
const status = this.invokeProcessSegment(
|
|
1136
1205
|
startFrame,
|
|
1137
1206
|
segmentFrames,
|
|
1138
1207
|
flags,
|
|
1139
1208
|
);
|
|
1209
|
+
if (status !== 0) {
|
|
1210
|
+
this.executionFailed = true;
|
|
1211
|
+
this.clearOutputs(outputs);
|
|
1212
|
+
this.port.postMessage({
|
|
1213
|
+
type: "onda-error",
|
|
1214
|
+
operation: "process",
|
|
1215
|
+
error: `processor process failed with Onda execution status ${String(status)}`,
|
|
1216
|
+
});
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1140
1219
|
this.marshalOutputSegment(
|
|
1141
1220
|
outputs,
|
|
1142
1221
|
frames,
|