@grame/faustwasm 0.0.45 → 0.0.46

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.
@@ -29,170 +29,187 @@ audioContext.suspend();
29
29
  * @param {FaustAudioWorkletNode} faustNode
30
30
  */
31
31
  const buildAudioDeviceMenu = async (faustNode) => {
32
- /** @type {MediaStreamAudioSourceNode} */
33
- let inputStreamNode;
34
- const handleDeviceChange = async () => {
35
- const devicesInfo = await navigator.mediaDevices.enumerateDevices();
36
- $selectAudioInput.innerHTML = '';
37
- devicesInfo.forEach((deviceInfo, i) => {
38
- const { kind, deviceId, label } = deviceInfo;
39
- if (kind === "audioinput") {
40
- const option = new Option(label || `microphone ${i + 1}`, deviceId);
41
- $selectAudioInput.add(option);
42
- }
43
- });
44
- }
45
- await handleDeviceChange();
46
- navigator.mediaDevices.addEventListener("devicechange", handleDeviceChange)
47
- $selectAudioInput.onchange = async () => {
48
- const id = $selectAudioInput.value;
49
- const constraints = {
50
- audio: {
51
- echoCancellation: false,
52
- mozNoiseSuppression: false,
53
- mozAutoGainControl: false,
54
- deviceId: id ? { exact: id } : undefined,
55
- },
56
- };
57
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
58
- if (inputStreamNode) inputStreamNode.disconnect();
59
- inputStreamNode = audioContext.createMediaStreamSource(stream);
60
- inputStreamNode.connect(faustNode);
61
- };
62
-
63
- const defaultConstraints = {
64
- audio: {
65
- echoCancellation: false,
66
- mozNoiseSuppression: false,
67
- mozAutoGainControl: false
68
- }
69
- };
70
- const defaultStream = await navigator.mediaDevices.getUserMedia(defaultConstraints);
71
- if (defaultStream) {
72
- inputStreamNode = audioContext.createMediaStreamSource(defaultStream);
73
- inputStreamNode.connect(faustNode);
74
- }
32
+ /** @type {MediaStreamAudioSourceNode} */
33
+ let inputStreamNode;
34
+ const handleDeviceChange = async () => {
35
+ const devicesInfo = await navigator.mediaDevices.enumerateDevices();
36
+ $selectAudioInput.innerHTML = '';
37
+ devicesInfo.forEach((deviceInfo, i) => {
38
+ const { kind, deviceId, label } = deviceInfo;
39
+ if (kind === "audioinput") {
40
+ const option = new Option(label || `microphone ${i + 1}`, deviceId);
41
+ $selectAudioInput.add(option);
42
+ }
43
+ });
44
+ }
45
+ await handleDeviceChange();
46
+ navigator.mediaDevices.addEventListener("devicechange", handleDeviceChange)
47
+ $selectAudioInput.onchange = async () => {
48
+ const id = $selectAudioInput.value;
49
+ const constraints = {
50
+ audio: {
51
+ echoCancellation: false,
52
+ mozNoiseSuppression: false,
53
+ mozAutoGainControl: false,
54
+ deviceId: id ? { exact: id } : undefined,
55
+ },
56
+ };
57
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
58
+ if (inputStreamNode) inputStreamNode.disconnect();
59
+ inputStreamNode = audioContext.createMediaStreamSource(stream);
60
+ inputStreamNode.connect(faustNode);
61
+ };
62
+
63
+ const defaultConstraints = {
64
+ audio: {
65
+ echoCancellation: false,
66
+ mozNoiseSuppression: false,
67
+ mozAutoGainControl: false
68
+ }
69
+ };
70
+ const defaultStream = await navigator.mediaDevices.getUserMedia(defaultConstraints);
71
+ if (defaultStream) {
72
+ inputStreamNode = audioContext.createMediaStreamSource(defaultStream);
73
+ inputStreamNode.connect(faustNode);
74
+ }
75
75
  };
76
76
 
77
77
  /**
78
78
  * @param {FaustAudioWorkletNode} faustNode
79
79
  */
