@grame/faustwasm 0.12.2 → 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 +38 -16
- package/assets/standalone/faustwasm/index.js +1574 -404
- package/assets/standalone/faustwasm/index.js.map +2 -2
- package/dist/cjs/index.d.ts +38 -16
- package/dist/cjs/index.js +1573 -404
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs-bundle/index.d.ts +38 -16
- package/dist/cjs-bundle/index.js +1576 -406
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm/index.d.ts +38 -16
- package/dist/esm/index.js +1574 -404
- package/dist/esm/index.js.map +2 -2
- package/dist/esm-bundle/index.d.ts +38 -16
- package/dist/esm-bundle/index.js +1576 -406
- 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 +561 -117
- 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 +1060 -349
- 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 +101 -25
- package/test/faustlive-wasm/faustwasm/index.d.ts +38 -16
- package/test/faustlive-wasm/faustwasm/index.js +1574 -404
- 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/test/organ/create-node.js +0 -252
- package/test/organ/dsp-meta.json +0 -131
- package/test/organ/dsp-module.wasm +0 -0
- package/test/organ/faust-pwa.js +0 -344
- package/test/organ/faust-ui/index.css +0 -442
- package/test/organ/faust-ui/index.css.map +0 -1
- package/test/organ/faust-ui/index.d.ts +0 -1
- package/test/organ/faust-ui/index.js +0 -3756
- package/test/organ/faust-ui/index.js.map +0 -1
- package/test/organ/faustwasm/index.d.ts +0 -1705
- package/test/organ/faustwasm/index.js +0 -4773
- package/test/organ/faustwasm/index.js.map +0 -7
- package/test/organ/icon.png +0 -0
- package/test/organ/index.html +0 -76
- package/test/organ/index.js +0 -69
- package/test/organ/manifest.json +0 -17
- package/test/organ/service-worker.js +0 -165
package/src/FaustDspGenerator.ts
CHANGED
|
@@ -1,21 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
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';
|
|
14
51
|
|
|
15
52
|
export interface GeneratorSupportingSoundfiles {
|
|
16
53
|
/**
|
|
17
54
|
* Attach a map of id - audio data, call after `compile()` before `createNode()`
|
|
18
|
-
*
|
|
55
|
+
*
|
|
19
56
|
* @param soundfileMap a map of id - `AudioData` as an object where `AudioData` contains channel data as `audioBuffer: Float32Array[]` and `sampleRate: number`
|
|
20
57
|
*/
|
|
21
58
|
addSoundfiles(soundfileMap: Record<string, AudioData>): void;
|
|
@@ -26,17 +63,43 @@ export interface GeneratorSupportingSoundfiles {
|
|
|
26
63
|
getSoundfileList(): string[];
|
|
27
64
|
}
|
|
28
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
|
+
|
|
29
87
|
export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
30
88
|
/**
|
|
31
89
|
* Compile a monophonic DSP factory from given code.
|
|
32
|
-
*
|
|
90
|
+
*
|
|
33
91
|
* @param compiler - the Faust compiler
|
|
34
92
|
* @param name - the DSP name
|
|
35
93
|
* @param code - the DSP code
|
|
36
94
|
* @param args - the compilation parameters
|
|
37
95
|
* @returns the compiled factory or 'null' if failure
|
|
38
96
|
*/
|
|
39
|
-
compile(
|
|
97
|
+
compile(
|
|
98
|
+
compiler: IFaustCompiler,
|
|
99
|
+
name: string,
|
|
100
|
+
code: string,
|
|
101
|
+
args: string
|
|
102
|
+
): Promise<{
|
|
40
103
|
factory: FaustDspFactory | null;
|
|
41
104
|
name?: string;
|
|
42
105
|
meta?: FaustDspMeta;
|
|
@@ -127,7 +190,7 @@ export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
127
190
|
export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
128
191
|
/**
|
|
129
192
|
* Compile a monophonic DSP factory from given code.
|
|
130
|
-
*
|
|
193
|
+
*
|
|
131
194
|
* @param compiler - the Faust compiler
|
|
132
195
|
* @param name - the DSP name
|
|
133
196
|
* @param dspCode - the DSP code ('dspCode' can possibly contain an integrated effect)
|
|
@@ -135,7 +198,13 @@ export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
135
198
|
* @param effectCode - optional effect DSP code
|
|
136
199
|
* @returns the compiled factory or 'null' if failure
|
|
137
200
|
*/
|
|
138
|
-
compile(
|
|
201
|
+
compile(
|
|
202
|
+
compiler: IFaustCompiler,
|
|
203
|
+
name: string,
|
|
204
|
+
dspCode: string,
|
|
205
|
+
args: string,
|
|
206
|
+
effectCode?: string
|
|
207
|
+
): Promise<{
|
|
139
208
|
voiceFactory: FaustDspFactory | null;
|
|
140
209
|
effectFactory?: FaustDspFactory | null;
|
|
141
210
|
} | null>;
|
|
@@ -212,7 +281,8 @@ export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
212
281
|
|
|
213
282
|
export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
214
283
|
// Set of all created WorkletProcessors, each of them has to be unique
|
|
215
|
-
private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> =
|
|
284
|
+
private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> =
|
|
285
|
+
new Map();
|
|
216
286
|
|
|
217
287
|
name: string;
|
|
218
288
|
factory!: FaustDspFactory | null;
|
|
@@ -220,7 +290,12 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
|
220
290
|
constructor() {
|
|
221
291
|
this.factory = null;
|
|
222
292
|
}
|
|
223
|
-
async compile(
|
|
293
|
+
async compile(
|
|
294
|
+
compiler: IFaustCompiler,
|
|
295
|
+
name: string,
|
|
296
|
+
code: string,
|
|
297
|
+
args: string
|
|
298
|
+
) {
|
|
224
299
|
this.factory = await compiler.createMonoDSPFactory(name, code, args);
|
|
225
300
|
if (this.factory) {
|
|
226
301
|
this.name = name;
|
|
@@ -231,13 +306,19 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
|
231
306
|
}
|
|
232
307
|
|
|
233
308
|
addSoundfiles(soundfileMap: Record<string, AudioData>) {
|
|
234
|
-
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
|
+
);
|
|
235
313
|
for (const id in soundfileMap) {
|
|
236
314
|
this.factory.soundfiles[id] = soundfileMap[id];
|
|
237
315
|
}
|
|
238
316
|
}
|
|
239
317
|
getSoundfileList() {
|
|
240
|
-
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
|
+
);
|
|
241
322
|
const meta = JSON.parse(this.factory.json);
|
|
242
323
|
const map = SoundfileReader.findSoundfilesFromMeta(meta);
|
|
243
324
|
if (!map) return [];
|
|
@@ -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,13 +764,19 @@ 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);
|
|
580
782
|
if (!map) return [];
|
|
@@ -593,29 +795,77 @@ process = adaptorIns(dsp_code.process) : dsp_code.effect : adaptorOuts;
|
|
|
593
795
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
594
796
|
sp = false as SP,
|
|
595
797
|
bufferSize = 1024,
|
|
596
|
-
processorName = (
|
|
798
|
+
processorName = (voiceFactory?.shaKey || '') +
|
|
799
|
+
(effectFactory?.shaKey || '') || `${name}_poly`,
|
|
597
800
|
processorOptions = {}
|
|
598
|
-
): Promise<
|
|
599
|
-
|
|
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
|
+
);
|
|
600
810
|
|
|
601
811
|
const voiceMeta = JSON.parse(voiceFactory.json);
|
|
602
|
-
const effectMeta = effectFactory
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
+
);
|
|
606
827
|
if (sp) {
|
|
607
|
-
const instance =
|
|
608
|
-
|
|
609
|
-
|
|
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
|
+
);
|
|
610
846
|
|
|
611
|
-
const sp = context.createScriptProcessor(
|
|
847
|
+
const sp = context.createScriptProcessor(
|
|
848
|
+
bufferSize,
|
|
849
|
+
polyDsp.getNumInputs(),
|
|
850
|
+
polyDsp.getNumOutputs()
|
|
851
|
+
) as FaustPolyScriptProcessorNode;
|
|
612
852
|
Object.setPrototypeOf(sp, FaustPolyScriptProcessorNode.prototype);
|
|
613
853
|
sp.init(polyDsp);
|
|
614
|
-
return sp as SP extends true
|
|
854
|
+
return sp as SP extends true
|
|
855
|
+
? FaustPolyScriptProcessorNode
|
|
856
|
+
: FaustPolyAudioWorkletNode;
|
|
615
857
|
} else {
|
|
616
858
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
617
|
-
if (!FaustPolyDspGenerator.gWorkletProcessors.has(context))
|
|
618
|
-
|
|
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
|
+
) {
|
|
619
869
|
try {
|
|
620
870
|
const processorCode = `
|
|
621
871
|
// DSP name and JSON string for DSP are generated
|
|
@@ -657,10 +907,14 @@ const dependencies = {
|
|
|
657
907
|
// Generate the actual AudioWorkletProcessor code
|
|
658
908
|
(${getFaustAudioWorkletProcessor.toString()})(dependencies, faustData);
|
|
659
909
|
`;
|
|
660
|
-
const url = URL.createObjectURL(
|
|
910
|
+
const url = URL.createObjectURL(
|
|
911
|
+
new Blob([processorCode], { type: 'text/javascript' })
|
|
912
|
+
);
|
|
661
913
|
await context.audioWorklet.addModule(url);
|
|
662
914
|
// Keep the DSP name
|
|
663
|
-
FaustPolyDspGenerator.gWorkletProcessors
|
|
915
|
+
FaustPolyDspGenerator.gWorkletProcessors
|
|
916
|
+
.get(context)
|
|
917
|
+
?.add(processorName);
|
|
664
918
|
} catch (e) {
|
|
665
919
|
// console.error(`=> exception raised while running createPolyNode: ${e}`);
|
|
666
920
|
// console.error(`=> check that your page is served using https.${e}`);
|
|
@@ -668,9 +922,21 @@ const dependencies = {
|
|
|
668
922
|
}
|
|
669
923
|
}
|
|
670
924
|
// Create the AWN
|
|
671
|
-
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
|
+
});
|
|
672
936
|
|
|
673
|
-
return node as SP extends true
|
|
937
|
+
return node as SP extends true
|
|
938
|
+
? FaustPolyScriptProcessorNode
|
|
939
|
+
: FaustPolyAudioWorkletNode;
|
|
674
940
|
}
|
|
675
941
|
}
|
|
676
942
|
|
|
@@ -678,13 +944,19 @@ const dependencies = {
|
|
|
678
944
|
name = this.name,
|
|
679
945
|
voiceFactory = this.voiceFactory as LooseFaustDspFactory,
|
|
680
946
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
681
|
-
processorName = (
|
|
947
|
+
processorName = (voiceFactory?.shaKey || '') +
|
|
948
|
+
(effectFactory?.shaKey || '') || `${name}_poly`
|
|
682
949
|
) {
|
|
683
|
-
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
|
+
);
|
|
684
954
|
|
|
685
955
|
const voiceMeta = JSON.parse(voiceFactory.json);
|
|
686
|
-
const effectMeta = effectFactory
|
|
687
|
-
|
|
956
|
+
const effectMeta = effectFactory
|
|
957
|
+
? JSON.parse(effectFactory.json)
|
|
958
|
+
: undefined;
|
|
959
|
+
const sampleSize = voiceMeta.compile_options.match('-double') ? 8 : 4;
|
|
688
960
|
// Dynamically create AudioWorkletProcessor if code not yet created
|
|
689
961
|
try {
|
|
690
962
|
const dependencies = {
|
|
@@ -704,7 +976,10 @@ const dependencies = {
|
|
|
704
976
|
effectMeta
|
|
705
977
|
} as FaustData;
|
|
706
978
|
// Generate the actual AudioWorkletProcessor code
|
|
707
|
-
const Processor = getFaustAudioWorkletProcessor<true>(
|
|
979
|
+
const Processor = getFaustAudioWorkletProcessor<true>(
|
|
980
|
+
dependencies,
|
|
981
|
+
faustData
|
|
982
|
+
);
|
|
708
983
|
return Processor;
|
|
709
984
|
} catch (e) {
|
|
710
985
|
// console.error(`=> exception raised while running createPolyNode: ${e}`);
|
|
@@ -722,38 +997,74 @@ const dependencies = {
|
|
|
722
997
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
723
998
|
context?: BaseAudioContext
|
|
724
999
|
) {
|
|
725
|
-
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
|
+
);
|
|
726
1004
|
|
|
727
1005
|
const voiceMeta = JSON.parse(voiceFactory.json);
|
|
728
|
-
const effectMeta = effectFactory
|
|
729
|
-
|
|
730
|
-
|
|
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;
|
|
731
1016
|
if (context) {
|
|
732
|
-
voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(
|
|
733
|
-
|
|
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
|
+
);
|
|
734
1028
|
}
|
|
735
|
-
const soundfiles = {
|
|
736
|
-
|
|
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
|
+
);
|
|
737
1040
|
return new FaustPolyOfflineProcessor(polyDsp, bufferSize);
|
|
738
1041
|
}
|
|
739
1042
|
|
|
740
1043
|
getMeta() {
|
|
741
|
-
const o =
|
|
742
|
-
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;
|
|
743
1048
|
const r = { ...o };
|
|
744
1049
|
if (e) {
|
|
745
|
-
r.ui = [
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
+
];
|
|
751
1060
|
} else {
|
|
752
|
-
r.ui = [
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1061
|
+
r.ui = [
|
|
1062
|
+
{
|
|
1063
|
+
type: 'tgroup',
|
|
1064
|
+
label: 'Polyphonic',
|
|
1065
|
+
items: [{ type: 'vgroup', label: 'Voices', items: o.ui }]
|
|
1066
|
+
}
|
|
1067
|
+
];
|
|
757
1068
|
}
|
|
758
1069
|
return r as FaustDspMeta;
|
|
759
1070
|
}
|
|
@@ -766,3 +1077,136 @@ const dependencies = {
|
|
|
766
1077
|
return this.getMeta().ui;
|
|
767
1078
|
}
|
|
768
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
|
+
}
|