@grame/faustwasm 0.0.25

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 (111) hide show
  1. package/README.md +181 -0
  2. package/assets/standalone/faust-ui/index.css +414 -0
  3. package/assets/standalone/faust-ui/index.css.map +1 -0
  4. package/assets/standalone/faust-ui/index.d.ts +1 -0
  5. package/assets/standalone/faust-ui/index.js +3478 -0
  6. package/assets/standalone/faust-ui/index.js.map +1 -0
  7. package/assets/standalone/faustwasm/index.d.ts +1252 -0
  8. package/assets/standalone/faustwasm/index.js +3772 -0
  9. package/assets/standalone/faustwasm/index.js.map +7 -0
  10. package/assets/standalone/index.html +73 -0
  11. package/assets/standalone/index.js +198 -0
  12. package/assets/standalone/types.d.ts +9 -0
  13. package/dist/cjs/index.d.ts +1252 -0
  14. package/dist/cjs/index.js +3752 -0
  15. package/dist/cjs/index.js.map +7 -0
  16. package/dist/cjs-bundle/index.d.ts +1259 -0
  17. package/dist/cjs-bundle/index.js +11507 -0
  18. package/dist/cjs-bundle/index.js.map +7 -0
  19. package/dist/esm/index.d.ts +1252 -0
  20. package/dist/esm/index.js +3772 -0
  21. package/dist/esm/index.js.map +7 -0
  22. package/dist/esm-bundle/index.d.ts +1259 -0
  23. package/dist/esm-bundle/index.js +11495 -0
  24. package/dist/esm-bundle/index.js.map +7 -0
  25. package/fileutils.js +40 -0
  26. package/libfaust-wasm/libfaust-wasm.d.cts +3 -0
  27. package/libfaust-wasm/libfaust-wasm.d.ts +3 -0
  28. package/libfaust-wasm/libfaust-wasm.data +0 -0
  29. package/libfaust-wasm/libfaust-wasm.data.d.ts +2 -0
  30. package/libfaust-wasm/libfaust-wasm.js +21 -0
  31. package/libfaust-wasm/libfaust-wasm.wasm +0 -0
  32. package/libfaust-wasm/libfaust-wasm.wasm.d.ts +2 -0
  33. package/library.md +91 -0
  34. package/package.json +43 -0
  35. package/postbuild-bundled.js +11 -0
  36. package/postbuild.js +63 -0
  37. package/prebuild-bundled.js +11 -0
  38. package/rsrc/overview.png +0 -0
  39. package/rsrc/overview.xlsx +0 -0
  40. package/scripts/faust2sndfile.js +43 -0
  41. package/scripts/faust2svg.js +17 -0
  42. package/scripts/faust2wasm.js +29 -0
  43. package/src/FaustAudioWorkletNode.ts +264 -0
  44. package/src/FaustAudioWorkletProcessor.ts +273 -0
  45. package/src/FaustCompiler.ts +252 -0
  46. package/src/FaustDspGenerator.ts +498 -0
  47. package/src/FaustDspInstance.ts +141 -0
  48. package/src/FaustFFTAudioWorkletProcessor.ts +554 -0
  49. package/src/FaustOfflineProcessor.ts +141 -0
  50. package/src/FaustScriptProcessorNode.ts +80 -0
  51. package/src/FaustSvgDiagrams.ts +37 -0
  52. package/src/FaustWasmInstantiator.ts +202 -0
  53. package/src/FaustWebAudioDsp.ts +1060 -0
  54. package/src/LibFaust.ts +57 -0
  55. package/src/WavDecoder.ts +207 -0
  56. package/src/WavEncoder.ts +204 -0
  57. package/src/copyWebStandaloneAssets.d.ts +3 -0
  58. package/src/copyWebStandaloneAssets.js +20 -0
  59. package/src/exports-bundle.ts +5 -0
  60. package/src/exports.ts +24 -0
  61. package/src/faust2svgFiles.d.ts +3 -0
  62. package/src/faust2svgFiles.js +43 -0
  63. package/src/faust2wasmFiles.d.ts +5 -0
  64. package/src/faust2wasmFiles.js +93 -0
  65. package/src/faust2wavFiles.d.ts +3 -0
  66. package/src/faust2wavFiles.js +58 -0
  67. package/src/index-bundle-iife.ts +7 -0
  68. package/src/index-bundle.ts +5 -0
  69. package/src/index.ts +5 -0
  70. package/src/instantiateFaustModule.ts +32 -0
  71. package/src/instantiateFaustModuleFromFile.ts +57 -0
  72. package/src/types.ts +234 -0
  73. package/test/clarinet.dsp +1 -0
  74. package/test/faustlive-wasm/faust-ui/index.css +414 -0
  75. package/test/faustlive-wasm/faust-ui/index.css.map +1 -0
  76. package/test/faustlive-wasm/faust-ui/index.d.ts +1 -0
  77. package/test/faustlive-wasm/faust-ui/index.js +3478 -0
  78. package/test/faustlive-wasm/faust-ui/index.js.map +1 -0
  79. package/test/faustlive-wasm/faustwasm/index.d.ts +1252 -0
  80. package/test/faustlive-wasm/faustwasm/index.js +3772 -0
  81. package/test/faustlive-wasm/faustwasm/index.js.map +7 -0
  82. package/test/faustlive-wasm/index.css +97 -0
  83. package/test/faustlive-wasm/index.html +130 -0
  84. package/test/faustlive-wasm/index.js +638 -0
  85. package/test/guitar.dsp +1 -0
  86. package/test/guitar1.dsp +2 -0
  87. package/test/libfaust-in-worklet/index.html +13 -0
  88. package/test/libfaust-in-worklet/index.js +40 -0
  89. package/test/mono.dsp +8 -0
  90. package/test/node/test.js +17 -0
  91. package/test/noise.dsp +3 -0
  92. package/test/organ.dsp +5 -0
  93. package/test/organ1.dsp +6 -0
  94. package/test/osc.dsp +4 -0
  95. package/test/osc2.dsp +5 -0
  96. package/test/osc3.dsp +4 -0
  97. package/test/p-dj.dsp +4 -0
  98. package/test/poly.dsp +13 -0
  99. package/test/rev.dsp +2 -0
  100. package/test/t1.dsp +1 -0
  101. package/test/web/fft.html +87 -0
  102. package/test/web/fft1.html +88 -0
  103. package/test/web/fft2.html +114 -0
  104. package/test/web/fftw.js +1907 -0
  105. package/test/web/index.html +12 -0
  106. package/test/web/index.js +235 -0
  107. package/test/web/kissfft.js +904 -0
  108. package/test/web/mono.html +46 -0
  109. package/test/web/poly-key.html +62 -0
  110. package/test/web/poly.html +59 -0
  111. package/tsconfig.json +22 -0