80
80
  const buildMidiDeviceMenu = async (faustNode) => {
81
- const midiAccess = await navigator.requestMIDIAccess();
82
- /** @type {WebMidi.MIDIInput} */
83
- let currentInput;
84
- /**
85
- * @param {WebMidi.MIDIMessageEvent} e
86
- */
87
- const handleMidiMessage = e => faustNode.midiMessage(e.data);
88
- const handleStateChange = () => {
89
- const { inputs } = midiAccess;
90
- if ($selectMidiInput.options.length === inputs.size + 1) return;
91
- if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
92
- $selectMidiInput.innerHTML = '<option value="-1" disabled selected>Select...</option>';
93
- inputs.forEach((midiInput) => {
94
- const { name, id } = midiInput;
95
- const option = new Option(name, id);
96
- $selectMidiInput.add(option);
97
- });
98
- };
99
- handleStateChange();
100
- midiAccess.addEventListener("statechange", handleStateChange);
101
- $selectMidiInput.onchange = () => {
102
- if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
103
- const id = $selectMidiInput.value;
104
- currentInput = midiAccess.inputs.get(id);
105
- currentInput.addEventListener("midimessage", handleMidiMessage);
106
- };
81
+ const midiAccess = await navigator.requestMIDIAccess();
82
+ /** @type {WebMidi.MIDIInput} */
83
+ let currentInput;
84
+ /**
85
+ * @param {WebMidi.MIDIMessageEvent} e
86
+ */
87
+ const handleMidiMessage = e => faustNode.midiMessage(e.data);
88
+ const handleStateChange = () => {
89
+ const { inputs } = midiAccess;
90
+ if ($selectMidiInput.options.length === inputs.size + 1) return;
91
+ if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
92
+ $selectMidiInput.innerHTML = '<option value="-1" disabled selected>Select...</option>';
93
+ inputs.forEach((midiInput) => {
94
+ const { name, id } = midiInput;
95
+ const option = new Option(name, id);
96
+ $selectMidiInput.add(option);
97
+ });
98
+ };
99
+ handleStateChange();
100
+ midiAccess.addEventListener("statechange", handleStateChange);
101
+ $selectMidiInput.onchange = () => {
102
+ if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
103
+ const id = $selectMidiInput.value;
104
+ currentInput = midiAccess.inputs.get(id);
105
+ currentInput.addEventListener("midimessage", handleMidiMessage);
106
+ };
107
107
  };
108
108
 
109
109
  $buttonDsp.disabled = true;
110
110
  $buttonDsp.onclick = () => {
111
- if (audioContext.state === "running") {
112
- $buttonDsp.textContent = "Suspended";
113
- audioContext.suspend();
114
- } else if (audioContext.state === "suspended") {
115
- $buttonDsp.textContent = "Running";
116
- audioContext.resume();
117
- }
111
+ if (audioContext.state === "running") {
112
+ $buttonDsp.textContent = "Suspended";
113
+ audioContext.suspend();
114
+ } else if (audioContext.state === "suspended") {
115
+ $buttonDsp.textContent = "Running";
116
+ audioContext.resume();
117
+ }
118
118
  }
119
119
 
120
120
  /**
121
- * @param {AudioContext} audioContext
121
+ * Creates a Faust audio node for use in the Web Audio API.
122
+ *
123
+ * @param {AudioContext} audioContext - The Web Audio API AudioContext to which the Faust audio node will be connected.
124
+ * @returns {Object} - An object containing the Faust audio node, the number of voices (if polyphonic), and the DSP metadata.
122
125
  */
