@opendaw/studio-core-wasm 0.0.2

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.
Files changed (79) hide show
  1. package/dist/WasmEngine.d.ts +14 -0
  2. package/dist/WasmEngine.d.ts.map +1 -0
  3. package/dist/WasmEngine.js +152 -0
  4. package/dist/boot.d.ts +14 -0
  5. package/dist/boot.d.ts.map +1 -0
  6. package/dist/boot.js +91 -0
  7. package/dist/device-linker.d.ts +47 -0
  8. package/dist/device-linker.d.ts.map +1 -0
  9. package/dist/device-linker.js +109 -0
  10. package/dist/engine-exports.d.ts +86 -0
  11. package/dist/engine-exports.d.ts.map +1 -0
  12. package/dist/engine-exports.js +13 -0
  13. package/dist/engine-modules.d.ts +26 -0
  14. package/dist/engine-modules.d.ts.map +1 -0
  15. package/dist/engine-modules.js +62 -0
  16. package/dist/index.d.ts +5 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +6 -0
  19. package/dist/nam-bridge.d.ts +7 -0
  20. package/dist/nam-bridge.d.ts.map +1 -0
  21. package/dist/nam-bridge.js +265 -0
  22. package/dist/nam-loader.d.ts +4 -0
  23. package/dist/nam-loader.d.ts.map +1 -0
  24. package/dist/nam-loader.js +1 -0
  25. package/dist/protocol.d.ts +22 -0
  26. package/dist/protocol.d.ts.map +1 -0
  27. package/dist/protocol.js +3 -0
  28. package/dist/script-bridge.d.ts +11 -0
  29. package/dist/script-bridge.d.ts.map +1 -0
  30. package/dist/script-bridge.js +343 -0
  31. package/dist/script-spielwerk.d.ts +24 -0
  32. package/dist/script-spielwerk.d.ts.map +1 -0
  33. package/dist/script-spielwerk.js +179 -0
  34. package/dist/soundfont-simplify.d.ts +28 -0
  35. package/dist/soundfont-simplify.d.ts.map +1 -0
  36. package/dist/soundfont-simplify.js +143 -0
  37. package/dist/sync/loopback.d.ts +8 -0
  38. package/dist/sync/loopback.d.ts.map +1 -0
  39. package/dist/sync/loopback.js +23 -0
  40. package/dist/sync/serialize-update-tasks.d.ts +4 -0
  41. package/dist/sync/serialize-update-tasks.d.ts.map +1 -0
  42. package/dist/sync/serialize-update-tasks.js +40 -0
  43. package/dist/utf8.d.ts +2 -0
  44. package/dist/utf8.d.ts.map +1 -0
  45. package/dist/utf8.js +19 -0
  46. package/dist/wasm/engine.wasm +0 -0
  47. package/dist/wasm/plugins/device_apparat.wasm +0 -0
  48. package/dist/wasm/plugins/device_arpeggio.wasm +0 -0
  49. package/dist/wasm/plugins/device_compressor.wasm +0 -0
  50. package/dist/wasm/plugins/device_crusher.wasm +0 -0
  51. package/dist/wasm/plugins/device_dattorro_reverb.wasm +0 -0
  52. package/dist/wasm/plugins/device_delay.wasm +0 -0
  53. package/dist/wasm/plugins/device_fold.wasm +0 -0
  54. package/dist/wasm/plugins/device_gate.wasm +0 -0
  55. package/dist/wasm/plugins/device_maximizer.wasm +0 -0
  56. package/dist/wasm/plugins/device_nano.wasm +0 -0
  57. package/dist/wasm/plugins/device_neural_amp.wasm +0 -0
  58. package/dist/wasm/plugins/device_pitch.wasm +0 -0
  59. package/dist/wasm/plugins/device_playfield_sample.wasm +0 -0
  60. package/dist/wasm/plugins/device_revamp.wasm +0 -0
  61. package/dist/wasm/plugins/device_reverb.wasm +0 -0
  62. package/dist/wasm/plugins/device_soundfont.wasm +0 -0
  63. package/dist/wasm/plugins/device_spielwerk.wasm +0 -0
  64. package/dist/wasm/plugins/device_stereo_tool.wasm +0 -0
  65. package/dist/wasm/plugins/device_tidal.wasm +0 -0
  66. package/dist/wasm/plugins/device_vaporisateur.wasm +0 -0
  67. package/dist/wasm/plugins/device_velocity.wasm +0 -0
  68. package/dist/wasm/plugins/device_vocoder.wasm +0 -0
  69. package/dist/wasm/plugins/device_waveshaper.wasm +0 -0
  70. package/dist/wasm/plugins/device_werkstatt.wasm +0 -0
  71. package/dist/wasm/plugins/device_zeitgeist.wasm +0 -0
  72. package/dist/wasm-offline-worker.js +97 -0
  73. package/dist/wasm-offline-worker.js.map +7 -0
  74. package/dist/wasm-processor.js +97 -0
  75. package/dist/wasm-processor.js.map +7 -0
  76. package/dist/worklet-scope.d.ts +2 -0
  77. package/dist/worklet-scope.d.ts.map +1 -0
  78. package/dist/worklet-scope.js +10 -0
  79. package/package.json +53 -0
