@grame/faust-web-component 0.3.9 → 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/README.md +2 -2
- package/dist/faust-web-component.js +154 -98
- package/index.html +7 -4
- package/package.json +2 -2
- package/src/faust-editor.ts +78 -25
- package/src/faust-widget.ts +63 -17
package/index.html
CHANGED
@@ -8,11 +8,14 @@
|
|
8
8
|
|
9
9
|
<faust-editor>
|
10
10
|
<!--
|
11
|
+
declare name "Vocal FOF MIDI";
|
12
|
+
declare description "MIDI-controllable FOF vocal synthesizer.";
|
13
|
+
declare license "MIT";
|
14
|
+
declare copyright "(c)Mike Olsen, CCRMA (Stanford University)";
|
15
|
+
|
11
16
|
import("stdfaust.lib");
|
12
|
-
|
13
|
-
|
14
|
-
gain = hslider("gain",1,0,1,0.01);
|
15
|
-
process = no.noise : fi.resonlp(ctFreq,q,gain);
|
17
|
+
|
18
|
+
process = pm.SFFormantModelFofSmooth_ui_MIDI <: _,_;
|
16
19
|
-->
|
17
20
|
</faust-editor>
|
18
21
|
<center>
|
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
|
+
"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.
|
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"
|
package/src/faust-editor.ts
CHANGED
@@ -1,13 +1,28 @@
|
|
1
|
-
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import {
|
8
|
-
|
9
|
-
|
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,30 +292,33 @@ 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
|
280
305
|
try {
|
281
306
|
// Compile Faust code to access JSON metadata
|
282
|
-
await default_generator.compile(compiler, "main", code, "")
|
307
|
+
await default_generator.compile(compiler, "main", code, "-ftz 2")
|
283
308
|
const json = default_generator.getMeta()
|
284
309
|
let { midi, nvoices } = extractMidiAndNvoices(json);
|
285
310
|
gmidi = midi;
|
286
311
|
gnvoices = nvoices;
|
287
312
|
|
288
|
-
// Build the generator
|
289
|
-
generator = nvoices > 0 ? get_poly_generator() :
|
313
|
+
// Build the generator (possibly reusing default_generator which is a FaustMonoDspGenerator)
|
314
|
+
generator = nvoices > 0 ? get_poly_generator() : default_generator;
|
290
315
|
await generator.compile(compiler, "main", code, "-ftz 2");
|
291
316
|
|
292
317
|
} catch (e: any) {
|
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
|
-
//
|
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()
|
@@ -350,15 +382,13 @@ export default class FaustEditor extends HTMLElement {
|
|
350
382
|
faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value)
|
351
383
|
node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value))
|
352
384
|
|
353
|
-
// Create SVG block diagram
|
354
|
-
setSVG(svgDiagrams.from("main", code, "")["process.svg"])
|
355
|
-
|
356
385
|
// Set editor size to fit UI size
|
357
386
|
editorEl.style.height = `${Math.max(125, faustUI.minHeight)}px`;
|
358
387
|
faustUIRoot.style.width = faustUI.minWidth * 1.25 + "px"
|
359
388
|
faustUIRoot.style.height = faustUI.minHeight * 1.25 + "px"
|
360
389
|
}
|
361
390
|
|
391
|
+
// Function to set SVG in the block diagram tab
|
362
392
|
const setSVG = (svgString: string) => {
|
363
393
|
faustDiagram.innerHTML = svgString
|
364
394
|
|
@@ -373,6 +403,8 @@ export default class FaustEditor extends HTMLElement {
|
|
373
403
|
}
|
374
404
|
|
375
405
|
let animPlot: number | undefined
|
406
|
+
|
407
|
+
// Function to render the scope
|
376
408
|
const drawScope = () => {
|
377
409
|
scope!.renderScope([{
|
378
410
|
analyser: analyser!,
|
@@ -382,11 +414,13 @@ export default class FaustEditor extends HTMLElement {
|
|
382
414
|
animPlot = requestAnimationFrame(drawScope)
|
383
415
|
}
|
384
416
|
|
417
|
+
// Function to render the spectrum
|
385
418
|
const drawSpectrum = () => {
|
386
419
|
spectrum!.renderSpectrum(analyser!)
|
387
420
|
animPlot = requestAnimationFrame(drawSpectrum)
|
388
421
|
}
|
389
422
|
|
423
|
+
// Function to switch between tabs
|
390
424
|
const openTab = (i: number) => {
|
391
425
|
for (const [j, tab] of tabButtons.entries()) {
|
392
426
|
if (i === j) {
|
@@ -397,7 +431,20 @@ export default class FaustEditor extends HTMLElement {
|
|
397
431
|
tabContents[j].classList.remove("active")
|
398
432
|
}
|
399
433
|
}
|
400
|
-
if (
|
434
|
+
// Check if the clicked tab is the "Block Diagram" tab (index 1)
|
435
|
+
if (i === 1) {
|
436
|
+
// Only set the SVG if it hasn't been set before (avoiding multiple loads)
|
437
|
+
if (faustDiagram.innerHTML.trim() === "") {
|
438
|
+
|
439
|
+
// Display a "Computing SVG..." message while the SVG is being generated
|
440
|
+
faustDiagram.innerHTML = "<p><center>Computing SVG...</center></p>";
|
441
|
+
|
442
|
+
// Use setTimeout to defer the SVG rendering to a separate task
|
443
|
+
setTimeout(() => {
|
444
|
+
setSVG(svgDiagrams.from("main", code, "")["process.svg"]);
|
445
|
+
}, 0);
|
446
|
+
}
|
447
|
+
} else if (i === 2) {
|
401
448
|
scope!.onResize()
|
402
449
|
if (animPlot !== undefined) cancelAnimationFrame(animPlot)
|
403
450
|
animPlot = requestAnimationFrame(drawScope)
|
@@ -411,22 +458,27 @@ export default class FaustEditor extends HTMLElement {
|
|
411
458
|
}
|
412
459
|
}
|
413
460
|
|
461
|
+
// Attach event listeners to tab buttons
|
414
462
|
for (const [i, tabButton] of tabButtons.entries()) {
|
415
463
|
tabButton.onclick = () => openTab(i)
|
416
464
|
}
|
417
465
|
|
466
|
+
// Event handler for the stop button
|
418
467
|
stopButton.onclick = () => {
|
419
468
|
if (node !== undefined) {
|
420
|
-
node.disconnect()
|
421
|
-
node.
|
422
|
-
node
|
423
|
-
|
469
|
+
node.disconnect();
|
470
|
+
node.stopSensors();
|
471
|
+
node.destroy();
|
472
|
+
node = undefined;
|
473
|
+
stopButton.disabled = true;
|
424
474
|
// TODO: Maybe disable controls in faust-ui tab.
|
425
475
|
}
|
426
476
|
}
|
427
477
|
|
478
|
+
// Audio input selector element
|
428
479
|
const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement
|
429
480
|
|
481
|
+
// Update the audio input device list
|
430
482
|
const updateInputDevices = (devices: MediaDeviceInfo[]) => {
|
431
483
|
if (audioInputSelector.disabled) return
|
432
484
|
while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove()
|
@@ -439,6 +491,7 @@ export default class FaustEditor extends HTMLElement {
|
|
439
491
|
}
|
440
492
|
deviceUpdateCallbacks.push(updateInputDevices)
|
441
493
|
|
494
|
+
// Connect the selected audio input device
|
442
495
|
const connectInput = async () => {
|
443
496
|
const deviceId = audioInputSelector.value
|
444
497
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })
|
package/src/faust-widget.ts
CHANGED
@@ -1,11 +1,29 @@
|
|
1
|
-
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import {
|
6
|
-
|
7
|
-
|
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,32 +144,37 @@ 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
|
124
|
-
|
125
|
-
|
150
|
+
|
151
|
+
// Compile Faust code to access JSON metadata
|
152
|
+
await default_generator.compile(compiler, "main", code, "-ftz 2")
|
126
153
|
const json = default_generator.getMeta()
|
127
154
|
let { midi, nvoices } = extractMidiAndNvoices(json);
|
128
155
|
gmidi = midi;
|
129
156
|
gnvoices = nvoices;
|
130
157
|
|
131
|
-
// Build the generator
|
132
|
-
|
158
|
+
// Build the generator (possibly reusing default_generator which is a FaustMonoDspGenerator)
|
159
|
+
// and generate UI
|
160
|
+
generator = nvoices > 0 ? get_poly_generator() : default_generator;
|
133
161
|
await generator.compile(compiler, "main", code, "-ftz 2");
|
134
162
|
const ui = generator.getUI();
|
135
163
|
|
164
|
+
// Generate Faust UI
|
136
165
|
faustUI = new FaustUI({ ui, root: faustUIRoot });
|
137
166
|
faustUIRoot.style.width = faustUI.minWidth * 1.25 + "px";
|
138
167
|
faustUIRoot.style.height = faustUI.minHeight * 1.25 + "px";
|
139
168
|
faustUI.resize();
|
140
169
|
}
|
141
170
|
|
171
|
+
// Function to start the Faust node and audio context
|
142
172
|
const start = async () => {
|
143
173
|
if (audioCtx.state === "suspended") {
|
144
174
|
await audioCtx.resume()
|
145
175
|
}
|
146
176
|
|
147
|
-
// Create an audio node from compiled Faust
|
177
|
+
// Create an audio node from compiled Faust if not already created
|
148
178
|
if (node === undefined) {
|
149
179
|
if (gnvoices > 0) {
|
150
180
|
node = (await (generator as FaustPolyDspGenerator).createNode(audioCtx, gnvoices))!
|
@@ -153,7 +183,10 @@ export default class FaustWidget extends HTMLElement {
|
|
153
183
|
}
|
154
184
|
}
|
155
185
|
|
156
|
-
//
|
186
|
+
// Start sensors if available
|
187
|
+
await node.startSensors();
|
188
|
+
|
189
|
+
// Access MIDI device if available
|
157
190
|
if (gmidi) {
|
158
191
|
accessMIDIDevice(midiInputCallback(node))
|
159
192
|
.then(() => {
|
@@ -164,9 +197,11 @@ export default class FaustWidget extends HTMLElement {
|
|
164
197
|
});
|
165
198
|
}
|
166
199
|
|
200
|
+
// Set up parameter handling for Faust UI
|
167
201
|
faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value)
|
168
202
|
node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value))
|
169
203
|
|
204
|
+
// Enable audio input if necessary
|
170
205
|
if (node.numberOfInputs > 0) {
|
171
206
|
audioInputSelector.disabled = false
|
172
207
|
updateInputDevices(await getInputDevices())
|
@@ -176,15 +211,19 @@ export default class FaustWidget extends HTMLElement {
|
|
176
211
|
audioInputSelector.innerHTML = "<option>Audio input</option>"
|
177
212
|
}
|
178
213
|
|
214
|
+
// Connect Faust node to the audio context destination
|
179
215
|
node.connect(audioCtx.destination)
|
180
216
|
powerButton.style.color = "#ffa500"
|
181
217
|
}
|
182
218
|
|
219
|
+
// Function to stop the Faust node
|
183
220
|
const stop = () => {
|
184
221
|
node?.disconnect()
|
222
|
+
node?.stopSensors();
|
185
223
|
powerButton.style.color = "#fff"
|
186
224
|
}
|
187
225
|
|
226
|
+
// Toggle the Faust node on/off
|
188
227
|
powerButton.onclick = () => {
|
189
228
|
if (on) {
|
190
229
|
stop()
|
@@ -194,8 +233,7 @@ export default class FaustWidget extends HTMLElement {
|
|
194
233
|
on = !on
|
195
234
|
}
|
196
235
|
|
197
|
-
|
198
|
-
|
236
|
+
// Function to update available audio input devices
|
199
237
|
const updateInputDevices = (devices: MediaDeviceInfo[]) => {
|
200
238
|
if (audioInputSelector.disabled) return
|
201
239
|
while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove()
|
@@ -208,6 +246,7 @@ export default class FaustWidget extends HTMLElement {
|
|
208
246
|
}
|
209
247
|
deviceUpdateCallbacks.push(updateInputDevices)
|
210
248
|
|
249
|
+
// Function to connect selected audio input device
|
211
250
|
const connectInput = async () => {
|
212
251
|
const deviceId = audioInputSelector.value
|
213
252
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })
|
@@ -247,9 +286,16 @@ export default class FaustWidget extends HTMLElement {
|
|
247
286
|
}
|
248
287
|
}
|
249
288
|
|
289
|
+
// Set input change handler
|
250
290
|
audioInputSelector.onchange = connectInput
|
251
291
|
|
252
|
-
setup
|
292
|
+
// Initial setup
|
293
|
+
setTimeout(() => {
|
294
|
+
// Display a "Compiling..." message while Faust is compiling
|
295
|
+
faustUIRoot.innerHTML = "<p><center>Compiling...</center></p>";
|
296
|
+
setup();
|
297
|
+
}, 0);
|
298
|
+
|
253
299
|
}
|
254
300
|
}
|
255
301
|
|