@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.
- package/README.md +181 -0
- package/assets/standalone/faust-ui/index.css +414 -0
- package/assets/standalone/faust-ui/index.css.map +1 -0
- package/assets/standalone/faust-ui/index.d.ts +1 -0
- package/assets/standalone/faust-ui/index.js +3478 -0
- package/assets/standalone/faust-ui/index.js.map +1 -0
- package/assets/standalone/faustwasm/index.d.ts +1252 -0
- package/assets/standalone/faustwasm/index.js +3772 -0
- package/assets/standalone/faustwasm/index.js.map +7 -0
- package/assets/standalone/index.html +73 -0
- package/assets/standalone/index.js +198 -0
- package/assets/standalone/types.d.ts +9 -0
- package/dist/cjs/index.d.ts +1252 -0
- package/dist/cjs/index.js +3752 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs-bundle/index.d.ts +1259 -0
- package/dist/cjs-bundle/index.js +11507 -0
- package/dist/cjs-bundle/index.js.map +7 -0
- package/dist/esm/index.d.ts +1252 -0
- package/dist/esm/index.js +3772 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm-bundle/index.d.ts +1259 -0
- package/dist/esm-bundle/index.js +11495 -0
- package/dist/esm-bundle/index.js.map +7 -0
- package/fileutils.js +40 -0
- package/libfaust-wasm/libfaust-wasm.d.cts +3 -0
- package/libfaust-wasm/libfaust-wasm.d.ts +3 -0
- package/libfaust-wasm/libfaust-wasm.data +0 -0
- package/libfaust-wasm/libfaust-wasm.data.d.ts +2 -0
- package/libfaust-wasm/libfaust-wasm.js +21 -0
- package/libfaust-wasm/libfaust-wasm.wasm +0 -0
- package/libfaust-wasm/libfaust-wasm.wasm.d.ts +2 -0
- package/library.md +91 -0
- package/package.json +43 -0
- package/postbuild-bundled.js +11 -0
- package/postbuild.js +63 -0
- package/prebuild-bundled.js +11 -0
- package/rsrc/overview.png +0 -0
- package/rsrc/overview.xlsx +0 -0
- package/scripts/faust2sndfile.js +43 -0
- package/scripts/faust2svg.js +17 -0
- package/scripts/faust2wasm.js +29 -0
- package/src/FaustAudioWorkletNode.ts +264 -0
- package/src/FaustAudioWorkletProcessor.ts +273 -0
- package/src/FaustCompiler.ts +252 -0
- package/src/FaustDspGenerator.ts +498 -0
- package/src/FaustDspInstance.ts +141 -0
- package/src/FaustFFTAudioWorkletProcessor.ts +554 -0
- package/src/FaustOfflineProcessor.ts +141 -0
- package/src/FaustScriptProcessorNode.ts +80 -0
- package/src/FaustSvgDiagrams.ts +37 -0
- package/src/FaustWasmInstantiator.ts +202 -0
- package/src/FaustWebAudioDsp.ts +1060 -0
- package/src/LibFaust.ts +57 -0
- package/src/WavDecoder.ts +207 -0
- package/src/WavEncoder.ts +204 -0
- package/src/copyWebStandaloneAssets.d.ts +3 -0
- package/src/copyWebStandaloneAssets.js +20 -0
- package/src/exports-bundle.ts +5 -0
- package/src/exports.ts +24 -0
- package/src/faust2svgFiles.d.ts +3 -0
- package/src/faust2svgFiles.js +43 -0
- package/src/faust2wasmFiles.d.ts +5 -0
- package/src/faust2wasmFiles.js +93 -0
- package/src/faust2wavFiles.d.ts +3 -0
- package/src/faust2wavFiles.js +58 -0
- package/src/index-bundle-iife.ts +7 -0
- package/src/index-bundle.ts +5 -0
- package/src/index.ts +5 -0
- package/src/instantiateFaustModule.ts +32 -0
- package/src/instantiateFaustModuleFromFile.ts +57 -0
- package/src/types.ts +234 -0
- package/test/clarinet.dsp +1 -0
- package/test/faustlive-wasm/faust-ui/index.css +414 -0
- package/test/faustlive-wasm/faust-ui/index.css.map +1 -0
- package/test/faustlive-wasm/faust-ui/index.d.ts +1 -0
- package/test/faustlive-wasm/faust-ui/index.js +3478 -0
- package/test/faustlive-wasm/faust-ui/index.js.map +1 -0
- package/test/faustlive-wasm/faustwasm/index.d.ts +1252 -0
- package/test/faustlive-wasm/faustwasm/index.js +3772 -0
- package/test/faustlive-wasm/faustwasm/index.js.map +7 -0
- package/test/faustlive-wasm/index.css +97 -0
- package/test/faustlive-wasm/index.html +130 -0
- package/test/faustlive-wasm/index.js +638 -0
- package/test/guitar.dsp +1 -0
- package/test/guitar1.dsp +2 -0
- package/test/libfaust-in-worklet/index.html +13 -0
- package/test/libfaust-in-worklet/index.js +40 -0
- package/test/mono.dsp +8 -0
- package/test/node/test.js +17 -0
- package/test/noise.dsp +3 -0
- package/test/organ.dsp +5 -0
- package/test/organ1.dsp +6 -0
- package/test/osc.dsp +4 -0
- package/test/osc2.dsp +5 -0
- package/test/osc3.dsp +4 -0
- package/test/p-dj.dsp +4 -0
- package/test/poly.dsp +13 -0
- package/test/rev.dsp +2 -0
- package/test/t1.dsp +1 -0
- package/test/web/fft.html +87 -0
- package/test/web/fft1.html +88 -0
- package/test/web/fft2.html +114 -0
- package/test/web/fftw.js +1907 -0
- package/test/web/index.html +12 -0
- package/test/web/index.js +235 -0
- package/test/web/kissfft.js +904 -0
- package/test/web/mono.html +46 -0
- package/test/web/poly-key.html +62 -0
- package/test/web/poly.html +59 -0
- 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
|
+
}
|