@grame/faustwasm 0.11.2 → 0.12.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.
- package/assets/standalone/faust-pwa.js +344 -0
- package/assets/standalone/index-pwa.js +49 -181
- package/dist/cjs-bundle/index.js +4 -4
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm-bundle/index.js +4 -4
- package/dist/esm-bundle/index.js.map +2 -2
- package/libfaust-wasm/libfaust-wasm.data +0 -0
- package/libfaust-wasm/libfaust-wasm.js +1 -1
- package/libfaust-wasm/libfaust-wasm.wasm +0 -0
- package/package.json +1 -1
- package/src/copyWebStandaloneAssets.js +3 -0
- package/test/faustlive-wasm/index.html +0 -3
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module faust-pwa
|
|
3
|
+
* High-level controller around a Faust DSP node for Web Audio apps.
|
|
4
|
+
*
|
|
5
|
+
* ## Public surface
|
|
6
|
+
* - {@link createFaustPWA} – factory function (recommended entry point)
|
|
7
|
+
* - {@link FaustPWA} – class (advanced use). Methods marked @public are supported.
|
|
8
|
+
*
|
|
9
|
+
* ## Private surface
|
|
10
|
+
* Any symbol/method/field marked `@private` or not listed as @public is internal.
|
|
11
|
+
* Names prefixed with `f` (e.g. `fFaustNode`) are implementation details.
|
|
12
|
+
*
|
|
13
|
+
* ## Emitted events
|
|
14
|
+
* Instances emit the following CustomEvent names via {@link FaustPWA#on}:
|
|
15
|
+
* - `created` — when the DSP node and (optional) UI are created.
|
|
16
|
+
* - `started` — when audio/MIDI/sensors are activated.
|
|
17
|
+
* - `stopped` — when audio/MIDI/sensors are deactivated.
|
|
18
|
+
* - `destroy` — after teardown completes.
|
|
19
|
+
* - `error` — when an operational error occurs; payload in `event.detail.error`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {import("./faustwasm").FaustAudioWorkletNode} FaustAudioWorkletNode
|
|
24
|
+
* @typedef {import("./faustwasm").FaustDspMeta} FaustDspMeta
|
|
25
|
+
* @typedef {import("./faustwasm").FaustUIDescriptor} FaustUIDescriptor
|
|
26
|
+
* @typedef {import("./faustwasm").FaustUIGroup} FaustUIGroup
|
|
27
|
+
* @typedef {import("./faustwasm").FaustUIItem} FaustUIItem
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef FaustPWAOptions
|
|
32
|
+
* @property {string} dspName - DSP name string for createFaustNode (required).
|
|
33
|
+
* @property {number} [voices=0] - Polyphony: 0 for mono, >0 for poly.
|
|
34
|
+
* @property {boolean} [useScriptProcessor=false] - Fallback to ScriptProcessorNode mode.
|
|
35
|
+
* @property {number} [bufferSize=512] - Buffer size for ScriptProcessorNode mode.
|
|
36
|
+
* @property {HTMLElement | null} uiContainer - Where to render the Faust UI (optional).
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* FaustPWA class to manage Faust DSP with PWA features (service worker, MIDI, sensors, keyboard).
|
|
41
|
+
*/
|
|
42
|
+
export class FaustPWA {
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {FaustPWAOptions} options;
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
constructor(options) {
|
|
49
|
+
|
|
50
|
+
/** @private internal activation flags */
|
|
51
|
+
this.fActive = { midi: false, sensors: false };
|
|
52
|
+
|
|
53
|
+
/** @type {FaustPWAOptions} */
|
|
54
|
+
this.fOptions = {
|
|
55
|
+
voices: 0,
|
|
56
|
+
useScriptProcessor: false,
|
|
57
|
+
bufferSize: 512,
|
|
58
|
+
uiContainer: null,
|
|
59
|
+
...options,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** @type {AudioContext} */
|
|
63
|
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
64
|
+
this.fAudioContext = this.fOptions.audioContext || new AudioCtx({ latencyHint: 0.00001 })
|
|
65
|
+
this.fAudioContext.destination.channelInterpretation = "discrete";
|
|
66
|
+
this.fAudioContext.suspend();
|
|
67
|
+
|
|
68
|
+
/** @type {FaustAudioWorkletNode | null} */
|
|
69
|
+
this.fFaustNode = null;
|
|
70
|
+
|
|
71
|
+
// Event system
|
|
72
|
+
this.fEvents = new EventTarget();
|
|
73
|
+
|
|
74
|
+
// MIDI and Sensor handlers state
|
|
75
|
+
this.fActive.sensors = false;
|
|
76
|
+
this.fActive.midi = false;
|
|
77
|
+
|
|
78
|
+
// Keyboard to MIDI handing
|
|
79
|
+
this.fKeyboard2MIDI = null;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Registers the service worker.
|
|
83
|
+
*/
|
|
84
|
+
if ("serviceWorker" in navigator) {
|
|
85
|
+
window.addEventListener("load", () => {
|
|
86
|
+
navigator.serviceWorker.register("./service-worker.js")
|
|
87
|
+
.then(reg => console.log("Service Worker registered", reg))
|
|
88
|
+
.catch(err => console.log("Service Worker registration failed", err));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Function to start MIDI
|
|
94
|
+
startMIDI() {
|
|
95
|
+
// Check if the browser supports the Web MIDI API
|
|
96
|
+
if (navigator.requestMIDIAccess) {
|
|
97
|
+
navigator.requestMIDIAccess().then(
|
|
98
|
+
midiAccess => {
|
|
99
|
+
console.log("MIDI Access obtained.");
|
|
100
|
+
for (let input of midiAccess.inputs.values()) {
|
|
101
|
+
input.onmidimessage = (event) => this.fFaustNode.midiMessage(event.data);
|
|
102
|
+
console.log(`Connected to input: ${input.name}`);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
() => console.error("Failed to access MIDI devices.")
|
|
106
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
console.log("Web MIDI API is not supported in this browser.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Function to stop MIDI
|
|
113
|
+
stopMIDI() {
|
|
114
|
+
// Check if the browser supports the Web MIDI API
|
|
115
|
+
if (navigator.requestMIDIAccess) {
|
|
116
|
+
navigator.requestMIDIAccess().then(
|
|
117
|
+
midiAccess => {
|
|
118
|
+
console.log("MIDI Access obtained.");
|
|
119
|
+
for (let input of midiAccess.inputs.values()) {
|
|
120
|
+
input.onmidimessage = null;
|
|
121
|
+
console.log(`Disconnected from input: ${input.name}`);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
() => console.error("Failed to access MIDI devices.")
|
|
125
|
+
);
|
|
126
|
+
} else {
|
|
127
|
+
console.log("Web MIDI API is not supported in this browser.");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Function to start Keyboard to MIDI
|
|
132
|
+
async startKeyboard2MIDI() {
|
|
133
|
+
// Import the create-node module
|
|
134
|
+
const { createKey2MIDI } = await import("./create-node.js");
|
|
135
|
+
|
|
136
|
+
this.fKeyboard2MIDI = createKey2MIDI((event) => this.fFaustNode.midiMessage(event));
|
|
137
|
+
this.fKeyboard2MIDI.start();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Function to stop Keyboard to MIDI
|
|
141
|
+
stopKeyboard2MIDI() {
|
|
142
|
+
this.fKeyboard2MIDI.stop();
|
|
143
|
+
this.fKeyboard2MIDI = null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Dispatch a custom event.
|
|
148
|
+
* @param {string} type
|
|
149
|
+
* @param {any} detail
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
dispatch(type, detail = undefined) {
|
|
153
|
+
this.fEvents.dispatchEvent(new CustomEvent(type, { detail, bubbles: false, composed: true }));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Public API
|
|
157
|
+
|
|
158
|
+
// ---------------------- Event API ---------------------------------
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Add a listener for a custom event.
|
|
162
|
+
* @param {string} type
|
|
163
|
+
* @param {(e: CustomEvent) => void} handler
|
|
164
|
+
*/
|
|
165
|
+
on(type, handler) {
|
|
166
|
+
this.fEvents.addEventListener(type, handler);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Remove a listener for a custom event.
|
|
171
|
+
* @param {string} type
|
|
172
|
+
* @param {(e: CustomEvent) => void} handler
|
|
173
|
+
*/
|
|
174
|
+
off(type, handler) {
|
|
175
|
+
this.fEvents.removeEventListener(type, handler);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------------------- Audio/MIDI/Sensors API ---------------------------------
|
|
179
|
+
|
|
180
|
+
// Getters for AudioContext
|
|
181
|
+
get audioContext() {
|
|
182
|
+
return this.fAudioContext;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Getter for FaustNode
|
|
186
|
+
get faustNode() {
|
|
187
|
+
return this.fFaustNode;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Synchronous function to resume AudioContext, to be called first in the synchronous event listener
|
|
191
|
+
resumeAudioContext() {
|
|
192
|
+
if (this.fAudioContext.state === 'suspended') {
|
|
193
|
+
this.fAudioContext.resume().then(() => {
|
|
194
|
+
console.log('AudioContext resumed successfully');
|
|
195
|
+
}).catch(error => {
|
|
196
|
+
console.error('Error when resuming AudioContext:', error);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Asynchronous function to suspend AudioContext
|
|
202
|
+
async suspendAudioContext() {
|
|
203
|
+
// Suspend the AudioContext
|
|
204
|
+
if (this.fAudioContext.state === 'running') {
|
|
205
|
+
await this.fAudioContext.suspend();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Function to activate MIDI and Sensors on user interaction
|
|
210
|
+
async activateMIDISensors() {
|
|
211
|
+
|
|
212
|
+
// Import the create-node module
|
|
213
|
+
const { connectToAudioInput, requestPermissions } = await import("./create-node.js");
|
|
214
|
+
|
|
215
|
+
// Request permission for sensors
|
|
216
|
+
await requestPermissions();
|
|
217
|
+
|
|
218
|
+
// Activate sensor listeners
|
|
219
|
+
if (!this.fActive.sensors) {
|
|
220
|
+
await this.fFaustNode.startSensors();
|
|
221
|
+
this.fActive.sensors = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Initialize the MIDI setup
|
|
225
|
+
if (!this.fActive.midi) {
|
|
226
|
+
this.startMIDI();
|
|
227
|
+
await this.startKeyboard2MIDI();
|
|
228
|
+
this.fActive.midi = true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Connect the Faust node to the audio output@
|
|
232
|
+
this.fFaustNode.connect(this.fAudioContext.destination);
|
|
233
|
+
|
|
234
|
+
// Connect the Faust node to the audio input
|
|
235
|
+
if (this.fFaustNode.numberOfInputs > 0) {
|
|
236
|
+
await connectToAudioInput(this.fAudioContext, null, this.fFaustNode, null);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Function to deactivate MIDI and Sensors on user interaction
|
|
241
|
+
async deactivateMIDISensors() {
|
|
242
|
+
|
|
243
|
+
// Deactivate sensor listeners
|
|
244
|
+
if (this.fActive.sensors) {
|
|
245
|
+
this.fFaustNode.stopSensors();
|
|
246
|
+
this.fActive.sensors = false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Deactivate the MIDI setup
|
|
250
|
+
if (this.fActive.midi && this.fOptions.voices > 0) {
|
|
251
|
+
this.stopMIDI();
|
|
252
|
+
this.stopKeyboard2MIDI();
|
|
253
|
+
this.fActive.midi = false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Load and initialize the Faust node and (optionally) UI.
|
|
259
|
+
* @returns {Promise<void>}
|
|
260
|
+
*/
|
|
261
|
+
async create() {
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const { createFaustNode, createFaustUI } = await import('./create-node.js');
|
|
265
|
+
|
|
266
|
+
// Create the Faust node
|
|
267
|
+
const result = await createFaustNode(
|
|
268
|
+
this.fAudioContext,
|
|
269
|
+
this.fOptions.dspName,
|
|
270
|
+
this.fOptions.voices ?? 0,
|
|
271
|
+
this.fOptions.useScriptProcessor ?? false,
|
|
272
|
+
this.fOptions.bufferSize ?? 512
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
this.fFaustNode = result.faustNode; // Assign to the global variable
|
|
276
|
+
if (!this.fFaustNode) throw new Error("Faust DSP not compiled");
|
|
277
|
+
|
|
278
|
+
// Create the Faust UI
|
|
279
|
+
await createFaustUI(this.fOptions.uiContainer, this.fFaustNode);
|
|
280
|
+
|
|
281
|
+
this.dispatch('created', {
|
|
282
|
+
workletName: result.workletName,
|
|
283
|
+
sampleRate: result.sampleRate,
|
|
284
|
+
voices: this.fOptions.voices ?? 0,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
} catch (err) {
|
|
288
|
+
this.dispatch('error', { error: err });
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Fully disposes audio resources and UI.
|
|
295
|
+
* @returns {Promise<void>}
|
|
296
|
+
*/
|
|
297
|
+
async destroy() {
|
|
298
|
+
// Cleanup MIDI and Sensors
|
|
299
|
+
this.stop();
|
|
300
|
+
this.fFaustNode.destroy();
|
|
301
|
+
this.dispatch('destroy', {});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Start audio: resumes AudioContext if needed and connects to destination.
|
|
306
|
+
* @returns {Promise<void>}
|
|
307
|
+
*/
|
|
308
|
+
async start() {
|
|
309
|
+
// Resume AudioContext synchronously
|
|
310
|
+
this.resumeAudioContext();
|
|
311
|
+
|
|
312
|
+
// Launch the activation of MIDI and Sensors
|
|
313
|
+
this.activateMIDISensors().catch(error => {
|
|
314
|
+
console.error('Error when activating audio, MIDI and sensors:', error);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Dispatch the started event
|
|
318
|
+
this.dispatch('started', { when: this.audioContext.currentTime });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Stop audio: disconnects from destination but keeps node allocated.
|
|
323
|
+
*/
|
|
324
|
+
stop() {
|
|
325
|
+
|
|
326
|
+
// Deactivate MIDI and Sensors
|
|
327
|
+
this.deactivateMIDISensors();
|
|
328
|
+
this.fFaustNode.stop();
|
|
329
|
+
|
|
330
|
+
// Dispatch the stopped event
|
|
331
|
+
this.dispatch('stopped', { when: this.audioContext.currentTime });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Factory to create a {@link FaustPWA} instance.
|
|
337
|
+
* @function
|
|
338
|
+
* @public
|
|
339
|
+
* @param {FaustPWAOptions} options
|
|
340
|
+
* @returns {FaustPWA}
|
|
341
|
+
*/
|
|
342
|
+
export function createFaustPWA(options) {
|
|
343
|
+
return new FaustPWA(options);
|
|
344
|
+
}
|
|
@@ -1,201 +1,69 @@
|
|
|
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
1
|
|
|
12
2
|
/**
|
|
13
|
-
*
|
|
3
|
+
* @file index-pwa.js
|
|
4
|
+
* Entry point for initializing and managing the Faust PWA instance.
|
|
5
|
+
*
|
|
6
|
+
* ## Overview
|
|
7
|
+
* This script dynamically imports {@link createFaustPWA} from `faust-pwa.js`,
|
|
8
|
+
* creates the Faust PWA controller, and manages user-activation of the
|
|
9
|
+
* Web Audio API (click/touch) as well as visibility-based suspension.
|
|
10
|
+
*
|
|
11
|
+
* ## Responsibilities
|
|
12
|
+
* - Dynamically import `createFaustPWA` (ES module)
|
|
13
|
+
* - Create the PWA instance for the selected DSP
|
|
14
|
+
* - Resume/suspend AudioContext on user interaction or page visibility changes
|
|
15
|
+
* - Activate/deactivate MIDI and sensors
|
|
16
|
+
*
|
|
17
|
+
* ## Emitted Events (from FaustPWA)
|
|
18
|
+
* - `created` — DSP and UI successfully initialized
|
|
19
|
+
* - `started` — Audio, MIDI, and sensors activated
|
|
20
|
+
* - `stopped` — Audio, MIDI, and sensors deactivated
|
|
21
|
+
* - `destroy` — Resources released
|
|
22
|
+
* - `error` — Operational error; see event.detail.error
|
|
23
|
+
*
|
|
24
|
+
* @module index-pwa
|
|
14
25
|
*/
|
|
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
26
|
|
|
23
|
-
|
|
24
|
-
const
|
|
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;
|
|
27
|
+
// Set to > 0 if the DSP is polyphonic
|
|
28
|
+
const FAUST_DSP_VOICES = 0;
|
|
34
29
|
|
|
35
|
-
// Called at load time
|
|
36
30
|
(async () => {
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
const { createFaustNode, createFaustUI } = await import("./create-node.js");
|
|
32
|
+
const { FaustPWA } = await import("./faust-pwa.js");
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const result = await createFaustNode(audioContext, "FAUST_DSP_NAME", FAUST_DSP_VOICES);
|
|
44
|
-
faustNode = result.faustNode; // Assign to the global variable
|
|
45
|
-
if (!faustNode) throw new Error("Faust DSP not compiled");
|
|
34
|
+
/** @type {HTMLDivElement} */
|
|
35
|
+
const divFaustUI = document.getElementById("div-faust-ui");
|
|
46
36
|
|
|
47
|
-
//
|
|
48
|
-
|
|
37
|
+
// Declare faustNode as a global variable
|
|
38
|
+
const pwa = new FaustPWA({ dspName: "FAUST_DSP_NAME", voices: FAUST_DSP_VOICES, uiContainer: divFaustUI });
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
// Create PWA and UI
|
|
41
|
+
await pwa.create();
|
|
51
42
|
|
|
52
|
-
//
|
|
53
|
-
function
|
|
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
|
-
}
|
|
43
|
+
// Event listener to handle user interaction
|
|
44
|
+
function handleUserInteraction() {
|
|
62
45
|
|
|
63
|
-
//
|
|
64
|
-
|
|
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
|
-
}
|
|
46
|
+
// Resume AudioContext synchronously
|
|
47
|
+
pwa.resumeAudioContext();
|
|
81
48
|
|
|
82
|
-
//
|
|
83
|
-
|
|
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.");
|
|
49
|
+
// Activate MIDI and Sensors
|
|
50
|
+
pwa.activateMIDISensors()
|
|
98
51
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let sensorHandlersBound = false;
|
|
102
|
-
let midiHandlersBound = false;
|
|
103
|
-
|
|
104
|
-
// Keyboard to MIDI handing
|
|
105
|
-
let keyboard2MIDI = null;
|
|
106
|
-
|
|
107
|
-
async function startKeyboard2MIDI() {
|
|
108
|
-
// Import the create-node module
|
|
109
|
-
const { createKey2MIDI } = await import("./create-node.js");
|
|
110
52
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
53
|
+
// Activate AudioContext, MIDI and Sensors on user interaction
|
|
54
|
+
window.addEventListener('click', handleUserInteraction);
|
|
55
|
+
window.addEventListener('touchstart', handleUserInteraction);
|
|
114
56
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
57
|
+
// Deactivate AudioContext, MIDI and Sensors on user interaction
|
|
58
|
+
window.addEventListener('visibilitychange', function () {
|
|
59
|
+
if (window.visibilityState === 'hidden') {
|
|
119
60
|
|
|
120
|
-
//
|
|
121
|
-
|
|
61
|
+
// Suspend AudioContext
|
|
62
|
+
pwa.suspendAudioContext();
|
|
122
63
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Request permission for sensors
|
|
127
|
-
await requestPermissions();
|
|
128
|
-
|
|
129
|
-
// Activate sensor listeners
|
|
130
|
-
if (!sensorHandlersBound) {
|
|
131
|
-
await faustNode.startSensors();
|
|
132
|
-
sensorHandlersBound = true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Initialize the MIDI setup
|
|
136
|
-
if (!midiHandlersBound) {
|
|
137
|
-
startMIDI();
|
|
138
|
-
await startKeyboard2MIDI();
|
|
139
|
-
midiHandlersBound = true;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Connect the Faust node to the audio output
|
|
143
|
-
faustNode.connect(audioContext.destination);
|
|
144
|
-
|
|
145
|
-
// Connect the Faust node to the audio input
|
|
146
|
-
if (faustNode.numberOfInputs > 0) {
|
|
147
|
-
await connectToAudioInput(audioContext, null, faustNode, null);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Resume the AudioContext
|
|
151
|
-
if (audioContext.state === 'suspended') {
|
|
152
|
-
await audioContext.resume();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Function to suspend AudioContext, deactivate MIDI and Sensors on user interaction
|
|
157
|
-
async function deactivateAudioMIDISensors() {
|
|
158
|
-
|
|
159
|
-
// Suspend the AudioContext
|
|
160
|
-
if (audioContext.state === 'running') {
|
|
161
|
-
await audioContext.suspend();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Deactivate sensor listeners
|
|
165
|
-
if (sensorHandlersBound) {
|
|
166
|
-
faustNode.stopSensors();
|
|
167
|
-
sensorHandlersBound = false;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Deactivate the MIDI setup
|
|
171
|
-
if (midiHandlersBound && FAUST_DSP_VOICES > 0) {
|
|
172
|
-
stopMIDI();
|
|
173
|
-
stopKeyboard2MIDI();
|
|
174
|
-
midiHandlersBound = false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Event listener to handle user interaction
|
|
179
|
-
function handleUserInteraction() {
|
|
180
|
-
|
|
181
|
-
// Resume AudioContext synchronously
|
|
182
|
-
resumeAudioContext();
|
|
183
|
-
|
|
184
|
-
// Launch the activation of MIDI and Sensors
|
|
185
|
-
activateMIDISensors().catch(error => {
|
|
186
|
-
console.error('Error when activating audio, MIDI and sensors:', error);
|
|
64
|
+
// Deactivate MIDI and Sensors
|
|
65
|
+
pwa.deactivateMIDISensors();
|
|
66
|
+
}
|
|
187
67
|
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Activate AudioContext, MIDI and Sensors on user interaction
|
|
191
|
-
window.addEventListener('click', handleUserInteraction);
|
|
192
|
-
window.addEventListener('touchstart', handleUserInteraction);
|
|
193
|
-
|
|
194
|
-
// Deactivate AudioContext, MIDI and Sensors on user interaction
|
|
195
|
-
window.addEventListener('visibilitychange', function () {
|
|
196
|
-
if (window.visibilityState === 'hidden') {
|
|
197
|
-
deactivateAudioMIDISensors();
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
68
|
|
|
69
|
+
})();
|