@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,1060 @@
1
+ import type { FaustMonoDspInstance, FaustPolyDspInstance, IFaustDspInstance } from "./FaustDspInstance";
2
+ import type { FaustDspMeta, FaustUIDescriptor, FaustUIGroup, FaustUIInputItem, FaustUIItem } from "./types";
3
+
4
+ // Public API
5
+ export type OutputParamHandler = (path: string, value: number) => void;
6
+ export type ComputeHandler = (buffer_size: number) => void;
7
+ export type PlotHandler = (plotted: Float32Array[] | Float64Array[], index: number, events?: { type: string; data: any }[]) => void;
8
+ export type MetadataHandler = (key: string, value: string) => void;
9
+
10
+ // Implementation API
11
+ export type UIHandler = (item: FaustUIItem) => void;
12
+
13
+ /**
14
+ * DSP implementation: mimic the C++ 'dsp' class:
15
+ * - adding MIDI control: metadata are decoded and incoming MIDI messages will control the associated controllers
16
+ * - an output handler can be set to treat produced output controllers (like 'bargraph')
17
+ * - regular controllers are handled using setParamValue/getParamValue
18
+ */
19
+ export interface IFaustBaseWebAudioDsp {
20
+ /**
21
+ * Set the parameter output handler, to be called in the 'compute' method with output parameters (like bargraph).
22
+ *
23
+ * @param handler - the output handler
24
+ */
25
+ setOutputParamHandler(handler: OutputParamHandler | null): void;
26
+
27
+ /**
28
+ * Get the parameter output handler.
29
+ *
30
+ * @return the current output handler
31
+ */
32
+ getOutputParamHandler(): OutputParamHandler | null;
33
+
34
+ /**
35
+ * Set the compute handler, to be called in the 'compute' method with buffer size.
36
+ *
37
+ * @param handler - the compute handler
38
+ */
39
+ setComputeHandler(handler: ComputeHandler | null): void;
40
+
41
+ /**
42
+ * Get the compute handler.
43
+ *
44
+ * @return the current output handler
45
+ */
46
+ getComputeHandler(): ComputeHandler | null;
47
+
48
+ /**
49
+ * Set the plot handler, to be called in the 'compute' method with various info (see PlotHandler type).
50
+ *
51
+ * @param handler - the plot handler
52
+ */
53
+ setPlotHandler(handler: PlotHandler | null): void;
54
+
55
+ /**
56
+ * Get the plot handler.
57
+ *
58
+ * @return the current plot handler
59
+ */
60
+ getPlotHandler(): PlotHandler | null;
61
+
62
+ /**
63
+ * Return instance number of audio inputs.
64
+ *
65
+ * @return the instance number of audio inputs
66
+ */
67
+ getNumInputs(): number;
68
+
69
+ /**
70
+ * Return instance number of audio outputs.
71
+ *
72
+ * @return the instance number of audio outputs
73
+ */
74
+ getNumOutputs(): number;
75
+
76
+ /**
77
+ * DSP instance computation, to be called with successive input/output audio buffers, using their size.
78
+ *
79
+ * @param inputs - the input audio buffers
80
+ * @param outputs - the output audio buffers
81
+ */
82
+ compute(inputs: Float32Array[], outputs: Float32Array[]): boolean;
83
+
84
+ /**
85
+ * Give a handler to be called on 'declare key value' kind of metadata.
86
+ *
87
+ * @param handler - the handler to be used
88
+ */
89
+ metadata(handler: MetadataHandler): void;
90
+
91
+ /**
92
+ * Handle untyped MIDI messages.
93
+ *
94
+ * @param data - and arry of MIDI bytes
95
+ */
96
+ midiMessage(data: number[] | Uint8Array): void;
97
+
98
+ /**
99
+ * Handle MIDI ctrlChange messages.
100
+ *
101
+ * @param channel - the MIDI channel (0..15, not used for now)
102
+ * @param ctrl - the MIDI controller number (0..127)
103
+ * @param value - the MIDI controller value (0..127)
104
+ */
105
+ ctrlChange(chan: number, ctrl: number, value: number): void;
106
+
107
+ /**
108
+ * Handle MIDI pitchWheel messages.
109
+ *
110
+ * @param channel - the MIDI channel (0..15, not used for now)
111
+ * @param value - the MIDI controller value (0..16383)
112
+ */
113
+ pitchWheel(chan: number, value: number): void;
114
+
115
+ /**
116
+ * Set parameter value.
117
+ *
118
+ * @param path - the path to the wanted parameter (retrieved using 'getParams' method)
119
+ * @param val - the float value for the wanted control
120
+ */
121
+ setParamValue(path: string, value: number): void;
122
+
123
+ /**
124
+ * Get parameter value.
125
+ *
126
+ * @param path - the path to the wanted parameter (retrieved using 'getParams' method)
127
+ *
128
+ * @return the float value
129
+ */
130
+ getParamValue(path: string): number;
131
+
132
+ /**
133
+ * Get the table of all input parameters paths.
134
+ *
135
+ * @return the table of all input parameters paths
136
+ */
137
+ getParams(): string[];
138
+
139
+ /**
140
+ * Get DSP JSON description with its UI and metadata as object.
141
+ *
142
+ * @return the DSP JSON description as object
143
+ */
144
+ getMeta(): FaustDspMeta;
145
+
146
+ /**
147
+ * Get DSP JSON description with its UI and metadata.
148
+ *
149
+ * @return the DSP JSON description
150
+ */
151
+ getJSON(): string;
152
+
153
+ /**
154
+ * Get DSP UI description.
155
+ *
156
+ * @return the DSP UI description
157
+ */
158
+ getUI(): FaustUIDescriptor;
159
+
160
+ /**
161
+ * Get DSP UI items description.
162
+ *
163
+ * @return the DSP UI items description
164
+ */
165
+ getDescriptors(): FaustUIInputItem[];
166
+
167
+ /**
168
+ * Start the DSP.
169
+ */
170
+ start(): void;
171
+
172
+ /**
173
+ * Stop the DSP.
174
+ */
175
+ stop(): void;
176
+
177
+ /**
178
+ * Destroy the DSP.
179
+ */
180
+ destroy(): void;
181
+ }
182
+
183
+ export interface IFaustMonoWebAudioDsp extends IFaustBaseWebAudioDsp {}
184
+ export interface IFaustMonoWebAudioNode extends IFaustMonoWebAudioDsp, AudioNode {}
185
+
186
+ export interface IFaustPolyWebAudioDsp extends IFaustBaseWebAudioDsp {
187
+ /**
188
+ * Handle MIDI keyOn messages.
189
+ *
190
+ * @param channel - the MIDI channel (0..15, not used for now)
191
+ * @param pitch - the MIDI pitch value (0..127)
192
+ * @param velocity - the MIDI velocity value (0..127)
193
+ */
194
+ keyOn(channel: number, pitch: number, velocity: number): void;
195
+
196
+ /**
197
+ * Handle MIDI keyOff messages.
198
+ *
199
+ * @param channel - the MIDI channel (0..15, not used for now)
200
+ * @param pitch - the MIDI pitch value (0..127)
201
+ * @param velocity - the MIDI velocity value (0..127)
202
+ */
203
+ keyOff(channel: number, pitch: number, velocity: number): void;
204
+
205
+ /**
206
+ * Stop all playing notes.
207
+ *
208
+ * @param hard - whether to immediately stop notes or put them in release mode
209
+ */
210
+ allNotesOff(hard: boolean): void;
211
+ }
212
+ export interface IFaustPolyWebAudioNode extends IFaustPolyWebAudioDsp, AudioNode {}
213
+
214
+ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
215
+ protected fOutputHandler: OutputParamHandler | null;
216
+ protected fComputeHandler: ComputeHandler | null;
217
+
218
+ // To handle MIDI events plot
219
+ protected fPlotHandler: PlotHandler | null;
220
+ protected fCachedEvents: { type: string; data: any }[];
221
+ protected fBufferNum: number;
222
+
223
+ protected fInChannels: Float32Array[] | Float64Array[];
224
+ protected fOutChannels: Float32Array[] | Float64Array[];
225
+
226
+ protected fOutputsTimer: number;
227
+
228
+ // UI items path
229
+ protected fInputsItems: string[];
230
+ protected fOutputsItems: string[];
231
+ protected fDescriptor: FaustUIInputItem[];
232
+
233
+ // Buffers in wasm memory
234
+ protected fAudioInputs!: number;
235
+ protected fAudioOutputs!: number;
236
+
237
+ protected fBufferSize: number;
238
+ protected gPtrSize: number;
239
+ protected gSampleSize: number;
240
+
241
+ // MIDI handling
242
+ protected fPitchwheelLabel: { path: string; min: number; max: number }[];
243
+ protected fCtrlLabel: { path: string; min: number; max: number }[][];
244
+ protected fPathTable: { [address: string]: number };
245
+ protected fUICallback: UIHandler;
246
+
247
+ protected fProcessing: boolean;
248
+
249
+ protected fDestroyed: boolean;
250
+
251
+ protected fJSONDsp!: FaustDspMeta;
252
+
253
+ constructor(sampleSize: number, bufferSize: number) {
254
+ this.fOutputHandler = null;
255
+ this.fComputeHandler = null;
256
+
257
+ // To handle MIDI events plot
258
+ this.fCachedEvents = [];
259
+ this.fBufferNum = 0;
260
+ this.fPlotHandler = null;
261
+
262
+ this.fBufferSize = bufferSize;
263
+
264
+ this.fInChannels = [];
265
+ this.fOutChannels = [];
266
+
267
+ this.gPtrSize = sampleSize; // Done on wast/wasm backend side
268
+ this.gSampleSize = sampleSize;
269
+
270
+ this.fOutputsTimer = 5;
271
+ this.fInputsItems = [];
272
+ this.fOutputsItems = [];
273
+ this.fDescriptor = [];
274
+
275
+ this.fPitchwheelLabel = [];
276
+ this.fCtrlLabel = new Array(128).fill(null).map(() => []);
277
+ this.fPathTable = {};
278
+
279
+ this.fProcessing = false;
280
+ this.fDestroyed = false;
281
+
282
+ this.fUICallback = (item: FaustUIItem) => {
283
+ if (item.type === "hbargraph" || item.type === "vbargraph") {
284
+ // Keep bargraph adresses
285
+ this.fOutputsItems.push(item.address);
286
+ this.fPathTable[item.address] = item.index;
287
+ } else if (item.type === "vslider" || item.type === "hslider" || item.type === "button" || item.type === "checkbox" || item.type === "nentry") {
288
+ // Keep inputs adresses
289
+ this.fInputsItems.push(item.address);
290
+ this.fPathTable[item.address] = item.index;
291
+ this.fDescriptor.push(item);
292
+ // Parse 'midi' metadata
293
+ if (!item.meta) return;
294
+ item.meta.forEach((meta) => {
295
+ const { midi } = meta;
296
+ if (!midi) return;
297
+ const strMidi = midi.trim();
298
+ if (strMidi === "pitchwheel") {
299
+ this.fPitchwheelLabel.push({ path: item.address, min: item.min as number, max: item.max as number });
300
+ } else {
301
+ const matched = strMidi.match(/^ctrl\s(\d+)/);
302
+ if (!matched) return;
303
+ this.fCtrlLabel[parseInt(matched[1])].push({ path: item.address, min: item.min as number, max: item.max as number });
304
+ }
305
+ });
306
+ }
307
+ }
308
+ }
309
+
310
+ // Tools
311
+ static remap(v: number, mn0: number, mx0: number, mn1: number, mx1: number) {
312
+ return (v - mn0) / (mx0 - mn0) * (mx1 - mn1) + mn1;
313
+ }
314
+
315
+ // JSON parsing functions
316
+ static parseUI(ui: FaustUIDescriptor, callback: (item: FaustUIItem) => any) {
317
+ ui.forEach(group => this.parseGroup(group, callback));
318
+ }
319
+
320
+ static parseGroup(group: FaustUIGroup, callback: (item: FaustUIItem) => any) {
321
+ if (group.items) {
322
+ this.parseItems(group.items, callback);
323
+ }
324
+ }
325
+ static parseItems(items: FaustUIItem[], callback: (item: FaustUIItem) => any) {
326
+ items.forEach(item => this.parseItem(item, callback));
327
+ }
328
+
329
+ static parseItem(item: FaustUIItem, callback: (item: FaustUIItem) => any) {
330
+ if (item.type === "vgroup" || item.type === "hgroup" || item.type === "tgroup") {
331
+ this.parseItems(item.items, callback);
332
+ } else {
333
+ callback(item);
334
+ }
335
+ }
336
+
337
+ protected updateOutputs() {
338
+ if (this.fOutputsItems.length > 0 && this.fOutputHandler && this.fOutputsTimer-- === 0) {
339
+ this.fOutputsTimer = 5;
340
+ this.fOutputsItems.forEach(item => this.fOutputHandler?.(item, this.getParamValue(item)));
341
+ }
342
+ }
343
+
344
+ // Public API
345
+ metadata(handler: MetadataHandler) {
346
+ if (this.fJSONDsp.meta) {
347
+ this.fJSONDsp.meta.forEach(meta => handler(Object.keys(meta)[0], meta[Object.keys(meta)[0]]));
348
+ }
349
+ }
350
+
351
+ compute(input: Float32Array[], output: Float32Array[]) {
352
+ return false;
353
+ }
354
+
355
+ setOutputParamHandler(handler: OutputParamHandler | null) {
356
+ this.fOutputHandler = handler;
357
+ }
358
+ getOutputParamHandler() {
359
+ return this.fOutputHandler;
360
+ }
361
+
362
+ setComputeHandler(handler: ComputeHandler | null) {
363
+ this.fComputeHandler = handler;
364
+ }
365
+ getComputeHandler() {
366
+ return this.fComputeHandler;
367
+ }
368
+
369
+ setPlotHandler(handler: PlotHandler | null) {
370
+ this.fPlotHandler = handler;
371
+ }
372
+ getPlotHandler() {
373
+ return this.fPlotHandler;
374
+ }
375
+
376
+ getNumInputs() {
377
+ return -1;
378
+ }
379
+ getNumOutputs() {
380
+ return -1;
381
+ }
382
+
383
+ midiMessage(data: number[] | Uint8Array) {
384
+ if (this.fPlotHandler) this.fCachedEvents.push({ data, type: "midi" });
385
+ const cmd = data[0] >> 4;
386
+ const channel = data[0] & 0xf;
387
+ const data1 = data[1];
388
+ const data2 = data[2];
389
+ if (cmd === 11) return this.ctrlChange(channel, data1, data2);
390
+ if (cmd === 14) return this.pitchWheel(channel, (data2 * 128.0 + data1));
391
+ }
392
+
393
+ ctrlChange(channel: number, ctrl: number, value: number) {
394
+ if (this.fPlotHandler) this.fCachedEvents.push({ type: "ctrlChange", data: [channel, ctrl, value] });
395
+ if (this.fCtrlLabel[ctrl].length) {
396
+ this.fCtrlLabel[ctrl].forEach((ctrl) => {
397
+ const { path } = ctrl;
398
+ this.setParamValue(path, FaustBaseWebAudioDsp.remap(value, 0, 127, ctrl.min, ctrl.max));
399
+ // Typically used to reflect parameter change on GUI
400
+ if (this.fOutputHandler) this.fOutputHandler(path, this.getParamValue(path));
401
+ });
402
+ }
403
+ }
404
+
405
+ pitchWheel(channel: number, wheel: number) {
406
+ if (this.fPlotHandler) this.fCachedEvents.push({ type: "pitchWheel", data: [channel, wheel] });
407
+ this.fPitchwheelLabel.forEach((pw) => {
408
+ this.setParamValue(pw.path, FaustBaseWebAudioDsp.remap(wheel, 0, 16383, pw.min, pw.max));
409
+ // Typically used to reflect parameter change on GUI
410
+ if (this.fOutputHandler) this.fOutputHandler(pw.path, this.getParamValue(pw.path));
411
+ });
412
+ }
413
+
414
+ setParamValue(path: string, value: number) { }
415
+ getParamValue(path: string) { return 0; }
416
+
417
+ getParams() { return this.fInputsItems; }
418
+ getMeta() { return this.fJSONDsp; }
419
+ getJSON() { return JSON.stringify(this.getMeta()); }
420
+ getUI() { return this.fJSONDsp.ui; }
421
+ getDescriptors() { return this.fDescriptor; }
422
+
423
+ start() {
424
+ this.fProcessing = true;
425
+ }
426
+
427
+ stop() {
428
+ this.fProcessing = false;
429
+ }
430
+
431
+ destroy() {
432
+ this.fDestroyed = true;
433
+ this.fOutputHandler = null;
434
+ this.fComputeHandler = null;
435
+ this.fPlotHandler = null;
436
+ }
437
+ }
438
+
439
+ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaustMonoWebAudioDsp {
440
+
441
+ private fInstance: FaustMonoDspInstance;
442
+ private fDSP!: number;
443
+
444
+ constructor(instance: FaustMonoDspInstance, sampleRate: number, sampleSize: number, bufferSize: number) {
445
+
446
+ super(sampleSize, bufferSize);
447
+ this.fInstance = instance;
448
+
449
+ // Create JSON object
450
+ this.fJSONDsp = JSON.parse(this.fInstance.json);
451
+
452
+ // Setup GUI
453
+ FaustBaseWebAudioDsp.parseUI(this.fJSONDsp.ui, this.fUICallback);
454
+
455
+ // Setup wasm memory
456
+ this.initMemory();
457
+
458
+ // Init DSP
459
+ this.fInstance.api.init(this.fDSP, sampleRate);
460
+ }
461
+
462
+ private initMemory() {
463
+
464
+ // Start of DSP memory: Mono DSP is placed first with index 0
465
+ this.fDSP = 0;
466
+
467
+ // Audio buffer start at the end of DSP
468
+ const $audio = this.fJSONDsp.size;
469
+
470
+ // Setup audio pointers offset
471
+ this.fAudioInputs = $audio;
472
+ this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.gPtrSize;
473
+
474
+ // Prepare wasm memory layout
475
+ const $audioInputs = this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize;
476
+ const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.gSampleSize;
477
+
478
+ const HEAP = this.fInstance.memory.buffer;
479
+ const HEAP32 = new Int32Array(HEAP);
480
+ const HEAPF = (this.gSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
481
+
482
+ if (this.getNumInputs() > 0) {
483
+ for (let chan = 0; chan < this.getNumInputs(); chan++) {
484
+ HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.gSampleSize * chan;
485
+ }
486
+ // Prepare Ins buffer tables
487
+ const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.gPtrSize) >> 2);
488
+ for (let chan = 0; chan < this.getNumInputs(); chan++) {
489
+ this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.gSampleSize), (dspInChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
490
+ }
491
+ }
492
+ if (this.getNumOutputs() > 0) {
493
+ for (let chan = 0; chan < this.getNumOutputs(); chan++) {
494
+ HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.gSampleSize * chan;
495
+ }
496
+ // Prepare Out buffer tables
497
+ const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize) >> 2);
498
+ for (let chan = 0; chan < this.getNumOutputs(); chan++) {
499
+ this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.gSampleSize), (dspOutChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
500
+ }
501
+ }
502
+ }
503
+
504
+ toString() {
505
+ return `============== Mono Memory layout ==============
506
+ this.fBufferSize: ${this.fBufferSize}
507
+ this.fJSONDsp.size: ${this.fJSONDsp.size}
508
+ this.fAudioInputs: ${this.fAudioInputs}
509
+ this.fAudioOutputs: ${this.fAudioOutputs}
510
+ this.fDSP: ${this.fDSP}`;
511
+ }
512
+
513
+ // Public API
514
+ compute(input: Float32Array[] | ((input: Float32Array[] | Float64Array[]) => any), output: Float32Array[] | ((output: Float32Array[] | Float64Array[]) => any)) {
515
+
516
+ // Check DSP state
517
+ if (this.fDestroyed) return false;
518
+
519
+ // Check Processing state: the node returns 'true' to stay in the graph, even if not processing
520
+ if (!this.fProcessing) return true;
521
+
522
+ if (typeof input === "function") {
523
+ // Call input callback to avoid array copy
524
+ input(this.fInChannels);
525
+ } else {
526
+ // Check inputs
527
+ if (this.getNumInputs() > 0 && (!input || !input[0] || input[0].length === 0)) {
528
+ // console.log("Process input error");
529
+ return true;
530
+ }
531
+
532
+ // Check outputs
533
+ if (this.getNumOutputs() > 0 && typeof output !== "function" && (!output || !output[0] || output[0].length === 0)) {
534
+ // console.log("Process output error");
535
+ return true;
536
+ }
537
+
538
+ // Copy inputs
539
+ if (input !== undefined) {
540
+ for (let chan = 0; chan < Math.min(this.getNumInputs(), input.length); chan++) {
541
+ const dspInput = this.fInChannels[chan];
542
+ dspInput.set(input[chan]);
543
+ }
544
+ }
545
+ }
546
+ // Possibly call an externally given callback (for instance to synchronize playing a MIDIFile...)
547
+ if (this.fComputeHandler) this.fComputeHandler(this.fBufferSize);
548
+
549
+ // Compute
550
+ this.fInstance.api.compute(this.fDSP, this.fBufferSize, this.fAudioInputs, this.fAudioOutputs);
551
+
552
+ // Update bargraph
553
+ this.updateOutputs();
554
+
555
+ let forPlot = this.fOutChannels;
556
+ if (typeof output === "function") {
557
+ // Call output callback to avoid array copy
558
+ output(this.fOutChannels);
559
+ } else {
560
+ // Copy outputs
561
+ for (let chan = 0; chan < Math.min(this.getNumOutputs(), output.length); chan++) {
562
+ const dspOutput = this.fOutChannels[chan];
563
+ output[chan].set(dspOutput);
564
+ }
565
+ forPlot = output;
566
+ }
567
+
568
+ // PlotHandler handling
569
+ if (this.fPlotHandler) {
570
+ this.fPlotHandler(forPlot, this.fBufferNum++, (this.fCachedEvents.length ? this.fCachedEvents : undefined));
571
+ this.fCachedEvents = [];
572
+ }
573
+
574
+ return true;
575
+ }
576
+
577
+ metadata(handler: MetadataHandler) { super.metadata(handler); }
578
+
579
+ getNumInputs() {
580
+ return this.fInstance.api.getNumInputs(this.fDSP);
581
+ }
582
+ getNumOutputs() {
583
+ return this.fInstance.api.getNumOutputs(this.fDSP);
584
+ }
585
+
586
+ setParamValue(path: string, value: number) {
587
+ if (this.fPlotHandler) this.fCachedEvents.push({ type: "param", data: { path, value } });
588
+ this.fInstance.api.setParamValue(this.fDSP, this.fPathTable[path], value);
589
+ }
590
+ getParamValue(path: string) {
591
+ return this.fInstance.api.getParamValue(this.fDSP, this.fPathTable[path]);
592
+ }
593
+
594
+ getMeta() { return this.fJSONDsp; }
595
+ getJSON() { return this.fInstance.json; }
596
+ getDescriptors() { return this.fDescriptor; }
597
+ getUI() { return this.fJSONDsp.ui; }
598
+ }
599
+
600
+ export class FaustWebAudioDspVoice {
601
+
602
+ static kActiveVoice: number;
603
+ static kFreeVoice: number;
604
+ static kReleaseVoice: number;
605
+ static kLegatoVoice: number;
606
+ static kNoVoice: number;
607
+ static VOICE_STOP_LEVEL: number;
608
+ private fFreqLabel: number[];
609
+ private fGateLabel: number[];
610
+ private fGainLabel: number[];
611
+ private fKeyLabel: number[];
612
+ private fVelLabel: number[];
613
+ private fDSP: number; // Voice DSP location in wasm memory
614
+ private fAPI: IFaustDspInstance; // Voice DSP code
615
+ // Accessed by PolyDSPImp class
616
+ fCurNote: number;
617
+ fNextNote: number;
618
+ fNextVel: number;
619
+ fDate: number;
620
+ fLevel: number;
621
+ fRelease: number;
622
+
623
+ constructor($dsp: number, api: IFaustDspInstance, inputItems: string[], pathTable: { [address: string]: number }, sampleRate: number) {
624
+ // Voice state
625
+ FaustWebAudioDspVoice.kActiveVoice = 0;
626
+ FaustWebAudioDspVoice.kFreeVoice = -1;
627
+ FaustWebAudioDspVoice.kReleaseVoice = -2;
628
+ FaustWebAudioDspVoice.kLegatoVoice = -3;
629
+ FaustWebAudioDspVoice.kNoVoice = -4;
630
+ FaustWebAudioDspVoice.VOICE_STOP_LEVEL = 0.0005;
631
+
632
+ this.fCurNote = FaustWebAudioDspVoice.kFreeVoice;
633
+ this.fNextNote = this.fNextVel = -1;
634
+ this.fLevel = 0;
635
+ this.fDate = this.fRelease = 0;
636
+ this.fDSP = $dsp;
637
+ this.fAPI = api;
638
+ this.fGateLabel = [];
639
+ this.fGainLabel = [];
640
+ this.fFreqLabel = [];
641
+ this.fKeyLabel = [];
642
+ this.fVelLabel = [];
643
+ this.fAPI.init(this.fDSP, sampleRate);
644
+ this.extractPaths(inputItems, pathTable);
645
+ }
646
+
647
+ static midiToFreq(note: number) { return 440.0 * 2 ** ((note - 69) / 12); }
648
+
649
+ static normalizeVelocity(velocity: number) { return velocity / 127.0; }
650
+
651
+ private extractPaths(inputItems: string[], pathTable: { [address: string]: number }) {
652
+ inputItems.forEach((item) => {
653
+ if (item.endsWith("/gate")) {
654
+ this.fGateLabel.push(pathTable[item]);
655
+ } else if (item.endsWith("/freq")) {
656
+ this.fFreqLabel.push(pathTable[item]);
657
+ } else if (item.endsWith("/key")) {
658
+ this.fKeyLabel.push(pathTable[item]);
659
+ } else if (item.endsWith("/gain")) {
660
+ this.fGainLabel.push(pathTable[item]);
661
+ } else if (item.endsWith("/vel") && item.endsWith("/velocity")) {
662
+ this.fVelLabel.push(pathTable[item]);
663
+ }
664
+ });
665
+ }
666
+
667
+ // Public API
668
+ keyOn(pitch: number, velocity: number, legato: boolean = false) {
669
+ if (legato) {
670
+ this.fNextNote = pitch;
671
+ this.fNextVel = velocity;
672
+ } else {
673
+ this.fFreqLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, FaustWebAudioDspVoice.midiToFreq(pitch)));
674
+ this.fGateLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, 1));
675
+ this.fGainLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, FaustWebAudioDspVoice.normalizeVelocity(velocity)));
676
+ this.fKeyLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, pitch));
677
+ this.fVelLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, velocity));
678
+ // Keep pitch
679
+ this.fCurNote = pitch;
680
+ }
681
+ }
682
+
683
+ keyOff(hard: boolean = false) {
684
+ this.fGateLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, 0));
685
+ if (hard) {
686
+ this.fCurNote = FaustWebAudioDspVoice.kFreeVoice;
687
+ } else {
688
+ this.fRelease = this.fAPI.getSampleRate(this.fDSP) / 2;
689
+ this.fCurNote = FaustWebAudioDspVoice.kReleaseVoice;
690
+ }
691
+ }
692
+
693
+ computeLegato(bufferSize: number, $inputs: number, $outputZero: number, $outputsHalf: number) {
694
+
695
+ let size = bufferSize / 2;
696
+
697
+ // Reset envelops
698
+ this.fGateLabel.forEach(index => this.fAPI.setParamValue(this.fDSP, index, 0));
699
+
700
+ // Compute current voice on half buffer
701
+ this.fAPI.compute(this.fDSP, size, $inputs, $outputZero);
702
+
703
+ // Start next keyOn
704
+ this.keyOn(this.fNextNote, this.fNextVel);
705
+
706
+ // Compute on second half buffer
707
+ this.fAPI.compute(this.fDSP, size, $inputs, $outputsHalf);
708
+ }
709
+
710
+ compute(bufferSize: number, $inputs: number, $outputs: number) {
711
+ this.fAPI.compute(this.fDSP, bufferSize, $inputs, $outputs);
712
+ }
713
+
714
+ setParamValue(index: number, value: number) {
715
+ this.fAPI.setParamValue(this.fDSP, index, value);
716
+ }
717
+ getParamValue(index: number) {
718
+ return this.fAPI.getParamValue(this.fDSP, index);
719
+ }
720
+ }
721
+
722
+ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaustPolyWebAudioDsp {
723
+
724
+ private fInstance: FaustPolyDspInstance;
725
+ private fEffect!: number;
726
+ private fJSONEffect: FaustDspMeta | null;
727
+ private fAudioMixing!: number;
728
+ private fAudioMixingHalf!: number;
729
+ private fVoiceTable: FaustWebAudioDspVoice[];
730
+
731
+ constructor(instance: FaustPolyDspInstance, sampleRate: number, sampleSize: number, bufferSize: number) {
732
+ super(sampleSize, bufferSize);
733
+ this.fInstance = instance;
734
+
735
+ // Create JSON for voice
736
+ this.fJSONDsp = JSON.parse(this.fInstance.voiceJSON);
737
+
738
+ // Create JSON for effect
739
+ this.fJSONEffect = (this.fInstance.effectAPI && this.fInstance.effectJSON) ? JSON.parse(this.fInstance.effectJSON) : null;
740
+
741
+ // Setup GUI
742
+ FaustBaseWebAudioDsp.parseUI(this.fJSONDsp.ui, this.fUICallback);
743
+ if (this.fJSONEffect) FaustBaseWebAudioDsp.parseUI(this.fJSONEffect.ui, this.fUICallback);
744
+
745
+ // Setup wasm memory
746
+ this.initMemory();
747
+
748
+ // Init DSP voices
749
+ this.fVoiceTable = [];
750
+ for (let voice = 0; voice < this.fInstance.voices; voice++) {
751
+ this.fVoiceTable.push(new FaustWebAudioDspVoice(
752
+ this.fJSONDsp.size * voice,
753
+ this.fInstance.voiceAPI,
754
+ this.fInputsItems,
755
+ this.fPathTable,
756
+ sampleRate
757
+ ));
758
+ }
759
+
760
+ // Init effect
761
+ if (this.fInstance.effectAPI) this.fInstance.effectAPI.init(this.fEffect, sampleRate);
762
+ }
763
+
764
+ private initMemory() {
765
+
766
+ // Effet start at the end of all DSP voices
767
+ this.fEffect = this.fJSONDsp.size * this.fInstance.voices;
768
+
769
+ // Audio buffer start at the end of effect
770
+ const $audio = this.fEffect + (this.fJSONEffect ? this.fJSONEffect.size : 0);
771
+
772
+ // Setup audio pointers offset
773
+ this.fAudioInputs = $audio;
774
+ this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.gPtrSize;
775
+ this.fAudioMixing = this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize;
776
+ this.fAudioMixingHalf = this.fAudioMixing + this.getNumOutputs() * this.gPtrSize;
777
+
778
+ // Prepare wasm memory layout
779
+ const $audioInputs = this.fAudioMixingHalf + this.getNumOutputs() * this.gPtrSize;
780
+ const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.gSampleSize;
781
+ const $audioMixing = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.gSampleSize;
782
+
783
+ const HEAP = this.fInstance.memory.buffer;
784
+ const HEAP32 = new Int32Array(HEAP);
785
+ const HEAPF = (this.gSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
786
+
787
+ if (this.getNumInputs() > 0) {
788
+ for (let chan = 0; chan < this.getNumInputs(); chan++) {
789
+ HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.gSampleSize * chan;
790
+ }
791
+ // Prepare Ins buffer tables
792
+ const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.gPtrSize) >> 2);
793
+ for (let chan = 0; chan < this.getNumInputs(); chan++) {
794
+ this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.gSampleSize), (dspInChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
795
+ }
796
+ }
797
+ if (this.getNumOutputs() > 0) {
798
+ for (let chan = 0; chan < this.getNumOutputs(); chan++) {
799
+ HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.gSampleSize * chan;
800
+ HEAP32[(this.fAudioMixing >> 2) + chan] = $audioMixing + this.fBufferSize * this.gSampleSize * chan;
801
+ HEAP32[(this.fAudioMixingHalf >> 2) + chan] = $audioMixing + this.fBufferSize * this.gSampleSize * chan + this.fBufferSize / 2 * this.gSampleSize;
802
+ }
803
+ // Prepare Out buffer tables
804
+ const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize) >> 2);
805
+ for (let chan = 0; chan < this.getNumOutputs(); chan++) {
806
+ this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.gSampleSize), (dspOutChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
807
+ }
808
+ }
809
+ }
810
+
811
+ toString() {
812
+ return `============== Poly Memory layout ==============
813
+ this.fBufferSize: ${this.fBufferSize}
814
+ this.fJSONDsp.size: ${this.fJSONDsp.size}
815
+ this.fAudioInputs: ${this.fAudioInputs}
816
+ this.fAudioOutputs: ${this.fAudioOutputs}
817
+ this.fAudioMixing: ${this.fAudioMixing}
818
+ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
819
+ }
820
+
821
+ private allocVoice(voice: number, type: number) {
822
+ this.fVoiceTable[voice].fDate++;
823
+ this.fVoiceTable[voice].fCurNote = type;
824
+ return voice;
825
+ }
826
+
827
+ private getPlayingVoice(pitch: number) {
828
+ let voicePlaying = FaustWebAudioDspVoice.kNoVoice;
829
+ let oldestDatePlaying = Number.MAX_VALUE;
830
+
831
+ for (let voice = 0; voice < this.fInstance.voices; voice++) {
832
+ if (this.fVoiceTable[voice].fCurNote === pitch) {
833
+ // Keeps oldest playing voice
834
+ if (this.fVoiceTable[voice].fDate < oldestDatePlaying) {
835
+ oldestDatePlaying = this.fVoiceTable[voice].fDate;
836
+ voicePlaying = voice;
837
+ }
838
+ }
839
+ }
840
+ return voicePlaying;
841
+ }
842
+
843
+ private getFreeVoice() {
844
+ for (let voice = 0; voice < this.fInstance.voices; voice++) {
845
+ if (this.fVoiceTable[voice].fCurNote === FaustWebAudioDspVoice.kFreeVoice) {
846
+ return this.allocVoice(voice, FaustWebAudioDspVoice.kActiveVoice);
847
+ }
848
+ }
849
+
850
+ let voiceRelease = FaustWebAudioDspVoice.kNoVoice;
851
+ let voicePlaying = FaustWebAudioDspVoice.kNoVoice;
852
+ let oldestDateRelease = Number.MAX_VALUE;
853
+ let oldestDatePlaying = Number.MAX_VALUE;
854
+
855
+ for (let voice = 0; voice < this.fInstance.voices; voice++) { // Scan all voices
856
+ // Try to steal a voice in DspVoice.kReleaseVoice mode...
857
+ if (this.fVoiceTable[voice].fCurNote === FaustWebAudioDspVoice.kReleaseVoice) {
858
+ // Keeps oldest release voice
859
+ if (this.fVoiceTable[voice].fDate < oldestDateRelease) {
860
+ oldestDateRelease = this.fVoiceTable[voice].fDate;
861
+ voiceRelease = voice;
862
+ }
863
+ } else if (this.fVoiceTable[voice].fDate < oldestDatePlaying) {
864
+ oldestDatePlaying = this.fVoiceTable[voice].fDate;
865
+ voicePlaying = voice;
866
+ }
867
+ }
868
+ // Then decide which one to steal
869
+ if (oldestDateRelease !== Number.MAX_VALUE) {
870
+ console.log(`Steal release voice : voice_date = ${this.fVoiceTable[voiceRelease].fDate} voice = ${voiceRelease}`);
871
+ return this.allocVoice(voiceRelease, FaustWebAudioDspVoice.kLegatoVoice);
872
+ }
873
+ if (oldestDatePlaying !== Number.MAX_VALUE) {
874
+ console.log(`Steal playing voice : voice_date = ${this.fVoiceTable[voicePlaying].fDate} voice = ${voicePlaying}`);
875
+ return this.allocVoice(voicePlaying, FaustWebAudioDspVoice.kLegatoVoice);
876
+ }
877
+ return FaustWebAudioDspVoice.kNoVoice;
878
+ }
879
+
880
+ // Public API
881
+ compute(input: Float32Array[], output: Float32Array[]) {
882
+
883
+ // Check DSP state
884
+ if (this.fDestroyed) return false;
885
+
886
+ // Check Processing state: the node returns 'true' to stay in the graph, even if not processing
887
+ if (!this.fProcessing) return true;
888
+
889
+ // Check inputs
890
+ if (this.getNumInputs() > 0 && (!input || !input[0] || input[0].length === 0)) {
891
+ // console.log("Process input error");
892
+ return true;
893
+ }
894
+
895
+ // Check outputs
896
+ if (this.getNumOutputs() > 0 && (!output || !output[0] || output[0].length === 0)) {
897
+ // console.log("Process output error");
898
+ return true;
899
+ }
900
+
901
+ // Copy inputs
902
+ if (input !== undefined) {
903
+ for (let chan = 0; chan < Math.min(this.getNumInputs(), input.length); ++chan) {
904
+ const dspInput = this.fInChannels[chan];
905
+ dspInput.set(input[chan]);
906
+ }
907
+ }
908
+
909
+ // Possibly call an externally given callback (for instance to synchronize playing a MIDIFile...)
910
+ if (this.fComputeHandler) this.fComputeHandler(this.fBufferSize);
911
+
912
+ // Compute
913
+ this.fInstance.mixerAPI.clearOutput(this.fBufferSize, this.getNumOutputs(), this.fAudioOutputs);
914
+ this.fVoiceTable.forEach((voice) => {
915
+ if (voice.fCurNote === FaustWebAudioDspVoice.kLegatoVoice) {
916
+ // Play from current note and next note
917
+ voice.computeLegato(this.fBufferSize, this.fAudioInputs, this.fAudioMixing, this.fAudioMixingHalf);
918
+ // FadeOut on first half buffer
919
+ this.fInstance.mixerAPI.fadeOut(this.fBufferSize / 2, this.getNumOutputs(), this.fAudioMixing);
920
+ // Mix it in result
921
+ voice.fLevel = this.fInstance.mixerAPI.mixCheckVoice(this.fBufferSize, this.getNumOutputs(), this.fAudioMixing, this.fAudioOutputs);
922
+ } else if (voice.fCurNote !== FaustWebAudioDspVoice.kFreeVoice) {
923
+ // Compute current note
924
+ voice.compute(this.fBufferSize, this.fAudioInputs, this.fAudioMixing);
925
+ // Mix it in result
926
+ voice.fLevel = this.fInstance.mixerAPI.mixCheckVoice(this.fBufferSize, this.getNumOutputs(), this.fAudioMixing, this.fAudioOutputs);
927
+ // Check the level to possibly set the voice in kFreeVoice again
928
+ voice.fRelease -= this.fBufferSize;
929
+ if ((voice.fCurNote == FaustWebAudioDspVoice.kReleaseVoice) && ((voice.fLevel < FaustWebAudioDspVoice.VOICE_STOP_LEVEL) && (voice.fRelease < 0))) {
930
+ voice.fCurNote = FaustWebAudioDspVoice.kFreeVoice;
931
+ }
932
+ }
933
+ });
934
+ if (this.fInstance.effectAPI) this.fInstance.effectAPI.compute(this.fEffect, this.fBufferSize, this.fAudioOutputs, this.fAudioOutputs);
935
+
936
+ // Update bargraph
937
+ this.updateOutputs();
938
+
939
+ if (output !== undefined) {
940
+ // Copy outputs
941
+ for (let chan = 0; chan < Math.min(this.getNumOutputs(), output.length); chan++) {
942
+ const dspOutput = this.fOutChannels[chan];
943
+ output[chan].set(dspOutput);
944
+ }
945
+
946
+ // PlotHandler handling
947
+ if (this.fPlotHandler) {
948
+ this.fPlotHandler(output, this.fBufferNum++, (this.fCachedEvents.length ? this.fCachedEvents : undefined));
949
+ this.fCachedEvents = [];
950
+ }
951
+ }
952
+
953
+ return true;
954
+ }
955
+ getNumInputs() {
956
+ return this.fInstance.voiceAPI.getNumInputs(0);
957
+ }
958
+ getNumOutputs() {
959
+ return this.fInstance.voiceAPI.getNumOutputs(0);
960
+ }
961
+
962
+ private static findPath(o: any, p: string) {
963
+ if (typeof o !== "object") {
964
+ return false;
965
+ } else if (o.address) {
966
+ return (o.address === p);
967
+ } else {
968
+ for (const k in o) {
969
+ if (FaustPolyWebAudioDsp.findPath(o[k], p)) return true;
970
+ }
971
+ return false;
972
+ }
973
+ }
974
+
975
+ setParamValue(path: string, value: number) {
976
+ if (this.fPlotHandler) this.fCachedEvents.push({ type: "param", data: { path, value } });
977
+ if (this.fJSONEffect && FaustPolyWebAudioDsp.findPath(this.fJSONEffect.ui, path) && this.fInstance.effectAPI) {
978
+ this.fInstance.effectAPI.setParamValue(this.fEffect, this.fPathTable[path], value);
979
+ } else {
980
+ this.fVoiceTable.forEach(voice => voice.setParamValue(this.fPathTable[path], value));
981
+ }
982
+ }
983
+ getParamValue(path: string) {
984
+ if (this.fJSONEffect && FaustPolyWebAudioDsp.findPath(this.fJSONEffect.ui, path) && this.fInstance.effectAPI) {
985
+ return this.fInstance.effectAPI.getParamValue(this.fEffect, this.fPathTable[path]);
986
+ } else {
987
+ return this.fVoiceTable[0].getParamValue(this.fPathTable[path]);
988
+ }
989
+ }
990
+
991
+ getMeta() {
992
+ const o = this.fJSONDsp;
993
+ const e = this.fJSONEffect;
994
+ const r = { ...o };
995
+ if (e) {
996
+ r.ui = [{
997
+ type: "tgroup", label: "Sequencer", items: [
998
+ { type: "vgroup", label: "Instrument", items: o.ui },
999
+ { type: "vgroup", label: "Effect", items: e.ui }
1000
+ ]
1001
+ }];
1002
+ } else {
1003
+ r.ui = [{
1004
+ type: "tgroup", label: "Polyphonic", items: [
1005
+ { type: "vgroup", label: "Voices", items: o.ui }
1006
+ ]
1007
+ }];
1008
+ }
1009
+ return r as FaustDspMeta;
1010
+ }
1011
+
1012
+ getJSON() {
1013
+ return JSON.stringify(this.getMeta());
1014
+ }
1015
+
1016
+ getUI() {
1017
+ return this.getMeta().ui;
1018
+ }
1019
+
1020
+ getDescriptors() { return this.fDescriptor; }
1021
+
1022
+ midiMessage(data: number[] | Uint8Array) {
1023
+ const cmd = data[0] >> 4;
1024
+ const channel = data[0] & 0xf;
1025
+ const data1 = data[1];
1026
+ const data2 = data[2];
1027
+ if (cmd === 8 || (cmd === 9 && data2 === 0)) return this.keyOff(channel, data1, data2);
1028
+ else if (cmd === 9) return this.keyOn(channel, data1, data2);
1029
+ else super.midiMessage(data);
1030
+ };
1031
+
1032
+ ctrlChange(channel: number, ctrl: number, value: number) {
1033
+ if (ctrl === 123 || ctrl === 120) {
1034
+ this.allNotesOff(true);
1035
+ } else {
1036
+ super.ctrlChange(channel, ctrl, value);
1037
+ }
1038
+ }
1039
+
1040
+ keyOn(channel: number, pitch: number, velocity: number) {
1041
+ if (this.fPlotHandler) this.fCachedEvents.push({ type: "keyOn", data: [channel, pitch, velocity] });
1042
+ const voice = this.getFreeVoice();
1043
+ this.fVoiceTable[voice].keyOn(pitch, velocity, this.fVoiceTable[voice].fCurNote == FaustWebAudioDspVoice.kLegatoVoice);
1044
+ }
1045
+
1046
+ keyOff(channel: number, pitch: number, velocity: number) {
1047
+ if (this.fPlotHandler) this.fCachedEvents.push({ type: "keyOff", data: [channel, pitch, velocity] });
1048
+ const voice = this.getPlayingVoice(pitch);
1049
+ if (voice !== FaustWebAudioDspVoice.kNoVoice) {
1050
+ this.fVoiceTable[voice].keyOff();
1051
+ } else {
1052
+ console.log("Playing pitch = %d not found\n", pitch);
1053
+ }
1054
+ }
1055
+
1056
+ allNotesOff(hard: boolean = true) {
1057
+ this.fCachedEvents.push({ type: "ctrlChange", data: [0, 123, 0] });
1058
+ this.fVoiceTable.forEach(voice => voice.keyOff(hard));
1059
+ }
1060
+ }