@grame/faustwasm 0.0.48 → 0.0.50

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 CHANGED
@@ -40,20 +40,36 @@ You'll have to raise the package version number in `package.json` before `npm ru
40
40
  #### Generate WebAssembly version of a Faust DSP
41
41
 
42
42
  For example:
43
+
43
44
  ```bash
44
45
  rm -rf test/out # make sure you are under the faustwasm directory.
45
46
  node scripts/faust2wasm.js test/mono.dsp test/out
46
47
  ```
47
- or
48
+ will create a set of files: `mono.js`, `mono.wasm`, `mono.json`, `mono.html` and the `faustwasm` folder.
49
+
50
+ Polyphonic instrument with:
51
+
48
52
  ```bash
49
53
  rm -rf test/out # make sure you are under the faustwasm directory.
50
54
  node scripts/faust2wasm.js test/poly.dsp test/out -poly
51
55
  ```
52
- You can create standalone DSP on a web page using the same command line
56
+ will create a set of files: `poly.js`, `poly.wasm`, `poly.json`, `poly.html` (and possibly `poly_effect.wasm`, `poly_effect.json`) and the `faustwasm` folder.
57
+
58
+ You can create a standalone DSP on a web page using the same command line:
59
+
53
60
  ```bash
54
61
  rm -rf test/out # make sure you are under the faustwasm directory.
55
62
  node scripts/faust2wasm.js test/rev.dsp test/out -standalone
56
63
  ```
64
+ will create a set of files: `rev.js`, `rev.wasm`, `rev.json`, `rev.html`, and the `faustwasm`, `faust-ui` folders.
65
+
66
+ Or a standalone polyphonic DSP on a web page with:
67
+
68
+ ```bash
69
+ rm -rf test/out # make sure you are under the faustwasm directory.
70
+ node scripts/faust2wasm.js test/organ1.dsp test/out -standalone
71
+ ```
72
+ will create a set of files: `organ1.js`, `organ1.wasm`, `organ1.json`, `organ1_effect.wasm`, `organ1_effect.json`, `organ1.html`, and the `faustwasm`, `faust-ui` folders.
57
73
 
58
74
  #### Generate SVG Diagrams of a Faust DSP
59
75
 
