@effetune/dsp 0.1.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -5
- package/dist/assets/effetune-dsp.meta.json +491 -7
- package/dist/assets/effetune-dsp.simd.wasm +0 -0
- package/dist/assets/effetune-dsp.wasm +0 -0
- package/dist/catalog/effects-v1.json +1778 -242
- package/dist/errors.js +21 -0
- package/dist/generated-effects.d.ts +317 -1
- package/dist/generated-effects.js +185 -3
- package/dist/generated-graph-contract.js +8 -0
- package/dist/graph-document.js +644 -0
- package/dist/graph-engine.js +493 -0
- package/dist/graph.js +467 -0
- package/dist/index.d.ts +268 -1
- package/dist/index.js +36 -0
- package/dist/internal/dsp-engine-binding.js +162 -1
- package/dist/internal/dsp-params.generated.js +537 -70
- package/dist/internal/dsp-wasm-loader.js +2 -0
- package/dist/preset.js +29 -0
- package/dist/runtime.js +73 -5
- package/dist/schemas/chain-v1.schema.json +1740 -167
- package/dist/schemas/graph-v1.schema.json +8208 -0
- package/dist/semantics.js +88 -30
- package/dist/worklet-processor.js +8 -1
- package/dist/worklet.d.ts +1 -0
- package/dist/worklet.js +15 -0
- package/package.json +2 -1
package/dist/graph.js
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { loadDspArtifact } from './artifacts.js';
|
|
2
|
+
import { resolveChainAssets } from './assets.js';
|
|
3
|
+
import { AssetError, StateError, ValidationError } from './errors.js';
|
|
4
|
+
import { createGraphEngineSession, effectiveNodeIds } from './graph-engine.js';
|
|
5
|
+
import {
|
|
6
|
+
_normalizeGraphInput,
|
|
7
|
+
chainDocumentFromGraph,
|
|
8
|
+
cloneGraphDocument,
|
|
9
|
+
createSendReturnGraphDocument,
|
|
10
|
+
createWetDryGraphDocument,
|
|
11
|
+
graphDocumentFromChain,
|
|
12
|
+
graphStructuralSnapshot,
|
|
13
|
+
graphVisualizationSnapshot
|
|
14
|
+
} from './graph-document.js';
|
|
15
|
+
import {
|
|
16
|
+
channelRange,
|
|
17
|
+
packEffect,
|
|
18
|
+
setEffectParameter,
|
|
19
|
+
validateEffectSampleRate,
|
|
20
|
+
validateSampleRate,
|
|
21
|
+
validateSeed
|
|
22
|
+
} from './semantics.js';
|
|
23
|
+
|
|
24
|
+
const STREAM_SAFE_PARAMETERS = new Map([
|
|
25
|
+
['Volume', new Set(['volume'])]
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function validateAudio(audio) {
|
|
29
|
+
if (!Array.isArray(audio) || audio.length < 1 || audio.length > 8) {
|
|
30
|
+
throw new ValidationError('Audio must be an array containing between 1 and 8 Float32Array channels.');
|
|
31
|
+
}
|
|
32
|
+
const frames = audio[0] instanceof Float32Array ? audio[0].length : -1;
|
|
33
|
+
for (const channel of audio) {
|
|
34
|
+
if (!(channel instanceof Float32Array) || channel.length !== frames) {
|
|
35
|
+
throw new ValidationError('Audio channels must be equally sized Float32Array values.');
|
|
36
|
+
}
|
|
37
|
+
for (const sample of channel) {
|
|
38
|
+
if (!Number.isFinite(sample)) throw new ValidationError('Audio samples must all be finite.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { channels: audio.length, frames };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateBlockSize(value = 128) {
|
|
45
|
+
if (!Number.isInteger(value) || value < 1 || value > 16384) {
|
|
46
|
+
throw new ValidationError('blockSize must be an integer from 1 to 16384.');
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateChannels(channels) {
|
|
52
|
+
if (!Number.isInteger(channels) || channels < 1 || channels > 8) {
|
|
53
|
+
throw new ValidationError('channels must be an integer from 1 to 8.');
|
|
54
|
+
}
|
|
55
|
+
return channels;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validateLayout(state, sampleRate, channels) {
|
|
59
|
+
const effective = effectiveNodeIds(state.document);
|
|
60
|
+
for (const node of state.document.nodes) {
|
|
61
|
+
if (node.enabled && effective.has(node.id)) validateEffectSampleRate(node, sampleRate);
|
|
62
|
+
try {
|
|
63
|
+
channelRange(node.channel, channels);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof ValidationError) {
|
|
66
|
+
throw new ValidationError(error.message, {
|
|
67
|
+
code: 'GRAPH_DOCUMENT_CHANNEL',
|
|
68
|
+
path: `/nodes/${state.originalNodeIndexes.get(node.id)}/channel`,
|
|
69
|
+
nodeId: node.id,
|
|
70
|
+
cause: error
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (channels !== 2) {
|
|
77
|
+
const edge = state.document.edges.find(candidate => Object.hasOwn(candidate, 'pan'));
|
|
78
|
+
if (edge) {
|
|
79
|
+
throw new ValidationError('Edge pan is supported only for stereo Graph streams.', {
|
|
80
|
+
code: 'GRAPH_DOCUMENT_CHANNEL',
|
|
81
|
+
path: `/edges/${state.originalEdgeIndexes.get(edge.id)}/pan`,
|
|
82
|
+
edgeId: edge.id
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function reconfigurationRequired(state, node, parameterName) {
|
|
89
|
+
const index = state.originalNodeIndexes.get(node.id);
|
|
90
|
+
return new ValidationError(
|
|
91
|
+
`${node.type}.${parameterName} cannot be updated while a Graph stream is open; create a new stream.`,
|
|
92
|
+
{
|
|
93
|
+
code: 'GRAPH_RECONFIGURATION_REQUIRED',
|
|
94
|
+
path: `/nodes/${index}/parameters/${parameterName}`,
|
|
95
|
+
nodeId: node.id
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateStreamUpdate(state, node, parameterName, value) {
|
|
101
|
+
if (node.type === 'IRReverb' && parameterName === 'dryLevel') {
|
|
102
|
+
if (node.parameters.latency === 0 ||
|
|
103
|
+
node.parameters.dryEnabled === false ||
|
|
104
|
+
(node.parameters.dryLevel <= -96 && value <= -96)) return 5;
|
|
105
|
+
throw reconfigurationRequired(state, node, parameterName);
|
|
106
|
+
}
|
|
107
|
+
if (node.type === 'IRReverb' && parameterName === 'dryEnabled') {
|
|
108
|
+
if (node.parameters.latency === 0 || node.parameters.dryLevel <= -96 ||
|
|
109
|
+
(node.parameters.dryEnabled === false && value === false)) return 4;
|
|
110
|
+
throw reconfigurationRequired(state, node, parameterName);
|
|
111
|
+
}
|
|
112
|
+
if (!STREAM_SAFE_PARAMETERS.get(node.type)?.has(parameterName)) {
|
|
113
|
+
throw reconfigurationRequired(state, node, parameterName);
|
|
114
|
+
}
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function replaceNode(state, index, node) {
|
|
119
|
+
const nodes = [...state.document.nodes];
|
|
120
|
+
nodes[index] = node;
|
|
121
|
+
return { ...state, document: { ...state.document, nodes } };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function identitySnapshot(channels) {
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
version: 1,
|
|
127
|
+
identity: true,
|
|
128
|
+
silence: false,
|
|
129
|
+
effectiveSchedule: [],
|
|
130
|
+
nodes: [],
|
|
131
|
+
edges: [],
|
|
132
|
+
outputLatency: Array(channels).fill(0),
|
|
133
|
+
outputCompensation: Array(channels).fill(0),
|
|
134
|
+
latencySamples: 0,
|
|
135
|
+
capacity: { bufferSlots: 0, workspaceBytes: 0 }
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export class GraphStream {
|
|
140
|
+
constructor(state, session, { sampleRate, channels, blockSize }) {
|
|
141
|
+
this._initialState = state;
|
|
142
|
+
this._state = { ...state, document: cloneGraphDocument(state.document) };
|
|
143
|
+
this._session = session;
|
|
144
|
+
this._sampleRate = sampleRate;
|
|
145
|
+
this._channels = channels;
|
|
146
|
+
this._blockSize = blockSize;
|
|
147
|
+
this._processedFrames = 0;
|
|
148
|
+
this._closed = false;
|
|
149
|
+
this._compileSnapshot = session?.snapshot ?? identitySnapshot(channels);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_assertOpen() {
|
|
153
|
+
if (this._closed) throw new StateError('The Graph stream is closed.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get graph() {
|
|
157
|
+
this._assertOpen();
|
|
158
|
+
return cloneGraphDocument(this._state.document);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
get latencySamples() {
|
|
162
|
+
this._assertOpen();
|
|
163
|
+
return this._session?.latencySamples ?? 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
get compileSnapshot() {
|
|
167
|
+
this._assertOpen();
|
|
168
|
+
return cloneGraphDocument(this._compileSnapshot);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
visualizationSnapshot() {
|
|
172
|
+
this._assertOpen();
|
|
173
|
+
return graphVisualizationSnapshot(this._state, this._compileSnapshot);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setParam(nodeId, parameterName, value) {
|
|
177
|
+
this._assertOpen();
|
|
178
|
+
const index = this._state.document.nodes.findIndex(node => node.id === nodeId);
|
|
179
|
+
if (index < 0) {
|
|
180
|
+
throw new ValidationError(`Unknown Graph node: ${nodeId}`, {
|
|
181
|
+
code: 'GRAPH_DOCUMENT_REFERENCE', path: '', nodeId
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const node = this._state.document.nodes[index];
|
|
185
|
+
const originalIndex = this._state.originalNodeIndexes.get(nodeId);
|
|
186
|
+
if (this._session && !this._session.hasEffectiveNode(nodeId)) {
|
|
187
|
+
// Enabling a node only helps when its edges already reach the main output, so a dormant
|
|
188
|
+
// node keeps the routing wording even when it is also disabled.
|
|
189
|
+
const dormant = this._compileSnapshot.nodes.find(entry => entry.id === nodeId)?.dormant ?? false;
|
|
190
|
+
throw new ValidationError(
|
|
191
|
+
!node.enabled && !dormant
|
|
192
|
+
? `Graph node ${nodeId} is disabled and bypassed; enable it and create a new stream.`
|
|
193
|
+
: `Graph node ${nodeId} is not effective; create a new stream to change it.`,
|
|
194
|
+
{
|
|
195
|
+
code: 'GRAPH_RECONFIGURATION_REQUIRED',
|
|
196
|
+
path: `/nodes/${originalIndex}`,
|
|
197
|
+
nodeId
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
let updated;
|
|
202
|
+
try {
|
|
203
|
+
updated = setEffectParameter(node, parameterName, value);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error instanceof ValidationError) {
|
|
206
|
+
throw new ValidationError(error.message, {
|
|
207
|
+
code: 'GRAPH_DOCUMENT_PARAMETER',
|
|
208
|
+
path: `/nodes/${originalIndex}/parameters/${parameterName}`,
|
|
209
|
+
nodeId,
|
|
210
|
+
cause: error
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
const changedIndex = validateStreamUpdate(
|
|
216
|
+
this._state, node, parameterName, updated.parameters[parameterName]
|
|
217
|
+
);
|
|
218
|
+
const packed = packEffect(updated);
|
|
219
|
+
const status = this._session?.setPacked(nodeId, packed.values, packed.hash, changedIndex);
|
|
220
|
+
if (status !== undefined && status !== 0) {
|
|
221
|
+
throw reconfigurationRequired(this._state, node, parameterName);
|
|
222
|
+
}
|
|
223
|
+
this._state = replaceNode(this._state, index, updated);
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async process(audio, ...unexpected) {
|
|
228
|
+
this._assertOpen();
|
|
229
|
+
if (unexpected.length !== 0) {
|
|
230
|
+
throw new ValidationError(
|
|
231
|
+
'GraphStream.process() does not accept options or scheduled events.'
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const layout = validateAudio(audio);
|
|
235
|
+
if (layout.channels !== this._channels) {
|
|
236
|
+
throw new ValidationError(`Audio has ${layout.channels} channel(s); this Graph stream requires ${this._channels}.`);
|
|
237
|
+
}
|
|
238
|
+
const output = audio.map(channel => new Float32Array(channel));
|
|
239
|
+
if (!this._session || layout.frames === 0) return output;
|
|
240
|
+
for (let offset = 0; offset < layout.frames; offset += this._blockSize) {
|
|
241
|
+
const frameCount = Math.min(this._blockSize, layout.frames - offset);
|
|
242
|
+
this._session.process(audio, output, offset, frameCount, this._sampleRate, this._processedFrames + offset);
|
|
243
|
+
}
|
|
244
|
+
this._processedFrames += layout.frames;
|
|
245
|
+
return output;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
reset() {
|
|
249
|
+
this._assertOpen();
|
|
250
|
+
this._session?.reset();
|
|
251
|
+
this._state = { ...this._initialState, document: cloneGraphDocument(this._initialState.document) };
|
|
252
|
+
this._processedFrames = 0;
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
close() {
|
|
257
|
+
if (this._closed) return;
|
|
258
|
+
this._closed = true;
|
|
259
|
+
this._session?.close();
|
|
260
|
+
this._session = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export class Graph {
|
|
265
|
+
constructor(state, resolvedAssets, artifact, artifactOptions, assetResolver) {
|
|
266
|
+
this._state = state;
|
|
267
|
+
this._resolvedAssets = resolvedAssets;
|
|
268
|
+
this._artifact = artifact;
|
|
269
|
+
this._artifactOptions = artifactOptions;
|
|
270
|
+
this._assetResolver = assetResolver;
|
|
271
|
+
this._closed = false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
static async fromChain(chain, options = {}) {
|
|
275
|
+
const inherited = chain?._resolvedAssets instanceof Map
|
|
276
|
+
? new Map(chain._resolvedAssets)
|
|
277
|
+
: new Map();
|
|
278
|
+
return createGraph(graphDocumentFromChain(chain?.preset ?? chain), {
|
|
279
|
+
...(chain?._artifactOptions ?? {}),
|
|
280
|
+
...options,
|
|
281
|
+
_resolvedAssets: inherited
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
static async load(input, options = {}) {
|
|
286
|
+
return createGraph(input, options);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static wetDry(effect, options = {}) {
|
|
290
|
+
return createWetDryGraphDocument(effect, options);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static sendReturn(effect, options = {}) {
|
|
294
|
+
return createSendReturnGraphDocument(effect, options);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_assertOpen() {
|
|
298
|
+
if (this._closed) throw new StateError('The Graph is closed.');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
toJSON() {
|
|
302
|
+
this._assertOpen();
|
|
303
|
+
return cloneGraphDocument(this._state.document);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
serialize(space = 0) {
|
|
307
|
+
return JSON.stringify(this.toJSON(), null, space);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
toChain() {
|
|
311
|
+
this._assertOpen();
|
|
312
|
+
return chainDocumentFromGraph(this._state);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
get nodes() {
|
|
316
|
+
return this.toJSON().nodes;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
get edges() {
|
|
320
|
+
return this.toJSON().edges;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getNode(id) {
|
|
324
|
+
this._assertOpen();
|
|
325
|
+
const node = this._state.document.nodes.find(candidate => candidate.id === id);
|
|
326
|
+
return node ? cloneGraphDocument(node) : null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
getEdge(id) {
|
|
330
|
+
this._assertOpen();
|
|
331
|
+
const edge = this._state.document.edges.find(candidate => candidate.id === id);
|
|
332
|
+
return edge ? cloneGraphDocument(edge) : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
incoming(id) {
|
|
336
|
+
this._assertOpen();
|
|
337
|
+
return (this._state.incoming.get(id) ?? []).map(cloneGraphDocument);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
outgoing(id) {
|
|
341
|
+
this._assertOpen();
|
|
342
|
+
return (this._state.outgoing.get(id) ?? []).map(cloneGraphDocument);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
structuralSnapshot() {
|
|
346
|
+
this._assertOpen();
|
|
347
|
+
return graphStructuralSnapshot(this._state);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
visualizationSnapshot() {
|
|
351
|
+
this._assertOpen();
|
|
352
|
+
return graphVisualizationSnapshot(this._state);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async _resolveEffectiveAssets(state) {
|
|
356
|
+
const effective = effectiveNodeIds(state.document);
|
|
357
|
+
const unresolved = state.document.nodes.filter(node =>
|
|
358
|
+
node.enabled && effective.has(node.id) && node.assets && !this._resolvedAssets.has(node.id)
|
|
359
|
+
);
|
|
360
|
+
// Resolving one node at a time keeps the failing node identifiable; resolveChainAssets is
|
|
361
|
+
// already sequential, so this costs nothing.
|
|
362
|
+
for (const node of unresolved) {
|
|
363
|
+
let resolved;
|
|
364
|
+
try {
|
|
365
|
+
resolved = await resolveChainAssets(
|
|
366
|
+
{ version: 1, chain: [node] },
|
|
367
|
+
{ assetResolver: this._assetResolver }
|
|
368
|
+
);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
if (error instanceof AssetError) {
|
|
371
|
+
throw new AssetError(error.message, {
|
|
372
|
+
code: 'GRAPH_INSTANCE_PREPARE',
|
|
373
|
+
path: `/nodes/${state.originalNodeIndexes.get(node.id)}/assets`,
|
|
374
|
+
nodeId: node.id,
|
|
375
|
+
cause: error
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
for (const [nodeId, assets] of resolved) this._resolvedAssets.set(nodeId, assets);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async _artifactForCurrentDocument() {
|
|
385
|
+
if (this._state.document.nodes.length === 0 && this._state.document.edges.length === 0) return null;
|
|
386
|
+
if (!this._artifact) this._artifact = await loadDspArtifact(this._artifactOptions);
|
|
387
|
+
return this._artifact;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async stream({ sampleRate, channels = 2, blockSize = 128, seed = 0 } = {}) {
|
|
391
|
+
this._assertOpen();
|
|
392
|
+
const rate = validateSampleRate(sampleRate);
|
|
393
|
+
const channelCount = validateChannels(channels);
|
|
394
|
+
const framesPerBlock = validateBlockSize(blockSize);
|
|
395
|
+
const normalizedSeed = validateSeed(seed);
|
|
396
|
+
validateLayout(this._state, rate, channelCount);
|
|
397
|
+
const artifact = await this._artifactForCurrentDocument();
|
|
398
|
+
const state = {
|
|
399
|
+
...this._state,
|
|
400
|
+
document: cloneGraphDocument(this._state.document)
|
|
401
|
+
};
|
|
402
|
+
await this._resolveEffectiveAssets(state);
|
|
403
|
+
const session = artifact
|
|
404
|
+
? await createGraphEngineSession(artifact, state, this._resolvedAssets, {
|
|
405
|
+
sampleRate: rate,
|
|
406
|
+
channels: channelCount,
|
|
407
|
+
maxFrames: Math.max(128, framesPerBlock),
|
|
408
|
+
seed: normalizedSeed
|
|
409
|
+
})
|
|
410
|
+
: null;
|
|
411
|
+
return new GraphStream(state, session, {
|
|
412
|
+
sampleRate: rate,
|
|
413
|
+
channels: channelCount,
|
|
414
|
+
blockSize: framesPerBlock
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async latencySamples(options = {}) {
|
|
419
|
+
const stream = await this.stream(options);
|
|
420
|
+
try {
|
|
421
|
+
return stream.latencySamples;
|
|
422
|
+
} finally {
|
|
423
|
+
stream.close();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async process(audio, { sampleRate, seed = 0, blockSize = 128 } = {}) {
|
|
428
|
+
this._assertOpen();
|
|
429
|
+
const layout = validateAudio(audio);
|
|
430
|
+
const stream = await this.stream({
|
|
431
|
+
sampleRate,
|
|
432
|
+
channels: layout.channels,
|
|
433
|
+
blockSize,
|
|
434
|
+
seed
|
|
435
|
+
});
|
|
436
|
+
try {
|
|
437
|
+
return await stream.process(audio);
|
|
438
|
+
} finally {
|
|
439
|
+
stream.close();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
close() {
|
|
444
|
+
if (this._closed) return;
|
|
445
|
+
this._closed = true;
|
|
446
|
+
this._artifact = null;
|
|
447
|
+
this._resolvedAssets.clear();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export async function createGraph(input, options = {}) {
|
|
452
|
+
const state = _normalizeGraphInput(input);
|
|
453
|
+
const resolvedAssets = new Map(options._resolvedAssets ?? []);
|
|
454
|
+
const artifactOptions = {
|
|
455
|
+
variant: options.variant,
|
|
456
|
+
wasmUrl: options.wasmUrl,
|
|
457
|
+
simdWasmUrl: options.simdWasmUrl,
|
|
458
|
+
metaUrl: options.metaUrl,
|
|
459
|
+
fetch: options.fetch,
|
|
460
|
+
webAssembly: options.webAssembly,
|
|
461
|
+
cache: options.cache
|
|
462
|
+
};
|
|
463
|
+
const artifact = state.document.nodes.length === 0 && state.document.edges.length === 0
|
|
464
|
+
? null
|
|
465
|
+
: await loadDspArtifact(artifactOptions);
|
|
466
|
+
return new Graph(state, resolvedAssets, artifact, artifactOptions, options.assetResolver);
|
|
467
|
+
}
|