123
126
  const createFaustNode = async (audioContext) => {
124
- const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
125
-
126
- /** @type {FaustDspMeta} */
127
- const dspMeta = await (await fetch("./dspMeta.json")).json();
128
- const dspModule = await WebAssembly.compileStreaming(await fetch("./dspModule.wasm"));
129
- /** @type {FaustDspDistribution} */
130
- const faustDsp = { dspMeta, dspModule };
131
- try {
132
- faustDsp.mixerModule = await WebAssembly.compileStreaming(await fetch("./mixerModule.wasm"));
133
- faustDsp.effectMeta = await (await fetch("./effectMeta.json")).json();
134
- faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch("./effectModule.wasm"));
135
- } catch (e) {}
136
- const voices = faustDsp.mixerModule ? 64 : 0;
137
-
138
- /** @type {FaustAudioWorkletNode} */
139
- let faustNode;
140
- if (voices) {
141
- const generator = new FaustPolyDspGenerator();
142
- faustNode = await generator.createNode(
143
- audioContext,
144
- voices,
145
- "FaustPolyDSP",
146
- { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) },
147
- faustDsp.mixerModule,
148
- faustDsp.effectModule ? { module: faustDsp.effectModule, json: JSON.stringify(faustDsp.effectMeta) } : undefined
149
- );
150
- } else {
151
- const generator = new FaustMonoDspGenerator();
152
- faustNode = await generator.createNode(
153
- audioContext,
154
- "FaustMonoDSP",
155
- { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) }
156
- );
157
- }
158
- return { faustNode, voices, dspMeta };
127
+ // Import necessary Faust modules and data
128
+ const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
129
+
130
+ // Load DSP metadata from JSON
131
+ /** @type {FaustDspMeta} */
132
+ const dspMeta = await (await fetch("./dspMeta.json")).json();
133
+
134
+ // Compile the DSP module from WebAssembly binary data
135
+ const dspModule = await WebAssembly.compileStreaming(await fetch("./dspModule.wasm"));
136
+
137
+ // Create an object representing Faust DSP with metadata and module
138
+ /** @type {FaustDspDistribution} */
139
+ const faustDsp = { dspMeta, dspModule };
140
+
141
+ // Try to load optional mixer and effect modules
142
+ try {
143
+ faustDsp.mixerModule = await WebAssembly.compileStreaming(await fetch("./mixerModule.wasm"));
144
+ faustDsp.effectMeta = await (await fetch("./effectMeta.json")).json();
145
+ faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch("./effectModule.wasm"));
146
+ } catch (e) { }
147
+
148
+ // Determine the number of voices based on the mixer module
149
+ const voices = faustDsp.mixerModule ? 64 : 0;
150
+
151
+ /** @type {FaustAudioWorkletNode} */
152
+ let faustNode;
153
+
154
+ // Create either a polyphonic or monophonic Faust audio node based on the number of voices
155
+ if (voices) {
156
+ const generator = new FaustPolyDspGenerator();
157
+ faustNode = await generator.createNode(
158
+ audioContext,
159
+ voices,
160
+ "FaustPolyDSP",
161
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) },
162
+ faustDsp.mixerModule,
163
+ faustDsp.effectModule ? { module: faustDsp.effectModule, json: JSON.stringify(faustDsp.effectMeta) } : undefined
164
+ );
165
+ } else {
166
+ const generator = new FaustMonoDspGenerator();
167
+ faustNode = await generator.createNode(
168
+ audioContext,
169
+ "FaustMonoDSP",
170
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) }
171
+ );
172
+ }
173
+
174
+ // Return an object with the Faust audio node, the number of voices, and the DSP metadata
175
+ return { faustNode, voices, dspMeta };
159
176
  }
160
177
 
161
178
  /**
162
179
  * @param {FaustAudioWorkletNode} faustNode
163
180
  */
164
181
  const createFaustUI = async (faustNode) => {
165
- const { FaustUI } = await import("./faust-ui/index.js");
166
- const $container = document.createElement("div");
167
- $container.style.margin = "0";
168
- $container.style.position = "absolute";
169
- $container.style.overflow = "auto";
170
- $container.style.display = "flex";
171
- $container.style.flexDirection = "column";
172
- $container.style.width = "100%";
173
- $container.style.height = "100%";
174
- $divFaustUI.appendChild($container);
175
- const faustUI = new FaustUI({
176
- ui: faustNode.getUI(),
177
- root: $container,
178
- listenWindowMessage: false,
179
- listenWindowResize: true,
180
- });
181
- faustUI.paramChangeByUI = (path, value) => faustNode.setParamValue(path, value);
182
- faustNode.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value));
183
- $container.style.minWidth = `${faustUI.minWidth}px`;
184
- $container.style.minHeight = `${faustUI.minHeight}px`;
185
- faustUI.resize();
182
+ const { FaustUI } = await import("./faust-ui/index.js");
183
+ const $container = document.createElement("div");
184
+ $container.style.margin = "0";
185
+ $container.style.position = "absolute";
186
+ $container.style.overflow = "auto";
187
+ $container.style.display = "flex";
188
+ $container.style.flexDirection = "column";
189
+ $container.style.width = "100%";
190
+ $container.style.height = "100%";
191
+ $divFaustUI.appendChild($container);
192
+ const faustUI = new FaustUI({
193
+ ui: faustNode.getUI(),
194
+ root: $container,
195
+ listenWindowMessage: false,
196
+ listenWindowResize: true,
197
+ });
198
+ faustUI.paramChangeByUI = (path, value) => faustNode.setParamValue(path, value);
199
+ faustNode.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value));
200
+ $container.style.minWidth = `${faustUI.minWidth}px`;
201
+ $container.style.minHeight = `${faustUI.minHeight}px`;
202
+ faustUI.resize();
186
203
  };
