@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.
Files changed (64) hide show
  1. package/.prettierrc +5 -0
  2. package/README.md +21 -3
  3. package/assets/standalone/faustwasm/index.d.ts +39 -17
  4. package/assets/standalone/faustwasm/index.js +1580 -408
  5. package/assets/standalone/faustwasm/index.js.map +2 -2
  6. package/dist/cjs/index.d.ts +39 -17
  7. package/dist/cjs/index.js +1579 -408
  8. package/dist/cjs/index.js.map +2 -2
  9. package/dist/cjs-bundle/index.d.ts +39 -17
  10. package/dist/cjs-bundle/index.js +1582 -410
  11. package/dist/cjs-bundle/index.js.map +2 -2
  12. package/dist/esm/index.d.ts +39 -17
  13. package/dist/esm/index.js +1580 -408
  14. package/dist/esm/index.js.map +2 -2
  15. package/dist/esm-bundle/index.d.ts +39 -17
  16. package/dist/esm-bundle/index.js +1582 -410
  17. package/dist/esm-bundle/index.js.map +2 -2
  18. package/eslint.config.js +11 -0
  19. package/package.json +57 -51
  20. package/sound.cpp +163 -0
  21. package/sound.dsp +4 -0
  22. package/sound.json +1 -0
  23. package/sound.wasm +0 -0
  24. package/src/FaustAudioWorkletCommunicator.ts +150 -143
  25. package/src/FaustAudioWorkletNode.ts +173 -75
  26. package/src/FaustAudioWorkletProcessor.ts +218 -90
  27. package/src/FaustCmajor.ts +9 -3
  28. package/src/FaustCompiler.ts +112 -35
  29. package/src/FaustDspGenerator.ts +563 -118
  30. package/src/FaustDspInstance.ts +58 -24
  31. package/src/FaustFFTAudioWorkletProcessor.ts +822 -612
  32. package/src/FaustOfflineProcessor.ts +187 -56
  33. package/src/FaustScriptProcessorNode.ts +156 -49
  34. package/src/FaustSensors.ts +426 -96
  35. package/src/FaustSvgDiagrams.ts +18 -5
  36. package/src/FaustWasmInstantiator.ts +224 -70
  37. package/src/FaustWebAudioDsp.ts +1062 -351
  38. package/src/LibFaust.ts +13 -4
  39. package/src/SoundfileReader.ts +175 -131
  40. package/src/WavDecoder.ts +42 -20
  41. package/src/WavEncoder.ts +35 -21
  42. package/src/copyWebStandaloneAssets.d.ts +20 -5
  43. package/src/copyWebStandaloneAssets.js +203 -75
  44. package/src/exports-bundle.ts +5 -5
  45. package/src/exports.ts +24 -24
  46. package/src/faust2CmajorFiles.d.ts +7 -3
  47. package/src/faust2cmajorFiles.js +10 -8
  48. package/src/faust2svgFiles.d.ts +7 -3
  49. package/src/faust2svgFiles.js +9 -7
  50. package/src/faust2wasmFiles.d.ts +10 -5
  51. package/src/faust2wasmFiles.js +34 -17
  52. package/src/faust2wavFiles.d.ts +12 -3
  53. package/src/faust2wavFiles.js +25 -12
  54. package/src/index-bundle-iife.ts +2 -2
  55. package/src/index-bundle.ts +1 -1
  56. package/src/index.ts +1 -1
  57. package/src/instantiateFaustModule.ts +43 -36
  58. package/src/instantiateFaustModuleFromFile.ts +30 -14
  59. package/src/types.ts +102 -26
  60. package/test/faustlive-wasm/faustwasm/index.d.ts +39 -17
  61. package/test/faustlive-wasm/faustwasm/index.js +1580 -408
  62. package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
  63. package/test/web/create-faust-node.html +53 -0
  64. package/test/web/create-faust-node.js +186 -0
