@grame/faustwasm 0.8.2 → 0.9.0

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.
Files changed (61) hide show
  1. package/assets/standalone/create-node.js +15 -8
  2. package/assets/standalone/faustwasm/index.d.ts +40 -0
  3. package/assets/standalone/faustwasm/index.js +109 -30
  4. package/assets/standalone/faustwasm/index.js.map +2 -2
  5. package/assets/standalone/index-pwa.js +1 -1
  6. package/assets/standalone/service-worker.js +58 -5
  7. package/dist/cjs/index.d.ts +40 -0
  8. package/dist/cjs/index.js +109 -30
  9. package/dist/cjs/index.js.map +2 -2
  10. package/dist/cjs-bundle/index.d.ts +40 -0
  11. package/dist/cjs-bundle/index.js +109 -30
  12. package/dist/cjs-bundle/index.js.map +2 -2
  13. package/dist/esm/index.d.ts +40 -0
  14. package/dist/esm/index.js +109 -30
  15. package/dist/esm/index.js.map +2 -2
  16. package/dist/esm-bundle/index.d.ts +40 -0
  17. package/dist/esm-bundle/index.js +109 -30
  18. package/dist/esm-bundle/index.js.map +2 -2
  19. package/package.json +1 -1
  20. package/src/FaustAudioWorkletNode.ts +10 -0
  21. package/src/FaustAudioWorkletProcessor.ts +16 -0
  22. package/src/FaustOfflineProcessor.ts +2 -0
  23. package/src/FaustScriptProcessorNode.ts +2 -0
  24. package/src/FaustWebAudioDsp.ts +88 -0
  25. package/src/copyWebStandaloneAssets.js +14 -0
  26. package/test/faustlive-wasm/faustwasm/index.d.ts +40 -0
  27. package/test/faustlive-wasm/faustwasm/index.js +109 -30
  28. package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
  29. package/test/midi/create-node.js +177 -0
  30. package/test/midi/dsp-meta.json +133 -0
  31. package/test/midi/dsp-module.wasm +0 -0
  32. package/test/midi/faust-ui/index.css +442 -0
  33. package/test/midi/faust-ui/index.css.map +1 -0
  34. package/test/midi/faust-ui/index.d.ts +1 -0
  35. package/test/midi/faust-ui/index.js +3729 -0
  36. package/test/midi/faust-ui/index.js.map +1 -0
  37. package/test/midi/faustwasm/index.d.ts +1680 -0
  38. package/test/midi/faustwasm/index.js +4884 -0
  39. package/test/midi/faustwasm/index.js.map +7 -0
  40. package/test/midi/icon.png +0 -0
  41. package/test/midi/index.html +76 -0
  42. package/test/midi/index.js +183 -0
  43. package/test/midi/manifest.json +17 -0
  44. package/test/midi/service-worker.js +165 -0
  45. package/test/midi.dsp +8 -1
  46. package/test/organ1/create-node.js +177 -0
  47. package/test/organ1/dsp-meta.json +126 -0
  48. package/test/organ1/dsp-module.wasm +0 -0
  49. package/test/organ1/faust-ui/index.css +442 -0
  50. package/test/organ1/faust-ui/index.css.map +1 -0
  51. package/test/organ1/faust-ui/index.d.ts +1 -0
  52. package/test/organ1/faust-ui/index.js +3729 -0
  53. package/test/organ1/faust-ui/index.js.map +1 -0
  54. package/test/organ1/faustwasm/index.d.ts +1656 -0
  55. package/test/organ1/faustwasm/index.js +4783 -0
  56. package/test/organ1/faustwasm/index.js.map +7 -0
  57. package/test/organ1/icon.png +0 -0
  58. package/test/organ1/index.html +76 -0
  59. package/test/organ1/index.js +203 -0
  60. package/test/organ1/manifest.json +17 -0
  61. package/test/organ1/service-worker.js +181 -0
