@grame/faust-web-component 0.4.0 → 0.4.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grame/faust-web-component",
3
3
  "description": "Web component embedding the Faust Compiler",
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "module": "dist/faust-web-component.js",
6
6
  "files": [
7
7
  "src/",
@@ -42,7 +42,7 @@
42
42
  "@codemirror/legacy-modes": "^6.3.3",
43
43
  "@fortawesome/fontawesome-svg-core": "^6.4.2",
44
44
  "@fortawesome/free-solid-svg-icons": "^6.4.2",
45
- "@grame/faustwasm": "^0.5.4",
45
+ "@grame/faustwasm": "^0.7.0",
46
46
  "@shren/faust-ui": "^1.1.13",
47
47
  "codemirror": "^6.0.1",
48
48
  "split.js": "^1.6.5"
@@ -1,13 +1,28 @@
1
- import { icon } from "@fortawesome/fontawesome-svg-core"
2
- import { FaustMonoDspGenerator, FaustPolyDspGenerator, IFaustMonoWebAudioNode } from "@grame/faustwasm"
3
- import { FaustUI } from "@shren/faust-ui"
4
- import faustCSS from "@shren/faust-ui/dist/esm/index.css?inline"
5
- import Split from "split.js"
6
- import { faustPromise, audioCtx, compiler, svgDiagrams, default_generator, get_mono_generator, get_poly_generator, getInputDevices, deviceUpdateCallbacks, accessMIDIDevice, midiInputCallback, extractMidiAndNvoices } from "./common"
7
- import { createEditor, setError, clearError } from "./editor"
8
- import { Scope } from "./scope"
9
- import faustSvg from "./faustText.svg"
10
-
1
+ // Import necessary libraries and modules
2
+ import { icon } from "@fortawesome/fontawesome-svg-core";
3
+ import { FaustMonoDspGenerator, FaustPolyDspGenerator, IFaustMonoWebAudioNode } from "@grame/faustwasm";
4
+ import { FaustUI } from "@shren/faust-ui";
5
+ import faustCSS from "@shren/faust-ui/dist/esm/index.css?inline";
6
+ import Split from "split.js";
7
+ import {
8
+ faustPromise,
9
+ audioCtx,
10
+ compiler,
11
+ svgDiagrams,
12
+ default_generator,
13
+ get_mono_generator,
14
+ get_poly_generator,
15
+ getInputDevices,
16
+ deviceUpdateCallbacks,
17
+ accessMIDIDevice,
18
+ midiInputCallback,
19
+ extractMidiAndNvoices
20
+ } from "./common";
21
+ import { createEditor, setError, clearError } from "./editor";
22
+ import { Scope } from "./scope";
23
+ import faustSvg from "./faustText.svg";
24
+
25
+ // Create a template for the component
11
26
  const template = document.createElement("template")
12
27
  template.innerHTML = `
13
28
  <div id="root">
@@ -212,16 +227,22 @@ template.innerHTML = `
212
227
  </style>
213
228
  `
214
229
 
230
+ // FaustEditor Web Component
215
231
  export default class FaustEditor extends HTMLElement {
216
232
  constructor() {
217
233
  super()
218
234
  }
219
235
 
220
236
  connectedCallback() {
237
+ // Initial setup when the component is attached to the DOM
221
238
  const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim()
222
239
  this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true))
223
240
 
241
+ // Set up links, buttons, and editor
224
242
  const ideLink = this.shadowRoot!.querySelector("#ide") as HTMLAnchorElement
243
+ const editorEl = this.shadowRoot!.querySelector("#editor") as HTMLDivElement
244
+ const editor = createEditor(editorEl, code)
245
+
225
246
  ideLink.onfocus = () => {
226
247
  // Open current contents of editor in IDE
227
248
  const urlParams = new URLSearchParams()
@@ -229,9 +250,6 @@ export default class FaustEditor extends HTMLElement {
229
250
  ideLink.href = `https://faustide.grame.fr/?${urlParams.toString()}`
230
251
  }
231
252
 
232
- const editorEl = this.shadowRoot!.querySelector("#editor") as HTMLDivElement
233
- const editor = createEditor(editorEl, code)
234
-
235
253
  const runButton = this.shadowRoot!.querySelector("#run") as HTMLButtonElement
236
254
  const stopButton = this.shadowRoot!.querySelector("#stop") as HTMLButtonElement
237
255
  const faustUIRoot = this.shadowRoot!.querySelector("#faust-ui") as HTMLDivElement
