@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,554 @@
1
+ import type { FaustMonoDspInstance } from "./FaustDspInstance";
2
+ import type FaustWasmInstantiator from "./FaustWasmInstantiator";
3
+ import type { FaustBaseWebAudioDsp, FaustMonoWebAudioDsp, PlotHandler } from "./FaustWebAudioDsp";
4
+ import type { AudioParamDescriptor, AudioWorkletGlobalScope, LooseFaustDspFactory, FaustDspMeta, FaustUIItem, InterfaceFFT, TWindowFunction, Writeable, TypedArray, FFTUtils } from "./types";
5
+
6
+ export interface FaustFFTOptionsData {
7
+ fftSize: number;
8
+ fftOverlap: number;
9
+ noIFFT: boolean;
10
+ /** Index number of the default window function, leave undefined or -1 for rectangular (no windowing) */
11
+ defaultWindowFunction: number;
12
+ }
13
+
14
+ /**
15
+ * Injected in the string to be compiled on AudioWorkletProcessor side
16
+ */
17
+ export interface FaustFFTData {
18
+ processorName: string;
19
+ dspName: string;
20
+ dspMeta: FaustDspMeta;
21
+ fftOptions?: Partial<FaustFFTOptionsData>;
22
+ };
23
+ export interface FaustFFTAudioWorkletProcessorDependencies {
24
+ FaustBaseWebAudioDsp: typeof FaustBaseWebAudioDsp;
25
+ FaustMonoWebAudioDsp: typeof FaustMonoWebAudioDsp;
26
+ FaustWasmInstantiator: typeof FaustWasmInstantiator;
27
+ FFTUtils: typeof FFTUtils;
28
+ }
29
+ export interface FaustFFTAudioWorkletNodeOptions extends AudioWorkletNodeOptions {
30
+ processorOptions: FaustFFTAudioWorkletProcessorOptions;
31
+ }
32
+ export interface FaustFFTAudioWorkletProcessorOptions {
33
+ name: string;
34
+ sampleSize: number;
35
+ factory: LooseFaustDspFactory;
36
+ }
37
+
38
+
39
+ // Dynamic AudioWorkletProcessor code generator
40
+ const getFaustFFTAudioWorkletProcessor = (dependencies: FaustFFTAudioWorkletProcessorDependencies, faustData: FaustFFTData, register = true): typeof AudioWorkletProcessor => {
41
+ const { registerProcessor, AudioWorkletProcessor, sampleRate } = globalThis as unknown as AudioWorkletGlobalScope;
42
+
43
+ const {
44
+ FaustBaseWebAudioDsp,
45
+ FaustWasmInstantiator,
46
+ FaustMonoWebAudioDsp,
47
+ FFTUtils
48
+ } = dependencies;
49
+
50
+ const {
51
+ processorName,
52
+ dspName,
53
+ dspMeta,
54
+ fftOptions
55
+ } = faustData;
56
+
57
+ const {
58
+ windowFunctions,
59
+ getFFT,
60
+ fftToSignal,
61
+ signalToFFT,
62
+ signalToNoFFT
63
+ } = FFTUtils;
64
+
65
+ /**
66
+ * Ceil a number to multiple of another
67
+ */
68
+ const ceil = (x: number, to: number): number => (Math.abs(to) < 1 ? Math.ceil(x * (1 / to)) / (1 / to) : Math.ceil(x / to) * to);
69
+
70
+ /**
71
+ * Mod support wrapping with negative numbers
72
+ */
73
+ const mod = (x: number, y: number): number => (x % y + y) % y;
74
+
75
+ const apply = (array: Writeable<ArrayLike<number>>, windowFunction: TWindowFunction) => {
76
+ for (let i = 0; i < array.length; i++) {
77
+ array[i] *= windowFunction(i, array.length);
78
+ }
79
+ };
80
+
81
+ const fftParamKeywords = ["/fftSize", "/fftHopSize", "/fftOverlap", "/windowFunction", "/noIFFT"];
82
+
83
+ /**
84
+ * Copy buffer to another, support negative offset index
85
+ */
86
+ const setTypedArray = <T extends TypedArray = TypedArray>(to: T, from: T, offsetTo = 0, offsetFrom = 0) => {
87
+ const toLength = to.length;
88
+ const fromLength = from.length;
89
+ const spillLength = Math.min(toLength, fromLength);
90
+ let spilled = 0;
91
+ let $to = mod(offsetTo, toLength) || 0;
92
+ let $from = mod(offsetFrom, fromLength) || 0;
93
+ while (spilled < spillLength) {
94
+ const $spillLength = Math.min(spillLength - spilled, toLength - $to, fromLength - $from);
95
+ const $fromEnd = $from + $spillLength;
96
+ if ($from === 0 && $fromEnd === fromLength) to.set(from, $to);
97
+ else to.set(from.subarray($from, $fromEnd), $to);
98
+ $to = ($to + $spillLength) % toLength;
99
+ $from = $fromEnd % fromLength;
100
+ spilled += $spillLength;
101
+ }
102
+ return $to;
103
+ };
104
+
105
+ // Analyse JSON to generate AudioParam parameters
106
+ const analyseParameters = (item: FaustUIItem): AudioParamDescriptor | null => {
107
+ const isFFTReserved = "address" in item && !!fftParamKeywords.find(k => item.address.endsWith(k));
108
+ if (isFFTReserved) return null;
109
+ if (item.type === "vslider" || item.type === "hslider" || item.type === "nentry") {
110
+ return { name: item.address, defaultValue: item.init || 0, minValue: item.min || 0, maxValue: item.max || 0 };
111
+ } else if (item.type === "button" || item.type === "checkbox") {
112
+ return { name: item.address, defaultValue: item.init || 0, minValue: 0, maxValue: 1 };
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * Class for Faust FFT AudioWorkletProcessor
118
+ */
119
+ class FaustFFTAudioWorkletProcessor extends AudioWorkletProcessor {
120
+
121
+ protected fDSPCode: FaustMonoWebAudioDsp;
122
+
123
+ protected paramValuesCache: Record<string, number> = {};
124
+
125
+ private dspInstance!: FaustMonoDspInstance;
126
+ private sampleSize!: number;
127
+
128
+ private destroyed = false;
129
+ /** Pointer of next start sample to write of the FFT input window */
130
+ private $inputWrite = 0;
131
+ /** Pointer of next start sample to read of the FFT input window */
132
+ private $inputRead = 0;
133
+ /** Pointer of next start sample to write of the FFT output window */
134
+ private $outputWrite = 0;
135
+ /** Pointer of next start sample to read of the FFT output window */
136
+ private $outputRead = 0;
137
+ /** Not perform in IFFT when reconstruct the audio signal */
138
+ private noIFFT = false;
139
+ /** audio data from input, array of channels */
140
+ private readonly fftInput: Float32Array[] = [];
141
+ /** audio data for output, array of channels */
142
+ private readonly fftOutput: Float32Array[] = [];
143
+ /** Generated from the current window function */
144
+ private window: Float32Array;
145
+ /** Generated from the current window's rolling sum square */
146
+ private windowSumSquare: Float32Array;
147
+
148
+ /** FFT constructor */
149
+ private FFT: typeof InterfaceFFT;
150
+ /** Real FFT interface */
151
+ private rfft: InterfaceFFT;
152
+ /** Faust param name of fftHopSize */
153
+ private fftHopSizeParam: string | undefined;
154
+ /** FFT Overlaps, 1 means no overlap */
155
+ private fftOverlap = 0;
156
+ private fftHopSize = 0;
157
+ private fftSize = 0;
158
+ private fftBufferSize = 0;
159
+ private fftProcessorZeros: Float32Array;
160
+ private noIFFTBuffer: Float32Array;
161
+
162
+ private fPlotHandler: PlotHandler | null = null;
163
+ private fCachedEvents: { type: string; data: any }[] = [];
164
+ private fBufferNum = 0;
165
+ get fftProcessorBufferSize() {
166
+ return this.fftSize / 2 + 1;
167
+ }
168
+ private windowFunction: TWindowFunction | null = null;
169
+
170
+ constructor(options: FaustFFTAudioWorkletNodeOptions) {
171
+ super(options);
172
+
173
+ // Setup port message handling
174
+ this.port.onmessage = (e: MessageEvent) => this.handleMessageAux(e);
175
+
176
+ const { parameterDescriptors } = (this.constructor as typeof AudioWorkletProcessor);
177
+ parameterDescriptors.forEach((pd) => {
178
+ this.paramValuesCache[pd.name] = pd.defaultValue || 0;
179
+ })
180
+
181
+ const { factory, sampleSize } = options.processorOptions;
182
+
183
+ this.dspInstance = FaustWasmInstantiator.createSyncMonoDSPInstance(factory);
184
+ this.sampleSize = sampleSize;
185
+
186
+ // Init the FFT constructor and the Faust FFT Processor
187
+ this.initFFT();
188
+ }
189
+
190
+ async initFFT(): Promise<true> {
191
+ // Use injected function to instantiate the FFT constructor
192
+ this.FFT = await getFFT();
193
+ // Init Faust FFT Processor
194
+ await this.createFFTProcessor();
195
+ return true;
196
+ }
197
+
198
+ static get parameterDescriptors() {
199
+ const params = [] as AudioParamDescriptor[];
200
+ // Analyse voice JSON to generate AudioParam parameters
201
+ const callback = (item: FaustUIItem) => {
202
+ const param = analyseParameters(item);
203
+ if (param) params.push(param);
204
+ }
205
+ FaustBaseWebAudioDsp.parseUI(dspMeta.ui, callback);
206
+ // Add to Faust parameters, FFT specified parameters
207
+ return [
208
+ ...params,
209
+ {
210
+ defaultValue: fftOptions?.fftSize || 1024,
211
+ maxValue: 2 ** 32,
212
+ minValue: 2,
213
+ name: "fftSize"
214
+ }, {
215
+ defaultValue: fftOptions?.fftOverlap || 2,
216
+ maxValue: 32,
217
+ minValue: 1,
218
+ name: "fftOverlap"
219
+ }, {
220
+ defaultValue: typeof fftOptions?.defaultWindowFunction === "number" ? fftOptions.defaultWindowFunction + 1 : 0,
221
+ maxValue: windowFunctions?.length || 0,
222
+ minValue: 0,
223
+ name: "windowFunction"
224
+ }, {
225
+ defaultValue: +!!fftOptions?.noIFFT || 0,
226
+ maxValue: 1,
227
+ minValue: 0,
228
+ name: "noIFFT"
229
+ }
230
+ ];
231
+ }
232
+
233
+ processFFT() {
234
+ // Get the number of samples that need to proceed, from the input r/w pointers
235
+ let samplesForFFT = mod(this.$inputWrite - this.$inputRead, this.fftBufferSize) || this.fftBufferSize;
236
+ // Start process, until no more enough samples
237
+ while (samplesForFFT >= this.fftSize) {
238
+ let fftProcessorOutputs: Float32Array[] = [];
239
+ // Faust processing, use a callback to avoid extra data copy
240
+ this.fDSPCode.compute((inputs) => {
241
+ // for each audio input channel, three Faust FFT input buffers can be generated (real, imag, FFT bin index)
242
+ for (let i = 0; i < Math.min(this.fftInput.length, Math.ceil(inputs.length / 3)); i++) {
243
+ // FFT forward, use a callback to avoid extra data copy
244
+ const ffted = this.rfft.forward((fftBuffer) => {
245
+ setTypedArray(fftBuffer, this.fftInput[i], 0, this.$inputRead);
246
+ // Windowing the input
247
+ for (let j = 0; j < fftBuffer.length; j++) {
248
+ fftBuffer[j] *= this.window[j];
249
+ }
250
+ // data for FFT (fftBuffer) is prepared
251
+ });
252
+ // write FFTed spectral data to three Faust FFT input buffers (real, imag, FFT bin index)
253
+ fftToSignal(ffted, inputs[i * 3], inputs[i * 3 + 1], inputs[i * 3 + 2]);
254
+ // Faust inputs are prepared
255
+ }
256
+ // If the Faust DSP has more inputs, fill them (zeros or real/imag, fill FFT bin indexes)
257
+ for (let i = this.fftInput.length * 3; i < inputs.length; i++) {
258
+ if (i % 3 === 2) inputs[i].forEach((v, j) => inputs[i][j] = j);
259
+ else inputs[i].fill(0);
260
+ }
261
+ }, (outputs) => {
262
+ // Get the Faust DSP outputs
263
+ fftProcessorOutputs = outputs as Float32Array[];
264
+ });
265
+
266
+ // Advance FFT input read pointers
267
+ this.$inputRead += this.fftHopSize;
268
+ this.$inputRead %= this.fftBufferSize;
269
+
270
+ samplesForFFT -= this.fftHopSize;
271
+
272
+ // Do inverse FFT on the processed data by Faust DSP, and write the reconstructed signal to the output buffer
273
+ for (let i = 0; i < this.fftOutput.length; i++) {
274
+ let iffted: Float32Array;
275
+ // If noIFFT option in enabled, then no need to do inverse IFFT, use the injected function to convert
276
+ if (this.noIFFT) {
277
+ iffted = this.noIFFTBuffer;
278
+ signalToNoFFT(fftProcessorOutputs[i * 2] || this.fftProcessorZeros, fftProcessorOutputs[i * 2 + 1] || this.fftProcessorZeros, iffted);
279
+ } else {
280
+ // FFT inverse, use a callback to avoid extra data copy
281
+ iffted = this.rfft.inverse((ifftBuffer) => {
282
+ // Convert the Faust DSP output (real/imag plans) to an array for inverse FFT
283
+ signalToFFT(fftProcessorOutputs[i * 2] || this.fftProcessorZeros, fftProcessorOutputs[i * 2 + 1] || this.fftProcessorZeros, ifftBuffer);
284
+ // ifftBuffer is prepared
285
+ });
286
+ }
287
+ // Windowing the output
288
+ for (let j = 0; j < iffted.length; j++) {
289
+ iffted[j] *= this.window[j];
290
+ }
291
+ // Overlap-add, preparing the windowSumSquare array for reverse the windowing effect when output the audio
292
+ let $: number;
293
+ // First part, add the part that is overlaped with the previous window
294
+ for (let j = 0; j < iffted.length - this.fftHopSize; j++) {
295
+ $ = mod(this.$outputWrite + j, this.fftBufferSize);
296
+ this.fftOutput[i][$] += iffted[j];
297
+ if (i === 0) this.windowSumSquare[$] += this.noIFFT ? this.window[j] : this.window[j] ** 2;
298
+ }
299
+ // Second part, write directly to the output buffer
300
+ for (let j = iffted.length - this.fftHopSize; j < iffted.length; j++) {
301
+ $ = mod(this.$outputWrite + j, this.fftBufferSize);
302
+ this.fftOutput[i][$] = iffted[j];
303
+ if (i === 0) this.windowSumSquare[$] = this.noIFFT ? this.window[j] : this.window[j] ** 2;
304
+ }
305
+ }
306
+ // Advance FFT output write pointers
307
+ this.$outputWrite += this.fftHopSize;
308
+ this.$outputWrite %= this.fftBufferSize;
309
+ }
310
+ }
311
+
312
+ process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: { [key: string]: Float32Array }) {
313
+
314
+ if (this.destroyed) return false;
315
+ if (!this.FFT) return true;
316
+ const input = inputs[0];
317
+ const output = outputs[0];
318
+ const inputChannels = input?.length || 0;
319
+ const outputChannels = output?.length || 0;
320
+ // if (input.length === 0) return true;
321
+
322
+ const bufferSize = input?.length ? Math.max(...input.map(c => c.length)) || 128 : 128;
323
+
324
+ // Reset FFT and related buffers if necessary (checks in the resetFFT method)
325
+ this.noIFFT = !!parameters.noIFFT[0];
326
+ this.resetFFT(~~parameters.fftSize[0], ~~parameters.fftOverlap[0], ~~parameters.windowFunction[0], inputChannels, outputChannels, bufferSize);
327
+
328
+ if (!this.fDSPCode) return true;
329
+
330
+ for (const path in parameters) {
331
+ if (!!fftParamKeywords.find(k => `/${path}`.endsWith(k))) continue;
332
+ const [paramValue] = parameters[path];
333
+ if (paramValue !== this.paramValuesCache[path]) {
334
+ this.fDSPCode.setParamValue(path, paramValue);
335
+ this.paramValuesCache[path] = paramValue;
336
+ }
337
+ }
338
+
339
+ // Write audio input into fftInput buffer, advance pointers
340
+ if (input?.length) {
341
+ let $inputWrite = 0;
342
+ for (let i = 0; i < input.length; i++) {
343
+ const inputWindow = this.fftInput[i];
344
+ const channel = input[i].length ? input[i] : new Float32Array(bufferSize);
345
+ $inputWrite = setTypedArray(inputWindow, channel, this.$inputWrite);
346
+ }
347
+ this.$inputWrite = $inputWrite;
348
+ } else {
349
+ this.$inputWrite += bufferSize;
350
+ this.$inputWrite %= this.fftBufferSize;
351
+ }
352
+
353
+ // Do FFT if necessary
354
+ this.processFFT();
355
+
356
+ // Read from fftOutput buffer for audio output, applying windowSumSquare to reverse the doubled windowing effect
357
+ for (let i = 0; i < output.length; i++) {
358
+ setTypedArray(output[i], this.fftOutput[i], 0, this.$outputRead);
359
+ // let a = 0;
360
+ let div = 0;
361
+ for (let j = 0; j < bufferSize; j++) {
362
+ div = this.windowSumSquare[mod(this.$outputRead + j, this.fftBufferSize)];
363
+ output[i][j] /= div < 1e-8 ? 1 : div;
364
+ }
365
+ }
366
+ // Advance pointers
367
+ this.$outputRead += bufferSize;
368
+ this.$outputRead %= this.fftBufferSize;
369
+
370
+ // plot
371
+ if (this.fPlotHandler) {
372
+ this.port.postMessage({ type: "plot", value: output, index: this.fBufferNum++, events: this.fCachedEvents });
373
+ this.fCachedEvents = [];
374
+ }
375
+ return true;
376
+ }
377
+
378
+ protected handleMessageAux(e: MessageEvent) { // use arrow function for binding
379
+ const msg = e.data;
380
+
381
+ switch (msg.type) {
382
+ // Generic MIDI message
383
+ case "midi": this.midiMessage(msg.data); break;
384
+ // Typed MIDI message
385
+ case "ctrlChange": this.ctrlChange(msg.data[0], msg.data[1], msg.data[2]); break;
386
+ case "pitchWheel": this.pitchWheel(msg.data[0], msg.data[1]); break;
387
+ // Generic data message
388
+ case "param": this.setParamValue(msg.data.path, msg.data.value); break;
389
+ // Plot handler set on demand
390
+ case "setPlotHandler": {
391
+ if (msg.data) {
392
+ this.fPlotHandler = (output, index, events) => {
393
+ if (events) this.fCachedEvents.push(...events);
394
+ };
395
+ } else {
396
+ this.fPlotHandler = null;
397
+ }
398
+ this.fDSPCode?.setPlotHandler(this.fPlotHandler);
399
+ break;
400
+ }
401
+ case "start": {
402
+ this.fDSPCode?.start();
403
+ break;
404
+ }
405
+ case "stop": {
406
+ this.fDSPCode?.stop();
407
+ break;
408
+ }
409
+ case "destroy": {
410
+ this.port.close();
411
+ this.destroy();
412
+ break;
413
+ }
414
+ default:
415
+ break;
416
+ }
417
+ }
418
+
419
+ protected setParamValue(path: string, value: number) {
420
+ this.fDSPCode?.setParamValue(path, value);
421
+ this.paramValuesCache[path] = value;
422
+ }
423
+
424
+ protected midiMessage(data: number[] | Uint8Array) {
425
+ this.fDSPCode?.midiMessage(data);
426
+ }
427
+
428
+ protected ctrlChange(channel: number, ctrl: number, value: number) {
429
+ this.fDSPCode?.ctrlChange(channel, ctrl, value);
430
+ }
431
+
432
+ protected pitchWheel(channel: number, wheel: number) {
433
+ this.fDSPCode?.pitchWheel(channel, wheel);
434
+ }
435
+
436
+ resetFFT(sizeIn: number, overlapIn: number, windowFunctionIn: number, inputChannels: number, outputChannels: number, bufferSize: number) {
437
+ const fftSize = ~~ceil(Math.max(2, sizeIn || 1024), 2);
438
+ const fftOverlap = ~~Math.min(fftSize, Math.max(1, overlapIn));
439
+ const fftHopSize = ~~Math.max(1, fftSize / fftOverlap);
440
+ const latency = fftSize - Math.min(fftHopSize, bufferSize);
441
+ let windowFunction: TWindowFunction | null = null;
442
+
443
+ // set the window function from the injected list
444
+ if (windowFunctionIn !== 0) {
445
+ windowFunction = typeof windowFunctions === "object" ? windowFunctions[~~windowFunctionIn - 1] || null : null;
446
+ }
447
+ const fftSizeChanged = fftSize !== this.fftSize;
448
+
449
+ const fftOverlapChanged = fftOverlap !== this.fftOverlap;
450
+ // Reset FFT vars if the size is changed
451
+ if (fftSizeChanged || fftOverlapChanged) {
452
+ this.fftSize = fftSize;
453
+ this.fftOverlap = fftOverlap;
454
+ this.fftHopSize = fftHopSize;
455
+ this.$inputWrite = 0;
456
+ this.$inputRead = 0;
457
+ this.$outputWrite = 0;
458
+ this.$outputRead = -latency;
459
+ this.fftBufferSize = Math.max(fftSize * 2 - this.fftHopSize, bufferSize * 2);
460
+ if (!fftSizeChanged && this.fftHopSizeParam) this.fDSPCode?.setParamValue(this.fftHopSizeParam, this.fftHopSize);
461
+ }
462
+
463
+ // Reset the FFT interface and the Faust Processor
464
+ if (fftSizeChanged) {
465
+ this.rfft?.dispose();
466
+ this.rfft = new this.FFT(fftSize);
467
+ this.noIFFTBuffer = new Float32Array(this.fftSize);
468
+ this.createFFTProcessor();
469
+ }
470
+
471
+ // Calculate a window from the window function, prepare the windowSumSquare buffer
472
+ if (fftSizeChanged || fftOverlapChanged || windowFunction !== this.windowFunction) {
473
+ this.windowFunction = windowFunction;
474
+ this.window = new Float32Array(fftSize);
475
+ this.window.fill(1);
476
+ if (windowFunction) apply(this.window, windowFunction);
477
+ this.windowSumSquare = new Float32Array(this.fftBufferSize);
478
+ }
479
+
480
+ // Reset FFT I/O buffers if necessary
481
+ if (this.fftInput.length > inputChannels) {
482
+ this.fftInput.splice(inputChannels);
483
+ }
484
+ if (this.fftOutput.length > outputChannels) {
485
+ this.fftOutput.splice(outputChannels);
486
+ }
487
+ if (fftSizeChanged || fftOverlapChanged) {
488
+ for (let i = 0; i < inputChannels; i++) {
489
+ this.fftInput[i] = new Float32Array(this.fftBufferSize);
490
+ }
491
+ for (let i = 0; i < outputChannels; i++) {
492
+ this.fftOutput[i] = new Float32Array(this.fftBufferSize);
493
+ }
494
+ } else {
495
+ if (this.fftInput.length < inputChannels) {
496
+ for (let i = this.fftInput.length; i < inputChannels; i++) {
497
+ this.fftInput[i] = new Float32Array(this.fftBufferSize);
498
+ }
499
+ }
500
+ if (this.fftOutput.length < outputChannels) {
501
+ for (let i = this.fftOutput.length; i < outputChannels; i++) {
502
+ this.fftOutput[i] = new Float32Array(this.fftBufferSize);
503
+ }
504
+ }
505
+ }
506
+ }
507
+ async createFFTProcessor() {
508
+ this.fDSPCode?.stop();
509
+ this.fDSPCode?.destroy();
510
+
511
+ // Create Monophonic DSP
512
+ this.fDSPCode = new FaustMonoWebAudioDsp(this.dspInstance, sampleRate, this.sampleSize, this.fftProcessorBufferSize);
513
+
514
+ // Setup output handler
515
+ this.fDSPCode.setOutputParamHandler((path, value) => this.port.postMessage({ path, value, type: "param" }));
516
+ this.fDSPCode.setPlotHandler(this.fPlotHandler);
517
+ const params = this.fDSPCode.getParams();
518
+ this.fDSPCode.start();
519
+ // Write the cached parameters
520
+ for (const path in this.paramValuesCache) {
521
+ if (!!fftParamKeywords.find(k => `/${path}`.endsWith(k))) continue;
522
+ this.fDSPCode.setParamValue(path, this.paramValuesCache[path])
523
+ }
524
+ // Write the FFT reverved parameters
525
+ const fftSizeParam = params.find(s => s.endsWith("/fftSize"));
526
+ if (fftSizeParam) this.fDSPCode.setParamValue(fftSizeParam, this.fftSize);
527
+ this.fftHopSizeParam = params.find(s => s.endsWith("/fftHopSize"));
528
+ if (this.fftHopSizeParam) this.fDSPCode.setParamValue(this.fftHopSizeParam, this.fftHopSize);
529
+ // Prepare a array of zeros for furthur usage
530
+ this.fftProcessorZeros = new Float32Array(this.fftProcessorBufferSize);
531
+ }
532
+ destroy() {
533
+ this.fDSPCode?.stop();
534
+ this.fDSPCode?.destroy();
535
+ this.rfft?.dispose();
536
+ this.destroyed = true;
537
+ }
538
+
539
+ }
540
+
541
+ const Processor = FaustFFTAudioWorkletProcessor;
542
+ if (register) {
543
+ try {
544
+ registerProcessor(processorName || dspName || "myfftdsp", Processor);
545
+ } catch (error) {
546
+ console.warn(error);
547
+ }
548
+ }
549
+
550
+ return FaustFFTAudioWorkletProcessor;
551
+
552
+ };
553
+
554
+ export default getFaustFFTAudioWorkletProcessor;