@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.
Files changed (81) hide show
  1. package/.prettierrc +5 -0
  2. package/README.md +21 -3
  3. package/assets/standalone/faustwasm/index.d.ts +38 -16
  4. package/assets/standalone/faustwasm/index.js +1574 -404
  5. package/assets/standalone/faustwasm/index.js.map +2 -2
  6. package/dist/cjs/index.d.ts +38 -16
  7. package/dist/cjs/index.js +1573 -404
  8. package/dist/cjs/index.js.map +2 -2
  9. package/dist/cjs-bundle/index.d.ts +38 -16
  10. package/dist/cjs-bundle/index.js +1576 -406
  11. package/dist/cjs-bundle/index.js.map +2 -2
  12. package/dist/esm/index.d.ts +38 -16
  13. package/dist/esm/index.js +1574 -404
  14. package/dist/esm/index.js.map +2 -2
  15. package/dist/esm-bundle/index.d.ts +38 -16
  16. package/dist/esm-bundle/index.js +1576 -406
  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 +561 -117
  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 +1060 -349
  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 +101 -25
  60. package/test/faustlive-wasm/faustwasm/index.d.ts +38 -16
  61. package/test/faustlive-wasm/faustwasm/index.js +1574 -404
  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
  65. package/test/organ/create-node.js +0 -252
  66. package/test/organ/dsp-meta.json +0 -131
  67. package/test/organ/dsp-module.wasm +0 -0
  68. package/test/organ/faust-pwa.js +0 -344
  69. package/test/organ/faust-ui/index.css +0 -442
  70. package/test/organ/faust-ui/index.css.map +0 -1
  71. package/test/organ/faust-ui/index.d.ts +0 -1
  72. package/test/organ/faust-ui/index.js +0 -3756
  73. package/test/organ/faust-ui/index.js.map +0 -1
  74. package/test/organ/faustwasm/index.d.ts +0 -1705
  75. package/test/organ/faustwasm/index.js +0 -4773
  76. package/test/organ/faustwasm/index.js.map +0 -7
  77. package/test/organ/icon.png +0 -0
  78. package/test/organ/index.html +0 -76
  79. package/test/organ/index.js +0 -69
  80. package/test/organ/manifest.json +0 -17
  81. package/test/organ/service-worker.js +0 -165
@@ -1,21 +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";
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(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<{
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(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<{
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>> = new Map();
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(compiler: IFaustCompiler, name: string, code: string, args: string) {
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) 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
+ );
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) 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
+ );
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<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,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) 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);
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 = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`,
798
+ processorName = (voiceFactory?.shaKey || '') +
799
+ (effectFactory?.shaKey || '') || `${name}_poly`,
597
800
  processorOptions = {}
598
- ): Promise<SP extends true ? FaustPolyScriptProcessorNode | null : FaustPolyAudioWorkletNode | null> {
599
- 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
+ );
600
810
 
601
811
  const voiceMeta = JSON.parse(voiceFactory.json);
602
- const effectMeta = effectFactory ? JSON.parse(effectFactory.json) : undefined;
603
- const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
604
- voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(voiceMeta, voiceFactory.soundfiles || {}, context);
605
- 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
+ );
606
827
  if (sp) {
607
- const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
608
- const soundfiles = { ...effectFactory?.soundfiles, ...voiceFactory.soundfiles };
609
- 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
+ );
610
846
 
611
- 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;
612
852
  Object.setPrototypeOf(sp, FaustPolyScriptProcessorNode.prototype);
613
853
  sp.init(polyDsp);
614
- return sp as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
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)) FaustPolyDspGenerator.gWorkletProcessors.set(context, new Set());
618
- 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
+ ) {
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(new Blob([processorCode], { type: "text/javascript" }));
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.get(context)?.add(processorName);
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, { 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
+ });
672
936
 
673
- return node as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
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 = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`
947
+ processorName = (voiceFactory?.shaKey || '') +
948
+ (effectFactory?.shaKey || '') || `${name}_poly`
682
949
  ) {
683
- 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
+ );
684
954
 
685
955
  const voiceMeta = JSON.parse(voiceFactory.json);
686
- const effectMeta = effectFactory ? JSON.parse(effectFactory.json) : undefined;
687
- 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;
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>(dependencies, faustData);
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) 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
+ );
726
1004
 
727
1005
  const voiceMeta = JSON.parse(voiceFactory.json);
728
- const effectMeta = effectFactory ? JSON.parse(effectFactory.json) : undefined;
729
- const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
730
- 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;
731
1016
  if (context) {
732
- voiceFactory.soundfiles = await SoundfileReader.loadSoundfiles(voiceMeta, voiceFactory.soundfiles || {}, context);
733
- 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
+ );
734
1028
  }
735
- const soundfiles = { ...effectFactory?.soundfiles, ...voiceFactory.soundfiles };
736
- 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
+ );
737
1040
  return new FaustPolyOfflineProcessor(polyDsp, bufferSize);
738
1041
  }
739
1042
 
740
1043
  getMeta() {
741
- const o = (this.voiceFactory) ? JSON.parse(this.voiceFactory.json) : null;
742
- 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;
743
1048
  const r = { ...o };
744
1049
  if (e) {
745
- r.ui = [{
746
- type: "tgroup", label: "Sequencer", items: [
747
- { type: "vgroup", label: "Instrument", items: o.ui },
748
- { type: "vgroup", label: "Effect", items: e.ui }
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
- type: "tgroup", label: "Polyphonic", items: [
754
- { type: "vgroup", label: "Voices", items: o.ui }
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
+ }