@@ -241,6 +259,7 @@ export default class FaustEditor extends HTMLElement {
241
259
  const tabButtons = [...this.shadowRoot!.querySelectorAll(".tab")] as HTMLButtonElement[]
242
260
  const tabContents = [...sidebarContent.querySelectorAll("div")] as HTMLDivElement[]
243
261
 
262
+ // Initialize split.js for resizable editor and sidebar
244
263
  const split = Split([editorEl, sidebar], {
245
264
  sizes: [100, 0],
246
265
  minSize: [0, 20],
@@ -251,8 +270,11 @@ export default class FaustEditor extends HTMLElement {
251
270
 
252
271
  faustPromise.then(() => runButton.disabled = false)
253
272
 
273
+ // Default sizes for sidebar
254
274
  const defaultSizes = [70, 30]
255
275
  let sidebarOpen = false
276
+
277
+ // Function to open the sidebar with predefined sizes
256
278
  const openSidebar = () => {
257
279
  if (!sidebarOpen) {
258
280
  split.setSizes(defaultSizes)
@@ -260,6 +282,7 @@ export default class FaustEditor extends HTMLElement {
260
282
  sidebarOpen = true
261
283
  }
262
284
 
285
+ // Variables for audio and visualization nodes
263
286
  let node: IFaustMonoWebAudioNode | undefined
264
287
  let input: MediaStreamAudioSourceNode | undefined
265
288
  let analyser: AnalyserNode | undefined
@@ -269,11 +292,13 @@ export default class FaustEditor extends HTMLElement {
269
292
  let gnvoices = -1
270
293
  let sourceNode: AudioBufferSourceNode = undefined;
271
294
 
295
+ // Event handler for the run button
272
296
  runButton.onclick = async () => {
273
297
  if (audioCtx.state === "suspended") {
274
298
  await audioCtx.resume()
275
299
  }
276
300
  await faustPromise
301
+
277
302
  // Compile Faust code
278
303
  const code = editor.state.doc.toString()
279
304
  let generator = null
@@ -293,6 +318,7 @@ export default class FaustEditor extends HTMLElement {
293
318
  setError(editor, e)
294
319
  return
295
320
  }
321
+
296
322
  // Clear any old errors
297
323
  clearError(editor)
298
324
 
@@ -303,6 +329,8 @@ export default class FaustEditor extends HTMLElement {
303
329
  } else {
304
330
  node = (await (generator as FaustMonoDspGenerator).createNode(audioCtx))!
305
331
  }
332
+
333
+ // Set up audio input if necessary
306
334
  if (node.numberOfInputs > 0) {
307
335
  audioInputSelector.disabled = false
308
336
  updateInputDevices(await getInputDevices())
@@ -317,7 +345,10 @@ export default class FaustEditor extends HTMLElement {
317
345
  tabButton.disabled = false
318
346
  }
319
347
 
320
- // Access MIDI device
348
+ // Start sensors if available
349
+ await node.startSensors();
350
+
351
+ // Access MIDI device if available
321
352
  if (gmidi) {
322
353
  accessMIDIDevice(midiInputCallback(node))
323
354
  .then(() => {
@@ -329,6 +360,7 @@ export default class FaustEditor extends HTMLElement {
329
360
  }
330
361
 
331
362
  openSidebar()
363
+
332
364
  // Clear old tab contents
333
365
  for (const tab of tabContents) {
334
366
  while (tab.lastChild) tab.lastChild.remove()
@@ -356,6 +388,7 @@ export default class FaustEditor extends HTMLElement {
356
388
  faustUIRoot.style.height = faustUI.minHeight * 1.25 + "px"
357
389
  }
358
390
 
391
+ // Function to set SVG in the block diagram tab
359
392
  const setSVG = (svgString: string) => {
360
393
  faustDiagram.innerHTML = svgString
361
394
 
@@ -371,6 +404,7 @@ export default class FaustEditor extends HTMLElement {
371
404
 
372
405
  let animPlot: number | undefined
373
406
 
407
+ // Function to render the scope
374
408
  const drawScope = () => {
375
409
  scope!.renderScope([{
376
410
  analyser: analyser!,
@@ -380,11 +414,13 @@ export default class FaustEditor extends HTMLElement {
380
414
  animPlot = requestAnimationFrame(drawScope)
381
415
  }
382
416
 
417
+ // Function to render the spectrum
383
418
  const drawSpectrum = () => {
384
419
  spectrum!.renderSpectrum(analyser!)
385
420
  animPlot = requestAnimationFrame(drawSpectrum)
386
421
  }
387
422
 
423
+ // Function to switch between tabs
388
424
  const openTab = (i: number) => {
389
425
  for (const [j, tab] of tabButtons.entries()) {
390
426
  if (i === j) {
@@ -422,22 +458,27 @@ export default class FaustEditor extends HTMLElement {
422
458
  }
423
459
  }
424
460
 
461
+ // Attach event listeners to tab buttons
425
462
  for (const [i, tabButton] of tabButtons.entries()) {
426
463
  tabButton.onclick = () => openTab(i)
427
464
  }
428
465
 
466
+ // Event handler for the stop button
429
467
  stopButton.onclick = () => {
430
468
  if (node !== undefined) {
431
- node.disconnect()
432
- node.destroy()
433
- node = undefined
434
- stopButton.disabled = true
469
+ node.disconnect();
470
+ node.stopSensors();
471
+ node.destroy();
472
+ node = undefined;
473
+ stopButton.disabled = true;
435
474
  // TODO: Maybe disable controls in faust-ui tab.
436
475
  }
437
476
  }
438
477
 
478
+ // Audio input selector element
439
479
  const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement
440
480
 
481
+ // Update the audio input device list
441
482
  const updateInputDevices = (devices: MediaDeviceInfo[]) => {
442
483
  if (audioInputSelector.disabled) return
443
484
  while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove()
@@ -450,6 +491,7 @@ export default class FaustEditor extends HTMLElement {
450
491
  }
451
492
  deviceUpdateCallbacks.push(updateInputDevices)
452
493
 
494
+ // Connect the selected audio input device
453
495
  const connectInput = async () => {
454
496
  const deviceId = audioInputSelector.value
455
497
  const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })
@@ -1,11 +1,29 @@
1
- import { icon } from "@fortawesome/fontawesome-svg-core"
2
- import faustCSS from "@shren/faust-ui/dist/esm/index.css?inline"
3
- import faustSvg from "./faustText.svg"
4
- import { FaustMonoDspGenerator, FaustPolyDspGenerator, IFaustMonoWebAudioNode } from "@grame/faustwasm"
5
- import { IFaustPolyWebAudioNode } from "@grame/faustwasm"
6
- import { FaustUI } from "@shren/faust-ui"
7
- import { faustPromise, audioCtx, get_mono_generator, get_poly_generator, compiler, getInputDevices, deviceUpdateCallbacks, accessMIDIDevice, midiInputCallback, extractMidiAndNvoices, default_generator } from "./common"
8
-
1
+ // Import necessary libraries and modules
2
+ import { icon } from "@fortawesome/fontawesome-svg-core";
3
+ import faustCSS from "@shren/faust-ui/dist/esm/index.css?inline";
4
+ import faustSvg from "./faustText.svg";
5
+ import {
6
+ FaustMonoDspGenerator,
7
+ FaustPolyDspGenerator,
8
+ IFaustMonoWebAudioNode,
9
+ IFaustPolyWebAudioNode,
10
+ } from "@grame/faustwasm";
11
+ import { FaustUI } from "@shren/faust-ui";
12
+ import {
13
+ faustPromise,
14
+ audioCtx,
15
+ get_mono_generator,
16
+ get_poly_generator,
17
+ compiler,
18
+ getInputDevices,
19
+ deviceUpdateCallbacks,
20
+ accessMIDIDevice,
21
+ midiInputCallback,
22
+ extractMidiAndNvoices,
23
+ default_generator,
24
+ } from "./common";
25
+
26
+ // Create a template for the FaustWidget component
9
27
  const template = document.createElement("template")
10
28
  template.innerHTML = `
11
29
  <div id="root">
@@ -96,20 +114,27 @@ template.innerHTML = `
96
114
  </style>
97
115
  `
98
116
 
117
+ // Define the FaustWidget web component
99
118
  export default class FaustWidget extends HTMLElement {
100
119
  constructor() {
101
120
  super()
102
121
  }
103
122
 
123
+ // Called when the component is connected to the DOM
104
124
  connectedCallback() {
125
+ // Extract the Faust code from the inner HTML
105
126
  const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim()
106
127
  this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true))
107
128
 
129
+ // Query and initialize various elements in the shadow DOM
108
130
  const powerButton = this.shadowRoot!.querySelector("#power") as HTMLButtonElement
109
131
  const faustUIRoot = this.shadowRoot!.querySelector("#faust-ui") as HTMLDivElement
132
+ const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement
110
133
 
134
+ // Enable the power button once Faust is ready
111
135
  faustPromise.then(() => powerButton.disabled = false)
112
136
 
137
+ // State variables
113
138
  let on = false
114
139
  let gmidi = false
115
140
  let gnvoices = -1
@@ -119,8 +144,10 @@ export default class FaustWidget extends HTMLElement {
119
144
  let generator: FaustMonoDspGenerator | FaustPolyDspGenerator
120
145
  let sourceNode: AudioBufferSourceNode = undefined;
121
146
 
147
+ // Function to setup the Faust environment
122
148
  const setup = async () => {
123
149
  await faustPromise
150
+
124
151
  // Compile Faust code to access JSON metadata
125
152
  await default_generator.compile(compiler, "main", code, "-ftz 2")
126
153
  const json = default_generator.getMeta()
@@ -134,18 +161,20 @@ export default class FaustWidget extends HTMLElement {
134
161
  await generator.compile(compiler, "main", code, "-ftz 2");
135
162
  const ui = generator.getUI();
136
163
 
164
+ // Generate Faust UI
137
165
  faustUI = new FaustUI({ ui, root: faustUIRoot });
138
166
  faustUIRoot.style.width = faustUI.minWidth * 1.25 + "px";
139
167
  faustUIRoot.style.height = faustUI.minHeight * 1.25 + "px";
140
168
  faustUI.resize();
141
169
  }
142
170
 
171
+ // Function to start the Faust node and audio context
143
172
  const start = async () => {
144
173
  if (audioCtx.state === "suspended") {
145
174
  await audioCtx.resume()
146
175
  }
147
176
 
148
- // Create an audio node from compiled Faust
177
+ // Create an audio node from compiled Faust if not already created
149
178
  if (node === undefined) {
150
179
  if (gnvoices > 0) {
151
180
  node = (await (generator as FaustPolyDspGenerator).createNode(audioCtx, gnvoices))!
@@ -154,7 +183,10 @@ export default class FaustWidget extends HTMLElement {
154
183
  }
155
184
  }
156
185
 
157
- // Access MIDI device
186
+ // Start sensors if available
187
+ await node.startSensors();
188
+
189
+ // Access MIDI device if available
158
190
  if (gmidi) {
159
191
  accessMIDIDevice(midiInputCallback(node))
160
192
  .then(() => {
@@ -165,9 +197,11 @@ export default class FaustWidget extends HTMLElement {
165
197
  });
166
198
  }
167
199
 
200
+ // Set up parameter handling for Faust UI
168
201
  faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value)
169
202
  node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value))
170
203
 
204
+ // Enable audio input if necessary
171
205
  if (node.numberOfInputs > 0) {
172
206
  audioInputSelector.disabled = false
173
207
  updateInputDevices(await getInputDevices())
@@ -177,15 +211,19 @@ export default class FaustWidget extends HTMLElement {
177
211
  audioInputSelector.innerHTML = "<option>Audio input</option>"
178
212
  }
179
213
 
214
+ // Connect Faust node to the audio context destination
180
215
  node.connect(audioCtx.destination)
181
216
  powerButton.style.color = "#ffa500"
182
217
  }
183
218
 
219
+ // Function to stop the Faust node
184
220
  const stop = () => {
185
221
  node?.disconnect()
222
+ node?.stopSensors();
186
223
  powerButton.style.color = "#fff"
187
224
  }
188
225
 
226
+ // Toggle the Faust node on/off
189
227
  powerButton.onclick = () => {
190
228
  if (on) {
191
229
  stop()
@@ -195,8 +233,7 @@ export default class FaustWidget extends HTMLElement {
195
233
  on = !on
196
234
  }
197
235
 
198
- const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement
199
-
236
+ // Function to update available audio input devices
200
237
  const updateInputDevices = (devices: MediaDeviceInfo[]) => {
201
238
  if (audioInputSelector.disabled) return
202
239
  while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove()
@@ -209,6 +246,7 @@ export default class FaustWidget extends HTMLElement {
209
246
  }
210
247
  deviceUpdateCallbacks.push(updateInputDevices)
211
248
 
249
+ // Function to connect selected audio input device
212
250
  const connectInput = async () => {
213
251
  const deviceId = audioInputSelector.value
214
252
  const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })
@@ -248,8 +286,10 @@ export default class FaustWidget extends HTMLElement {
248
286
  }
249
287
  }
250
288
 
289
+ // Set input change handler
251
290
  audioInputSelector.onchange = connectInput
252
291
 
292
+ // Initial setup
253
293
  setTimeout(() => {
254
294
  // Display a "Compiling..." message while Faust is compiling
255
295
  faustUIRoot.innerHTML = "<p><center>Compiling...</center></p>";