@grame/faustwasm 0.12.1 → 0.13.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/.prettierrc +5 -0
- package/README.md +21 -3
- package/assets/standalone/faustwasm/index.d.ts +39 -17
- package/assets/standalone/faustwasm/index.js +1580 -408
- package/assets/standalone/faustwasm/index.js.map +2 -2
- package/dist/cjs/index.d.ts +39 -17
- package/dist/cjs/index.js +1579 -408
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs-bundle/index.d.ts +39 -17
- package/dist/cjs-bundle/index.js +1582 -410
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm/index.d.ts +39 -17
- package/dist/esm/index.js +1580 -408
- package/dist/esm/index.js.map +2 -2
- package/dist/esm-bundle/index.d.ts +39 -17
- package/dist/esm-bundle/index.js +1582 -410
- package/dist/esm-bundle/index.js.map +2 -2
- package/eslint.config.js +11 -0
- package/package.json +57 -51
- package/sound.cpp +163 -0
- package/sound.dsp +4 -0
- package/sound.json +1 -0
- package/sound.wasm +0 -0
- package/src/FaustAudioWorkletCommunicator.ts +150 -143
- package/src/FaustAudioWorkletNode.ts +173 -75
- package/src/FaustAudioWorkletProcessor.ts +218 -90
- package/src/FaustCmajor.ts +9 -3
- package/src/FaustCompiler.ts +112 -35
- package/src/FaustDspGenerator.ts +563 -118
- package/src/FaustDspInstance.ts +58 -24
- package/src/FaustFFTAudioWorkletProcessor.ts +822 -612
- package/src/FaustOfflineProcessor.ts +187 -56
- package/src/FaustScriptProcessorNode.ts +156 -49
- package/src/FaustSensors.ts +426 -96
- package/src/FaustSvgDiagrams.ts +18 -5
- package/src/FaustWasmInstantiator.ts +224 -70
- package/src/FaustWebAudioDsp.ts +1062 -351
- package/src/LibFaust.ts +13 -4
- package/src/SoundfileReader.ts +175 -131
- package/src/WavDecoder.ts +42 -20
- package/src/WavEncoder.ts +35 -21
- package/src/copyWebStandaloneAssets.d.ts +20 -5
- package/src/copyWebStandaloneAssets.js +203 -75
- package/src/exports-bundle.ts +5 -5
- package/src/exports.ts +24 -24
- package/src/faust2CmajorFiles.d.ts +7 -3
- package/src/faust2cmajorFiles.js +10 -8
- package/src/faust2svgFiles.d.ts +7 -3
- package/src/faust2svgFiles.js +9 -7
- package/src/faust2wasmFiles.d.ts +10 -5
- package/src/faust2wasmFiles.js +34 -17
- package/src/faust2wavFiles.d.ts +12 -3
- package/src/faust2wavFiles.js +25 -12
- package/src/index-bundle-iife.ts +2 -2
- package/src/index-bundle.ts +1 -1
- package/src/index.ts +1 -1
- package/src/instantiateFaustModule.ts +43 -36
- package/src/instantiateFaustModuleFromFile.ts +30 -14
- package/src/types.ts +102 -26
- package/test/faustlive-wasm/faustwasm/index.d.ts +39 -17
- package/test/faustlive-wasm/faustwasm/index.js +1580 -408
- package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
- package/test/web/create-faust-node.html +53 -0
- package/test/web/create-faust-node.js +186 -0
package/src/FaustDspGenerator.ts
CHANGED
|
@@ -1,22 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
1
|
+
import {
|
|
2
|
+
FaustMonoAudioWorkletNode,
|
|
3
|
+
FaustPolyAudioWorkletNode
|
|
4
|
+
} from './FaustAudioWorkletNode';
|
|
5
|
+
import getFaustAudioWorkletProcessor, {
|
|
6
|
+
FaustData
|
|
7
|
+
} from './FaustAudioWorkletProcessor';
|
|
8
|
+
import getFaustFFTAudioWorkletProcessor, {
|
|
9
|
+
FaustFFTData,
|
|
10
|
+
FaustFFTOptionsData
|
|
11
|
+
} from './FaustFFTAudioWorkletProcessor';
|
|
12
|
+
import { FaustDspInstance } from './FaustDspInstance';
|
|
13
|
+
import FaustWasmInstantiator from './FaustWasmInstantiator';
|
|
14
|
+
import {
|
|
15
|
+
FaustMonoOfflineProcessor,
|
|
16
|
+
FaustPolyOfflineProcessor,
|
|
17
|
+
IFaustMonoOfflineProcessor,
|
|
18
|
+
IFaustPolyOfflineProcessor
|
|
19
|
+
} from './FaustOfflineProcessor';
|
|
20
|
+
import {
|
|
21
|
+
FaustMonoScriptProcessorNode,
|
|
22
|
+
FaustPolyScriptProcessorNode
|
|
23
|
+
} from './FaustScriptProcessorNode';
|
|
24
|
+
import {
|
|
25
|
+
FaustBaseWebAudioDsp,
|
|
26
|
+
FaustMonoWebAudioDsp,
|
|
27
|
+
FaustPolyWebAudioDsp,
|
|
28
|
+
FaustWebAudioDspVoice,
|
|
29
|
+
IFaustMonoWebAudioNode,
|
|
30
|
+
IFaustPolyWebAudioNode,
|
|
31
|
+
Soundfile,
|
|
32
|
+
WasmAllocator
|
|
33
|
+
} from './FaustWebAudioDsp';
|
|
34
|
+
import SoundfileReader from './SoundfileReader';
|
|
35
|
+
import FaustSensors from './FaustSensors';
|
|
36
|
+
import FaustCompiler, { IFaustCompiler } from './FaustCompiler';
|
|
37
|
+
import type {
|
|
38
|
+
FaustDspFactory,
|
|
39
|
+
FaustUIDescriptor,
|
|
40
|
+
FaustDspMeta,
|
|
41
|
+
FFTUtils,
|
|
42
|
+
LooseFaustDspFactory,
|
|
43
|
+
AudioData
|
|
44
|
+
} from './types';
|
|
45
|
+
import {
|
|
46
|
+
FaustAudioWorkletCommunicator,
|
|
47
|
+
FaustAudioWorkletProcessorCommunicator
|
|
48
|
+
} from './FaustAudioWorkletCommunicator';
|
|
49
|
+
import instantiateFaustModuleFromFile from './instantiateFaustModuleFromFile';
|
|
50
|
+
import LibFaust from './LibFaust';
|
|
15
51
|
|
|
16
52
|
export interface GeneratorSupportingSoundfiles {
|
|
17
53
|
/**
|
|
18
54
|
* Attach a map of id - audio data, call after `compile()` before `createNode()`
|
|
19
|
-
*
|
|
55
|
+
*
|
|
20
56
|
* @param soundfileMap a map of id - `AudioData` as an object where `AudioData` contains channel data as `audioBuffer: Float32Array[]` and `sampleRate: number`
|
|
21
57
|
*/
|
|
22
58
|
addSoundfiles(soundfileMap: Record<string, AudioData>): void;
|
|
@@ -27,17 +63,43 @@ export interface GeneratorSupportingSoundfiles {
|
|
|
27
63
|
getSoundfileList(): string[];
|
|
28
64
|
}
|
|
29
65
|
|
|
66
|
+
export interface IFaustDspGenerator {
|
|
67
|
+
/**
|
|
68
|
+
* Create a monophonic or polyphonic WebAudio node (either ScriptProcessorNode or AudioWorkletNode).
|
|
69
|
+
* Analyze the code to decide whether to create a monophonic or polyphonic node.
|
|
70
|
+
*
|
|
71
|
+
* @param context - the WebAudio context
|
|
72
|
+
* @param name - DSP name, can be used for processorName
|
|
73
|
+
* @param code - the DSP code
|
|
74
|
+
* @param sp - whether to compile a ScriptProcessorNode or an AudioWorkletNode
|
|
75
|
+
* @param bufferSize - the buffer size in frames to be used in ScriptProcessorNode only, since AudioWorkletNode always uses 128 frames
|
|
76
|
+
* @returns the compiled monophonic or polyphonic WebAudio node or 'null' if failure
|
|
77
|
+
*/
|
|
78
|
+
createFaustNode(
|
|
79
|
+
context: BaseAudioContext,
|
|
80
|
+
name: string,
|
|
81
|
+
code: string,
|
|
82
|
+
sp?: boolean,
|
|
83
|
+
bufferSize?: number,
|
|
84
|
+
): Promise<IFaustMonoWebAudioNode | IFaustPolyWebAudioNode | null>;
|
|
85
|
+
}
|
|
86
|
+
|
|
30
87
|
export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
31
88
|
/**
|
|
32
89
|
* Compile a monophonic DSP factory from given code.
|
|
33
|
-
*
|
|
90
|
+
*
|
|
34
91
|
* @param compiler - the Faust compiler
|
|
35
92
|
* @param name - the DSP name
|
|
36
93
|
* @param code - the DSP code
|
|
37
94
|
* @param args - the compilation parameters
|
|
38
95
|
* @returns the compiled factory or 'null' if failure
|
|
39
96
|
*/
|
|
40
|
-
compile(
|
|
97
|
+
compile(
|
|
98
|
+
compiler: IFaustCompiler,
|
|
99
|
+
name: string,
|
|
100
|
+
code: string,
|
|
101
|
+
args: string
|
|
102
|
+
): Promise<{
|
|
41
103
|
factory: FaustDspFactory | null;
|
|
42
104
|
name?: string;
|
|
43
105
|
meta?: FaustDspMeta;
|
|
@@ -128,7 +190,7 @@ export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
128
190
|
export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
129
191
|
/**
|
|
130
192
|
* Compile a monophonic DSP factory from given code.
|
|
131
|
-
*
|
|
193
|
+
*
|
|
132
194
|
* @param compiler - the Faust compiler
|
|
133
195
|
* @param name - the DSP name
|
|
134
196
|
* @param dspCode - the DSP code ('dspCode' can possibly contain an integrated effect)
|
|
@@ -136,7 +198,13 @@ export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
136
198
|
* @param effectCode - optional effect DSP code
|
|
137
199
|
* @returns the compiled factory or 'null' if failure
|
|
138
200
|
*/
|
|
139
|
-
compile(
|
|
201
|
+
compile(
|
|
202
|
+
compiler: IFaustCompiler,
|
|
203
|
+
name: string,
|
|
204
|
+
dspCode: string,
|
|
205
|
+
args: string,
|
|
206
|
+
effectCode?: string
|
|
207
|
+
): Promise<{
|
|
140
208
|
voiceFactory: FaustDspFactory | null;
|
|
141
209
|
effectFactory?: FaustDspFactory | null;
|
|
142
210
|
} | null>;
|
|
@@ -213,7 +281,8 @@ export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
213
281
|
|
|
214
282
|
export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
215
283
|
// Set of all created WorkletProcessors, each of them has to be unique
|
|
216
|
-
private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> =
|
|
284
|
+
private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> =
|
|
285
|
+
new Map();
|
|
217
286
|
|
|
218
287
|
name: string;
|
|
219
288
|
factory!: FaustDspFactory | null;
|
|
@@ -221,7 +290,12 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
|
221
290
|
constructor() {
|
|
222
291
|
this.factory = null;
|
|
223
292
|
}
|
|
224
|
-
async compile(
|
|
293
|
+
async compile(
|
|
294
|
+
compiler: IFaustCompiler,
|
|
295
|
+
name: string,
|
|
296
|
+
code: string,
|
|
297
|
+
args: string
|
|
298
|
+
) {
|
|
225
299
|
this.factory = await compiler.createMonoDSPFactory(name, code, args);
|
|
226
300
|
if (this.factory) {
|
|
227
301
|
this.name = name;
|
|
@@ -232,15 +306,22 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
|
232
306
|
}
|
|
233
307
|
|
|
234
308
|
addSoundfiles(soundfileMap: Record<string, AudioData>) {
|
|
235
|
-
if (!this.factory)
|
|
309
|
+
if (!this.factory)
|
|
310
|
+
throw new Error(
|
|
311
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
312
|
+
);
|
|
236
313
|
for (const id in soundfileMap) {
|
|
237
314
|
this.factory.soundfiles[id] = soundfileMap[id];
|
|
238
315
|
}
|
|
239
316
|
}
|
|
240
317
|
getSoundfileList() {
|
|
241
|
-
if (!this.factory)
|
|
318
|
+
if (!this.factory)
|
|
319
|
+
throw new Error(
|
|
320
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
321
|
+
);
|
|
242
322
|
const meta = JSON.parse(this.factory.json);
|
|
243
323
|
const map = SoundfileReader.findSoundfilesFromMeta(meta);
|
|
324
|
+
if (!map) return [];
|
|
244
325
|
return Object.keys(map);
|
|
245
326
|
}
|
|
246
327
|
|
|
@@ -252,24 +333,56 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
|
252
333
|
bufferSize = 1024,
|
|
253
334
|
processorName = factory?.shaKey || name,
|
|
254
335
|
processorOptions: Record<string, any> = {}
|
|
255
|
-
): Promise<
|
|
256
|
-
|
|
336
|
+
): Promise<
|
|
337
|
+
SP extends true
|
|
338
|
+
? FaustMonoScriptProcessorNode | null
|
|
339
|
+
: FaustMonoAudioWorkletNode | null
|
|
340
|
+
> {
|
|
341
|
+
if (!factory)
|
|
342
|
+
throw new Error(
|
|
343
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
344
|
+
);
|
|
257
345
|
|
|
258
346
|
const meta = JSON.parse(factory.json);
|
|
259
|
-
const sampleSize = meta.compile_options.match(
|
|
260
|
-
factory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
347
|
+
const sampleSize = meta.compile_options.match('-double') ? 8 : 4;
|
|
348
|
+
factory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
349
|
+
meta,
|
|
350
|
+
factory.soundfiles || {},
|
|
351
|
+
context
|
|
352
|
+
);
|
|
261
353
|
if (sp) {
|
|
262
|
-
const instance =
|
|
263
|
-
|
|
354
|
+
const instance =
|
|
355
|
+
await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
|
|
356
|
+
const monoDsp = new FaustMonoWebAudioDsp(
|
|
357
|
+
instance,
|
|
358
|
+
context.sampleRate,
|
|
359
|
+
sampleSize,
|
|
360
|
+
bufferSize,
|
|
361
|
+
factory.soundfiles
|
|
362
|
+
);
|
|
264
363
|
|
|
265
|
-
const sp = context.createScriptProcessor(
|
|
364
|
+
const sp = context.createScriptProcessor(
|
|
365
|
+
bufferSize,
|
|
366
|
+
monoDsp.getNumInputs(),
|
|
367
|
+
monoDsp.getNumOutputs()
|
|
368
|
+
) as FaustMonoScriptProcessorNode;
|
|
266
369
|
Object.setPrototypeOf(sp, FaustMonoScriptProcessorNode.prototype);
|
|
267
370
|
sp.init(monoDsp);
|
|
268
|
-
return sp as SP extends true
|
|
371
|
+
return sp as SP extends true
|
|
372
|
+
? FaustMonoScriptProcessorNode
|
|
373
|
+
: FaustMonoAudioWorkletNode;
|
|
269
374
|
} else {
|
|
270
375
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
271
|
-
if (!FaustMonoDspGenerator.gWorkletProcessors.has(context))
|
|
272
|
-
|
|
376
|
+
if (!FaustMonoDspGenerator.gWorkletProcessors.has(context))
|
|
377
|
+
FaustMonoDspGenerator.gWorkletProcessors.set(
|
|
378
|
+
context,
|
|
379
|
+
new Set()
|
|
380
|
+
);
|
|
381
|
+
if (
|
|
382
|
+
!FaustMonoDspGenerator.gWorkletProcessors
|
|
383
|
+
.get(context)
|
|
384
|
+
?.has(processorName)
|
|
385
|
+
) {
|
|
273
386
|
try {
|
|
274
387
|
const processorCode = `
|
|
275
388
|
// DSP name and JSON string for DSP are generated
|
|
@@ -308,10 +421,14 @@ const dependencies = {
|
|
|
308
421
|
// Generate the actual AudioWorkletProcessor code
|
|
309
422
|
(${getFaustAudioWorkletProcessor.toString()})(dependencies, faustData);
|
|
310
423
|
`;
|
|
311
|
-
const url = URL.createObjectURL(
|
|
424
|
+
const url = URL.createObjectURL(
|
|
425
|
+
new Blob([processorCode], { type: 'text/javascript' })
|
|
426
|
+
);
|
|
312
427
|
await context.audioWorklet.addModule(url);
|
|
313
428
|
// Keep the DSP name
|
|
314
|
-
FaustMonoDspGenerator.gWorkletProcessors
|
|
429
|
+
FaustMonoDspGenerator.gWorkletProcessors
|
|
430
|
+
.get(context)
|
|
431
|
+
?.add(processorName);
|
|
315
432
|
} catch (e) {
|
|
316
433
|
// console.error(`=> exception raised while running createMonoNode: ${e}`);
|
|
317
434
|
// console.error(`=> check that your page is served using https.${e}`);
|
|
@@ -319,9 +436,18 @@ const dependencies = {
|
|
|
319
436
|
}
|
|
320
437
|
}
|
|
321
438
|
// Create the AWN
|
|
322
|
-
const node = new FaustMonoAudioWorkletNode(context, {
|
|
439
|
+
const node = new FaustMonoAudioWorkletNode(context, {
|
|
440
|
+
processorOptions: {
|
|
441
|
+
name: processorName,
|
|
442
|
+
factory,
|
|
443
|
+
sampleSize,
|
|
444
|
+
...processorOptions
|
|
445
|
+
}
|
|
446
|
+
});
|
|
323
447
|
|
|
324
|
-
return node as SP extends true
|
|
448
|
+
return node as SP extends true
|
|
449
|
+
? FaustMonoScriptProcessorNode
|
|
450
|
+
: FaustMonoAudioWorkletNode;
|
|
325
451
|
}
|
|
326
452
|
}
|
|
327
453
|
|
|
@@ -334,14 +460,26 @@ const dependencies = {
|
|
|
334
460
|
processorName = factory?.shaKey ? `${factory.shaKey}_fft` : name,
|
|
335
461
|
processorOptions: Record<string, any> = {}
|
|
336
462
|
): Promise<FaustMonoAudioWorkletNode | null> {
|
|
337
|
-
if (!factory)
|
|
463
|
+
if (!factory)
|
|
464
|
+
throw new Error(
|
|
465
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
466
|
+
);
|
|
338
467
|
|
|
339
468
|
const meta: FaustDspMeta = JSON.parse(factory.json);
|
|
340
|
-
const sampleSize = meta.compile_options.match(
|
|
341
|
-
factory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
469
|
+
const sampleSize = meta.compile_options.match('-double') ? 8 : 4;
|
|
470
|
+
factory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
471
|
+
meta,
|
|
472
|
+
factory.soundfiles || {},
|
|
473
|
+
context
|
|
474
|
+
);
|
|
342
475
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
343
|
-
if (!FaustMonoDspGenerator.gWorkletProcessors.has(context))
|
|
344
|
-
|
|
476
|
+
if (!FaustMonoDspGenerator.gWorkletProcessors.has(context))
|
|
477
|
+
FaustMonoDspGenerator.gWorkletProcessors.set(context, new Set());
|
|
478
|
+
if (
|
|
479
|
+
!FaustMonoDspGenerator.gWorkletProcessors
|
|
480
|
+
.get(context)
|
|
481
|
+
?.has(processorName)
|
|
482
|
+
) {
|
|
345
483
|
try {
|
|
346
484
|
const processorCode = `
|
|
347
485
|
// DSP name and JSON string for DSP are generated
|
|
@@ -382,10 +520,14 @@ const dependencies = {
|
|
|
382
520
|
// Generate the actual AudioWorkletProcessor code
|
|
383
521
|
(${getFaustFFTAudioWorkletProcessor.toString()})(dependencies, faustData);
|
|
384
522
|
`;
|
|
385
|
-
const url = URL.createObjectURL(
|
|
523
|
+
const url = URL.createObjectURL(
|
|
524
|
+
new Blob([processorCode], { type: 'text/javascript' })
|
|
525
|
+
);
|
|
386
526
|
await context.audioWorklet.addModule(url);
|
|
387
527
|
// Keep the DSP name
|
|
388
|
-
FaustMonoDspGenerator.gWorkletProcessors
|
|
528
|
+
FaustMonoDspGenerator.gWorkletProcessors
|
|
529
|
+
.get(context)
|
|
530
|
+
?.add(processorName);
|
|
389
531
|
} catch (e) {
|
|
390
532
|
// console.error(`=> exception raised while running createMonoNode: ${e}`);
|
|
391
533
|
// console.error(`=> check that your page is served using https.${e}`);
|
|
@@ -393,21 +535,30 @@ const dependencies = {
|
|
|
393
535
|
}
|
|
394
536
|
}
|
|
395
537
|
// Create the AWN
|
|
396
|
-
const node = new FaustMonoAudioWorkletNode(context, {
|
|
538
|
+
const node = new FaustMonoAudioWorkletNode(context, {
|
|
539
|
+
channelCount: Math.max(1, Math.ceil(meta.inputs / 3)),
|
|
540
|
+
outputChannelCount: [Math.ceil(meta.outputs / 2)],
|
|
541
|
+
processorOptions: {
|
|
542
|
+
name: processorName,
|
|
543
|
+
factory,
|
|
544
|
+
sampleSize,
|
|
545
|
+
...processorOptions
|
|
546
|
+
}
|
|
547
|
+
});
|
|
397
548
|
if (fftOptions.fftSize) {
|
|
398
|
-
const param = node.parameters.get(
|
|
549
|
+
const param = node.parameters.get('fftSize');
|
|
399
550
|
if (param) param.value = fftOptions.fftSize;
|
|
400
551
|
}
|
|
401
552
|
if (fftOptions.fftOverlap) {
|
|
402
|
-
const param = node.parameters.get(
|
|
553
|
+
const param = node.parameters.get('fftOverlap');
|
|
403
554
|
if (param) param.value = fftOptions.fftOverlap;
|
|
404
555
|
}
|
|
405
|
-
if (typeof fftOptions.defaultWindowFunction ===
|
|
406
|
-
const param = node.parameters.get(
|
|
556
|
+
if (typeof fftOptions.defaultWindowFunction === 'number') {
|
|
557
|
+
const param = node.parameters.get('windowFunction');
|
|
407
558
|
if (param) param.value = fftOptions.defaultWindowFunction + 1;
|
|
408
559
|
}
|
|
409
|
-
if (typeof fftOptions.noIFFT ===
|
|
410
|
-
const param = node.parameters.get(
|
|
560
|
+
if (typeof fftOptions.noIFFT === 'boolean') {
|
|
561
|
+
const param = node.parameters.get('noIFFT');
|
|
411
562
|
if (param) param.value = +fftOptions.noIFFT;
|
|
412
563
|
}
|
|
413
564
|
return node;
|
|
@@ -418,7 +569,10 @@ const dependencies = {
|
|
|
418
569
|
factory = this.factory as LooseFaustDspFactory,
|
|
419
570
|
processorName = factory?.shaKey || name
|
|
420
571
|
) {
|
|
421
|
-
if (!factory)
|
|
572
|
+
if (!factory)
|
|
573
|
+
throw new Error(
|
|
574
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
575
|
+
);
|
|
422
576
|
|
|
423
577
|
const meta = JSON.parse(factory.json);
|
|
424
578
|
const dependencies = {
|
|
@@ -427,8 +581,8 @@ const dependencies = {
|
|
|
427
581
|
FaustWasmInstantiator,
|
|
428
582
|
FaustAudioWorkletProcessorCommunicator,
|
|
429
583
|
FaustPolyWebAudioDsp: undefined,
|
|
430
|
-
FaustWebAudioDspVoice: undefined
|
|
431
|
-
}
|
|
584
|
+
FaustWebAudioDspVoice: undefined
|
|
585
|
+
};
|
|
432
586
|
// const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
|
|
433
587
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
434
588
|
try {
|
|
@@ -440,7 +594,10 @@ const dependencies = {
|
|
|
440
594
|
poly: false
|
|
441
595
|
} as FaustData;
|
|
442
596
|
// Generate the actual AudioWorkletProcessor code
|
|
443
|
-
const Processor = getFaustAudioWorkletProcessor(
|
|
597
|
+
const Processor = getFaustAudioWorkletProcessor(
|
|
598
|
+
dependencies,
|
|
599
|
+
faustData
|
|
600
|
+
);
|
|
444
601
|
return Processor;
|
|
445
602
|
} catch (e) {
|
|
446
603
|
// console.error(`=> exception raised while running createMonoNode: ${e}`);
|
|
@@ -455,24 +612,46 @@ const dependencies = {
|
|
|
455
612
|
factory = this.factory as LooseFaustDspFactory,
|
|
456
613
|
context?: BaseAudioContext
|
|
457
614
|
) {
|
|
458
|
-
if (!factory)
|
|
615
|
+
if (!factory)
|
|
616
|
+
throw new Error(
|
|
617
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
618
|
+
);
|
|
459
619
|
|
|
460
620
|
const meta = JSON.parse(factory.json);
|
|
461
|
-
const instance =
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
621
|
+
const instance =
|
|
622
|
+
await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
|
|
623
|
+
const sampleSize = meta.compile_options.match('-double') ? 8 : 4;
|
|
624
|
+
if (context)
|
|
625
|
+
factory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
626
|
+
meta,
|
|
627
|
+
factory.soundfiles || {},
|
|
628
|
+
context
|
|
629
|
+
);
|
|
630
|
+
const monoDsp = new FaustMonoWebAudioDsp(
|
|
631
|
+
instance,
|
|
632
|
+
sampleRate,
|
|
633
|
+
sampleSize,
|
|
634
|
+
bufferSize,
|
|
635
|
+
factory.soundfiles
|
|
636
|
+
);
|
|
465
637
|
return new FaustMonoOfflineProcessor(monoDsp, bufferSize);
|
|
466
638
|
}
|
|
467
639
|
|
|
468
|
-
getMeta() {
|
|
469
|
-
|
|
470
|
-
|
|
640
|
+
getMeta() {
|
|
641
|
+
return JSON.parse(this.factory!.json);
|
|
642
|
+
}
|
|
643
|
+
getJSON() {
|
|
644
|
+
return JSON.stringify(this.getMeta());
|
|
645
|
+
}
|
|
646
|
+
getUI() {
|
|
647
|
+
return this.getMeta().ui;
|
|
648
|
+
}
|
|
471
649
|
}
|
|
472
650
|
|
|
473
651
|
export class FaustPolyDspGenerator implements IFaustPolyDspGenerator {
|
|
474
652
|
// Set of all created WorkletProcessors, each of them has to be unique
|
|
475
|
-
private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> =
|
|
653
|
+
private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> =
|
|
654
|
+
new Map();
|
|
476
655
|
|
|
477
656
|
name: string;
|
|
478
657
|
voiceFactory!: FaustDspFactory | null;
|
|
@@ -489,7 +668,7 @@ export class FaustPolyDspGenerator implements IFaustPolyDspGenerator {
|
|
|
489
668
|
name: string,
|
|
490
669
|
dspCodeAux: string,
|
|
491
670
|
args: string,
|
|
492
|
-
// The ${dspCode} has to be added with wrapping new lines to make it properly formatted and ready to compile
|
|
671
|
+
// The ${dspCode} has to be added with wrapping new lines to make it properly formatted and ready to compile
|
|
493
672
|
effectCodeAux = `dsp_code = environment{
|
|
494
673
|
${dspCodeAux}
|
|
495
674
|
};
|
|
@@ -497,8 +676,12 @@ export class FaustPolyDspGenerator implements IFaustPolyDspGenerator {
|
|
|
497
676
|
) {
|
|
498
677
|
// Try to compile effect, possibly failing
|
|
499
678
|
try {
|
|
500
|
-
this.effectFactory = await compiler.createPolyDSPFactory(
|
|
501
|
-
|
|
679
|
+
this.effectFactory = await compiler.createPolyDSPFactory(
|
|
680
|
+
name,
|
|
681
|
+
effectCodeAux,
|
|
682
|
+
args
|
|
683
|
+
);
|
|
684
|
+
// Since the effect is processing the same buffers for inputs and outputs (in place processing),
|
|
502
685
|
// the voice and effect are adapted, possibly clearing buffers
|
|
503
686
|
if (this.effectFactory) {
|
|
504
687
|
const effectJSON = JSON.parse(this.effectFactory.json);
|
|
@@ -541,24 +724,37 @@ dsp_code = environment{
|
|
|
541
724
|
};
|
|
542
725
|
process = adaptorIns(dsp_code.process) : dsp_code.effect : adaptorOuts;
|
|
543
726
|
`;
|
|
544
|
-
this.voiceFactory = await compiler.createPolyDSPFactory(
|
|
727
|
+
this.voiceFactory = await compiler.createPolyDSPFactory(
|
|
728
|
+
name,
|
|
729
|
+
dspCode,
|
|
730
|
+
args
|
|
731
|
+
);
|
|
545
732
|
try {
|
|
546
733
|
// Effect is processing same buffers for inputs and outputs, so has to use -inpl option
|
|
547
|
-
this.effectFactory = await compiler.createPolyDSPFactory(
|
|
734
|
+
this.effectFactory = await compiler.createPolyDSPFactory(
|
|
735
|
+
name,
|
|
736
|
+
effectCode,
|
|
737
|
+
args + ' -inpl'
|
|
738
|
+
);
|
|
548
739
|
} catch (e) {
|
|
549
740
|
console.warn(e);
|
|
550
741
|
}
|
|
551
742
|
}
|
|
552
743
|
} catch (e) {
|
|
553
744
|
console.warn(e);
|
|
554
|
-
this.voiceFactory = await compiler.createPolyDSPFactory(
|
|
745
|
+
this.voiceFactory = await compiler.createPolyDSPFactory(
|
|
746
|
+
name,
|
|
747
|
+
dspCodeAux,
|
|
748
|
+
args
|
|
749
|
+
);
|
|
555
750
|
}
|
|
556
751
|
|
|
557
752
|
if (this.voiceFactory) {
|
|
558
753
|
this.name = name;
|
|
559
754
|
const voiceMeta = JSON.parse(this.voiceFactory.json);
|
|
560
|
-
const isDouble = voiceMeta.compile_options.match(
|
|
561
|
-
const { mixerBuffer, mixerModule } =
|
|
755
|
+
const isDouble = voiceMeta.compile_options.match('-double');
|
|
756
|
+
const { mixerBuffer, mixerModule } =
|
|
757
|
+
await compiler.getAsyncInternalMixerModule(!!isDouble);
|
|
562
758
|
this.mixerBuffer = mixerBuffer;
|
|
563
759
|
this.mixerModule = mixerModule;
|
|
564
760
|
return this;
|
|
@@ -568,15 +764,22 @@ process = adaptorIns(dsp_code.process) : dsp_code.effect : adaptorOuts;
|
|
|
568
764
|
}
|
|
569
765
|
|
|
570
766
|
addSoundfiles(soundfileMap: Record<string, AudioData>) {
|
|
571
|
-
if (!this.voiceFactory)
|
|
767
|
+
if (!this.voiceFactory)
|
|
768
|
+
throw new Error(
|
|
769
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
770
|
+
);
|
|
572
771
|
for (const id in soundfileMap) {
|
|
573
772
|
this.voiceFactory.soundfiles[id] = soundfileMap[id];
|
|
574
773
|
}
|
|
575
774
|
}
|
|
576
775
|
getSoundfileList() {
|
|
577
|
-
if (!this.voiceFactory)
|
|
776
|
+
if (!this.voiceFactory)
|
|
777
|
+
throw new Error(
|
|
778
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
779
|
+
);
|
|
578
780
|
const meta = JSON.parse(this.voiceFactory.json);
|
|
579
781
|
const map = SoundfileReader.findSoundfilesFromMeta(meta);
|
|
782
|
+
if (!map) return [];
|
|
580
783
|
if (!this.effectFactory) return Object.keys(map);
|
|
581
784
|
const effectMeta = JSON.parse(this.effectFactory.json);
|
|
582
785
|
const effectMap = SoundfileReader.findSoundfilesFromMeta(effectMeta);
|
|
@@ -592,29 +795,77 @@ process = adaptorIns(dsp_code.process) : dsp_code.effect : adaptorOuts;
|
|
|
592
795
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
593
796
|
sp = false as SP,
|
|
594
797
|
bufferSize = 1024,
|
|
595
|
-
processorName = (
|
|
798
|
+
processorName = (voiceFactory?.shaKey || '') +
|
|
799
|
+
(effectFactory?.shaKey || '') || `${name}_poly`,
|
|
596
800
|
processorOptions = {}
|
|
597
|
-
): Promise<
|
|
598
|
-
|
|
801
|
+
): Promise<
|
|
802
|
+
SP extends true
|
|
803
|
+
? FaustPolyScriptProcessorNode | null
|
|
804
|
+
: FaustPolyAudioWorkletNode | null
|
|
805
|
+
> {
|
|
806
|
+
if (!voiceFactory)
|
|
807
|
+
throw new Error(
|
|
808
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
809
|
+
);
|
|
599
810
|
|
|
600
811
|
const voiceMeta = JSON.parse(voiceFactory.json);
|
|
601
|
-
const effectMeta = effectFactory
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
812
|
+
const effectMeta = effectFactory
|
|
813
|
+
? JSON.parse(effectFactory.json)
|
|
814
|
+
: undefined;
|
|
815
|
+
const sampleSize = voiceMeta.compile_options.match('-double') ? 8 : 4;
|
|
816
|
+
voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
817
|
+
voiceMeta,
|
|
818
|
+
voiceFactory.soundfiles || {},
|
|
819
|
+
context
|
|
820
|
+
);
|
|
821
|
+
if (effectFactory)
|
|
822
|
+
effectFactory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
823
|
+
effectMeta,
|
|
824
|
+
effectFactory.soundfiles || {},
|
|
825
|
+
context
|
|
826
|
+
);
|
|
605
827
|
if (sp) {
|
|
606
|
-
const instance =
|
|
607
|
-
|
|
608
|
-
|
|
828
|
+
const instance =
|
|
829
|
+
await FaustWasmInstantiator.createAsyncPolyDSPInstance(
|
|
830
|
+
voiceFactory,
|
|
831
|
+
mixerModule,
|
|
832
|
+
voices,
|
|
833
|
+
effectFactory || undefined
|
|
834
|
+
);
|
|
835
|
+
const soundfiles = {
|
|
836
|
+
...effectFactory?.soundfiles,
|
|
837
|
+
...voiceFactory.soundfiles
|
|
838
|
+
};
|
|
839
|
+
const polyDsp = new FaustPolyWebAudioDsp(
|
|
840
|
+
instance,
|
|
841
|
+
context.sampleRate,
|
|
842
|
+
sampleSize,
|
|
843
|
+
bufferSize,
|
|
844
|
+
soundfiles
|
|
845
|
+
);
|
|
609
846
|
|
|
610
|
-
const sp = context.createScriptProcessor(
|
|
847
|
+
const sp = context.createScriptProcessor(
|
|
848
|
+
bufferSize,
|
|
849
|
+
polyDsp.getNumInputs(),
|
|
850
|
+
polyDsp.getNumOutputs()
|
|
851
|
+
) as FaustPolyScriptProcessorNode;
|
|
611
852
|
Object.setPrototypeOf(sp, FaustPolyScriptProcessorNode.prototype);
|
|
612
853
|
sp.init(polyDsp);
|
|
613
|
-
return sp as SP extends true
|
|
854
|
+
return sp as SP extends true
|
|
855
|
+
? FaustPolyScriptProcessorNode
|
|
856
|
+
: FaustPolyAudioWorkletNode;
|
|
614
857
|
} else {
|
|
615
858
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
616
|
-
if (!FaustPolyDspGenerator.gWorkletProcessors.has(context))
|
|
617
|
-
|
|
859
|
+
if (!FaustPolyDspGenerator.gWorkletProcessors.has(context))
|
|
860
|
+
FaustPolyDspGenerator.gWorkletProcessors.set(
|
|
861
|
+
context,
|
|
862
|
+
new Set()
|
|
863
|
+
);
|
|
864
|
+
if (
|
|
865
|
+
!FaustPolyDspGenerator.gWorkletProcessors
|
|
866
|
+
.get(context)
|
|
867
|
+
?.has(processorName)
|
|
868
|
+
) {
|
|
618
869
|
try {
|
|
619
870
|
const processorCode = `
|
|
620
871
|
// DSP name and JSON string for DSP are generated
|
|
@@ -656,10 +907,14 @@ const dependencies = {
|
|
|
656
907
|
// Generate the actual AudioWorkletProcessor code
|
|
657
908
|
(${getFaustAudioWorkletProcessor.toString()})(dependencies, faustData);
|
|
658
909
|
`;
|
|
659
|
-
const url = URL.createObjectURL(
|
|
910
|
+
const url = URL.createObjectURL(
|
|
911
|
+
new Blob([processorCode], { type: 'text/javascript' })
|
|
912
|
+
);
|
|
660
913
|
await context.audioWorklet.addModule(url);
|
|
661
914
|
// Keep the DSP name
|
|
662
|
-
FaustPolyDspGenerator.gWorkletProcessors
|
|
915
|
+
FaustPolyDspGenerator.gWorkletProcessors
|
|
916
|
+
.get(context)
|
|
917
|
+
?.add(processorName);
|
|
663
918
|
} catch (e) {
|
|
664
919
|
// console.error(`=> exception raised while running createPolyNode: ${e}`);
|
|
665
920
|
// console.error(`=> check that your page is served using https.${e}`);
|
|
@@ -667,9 +922,21 @@ const dependencies = {
|
|
|
667
922
|
}
|
|
668
923
|
}
|
|
669
924
|
// Create the AWN
|
|
670
|
-
const node = new FaustPolyAudioWorkletNode(context, {
|
|
925
|
+
const node = new FaustPolyAudioWorkletNode(context, {
|
|
926
|
+
processorOptions: {
|
|
927
|
+
name: processorName,
|
|
928
|
+
voiceFactory,
|
|
929
|
+
mixerModule,
|
|
930
|
+
voices,
|
|
931
|
+
sampleSize,
|
|
932
|
+
effectFactory: effectFactory || undefined,
|
|
933
|
+
...processorOptions
|
|
934
|
+
}
|
|
935
|
+
});
|
|
671
936
|
|
|
672
|
-
return node as SP extends true
|
|
937
|
+
return node as SP extends true
|
|
938
|
+
? FaustPolyScriptProcessorNode
|
|
939
|
+
: FaustPolyAudioWorkletNode;
|
|
673
940
|
}
|
|
674
941
|
}
|
|
675
942
|
|
|
@@ -677,13 +944,19 @@ const dependencies = {
|
|
|
677
944
|
name = this.name,
|
|
678
945
|
voiceFactory = this.voiceFactory as LooseFaustDspFactory,
|
|
679
946
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
680
|
-
processorName = (
|
|
947
|
+
processorName = (voiceFactory?.shaKey || '') +
|
|
948
|
+
(effectFactory?.shaKey || '') || `${name}_poly`
|
|
681
949
|
) {
|
|
682
|
-
if (!voiceFactory)
|
|
950
|
+
if (!voiceFactory)
|
|
951
|
+
throw new Error(
|
|
952
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
953
|
+
);
|
|
683
954
|
|
|
684
955
|
const voiceMeta = JSON.parse(voiceFactory.json);
|
|
685
|
-
const effectMeta = effectFactory
|
|
686
|
-
|
|
956
|
+
const effectMeta = effectFactory
|
|
957
|
+
? JSON.parse(effectFactory.json)
|
|
958
|
+
: undefined;
|
|
959
|
+
const sampleSize = voiceMeta.compile_options.match('-double') ? 8 : 4;
|
|
687
960
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
688
961
|
try {
|
|
689
962
|
const dependencies = {
|
|
@@ -703,7 +976,10 @@ const dependencies = {
|
|
|
703
976
|
effectMeta
|
|
704
977
|
} as FaustData;
|
|
705
978
|
// Generate the actual AudioWorkletProcessor code
|
|
706
|
-
const Processor = getFaustAudioWorkletProcessor<true>(
|
|
979
|
+
const Processor = getFaustAudioWorkletProcessor<true>(
|
|
980
|
+
dependencies,
|
|
981
|
+
faustData
|
|
982
|
+
);
|
|
707
983
|
return Processor;
|
|
708
984
|
} catch (e) {
|
|
709
985
|
// console.error(`=> exception raised while running createPolyNode: ${e}`);
|
|
@@ -721,38 +997,74 @@ const dependencies = {
|
|
|
721
997
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
722
998
|
context?: BaseAudioContext
|
|
723
999
|
) {
|
|
724
|
-
if (!voiceFactory)
|
|
1000
|
+
if (!voiceFactory)
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
'Code is not compiled, please define the factory or call `await this.compile()` first.'
|
|
1003
|
+
);
|
|
725
1004
|
|
|
726
1005
|
const voiceMeta = JSON.parse(voiceFactory.json);
|
|
727
|
-
const effectMeta = effectFactory
|
|
728
|
-
|
|
729
|
-
|
|
1006
|
+
const effectMeta = effectFactory
|
|
1007
|
+
? JSON.parse(effectFactory.json)
|
|
1008
|
+
: undefined;
|
|
1009
|
+
const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(
|
|
1010
|
+
voiceFactory,
|
|
1011
|
+
mixerModule,
|
|
1012
|
+
voices,
|
|
1013
|
+
effectFactory || undefined
|
|
1014
|
+
);
|
|
1015
|
+
const sampleSize = voiceMeta.compile_options.match('-double') ? 8 : 4;
|
|
730
1016
|
if (context) {
|
|
731
|
-
voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
732
|
-
|
|
1017
|
+
voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
1018
|
+
voiceMeta,
|
|
1019
|
+
voiceFactory.soundfiles || {},
|
|
1020
|
+
context
|
|
1021
|
+
);
|
|
1022
|
+
if (effectFactory)
|
|
1023
|
+
effectFactory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
1024
|
+
effectMeta,
|
|
1025
|
+
effectFactory.soundfiles || {},
|
|
1026
|
+
context
|
|
1027
|
+
);
|
|
733
1028
|
}
|
|
734
|
-
const soundfiles = {
|
|
735
|
-
|
|
1029
|
+
const soundfiles = {
|
|
1030
|
+
...effectFactory?.soundfiles,
|
|
1031
|
+
...voiceFactory.soundfiles
|
|
1032
|
+
};
|
|
1033
|
+
const polyDsp = new FaustPolyWebAudioDsp(
|
|
1034
|
+
instance,
|
|
1035
|
+
sampleRate,
|
|
1036
|
+
sampleSize,
|
|
1037
|
+
bufferSize,
|
|
1038
|
+
soundfiles
|
|
1039
|
+
);
|
|
736
1040
|
return new FaustPolyOfflineProcessor(polyDsp, bufferSize);
|
|
737
1041
|
}
|
|
738
1042
|
|
|
739
1043
|
getMeta() {
|
|
740
|
-
const o =
|
|
741
|
-
const e =
|
|
1044
|
+
const o = this.voiceFactory ? JSON.parse(this.voiceFactory.json) : null;
|
|
1045
|
+
const e = this.effectFactory
|
|
1046
|
+
? JSON.parse(this.effectFactory.json)
|
|
1047
|
+
: null;
|
|
742
1048
|
const r = { ...o };
|
|
743
1049
|
if (e) {
|
|
744
|
-
r.ui = [
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1050
|
+
r.ui = [
|
|
1051
|
+
{
|
|
1052
|
+
type: 'tgroup',
|
|
1053
|
+
label: 'Sequencer',
|
|
1054
|
+
items: [
|
|
1055
|
+
{ type: 'vgroup', label: 'Instrument', items: o.ui },
|
|
1056
|
+
{ type: 'vgroup', label: 'Effect', items: e.ui }
|
|
1057
|
+
]
|
|
1058
|
+
}
|
|
1059
|
+
];
|
|
750
1060
|
} else {
|
|
751
|
-
r.ui = [
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1061
|
+
r.ui = [
|
|
1062
|
+
{
|
|
1063
|
+
type: 'tgroup',
|
|
1064
|
+
label: 'Polyphonic',
|
|
1065
|
+
items: [{ type: 'vgroup', label: 'Voices', items: o.ui }]
|
|
1066
|
+
}
|
|
1067
|
+
];
|
|
756
1068
|
}
|
|
757
1069
|
return r as FaustDspMeta;
|
|
758
1070
|
}
|
|
@@ -765,3 +1077,136 @@ const dependencies = {
|
|
|
765
1077
|
return this.getMeta().ui;
|
|
766
1078
|
}
|
|
767
1079
|
}
|
|
1080
|
+
|
|
1081
|
+
interface JSONData {
|
|
1082
|
+
meta: Array<{ options?: string }>;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export class FaustDspGenerator implements IFaustDspGenerator {
|
|
1086
|
+
|
|
1087
|
+
private static compilerPromise: Promise<FaustCompiler> | null = null;
|
|
1088
|
+
|
|
1089
|
+
// Analyze the metadata of a Faust JSON file and extract the [midi:on] and [nvoices:n] options
|
|
1090
|
+
private extractMidiAndNvoices(
|
|
1091
|
+
jsonData: JSONData
|
|
1092
|
+
): { midi: boolean; nvoices: number } {
|
|
1093
|
+
const optionsMetadata = jsonData.meta.find((meta) => meta.options);
|
|
1094
|
+
if (optionsMetadata && optionsMetadata.options) {
|
|
1095
|
+
const options = optionsMetadata.options;
|
|
1096
|
+
|
|
1097
|
+
const midiRegex = /\[midi:(on|off)\]/;
|
|
1098
|
+
const nvoicesRegex = /\[nvoices:(\d+)\]/;
|
|
1099
|
+
|
|
1100
|
+
const midiMatch = options.match(midiRegex);
|
|
1101
|
+
const nvoicesMatch = options.match(nvoicesRegex);
|
|
1102
|
+
|
|
1103
|
+
const midi = midiMatch ? midiMatch[1] === 'on' : false;
|
|
1104
|
+
const nvoices = nvoicesMatch ? parseInt(nvoicesMatch[1], 10) : -1;
|
|
1105
|
+
|
|
1106
|
+
return { midi, nvoices };
|
|
1107
|
+
} else {
|
|
1108
|
+
return { midi: false, nvoices: -1 };
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Compile DSP code, inspect metadata for [nvoices:] (and optionally [midi:on]), and build either a mono
|
|
1114
|
+
* or poly WebAudio node (ScriptProcessor or AudioWorklet depending on `sp`). Compilation uses a shared,
|
|
1115
|
+
* lazily-created libfaust instance to avoid repeatedly instantiating the WASM compiler.
|
|
1116
|
+
*/
|
|
1117
|
+
async createFaustNode(
|
|
1118
|
+
context: BaseAudioContext,
|
|
1119
|
+
name: string,
|
|
1120
|
+
code: string,
|
|
1121
|
+
sp?: boolean,
|
|
1122
|
+
bufferSize?: number,
|
|
1123
|
+
): Promise<IFaustMonoWebAudioNode | IFaustPolyWebAudioNode | null> {
|
|
1124
|
+
const getCompiler = async () => {
|
|
1125
|
+
if (!FaustDspGenerator.compilerPromise) {
|
|
1126
|
+
// Resolve libfaust-wasm assets relative to current script/page location (works in browser and bundlers).
|
|
1127
|
+
// Falling back to document/baseURI keeps things working when import.meta.url is unavailable (es2019 target).
|
|
1128
|
+
const baseURL =
|
|
1129
|
+
(typeof document !== 'undefined'
|
|
1130
|
+
? ('src' in (document.currentScript || {})
|
|
1131
|
+
? (document.currentScript as HTMLScriptElement)
|
|
1132
|
+
.src
|
|
1133
|
+
: document.baseURI)
|
|
1134
|
+
: undefined) ||
|
|
1135
|
+
(typeof window !== 'undefined'
|
|
1136
|
+
? window.location.href
|
|
1137
|
+
: undefined);
|
|
1138
|
+
if (!baseURL)
|
|
1139
|
+
throw new Error('Cannot resolve libfaust-wasm location.');
|
|
1140
|
+
const jsURL = new URL(
|
|
1141
|
+
'../libfaust-wasm/libfaust-wasm.js',
|
|
1142
|
+
baseURL
|
|
1143
|
+
).href;
|
|
1144
|
+
const dataURL = jsURL.replace(/c?js$/, 'data');
|
|
1145
|
+
const wasmURL = jsURL.replace(/c?js$/, 'wasm');
|
|
1146
|
+
FaustDspGenerator.compilerPromise =
|
|
1147
|
+
instantiateFaustModuleFromFile(
|
|
1148
|
+
jsURL,
|
|
1149
|
+
dataURL,
|
|
1150
|
+
wasmURL
|
|
1151
|
+
).then((module) => new FaustCompiler(new LibFaust(module)));
|
|
1152
|
+
}
|
|
1153
|
+
return FaustDspGenerator.compilerPromise;
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
const args = '-ftz 2';
|
|
1157
|
+
|
|
1158
|
+
try {
|
|
1159
|
+
const compiler = await getCompiler();
|
|
1160
|
+
// First compile as mono to inspect metadata for nvoices/midi tags; if nvoices > 0 we recompile as poly.
|
|
1161
|
+
const monoGenerator = new FaustMonoDspGenerator();
|
|
1162
|
+
const compiledMono = await monoGenerator.compile(
|
|
1163
|
+
compiler,
|
|
1164
|
+
name,
|
|
1165
|
+
code,
|
|
1166
|
+
args
|
|
1167
|
+
);
|
|
1168
|
+
if (!compiledMono) return null;
|
|
1169
|
+
|
|
1170
|
+
// Extract nvoices from metadata
|
|
1171
|
+
const { nvoices } = this.extractMidiAndNvoices(
|
|
1172
|
+
monoGenerator.getMeta() as unknown as JSONData
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
// If nvoices > 0, recompile as polyphonic
|
|
1176
|
+
if (nvoices > 0) {
|
|
1177
|
+
const polyGenerator = new FaustPolyDspGenerator();
|
|
1178
|
+
const compiledPoly = await polyGenerator.compile(
|
|
1179
|
+
compiler,
|
|
1180
|
+
name,
|
|
1181
|
+
code,
|
|
1182
|
+
args
|
|
1183
|
+
);
|
|
1184
|
+
if (!compiledPoly) return null;
|
|
1185
|
+
|
|
1186
|
+
// Poly creation: combine voice/effect factories, create node with requested SP/AWN and buffer size.
|
|
1187
|
+
return await polyGenerator.createNode(
|
|
1188
|
+
context,
|
|
1189
|
+
nvoices,
|
|
1190
|
+
name,
|
|
1191
|
+
undefined,
|
|
1192
|
+
undefined,
|
|
1193
|
+
undefined,
|
|
1194
|
+
sp,
|
|
1195
|
+
bufferSize
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Mono creation path when no polyphony hint is present.
|
|
1200
|
+
return await monoGenerator.createNode(
|
|
1201
|
+
context,
|
|
1202
|
+
name,
|
|
1203
|
+
undefined,
|
|
1204
|
+
sp,
|
|
1205
|
+
bufferSize
|
|
1206
|
+
);
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
console.error(e);
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|