@@ -0,0 +1,62 @@
1
+ // Fetch + compile the wasm modules the engine worklet needs: the engine (the dynamic-linker host) and
2
+ // the device PLUGINS (PIC side modules the engine loads at host-assigned bases). All are handed to the
3
+ // "engine" AudioWorkletProcessor via processorOptions; the worklet links the devices into the engine.
4
+ // The engine's single linear memory: SHARED, so the main thread can see the WASM heap (e.g. to write
5
+ // decoded sample data straight into it at an engine-allocated offset). wasm32 caps at 65536 pages (4 GiB),
6
+ // so that is the maximum; pages commit lazily on grow. Needs cross-origin isolation (COOP/COEP, set in
7
+ // vite.config). Created on the main thread and passed into the worklet via processorOptions.
8
+ export const createEngineMemory = () => new WebAssembly.Memory({ initial: 256, maximum: 65536, shared: true });
9
+ // The device PIC side modules to load: each wasm plus the device-BOX TYPE it realizes. This is the device
10
+ // table the engine uses to instantiate a device box: when the box graph presents e.g. an ArpeggioDeviceBox,
11
+ // the engine looks up its type here to find its plugin under /wasm/plugins/. Load order is irrelevant now (the engine reads
12
+ // each unit's chains from the box, ordered by the device `index`); only the type mapping matters.
13
+ export const DEVICES = [
14
+ { url: "/wasm/plugins/device_vaporisateur.wasm", boxType: "VaporisateurDeviceBox" }, // instrument
15
+ { url: "/wasm/plugins/device_nano.wasm", boxType: "NanoDeviceBox" }, // instrument (sampler)
16
+ { url: "/wasm/plugins/device_revamp.wasm", boxType: "RevampDeviceBox" }, // audio effect
17
+ { url: "/wasm/plugins/device_tidal.wasm", boxType: "TidalDeviceBox" }, // audio effect
18
+ { url: "/wasm/plugins/device_delay.wasm", boxType: "DelayDeviceBox" }, // audio effect
19
+ { url: "/wasm/plugins/device_gate.wasm", boxType: "GateDeviceBox" }, // audio effect (sidechain)
20
+ { url: "/wasm/plugins/device_arpeggio.wasm", boxType: "ArpeggioDeviceBox" }, // midi effect
21
+ { url: "/wasm/plugins/device_zeitgeist.wasm", boxType: "ZeitgeistDeviceBox" }, // midi effect
22
+ { url: "/wasm/plugins/device_pitch.wasm", boxType: "PitchDeviceBox" }, // midi effect
23
+ { url: "/wasm/plugins/device_werkstatt.wasm", boxType: "WerkstattDeviceBox" }, // scriptable audio effect
24
+ { url: "/wasm/plugins/device_apparat.wasm", boxType: "ApparatDeviceBox" }, // scriptable instrument
25
+ { url: "/wasm/plugins/device_spielwerk.wasm", boxType: "SpielwerkDeviceBox" }, // scriptable midi effect
26
+ { url: "/wasm/plugins/device_waveshaper.wasm", boxType: "WaveshaperDeviceBox" }, // audio effect
27
+ { url: "/wasm/plugins/device_crusher.wasm", boxType: "CrusherDeviceBox" }, // audio effect
28
+ { url: "/wasm/plugins/device_fold.wasm", boxType: "FoldDeviceBox" }, // audio effect (wavefolder)
29
+ { url: "/wasm/plugins/device_stereo_tool.wasm", boxType: "StereoToolDeviceBox" }, // audio effect
30
+ { url: "/wasm/plugins/device_velocity.wasm", boxType: "VelocityDeviceBox" }, // midi effect
31
+ { url: "/wasm/plugins/device_maximizer.wasm", boxType: "MaximizerDeviceBox" }, // audio effect
32
+ { url: "/wasm/plugins/device_compressor.wasm", boxType: "CompressorDeviceBox" }, // audio effect (sidechain)
33
+ { url: "/wasm/plugins/device_reverb.wasm", boxType: "ReverbDeviceBox" }, // audio effect
34
+ { url: "/wasm/plugins/device_dattorro_reverb.wasm", boxType: "DattorroReverbDeviceBox" }, // audio effect
35
+ { url: "/wasm/plugins/device_soundfont.wasm", boxType: "SoundfontDeviceBox" }, // instrument (preset sampler)
36
+ { url: "/wasm/plugins/device_vocoder.wasm", boxType: "VocoderDeviceBox" }, // audio effect (channel vocoder + sidechain)
37
+ { url: "/wasm/plugins/device_neural_amp.wasm", boxType: "NeuralAmpDeviceBox" }, // audio effect (NAM, via the nam bridge)
38
+ { url: "/wasm/plugins/device_playfield_sample.wasm", boxType: "PlayfieldSampleBox" } // composite child (one Playfield slot)
39
+ ];
40
+ // The composite box types. Playfield hosts its slots in the `samples` field (key 10); each slot's note is its
41
+ // `index` field (key 15) and its choke-group flag is `exclude` (key 42). The slot plugin itself is the
42
+ // PlayfieldSampleBox entry in DEVICES above.
43
+ export const COMPOSITES = [
44
+ // Playfield: direct children (self-hosting slots, device-declared chains), routed by note index + choke.
45
+ { boxType: "PlayfieldDeviceBox", childrenField: 10, indexKey: 15, excludeKey: 42,
46
+ cellInstrumentField: 0, cellMidiField: 0, cellAudioField: 0, childEnabledKey: 22,
47
+ childMuteKey: 40, childSoloKey: 41 },
48
+ // A generic instrument bundle: children are CELLS (CompositeCellBox) at field 10, each wrapping one
49
+ // instrument (field 2) plus its midi-fx (3) and audio-fx (4) chains, ordered by the cell's own `index`
50
+ // (field 5, UI position + engine sort). No note routing, no choke.
51
+ { boxType: "CompositeDeviceBox", childrenField: 10, indexKey: 5, excludeKey: 0,
52
+ cellInstrumentField: 2, cellMidiField: 3, cellAudioField: 4, childEnabledKey: 0,
53
+ childMuteKey: 0, childSoloKey: 0 }
54
+ ];
55
+ export const loadEngineModules = async (base = "") => {
56
+ const urls = [`${base}/wasm/engine.wasm`, ...DEVICES.map(device => `${base}${device.url}`)];
57
+ const buffers = await Promise.all(urls.map(url => fetch(url).then(response => response.ok
58
+ ? response.arrayBuffer()
59
+ : Promise.reject(new Error(`Could not load wasm module '${url}' (${response.status} ${response.statusText})`)))));
60
+ const [engineModule, ...deviceModules] = await Promise.all(buffers.map(bytes => WebAssembly.compile(bytes)));
61
+ return { engineModule, deviceModules, deviceBoxTypes: DEVICES.map(device => device.boxType), composites: COMPOSITES };
62
+ };
@@ -0,0 +1,5 @@
1
+ export * from "./WasmEngine";
2
+ export * from "./engine-modules";
3
+ export type { EngineExports } from "./engine-exports";
4
+ export { readPanicMessage } from "./engine-exports";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,YAAY,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // The published main-thread surface of the WASM engine. The worklet/worker side ships as prebuilt bundles
2
+ // (dist/wasm-processor.js, dist/wasm-offline-worker.js) next to the wasm binaries (dist/wasm/*); a host
3
+ // serves those and hands their URLs to `WasmEngine.install`.
4
+ export * from "./WasmEngine";
5
+ export * from "./engine-modules";
6
+ export { readPanicMessage } from "./engine-exports";
@@ -0,0 +1,7 @@
1
+ export declare class NamBridges {
2
+ #private;
3
+ constructor(memory: WebAssembly.Memory, fetchWasm: () => Promise<ArrayBuffer>, sampleRate: number);
4
+ settle(): Promise<void>;
5
+ imports(): Record<string, (...args: Array<number>) => number | void>;
6
+ }
7
+ //# sourceMappingURL=nam-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nam-bridge.d.ts","sourceRoot":"","sources":["../src/nam-bridge.ts"],"names":[],"mappings":"AAsCA,qBAAa,UAAU;;gBAYP,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM;IAS3F,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAK7B,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;CAsKvE"}
@@ -0,0 +1,265 @@
1
+ // The NAM BRIDGE: the JS side of the NeuralAmp device. Its Rust side-module does the wrapper DSP (gains,
2
+ // mono downmix, dry/wet mix) and calls the `host_nam_*` env imports defined here, which run the SAME
3
+ // `@opendaw/nam-wasm` module (NeuralAmpModelerCore, A2-capable) the TS engine's `NeuralAmpDeviceProcessor`
4
+ // runs — instantiated as its OWN wasm instance next to the engine (an Emscripten build cannot join the
5
+ // engine's shared memory). Per chunk the bridge copies ≤128 samples per channel between the two memories,
6
+ // negligible next to the inference. Like the SCRIPT BRIDGE, this is a contained relaxation of the engine's
7
+ // zero-JS-in-render rule.
8
+ //
9
+ // Identity: the device's `init` calls `host_nam_create(uuidPtr)` -> a handle KEYED BY THE DEVICE BOX UUID, so
10
+ // a rebind reuses the existing instances and their loaded (prewarmed) model. The model JSON arrives as raw
11
+ // UTF-8 bytes out of the ENGINE's memory (`observe_target_string` on the device's model pointer) and is
12
+ // copied straight into the nam heap — no JS string, no TextDecoder (the worklet scope has none).
13
+ // Byte-identical deliveries (rebind catch-ups) are skipped. The nam module itself loads LAZILY on the first
14
+ // model delivery; until it is ready `host_nam_process` reports not-loaded and the device passes through,
15
+ // mirroring the TS processor while its wasm fetch is in flight.
16
+ import { isDefined, UUID } from "@opendaw/lib-std";
17
+ import { createNamModule, NamWasmModule } from "@opendaw/nam-wasm";
18
+ // One NeuralAmp device's JS-side state, keyed by the handle `host_nam_create` returned. `instances` mirrors
19
+ // the TS processor's `#instances: [int, int]` (mono uses only the first; -1 = none).
20
+ class Bridge {
21
+ uuid;
22
+ instances = [-1, -1];
23
+ json = null; // the cached model bytes; null = no model (unloaded)
24
+ loaded = false;
25
+ mono = true;
26
+ constructor(uuid) {
27
+ this.uuid = uuid;
28
+ }
29
+ }
30
+ const bytesEqual = (a, b) => {
31
+ if (a.length !== b.length) {
32
+ return false;
33
+ }
34
+ for (let index = 0; index < a.length; index++) {
35
+ if (a[index] !== b[index]) {
36
+ return false;
37
+ }
38
+ }
39
+ return true;
40
+ };
41
+ export class NamBridges {
42
+ #memory;
43
+ #fetchWasm;
44
+ #sampleRate;
45
+ #bridges = new Map();
46
+ #byUuid = new Map();
47
+ #nextHandle = 1;
48
+ #emscripten = null;
49
+ #module = null;
50
+ #loading = false;
51
+ #failed = false;
52
+ constructor(memory, fetchWasm, sampleRate) {
53
+ this.#memory = memory;
54
+ this.#fetchWasm = fetchWasm;
55
+ this.#sampleRate = sampleRate;
56
+ }
57
+ /// Resolves once no module fetch/instantiation is in flight (models are applied synchronously when it
58
+ /// lands). For tests: after a bind delivered a model, `await settle()` guarantees `host_nam_process` stops
59
+ /// reporting not-loaded (or the load genuinely failed).
60
+ async settle() {
61
+ while (this.#loading) {
62
+ await new Promise(resolve => setTimeout(resolve, 1));
63
+ }
64
+ }
65
+ /// The `host_nam_*` closures to bind into each device's `env`.
66
+ imports() {
67
+ return {
68
+ host_nam_create: (uuidPtr) => this.#create(uuidPtr),
69
+ host_nam_load: (handle, jsonPtr, jsonLen) => this.#load(handle, jsonPtr, jsonLen),
70
+ host_nam_set_mono: (handle, mono) => this.#setMono(handle, mono !== 0),
71
+ host_nam_process: (handle, in0, in1, out0, out1, frames, channels) => this.#process(handle, in0, in1, out0, out1, frames, channels),
72
+ host_nam_reset: (handle) => this.#reset(handle),
73
+ host_nam_release: (handle) => this.#release(handle)
74
+ };
75
+ }
76
+ #create(uuidPtr) {
77
+ const uuid = UUID.toString(new Uint8Array(this.#memory.buffer, uuidPtr, 16).slice());
78
+ const existing = this.#byUuid.get(uuid);
79
+ if (isDefined(existing)) {
80
+ return existing;
81
+ }
82
+ const handle = this.#nextHandle++;
83
+ this.#bridges.set(handle, new Bridge(uuid));
84
+ this.#byUuid.set(uuid, handle);
85
+ return handle;
86
+ }
87
+ // The model delivery: copy the JSON bytes out of the engine memory SYNCHRONOUSLY (they borrow the box
88
+ // graph only for this call), dedupe against the cached bytes, and load / unload the instances. Kicks the
89
+ // lazy module fetch on the first real model.
90
+ #load(handle, jsonPtr, jsonLen) {
91
+ const bridge = this.#bridges.get(handle);
92
+ if (!isDefined(bridge)) {
93
+ return;
94
+ }
95
+ if (jsonLen === 0) {
96
+ bridge.json = null;
97
+ this.#unloadInstances(bridge);
98
+ return;
99
+ }
100
+ const json = new Uint8Array(this.#memory.buffer, jsonPtr, jsonLen).slice();
101
+ if (isDefined(bridge.json) && bytesEqual(bridge.json, json)) {
102
+ return;
103
+ }
104
+ bridge.json = json;
105
+ if (isDefined(this.#module)) {
106
+ this.#applyModel(bridge);
107
+ }
108
+ else {
109
+ this.#ensureModule();
110
+ }
111
+ }
112
+ // Mirror the TS `#onMonoChanged`: mono drops the second instance; stereo creates it and loads the cached
113
+ // model into it.
114
+ #setMono(handle, mono) {
115
+ const bridge = this.#bridges.get(handle);
116
+ if (!isDefined(bridge)) {
117
+ return;
118
+ }
119
+ bridge.mono = mono;
120
+ const module = this.#module;
121
+ if (!isDefined(module)) {
122
+ return;
123
+ }
124
+ if (mono) {
125
+ if (bridge.instances[1] >= 0) {
126
+ module.unloadModel(bridge.instances[1]);
127
+ module.destroyInstance(bridge.instances[1]);
128
+ bridge.instances[1] = -1;
129
+ }
130
+ }
131
+ else if (bridge.instances[1] < 0) {
132
+ bridge.instances[1] = module.createInstance();
133
+ if (isDefined(bridge.json) && bridge.loaded) {
134
+ bridge.loaded = this.#loadInto(bridge.instances[1], bridge.json) && bridge.loaded;
135
+ }
136
+ }
137
+ }
138
+ // One chunk of both channels: Float32Array views over the ENGINE memory (re-derived every call — the
139
+ // SharedArrayBuffer can grow), copied through the nam heap by `NamWasmModule.process`. Returns 1 only when
140
+ // the model is loaded; 0 is the device's passthrough cue (module still loading / no model / unknown handle).
141
+ #process(handle, in0, in1, out0, out1, frames, channels) {
142
+ const bridge = this.#bridges.get(handle);
143
+ const module = this.#module;
144
+ if (!isDefined(bridge) || !isDefined(module) || !bridge.loaded || bridge.instances[0] < 0) {
145
+ return 0;
146
+ }
147
+ const buffer = this.#memory.buffer;
148
+ module.process(bridge.instances[0], new Float32Array(buffer, in0, frames), new Float32Array(buffer, out0, frames));
149
+ if (channels > 1) {
150
+ if (bridge.instances[1] < 0) {
151
+ return 0;
152
+ }
153
+ module.process(bridge.instances[1], new Float32Array(buffer, in1, frames), new Float32Array(buffer, out1, frames));
154
+ }
155
+ return 1;
156
+ }
157
+ #reset(handle) {
158
+ const bridge = this.#bridges.get(handle);
159
+ const module = this.#module;
160
+ if (!isDefined(bridge) || !isDefined(module)) {
161
+ return;
162
+ }
163
+ for (const instance of bridge.instances) {
164
+ if (instance >= 0) {
165
+ module.reset(instance);
166
+ }
167
+ }
168
+ }
169
+ // THIS device's instance is dying (a genuine removal, never a chain-edit survivor, called from the
170
+ // engine's `terminate` export): destroy its native nam instance(s) and drop the bridge, so a removed or
171
+ // rebound NeuralAmp device no longer keeps its instance(s) resident forever.
172
+ #release(handle) {
173
+ const bridge = this.#bridges.get(handle);
174
+ if (!isDefined(bridge)) {
175
+ return;
176
+ }
177
+ const module = this.#module;
178
+ if (isDefined(module)) {
179
+ for (const instance of bridge.instances) {
180
+ if (instance >= 0) {
181
+ module.unloadModel(instance);
182
+ module.destroyInstance(instance);
183
+ }
184
+ }
185
+ }
186
+ this.#bridges.delete(handle);
187
+ if (this.#byUuid.get(bridge.uuid) === handle) {
188
+ this.#byUuid.delete(bridge.uuid);
189
+ }
190
+ }
191
+ // Fetch + instantiate the nam module ONCE (lazily, off the render path), then apply every bridge's pending
192
+ // model. A failure logs and stays failed: every device keeps passing through, the TS engine's behavior.
193
+ #ensureModule() {
194
+ if (this.#loading || this.#failed || isDefined(this.#module)) {
195
+ return;
196
+ }
197
+ this.#loading = true;
198
+ this.#fetchWasm()
199
+ .then(wasmBinary => createNamModule({ wasmBinary, locateFile: () => "" }))
200
+ .then(emscripten => {
201
+ this.#emscripten = emscripten;
202
+ const module = NamWasmModule.fromModule(emscripten);
203
+ module.setSampleRate(this.#sampleRate);
204
+ this.#module = module;
205
+ this.#loading = false;
206
+ for (const bridge of this.#bridges.values()) {
207
+ if (isDefined(bridge.json)) {
208
+ this.#applyModel(bridge);
209
+ }
210
+ }
211
+ })
212
+ .catch(error => {
213
+ this.#loading = false;
214
+ this.#failed = true;
215
+ console.error("Failed to load NAM WASM:", error);
216
+ });
217
+ }
218
+ // Mirror the TS `#applyModel`: ensure the instances match the mono flag, then load the cached JSON into
219
+ // each. `loaded` gates `#process`, so a failed load keeps the device passing through.
220
+ #applyModel(bridge) {
221
+ const module = this.#module;
222
+ if (!isDefined(module) || !isDefined(bridge.json)) {
223
+ return;
224
+ }
225
+ if (bridge.instances[0] < 0) {
226
+ bridge.instances[0] = module.createInstance();
227
+ }
228
+ if (!bridge.mono && bridge.instances[1] < 0) {
229
+ bridge.instances[1] = module.createInstance();
230
+ }
231
+ bridge.loaded = this.#loadInto(bridge.instances[0], bridge.json);
232
+ if (bridge.instances[1] >= 0) {
233
+ bridge.loaded = this.#loadInto(bridge.instances[1], bridge.json) && bridge.loaded;
234
+ }
235
+ }
236
+ #unloadInstances(bridge) {
237
+ bridge.loaded = false;
238
+ const module = this.#module;
239
+ if (!isDefined(module)) {
240
+ return;
241
+ }
242
+ for (const instance of bridge.instances) {
243
+ if (instance >= 0) {
244
+ module.unloadModel(instance);
245
+ }
246
+ }
247
+ }
248
+ // Load raw model bytes into one nam instance: straight into the nam heap (NUL-terminated), no JS string —
249
+ // `NamWasmModule.loadModel` marshals a string, but the worklet scope has no TextDecoder to make one.
250
+ #loadInto(instance, json) {
251
+ const emscripten = this.#emscripten;
252
+ if (!isDefined(emscripten)) {
253
+ return false;
254
+ }
255
+ const jsonPtr = emscripten._malloc(json.length + 1);
256
+ emscripten.HEAPU8.set(json, jsonPtr);
257
+ emscripten.HEAPU8[jsonPtr + json.length] = 0;
258
+ const loaded = emscripten._nam_loadModel(instance, jsonPtr);
259
+ emscripten._free(jsonPtr);
260
+ if (!loaded) {
261
+ console.error("NAM loadModel failed");
262
+ }
263
+ return !!loaded;
264
+ }
265
+ }
@@ -0,0 +1,4 @@
1
+ export interface NamLoader {
2
+ fetchWasm(): Promise<ArrayBuffer>;
3
+ }
4
+ //# sourceMappingURL=nam-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nam-loader.d.ts","sourceRoot":"","sources":["../src/nam-loader.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACtB,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;CACpC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { UUID } from "@opendaw/lib-std";
2
+ import type { CompositeSpec } from "./engine-modules";
3
+ export type WasmEngineAttachment = {
4
+ engineModule: WebAssembly.Module;
5
+ deviceModules: ReadonlyArray<WebAssembly.Module>;
6
+ deviceBoxTypes: ReadonlyArray<string>;
7
+ composites: ReadonlyArray<CompositeSpec>;
8
+ memory: WebAssembly.Memory;
9
+ };
10
+ export interface WasmSyncProtocol {
11
+ applyUpdates(bytes: ArrayBuffer): void;
12
+ checksum(bytes: Int8Array): Promise<void>;
13
+ }
14
+ export interface WasmFrozenProtocol {
15
+ frozenAllocate(frameCount: number, channels: number): Promise<number>;
16
+ frozenAttach(uuid: UUID.Bytes, frameCount: number, channels: number, sampleRate: number): void;
17
+ frozenClear(uuid: UUID.Bytes): void;
18
+ }
19
+ export declare const WASM_ENGINE_PROCESSOR_NAME = "engine-wasm-processor";
20
+ export declare const WASM_SYNC_CHANNEL = "engine-sync-bytes";
21
+ export declare const WASM_FROZEN_CHANNEL = "engine-frozen-audio";
22
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAC1C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAGnD,MAAM,MAAM,oBAAoB,GAAG;IAC/B,YAAY,EAAE,WAAW,CAAC,MAAM,CAAA;IAChC,aAAa,EAAE,aAAa,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAChD,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IACxC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAA;CAC7B,CAAA;AAMD,MAAM,WAAW,gBAAgB;IAC7B,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5C;AAMD,MAAM,WAAW,kBAAkB;IAC/B,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACrE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9F,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;CACtC;AAED,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AACpD,eAAO,MAAM,mBAAmB,wBAAwB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export const WASM_ENGINE_PROCESSOR_NAME = "engine-wasm-processor";
2
+ export const WASM_SYNC_CHANNEL = "engine-sync-bytes";
3
+ export const WASM_FROZEN_CHANNEL = "engine-frozen-audio";
@@ -0,0 +1,11 @@
1
+ export interface ScriptEngine {
2
+ host_resolve_sample(handle: number, outPtr: number): number;
3
+ input_reserve(length: number): number;
4
+ }
5
+ export declare class ScriptBridges {
6
+ #private;
7
+ constructor(memory: WebAssembly.Memory, engine: ScriptEngine, sampleRate: number, onMessage?: (uuid: string, message: string) => void);
8
+ imports(): Record<string, (...args: number[]) => number | void>;
9
+ liveBridgeCount(): number;
10
+ }
11
+ //# sourceMappingURL=script-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script-bridge.d.ts","sourceRoot":"","sources":["../src/script-bridge.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,YAAY;IACzB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3D,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;CACxC;AAsDD,qBAAa,aAAa;;gBASV,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EACpE,SAAS,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAe;IAQzE,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC;IA+M/D,eAAe,IAAI,MAAM;CAG5B"}