@@ -1,22 +1,58 @@
1
- import { FaustMonoAudioWorkletNode, FaustPolyAudioWorkletNode } from "./FaustAudioWorkletNode";
2
- import getFaustAudioWorkletProcessor, { FaustData } from "./FaustAudioWorkletProcessor";
3
- import getFaustFFTAudioWorkletProcessor, { FaustFFTData, FaustFFTOptionsData } from "./FaustFFTAudioWorkletProcessor";
4
- import { FaustDspInstance } from "./FaustDspInstance";
5
- import FaustWasmInstantiator from "./FaustWasmInstantiator";
6
- import { FaustMonoOfflineProcessor, FaustPolyOfflineProcessor, IFaustMonoOfflineProcessor, IFaustPolyOfflineProcessor } from "./FaustOfflineProcessor";
7
- import { FaustMonoScriptProcessorNode, FaustPolyScriptProcessorNode } from "./FaustScriptProcessorNode";
8
- import { FaustBaseWebAudioDsp, FaustMonoWebAudioDsp, FaustPolyWebAudioDsp, FaustWebAudioDspVoice, IFaustMonoWebAudioNode, IFaustPolyWebAudioNode, Soundfile, WasmAllocator } from "./FaustWebAudioDsp";
9
- import SoundfileReader from "./SoundfileReader";
10
- import FaustSensors from "./FaustSensors";
11
- import type { IFaustCompiler } from "./FaustCompiler";
12
- import type { FaustDspFactory, FaustUIDescriptor, FaustDspMeta, FFTUtils, LooseFaustDspFactory, AudioData } from "./types";
13
- import { FaustAudioWorkletCommunicator, FaustAudioWorkletProcessorCommunicator } from "./FaustAudioWorkletCommunicator";
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(compiler: IFaustCompiler, name: string, code: string, args: string): Promise<{
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(compiler: IFaustCompiler, name: string, dspCode: string, args: string, effectCode?: string): Promise<{
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>> = new Map();
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(compiler: IFaustCompiler, name: string, code: string, args: string) {
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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<SP extends true ? FaustMonoScriptProcessorNode | null : FaustMonoAudioWorkletNode | null> {
256
- if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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("-double") ? 8 : 4;
260
- factory.soundfiles = await SoundfileReader.loadSoundfiles(meta, factory.soundfiles || {}, context);
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 = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
263
- const monoDsp = new FaustMonoWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize, factory.soundfiles);
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(bufferSize, monoDsp.getNumInputs(), monoDsp.getNumOutputs()) as FaustMonoScriptProcessorNode;
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 ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
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)) FaustMonoDspGenerator.gWorkletProcessors.set(context, new Set());
272
- if (!FaustMonoDspGenerator.gWorkletProcessors.get(context)?.has(processorName)) {
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(new Blob([processorCode], { type: "text/javascript" }));
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.get(context)?.add(processorName);
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, { processorOptions: { name: processorName, factory, sampleSize, ...processorOptions } });
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 ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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("-double") ? 8 : 4;
341
- factory.soundfiles = await SoundfileReader.loadSoundfiles(meta, factory.soundfiles || {}, context);
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)) FaustMonoDspGenerator.gWorkletProcessors.set(context, new Set());
344
- if (!FaustMonoDspGenerator.gWorkletProcessors.get(context)?.has(processorName)) {
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(new Blob([processorCode], { type: "text/javascript" }));
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.get(context)?.add(processorName);
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, { channelCount: Math.max(1, Math.ceil(meta.inputs / 3)), outputChannelCount: [Math.ceil(meta.outputs / 2)], processorOptions: { name: processorName, factory, sampleSize, ...processorOptions } });
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("fftSize");
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("fftOverlap");
553
+ const param = node.parameters.get('fftOverlap');
403
554
  if (param) param.value = fftOptions.fftOverlap;
404
555
  }
