@grame/faustwasm 0.16.3 → 0.16.5

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.
@@ -53,7 +53,7 @@ export class FaustPWA {
53
53
  /** @type {FaustPWAOptions} */
54
54
  this.fOptions = {
55
55
  voices: 0,
56
- useScriptProcessor: false,
56
+ useScriptProcessor: undefined,
57
57
  bufferSize: 512,
58
58
  uiContainer: null,
59
59
  ...options,
@@ -74,6 +74,7 @@ export class FaustPWA {
74
74
  // MIDI and Sensor handlers state
75
75
  this.fActive.sensors = false;
76
76
  this.fActive.midi = false;
77
+ this.fActive.audioConnected = false;
77
78
 
78
79
  // Keyboard to MIDI handing
79
80
  this.fKeyboard2MIDI = null;
@@ -187,15 +188,59 @@ export class FaustPWA {
187
188
  return this.fFaustNode;
188
189
  }
189
190
 
191
+ /**
192
+ * Choose the default audio backend for the current runtime.
193
+ *
194
+ * AudioWorklet is always preferred. ScriptProcessor is only requested up
195
+ * front when the runtime has no AudioWorklet support at all. iOS/WebKit
196
+ * cases where an AudioWorklet graph is created but stays silent are handled
197
+ * at node-creation time by the AudioWorklet→ScriptProcessor retry in
198
+ * `createFaustNode`, so no preemptive iOS heuristic is needed here. The
199
+ * explicit `useScriptProcessor` option still overrides this default.
200
+ *
201
+ * @returns {boolean} True only when AudioWorklet is unavailable.
202
+ */
203
+ shouldUseScriptProcessor() {
204
+ return !this.fAudioContext.audioWorklet;
205
+ }
206
+
207
+ /**
208
+ * Prime iOS audio output inside the same user gesture used to resume audio.
209
+ *
210
+ * Some iOS versions report a resumed AudioContext while the hardware output
211
+ * path remains locked until a source node is started from the activation
212
+ * gesture. A one-sample silent buffer is enough to unlock that path without
213
+ * producing audible sound. Failure is non-fatal because this is only a
214
+ * compatibility assist.
215
+ */
216
+ unlockAudioOutput() {
217
+ try {
218
+ const buffer = this.fAudioContext.createBuffer(1, 1, this.fAudioContext.sampleRate);
219
+ const source = this.fAudioContext.createBufferSource();
220
+ const gain = this.fAudioContext.createGain();
221
+ gain.gain.value = 0;
222
+ source.buffer = buffer;
223
+ source.connect(gain).connect(this.fAudioContext.destination);
224
+ source.start(0);
225
+ source.stop(this.fAudioContext.currentTime + 0.01);
226
+ } catch (error) {
227
+ console.warn("Silent audio unlock failed.", error);
228
+ }
229
+ }
230
+
190
231
  // Synchronous function to resume AudioContext, to be called first in the synchronous event listener
191
232
  resumeAudioContext() {
233
+ this.unlockAudioOutput();
192
234
  if (this.fAudioContext.state === 'suspended') {
193
- this.fAudioContext.resume().then(() => {
235
+ return this.fAudioContext.resume().then(() => {
194
236
  console.log('AudioContext resumed successfully');
237
+ return this.fAudioContext.state;
195
238
  }).catch(error => {
196
239
  console.error('Error when resuming AudioContext:', error);
240
+ throw error;
197
241
  });
198
242
  }
243
+ return Promise.resolve(this.fAudioContext.state);
199
244
  }
200
245
 
201
246
  // Asynchronous function to suspend AudioContext
@@ -212,6 +257,13 @@ export class FaustPWA {
212
257
  // Import the create-node module
213
258
  const { connectToAudioInput, requestPermissions } = await import("./create-node.js");
214
259
 
260
+ // Connect the Faust node to the audio output before optional permissions
261
+ // so a denied MIDI/sensor/input permission cannot leave a synth silent.
262
+ if (!this.fActive.audioConnected) {
263
+ this.fFaustNode.connect(this.fAudioContext.destination);
264
+ this.fActive.audioConnected = true;
265
+ }
266
+
215
267
  // Request permission for sensors
216
268
  await requestPermissions();
217
269
 
@@ -228,12 +280,13 @@ export class FaustPWA {
228
280
  this.fActive.midi = true;
229
281
  }
230
282
 
231
- // Connect the Faust node to the audio output@
232
- this.fFaustNode.connect(this.fAudioContext.destination);
233
-
234
283
  // Connect the Faust node to the audio input
235
284
  if (this.fFaustNode.numberOfInputs > 0) {
236
- await connectToAudioInput(this.fAudioContext, null, this.fFaustNode, null);
285
+ try {
286
+ await connectToAudioInput(this.fAudioContext, null, this.fFaustNode, null);
287
+ } catch (error) {
288
+ console.error("Error when connecting audio input:", error);
289
+ }
237
290
  }
238
291
  }
239
292
 
@@ -268,7 +321,7 @@ export class FaustPWA {
268
321
  this.fAudioContext,
269
322
  this.fOptions.dspName,
270
323
  this.fOptions.voices ?? 0,
271
- this.fOptions.useScriptProcessor ?? false,
324
+ this.fOptions.useScriptProcessor ?? this.shouldUseScriptProcessor(),
272
325
  this.fOptions.bufferSize ?? 512
273
326
  );
274
327
 
@@ -307,12 +360,10 @@ export class FaustPWA {
307
360
  */
308
361
  async start() {
309
362
  // Resume AudioContext synchronously
310
- this.resumeAudioContext();
311
-
363
+ await this.resumeAudioContext();
364
+
312
365
  // Launch the activation of MIDI and Sensors
313
- this.activateMIDISensors().catch(error => {
314
- console.error('Error when activating audio, MIDI and sensors:', error);
315
- });
366
+ await this.activateMIDISensors();
316
367
 
317
368
  // Dispatch the started event
318
369
  this.dispatch('started', { when: this.audioContext.currentTime });
@@ -12,7 +12,7 @@ var instantiateFaustModuleFromFile = async (jsFile, dataFile = jsFile.replace(/c
12
12
  let FaustModule;
13
13
  let dataBinary;
14
14
  let wasmBinary;
15
- const jsCodeHead = /var (.+) = \(/;
15
+ const jsCodeHead = /var (\w+)\s*=\s*\(/;
16
16
  if (typeof window === "object") {
17
17
  let jsCode = await (await fetch(jsFile)).text();
18
18
  jsCode = `${jsCode}