@@ -0,0 +1,498 @@
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 } from "./FaustWebAudioDsp";
9
+ import type { IFaustCompiler } from "./FaustCompiler";
10
+ import type { FaustDspFactory, FaustDspMeta, FFTUtils, LooseFaustDspFactory } from "./types";
11
+
12
+ export interface IFaustMonoDspGenerator {
13
+ /**
14
+ * Compile a monophonic DSP factory from given code.
15
+ *
16
+ * @param compiler - the Faust compiler
17
+ * @param name - the DSP name
18
+ * @param code - the DSP code
19
+ * @param args - the compilation parameters
20
+ * @returns the compiled factory or 'null' if failure
21
+ */
22
+ compile(compiler: IFaustCompiler, name: string, code: string, args: string): Promise<{
23
+ factory: FaustDspFactory | null;
24
+ name?: string;
25
+ meta?: FaustDspMeta;
26
+ } | null>;
27
+
28
+ /**
29
+ * Create a monophonic WebAudio node (either ScriptProcessorNode or AudioWorkletNode).
30
+ *
31
+ * @param context - the WebAudio context
32
+ * @param name - DSP name, can be used for processorName
33
+ * @param factory - default is the compiled factory
34
+ * @param sp - whether to compile a ScriptProcessorNode or an AudioWorkletNode
35
+ * @param bufferSize - the buffer size in frames to be used in ScriptProcessorNode only, since AudioWorkletNode always uses 128 frames
36
+ * @param processorName - AudioWorklet Processor name
37
+ * @returns the compiled WebAudio node or 'null' if failure
38
+ */
39
+ createNode(
40
+ context: BaseAudioContext,
41
+ name?: string,
42
+ factory?: LooseFaustDspFactory,
43
+ sp?: boolean,
44
+ bufferSize?: number,
45
+ processorName?: string
46
+ ): Promise<IFaustMonoWebAudioNode | null>;
47
+
48
+ /**
49
+ * Create a monophonic WebAudio node (either ScriptProcessorNode or AudioWorkletNode).
50
+ *
51
+ * @param context - the WebAudio context
52
+ * @param fftUtils - should be an anonymous class with static methods, without any import from outside
53
+ * @param name - DSP name, can be used for processorName
54
+ * @param factory - default is the compiled factory
55
+ * @param fftOptions - initial FFT options
56
+ * @param processorName - AudioWorklet Processor name
57
+ * @returns the compiled WebAudio node or 'null' if failure
58
+ */
59
+ createFFTNode(
60
+ context: BaseAudioContext,
61
+ fftUtils: typeof FFTUtils,
62
+ name?: string,
63
+ factory?: LooseFaustDspFactory,
64
+ fftOptions?: Partial<FaustFFTOptionsData>,
65
+ processorName?: string
66
+ ): Promise<FaustMonoAudioWorkletNode | null>;
67
+
68
+ /**
69
+ * Create a monophonic Offline processor.
70
+ *
71
+ * @param sampleRate - the sample rate in Hz
72
+ * @param bufferSize - the buffer size in frames
73
+ * @param factory - default is the compiled factory
74
+ * @returns the compiled processor or 'null' if failure
75
+ */
76
+ createOfflineProcessor(sampleRate: number, bufferSize: number, factory?: LooseFaustDspFactory, meta?: FaustDspMeta): Promise<IFaustMonoOfflineProcessor | null>;
77
+ }
78
+
79
+ export interface IFaustPolyDspGenerator {
80
+ /**
81
+ * Compile a monophonic DSP factory from given code.
82
+ *
83
+ * @param compiler - the Faust compiler
84
+ * @param name - the DSP name
85
+ * @param dspCode - the DSP code ('dsp_code' can possibly contain an integrated effect)
86
+ * @param args - the compilation parameters
87
+ * @param effectCode - optional effect DSP code
88
+ * @returns the compiled factory or 'null' if failure
89
+ */
90
+ compile(compiler: IFaustCompiler, name: string, dspCode: string, args: string, effectCode?: string): Promise<{
91
+ voiceFactory: FaustDspFactory | null;
92
+ effectFactory?: FaustDspFactory | null;
93
+ } | null>;
94
+ /**
95
+ * Create a polyphonic WebAudio node (either ScriptProcessorNode or AudioWorkletNode).
96
+ *
97
+ * @param context the WebAudio context
98
+ * @param voices - the number of voices
99
+ * @param name - AudioWorklet Processor name
100
+ * @param voiceFactory - the Faust factory for voices, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
101
+ * @param mixerModule - the wasm Mixer module (loaded from 'mixer32.wasm' or 'mixer64.wasm' files)
102
+ * @param effectFactory - the Faust factory for the effect, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
103
+ * @param sp - whether to compile a ScriptProcessorNode or an AudioWorkletNode
104
+ * @param bufferSize - the buffer size in frames to be used in ScriptProcessorNode only, since AudioWorkletNode always uses 128 frames
105
+ * @returns the compiled WebAudio node or 'null' if failure
106
+ */
107
+ createNode(
108
+ context: BaseAudioContext,
109
+ voices: number,
110
+ name?: string,
111
+ voiceFactory?: LooseFaustDspFactory,
112
+ mixerModule?: WebAssembly.Module,
113
+ effectFactory?: LooseFaustDspFactory | null,
114
+ sp?: boolean,
115
+ bufferSize?: number
116
+ ): Promise<IFaustPolyWebAudioNode | null>;
117
+
118
+ /**
119
+ * Create a monophonic Offline processor.
120
+ *
121
+ * @param sampleRate - the sample rate in Hz
122
+ * @param bufferSize - the buffer size in frames
123
+ * @param voiceFactory - the Faust factory for voices, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
124
+ * @param mixerModule - the wasm Mixer module (loaded from 'mixer32.wasm' or 'mixer64.wasm' files)
125
+ * @param effectFactory - the Faust factory for the effect, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
126
+ * @returns the compiled processor or 'null' if failure
127
+ */
128
+ createOfflineProcessor(
129
+ sampleRate: number,
130
+ bufferSize: number,
131
+ voices: number,
132
+ voiceFactory?: LooseFaustDspFactory,
133
+ mixerModule?: WebAssembly.Module,
134
+ effectFactory?: LooseFaustDspFactory | null
135
+ ): Promise<IFaustPolyOfflineProcessor | null>;
136
+ }
137
+
138
+ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
139
+ // Set of all created WorkletProcessors, each of them has to be unique
140
+ private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> = new Map();
141
+
142
+ name: string;
143
+ factory!: FaustDspFactory | null;
144
+
145
+ constructor() {
146
+ this.factory = null;
147
+ }
148
+ async compile(compiler: IFaustCompiler, name: string, code: string, args: string) {
149
+ this.factory = await compiler.createMonoDSPFactory(name, code, args);
150
+ if (!this.factory) return null;
151
+ this.name = name;
152
+ return this;
153
+ }
154
+
155
+ async createNode<SP extends boolean = false>(
156
+ context: BaseAudioContext,
157
+ name = this.name,
158
+ factory = this.factory as LooseFaustDspFactory,
159
+ sp = false as SP,
160
+ bufferSize = 1024,
161
+ processorName = factory?.shaKey || name
162
+ ): Promise<SP extends true ? FaustMonoScriptProcessorNode | null : FaustMonoAudioWorkletNode | null> {
163
+ if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
164
+
165
+ const meta = JSON.parse(factory.json);
166
+ const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
167
+ if (sp) {
168
+ const instance = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
169
+ const monoDsp = new FaustMonoWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize);
170
+ const sp = context.createScriptProcessor(bufferSize, monoDsp.getNumInputs(), monoDsp.getNumOutputs()) as FaustMonoScriptProcessorNode;
171
+ Object.setPrototypeOf(sp, FaustMonoScriptProcessorNode.prototype);
172
+ sp.init(monoDsp);
173
+ return sp as SP extends true ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
174
+ } else {
175
+ // Dynamically create AudioWorkletProcessor if code not yet created
176
+ if (!FaustMonoDspGenerator.gWorkletProcessors.has(context)) FaustMonoDspGenerator.gWorkletProcessors.set(context, new Set());
177
+ if (!FaustMonoDspGenerator.gWorkletProcessors.get(context)?.has(processorName)) {
178
+ try {
179
+ const processorCode = `
180
+ // DSP name and JSON string for DSP are generated
181
+ const faustData = ${JSON.stringify({
182
+ processorName,
183
+ dspName: name,
184
+ dspMeta: meta,
185
+ poly: false
186
+ } as FaustData)};
187
+ // Implementation needed classes of functions
188
+ const ${FaustDspInstance.name}_default = ${FaustDspInstance.toString()}
189
+ const ${FaustBaseWebAudioDsp.name} = ${FaustBaseWebAudioDsp.toString()}
190
+ const ${FaustMonoWebAudioDsp.name} = ${FaustMonoWebAudioDsp.toString()}
191
+ const ${FaustWasmInstantiator.name} = ${FaustWasmInstantiator.toString()}
192
+ // Put them in dependencies
193
+ const dependencies = {
194
+ ${FaustBaseWebAudioDsp.name},
195
+ ${FaustMonoWebAudioDsp.name},
196
+ ${FaustWasmInstantiator.name}
197
+ };
198
+ // Generate the actual AudioWorkletProcessor code
199
+ (${getFaustAudioWorkletProcessor.toString()})(dependencies, faustData);
200
+ `;
201
+ const url = URL.createObjectURL(new Blob([processorCode], { type: "text/javascript" }));
202
+ await context.audioWorklet.addModule(url);
203
+ // Keep the DSP name
204
+ FaustMonoDspGenerator.gWorkletProcessors.get(context)?.add(processorName);
205
+ } catch (e) {
206
+ // console.error(`=> exception raised while running createMonoNode: ${e}`);
207
+ // console.error(`=> check that your page is served using https.${e}`);
208
+ throw e;
209
+ }
210
+ }
211
+ // Create the AWN
212
+ const node = new FaustMonoAudioWorkletNode(context, processorName, factory, sampleSize)
213
+ return node as SP extends true ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
214
+ }
215
+ }
216
+ async createFFTNode(
217
+ context: BaseAudioContext,
218
+ fftUtils: typeof FFTUtils,
219
+ name = this.name,
220
+ factory = this.factory as LooseFaustDspFactory,
221
+ fftOptions: Partial<FaustFFTOptionsData> = {},
222
+ processorName = factory?.shaKey ? `${factory.shaKey}_fft` : name
223
+ ): Promise<FaustMonoAudioWorkletNode | null> {
224
+ if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
225
+
226
+ const meta: FaustDspMeta = JSON.parse(factory.json);
227
+ const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
228
+ // Dynamically create AudioWorkletProcessor if code not yet created
229
+ if (!FaustMonoDspGenerator.gWorkletProcessors.has(context)) FaustMonoDspGenerator.gWorkletProcessors.set(context, new Set());
230
+ if (!FaustMonoDspGenerator.gWorkletProcessors.get(context)?.has(processorName)) {
231
+ try {
232
+ const processorCode = `
233
+ // DSP name and JSON string for DSP are generated
234
+ const faustData = ${JSON.stringify({
235
+ processorName,
236
+ dspName: name,
237
+ dspMeta: meta,
238
+ fftOptions
239
+ } as FaustFFTData)};
240
+ // Implementation needed classes of functions
241
+ const ${FaustDspInstance.name}_default = ${FaustDspInstance.toString()}
242
+ const ${FaustBaseWebAudioDsp.name} = ${FaustBaseWebAudioDsp.toString()}
243
+ const ${FaustMonoWebAudioDsp.name} = ${FaustMonoWebAudioDsp.toString()}
244
+ const ${FaustWasmInstantiator.name} = ${FaustWasmInstantiator.toString()}
245
+ const FFTUtils = ${fftUtils.toString()}
246
+ // Put them in dependencies
247
+ const dependencies = {
248
+ ${FaustBaseWebAudioDsp.name},
249
+ ${FaustMonoWebAudioDsp.name},
250
+ ${FaustWasmInstantiator.name},
251
+ FFTUtils
252
+ };
253
+ // Generate the actual AudioWorkletProcessor code
254
+ (${getFaustFFTAudioWorkletProcessor.toString()})(dependencies, faustData);
255
+ `;
256
+ const url = URL.createObjectURL(new Blob([processorCode], { type: "text/javascript" }));
257
+ await context.audioWorklet.addModule(url);
258
+ // Keep the DSP name
259
+ FaustMonoDspGenerator.gWorkletProcessors.get(context)?.add(processorName);
260
+ } catch (e) {
261
+ // console.error(`=> exception raised while running createMonoNode: ${e}`);
262
+ // console.error(`=> check that your page is served using https.${e}`);
263
+ throw e;
264
+ }
265
+ }
266
+ // Create the AWN
267
+ const node = new FaustMonoAudioWorkletNode(context, processorName, factory, sampleSize, { channelCount: Math.max(1, Math.ceil(meta.inputs / 3)), outputChannelCount: [Math.ceil(meta.outputs / 2)] });
268
+ if (fftOptions.fftSize) {
269
+ const param = node.parameters.get("fftSize");
270
+ if (param) param.value = fftOptions.fftSize;
271
+ }
272
+ if (fftOptions.fftOverlap) {
273
+ const param = node.parameters.get("fftOverlap");
274
+ if (param) param.value = fftOptions.fftOverlap;
275
+ }
276
+ if (typeof fftOptions.defaultWindowFunction === "number") {
277
+ const param = node.parameters.get("windowFunction");
278
+ if (param) param.value = fftOptions.defaultWindowFunction + 1;
279
+ }
280
+ if (typeof fftOptions.noIFFT === "boolean") {
281
+ const param = node.parameters.get("noIFFT");
282
+ if (param) param.value = +fftOptions.noIFFT;
283
+ }
284
+ return node;
285
+ }
286
+ async createAudioWorkletProcessor(
287
+ name = this.name,
288
+ factory = this.factory as LooseFaustDspFactory,
289
+ processorName = factory?.shaKey || name
290
+ ) {
291
+ if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
292
+
293
+ const meta = JSON.parse(factory.json);
294
+ const dependencies = {
295
+ FaustBaseWebAudioDsp,
296
+ FaustMonoWebAudioDsp,
297
+ FaustWasmInstantiator,
298
+ FaustPolyWebAudioDsp: undefined,
299
+ FaustWebAudioDspVoice: undefined,
300
+ }
301
+ // const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
302
+ // Dynamically create AudioWorkletProcessor if code not yet created
303
+ try {
304
+ // DSP name and JSON string for DSP are generated
305
+ const faustData = {
306
+ processorName,
307
+ dspName: name,
308
+ dspMeta: meta,
309
+ poly: false
310
+ } as FaustData;
311
+ // Generate the actual AudioWorkletProcessor code
312
+ const Processor = getFaustAudioWorkletProcessor(dependencies, faustData);
313
+ return Processor;
314
+ } catch (e) {
315
+ // console.error(`=> exception raised while running createMonoNode: ${e}`);
316
+ // console.error(`=> check that your page is served using https.${e}`);
317
+ throw e;
318
+ }
319
+ }
320
+ async createOfflineProcessor(
321
+ sampleRate: number,
322
+ bufferSize: number,
323
+ factory = this.factory as LooseFaustDspFactory,
324
+ ) {
325
+ if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
326
+
327
+ const meta = JSON.parse(factory.json);
328
+ const instance = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
329
+ const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
330
+ const monoDsp = new FaustMonoWebAudioDsp(instance, sampleRate, sampleSize, bufferSize);
331
+ return new FaustMonoOfflineProcessor(monoDsp, bufferSize);
332
+ }
333
+ }
334
+
335
+ export class FaustPolyDspGenerator implements IFaustPolyDspGenerator {
336
+ // Set of all created WorkletProcessors, each of them has to be unique
337
+ private static gWorkletProcessors: Map<BaseAudioContext, Set<string>> = new Map();
338
+
339
+ name: string;
340
+ voiceFactory!: FaustDspFactory | null;
341
+ effectFactory!: FaustDspFactory | null;
342
+ mixerBuffer!: Uint8Array;
343
+ mixerModule!: WebAssembly.Module;
344
+
345
+ constructor() {
346
+ this.voiceFactory = null;
347
+ this.effectFactory = null;
348
+ }
349
+ async compile(
350
+ compiler: IFaustCompiler,
351
+ name: string,
352
+ dspCode: string,
353
+ args: string,
354
+ effectCode = `
355
+ adapt(1,1) = _; adapt(2,2) = _,_; adapt(1,2) = _ <: _,_; adapt(2,1) = _,_ :> _;
356
+ adaptor(F,G) = adapt(outputs(F),inputs(G));
357
+ dsp_code = environment{${dspCode}};
358
+ process = adaptor(dsp_code.process, dsp_code.effect) : dsp_code.effect;`
359
+ ) {
360
+ this.voiceFactory = await compiler.createPolyDSPFactory(name, dspCode, args);
361
+ if (!this.voiceFactory) return null;
362
+ // Compile effect, possibly failing since 'compilePolyNode2' can be called by called by 'compilePolyNode'
363
+ try {
364
+ this.effectFactory = await compiler.createPolyDSPFactory(name, effectCode, args);
365
+ } catch (e) {
366
+ console.warn(e);
367
+ }
368
+ this.name = name;
369
+ const voiceMeta = JSON.parse(this.voiceFactory.json);
370
+ const isDouble = voiceMeta.compile_options.match("-double");
371
+ const { mixerBuffer, mixerModule } = await compiler.getAsyncInternalMixerModule(!!isDouble);
372
+ this.mixerBuffer = mixerBuffer;
373
+ this.mixerModule = mixerModule;
374
+ return this;
375
+ }
376
+
377
+ async createNode<SP extends boolean = false>(
378
+ context: BaseAudioContext,
379
+ voices: number,
380
+ name = this.name,
381
+ voiceFactory = this.voiceFactory as LooseFaustDspFactory,
382
+ mixerModule = this.mixerModule,
383
+ effectFactory = this.effectFactory as LooseFaustDspFactory | null,
384
+ sp = false as SP,
385
+ bufferSize = 1024,
386
+ processorName = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`
387
+ ): Promise<SP extends true ? FaustPolyScriptProcessorNode | null : FaustPolyAudioWorkletNode | null> {
388
+ if (!voiceFactory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
389
+
390
+ const voiceMeta = JSON.parse(voiceFactory.json);
391
+ const effectMeta = effectFactory ? JSON.parse(effectFactory.json) : undefined;
392
+ const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
393
+ if (sp) {
394
+ const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
395
+ const polyDsp = new FaustPolyWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize);
396
+ const sp = context.createScriptProcessor(bufferSize, polyDsp.getNumInputs(), polyDsp.getNumOutputs()) as FaustPolyScriptProcessorNode;
397
+ Object.setPrototypeOf(sp, FaustPolyScriptProcessorNode.prototype);
398
+ sp.init(polyDsp);
399
+ return sp as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
400
+ } else {
401
+ // Dynamically create AudioWorkletProcessor if code not yet created
402
+ if (!FaustPolyDspGenerator.gWorkletProcessors.has(context)) FaustPolyDspGenerator.gWorkletProcessors.set(context, new Set());
403
+ if (!FaustPolyDspGenerator.gWorkletProcessors.get(context)?.has(processorName)) {
404
+ try {
405
+ const processorCode = `
406
+ // DSP name and JSON string for DSP are generated
407
+ const faustData = ${JSON.stringify({
408
+ processorName,
409
+ dspName: name,
410
+ dspMeta: voiceMeta,
411
+ poly: true,
412
+ effectMeta
413
+ } as FaustData)};
414
+ // Implementation needed classes of functions
415
+ const ${FaustDspInstance.name}_default = ${FaustDspInstance.toString()}
416
+ const ${FaustBaseWebAudioDsp.name} = ${FaustBaseWebAudioDsp.toString()}
417
+ const ${FaustPolyWebAudioDsp.name} = ${FaustPolyWebAudioDsp.toString()}
418
+ const ${FaustWebAudioDspVoice.name} = ${FaustWebAudioDspVoice.toString()}
419
+ const ${FaustWasmInstantiator.name} = ${FaustWasmInstantiator.toString()}
420
+ // Put them in dependencies
421
+ const dependencies = {
422
+ ${FaustBaseWebAudioDsp.name},
423
+ ${FaustPolyWebAudioDsp.name},
424
+ ${FaustWasmInstantiator.name}
425
+ };
426
+ // Generate the actual AudioWorkletProcessor code
427
+ (${getFaustAudioWorkletProcessor.toString()})(dependencies, faustData);
428
+ `;
429
+ const url = URL.createObjectURL(new Blob([processorCode], { type: "text/javascript" }));
430
+ await context.audioWorklet.addModule(url);
431
+ // Keep the DSP name
432
+ FaustPolyDspGenerator.gWorkletProcessors.get(context)?.add(processorName);
433
+ } catch (e) {
434
+ // console.error(`=> exception raised while running createPolyNode: ${e}`);
435
+ // console.error(`=> check that your page is served using https.${e}`);
436
+ throw e;
437
+ }
438
+ }
439
+ // Create the AWN
440
+ const node = new FaustPolyAudioWorkletNode(context, processorName, voiceFactory, mixerModule, voices, sampleSize, effectFactory || undefined);
441
+ return node as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
442
+ }
443
+ }
444
+ async createAudioWorkletProcessor(
445
+ name = this.name,
446
+ voiceFactory = this.voiceFactory as LooseFaustDspFactory,
447
+ effectFactory = this.effectFactory as LooseFaustDspFactory | null,
448
+ processorName = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`
449
+ ) {
450
+ if (!voiceFactory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
451
+
452
+ const voiceMeta = JSON.parse(voiceFactory.json);
453
+ const effectMeta = effectFactory ? JSON.parse(effectFactory.json) : undefined;
454
+ const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
455
+ // Dynamically create AudioWorkletProcessor if code not yet created
456
+ try {
457
+ const dependencies = {
458
+ FaustBaseWebAudioDsp,
459
+ FaustMonoWebAudioDsp: undefined,
460
+ FaustWasmInstantiator,
461
+ FaustPolyWebAudioDsp,
462
+ FaustWebAudioDspVoice,
463
+ };
464
+ // DSP name and JSON string for DSP are generated
465
+ const faustData = {
466
+ processorName,
467
+ dspName: name,
468
+ dspMeta: voiceMeta,
469
+ poly: true,
470
+ effectMeta
471
+ } as FaustData;
472
+ // Generate the actual AudioWorkletProcessor code
473
+ const Processor = getFaustAudioWorkletProcessor<true>(dependencies, faustData);
474
+ return Processor;
475
+ } catch (e) {
476
+ // console.error(`=> exception raised while running createPolyNode: ${e}`);
477
+ // console.error(`=> check that your page is served using https.${e}`);
478
+ throw e;
479
+ }
480
+ }
481
+ async createOfflineProcessor(
482
+ sampleRate: number,
483
+ bufferSize: number,
484
+ voices: number,
485
+ voiceFactory = this.voiceFactory as LooseFaustDspFactory,
486
+ mixerModule = this.mixerModule,
487
+ effectFactory = this.effectFactory as LooseFaustDspFactory | null
488
+ ) {
489
+ if (!voiceFactory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
490
+
491
+ const voiceMeta = JSON.parse(voiceFactory.json);
492
+ const effectMeta = effectFactory ? JSON.parse(effectFactory.json) : undefined;
493
+ const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
494
+ const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
495
+ const polyDsp = new FaustPolyWebAudioDsp(instance, sampleRate, sampleSize, bufferSize);
496
+ return new FaustPolyOfflineProcessor(polyDsp, bufferSize);
497
+ }
498
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The Faust wasm instance interface.
3
+ */
4
+ export interface IFaustDspInstance {
5
+ /**
6
+ * The dsp computation, to be called with successive input/output audio buffers.
7
+ *
8
+ * @param $dsp - the DSP pointer
9
+ * @param count - the audio buffer size in frames
10
+ * @param $inputs - the input audio buffer as in index in wasm memory
11
+ * @param $output - the output audio buffer as in index in wasm memory
12
+ */
13
+ compute($dsp: number, count: number, $inputs: number, $output: number): void;
14
+
15
+ /**
16
+ * Give the number of inputs of a Faust wasm instance.
17
+ *
18
+ * @param $dsp - the DSP pointer
19
+ */
20
+ getNumInputs($dsp: number): number;
21
+
22
+ /**
23
+ * Give the number of outputs of a Faust wasm instance.
24
+ *
25
+ * @param $dsp - the DSP pointer
26
+ */
27
+ getNumOutputs($dsp: number): number;
28
+
29
+ /**
30
+ * Give a parameter current value.
31
+ *
32
+ * @param $dsp - the DSP pointer
33
+ * @param index - the parameter index
34
+ * @preturn the parameter value
35
+ */
36
+ getParamValue($dsp: number, index: number): number;
37
+
38
+ /**
39
+ * Give the Faust wasm instance sample rate.
40
+ *
41
+ * @param $dsp - the DSP pointer
42
+ * @preturn the sample rate
43
+ */
44
+ getSampleRate($dsp: number): number;
45
+
46
+ /**
47
+ * Global init, calls the following methods:
48
+ * - static class 'classInit': static tables initialization
49
+ * - 'instanceInit': constants and instance state initialization
50
+ *
51
+ * @param $dsp - the DSP pointer
52
+ * @param sampleRate - the sampling rate in Hertz
53
+ */
54
+ init($dsp: number, sampleRate: number): void;
55
+
56
+ /** Init instance state (delay lines...).
57
+ *
58
+ * @param $dsp - the DSP pointer
59
+ */
60
+ instanceClear($dsp: number): void;
61
+
62
+ /** Init instance constant state.
63
+ *
64
+ * @param $dsp - the DSP pointer
65
+ * @param sampleRate - the sampling rate in Hertz
66
+ */
67
+ instanceConstants($dsp: number, sampleRate: number): void;
68
+
69
+ /** Init instance state.
70
+ *
71
+ * @param $dsp - the DSP pointer
72
+ * @param sampleRate - the sampling rate in Hertz
73
+ */
74
+ instanceInit($dsp: number, sampleRate: number): void;
75
+
76
+ /** Init default control parameters values.
77
+ *
78
+ * @param $dsp - the DSP pointer
79
+ */
80
+ instanceResetUserInterface($dsp: number): void;
81
+
82
+ /**
83
+ * Set a parameter current value.
84
+ *
85
+ * @param $dsp - the DSP pointer
86
+ * @param index - the parameter index
87
+ * @param value - the parameter value
88
+ */
89
+ setParamValue($dsp: number, index: number, value: number): void;
90
+ }
91
+
92
+ /**
93
+ * Mixer used in polyphonic mode.
94
+ */
95
+ export interface IFaustMixerInstance {
96
+ clearOutput(bufferSize: number, chans: number, $outputs: number): void;
97
+ mixCheckVoice(bufferSize: number, chans: number, $inputs: number, $outputs: number): number;
98
+ fadeOut(bufferSize: number, chans: number, $outputs: number): void;
99
+ }
100
+
101
+ /**
102
+ * Monophonic instance.
103
+ */
104
+ export interface FaustMonoDspInstance {
105
+ memory: WebAssembly.Memory;
106
+ api: IFaustDspInstance;
107
+ json: string;
108
+ }
109
+
110
+ /**
111
+ * Polyphonic instance.
112
+ */
113
+ export interface FaustPolyDspInstance {
114
+ memory: WebAssembly.Memory;
115
+ voices: number;
116
+ voiceAPI: IFaustDspInstance;
117
+ effectAPI?: IFaustDspInstance;
118
+ mixerAPI: IFaustMixerInstance;
119
+ voiceJSON: string;
120
+ effectJSON?: string;
121
+ }
122
+
123
+ class FaustDspInstance implements IFaustDspInstance {
124
+ private readonly fExports: IFaustDspInstance;
125
+
126
+ constructor(exports: IFaustDspInstance) { this.fExports = exports; }
127
+
128
+ compute($dsp: number, count: number, $input: number, $output: number) { this.fExports.compute($dsp, count, $input, $output); }
129
+ getNumInputs($dsp: number) { return this.fExports.getNumInputs($dsp); }
130
+ getNumOutputs($dsp: number) { return this.fExports.getNumOutputs($dsp); }
131
+ getParamValue($dsp: number, index: number) { return this.fExports.getParamValue($dsp, index); }
132
+ getSampleRate($dsp: number) { return this.fExports.getSampleRate($dsp); }
133
+ init($dsp: number, sampleRate: number) { this.fExports.init($dsp, sampleRate); }
134
+ instanceClear($dsp: number) { this.fExports.instanceClear($dsp); }
135
+ instanceConstants($dsp: number, sampleRate: number) { this.fExports.instanceConstants($dsp, sampleRate); }
136
+ instanceInit($dsp: number, sampleRate: number) { this.fExports.instanceInit($dsp, sampleRate); }
137
+ instanceResetUserInterface($dsp: number) { this.fExports.instanceResetUserInterface($dsp); }
138
+ setParamValue($dsp: number, index: number, value: number) { this.fExports.setParamValue($dsp, index, value); }
139
+ }
140
+
141
+ export default FaustDspInstance;