405
- if (typeof fftOptions.defaultWindowFunction === "number") {
406
- const param = node.parameters.get("windowFunction");
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 === "boolean") {
410
- const param = node.parameters.get("noIFFT");
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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(dependencies, faustData);
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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 = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
462
- const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
463
- if (context) factory.soundfiles = await SoundfileReader.loadSoundfiles(meta, factory.soundfiles || {}, context);
464
- const monoDsp = new FaustMonoWebAudioDsp(instance, sampleRate, sampleSize, bufferSize, factory.soundfiles);
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() { return JSON.parse(this.factory!.json); }
469
- getJSON() { return JSON.stringify(this.getMeta()); }
470
- getUI() { return this.getMeta().ui; }
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>> = new Map();
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(name, effectCodeAux, args);
501
- // Since the effect is processing the same buffers for inputs and outputs (in place processing),
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(name, dspCode, args);
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(name, effectCode, args + " -inpl");
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(name, dspCodeAux, args);
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("-double");
561
- const { mixerBuffer, mixerModule } = await compiler.getAsyncInternalMixerModule(!!isDouble);
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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 = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`,
798
+ processorName = (voiceFactory?.shaKey || '') +
799
+ (effectFactory?.shaKey || '') || `${name}_poly`,
596
800
  processorOptions = {}
597
- ): Promise<SP extends true ? FaustPolyScriptProcessorNode | null : FaustPolyAudioWorkletNode | null> {
598
- if (!voiceFactory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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 ? JSON.parse(effectFactory.json) : undefined;
602
- const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
603
- voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(voiceMeta, voiceFactory.soundfiles || {}, context);
604
- if (effectFactory) effectFactory.soundfiles = await SoundfileReader.loadSoundfiles(effectMeta, effectFactory.soundfiles || {}, context);
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 = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
607
- const soundfiles = { ...effectFactory?.soundfiles, ...voiceFactory.soundfiles };
608
- const polyDsp = new FaustPolyWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize, soundfiles);
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(bufferSize, polyDsp.getNumInputs(), polyDsp.getNumOutputs()) as FaustPolyScriptProcessorNode;
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 ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
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)) FaustPolyDspGenerator.gWorkletProcessors.set(context, new Set());
617
- if (!FaustPolyDspGenerator.gWorkletProcessors.get(context)?.has(processorName)) {
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(new Blob([processorCode], { type: "text/javascript" }));
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.get(context)?.add(processorName);
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, { processorOptions: { name: processorName, voiceFactory, mixerModule, voices, sampleSize, effectFactory: effectFactory || undefined, ...processorOptions } });
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 ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
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 = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`
947
+ processorName = (voiceFactory?.shaKey || '') +
948
+ (effectFactory?.shaKey || '') || `${name}_poly`
681
949
  ) {
682
- if (!voiceFactory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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 ? JSON.parse(effectFactory.json) : undefined;
686
- const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
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>(dependencies, faustData);
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) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
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 ? JSON.parse(effectFactory.json) : undefined;
728
- const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
729
- const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
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(voiceMeta, voiceFactory.soundfiles || {}, context);
732
- if (effectFactory) effectFactory.soundfiles = await SoundfileReader.loadSoundfiles(effectMeta, effectFactory.soundfiles || {}, context);
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 = { ...effectFactory?.soundfiles, ...voiceFactory.soundfiles };
735
- const polyDsp = new FaustPolyWebAudioDsp(instance, sampleRate, sampleSize, bufferSize, soundfiles);
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 = (this.voiceFactory) ? JSON.parse(this.voiceFactory.json) : null;
741
- const e = (this.effectFactory) ? JSON.parse(this.effectFactory.json) : null;
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
- type: "tgroup", label: "Sequencer", items: [
746
- { type: "vgroup", label: "Instrument", items: o.ui },
747
- { type: "vgroup", label: "Effect", items: e.ui }
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
- type: "tgroup", label: "Polyphonic", items: [
753
- { type: "vgroup", label: "Voices", items: o.ui }
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
+ }