@effetune/dsp 0.0.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.
@@ -0,0 +1,859 @@
1
+ const REQUIRED_FUNCTION_EXPORTS = [
2
+ 'malloc',
3
+ 'free',
4
+ 'et_abi_version',
5
+ 'et_build_flags',
6
+ 'et_kernel_count',
7
+ 'et_kernel_name',
8
+ 'et_kernel_params_hash',
9
+ 'et_kernel_param_bytes_capacity',
10
+ 'et_kernel_asset_capacity',
11
+ 'et_engine_memory_required',
12
+ 'et_engine_create',
13
+ 'et_engine_destroy',
14
+ 'et_engine_prepare',
15
+ 'et_engine_reset',
16
+ 'et_engine_set_telemetry_rate',
17
+ 'et_instance_create',
18
+ 'et_instance_destroy',
19
+ 'et_instance_reset',
20
+ 'et_instance_latency',
21
+ 'et_instance_set_tap',
22
+ 'et_instance_set_seed',
23
+ 'et_instance_set_params',
24
+ 'et_instance_set_param_bytes',
25
+ 'et_instance_asset_begin',
26
+ 'et_instance_asset_commit',
27
+ 'et_instance_asset_abort',
28
+ 'et_instance_asset_state',
29
+ 'et_instance_process',
30
+ 'et_arena_combined_ptr',
31
+ 'et_arena_bus_ptr',
32
+ 'et_arena_scratch_ptr',
33
+ 'et_scratch_ptr',
34
+ 'et_telemetry_staging_ptr',
35
+ 'et_telemetry_capacity',
36
+ 'et_telemetry_read',
37
+ 'et_pipeline_configure',
38
+ 'et_pipeline_process'
39
+ ];
40
+
41
+ const ET_OK = 0;
42
+ const ET_ERR_STATE = -2;
43
+ const SCRATCH_BYTES = 4096;
44
+ const WASI_ERRNO_SUCCESS = 0;
45
+
46
+ function defaultWarning(message) {
47
+ if (globalThis.console?.warn) {
48
+ globalThis.console.warn(message);
49
+ }
50
+ }
51
+
52
+ function defaultDebugWrite(message) {
53
+ if (globalThis.console?.error) {
54
+ globalThis.console.error(message);
55
+ }
56
+ }
57
+
58
+ function isArrayBuffer(value) {
59
+ return value instanceof ArrayBuffer;
60
+ }
61
+
62
+ function toUint8View(value, label) {
63
+ if (value instanceof Uint8Array) {
64
+ return value;
65
+ }
66
+ if (isArrayBuffer(value)) {
67
+ return new Uint8Array(value);
68
+ }
69
+ if (ArrayBuffer.isView(value)) {
70
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
71
+ }
72
+ throw new TypeError(`${label} must be an ArrayBuffer or typed-array view`);
73
+ }
74
+
75
+ function decodeUtf8(bytes) {
76
+ if (typeof TextDecoder === 'function') {
77
+ return new TextDecoder().decode(bytes);
78
+ }
79
+ let text = '';
80
+ for (let i = 0; i < bytes.length; i++) {
81
+ text += String.fromCharCode(bytes[i]);
82
+ }
83
+ return text;
84
+ }
85
+
86
+ function encodeUtf8(text) {
87
+ if (typeof TextEncoder === 'function') {
88
+ return new TextEncoder().encode(text);
89
+ }
90
+ const bytes = new Uint8Array(text.length);
91
+ for (let i = 0; i < text.length; i++) {
92
+ const code = text.charCodeAt(i);
93
+ if (code > 0x7f) {
94
+ throw new TypeError('A TextEncoder is required for non-ASCII DSP names');
95
+ }
96
+ bytes[i] = code;
97
+ }
98
+ return bytes;
99
+ }
100
+
101
+ function mergeImports(base, extra) {
102
+ if (!extra) return base;
103
+ const merged = { ...base };
104
+ for (const [moduleName, imports] of Object.entries(extra)) {
105
+ merged[moduleName] = { ...(base[moduleName] || {}), ...imports };
106
+ }
107
+ return merged;
108
+ }
109
+
110
+ export class DspBindingError extends Error {
111
+ constructor(message) {
112
+ super(message);
113
+ this.name = 'DspBindingError';
114
+ }
115
+ }
116
+
117
+ export function createDspImports({
118
+ getMemory = () => null,
119
+ debug = false,
120
+ debugWrite = defaultDebugWrite,
121
+ onMemoryGrowth = () => {}
122
+ } = {}) {
123
+ const fdWrite = (fd, iovPtr, iovCount, writtenPtr) => {
124
+ const memory = getMemory();
125
+ if (!memory?.buffer) return WASI_ERRNO_SUCCESS;
126
+
127
+ let written = 0;
128
+ const chunks = [];
129
+ try {
130
+ const data = new DataView(memory.buffer);
131
+ for (let i = 0; i < iovCount; i++) {
132
+ const entry = iovPtr + i * 8;
133
+ const ptr = data.getUint32(entry, true);
134
+ const length = data.getUint32(entry + 4, true);
135
+ if (ptr + length > memory.buffer.byteLength) break;
136
+ written += length;
137
+ if (debug && length > 0) {
138
+ chunks.push(new Uint8Array(memory.buffer, ptr, length));
139
+ }
140
+ }
141
+ if (writtenPtr + 4 <= memory.buffer.byteLength) {
142
+ data.setUint32(writtenPtr, written, true);
143
+ }
144
+ } catch {
145
+ return WASI_ERRNO_SUCCESS;
146
+ }
147
+
148
+ if (debug && chunks.length > 0 && (fd === 1 || fd === 2)) {
149
+ const total = new Uint8Array(written);
150
+ let offset = 0;
151
+ for (const chunk of chunks) {
152
+ total.set(chunk, offset);
153
+ offset += chunk.length;
154
+ }
155
+ debugWrite(decodeUtf8(total));
156
+ }
157
+ return WASI_ERRNO_SUCCESS;
158
+ };
159
+
160
+ return {
161
+ wasi_snapshot_preview1: {
162
+ proc_exit(code) {
163
+ throw new DspBindingError(`WASM requested proc_exit(${code})`);
164
+ },
165
+ fd_write: fdWrite,
166
+ fd_close() {
167
+ return WASI_ERRNO_SUCCESS;
168
+ },
169
+ fd_seek() {
170
+ return WASI_ERRNO_SUCCESS;
171
+ }
172
+ },
173
+ env: {
174
+ emscripten_notify_memory_growth() {
175
+ onMemoryGrowth();
176
+ }
177
+ }
178
+ };
179
+ }
180
+
181
+ export class DspEngineBinding {
182
+ constructor(instance, {
183
+ warning = defaultWarning,
184
+ onUnexpectedMemoryGrowth = null
185
+ } = {}) {
186
+ this.instance = instance?.instance || instance;
187
+ this.exports = this.instance?.exports;
188
+ this.warning = warning;
189
+ this.onUnexpectedMemoryGrowth = onUnexpectedMemoryGrowth;
190
+ this.engine = 0;
191
+ this.prepared = false;
192
+ this.failed = false;
193
+ this.memoryGrowthViolation = false;
194
+ this.lastTelemetryDroppedFrames = 0;
195
+ this._preparing = false;
196
+ this._memoryBuffer = null;
197
+ this._warned = new Set();
198
+ this._arenaViews = null;
199
+ this._arenaRanges = [];
200
+ this._maxChannels = 0;
201
+ this._maxFrames = 0;
202
+ this._telemetryStagingPtr = 0;
203
+ this._telemetryDroppedPtr = 0;
204
+ this._telemetryCapacity = 0;
205
+
206
+ this._validateExports();
207
+ this.memory = this.exports.memory;
208
+ this._refreshViews(true);
209
+ }
210
+
211
+ _validateExports() {
212
+ if (!this.exports || typeof this.exports !== 'object') {
213
+ throw new DspBindingError('WASM instance exports are unavailable');
214
+ }
215
+ if (!this.exports.memory?.buffer) {
216
+ throw new DspBindingError('Missing WASM export: memory');
217
+ }
218
+ for (const name of REQUIRED_FUNCTION_EXPORTS) {
219
+ if (typeof this.exports[name] !== 'function') {
220
+ throw new DspBindingError(`Missing WASM export: ${name}`);
221
+ }
222
+ }
223
+ }
224
+
225
+ _warnOnce(key, message) {
226
+ if (this._warned.has(key)) return;
227
+ this._warned.add(key);
228
+ this.warning(`[dsp-wasm] ${message}`);
229
+ }
230
+
231
+ _refreshViews(initial = false) {
232
+ const buffer = this.memory.buffer;
233
+ if (buffer === this._memoryBuffer) return false;
234
+
235
+ const unexpected = !initial && !this._preparing && this._memoryBuffer !== null;
236
+ this._memoryBuffer = buffer;
237
+ this.u8 = new Uint8Array(buffer);
238
+ this.f32 = new Float32Array(buffer);
239
+ this.dataView = new DataView(buffer);
240
+ this._arenaViews = null;
241
+ this._arenaRanges = [];
242
+
243
+ if (unexpected) {
244
+ this.memoryGrowthViolation = true;
245
+ this._warnOnce('memory-growth', 'memory.buffer changed outside engine preparation');
246
+ if (typeof this.onUnexpectedMemoryGrowth === 'function') {
247
+ this.onUnexpectedMemoryGrowth();
248
+ }
249
+ }
250
+ return true;
251
+ }
252
+
253
+ handleMemoryGrowthNotification() {
254
+ return this._refreshViews();
255
+ }
256
+
257
+ checkMemoryBuffer() {
258
+ const changed = this._refreshViews();
259
+ return changed && this.memoryGrowthViolation && !this._preparing;
260
+ }
261
+
262
+ _assertRange(ptr, byteLength, label) {
263
+ if (!Number.isInteger(ptr) || ptr < 0 || !Number.isInteger(byteLength) || byteLength < 0 ||
264
+ ptr > this._memoryBuffer.byteLength - byteLength) {
265
+ throw new DspBindingError(`${label} points outside WASM memory`);
266
+ }
267
+ }
268
+
269
+ _writeScratchString(text) {
270
+ if (!this.engine || !this.prepared) {
271
+ throw new DspBindingError('DSP engine has not been prepared');
272
+ }
273
+ const bytes = encodeUtf8(String(text));
274
+ if (bytes.length + 1 > SCRATCH_BYTES) {
275
+ throw new DspBindingError('DSP name exceeds the scratch-buffer capacity');
276
+ }
277
+ this._refreshViews();
278
+ const ptr = this.exports.et_scratch_ptr(this.engine) >>> 0;
279
+ this._assertRange(ptr, SCRATCH_BYTES, 'DSP scratch buffer');
280
+ this.u8.fill(0, ptr, ptr + bytes.length + 1);
281
+ this.u8.set(bytes, ptr);
282
+ return ptr;
283
+ }
284
+
285
+ getAbiVersion() {
286
+ return this.exports.et_abi_version() >>> 0;
287
+ }
288
+
289
+ getBuildFlags() {
290
+ return this.exports.et_build_flags() >>> 0;
291
+ }
292
+
293
+ getKernelCount() {
294
+ return this.exports.et_kernel_count() >>> 0;
295
+ }
296
+
297
+ getKernelName(index) {
298
+ if (!Number.isInteger(index) || index < 0 || index >= this.getKernelCount()) {
299
+ throw new RangeError('Kernel index is out of range');
300
+ }
301
+ this._refreshViews();
302
+ const useEngineScratch = Boolean(this.engine && this.prepared);
303
+ const ptr = useEngineScratch
304
+ ? this.exports.et_scratch_ptr(this.engine) >>> 0
305
+ : this.exports.malloc(SCRATCH_BYTES) >>> 0;
306
+ if (!ptr) throw new DspBindingError('Unable to allocate kernel-name staging memory');
307
+ try {
308
+ this._refreshViews();
309
+ this._assertRange(ptr, SCRATCH_BYTES, 'DSP kernel-name buffer');
310
+ const length = this.exports.et_kernel_name(index, ptr, SCRATCH_BYTES);
311
+ if (!Number.isInteger(length) || length < 0 || length >= SCRATCH_BYTES) {
312
+ throw new DspBindingError(`Invalid kernel name length for index ${index}`);
313
+ }
314
+ return decodeUtf8(this.u8.subarray(ptr, ptr + length));
315
+ } finally {
316
+ if (!useEngineScratch) this.exports.free(ptr);
317
+ }
318
+ }
319
+
320
+ getKernelParamsHash(index) {
321
+ if (!Number.isInteger(index) || index < 0 || index >= this.getKernelCount()) {
322
+ throw new RangeError('Kernel index is out of range');
323
+ }
324
+ return this.exports.et_kernel_params_hash(index) >>> 0;
325
+ }
326
+
327
+ getKernelParamBytesCapacity(index) {
328
+ if (!Number.isInteger(index) || index < 0 || index >= this.getKernelCount()) {
329
+ throw new RangeError('Kernel index is out of range');
330
+ }
331
+ return this.exports.et_kernel_param_bytes_capacity(index) >>> 0;
332
+ }
333
+
334
+ getKernelAssetCapacity(index, slot = 0) {
335
+ if (!Number.isInteger(index) || index < 0 || index >= this.getKernelCount()) {
336
+ throw new RangeError('Kernel index is out of range');
337
+ }
338
+ if (!Number.isInteger(slot) || slot < 0) {
339
+ throw new RangeError('Asset slot is out of range');
340
+ }
341
+ return this.exports.et_kernel_asset_capacity(index, slot) >>> 0;
342
+ }
343
+
344
+ hasDesignFft() {
345
+ return [
346
+ 'et_design_fft_create',
347
+ 'et_design_fft_destroy',
348
+ 'et_design_fft_input',
349
+ 'et_design_fft_output',
350
+ 'et_design_fft_forward',
351
+ 'et_design_fft_inverse'
352
+ ].every(name => typeof this.exports[name] === 'function');
353
+ }
354
+
355
+ createDesignFft(size) {
356
+ if (!this.hasDesignFft()) return 0;
357
+ const preparing = this._preparing;
358
+ this._preparing = true;
359
+ try {
360
+ return this.exports.et_design_fft_create(size >>> 0) >>> 0;
361
+ } finally {
362
+ this._refreshViews();
363
+ this._preparing = preparing;
364
+ }
365
+ }
366
+
367
+ destroyDesignFft(handle) {
368
+ if (handle && this.hasDesignFft()) this.exports.et_design_fft_destroy(handle >>> 0);
369
+ }
370
+
371
+ getDesignFftInput(handle) {
372
+ return this.exports.et_design_fft_input(handle >>> 0) >>> 0;
373
+ }
374
+
375
+ getDesignFftOutput(handle) {
376
+ return this.exports.et_design_fft_output(handle >>> 0) >>> 0;
377
+ }
378
+
379
+ runDesignFft(handle, inverse = false) {
380
+ const transform = inverse ? this.exports.et_design_fft_inverse : this.exports.et_design_fft_forward;
381
+ return transform(handle >>> 0);
382
+ }
383
+
384
+ getCapabilities() {
385
+ const kernels = [];
386
+ const count = this.getKernelCount();
387
+ for (let index = 0; index < count; index++) {
388
+ kernels.push({
389
+ name: this.getKernelName(index),
390
+ hash: this.getKernelParamsHash(index),
391
+ byteCapacity: this.getKernelParamBytesCapacity(index),
392
+ assetCapacity: this.getKernelAssetCapacity(index),
393
+ kernelIndex: index
394
+ });
395
+ }
396
+ const buildFlags = this.getBuildFlags();
397
+ return {
398
+ abiVersion: this.getAbiVersion(),
399
+ buildFlags,
400
+ simd: (buildFlags & 1) !== 0,
401
+ kernels
402
+ };
403
+ }
404
+
405
+ memoryRequired(sampleRate, maxChannels, maxFrames, telemetryRingBytes) {
406
+ return this.exports.et_engine_memory_required(
407
+ sampleRate,
408
+ maxChannels,
409
+ maxFrames,
410
+ telemetryRingBytes
411
+ ) >>> 0;
412
+ }
413
+
414
+ createEngine() {
415
+ if (this.engine) {
416
+ throw new DspBindingError('DSP engine already exists');
417
+ }
418
+ const engine = this.exports.et_engine_create() >>> 0;
419
+ if (!engine) {
420
+ throw new DspBindingError('DSP engine creation failed');
421
+ }
422
+ this.engine = engine;
423
+ return engine;
424
+ }
425
+
426
+ destroyEngine() {
427
+ if (!this.engine) return;
428
+ const engine = this.engine;
429
+ this.engine = 0;
430
+ this.prepared = false;
431
+ this._arenaViews = null;
432
+ this._arenaRanges = [];
433
+ this.exports.et_engine_destroy(engine);
434
+ this._telemetryStagingPtr = 0;
435
+ this._telemetryDroppedPtr = 0;
436
+ this._telemetryCapacity = 0;
437
+ }
438
+
439
+ prepare(sampleRate, maxChannels, maxFrames, telemetryRingBytes) {
440
+ if (!this.engine) return ET_ERR_STATE;
441
+ this.prepared = false;
442
+ this._arenaViews = null;
443
+ this._arenaRanges = [];
444
+ this._telemetryStagingPtr = 0;
445
+ this._telemetryDroppedPtr = 0;
446
+ this._telemetryCapacity = 0;
447
+ this._preparing = true;
448
+ try {
449
+ const status = this.exports.et_engine_prepare(
450
+ this.engine,
451
+ sampleRate,
452
+ maxChannels,
453
+ maxFrames,
454
+ telemetryRingBytes
455
+ );
456
+ this._refreshViews();
457
+ if (status === ET_OK) {
458
+ this.prepared = true;
459
+ this._maxChannels = maxChannels;
460
+ this._maxFrames = maxFrames;
461
+ this._telemetryStagingPtr = this.exports.et_telemetry_staging_ptr(this.engine) >>> 0;
462
+ this._telemetryDroppedPtr = this.exports.et_scratch_ptr(this.engine) >>> 0;
463
+ this._telemetryCapacity = this.exports.et_telemetry_capacity(this.engine) >>> 0;
464
+ this._assertRange(
465
+ this._telemetryStagingPtr,
466
+ this._telemetryCapacity,
467
+ 'Telemetry staging buffer'
468
+ );
469
+ this._assertRange(this._telemetryDroppedPtr, 4, 'Telemetry drop counter');
470
+ this.getArenaViews();
471
+ }
472
+ return status;
473
+ } finally {
474
+ this._preparing = false;
475
+ }
476
+ }
477
+
478
+ reset() {
479
+ if (!this.engine) return ET_ERR_STATE;
480
+ return this.exports.et_engine_reset(this.engine);
481
+ }
482
+
483
+ setTelemetryRate(rateHz) {
484
+ if (!this.engine) return ET_ERR_STATE;
485
+ return this.exports.et_engine_set_telemetry_rate(this.engine, rateHz);
486
+ }
487
+
488
+ createInstance(typeName) {
489
+ if (!this.engine || !this.prepared) return 0;
490
+ const namePtr = this._writeScratchString(typeName);
491
+ this._preparing = true;
492
+ let instanceId = 0;
493
+ try {
494
+ instanceId = this.exports.et_instance_create(this.engine, namePtr) >>> 0;
495
+ } finally {
496
+ // Kernel prepare may grow memory at this control-rate lifecycle boundary.
497
+ this._refreshViews();
498
+ this._preparing = false;
499
+ }
500
+ if (instanceId) this.getArenaViews();
501
+ return instanceId;
502
+ }
503
+
504
+ destroyInstance(instanceId) {
505
+ if (!this.engine || !instanceId) return;
506
+ this.exports.et_instance_destroy(this.engine, instanceId);
507
+ }
508
+
509
+ resetInstance(instanceId) {
510
+ if (!this.engine) return ET_ERR_STATE;
511
+ return this.exports.et_instance_reset(this.engine, instanceId);
512
+ }
513
+
514
+ instanceLatency(instanceId) {
515
+ if (!this.engine) return 0;
516
+ return this.exports.et_instance_latency(this.engine, instanceId) >>> 0;
517
+ }
518
+
519
+ instanceSetTap(instanceId, tapId) {
520
+ if (!this.engine) return ET_ERR_STATE;
521
+ return this.exports.et_instance_set_tap(this.engine, instanceId, tapId >>> 0);
522
+ }
523
+
524
+ instanceSetSeed(instanceId, seedLow, seedHigh = 0) {
525
+ if (!this.engine) return ET_ERR_STATE;
526
+ return this.exports.et_instance_set_seed(
527
+ this.engine,
528
+ instanceId,
529
+ seedLow >>> 0,
530
+ seedHigh >>> 0
531
+ );
532
+ }
533
+
534
+ instanceSetParams(instanceId, packed, paramsHash, offsetFrames = 0) {
535
+ if (!this.engine || !this.prepared) return ET_ERR_STATE;
536
+ const values = packed instanceof Float32Array ? packed : Float32Array.from(packed || []);
537
+ const byteLength = values.length * Float32Array.BYTES_PER_ELEMENT;
538
+ if (byteLength > SCRATCH_BYTES) {
539
+ throw new DspBindingError('Packed parameters exceed the scratch-buffer capacity');
540
+ }
541
+ const ptr = this.exports.et_scratch_ptr(this.engine) >>> 0;
542
+ this._refreshViews();
543
+ this._assertRange(ptr, byteLength, 'Packed parameter block');
544
+ new Float32Array(this._memoryBuffer, ptr, values.length).set(values);
545
+ return this.exports.et_instance_set_params(
546
+ this.engine,
547
+ instanceId,
548
+ ptr,
549
+ values.length,
550
+ paramsHash >>> 0,
551
+ offsetFrames >>> 0
552
+ );
553
+ }
554
+
555
+ instanceSetParamBytes(instanceId, packed, paramsHash, offsetFrames = 0) {
556
+ if (!this.engine || !this.prepared) return ET_ERR_STATE;
557
+ const values = toUint8View(packed, 'Structured parameter block');
558
+ if (values.byteLength > SCRATCH_BYTES) {
559
+ throw new DspBindingError('Structured parameters exceed the scratch-buffer capacity');
560
+ }
561
+ const ptr = this.exports.et_scratch_ptr(this.engine) >>> 0;
562
+ this._refreshViews();
563
+ this._assertRange(ptr, values.byteLength, 'Structured parameter block');
564
+ new Uint8Array(this._memoryBuffer, ptr, values.byteLength).set(values);
565
+ return this.exports.et_instance_set_param_bytes(
566
+ this.engine,
567
+ instanceId,
568
+ ptr,
569
+ values.byteLength,
570
+ paramsHash >>> 0,
571
+ offsetFrames >>> 0
572
+ );
573
+ }
574
+
575
+ instanceAssetBegin(instanceId, slot, {
576
+ channels,
577
+ frames,
578
+ topology,
579
+ headBlock,
580
+ rateDivider,
581
+ pathCount = 0,
582
+ inputCount = 0,
583
+ processingChannels = 1,
584
+ byteSize,
585
+ footprintBytes = byteSize
586
+ }) {
587
+ if (!this.engine || !this.prepared) return 0;
588
+ this._preparing = true;
589
+ try {
590
+ const ptr = this.exports.et_instance_asset_begin(
591
+ this.engine,
592
+ instanceId,
593
+ slot >>> 0,
594
+ channels >>> 0,
595
+ frames >>> 0,
596
+ topology >>> 0,
597
+ headBlock >>> 0,
598
+ rateDivider >>> 0,
599
+ pathCount >>> 0,
600
+ inputCount >>> 0,
601
+ processingChannels >>> 0,
602
+ footprintBytes >>> 0,
603
+ byteSize >>> 0
604
+ ) >>> 0;
605
+ this._refreshViews();
606
+ if (ptr) this._assertRange(ptr, byteSize, 'Asset staging buffer');
607
+ return ptr;
608
+ } finally {
609
+ this._preparing = false;
610
+ }
611
+ }
612
+
613
+ instanceAssetCommit(instanceId, slot, byteSize, formatTag) {
614
+ if (!this.engine || !this.prepared) return ET_ERR_STATE;
615
+ return this.exports.et_instance_asset_commit(
616
+ this.engine,
617
+ instanceId,
618
+ slot >>> 0,
619
+ byteSize >>> 0,
620
+ formatTag >>> 0
621
+ );
622
+ }
623
+
624
+ instanceAssetAbort(instanceId, slot) {
625
+ if (!this.engine || !this.prepared) return;
626
+ this.exports.et_instance_asset_abort(this.engine, instanceId, slot >>> 0);
627
+ }
628
+
629
+ instanceAssetState(instanceId, slot) {
630
+ if (!this.engine || !this.prepared) return 0;
631
+ return this.exports.et_instance_asset_state(this.engine, instanceId, slot >>> 0) >>> 0;
632
+ }
633
+
634
+ instanceSetAsset(instanceId, slot, payload, beginInfo, formatTag = 1) {
635
+ const bytes = toUint8View(payload, 'Asset payload');
636
+ if (bytes.byteLength < 32) {
637
+ throw new DspBindingError('Asset payload is smaller than its header');
638
+ }
639
+ const header = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
640
+ const resolvedInfo = {
641
+ ...beginInfo,
642
+ channels: beginInfo?.channels ?? header.getUint32(4, true),
643
+ frames: beginInfo?.frames ?? header.getUint32(8, true),
644
+ topology: beginInfo?.topology ?? header.getUint32(16, true)
645
+ };
646
+ const ptr = this.instanceAssetBegin(instanceId, slot, {
647
+ ...resolvedInfo,
648
+ byteSize: bytes.byteLength
649
+ });
650
+ if (!ptr) return ET_ERR_STATE;
651
+ this.u8.set(bytes, ptr);
652
+ return this.instanceAssetCommit(instanceId, slot, bytes.byteLength, formatTag);
653
+ }
654
+
655
+ instanceProcess(instanceId, audioPtr, channelCount, frameCount, timeSeconds) {
656
+ if (!this.engine) return ET_ERR_STATE;
657
+ this._refreshViews();
658
+ return this.exports.et_instance_process(
659
+ this.engine,
660
+ instanceId,
661
+ audioPtr,
662
+ channelCount,
663
+ frameCount,
664
+ timeSeconds
665
+ );
666
+ }
667
+
668
+ arenaCombinedPtr() {
669
+ if (!this.engine) return 0;
670
+ return this.exports.et_arena_combined_ptr(this.engine) >>> 0;
671
+ }
672
+
673
+ arenaBusPtr(bus) {
674
+ if (!this.engine) return 0;
675
+ return this.exports.et_arena_bus_ptr(this.engine, bus) >>> 0;
676
+ }
677
+
678
+ arenaScratchPtr(which) {
679
+ if (!this.engine) return 0;
680
+ return this.exports.et_arena_scratch_ptr(this.engine, which) >>> 0;
681
+ }
682
+
683
+ scratchPtr() {
684
+ if (!this.engine) return 0;
685
+ return this.exports.et_scratch_ptr(this.engine) >>> 0;
686
+ }
687
+
688
+ _arenaView(ptr, floatLength, label) {
689
+ this._assertRange(ptr, floatLength * Float32Array.BYTES_PER_ELEMENT, label);
690
+ const view = new Float32Array(this._memoryBuffer, ptr, floatLength);
691
+ this._arenaRanges.push({
692
+ start: ptr,
693
+ end: ptr + view.byteLength
694
+ });
695
+ return view;
696
+ }
697
+
698
+ getArenaViews() {
699
+ if (!this.engine || !this.prepared) {
700
+ throw new DspBindingError('DSP engine must be prepared before adopting arena views');
701
+ }
702
+ this._refreshViews();
703
+ if (this._arenaViews?.buffer === this._memoryBuffer) return this._arenaViews;
704
+
705
+ const floatLength = this._maxChannels * this._maxFrames;
706
+ this._arenaRanges = [];
707
+ const combinedPtr = this.arenaCombinedPtr();
708
+ const combined = this._arenaView(combinedPtr, floatLength, 'Combined arena');
709
+ const buses = new Map([[0, combined]]);
710
+ const busOffsets = new Map([[0, combinedPtr]]);
711
+ for (let bus = 1; bus <= 4; bus++) {
712
+ const ptr = this.arenaBusPtr(bus);
713
+ buses.set(bus, this._arenaView(ptr, floatLength, `Bus ${bus} arena`));
714
+ busOffsets.set(bus, ptr);
715
+ }
716
+
717
+ const scratchNames = ['allChannels', 'mixing', 'stereo', 'mono'];
718
+ const scratchLengths = [
719
+ floatLength,
720
+ floatLength,
721
+ (this._maxChannels < 2 ? this._maxChannels : 2) * this._maxFrames,
722
+ this._maxFrames
723
+ ];
724
+ const scratch = {};
725
+ const scratchOffsets = {};
726
+ for (let which = 0; which < scratchNames.length; which++) {
727
+ const name = scratchNames[which];
728
+ const ptr = this.arenaScratchPtr(which);
729
+ scratch[name] = this._arenaView(ptr, scratchLengths[which], `${name} scratch arena`);
730
+ scratchOffsets[name] = ptr;
731
+ }
732
+
733
+ this._arenaViews = {
734
+ buffer: this._memoryBuffer,
735
+ combined,
736
+ buses,
737
+ scratch,
738
+ offsets: {
739
+ combined: combinedPtr,
740
+ buses: busOffsets,
741
+ scratch: scratchOffsets
742
+ }
743
+ };
744
+ return this._arenaViews;
745
+ }
746
+
747
+ pointerForArenaView(view) {
748
+ if (!ArrayBuffer.isView(view) || view.buffer !== this._memoryBuffer) return null;
749
+ const start = view.byteOffset;
750
+ const end = start + view.byteLength;
751
+ for (const range of this._arenaRanges) {
752
+ if (start >= range.start && end <= range.end) return start;
753
+ }
754
+ return null;
755
+ }
756
+
757
+ telemetryRead(target) {
758
+ if (!this.engine || !this.prepared) return 0;
759
+ const targetView = toUint8View(target, 'Telemetry packet');
760
+ const maxBytes = targetView.byteLength < this._telemetryCapacity
761
+ ? targetView.byteLength
762
+ : this._telemetryCapacity;
763
+ if (maxBytes === 0) return 0;
764
+
765
+ this._refreshViews();
766
+ this.dataView.setUint32(this._telemetryDroppedPtr, 0, true);
767
+ const bytes = this.exports.et_telemetry_read(
768
+ this.engine,
769
+ this._telemetryStagingPtr,
770
+ maxBytes,
771
+ this._telemetryDroppedPtr
772
+ );
773
+ this._refreshViews();
774
+ if (!Number.isInteger(bytes) || bytes < 0 || bytes > maxBytes) {
775
+ throw new DspBindingError('Telemetry reader returned an invalid byte count');
776
+ }
777
+ this.lastTelemetryDroppedFrames = this.dataView.getUint32(this._telemetryDroppedPtr, true);
778
+ if (bytes > 0) {
779
+ targetView.set(this.u8.subarray(this._telemetryStagingPtr, this._telemetryStagingPtr + bytes), 0);
780
+ }
781
+ return bytes;
782
+ }
783
+
784
+ pipelineConfigure(descriptor) {
785
+ if (!this.engine) return ET_ERR_STATE;
786
+ const bytes = toUint8View(descriptor, 'Pipeline descriptor');
787
+ if (bytes.byteLength > SCRATCH_BYTES) {
788
+ throw new DspBindingError('Pipeline descriptor exceeds the scratch-buffer capacity');
789
+ }
790
+ const ptr = this.exports.et_scratch_ptr(this.engine) >>> 0;
791
+ this._refreshViews();
792
+ this._assertRange(ptr, bytes.byteLength, 'Pipeline descriptor');
793
+ this.u8.set(bytes, ptr);
794
+ return this.exports.et_pipeline_configure(this.engine, ptr, bytes.byteLength);
795
+ }
796
+
797
+ pipelineProcess(channelCount, frameCount, timeSeconds, masterBypass = false) {
798
+ if (!this.engine) return ET_ERR_STATE;
799
+ this._refreshViews();
800
+ return this.exports.et_pipeline_process(
801
+ this.engine,
802
+ channelCount,
803
+ frameCount,
804
+ timeSeconds,
805
+ masterBypass ? 1 : 0
806
+ );
807
+ }
808
+
809
+ markFailed() {
810
+ this.failed = true;
811
+ }
812
+
813
+ get live() {
814
+ return Boolean(this.engine && this.prepared && !this.failed && !this.memoryGrowthViolation);
815
+ }
816
+
817
+ close() {
818
+ this.destroyEngine();
819
+ }
820
+ }
821
+
822
+ export async function instantiateDspBinding(moduleOrBytes, {
823
+ webAssembly = globalThis.WebAssembly,
824
+ imports = null,
825
+ debug = false,
826
+ debugWrite = defaultDebugWrite,
827
+ warning = defaultWarning,
828
+ onUnexpectedMemoryGrowth = null
829
+ } = {}) {
830
+ if (!webAssembly || typeof webAssembly.instantiate !== 'function') {
831
+ throw new DspBindingError('WebAssembly.instantiate is unavailable');
832
+ }
833
+
834
+ let memory = null;
835
+ let binding = null;
836
+ let pendingGrowthNotification = false;
837
+ const baseImports = createDspImports({
838
+ getMemory: () => memory,
839
+ debug,
840
+ debugWrite,
841
+ onMemoryGrowth: () => {
842
+ if (binding) {
843
+ binding.handleMemoryGrowthNotification();
844
+ } else {
845
+ pendingGrowthNotification = true;
846
+ }
847
+ }
848
+ });
849
+ const result = await webAssembly.instantiate(moduleOrBytes, mergeImports(baseImports, imports));
850
+ const instance = result?.instance || result;
851
+ memory = instance?.exports?.memory || null;
852
+ binding = new DspEngineBinding(instance, { warning, onUnexpectedMemoryGrowth });
853
+ if (pendingGrowthNotification) {
854
+ binding.handleMemoryGrowthNotification();
855
+ }
856
+ return binding;
857
+ }
858
+
859
+ export { ET_OK, ET_ERR_STATE, REQUIRED_FUNCTION_EXPORTS };