@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.
- package/dist/WasmEngine.d.ts +14 -0
- package/dist/WasmEngine.d.ts.map +1 -0
- package/dist/WasmEngine.js +152 -0
- package/dist/boot.d.ts +14 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +91 -0
- package/dist/device-linker.d.ts +47 -0
- package/dist/device-linker.d.ts.map +1 -0
- package/dist/device-linker.js +109 -0
- package/dist/engine-exports.d.ts +86 -0
- package/dist/engine-exports.d.ts.map +1 -0
- package/dist/engine-exports.js +13 -0
- package/dist/engine-modules.d.ts +26 -0
- package/dist/engine-modules.d.ts.map +1 -0
- package/dist/engine-modules.js +62 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/nam-bridge.d.ts +7 -0
- package/dist/nam-bridge.d.ts.map +1 -0
- package/dist/nam-bridge.js +265 -0
- package/dist/nam-loader.d.ts +4 -0
- package/dist/nam-loader.d.ts.map +1 -0
- package/dist/nam-loader.js +1 -0
- package/dist/protocol.d.ts +22 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +3 -0
- package/dist/script-bridge.d.ts +11 -0
- package/dist/script-bridge.d.ts.map +1 -0
- package/dist/script-bridge.js +343 -0
- package/dist/script-spielwerk.d.ts +24 -0
- package/dist/script-spielwerk.d.ts.map +1 -0
- package/dist/script-spielwerk.js +179 -0
- package/dist/soundfont-simplify.d.ts +28 -0
- package/dist/soundfont-simplify.d.ts.map +1 -0
- package/dist/soundfont-simplify.js +143 -0
- package/dist/sync/loopback.d.ts +8 -0
- package/dist/sync/loopback.d.ts.map +1 -0
- package/dist/sync/loopback.js +23 -0
- package/dist/sync/serialize-update-tasks.d.ts +4 -0
- package/dist/sync/serialize-update-tasks.d.ts.map +1 -0
- package/dist/sync/serialize-update-tasks.js +40 -0
- package/dist/utf8.d.ts +2 -0
- package/dist/utf8.d.ts.map +1 -0
- package/dist/utf8.js +19 -0
- package/dist/wasm/engine.wasm +0 -0
- package/dist/wasm/plugins/device_apparat.wasm +0 -0
- package/dist/wasm/plugins/device_arpeggio.wasm +0 -0
- package/dist/wasm/plugins/device_compressor.wasm +0 -0
- package/dist/wasm/plugins/device_crusher.wasm +0 -0
- package/dist/wasm/plugins/device_dattorro_reverb.wasm +0 -0
- package/dist/wasm/plugins/device_delay.wasm +0 -0
- package/dist/wasm/plugins/device_fold.wasm +0 -0
- package/dist/wasm/plugins/device_gate.wasm +0 -0
- package/dist/wasm/plugins/device_maximizer.wasm +0 -0
- package/dist/wasm/plugins/device_nano.wasm +0 -0
- package/dist/wasm/plugins/device_neural_amp.wasm +0 -0
- package/dist/wasm/plugins/device_pitch.wasm +0 -0
- package/dist/wasm/plugins/device_playfield_sample.wasm +0 -0
- package/dist/wasm/plugins/device_revamp.wasm +0 -0
- package/dist/wasm/plugins/device_reverb.wasm +0 -0
- package/dist/wasm/plugins/device_soundfont.wasm +0 -0
- package/dist/wasm/plugins/device_spielwerk.wasm +0 -0
- package/dist/wasm/plugins/device_stereo_tool.wasm +0 -0
- package/dist/wasm/plugins/device_tidal.wasm +0 -0
- package/dist/wasm/plugins/device_vaporisateur.wasm +0 -0
- package/dist/wasm/plugins/device_velocity.wasm +0 -0
- package/dist/wasm/plugins/device_vocoder.wasm +0 -0
- package/dist/wasm/plugins/device_waveshaper.wasm +0 -0
- package/dist/wasm/plugins/device_werkstatt.wasm +0 -0
- package/dist/wasm/plugins/device_zeitgeist.wasm +0 -0
- package/dist/wasm-offline-worker.js +97 -0
- package/dist/wasm-offline-worker.js.map +7 -0
- package/dist/wasm-processor.js +97 -0
- package/dist/wasm-processor.js.map +7 -0
- package/dist/worklet-scope.d.ts +2 -0
- package/dist/worklet-scope.d.ts.map +1 -0
- package/dist/worklet-scope.js +10 -0
- package/package.json +53 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type WasmEngineUrls = {
|
|
2
|
+
processorUrl: string;
|
|
3
|
+
offlineWorkerUrl: string;
|
|
4
|
+
wasmUrl: string;
|
|
5
|
+
};
|
|
6
|
+
export declare namespace WasmEngine {
|
|
7
|
+
const isEnabled: () => boolean;
|
|
8
|
+
const setEnabled: (enabled: boolean) => void;
|
|
9
|
+
const isReady: () => boolean;
|
|
10
|
+
const useForExports: () => boolean;
|
|
11
|
+
const ensureReady: (context: BaseAudioContext) => Promise<boolean>;
|
|
12
|
+
const install: (urls: WasmEngineUrls) => void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=WasmEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WasmEngine.d.ts","sourceRoot":"","sources":["../src/WasmEngine.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,cAAc,GAAG;IAEzB,YAAY,EAAE,MAAM,CAAA;IAEpB,gBAAgB,EAAE,MAAM,CAAA;IAExB,OAAO,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,yBAAiB,UAAU,CAAC;IAKjB,MAAM,SAAS,QAAO,OAAqD,CAAA;IAE3E,MAAM,UAAU,GAAI,SAAS,OAAO,KAAG,IAAuD,CAAA;IAG9F,MAAM,OAAO,QAAO,OAA6B,CAAA;IAIjD,MAAM,aAAa,QAAO,OAAyE,CAAA;IAInG,MAAM,WAAW,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,OAAO,CAa5E,CAAA;IAEM,MAAM,OAAO,GAAI,MAAM,cAAc,KAAG,IAsB9C,CAAA;CAqFJ"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Main-thread API of the WASM engine: `install` takes the host-served URLs (the prebuilt worklet module,
|
|
2
|
+
// the prebuilt offline render worker, and the base serving the wasm binaries — all shipped in this
|
|
3
|
+
// package's dist), registers the EngineVariant provider the studio-core EngineWorklet consults at
|
|
4
|
+
// construction, and wires the offline render variant. `ensureReady` compiles the modules + registers the
|
|
5
|
+
// processor module once. The WASM engine is the DEFAULT; localStorage only records an explicit opt-out.
|
|
6
|
+
// Every engine (re)boot re-reads it, so the existing restart machinery swaps engines without a page reload.
|
|
7
|
+
import { isNull, MutableObservableOption } from "@opendaw/lib-std";
|
|
8
|
+
import { Communicator, Promises } from "@opendaw/lib-runtime";
|
|
9
|
+
import { SyncSource } from "@opendaw/lib-box";
|
|
10
|
+
import { EngineVariant, OfflineEngineRenderer } from "@opendaw/studio-core";
|
|
11
|
+
import { createEngineMemory, loadEngineModules } from "./engine-modules";
|
|
12
|
+
import { serializeUpdateTasks } from "./sync/serialize-update-tasks";
|
|
13
|
+
import { createSyncLoopback } from "./sync/loopback";
|
|
14
|
+
import { WASM_ENGINE_PROCESSOR_NAME, WASM_FROZEN_CHANNEL, WASM_SYNC_CHANNEL } from "./protocol";
|
|
15
|
+
export var WasmEngine;
|
|
16
|
+
(function (WasmEngine) {
|
|
17
|
+
const FLAG_KEY = "opendaw-wasm-engine";
|
|
18
|
+
const modules = new MutableObservableOption();
|
|
19
|
+
const config = new MutableObservableOption();
|
|
20
|
+
WasmEngine.isEnabled = () => localStorage.getItem(FLAG_KEY) !== "false";
|
|
21
|
+
WasmEngine.setEnabled = (enabled) => localStorage.setItem(FLAG_KEY, String(enabled));
|
|
22
|
+
// Whether the wasm modules are compiled and the processor is registered (i.e. `ensureReady` succeeded).
|
|
23
|
+
WasmEngine.isReady = () => modules.nonEmpty();
|
|
24
|
+
// Whether an offline render (mixdown, stems, publish, video audio) should run through the wasm variant:
|
|
25
|
+
// the engine is enabled, its artifacts actually loaded, and the offline worker is installed.
|
|
26
|
+
WasmEngine.useForExports = () => WasmEngine.isEnabled() && WasmEngine.isReady() && OfflineEngineRenderer.hasVariant();
|
|
27
|
+
// Compile the wasm modules + register the processor module (both once). Returns false when the engine
|
|
28
|
+
// artifacts are unavailable (e.g. a deploy without them), so callers can revert to the TS engine.
|
|
29
|
+
WasmEngine.ensureReady = async (context) => {
|
|
30
|
+
if (modules.nonEmpty()) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const { processorUrl, wasmUrl } = config.unwrap("WasmEngine.install must run before ensureReady");
|
|
34
|
+
const { status, value, error } = await Promises.tryCatch((async () => {
|
|
35
|
+
await context.audioWorklet.addModule(processorUrl);
|
|
36
|
+
return loadEngineModules(wasmUrl);
|
|
37
|
+
})());
|
|
38
|
+
if (status === "rejected") {
|
|
39
|
+
console.warn("WASM engine unavailable:", error);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
modules.wrap(value);
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
WasmEngine.install = (urls) => {
|
|
46
|
+
config.wrap(urls);
|
|
47
|
+
// The OFFLINE render path (mixdown/stems/freeze/benchmarks): the worker self-loads the wasm
|
|
48
|
+
// artifacts from `wasmUrl`, so no preloading is needed here.
|
|
49
|
+
OfflineEngineRenderer.installVariant(urls.offlineWorkerUrl, { wasmUrl: urls.wasmUrl });
|
|
50
|
+
OfflineEngineRenderer.installVariantPolicy(() => WasmEngine.useForExports()); // freeze/consolidation follow the toggle
|
|
51
|
+
EngineVariant.install(() => {
|
|
52
|
+
if (!WasmEngine.isEnabled() || modules.isEmpty()) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const { engineModule, deviceModules, deviceBoxTypes, composites } = modules.unwrap();
|
|
56
|
+
const attachment = {
|
|
57
|
+
// A FRESH shared memory per boot: re-instantiating the engine re-applies its data segments,
|
|
58
|
+
// but a recycled heap would leak every allocation of the previous engine instance.
|
|
59
|
+
engineModule, deviceModules, deviceBoxTypes, composites, memory: createEngineMemory()
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
processorName: WASM_ENGINE_PROCESSOR_NAME,
|
|
63
|
+
attachment,
|
|
64
|
+
connectSync: (messenger, project) => connectSync(messenger, project),
|
|
65
|
+
connectFrozenAudio: (messenger) => connectFrozenAudio(messenger, attachment.memory)
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
// Freeze PCM delivery: the worklet only ALLOCATES (a small RPC returning the engine's planar stereo
|
|
70
|
+
// pointer) and ATTACHES — the bulk copy runs here on the MAIN thread, straight into the shared engine
|
|
71
|
+
// memory. Operations chain sequentially: a second freeze reallocates `frozen_pending`, so no write may
|
|
72
|
+
// start while another is between allocate and attach (the engine reboot replay sends several at once).
|
|
73
|
+
// The worklet may memory.grow inside `frozen_allocate`, which leaves previously obtained buffer
|
|
74
|
+
// references stale (they keep their pre-grow length), so the buffer is re-read after the RPC returns.
|
|
75
|
+
const connectFrozenAudio = (messenger, memory) => {
|
|
76
|
+
const sender = Communicator.sender(messenger.channel(WASM_FROZEN_CHANNEL), dispatcher => new class {
|
|
77
|
+
frozenAllocate(frameCount, channels) {
|
|
78
|
+
return dispatcher.dispatchAndReturn(this.frozenAllocate, frameCount, channels);
|
|
79
|
+
}
|
|
80
|
+
frozenAttach(uuid, frameCount, channels, sampleRate) {
|
|
81
|
+
dispatcher.dispatchAndForget(this.frozenAttach, uuid, frameCount, channels, sampleRate);
|
|
82
|
+
}
|
|
83
|
+
frozenClear(uuid) { dispatcher.dispatchAndForget(this.frozenClear, uuid); }
|
|
84
|
+
});
|
|
85
|
+
const queue = { tail: Promise.resolve() };
|
|
86
|
+
return (uuid, audioData) => {
|
|
87
|
+
queue.tail = queue.tail.then(() => {
|
|
88
|
+
if (isNull(audioData)) {
|
|
89
|
+
sender.frozenClear(uuid);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { frames, numberOfFrames, numberOfChannels, sampleRate } = audioData;
|
|
93
|
+
const channels = Math.min(numberOfChannels, 2);
|
|
94
|
+
return sender.frozenAllocate(numberOfFrames, channels).then(pointer => {
|
|
95
|
+
if (pointer === 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const buffer = memory.buffer;
|
|
99
|
+
for (let channel = 0; channel < channels; channel++) {
|
|
100
|
+
new Float32Array(buffer, pointer + channel * numberOfFrames * Float32Array.BYTES_PER_ELEMENT, numberOfFrames)
|
|
101
|
+
.set(frames[channel]);
|
|
102
|
+
}
|
|
103
|
+
sender.frozenAttach(uuid, numberOfFrames, channels, sampleRate);
|
|
104
|
+
});
|
|
105
|
+
}).catch(reason => console.warn("frozen audio delivery failed:", reason));
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
// SyncSource (unchanged) -> SYNCHRONOUS loopback -> serialize at emission time (the source graph's
|
|
109
|
+
// schema AND state — a MessageChannel hop would let a later transaction delete boxes before the batch
|
|
110
|
+
// resolves its primitive codecs) -> transaction bytes to the worklet's apply_updates. The `true` flag
|
|
111
|
+
// makes SyncSource open with a full dump of the graph, which is how the engine receives the project.
|
|
112
|
+
// A throttled checksum round-trip follows the batches on the same ordered channel: the worklet compares
|
|
113
|
+
// the source checksum against the engine's rolling checksum and escalates a divergence.
|
|
114
|
+
const CHECKSUM_INTERVAL_MS = 1_000;
|
|
115
|
+
const connectSync = (messenger, project) => {
|
|
116
|
+
const sender = Communicator.sender(messenger.channel(WASM_SYNC_CHANNEL), dispatcher => new class {
|
|
117
|
+
applyUpdates(bytes) {
|
|
118
|
+
dispatcher.dispatchAndForget(this.applyUpdates, Communicator.makeTransferable(bytes));
|
|
119
|
+
}
|
|
120
|
+
checksum(bytes) {
|
|
121
|
+
return dispatcher.dispatchAndReturn(this.checksum, bytes);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const throttle = { next: 0 };
|
|
125
|
+
const verifyChecksum = () => {
|
|
126
|
+
const now = performance.now();
|
|
127
|
+
if (now < throttle.next) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
throttle.next = now + CHECKSUM_INTERVAL_MS;
|
|
131
|
+
sender.checksum(project.boxGraph.checksum())
|
|
132
|
+
.catch(reason => console.warn("wasm engine checksum verification failed:", reason));
|
|
133
|
+
};
|
|
134
|
+
const target = {
|
|
135
|
+
sendUpdates: (tasks) => {
|
|
136
|
+
sender.applyUpdates(serializeUpdateTasks(tasks, project.boxGraph));
|
|
137
|
+
verifyChecksum();
|
|
138
|
+
},
|
|
139
|
+
checksum: (value) => sender.checksum(value)
|
|
140
|
+
};
|
|
141
|
+
const loopback = createSyncLoopback();
|
|
142
|
+
const executor = Communicator.executor(loopback.target, target);
|
|
143
|
+
const syncSource = new SyncSource(project.boxGraph, loopback.source, true);
|
|
144
|
+
return {
|
|
145
|
+
terminate: () => {
|
|
146
|
+
syncSource.terminate();
|
|
147
|
+
executor.terminate();
|
|
148
|
+
loopback.terminate();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
})(WasmEngine || (WasmEngine = {}));
|
package/dist/boot.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Procedure } from "@opendaw/lib-std";
|
|
2
|
+
import { EngineToClient } from "@opendaw/studio-adapters";
|
|
3
|
+
import { EngineExports } from "./engine-exports";
|
|
4
|
+
import { CompositeSpec } from "./engine-modules";
|
|
5
|
+
export type WasmEngineModules = {
|
|
6
|
+
engineModule: WebAssembly.Module;
|
|
7
|
+
deviceModules: ReadonlyArray<WebAssembly.Module>;
|
|
8
|
+
deviceBoxTypes: ReadonlyArray<string>;
|
|
9
|
+
composites: ReadonlyArray<CompositeSpec>;
|
|
10
|
+
};
|
|
11
|
+
export declare const describeEngineTrap: (engine: EngineExports, memory: WebAssembly.Memory, error: unknown) => unknown;
|
|
12
|
+
export declare const instantiateWasmEngine: (modules: WasmEngineModules, memory: WebAssembly.Memory, sampleRate: number, engineToClient: EngineToClient) => EngineExports;
|
|
13
|
+
export declare const drainResourceRequests: (engine: EngineExports, memory: WebAssembly.Memory, engineToClient: EngineToClient, pending: Set<Promise<unknown>>, fallbackSampleRate: number, onError: Procedure<unknown>) => void;
|
|
14
|
+
//# sourceMappingURL=boot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../src/boot.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,SAAS,EAA2B,MAAM,kBAAkB,CAAA;AAC/E,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAC,aAAa,EAAmB,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAQ9C,MAAM,MAAM,iBAAiB,GAAG;IAC5B,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;CAC3C,CAAA;AAKD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,aAAa,EAAE,QAAQ,WAAW,CAAC,MAAM,EAAE,OAAO,OAAO,KAAG,OAItG,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,SAAS,iBAAiB,EAAE,QAAQ,WAAW,CAAC,MAAM,EACtD,YAAY,MAAM,EAAE,gBAAgB,cAAc,KAAG,aAe1F,CAAA;AAOD,eAAO,MAAM,qBAAqB,GAAI,QAAQ,aAAa,EAAE,QAAQ,WAAW,CAAC,MAAM,EACjD,gBAAgB,cAAc,EAAE,SAAS,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAC9D,oBAAoB,MAAM,EAAE,SAAS,SAAS,CAAC,OAAO,CAAC,KAAG,IA4C/F,CAAA"}
|
package/dist/boot.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Shared WASM-engine boot + resource plumbing for BOTH studio hosts: the realtime worklet processor and
|
|
2
|
+
// the offline render worker. Links the engine + device side-modules (with the script/NAM bridges) and
|
|
3
|
+
// runs the sample/soundfont load handshakes over the UNCHANGED EngineToClient RPC.
|
|
4
|
+
import { isDefined, tryCatch } from "@opendaw/lib-std";
|
|
5
|
+
import { readPanicMessage } from "./engine-exports";
|
|
6
|
+
import { linkDevice, registerComposite } from "./device-linker";
|
|
7
|
+
import { ScriptBridges } from "./script-bridge";
|
|
8
|
+
import { NamBridges } from "./nam-bridge";
|
|
9
|
+
import { simplifySoundfont } from "./soundfont-simplify";
|
|
10
|
+
const ENGINE_TABLE_RESERVE = 512; // shared table slots reserved for the engine's own functions (it needs ~42)
|
|
11
|
+
// A wasm trap surfaces as an anonymous "RuntimeError: unreachable" (panic=abort strips the message);
|
|
12
|
+
// the engine's panic handler deposits the real message + location into a static buffer first, so attach
|
|
13
|
+
// it before the error leaves the host.
|
|
14
|
+
export const describeEngineTrap = (engine, memory, error) => {
|
|
15
|
+
const message = tryCatch(() => readPanicMessage(engine, memory));
|
|
16
|
+
if (message.status === "failure" || message.value.length === 0) {
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
return new Error(`wasm panic: ${message.value}`, { cause: error });
|
|
20
|
+
};
|
|
21
|
+
export const instantiateWasmEngine = (modules, memory, sampleRate, engineToClient) => {
|
|
22
|
+
const table = new WebAssembly.Table({ initial: ENGINE_TABLE_RESERVE, element: "anyfunc" });
|
|
23
|
+
const now = isDefined(globalThis.performance)
|
|
24
|
+
? () => performance.now() * 1000.0 : () => Date.now() * 1000.0;
|
|
25
|
+
const engine = new WebAssembly.Instance(modules.engineModule, { env: { memory, __indirect_function_table: table, host_perf_now: now } }).exports;
|
|
26
|
+
engine.init(sampleRate);
|
|
27
|
+
const scriptBridges = new ScriptBridges(memory, engine, sampleRate, (uuid, message) => engineToClient.deviceMessage(uuid, message));
|
|
28
|
+
const namBridges = new NamBridges(memory, () => engineToClient.fetchNamWasm(), sampleRate);
|
|
29
|
+
const bridgeImports = { ...scriptBridges.imports(), ...namBridges.imports() };
|
|
30
|
+
modules.deviceModules.forEach((deviceModule, index) => linkDevice(engine, memory, table, deviceModule, modules.deviceBoxTypes[index], sampleRate, bridgeImports));
|
|
31
|
+
modules.composites.forEach(composite => registerComposite(engine, memory, composite));
|
|
32
|
+
return engine;
|
|
33
|
+
};
|
|
34
|
+
// Pop every sample/soundfont the engine queued and run the load handshake for each: fetch + decode over
|
|
35
|
+
// the EngineToClient RPC, write into the engine allocation, mark ready. Each runs as its own async chain
|
|
36
|
+
// tracked in `pending` (queryLoadingComplete awaits them); a failed sample resolves as 1-frame silence.
|
|
37
|
+
// A throw INSIDE a continuation (an engine trap while writing the allocation) would otherwise vanish as
|
|
38
|
+
// an unhandled rejection in the host's global scope, so it routes into `onError`.
|
|
39
|
+
export const drainResourceRequests = (engine, memory, engineToClient, pending, fallbackSampleRate, onError) => {
|
|
40
|
+
const track = (promise) => {
|
|
41
|
+
const guarded = promise.catch(onError);
|
|
42
|
+
pending.add(guarded);
|
|
43
|
+
guarded.finally(() => pending.delete(guarded));
|
|
44
|
+
};
|
|
45
|
+
for (;;) {
|
|
46
|
+
const outPtr = engine.input_reserve(16);
|
|
47
|
+
const handle = engine.sample_take_request(outPtr);
|
|
48
|
+
if (handle < 0) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
const uuid = new Uint8Array(memory.buffer, outPtr, 16).slice();
|
|
52
|
+
track(engineToClient.fetchAudio(uuid).then(data => {
|
|
53
|
+
const { numberOfFrames, numberOfChannels, sampleRate: dataRate, frames } = data;
|
|
54
|
+
const bytesPerChannel = numberOfFrames * Float32Array.BYTES_PER_ELEMENT;
|
|
55
|
+
const pointer = engine.sample_allocate(handle, numberOfChannels * bytesPerChannel);
|
|
56
|
+
// A dead handle (its slot was freed + generation-bumped between the request and this async
|
|
57
|
+
// delivery — e.g. the AudioFileBox delete/recreate churn when a recorded take is finalized)
|
|
58
|
+
// returns 0. Writing the frames at address 0 would corrupt the engine's own memory, so skip:
|
|
59
|
+
// the engine re-requests against the fresh handle when the new box syncs.
|
|
60
|
+
if (pointer === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (let channel = 0; channel < numberOfChannels; channel++) {
|
|
64
|
+
new Float32Array(memory.buffer, pointer + channel * bytesPerChannel, numberOfFrames)
|
|
65
|
+
.set(frames[channel]);
|
|
66
|
+
}
|
|
67
|
+
engine.sample_set_ready(handle, numberOfFrames, numberOfChannels, dataRate);
|
|
68
|
+
}, (reason) => {
|
|
69
|
+
engine.sample_allocate(handle, 4);
|
|
70
|
+
engine.sample_set_ready(handle, 1, 1, fallbackSampleRate);
|
|
71
|
+
engineToClient.log(`sample load failed: ${reason}`);
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
for (;;) {
|
|
75
|
+
const outPtr = engine.input_reserve(16);
|
|
76
|
+
const handle = engine.soundfont_take_request(outPtr);
|
|
77
|
+
if (handle < 0) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
const uuid = new Uint8Array(memory.buffer, outPtr, 16).slice();
|
|
81
|
+
track(engineToClient.fetchSoundfont(uuid).then(soundfont => {
|
|
82
|
+
const blob = new Uint8Array(simplifySoundfont(soundfont));
|
|
83
|
+
const pointer = engine.soundfont_allocate(handle, blob.byteLength);
|
|
84
|
+
if (pointer === 0) {
|
|
85
|
+
return;
|
|
86
|
+
} // dead handle (see the sample path): writing at 0 would corrupt memory
|
|
87
|
+
new Uint8Array(memory.buffer, pointer, blob.byteLength).set(blob);
|
|
88
|
+
engine.soundfont_set_ready(handle);
|
|
89
|
+
}, (reason) => engineToClient.log(`soundfont load failed: ${reason}`)));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const DEVICE_STACK_SIZE: number;
|
|
2
|
+
export type BridgeImports = Record<string, (...args: Array<number>) => number | void>;
|
|
3
|
+
export type DeviceExports = {
|
|
4
|
+
process?: (descPtr: number) => void;
|
|
5
|
+
process_events?: (from: number, to: number, flags: number, statePtr: number, outPtr: number, max: number) => number;
|
|
6
|
+
state_size: (sampleRate: number) => number;
|
|
7
|
+
kind: () => number;
|
|
8
|
+
init?: (statePtr: number, sampleRate: number) => void;
|
|
9
|
+
parameter_changed?: (statePtr: number, id: number, kind: number, value: number) => void;
|
|
10
|
+
field_changed?: (statePtr: number, id: number, kind: number, bits: number, len: number) => void;
|
|
11
|
+
sample_changed?: (statePtr: number, id: number, handle: number, present: number) => void;
|
|
12
|
+
soundfont_changed?: (statePtr: number, id: number, handle: number, present: number) => void;
|
|
13
|
+
reset?: (statePtr: number) => void;
|
|
14
|
+
terminate?: (statePtr: number) => void;
|
|
15
|
+
midi_effects_field?: () => number;
|
|
16
|
+
audio_effects_field?: () => number;
|
|
17
|
+
observe_param_collection_field?: () => number;
|
|
18
|
+
observe_sample_collection_field?: () => number;
|
|
19
|
+
__wasm_apply_data_relocs?: () => void;
|
|
20
|
+
__wasm_call_ctors?: () => void;
|
|
21
|
+
};
|
|
22
|
+
export type LinkerEngine = {
|
|
23
|
+
device_alloc: (size: number) => number;
|
|
24
|
+
device_register: (processIndex: number, stateSize: number, kind: number, initIndex: number, parameterChangedIndex: number, fieldChangedIndex: number, sampleChangedIndex: number, soundfontChangedIndex: number, resetIndex: number, terminateIndex: number, midiEffectsField: number, audioEffectsField: number, paramCollectionField: number, sampleCollectionField: number) => number;
|
|
25
|
+
device_set_box_type: (deviceId: number, nameLen: number) => void;
|
|
26
|
+
composite_register: (nameLen: number, childrenField: number, indexKey: number, excludeKey: number, cellInstrumentField: number, cellMidiField: number, cellAudioField: number, childEnabledKey: number, childMuteKey: number, childSoloKey: number) => void;
|
|
27
|
+
input_reserve: (len: number) => number;
|
|
28
|
+
};
|
|
29
|
+
export type CompositeRegistration = {
|
|
30
|
+
boxType: string;
|
|
31
|
+
childrenField: number;
|
|
32
|
+
indexKey: number;
|
|
33
|
+
excludeKey: number;
|
|
34
|
+
cellInstrumentField: number;
|
|
35
|
+
cellMidiField: number;
|
|
36
|
+
cellAudioField: number;
|
|
37
|
+
childEnabledKey: number;
|
|
38
|
+
childMuteKey: number;
|
|
39
|
+
childSoloKey: number;
|
|
40
|
+
};
|
|
41
|
+
export declare const parseDylink: (module: WebAssembly.Module) => {
|
|
42
|
+
memorySize: number;
|
|
43
|
+
tableSize: number;
|
|
44
|
+
};
|
|
45
|
+
export declare const linkDevice: (engine: LinkerEngine, memory: WebAssembly.Memory, table: WebAssembly.Table, module: WebAssembly.Module, boxType: string, sampleRate: number, bridgeImports: BridgeImports) => void;
|
|
46
|
+
export declare const registerComposite: (engine: LinkerEngine, memory: WebAssembly.Memory, spec: CompositeRegistration) => void;
|
|
47
|
+
//# sourceMappingURL=device-linker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-linker.d.ts","sourceRoot":"","sources":["../src/device-linker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// The ONE owner of the device side-module linking ritual, shared by the worklet (engine-processor.ts), the
|
|
2
|
+
// perf worker (perf/offline-render.ts), and the test loader (test/helpers/load-full-engine.ts). Everything a
|
|
3
|
+
// device needs to join the engine lives here: the `dylink.0` parse, the talc base/stack allocation, the env
|
|
4
|
+
// (including the SINGLE authoritative host-import list), relocations, table installation, registration, and
|
|
5
|
+
// the box-type / composite name mapping. A new engine export added here reaches every context at once; a
|
|
6
|
+
// loader that misses one fails LOUDLY at link time with the import's name instead of a cryptic LinkError.
|
|
7
|
+
export const DEVICE_STACK_SIZE = 256 * 1024; // talc-allocated stack handed to each loaded device
|
|
8
|
+
// The engine exports a device side-module imports from `env` (wasm-to-wasm via the loader binding). This
|
|
9
|
+
// list is the CONTRACT: extend it here when the engine gains a host export, never in a loader.
|
|
10
|
+
const HOST_IMPORTS = [
|
|
11
|
+
"host_pull_events", "host_pulse_to_offset",
|
|
12
|
+
"host_bind_parameter", "host_bind_broadcast", "host_broadcast_ptr", "host_broadcast_active",
|
|
13
|
+
"host_update_parameters", "host_first_update_position", "host_next_update_position",
|
|
14
|
+
"host_resolve_sample", "host_observe_sample",
|
|
15
|
+
"host_resolve_soundfont", "host_observe_soundfont",
|
|
16
|
+
"host_observe_field", "host_observe_target_string",
|
|
17
|
+
"host_bind_sidechain", "host_resolve_input", "host_self_uuid", "host_panic",
|
|
18
|
+
"host_base_frequency"
|
|
19
|
+
];
|
|
20
|
+
const readVarU32 = (bytes, pos) => {
|
|
21
|
+
let result = 0, shift = 0, cursor = pos;
|
|
22
|
+
for (;;) {
|
|
23
|
+
const byte = bytes[cursor++];
|
|
24
|
+
result |= (byte & 0x7f) << shift;
|
|
25
|
+
if ((byte & 0x80) === 0) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
shift += 7;
|
|
29
|
+
}
|
|
30
|
+
return [result >>> 0, cursor];
|
|
31
|
+
};
|
|
32
|
+
// Read a PIC side module's memory/table requirements from its `dylink.0` custom section.
|
|
33
|
+
export const parseDylink = (module) => {
|
|
34
|
+
const sections = WebAssembly.Module.customSections(module, "dylink.0");
|
|
35
|
+
if (sections.length === 0) {
|
|
36
|
+
return { memorySize: 0, tableSize: 0 };
|
|
37
|
+
}
|
|
38
|
+
const bytes = new Uint8Array(sections[0]);
|
|
39
|
+
let pos = 0;
|
|
40
|
+
while (pos < bytes.length) {
|
|
41
|
+
const type = bytes[pos++];
|
|
42
|
+
const [size, afterSize] = readVarU32(bytes, pos);
|
|
43
|
+
if (type === 1) {
|
|
44
|
+
const [memorySize, afterMem] = readVarU32(bytes, afterSize);
|
|
45
|
+
const [, afterAlign] = readVarU32(bytes, afterMem);
|
|
46
|
+
const [tableSize] = readVarU32(bytes, afterAlign);
|
|
47
|
+
return { memorySize, tableSize };
|
|
48
|
+
}
|
|
49
|
+
pos = afterSize + size;
|
|
50
|
+
}
|
|
51
|
+
return { memorySize: 0, tableSize: 0 };
|
|
52
|
+
};
|
|
53
|
+
// Write an ASCII box-type name into the engine's input scratch (no TextEncoder in the worklet scope).
|
|
54
|
+
const writeName = (engine, memory, name) => {
|
|
55
|
+
const length = name.length;
|
|
56
|
+
const pointer = engine.input_reserve(length);
|
|
57
|
+
const bytes = new Uint8Array(memory.buffer, pointer, length);
|
|
58
|
+
for (let index = 0; index < length; index++) {
|
|
59
|
+
bytes[index] = name.charCodeAt(index) & 0xff;
|
|
60
|
+
}
|
|
61
|
+
return length;
|
|
62
|
+
};
|
|
63
|
+
// Link ONE device PIC side module into the engine: assign it memory + table + stack bases from talc,
|
|
64
|
+
// instantiate with the authoritative host imports plus the JS bridge closures, apply its relocations,
|
|
65
|
+
// install its `process` (or MIDI-fx `process_events`) into the shared table, install the optional hooks,
|
|
66
|
+
// register it, and map its device-box type.
|
|
67
|
+
export const linkDevice = (engine, memory, table, module, boxType, sampleRate, bridgeImports) => {
|
|
68
|
+
const { memorySize, tableSize } = parseDylink(module);
|
|
69
|
+
const memoryBase = engine.device_alloc(memorySize);
|
|
70
|
+
const tableBase = tableSize > 0 ? table.grow(tableSize) : table.length;
|
|
71
|
+
const stackBase = engine.device_alloc(DEVICE_STACK_SIZE);
|
|
72
|
+
const env = {
|
|
73
|
+
memory, __indirect_function_table: table,
|
|
74
|
+
__memory_base: new WebAssembly.Global({ value: "i32", mutable: false }, memoryBase),
|
|
75
|
+
__table_base: new WebAssembly.Global({ value: "i32", mutable: false }, tableBase),
|
|
76
|
+
__stack_pointer: new WebAssembly.Global({ value: "i32", mutable: true }, stackBase + DEVICE_STACK_SIZE)
|
|
77
|
+
};
|
|
78
|
+
for (const name of HOST_IMPORTS) {
|
|
79
|
+
const fn = engine[name];
|
|
80
|
+
if (typeof fn !== "function") {
|
|
81
|
+
throw new Error(`engine is missing the device host export '${name}'`);
|
|
82
|
+
}
|
|
83
|
+
env[name] = fn;
|
|
84
|
+
}
|
|
85
|
+
Object.assign(env, bridgeImports);
|
|
86
|
+
const device = new WebAssembly.Instance(module, { env: env }).exports;
|
|
87
|
+
device.__wasm_apply_data_relocs?.();
|
|
88
|
+
device.__wasm_call_ctors?.();
|
|
89
|
+
// Install an optional device export into a fresh table slot, 0 = "none" (device slots are grown above
|
|
90
|
+
// the engine's own table functions, so a real hook is never at 0).
|
|
91
|
+
const installOptional = (fn) => {
|
|
92
|
+
if (fn === undefined) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
const index = table.grow(1);
|
|
96
|
+
table.set(index, fn);
|
|
97
|
+
return index;
|
|
98
|
+
};
|
|
99
|
+
// An audio device installs `process`; a MIDI-fx device installs `process_events` (its pull responder).
|
|
100
|
+
const processIndex = table.grow(1);
|
|
101
|
+
table.set(processIndex, (device.process_events ?? device.process));
|
|
102
|
+
const deviceId = engine.device_register(processIndex, device.state_size(sampleRate), device.kind(), installOptional(device.init), installOptional(device.parameter_changed), installOptional(device.field_changed), installOptional(device.sample_changed), installOptional(device.soundfont_changed), installOptional(device.reset), installOptional(device.terminate), device.midi_effects_field?.() ?? 0, device.audio_effects_field?.() ?? 0, device.observe_param_collection_field?.() ?? 0, device.observe_sample_collection_field?.() ?? 0);
|
|
103
|
+
engine.device_set_box_type(deviceId, writeName(engine, memory, boxType));
|
|
104
|
+
};
|
|
105
|
+
// Register one COMPOSITE box type (a box hosting a child collection of its own instruments): write its
|
|
106
|
+
// name, then map its child collection fields. The child plugin itself is a normal `linkDevice` entry.
|
|
107
|
+
export const registerComposite = (engine, memory, spec) => {
|
|
108
|
+
engine.composite_register(writeName(engine, memory, spec.boxType), spec.childrenField, spec.indexKey, spec.excludeKey, spec.cellInstrumentField, spec.cellMidiField, spec.cellAudioField, spec.childEnabledKey, spec.childMuteKey, spec.childSoloKey);
|
|
109
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type EngineExports = {
|
|
2
|
+
init: (sampleRate: number) => void;
|
|
3
|
+
device_alloc: (size: number) => number;
|
|
4
|
+
device_register: (processIndex: number, stateSize: number, kind: number, initIndex: number, parameterChangedIndex: number, fieldChangedIndex: number, sampleChangedIndex: number, soundfontChangedIndex: number, resetIndex: number, terminateIndex: number, midiEffectsField: number, audioEffectsField: number, paramCollectionField: number, sampleCollectionField: number) => number;
|
|
5
|
+
device_set_box_type: (deviceId: number, nameLen: number) => void;
|
|
6
|
+
composite_register: (nameLen: number, childrenField: number, indexKey: number, excludeKey: number, cellInstrumentField: number, cellMidiField: number, cellAudioField: number, childEnabledKey: number, childMuteKey: number, childSoloKey: number) => void;
|
|
7
|
+
input_ptr: () => number;
|
|
8
|
+
input_capacity: () => number;
|
|
9
|
+
input_reserve: (len: number) => number;
|
|
10
|
+
apply_updates: (len: number) => number;
|
|
11
|
+
bind: () => number;
|
|
12
|
+
render: () => void;
|
|
13
|
+
output_ptr: () => number;
|
|
14
|
+
output_len: () => number;
|
|
15
|
+
heap_used: () => number;
|
|
16
|
+
heap_claimed: () => number;
|
|
17
|
+
engine_state_ptr: () => number;
|
|
18
|
+
engine_state_len: () => number;
|
|
19
|
+
set_metronome_enabled: (enabled: number) => void;
|
|
20
|
+
set_metronome_gain: (gainDb: number) => void;
|
|
21
|
+
set_metronome_beat_sub_division: (division: number) => void;
|
|
22
|
+
set_metronome_monophonic: (enabled: number) => void;
|
|
23
|
+
click_allocate: (frameCount: number, channels: number) => number;
|
|
24
|
+
set_click_sound: (index: number, frameCount: number, channels: number, sampleRate: number) => void;
|
|
25
|
+
checksum_ptr: () => number;
|
|
26
|
+
broadcast_generation: () => number;
|
|
27
|
+
broadcast_count: () => number;
|
|
28
|
+
broadcast_entry: (index: number, outPtr: number) => number;
|
|
29
|
+
broadcast_set_active: (index: number, active: number) => void;
|
|
30
|
+
play: () => void;
|
|
31
|
+
pause: () => void;
|
|
32
|
+
stop: () => void;
|
|
33
|
+
set_position: (position: number) => void;
|
|
34
|
+
prepare_recording_state: (countIn: number, countInBars: number) => void;
|
|
35
|
+
stop_recording: () => void;
|
|
36
|
+
ignore_note_region: () => void;
|
|
37
|
+
set_monitoring_map: (count: number) => void;
|
|
38
|
+
monitor_input_ptr: () => number;
|
|
39
|
+
monitor_output_ptr: () => number;
|
|
40
|
+
set_stem_export: (count: number) => void;
|
|
41
|
+
stem_output_ptr: () => number;
|
|
42
|
+
frozen_allocate: (frameCount: number, channels: number) => number;
|
|
43
|
+
set_frozen_audio: (frameCount: number, channels: number, sampleRate: number) => void;
|
|
44
|
+
clear_frozen_audio: () => void;
|
|
45
|
+
note_signal_on: (pitch: number, velocity: number) => void;
|
|
46
|
+
note_signal_off: (pitch: number) => void;
|
|
47
|
+
note_signal_audition: (pitch: number, duration: number, velocity: number) => void;
|
|
48
|
+
schedule_clip_play: () => void;
|
|
49
|
+
schedule_clip_stop: () => void;
|
|
50
|
+
clip_changes_count: () => number;
|
|
51
|
+
clip_changes_take: (outPtr: number) => number;
|
|
52
|
+
marker_changes_count: () => number;
|
|
53
|
+
marker_changes_take: (outPtr: number) => number;
|
|
54
|
+
midi_out_count: () => number;
|
|
55
|
+
midi_out_take: (outPtr: number) => number;
|
|
56
|
+
midi_out_device_id: (num: number, outPtr: number, max: number) => number;
|
|
57
|
+
host_pull_events: (from: number, to: number, flags: number, outPtr: number, max: number) => number;
|
|
58
|
+
host_pulse_to_offset: (pulse: number) => number;
|
|
59
|
+
host_bind_parameter: (pathPtr: number, pathLen: number) => number;
|
|
60
|
+
host_update_parameters: (position: number, outPtr: number, max: number) => number;
|
|
61
|
+
host_first_update_position: (at: number) => number;
|
|
62
|
+
host_next_update_position: (after: number) => number;
|
|
63
|
+
host_resolve_sample: (handle: number, outPtr: number) => number;
|
|
64
|
+
host_resolve_soundfont: (handle: number, outPtr: number) => number;
|
|
65
|
+
host_observe_soundfont: (pathPtr: number, pathLen: number) => number;
|
|
66
|
+
soundfont_take_request: (outPtr: number) => number;
|
|
67
|
+
soundfont_allocate: (handle: number, byteLength: number) => number;
|
|
68
|
+
soundfont_set_ready: (handle: number) => void;
|
|
69
|
+
host_self_uuid: (outPtr: number) => void;
|
|
70
|
+
host_observe_sample: (pathPtr: number, pathLen: number) => number;
|
|
71
|
+
host_observe_field: (pathPtr: number, pathLen: number) => number;
|
|
72
|
+
host_observe_target_string: (pathPtr: number, pathLen: number, fieldKey: number) => number;
|
|
73
|
+
host_bind_sidechain: (pathPtr: number, pathLen: number) => number;
|
|
74
|
+
host_resolve_input: (id: number, outPtr: number) => number;
|
|
75
|
+
host_base_frequency: () => number;
|
|
76
|
+
sample_take_request: (outPtr: number) => number;
|
|
77
|
+
sample_allocate: (handle: number, byteLength: number) => number;
|
|
78
|
+
sample_set_ready: (handle: number, frameCount: number, channelCount: number, sampleRate: number) => void;
|
|
79
|
+
set_allow_takes: (enabled: number) => void;
|
|
80
|
+
set_pause_on_loop_disabled: (enabled: number) => void;
|
|
81
|
+
set_truncate_notes_at_region_end: (enabled: number) => void;
|
|
82
|
+
panic_message_ptr: () => number;
|
|
83
|
+
panic_message_len: () => number;
|
|
84
|
+
};
|
|
85
|
+
export declare const readPanicMessage: (exports: EngineExports, memory: WebAssembly.Memory) => string;
|
|
86
|
+
//# sourceMappingURL=engine-exports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-exports.d.ts","sourceRoot":"","sources":["../src/engine-exports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The engine wasm module's export surface, shared by every host that instantiates it (the wasm app's own
|
|
2
|
+
// worklet, the offline perf renderer, and the studio's wasm engine processor).
|
|
3
|
+
import { decodeUtf8 } from "./utf8";
|
|
4
|
+
// Read the panic message the trapped engine left behind (empty when the failure was not a wasm panic).
|
|
5
|
+
// Decoded WITHOUT TextDecoder — the AudioWorkletGlobalScope has none, and this runs exactly when a
|
|
6
|
+
// worklet must report a panic.
|
|
7
|
+
export const readPanicMessage = (exports, memory) => {
|
|
8
|
+
const length = exports.panic_message_len();
|
|
9
|
+
if (length === 0) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
return decodeUtf8(new Uint8Array(memory.buffer, exports.panic_message_ptr(), length));
|
|
13
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type CompositeSpec = {
|
|
2
|
+
boxType: string;
|
|
3
|
+
childrenField: number;
|
|
4
|
+
indexKey: number;
|
|
5
|
+
excludeKey: number;
|
|
6
|
+
cellInstrumentField: number;
|
|
7
|
+
cellMidiField: number;
|
|
8
|
+
cellAudioField: number;
|
|
9
|
+
childEnabledKey: number;
|
|
10
|
+
childMuteKey: number;
|
|
11
|
+
childSoloKey: number;
|
|
12
|
+
};
|
|
13
|
+
export type EngineModules = {
|
|
14
|
+
engineModule: WebAssembly.Module;
|
|
15
|
+
deviceModules: ReadonlyArray<WebAssembly.Module>;
|
|
16
|
+
deviceBoxTypes: ReadonlyArray<string>;
|
|
17
|
+
composites: ReadonlyArray<CompositeSpec>;
|
|
18
|
+
};
|
|
19
|
+
export declare const createEngineMemory: () => WebAssembly.Memory;
|
|
20
|
+
export declare const DEVICES: ReadonlyArray<{
|
|
21
|
+
url: string;
|
|
22
|
+
boxType: string;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const COMPOSITES: ReadonlyArray<CompositeSpec>;
|
|
25
|
+
export declare const loadEngineModules: (base?: string) => Promise<EngineModules>;
|
|
26
|
+
//# sourceMappingURL=engine-modules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-modules.d.ts","sourceRoot":"","sources":["../src/engine-modules.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,aAAa,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAG7E,mBAAmB,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAE3E,eAAe,EAAE,MAAM,CAAC;IAGxB,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAC7C,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IACxB,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;CAC3C,CAAA;AAMD,eAAO,MAAM,kBAAkB,QAAO,WAAW,CAAC,MACsB,CAAA;AAMxE,eAAO,MAAM,OAAO,EAAE,aAAa,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA0BnE,CAAA;AAKD,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,aAAa,CAWnD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAU,OAAM,MAAW,KAAG,OAAO,CAAC,aAAa,CAOhF,CAAA"}
|