187
204
 
188
205
  (async () => {
189
- const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
190
- await createFaustUI(faustNode);
191
- faustNode.connect(audioContext.destination);
192
- if (faustNode.numberOfInputs) await buildAudioDeviceMenu(faustNode);
193
- else $spanAudioInput.hidden = true;
194
- if (voices && navigator.requestMIDIAccess) await buildMidiDeviceMenu(faustNode);
195
- else $spanMidiInput.hidden = true;
196
- $buttonDsp.disabled = false;
197
- document.title = name;
206
+ const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
207
+ await createFaustUI(faustNode);
208
+ faustNode.connect(audioContext.destination);
209
+ if (faustNode.numberOfInputs) await buildAudioDeviceMenu(faustNode);
210
+ else $spanAudioInput.hidden = true;
211
+ if (voices && navigator.requestMIDIAccess) await buildMidiDeviceMenu(faustNode);
212
+ else $spanMidiInput.hidden = true;
213
+ $buttonDsp.disabled = false;
214
+ document.title = name;
198
215
  })();
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <title>Faust DSP</title>
6
+ </head>
7
+
8
+ <body>
9
+ <main>
10
+ <span id="dsp-switch">
11
+ <center><button id="button-dsp">Activate DSP</button></center>
12
+ </span>
13
+ </main>
14
+ <script src="./template.js"></script>
15
+ <script>
16
+ (async () => {
17
+
18
+ // Create audio context
19
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
20
+ const audioContext = new AudioCtx();
21
+ audioContext.suspend();
22
+
23
+ // Create audio context activation button
24
+ const $buttonDsp = document.getElementById("button-dsp");
25
+
26
+ // Play some notes in loop
27
+ function play(node) {
28
+ node.keyOn(0, 60, 100);
29
+ setTimeout(() => node.keyOn(0, 64, 100), 1000);
30
+ setTimeout(() => node.keyOn(0, 67, 100), 2000);
31
+ setTimeout(() => node.allNotesOff(), 5000);
32
+ setTimeout(() => play(node), 7000);
33
+ }
34
+
35
+ // Activate audio context
36
+ $buttonDsp.disabled = true;
37
+ $buttonDsp.onclick = () => {
38
+ if (audioContext.state === "running") {
39
+ $buttonDsp.textContent = "Suspended";
40
+ audioContext.suspend();
41
+ } else if (audioContext.state === "suspended") {
42
+ $buttonDsp.textContent = "Running";
43
+ audioContext.resume();
44
+ play(faustNode);
45
+ }
46
+ }
47
+
48
+ // Create Faust node
49
+ const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
50
+
51
+ // Connect Faust node to audio context
52
+ faustNode.connect(audioContext.destination);
53
+
54
+ // Create Faust node activation button
55
+ $buttonDsp.disabled = false;
56
+ })();
57
+ </script>
58
+ </body>
59
+
60
+ </html>
@@ -0,0 +1,50 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <title>Faust DSP</title>
6
+ </head>
7
+
8
+ <body>
9
+ <main>
10
+ <span id="dsp-switch">
11
+ <center><button id="button-dsp">Activate DSP</button></center>
12
+ </span>
13
+ </main>
14
+ <script src="./template.js"></script>
15
+ <script>
16
+ (async () => {
17
+
18
+ // Create audio context
19
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
20
+ const audioContext = new AudioCtx();
21
+ audioContext.suspend();
22
+
23
+ // Create audio context activation button
24
+ const $buttonDsp = document.getElementById("button-dsp");
25
+
26
+ // Function to activate audio context
27
+ $buttonDsp.disabled = true;
28
+ $buttonDsp.onclick = () => {
29
+ if (audioContext.state === "running") {
30
+ $buttonDsp.textContent = "Suspended";
31
+ audioContext.suspend();
32
+ } else if (audioContext.state === "suspended") {
33
+ $buttonDsp.textContent = "Running";
34
+ audioContext.resume();
35
+ }
36
+ }
37
+
38
+ // Create Faust node
39
+ const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
40
+
41
+ // Connect Faust node to audio context
42
+ faustNode.connect(audioContext.destination);
43
+
44
+ // Create Faust node activation button
45
+ $buttonDsp.disabled = false;
46
+ })();
47
+ </script>
48
+ </body>
49
+
50
+ </html>
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @typedef {import("./types").FaustDspDistribution} FaustDspDistribution
3
+ * @typedef {import("./faustwasm").FaustAudioWorkletNode} FaustAudioWorkletNode
4
+ * @typedef {import("./faustwasm").FaustDspMeta} FaustDspMeta
5
+ */
6
+
7
+ /**
8
+ * Creates a Faust audio node for use in the Web Audio API.
9
+ *
10
+ * @param {AudioContext} audioContext - The Web Audio API AudioContext to which the Faust audio node will be connected.
11
+ * @returns {Object} - An object containing the Faust audio node, the number of voices (if polyphonic), and the DSP metadata.
12
+ */
13
+ const createFaustNode = async (audioContext) => {
14
+ // Import necessary Faust modules and data
15
+ const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
16
+
17
+ // Load DSP metadata from JSON
18
+ /** @type {FaustDspMeta} */
19
+ const dspMeta = await (await fetch("./dspMeta.json")).json();
20
+
21
+ // Compile the DSP module from WebAssembly binary data
22
+ const dspModule = await WebAssembly.compileStreaming(await fetch("./dspModule.wasm"));
23
+
24
+ // Create an object representing Faust DSP with metadata and module
25
+ /** @type {FaustDspDistribution} */
26
+ const faustDsp = { dspMeta, dspModule };
27
+
28
+ // Try to load optional mixer and effect modules
29
+ try {
30
+ faustDsp.mixerModule = await WebAssembly.compileStreaming(await fetch("./mixerModule.wasm"));
31
+ faustDsp.effectMeta = await (await fetch("./effectMeta.json")).json();
32
+ faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch("./effectModule.wasm"));
33
+ } catch (e) { }
34
+
35
+ // Determine the number of voices based on the mixer module
36
+ const voices = faustDsp.mixerModule ? 64 : 0;
37
+
38
+ /** @type {FaustAudioWorkletNode} */
39
+ let faustNode;
40
+
41
+ // Create either a polyphonic or monophonic Faust audio node based on the number of voices
42
+ if (voices) {
43
+ const generator = new FaustPolyDspGenerator();
44
+ faustNode = await generator.createNode(
45
+ audioContext,
46
+ voices,
47
+ "FaustPolyDSP",
48
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) },
49
+ faustDsp.mixerModule,
50
+ faustDsp.effectModule ? { module: faustDsp.effectModule, json: JSON.stringify(faustDsp.effectMeta) } : undefined
51
+ );
52
+ } else {
53
+ const generator = new FaustMonoDspGenerator();
54
+ faustNode = await generator.createNode(
55
+ audioContext,
56
+ "FaustMonoDSP",
57
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) }
58
+ );
59
+ }
60
+
61
+ // Return an object with the Faust audio node, the number of voices, and the DSP metadata
62
+ return { faustNode, voices, dspMeta };
63
+ }
@@ -1199,7 +1199,7 @@ export interface IFaustPolyDspGenerator {
1199
1199
  *
1200
1200
  * @param compiler - the Faust compiler
1201
1201
  * @param name - the DSP name
1202
- * @param dspCode - the DSP code ('dsp_code' can possibly contain an integrated effect)
1202
+ * @param dspCode - the DSP code ('dspCode' can possibly contain an integrated effect)
1203
1203
  * @param args - the compilation parameters
1204
1204
  * @param effectCode - optional effect DSP code
1205
1205
  * @returns the compiled factory or 'null' if failure
@@ -1215,7 +1215,7 @@ export interface IFaustPolyDspGenerator {
1215
1215
  * @param voices - the number of voices
1216
1216
  * @param name - AudioWorklet Processor name
1217
1217
  * @param voiceFactory - the Faust factory for voices, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
1218
- * @param mixerModule - the wasm Mixer module (loaded from 'mixer32.wasm' or 'mixer64.wasm' files)
1218
+ * @param mixerModule - the wasm Mixer module (loaded from 'mixer32.wasm' or 'mixer64.wasm' files located in the 'faustwasm' package)
1219
1219
  * @param effectFactory - the Faust factory for the effect, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
1220
1220
  * @param sp - whether to compile a ScriptProcessorNode or an AudioWorkletNode
1221
1221
  * @param bufferSize - the buffer size in frames to be used in ScriptProcessorNode only, since AudioWorkletNode always uses 128 frames