@@ -223,7 +239,6 @@ The package is used in the following projects:
223
239
  - [Misc. services](#misc)
224
240
  - [Important note](#note)
225
241
 
226
-
227
242
  ### Organisation of the API <a name="org"></a>
228
243
 
229
244
  The API is organised from low to high level as illustrated by the figure below.
@@ -0,0 +1,214 @@
1
+ /**
2
+ * @typedef {import("./types").FaustDspDistribution} FaustDspDistribution
3
+ * @typedef {import("./faustwasm").FaustAudioWorkletNode} FaustAudioWorkletNode
4
+ * @typedef {import("./faustwasm").FaustDspMeta} FaustDspMeta
5
+ * @typedef {import("./faustwasm").FaustUIDescriptor} FaustUIDescriptor
6
+ * @typedef {import("./faustwasm").FaustUIGroup} FaustUIGroup
7
+ * @typedef {import("./faustwasm").FaustUIItem} FaustUIItem
8
+ */
9
+
10
+ /** @type {HTMLSpanElement} */
11
+ const $spanAudioInput = document.getElementById("audio-input");
12
+ /** @type {HTMLSpanElement} */
13
+ const $spanMidiInput = document.getElementById("midi-input");
14
+ /** @type {HTMLSelectElement} */
15
+ const $selectAudioInput = document.getElementById("select-audio-input");
16
+ /** @type {HTMLSelectElement} */
17
+ const $selectMidiInput = document.getElementById("select-midi-input");
18
+ /** @type {HTMLSelectElement} */
19
+ const $buttonDsp = document.getElementById("button-dsp");
20
+ /** @type {HTMLDivElement} */
21
+ const $divFaustUI = document.getElementById("div-faust-ui");
22
+
23
+ /** @type {typeof AudioContext} */
24
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
25
+ const audioContext = new AudioCtx();
26
+ audioContext.suspend();
27
+
28
+ /**
29
+ * @param {FaustAudioWorkletNode} faustNode
30
+ */
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
+ }
75
+ };
76
+
77
+ /**
78
+ * @param {FaustAudioWorkletNode} faustNode
79
+ */
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
+ };
107
+ };
108
+
109
+ $buttonDsp.disabled = true;
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
+ }
118
+ }
119
+
120
+ /**
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
+ * @param {string} dspName - The name of the DSP to be loaded.
125
+ * @param {number} voices - The number of voices to be used for polyphonic DSPs.
126
+ * @returns {Object} - An object containing the Faust audio node and the DSP metadata.
127
+ */
128
+ const createFaustNode = async (audioContext, dspName = "template", voices = 0) => {
129
+ // Import necessary Faust modules and data
130
+ const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
131
+
132
+ // Load DSP metadata from JSON
133
+ /** @type {FaustDspMeta} */
134
+ const dspMeta = await (await fetch(`./${dspName}.json`)).json();
135
+
136
+ // Compile the DSP module from WebAssembly binary data
137
+ const dspModule = await WebAssembly.compileStreaming(await fetch(`./${dspName}.wasm`));
138
+
139
+ // Create an object representing Faust DSP with metadata and module
140
+ /** @type {FaustDspDistribution} */
141
+ const faustDsp = { dspMeta, dspModule };
142
+
143
+ // Try to load optional mixer and effect modules
144
+ try {
145
+ faustDsp.mixerModule = await WebAssembly.compileStreaming(await fetch("./mixerModule.wasm"));
146
+ faustDsp.effectMeta = await (await fetch(`./${dspName}_effect.json`)).json();
147
+ faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch(`./${dspName}_effect.wasm`));
148
+ } catch (e) { }
149
+
150
+ /** @type {FaustAudioWorkletNode} */
151
+ let faustNode;
152
+
153
+ // Create either a polyphonic or monophonic Faust audio node based on the number of voices
154
+ if (voices > 0) {
155
+ const generator = new FaustPolyDspGenerator();
156
+ faustNode = await generator.createNode(
157
+ audioContext,
158
+ voices,
159
+ "FaustPolyDSP",
160
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) },
161
+ faustDsp.mixerModule,
162
+ faustDsp.effectModule ? { module: faustDsp.effectModule, json: JSON.stringify(faustDsp.effectMeta) } : undefined
163
+ );
164
+ } else {
165
+ const generator = new FaustMonoDspGenerator();
166
+ faustNode = await generator.createNode(
167
+ audioContext,
168
+ "FaustMonoDSP",
169
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta) }
170
+ );
171
+ }
172
+
173
+ // Return an object with the Faust audio node and the DSP metadata
174
+ return { faustNode, dspMeta };
175
+ }
176
+
177
+ /**
178
+ * @param {FaustAudioWorkletNode} faustNode
179
+ */
180
+ const createFaustUI = async (faustNode) => {
181
+ const { FaustUI } = await import("./faust-ui/index.js");
182
+ const $container = document.createElement("div");
183
+ $container.style.margin = "0";
184
+ $container.style.position = "absolute";
185
+ $container.style.overflow = "auto";
186
+ $container.style.display = "flex";
187
+ $container.style.flexDirection = "column";
188
+ $container.style.width = "100%";
189
+ $container.style.height = "100%";
190
+ $divFaustUI.appendChild($container);
191
+ const faustUI = new FaustUI({
192
+ ui: faustNode.getUI(),
193
+ root: $container,
194
+ listenWindowMessage: false,
195
+ listenWindowResize: true,
196
+ });
197
+ faustUI.paramChangeByUI = (path, value) => faustNode.setParamValue(path, value);
198
+ faustNode.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value));
199
+ $container.style.minWidth = `${faustUI.minWidth}px`;
200
+ $container.style.minHeight = `${faustUI.minHeight}px`;
201
+ faustUI.resize();
202
+ };
203
+
204
+ (async () => {
205
+ const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "FAUST_DSP", 16);
206
+ await createFaustUI(faustNode);
207
+ faustNode.connect(audioContext.destination);
208
+ if (faustNode.numberOfInputs) await buildAudioDeviceMenu(faustNode);
209
+ else $spanAudioInput.hidden = true;
210
+ if (navigator.requestMIDIAccess) await buildMidiDeviceMenu(faustNode);
211
+ else $spanMidiInput.hidden = true;
212
+ $buttonDsp.disabled = false;
213
+ document.title = name;
214
+ })();
@@ -1,5 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+
3
4
  <head>
4
5
  <meta charset="UTF-8">
5
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -17,32 +18,40 @@
17
18
  margin: 0;
18
19
  position: absolute;
19
20
  }
21
+
20
22
  main {
21
23
  margin: 20px;
22
24
  display: flex;
23
25
  flex-direction: column;
24
26
  flex: 1 1 100%;
25
27
  }