Binary file
@@ -0,0 +1,76 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <link rel="manifest" href="./manifest.json">
5
+
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+ <title>Faust DSP</title>
11
+ <style>
12
+ body {
13
+ color: #b0b0b0;
14
+ background-color: #1f1f1f;
15
+ display: flex;
16
+ top: 0;
17
+ left: 0;
18
+ width: 100%;
19
+ height: 100%;
20
+ margin: 0;
21
+ position: absolute;
22
+ }
23
+
24
+ main {
25
+ margin: 20px;
26
+ display: flex;
27
+ flex-direction: column;
28
+ flex: 1 1 100%;
29
+ }
30
+
31
+ main span {
32
+ position: relative;
33
+ display: flex;
34
+ }
35
+
36
+ main span[hidden] {
37
+ display: none;
38
+ }
39
+
40
+ main span span {
41
+ margin: 0px 10px;
42
+ flex: 1;
43
+ }
44
+
45
+ main span select {
46
+ flex: 2;
47
+ }
48
+
49
+ #dsp-switch {
50
+ margin: 20px auto;
51
+ }
52
+
53
+ #button-dsp {
54
+ padding: 10px;
55
+ }
56
+
57
+ #div-faust-ui {
58
+ flex: 1 1 auto;
59
+ display: flex;
60
+ position: relative;
61
+ flex-direction: column;
62
+ color: black;
63
+ }
64
+ </style>
65
+ <link rel="stylesheet" href="./faust-ui/index.css">
66
+ </head>
67
+
68
+ <body>
69
+ <main>
70
+ <div id="div-faust-ui">
71
+ </div>
72
+ </main>
73
+ <script src="./index.js"></script>
74
+ </body>
75
+
76
+ </html>
@@ -0,0 +1,183 @@
1
+ // Set to > 0 if the DSP is polyphonic
2
+ const FAUST_DSP_VOICES = 0;
3
+
4
+ /**
5
+ * @typedef {import("./faustwasm").FaustAudioWorkletNode} FaustAudioWorkletNode
6
+ * @typedef {import("./faustwasm").FaustDspMeta} FaustDspMeta
7
+ * @typedef {import("./faustwasm").FaustUIDescriptor} FaustUIDescriptor
8
+ * @typedef {import("./faustwasm").FaustUIGroup} FaustUIGroup
9
+ * @typedef {import("./faustwasm").FaustUIItem} FaustUIItem
10
+ */
11
+
12
+ /**
13
+ * Registers the service worker.
14
+ */
15
+ if ("serviceWorker" in navigator) {
16
+ window.addEventListener("load", () => {
17
+ navigator.serviceWorker.register("./service-worker.js")
18
+ .then(reg => console.log("Service Worker registered", reg))
19
+ .catch(err => console.log("Service Worker registration failed", err));
20
+ });
21
+ }
22
+
23
+ /** @type {HTMLDivElement} */
24
+ const $divFaustUI = document.getElementById("div-faust-ui");
25
+
26
+ /** @type {typeof AudioContext} */
27
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
28
+ const audioContext = new AudioCtx({ latencyHint: 0.00001 });
29
+ audioContext.destination.channelInterpretation = "discrete";
30
+ audioContext.suspend();
31
+
32
+ // Declare faustNode as a global variable
33
+ let faustNode;
34
+
35
+ // Called at load time
36
+ (async () => {
37
+
38
+ // Import the create-node module
39
+ const { createFaustNode, createFaustUI } = await import("./create-node.js");
40
+
41
+ // To test the ScriptProcessorNode mode
42
+ const result = await createFaustNode(audioContext, "osc", FAUST_DSP_VOICES, true, 512);
43
+ //const result = await createFaustNode(audioContext, "osc", FAUST_DSP_VOICES);
44
+ faustNode = result.faustNode; // Assign to the global variable
45
+ if (!faustNode) throw new Error("Faust DSP not compiled");
46
+
47
+ // Create the Faust UI
48
+ await createFaustUI($divFaustUI, faustNode);
49
+
50
+ })();
51
+
52
+ // Synchronous function to resume AudioContext, to be called first in the synchronous event listener
53
+ function resumeAudioContext() {
54
+ if (audioContext.state === 'suspended') {
55
+ audioContext.resume().then(() => {
56
+ console.log('AudioContext resumed successfully');
57
+ }).catch(error => {
58
+ console.error('Error when resuming AudioContext:', error);
59
+ });
60
+ }
61
+ }
62
+
63
+ // Function to start MIDI
64
+ function startMIDI() {
65
+ // Check if the browser supports the Web MIDI API
66
+ if (navigator.requestMIDIAccess) {
67
+ navigator.requestMIDIAccess().then(
68
+ midiAccess => {
69
+ console.log("MIDI Access obtained.");
70
+ for (let input of midiAccess.inputs.values()) {
71
+ input.onmidimessage = (event) => faustNode.midiMessage(event.data);
72
+ console.log(`Connected to input: ${input.name}`);
73
+ }
74
+ },
75
+ () => console.error("Failed to access MIDI devices.")
76
+ );
77
+ } else {
78
+ console.log("Web MIDI API is not supported in this browser.");
79
+ }
80
+ }
81
+
82
+ // Function to stop MIDI
83
+ function stopMIDI() {
84
+ // Check if the browser supports the Web MIDI API
85
+ if (navigator.requestMIDIAccess) {
86
+ navigator.requestMIDIAccess().then(
87
+ midiAccess => {
88
+ console.log("MIDI Access obtained.");
89
+ for (let input of midiAccess.inputs.values()) {
90
+ input.onmidimessage = null;
91
+ console.log(`Disconnected from input: ${input.name}`);
92
+ }
93
+ },
94
+ () => console.error("Failed to access MIDI devices.")
95
+ );
96
+ } else {
97
+ console.log("Web MIDI API is not supported in this browser.");
98
+ }
99
+ }
100
+
101
+ let sensorHandlersBound = false;
102
+ let midiHandlersBound = false;
103
+
104
+ // Function to activate MIDI and Sensors on user interaction
105
+ async function activateMIDISensors() {
106
+
107
+ // Import the create-node module
108
+ const { connectToAudioInput, requestPermissions } = await import("./create-node.js");
109
+
110
+ // Request permission for sensors
111
+ await requestPermissions();
112
+
113
+ // Activate sensor listeners
114
+ if (!sensorHandlersBound) {
115
+ await faustNode.startSensors();
116
+ sensorHandlersBound = true;
117
+ }
118
+
119
+ // Initialize the MIDI setup
120
+ if (!midiHandlersBound) {
121
+ startMIDI();
122
+ midiHandlersBound = true;
123
+ }
124
+
125
+ // Connect the Faust node to the audio output
126
+ faustNode.connect(audioContext.destination);
127
+
128
+ // Connect the Faust node to the audio input
129
+ if (faustNode.numberOfInputs > 0) {
130
+ await connectToAudioInput(audioContext, null, faustNode, null);
131
+ }
132
+
133
+ // Resume the AudioContext
134
+ if (audioContext.state === 'suspended') {
135
+ await audioContext.resume();
136
+ }
137
+ }
138
+
139
+ // Function to suspend AudioContext, deactivate MIDI and Sensors on user interaction
140
+ async function deactivateAudioMIDISensors() {
141
+
142
+ // Suspend the AudioContext
143
+ if (audioContext.state === 'running') {
144
+ await audioContext.suspend();
145
+ }
146
+
147
+ // Deactivate sensor listeners
148
+ if (sensorHandlersBound) {
149
+ faustNode.stopSensors();
150
+ sensorHandlersBound = false;
151
+ }
152
+
153
+ // Deactivate the MIDI setup
154
+ if (midiHandlersBound && FAUST_DSP_VOICES > 0) {
155
+ stopMIDI();
156
+ midiHandlersBound = false;
157
+ }
158
+ }
159
+
160
+ // Event listener to handle user interaction
161
+ function handleUserInteraction() {
162
+
163
+ // Resume AudioContext synchronously
164
+ resumeAudioContext();
165
+
166
+ // Launch the activation of MIDI and Sensors
167
+ activateMIDISensors().catch(error => {
168
+ console.error('Error when activating audio, MIDI and sensors:', error);
169
+ });
170
+ }
171
+
172
+ // Activate AudioContext, MIDI and Sensors on user interaction
173
+ window.addEventListener('click', handleUserInteraction);
174
+ window.addEventListener('touchstart', handleUserInteraction);
175
+
176
+ // Deactivate AudioContext, MIDI and Sensors on user interaction
177
+ window.addEventListener('visibilitychange', function () {
178
+ if (window.visibilityState === 'hidden') {
179
+ deactivateAudioMIDISensors();
180
+ }
181
+ });
182
+
183
+
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "Faust midi",
3
+ "short_name": "midi",
4
+ "start_url": "./index.html",
5
+ "description": "Progressive Web App for midi",
6
+ "display": "standalone",
7
+ "background_color": "#ffffff",
8
+ "theme_color": "#000000",
9
+ "orientation": "portrait",
10
+ "icons": [
11
+ {
12
+ "src": "./icon.png",
13
+ "sizes": "390x390",
14
+ "type": "image/png"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,165 @@
1
+ /// <reference lib="webworker" />
2
+
3
+ // Set to > 0 if the DSP is polyphonic
4
+ const FAUST_DSP_VOICES = 0;
5
+ // Set to true if the DSP has an effect
6
+ const FAUST_DSP_HAS_EFFECT = false;
7
+
8
+ const CACHE_NAME = "midi_20250216-0831"; // Cache name with versioning
9
+
10
+ /**
11
+ * List of essential resources required for the **Mono DSP** version of the application.
12
+ *
13
+ * - These files are cached to enable offline functionality and improve loading speed.
14
+ * - Includes the main HTML, JavaScript, and CSS files required for the app.
15
+ * - Contains Faust-related files needed for DSP processing.
16
+ */
17
+ const MONO_RESOURCES = [
18
+ "./index.html",
19
+ "./index.js",
20
+ "./create-node.js",
21
+ "./faust-ui/index.js",
22
+ "./faust-ui/index.css",
23
+ "./faustwasm/index.js",
24
+ "./dsp-module.wasm",
25
+ "./dsp-meta.json"
26
+ ];
27
+
28
+ /**
29
+ * List of resources for the **Polyphonic DSP** version of the application.
30
+ *
31
+ * - Extends the mono resource list by adding a **mixer module**.
32
+ * - The mixer module is required to handle multiple simultaneous voices used in a polyphonic instrument.
33
+ */
34
+ const POLY_RESOURCES = [
35
+ ...MONO_RESOURCES,
36
+ "./mixer-module.wasm",
37
+ ];
38
+
39
+ /**
40
+ * List of resources for the **Polyphonic DSP with Effects** version.
41
+ *
42
+ * - Extends the polyphonic resource list by adding an **effect module**.
43
+ * - The effect module allows applying audio effects to the polyphonic instrument.
44
+ */
45
+ const POLY_EFFECT_RESOURCES = [
46
+ ...POLY_RESOURCES,
47
+ "./effect-module.wasm",
48
+ "./effect-meta.json",
49
+ ];
50
+
51
+ /** @type {ServiceWorkerGlobalScope} */
52
+ const serviceWorkerGlobalScope = self;
53
+
54
+ /**
55
+ * Install the service worker, cache essential resources, and prepare for immediate activation.
56
+ *
57
+ * - Opens the cache and stores required assets based on the app's configuration.
58
+ * - Ensures resources are preloaded for offline access.
59
+ */
60
+ serviceWorkerGlobalScope.addEventListener("install", (event) => {
61
+ console.log("Service worker installed");
62
+ event.waitUntil((async () => {
63
+ const cache = await caches.open(CACHE_NAME);
64
+ const resources = (FAUST_DSP_VOICES && FAUST_DSP_HAS_EFFECT) ? POLY_EFFECT_RESOURCES : (FAUST_DSP_VOICES ? POLY_RESOURCES : MONO_RESOURCES);
65
+ try {
66
+ return cache.addAll(resources);
67
+ } catch (error) {
68
+ console.error("Failed to cache resources during install:", error);
69
+ }
70
+ })());
71
+ });
72
+
73
+ /**
74
+ * Handles the activation of the Service Worker.
75
+ *
76
+ * - Claims control over all clients immediately, bypassing the default behavior that
77
+ * requires a page reload before the new service worker takes effect.
78
+ * - Once claimed, it finds all active window clients and reloads them to ensure they are
79
+ * controlled by the latest version of the service worker.
80
+ * - This approach ensures that updates to the service worker take effect immediately
81
+ * across all open pages, preventing potential inconsistencies.
82
+ */
83
+ serviceWorkerGlobalScope.addEventListener("activate", (event) => {
84
+ console.log("Service worker activated");
85
+ event.waitUntil(
86
+ clients.claim().then(() => {
87
+ return clients.matchAll({ type: "window" }).then((clients) => {
88
+ clients.forEach((client) => {
89
+ client.navigate(client.url);
90
+ });
91
+ });
92
+ })
93
+ );
94
+ });
95
+
96
+ /**
97
+ * Adjusts response headers to enforce Cross-Origin Opener Policy (COOP) and Cross-Origin Embedder Policy (COEP).
98
+ *
99
+ * - Ensures that the response is served with `Cross-Origin-Opener-Policy: same-origin`
100
+ * and `Cross-Origin-Embedder-Policy: require-corp`.
101
+ * - Required for enabling **cross-origin isolated** environments in web applications.
102
+ * - Necessary for features like **SharedArrayBuffer**, WebAssembly threads, and
103
+ * high-performance APIs that require isolation.
104
+ * - Creates a new `Response` object with the modified headers while preserving
105
+ * the original response body and status.
106
+ *
107
+ * @param {Response} response - The original HTTP response object.
108
+ * @returns {Response} A new response with updated security headers.
109
+ */
110
+ const getCrossOriginIsolatedResponse = (response) => {
111
+ // Modify headers to include COOP & COEP
112
+ const headers = new Headers(response.headers);
113
+ headers.set("Cross-Origin-Opener-Policy", "same-origin");
114
+ headers.set("Cross-Origin-Embedder-Policy", "require-corp");
115
+
116
+ // Create a new response with the modified headers
117
+ const modifiedResponse = new Response(response.body, {
118
+ status: response.status,
119
+ statusText: response.statusText,
120
+ headers
121
+ });
122
+
123
+ return modifiedResponse;
124
+ };
125
+
126
+ /**
127
+ * Intercepts fetch requests and enforces COOP and COEP headers for security.
128
+ *
129
+ * - Checks if the requested resource is available in the cache:
130
+ * - If found, returns a cached response with `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers.
131
+ * - If not found, fetches the resource from the network, applies COOP/COEP headers, and caches the response (for GET requests).
132
+ * - Ensures cross-origin isolation, required for APIs like `SharedArrayBuffer` and WebAssembly threading.
133
+ * - Handles network errors gracefully by returning a 503 "Service Unavailable" response when needed.
134
+ *
135
+ * @param {FetchEvent} event - The fetch event triggered by the browser.
136
+ */
137
+ serviceWorkerGlobalScope.addEventListener("fetch", (event) => {
138
+
139
+ event.respondWith((async () => {
140
+ const cache = await caches.open(CACHE_NAME);
141
+ const cachedResponse = await cache.match(event.request);
142
+
143
+ if (cachedResponse) {
144
+ return getCrossOriginIsolatedResponse(cachedResponse);
145
+ } else {
146
+ try {
147
+ const fetchResponse = await fetch(event.request);
148
+
149
+ if (event.request.method === "GET" && fetchResponse && fetchResponse.status === 200 && fetchResponse.type === "basic") {
150
+ const modifiedResponse = getCrossOriginIsolatedResponse(fetchResponse);
151
+ // Store the modified response in the cache
152
+ await cache.put(event.request, modifiedResponse.clone());
153
+ // Return the modified response to the browser
154
+ return modifiedResponse;
155
+ }
156
+
157
+ return fetchResponse;
158
+ } catch (error) {
159
+ console.error("Network access error", error);
160
+ return new Response("Network error", { status: 503, statusText: "Service Unavailable" });
161
+ }
162
+ }
163
+ })());
164
+ });
165
+
package/test/midi.dsp CHANGED
@@ -1,7 +1,14 @@
1
1
 
2
2
  import("stdfaust.lib");
3
+
4
+ /*
3
5
  process = os.osc(freq)*vol
4
6
  with {
5
7
  freq = hslider("Freq[midi:ctrl 1]", 200, 200, 2000, 0.01);
6
8
  vol = hslider("Volume[midi:ctrl 7 5]", 0.5, 0, 1, 0.01);
7
- };
9
+ };
10
+ */
11
+
12
+ process = (os.osc(500)*en.adsr(0.1, 0.1, 0.8, 0.5, button("gate1[midi:key 60]")),
13
+ os.osc(800)*en.adsr(0.1, 0.1, 0.8, 1.0, button("gate2[midi:key 62]")),
14
+ os.osc(200)*en.adsr(0.1, 0.1, 0.8, 1.5, button("gate3[midi:key 64]"))) :> _;
@@ -0,0 +1,177 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {{ dspModule: WebAssembly.Module; dspMeta: FaustDspMeta; effectModule?: WebAssembly.Module; effectMeta?: FaustDspMeta; mixerModule?: WebAssembly.Module }} FaustDspDistribution
5
+ * @typedef {import("./faustwasm").FaustDspMeta} FaustDspMeta
6
+ * @typedef {import("./faustwasm").FaustMonoAudioWorkletNode} FaustMonoAudioWorkletNode
7
+ * @typedef {import("./faustwasm").FaustPolyAudioWorkletNode} FaustPolyAudioWorkletNode
8
+ * @typedef {import("./faustwasm").FaustMonoScriptProcessorNode} FaustMonoScriptProcessorNode
9
+ * @typedef {import("./faustwasm").FaustPolyScriptProcessorNode} FaustPolyScriptProcessorNode
10
+ * @typedef {FaustMonoAudioWorkletNode | FaustPolyAudioWorkletNode | FaustMonoScriptProcessorNode | FaustPolyScriptProcessorNode} FaustNode
11
+ */
12
+
13
+ /**
14
+ * Creates a Faust audio node for use in the Web Audio API.
15
+ *
16
+ * @param {AudioContext} audioContext - The Web Audio API AudioContext to which the Faust audio node will be connected.
17
+ * @param {string} [dspName] - The name of the DSP to be loaded.
18
+ * @param {number} [voices] - The number of voices to be used for polyphonic DSPs.
19
+ * @param {boolean} [sp] - Whether to create a ScriptProcessorNode instead of an AudioWorkletNode.
20
+ * @returns {Promise<{ faustNode: FaustNode | null; dspMeta: FaustDspMeta }>} - An object containing the Faust audio node and the DSP metadata.
21
+ */
22
+ const createFaustNode = async (audioContext, dspName = "template", voices = 0, sp = false, bufferSize = 512) => {
23
+ // Set to true if the DSP has an effect
24
+ const FAUST_DSP_HAS_EFFECT = false;
25
+
26
+ // Import necessary Faust modules and data
27
+ const { FaustMonoDspGenerator, FaustPolyDspGenerator } = await import("./faustwasm/index.js");
28
+
29
+ // Load DSP metadata from JSON
30
+ /** @type {FaustDspMeta} */
31
+ const dspMeta = await (await fetch("./dsp-meta.json")).json();
32
+
33
+ // Compile the DSP module from WebAssembly binary data
34
+ const dspModule = await WebAssembly.compileStreaming(await fetch("./dsp-module.wasm"));
35
+
36
+ // Create an object representing Faust DSP with metadata and module
37
+ /** @type {FaustDspDistribution} */
38
+ const faustDsp = { dspMeta, dspModule };
39
+
40
+ /** @type {FaustNode | null} */
41
+ let faustNode = null;
42
+
43
+ // Create either a polyphonic or monophonic Faust audio node based on the number of voices
44
+ if (voices > 0) {
45
+
46
+ // Try to load optional mixer and effect modules
47
+ faustDsp.mixerModule = await WebAssembly.compileStreaming(await fetch("./mixer-module.wasm"));
48
+
49
+ if (FAUST_DSP_HAS_EFFECT) {
50
+ faustDsp.effectMeta = await (await fetch("./effect-meta.json")).json();
51
+ faustDsp.effectModule = await WebAssembly.compileStreaming(await fetch("./effect-module.wasm"));
52
+ }
53
+
54
+ // Create a polyphonic Faust audio node
55
+ const generator = new FaustPolyDspGenerator();
56
+ faustNode = await generator.createNode(
57
+ audioContext,
58
+ voices,
59
+ dspName,
60
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta), soundfiles: {} },
61
+ faustDsp.mixerModule,
62
+ faustDsp.effectModule ? { module: faustDsp.effectModule, json: JSON.stringify(faustDsp.effectMeta), soundfiles: {} } : undefined,
63
+ sp,
64
+ bufferSize
65
+ );
66
+ } else {
67
+ // Create a standard Faust audio node
68
+ const generator = new FaustMonoDspGenerator();
69
+ faustNode = await generator.createNode(
70
+ audioContext,
71
+ dspName,
72
+ { module: faustDsp.dspModule, json: JSON.stringify(faustDsp.dspMeta), soundfiles: {} },
73
+ sp,
74
+ bufferSize
75
+ );
76
+ }
77
+
78
+ // Return an object with the Faust audio node and the DSP metadata
79
+ return { faustNode, dspMeta };
80
+ }
81
+
82
+ /**
83
+ * Connects an audio input stream to a Faust WebAudio node.
84
+ *
85
+ * @param {AudioContext} audioContext - The Web Audio API AudioContext to which the Faust audio node is connected.
86
+ * @param {string} id - The ID of the audio input device to connect.
87
+ * @param {FaustNode} faustNode - The Faust audio node to which the audio input stream will be connected.
88
+ * @param {MediaStreamAudioSourceNode} oldInputStreamNode - The old audio input stream node to be disconnected from the Faust audio node.
89
+ * @returns {Promise<MediaStreamAudioSourceNode>} - The new audio input stream node connected to the Faust audio node.
90
+ */
91
+ async function connectToAudioInput(audioContext, id, faustNode, oldInputStreamNode) {
92
+ // Create an audio input stream node
93
+ const constraints = {
94
+ audio: {
95
+ echoCancellation: false,
96
+ noiseSuppression: false,
97
+ autoGainControl: false,
98
+ deviceId: id ? { exact: id } : undefined,
99
+ },
100
+ };
101
+ // Get the audio input stream
102
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
103
+ if (stream) {
104
+ if (oldInputStreamNode) oldInputStreamNode.disconnect();
105
+ const newInputStreamNode = audioContext.createMediaStreamSource(stream);
106
+ newInputStreamNode.connect(faustNode);
107
+ return newInputStreamNode;
108
+ } else {
109
+ return oldInputStreamNode;
110
+ }
111
+ };
112
+
113
+ /**
114
+ * Creates a Faust UI for a Faust audio node.
115
+ *
116
+ * @param {FaustAudioWorkletNode} faustNode
117
+ */
118
+ async function createFaustUI(divFaustUI, faustNode) {
119
+ const { FaustUI } = await import("./faust-ui/index.js");
120
+ const $container = document.createElement("div");
121
+ $container.style.margin = "0";
122
+ $container.style.position = "absolute";
123
+ $container.style.overflow = "auto";
124
+ $container.style.display = "flex";
125
+ $container.style.flexDirection = "column";
126
+ $container.style.width = "100%";
127
+ $container.style.height = "100%";
128
+ divFaustUI.appendChild($container);
129
+ const faustUI = new FaustUI({
130
+ ui: faustNode.getUI(),
131
+ root: $container,
132
+ listenWindowMessage: false,
133
+ listenWindowResize: true,
134
+ });
135
+ faustUI.paramChangeByUI = (path, value) => faustNode.setParamValue(path, value);
136
+ faustNode.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value));
137
+ $container.style.minWidth = `${faustUI.minWidth}px`;
138
+ $container.style.minHeight = `${faustUI.minHeight}px`;
139
+ faustUI.resize();
140
+ };
141
+
142
+ /**
143
+ * Request permission to use motion and orientation sensors.
144
+ */
145
+ async function requestPermissions() {
146
+
147
+ // Explicitly request permission on iOS before calling startSensors()
148
+ if (typeof window.DeviceMotionEvent !== "undefined" && typeof window.DeviceMotionEvent.requestPermission === "function") {
149
+ try {
150
+ const permissionState = await window.DeviceMotionEvent.requestPermission();
151
+ if (permissionState !== "granted") {
152
+ console.warn("Motion sensor permission denied.");
153
+ } else {
154
+ console.log("Motion sensor permission granted.");
155
+ }
156
+ } catch (error) {
157
+ console.error("Error requesting motion sensor permission:", error);
158
+ }
159
+ }
160
+
161
+ if (typeof window.DeviceOrientationEvent !== "undefined" && typeof window.DeviceOrientationEvent.requestPermission === "function") {
162
+ try {
163
+ const permissionState = await window.DeviceOrientationEvent.requestPermission();
164
+ if (permissionState !== "granted") {
165
+ console.warn("Orientation sensor permission denied.");
166
+ } else {
167
+ console.log("Orientation sensor permission granted.");
168
+ }
169
+ } catch (error) {
170
+ console.error("Error requesting orientation sensor permission:", error);
171
+ }
172
+ }
173
+ }
174
+
175
+ // Export the functions
176
+ export { createFaustNode, createFaustUI, connectToAudioInput, requestPermissions };
177
+