28
+
26
29
  main span {
27
30
  position: relative;
28
31
  display: flex;
29
32
  }
33
+
30
34
  main span[hidden] {
31
35
  display: none;
32
36
  }
37
+
33
38
  main span span {
34
39
  margin: 0px 10px;
35
40
  flex: 1;
36
41
  }
42
+
37
43
  main span select {
38
44
  flex: 2;
39
45
  }
46
+
40
47
  #dsp-switch {
41
48
  margin: 20px auto;
42
49
  }
50
+
43
51
  #button-dsp {
44
52
  padding: 10px;
45
53
  }
54
+
46
55
  #div-faust-ui {
47
56
  flex: 1 1 auto;
48
57
  display: flex;
@@ -52,6 +61,7 @@
52
61
  </style>
53
62
  <link rel="stylesheet" href="./faust-ui/index.css">
54
63
  </head>
64
+
55
65
  <body>
56
66
  <main>
57
67
  <span id="audio-input">
@@ -68,6 +78,7 @@
68
78
  <div id="div-faust-ui">
69
79
  </div>
70
80
  </main>
71
- <script src="./index.js"></script>
81
+ <script src="./FAUST_DSP.js"></script>
72
82
  </body>
83
+
73
84
  </html>
@@ -121,18 +121,20 @@ $buttonDsp.onclick = () => {
121
121
  * Creates a Faust audio node for use in the Web Audio API.
122
122
  *
123
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.
124
+ * @param {string} dspName - The name of the DSP to be loaded.
125
+ * @param {number} voices - The number of voices to be used for polyphonic DSPs.
126
+ * @returns {Object} - An object containing the Faust audio node and the DSP metadata.
125
127
  */
126
- const createFaustNode = async (audioContext) => {
128
+ const createFaustNode = async (audioContext, dspName = "template", voices = 0) => {
127
129
  // Import necessary Faust modules and data
128
130
  const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
129
131
 
130
132
  // Load DSP metadata from JSON
131
133
  /** @type {FaustDspMeta} */
132
- const dspMeta = await (await fetch("./dspMeta.json")).json();
134
+ const dspMeta = await (await fetch(`./${dspName}.json`)).json();
133
135
 
134
136
  // Compile the DSP module from WebAssembly binary data
135
- const dspModule = await WebAssembly.compileStreaming(await fetch("./dspModule.wasm"));
137
+ const dspModule = await WebAssembly.compileStreaming(await fetch(`./${dspName}.wasm`));
136
138
 
137
139
  // Create an object representing Faust DSP with metadata and module
138
140
  /** @type {FaustDspDistribution} */
@@ -141,18 +143,15 @@ const createFaustNode = async (audioContext) => {
141
143
  // Try to load optional mixer and effect modules
142
144
  try {
143
145
  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
+ faustDsp.effectMeta = await (await fetch(`./${dspName}_effect.json`)).json();
147
+ faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch(`./${dspName}_effect.wasm`));
146
148
  } catch (e) { }
147
149
 
148
- // Determine the number of voices based on the mixer module
149
- const voices = faustDsp.mixerModule ? 64 : 0;
150
-
151
150
  /** @type {FaustAudioWorkletNode} */
152
151
  let faustNode;
153
152
 
154
153
  // Create either a polyphonic or monophonic Faust audio node based on the number of voices
155
- if (voices) {
154
+ if (voices > 0) {
156
155
  const generator = new FaustPolyDspGenerator();
157
156
  faustNode = await generator.createNode(
158
157
  audioContext,
@@ -171,8 +170,8 @@ const createFaustNode = async (audioContext) => {
171
170
  );
172
171
  }
173
172
 
174
- // Return an object with the Faust audio node, the number of voices, and the DSP metadata
175
- return { faustNode, voices, dspMeta };
173
+ // Return an object with the Faust audio node and the DSP metadata
174
+ return { faustNode, dspMeta };
176
175
  }
177
176
 
178
177
  /**
@@ -203,12 +202,12 @@ const createFaustUI = async (faustNode) => {
203
202
  };
204
203
 
205
204
  (async () => {
206
- const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
205
+ const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "FAUST_DSP");
207
206
  await createFaustUI(faustNode);
208
207
  faustNode.connect(audioContext.destination);
209
208
  if (faustNode.numberOfInputs) await buildAudioDeviceMenu(faustNode);
210
209
  else $spanAudioInput.hidden = true;
211
- if (voices && navigator.requestMIDIAccess) await buildMidiDeviceMenu(faustNode);
210
+ if (navigator.requestMIDIAccess) await buildMidiDeviceMenu(faustNode);
212
211
  else $spanMidiInput.hidden = true;
213
212
  $buttonDsp.disabled = false;
214
213
  document.title = name;
@@ -11,7 +11,7 @@
11
11
  <center><button id="button-dsp">Activate DSP</button></center>
12
12
  </span>
13
13
  </main>
14
- <script src="./template.js"></script>
14
+ <script src="./FAUST_DSP.js"></script>
15
15
  <script>
16
16
  (async () => {
17
17
 
@@ -23,7 +23,7 @@
23
23
  // Create audio context activation button
24
24
  const $buttonDsp = document.getElementById("button-dsp");
25
25
 
26
- // Play some notes in loop
26
+ // Play some notes in loop
27
27
  function play(node) {
28
28
  node.keyOn(0, 60, 100);
29
29
  setTimeout(() => node.keyOn(0, 64, 100), 1000);
@@ -46,7 +46,7 @@
46
46
  }
47
47
 
48
48
  // Create Faust node
49
- const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
49
+ const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "FAUST_DSP", 16);
50
50
 
51
51
  // Connect Faust node to audio context
52
52
  faustNode.connect(audioContext.destination);
@@ -11,7 +11,7 @@
11
11
  <center><button id="button-dsp">Activate DSP</button></center>
12
12
  </span>
13
13
  </main>
14
- <script src="./template.js"></script>
14
+ <script src="./FAUST_DSP.js"></script>
15
15
  <script>
16
16
  (async () => {
17
17
 
@@ -36,7 +36,7 @@
36
36
  }
37
37
 
38
38
  // Create Faust node
39
- const { faustNode, voices, dspMeta: { name } } = await createFaustNode(audioContext);
39
+ const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "FAUST_DSP");
40
40
 
41
41
  // Connect Faust node to audio context
42
42
  faustNode.connect(audioContext.destination);
@@ -8,18 +8,20 @@
8
8
  * Creates a Faust audio node for use in the Web Audio API.
9
9
  *
10
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.
11
+ * @param {string} dspName - The name of the DSP to be loaded.
12
+ * @param {number} voices - The number of voices to be used for polyphonic DSPs.
13
+ * @returns {Object} - An object containing the Faust audio node and the DSP metadata.
12
14
  */
13
- const createFaustNode = async (audioContext) => {
15
+ const createFaustNode = async (audioContext, dspName = "template", voices = 0) => {
14
16
  // Import necessary Faust modules and data
15
17
  const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
16
18
 
17
19
  // Load DSP metadata from JSON
18
20
  /** @type {FaustDspMeta} */
19
- const dspMeta = await (await fetch("./dspMeta.json")).json();
21
+ const dspMeta = await (await fetch(`./${dspName}.json`)).json();
20
22
 
21
23
  // Compile the DSP module from WebAssembly binary data
22
- const dspModule = await WebAssembly.compileStreaming(await fetch("./dspModule.wasm"));
24
+ const dspModule = await WebAssembly.compileStreaming(await fetch(`./${dspName}.wasm`));
23
25
 
24
26
  // Create an object representing Faust DSP with metadata and module
25
27
  /** @type {FaustDspDistribution} */
@@ -28,18 +30,15 @@ const createFaustNode = async (audioContext) => {
28
30
  // Try to load optional mixer and effect modules
29
31
  try {
30
32
  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
+ faustDsp.effectMeta = await (await fetch(`./${dspName}_effect.json`)).json();
34
+ faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch(`./${dspName}_effect.wasm`));
33
35
  } catch (e) { }
34
36
 
35
- // Determine the number of voices based on the mixer module
36
- const voices = faustDsp.mixerModule ? 64 : 0;
37
-
38
37
  /** @type {FaustAudioWorkletNode} */
39
38
  let faustNode;
40
39
 
41
40
  // Create either a polyphonic or monophonic Faust audio node based on the number of voices
42
- if (voices) {
41
+ if (voices > 0) {
43
42
  const generator = new FaustPolyDspGenerator();
44
43
  faustNode = await generator.createNode(
45
44
  audioContext,
@@ -58,6 +57,6 @@ const createFaustNode = async (audioContext) => {
58
57
  );
59
58
  }
60
59
 
61
- // Return an object with the Faust audio node, the number of voices, and the DSP metadata
62
- return { faustNode, voices, dspMeta };
60
+ // Return an object with the Faust audio node and the DSP metadata
61
+ return { faustNode, dspMeta };
63
62
  }