@grame/faustwasm 0.7.9 → 0.8.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 (34) hide show
  1. package/README.md +2 -2
  2. package/assets/standalone/faustwasm/index.d.ts +76 -9
  3. package/assets/standalone/faustwasm/index.js +270 -77
  4. package/assets/standalone/faustwasm/index.js.map +4 -4
  5. package/assets/standalone/index-pwa.js +7 -8
  6. package/assets/standalone/index-template.js +2 -2
  7. package/assets/standalone/index.js +59 -61
  8. package/assets/standalone/service-worker.js +47 -8
  9. package/dist/cjs/index.d.ts +76 -9
  10. package/dist/cjs/index.js +270 -77
  11. package/dist/cjs/index.js.map +4 -4
  12. package/dist/cjs-bundle/index.d.ts +76 -9
  13. package/dist/cjs-bundle/index.js +271 -78
  14. package/dist/cjs-bundle/index.js.map +4 -4
  15. package/dist/esm/index.d.ts +76 -9
  16. package/dist/esm/index.js +270 -77
  17. package/dist/esm/index.js.map +4 -4
  18. package/dist/esm-bundle/index.d.ts +76 -9
  19. package/dist/esm-bundle/index.js +271 -78
  20. package/dist/esm-bundle/index.js.map +4 -4
  21. package/libfaust-wasm/libfaust-wasm.wasm +0 -0
  22. package/package.json +1 -1
  23. package/src/FaustAudioWorkletCommunicator.ts +143 -0
  24. package/src/FaustAudioWorkletNode.ts +23 -12
  25. package/src/FaustAudioWorkletProcessor.ts +34 -15
  26. package/src/FaustDspGenerator.ts +20 -2
  27. package/src/FaustFFTAudioWorkletProcessor.ts +31 -2
  28. package/src/FaustOfflineProcessor.ts +6 -0
  29. package/src/FaustScriptProcessorNode.ts +4 -1
  30. package/src/FaustWebAudioDsp.ts +38 -10
  31. package/test/faustlive-wasm/faustwasm/index.d.ts +76 -9
  32. package/test/faustlive-wasm/faustwasm/index.js +270 -77
  33. package/test/faustlive-wasm/faustwasm/index.js.map +4 -4
  34. package/assets/standalone/coi-serviceworker.js +0 -146
@@ -31,14 +31,14 @@ audioContext.suspend();
31
31
 
32
32
  (async () => {
33
33
 
34
- const { createFaustNode } = await import("./create-node.js");
34
+ const { createFaustNode, createFaustUI, connectToAudioInput } = await import("./create-node.js");
35
+
35
36
  // To test the ScriptProcessorNode mode
36
37
  //const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "osc", FAUST_DSP_VOICES, true, 512);
37
38
  const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "osc", FAUST_DSP_VOICES);
38
39
  if (!faustNode) throw new Error("Faust DSP not compiled");
39
40
 
40
41
  // Create the Faust UI
41
- const { createFaustUI } = await import("./create-node.js");
42
42
  await createFaustUI($divFaustUI, faustNode);
43
43
 
44
44
  // Function to start MIDI
@@ -85,11 +85,6 @@ audioContext.suspend();
85
85
  // Function to resume AudioContext, activate MIDI and Sensors on user interaction
86
86
  async function activateAudioMIDISensors() {
87
87
 
88
- // Resume the AudioContext
89
- if (audioContext.state === 'suspended') {
90
- await audioContext.resume();
91
- }
92
-
93
88
  // Activate sensor listeners
94
89
  if (!sensorHandlersBound) {
95
90
  await faustNode.startSensors();
@@ -107,9 +102,13 @@ audioContext.suspend();
107
102
 
108
103
  // Connect the Faust node to the audio input
109
104
  if (faustNode.numberOfInputs > 0) {
110
- const { connectToAudioInput } = await import("./create-node.js");
111
105
  await connectToAudioInput(audioContext, null, faustNode, null);
112
106
  }
107
+
108
+ // Resume the AudioContext
109
+ if (audioContext.state === 'suspended') {
110
+ await audioContext.resume();
111
+ }
113
112
  }
114
113
 
115
114
  // Function to suspend AudioContext, deactivate MIDI and Sensors on user interaction
@@ -2,7 +2,8 @@
2
2
  const FAUST_DSP_VOICES = 0;
3
3
 
4
4
  (async () => {
5
- const { createFaustNode } = await import("./create-node.js");
5
+
6
+ const { createFaustNode, connectToAudioInput } = await import("./create-node.js");
6
7
 
7
8
  // Create audio context
8
9
  const AudioCtx = window.AudioContext || window.webkitAudioContext;
@@ -42,7 +43,6 @@ const FAUST_DSP_VOICES = 0;
42
43
 
43
44
  // Connect the Faust node to the audio input
44
45
  if (faustNode.getNumInputs() > 0) {
45
- const { connectToAudioInput } = await import("./create-node.js");
46
46
  await connectToAudioInput(audioContext, null, faustNode, null);
47
47
  }
48
48
 
@@ -41,82 +41,80 @@ audioContext.suspend();
41
41
 
42
42
  $buttonDsp.disabled = true;
43
43
 
44
- /**
45
- * @param {FaustAudioWorkletNode} faustNode
46
- */
47
- const buildAudioDeviceMenu = async (faustNode) => {
48
-
49
- const { connectToAudioInput } = await import("./create-node.js");
50
-
51
- let inputStreamNode = null;
52
- const handleDeviceChange = async () => {
53
- const devicesInfo = await navigator.mediaDevices.enumerateDevices();
54
- $selectAudioInput.innerHTML = "";
55
- devicesInfo.forEach((deviceInfo, i) => {
56
- const { kind, deviceId, label } = deviceInfo;
57
- if (kind === "audioinput") {
58
- const option = new Option(label || `microphone ${i + 1}`, deviceId);
59
- $selectAudioInput.add(option);
44
+ (async () => {
45
+
46
+ const { createFaustNode, connectToAudioInput, createFaustUI } = await import("./create-node.js");
47
+
48
+ /**
49
+ * @param {FaustAudioWorkletNode} faustNode
50
+ */
51
+ const buildAudioDeviceMenu = async (faustNode) => {
52
+
53
+ let inputStreamNode = null;
54
+ const handleDeviceChange = async () => {
55
+ const devicesInfo = await navigator.mediaDevices.enumerateDevices();
56
+ $selectAudioInput.innerHTML = "";
57
+ devicesInfo.forEach((deviceInfo, i) => {
58
+ const { kind, deviceId, label } = deviceInfo;
59
+ if (kind === "audioinput") {
60
+ const option = new Option(label || `microphone ${i + 1}`, deviceId);
61
+ $selectAudioInput.add(option);
62
+ }
63
+ });
64
+ }
65
+ await handleDeviceChange();
66
+ navigator.mediaDevices.addEventListener("devicechange", handleDeviceChange)
67
+ $selectAudioInput.onchange = async () => {
68
+ const id = $selectAudioInput.value;
69
+ if (faustNode.getNumInputs() > 0) {
70
+ inputStreamNode = await connectToAudioInput(audioContext, id, faustNode, inputStreamNode);
60
71
  }
61
- });
62
- }
63
- await handleDeviceChange();
64
- navigator.mediaDevices.addEventListener("devicechange", handleDeviceChange)
65
- $selectAudioInput.onchange = async () => {
66
- const id = $selectAudioInput.value;
72
+ };
73
+
74
+ // Connect to the default audio input device
67
75
  if (faustNode.getNumInputs() > 0) {
68
- inputStreamNode = await connectToAudioInput(audioContext, id, faustNode, inputStreamNode);
76
+ inputStreamNode = await connectToAudioInput(audioContext, null, faustNode, inputStreamNode);
69
77
  }
70
78
  };
71
79
 
72
- // Connect to the default audio input device
73
- if (faustNode.getNumInputs() > 0) {
74
- inputStreamNode = await connectToAudioInput(audioContext, null, faustNode, inputStreamNode);
75
- }
76
- };
77
-
78
- /**
79
- * @param {FaustAudioWorkletNode} faustNode
80
- */
81
- const buildMidiDeviceMenu = async (faustNode) => {
82
- const midiAccess = await navigator.requestMIDIAccess();
83
- /** @type {WebMidi.MIDIInput} */
84
- let currentInput;
85
80
  /**
86
- * @param {WebMidi.MIDIMessageEvent} e
81
+ * @param {FaustAudioWorkletNode} faustNode
87
82
  */
88
- const handleMidiMessage = e => faustNode.midiMessage(e.data);
89
- const handleStateChange = () => {
90
- const { inputs } = midiAccess;
91
- if ($selectMidiInput.options.length === inputs.size + 1) return;
92
- if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
93
- $selectMidiInput.innerHTML = '<option value="-1" disabled selected>Select...</option>';
94
- inputs.forEach((midiInput) => {
95
- const { name, id } = midiInput;
96
- const option = new Option(name, id);
97
- $selectMidiInput.add(option);
98
- });
99
- };
100
- handleStateChange();
101
- midiAccess.addEventListener("statechange", handleStateChange);
102
- $selectMidiInput.onchange = () => {
103
- if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
104
- const id = $selectMidiInput.value;
105
- currentInput = midiAccess.inputs.get(id);
106
- currentInput.addEventListener("midimessage", handleMidiMessage);
83
+ const buildMidiDeviceMenu = async (faustNode) => {
84
+ const midiAccess = await navigator.requestMIDIAccess();
85
+ /** @type {WebMidi.MIDIInput} */
86
+ let currentInput;
87
+ /**
88
+ * @param {WebMidi.MIDIMessageEvent} e
89
+ */
90
+ const handleMidiMessage = e => faustNode.midiMessage(e.data);
91
+ const handleStateChange = () => {
92
+ const { inputs } = midiAccess;
93
+ if ($selectMidiInput.options.length === inputs.size + 1) return;
94
+ if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
95
+ $selectMidiInput.innerHTML = '<option value="-1" disabled selected>Select...</option>';
96
+ inputs.forEach((midiInput) => {
97
+ const { name, id } = midiInput;
98
+ const option = new Option(name, id);
99
+ $selectMidiInput.add(option);
100
+ });
101
+ };
102
+ handleStateChange();
103
+ midiAccess.addEventListener("statechange", handleStateChange);
104
+ $selectMidiInput.onchange = () => {
105
+ if (currentInput) currentInput.removeEventListener("midimessage", handleMidiMessage);
106
+ const id = $selectMidiInput.value;
107
+ currentInput = midiAccess.inputs.get(id);
108
+ currentInput.addEventListener("midimessage", handleMidiMessage);
109
+ };
107
110
  };
108
- };
109
-
110
- (async () => {
111
111
 
112
- const { createFaustNode } = await import("./create-node.js");
113
112
  // To test the ScriptProcessorNode mode
114
113
  // const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "FAUST_DSP_NAME", FAUST_DSP_VOICES, true);
115
114
  const { faustNode, dspMeta: { name } } = await createFaustNode(audioContext, "FAUST_DSP_NAME", FAUST_DSP_VOICES);
116
115
  if (!faustNode) throw new Error("Faust DSP not compiled");
117
116
 
118
117
  // Create the Faust UI
119
- const { createFaustUI } = await import("./create-node.js");
120
118
  await createFaustUI($divFaustUI, faustNode);
121
119
 
122
120
  // Connect the Faust node to the audio output
@@ -29,7 +29,7 @@ const POLY_EFFECT_RESOURCES = [
29
29
  "./effect-meta.json",
30
30
  ];
31
31
 
32
- /**@type {ServiceWorkerGlobalScope} */
32
+ /** @type {ServiceWorkerGlobalScope} */
33
33
  const serviceWorkerGlobalScope = self;
34
34
 
35
35
  /**
@@ -48,26 +48,65 @@ serviceWorkerGlobalScope.addEventListener("install", (event) => {
48
48
  })());
49
49
  });
50
50
 
51
- serviceWorkerGlobalScope.addEventListener("activate", () => console.log("Service worker activated"));
51
+ serviceWorkerGlobalScope.addEventListener("activate", (event) => {
52
+ console.log("Service worker activated");
53
+ event.waitUntil(
54
+ clients.claim().then(() => {
55
+ return clients.matchAll({ type: "window" }).then((clients) => {
56
+ clients.forEach((client) => {
57
+ client.navigate(client.url);
58
+ });
59
+ });
60
+ })
61
+ );
62
+ });
63
+
64
+ /** @type {(response: Response) => Response} */
65
+ const getCrossOriginIsolatedResponse = (response) => {
66
+ // Modify headers to include COOP & COEP
67
+ const headers = new Headers(response.headers);
68
+ headers.set("Cross-Origin-Opener-Policy", "same-origin");
69
+ headers.set("Cross-Origin-Embedder-Policy", "require-corp");
70
+
71
+ // Create a new response with the modified headers
72
+ const modifiedResponse = new Response(response.body, {
73
+ status: response.status,
74
+ statusText: response.statusText,
75
+ headers
76
+ });
52
77
 
78
+ return modifiedResponse;
79
+ };
80
+
81
+ /**
82
+ * Intercept fetch requests to enforce COOP and COEP headers.
83
+ */
53
84
  serviceWorkerGlobalScope.addEventListener("fetch", (event) => {
85
+
54
86
  event.respondWith((async () => {
55
87
  const cache = await caches.open(CACHE_NAME);
56
88
  const cachedResponse = await cache.match(event.request);
89
+
57
90
  if (cachedResponse) {
58
- return cachedResponse;
91
+ return getCrossOriginIsolatedResponse(cachedResponse);
59
92
  } else {
60
93
  try {
61
94
  const fetchResponse = await fetch(event.request);
62
- // Ensure the response is valid before caching it
95
+
63
96
  if (event.request.method === "GET" && fetchResponse && fetchResponse.status === 200 && fetchResponse.type === "basic") {
64
- cache.put(event.request, fetchResponse.clone());
97
+ const modifiedResponse = getCrossOriginIsolatedResponse(fetchResponse);
98
+ // Store the modified response in the cache
99
+ await cache.put(event.request, modifiedResponse.clone());
100
+ // Return the modified response to the browser
101
+ return modifiedResponse;
65
102
  }
103
+
66
104
  return fetchResponse;
67
- } catch (e) {
68
- // Network access failure
69
- console.log("Network access error", e);
105
+ } catch (error) {
106
+ console.error("Network access error", error);
107
+ return new Response("Network error", { status: 503, statusText: "Service Unavailable" });
70
108
  }
71
109
  }
72
110
  })());
73
111
  });
112
+
@@ -230,6 +230,49 @@ interface AudioData$1 {
230
230
  * @param wasmFile path to `libfaust-wasm.wasm`
231
231
  */
232
232
  export declare const instantiateFaustModuleFromFile: (jsFile: string, dataFile?: string, wasmFile?: string) => Promise<FaustModule>;
233
+ declare class FaustAudioWorkletCommunicator {
234
+ protected readonly port: MessagePort;
235
+ protected readonly supportSharedArrayBuffer: boolean;
236
+ protected readonly byteLength: number;
237
+ protected uin8Invert: Uint8ClampedArray;
238
+ protected uin8NewAccData: Uint8ClampedArray;
239
+ protected uin8NewGyrData: Uint8ClampedArray;
240
+ protected f32Acc: Float32Array;
241
+ protected f32Gyr: Float32Array;
242
+ constructor(port: MessagePort);
243
+ initializeBuffer(ab: SharedArrayBuffer | ArrayBuffer): void;
244
+ setNewAccDataAvailable(value: boolean): void;
245
+ getNewAccDataAvailable(): boolean;
246
+ setNewGyrDataAvailable(value: boolean): void;
247
+ getNewGyrDataAvailable(): boolean;
248
+ setAcc({ x, y, z }: {
249
+ x: number;
250
+ y: number;
251
+ z: number;
252
+ }, invert?: boolean): void;
253
+ getAcc(): {
254
+ x: number;
255
+ y: number;
256
+ z: number;
257
+ invert: boolean;
258
+ } | undefined;
259
+ setGyr({ alpha, beta, gamma }: {
260
+ alpha: number;
261
+ beta: number;
262
+ gamma: number;
263
+ }): void;
264
+ getGyr(): {
265
+ alpha: number;
266
+ beta: number;
267
+ gamma: number;
268
+ } | undefined;
269
+ }
270
+ declare class FaustAudioWorkletNodeCommunicator extends FaustAudioWorkletCommunicator {
271
+ constructor(port: MessagePort);
272
+ }
273
+ declare class FaustAudioWorkletProcessorCommunicator extends FaustAudioWorkletCommunicator {
274
+ constructor(port: MessagePort);
275
+ }
233
276
  /**
234
277
  * The Faust wasm instance interface.
235
278
  */
@@ -620,6 +663,29 @@ export interface IFaustBaseWebAudioDsp {
620
663
  * @return the DSP JSON description
621
664
  */
622
665
  getJSON(): string;
666
+ /**
667
+ * Start accelerometer and gyroscope handlers.
668
+ */
669
+ startSensors(): void;
670
+ /**
671
+ * Stop accelerometer and gyroscope handlers.
672
+ */
673
+ stopSensors(): void;
674
+ /** Indicating if the DSP handles the accelerometer */
675
+ readonly hasAccInput: boolean;
676
+ /**
677
+ * Accelerometer handling.
678
+ * accelerationIncludingGravity: DeviceMotionEvent["accelerationIncludingGravity"]
679
+ * invert: boolean
680
+ */
681
+ propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>, invert: boolean): void;
682
+ /** Indicating if the DSP handles the gyroscope */
683
+ readonly hasGyrInput: boolean;
684
+ /**
685
+ * Gyroscope handling.
686
+ * event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">
687
+ */
688
+ propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">): void;
623
689
  /**
624
690
  * Start the DSP audio processing.
625
691
  */
@@ -632,14 +698,6 @@ export interface IFaustBaseWebAudioDsp {
632
698
  * Destroy the DSP.
633
699
  */
634
700
  destroy(): void;
635
- /** Indicating if the DSP handles the accelerometer */
636
- readonly hasAccInput: boolean;
637
- /** Accelerometer handling */
638
- propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>, invert: boolean): void;
639
- /** Indicating if the DSP handles the gyroscope */
640
- readonly hasGyrInput: boolean;
641
- /** Gyroscope handling */
642
- propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">): void;
643
701
  }
644
702
  export interface IFaustMonoWebAudioDsp extends IFaustBaseWebAudioDsp {
645
703
  }
@@ -780,6 +838,8 @@ export declare class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
780
838
  start(): void;
781
839
  stop(): void;
782
840
  destroy(): void;
841
+ startSensors(): void;
842
+ stopSensors(): void;
783
843
  }
784
844
  export declare class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaustMonoWebAudioDsp {
785
845
  private fInstance;
@@ -876,6 +936,7 @@ export interface FaustAudioWorkletProcessorDependencies<Poly extends boolean = f
876
936
  FaustPolyWebAudioDsp: Poly extends true ? typeof FaustPolyWebAudioDsp : undefined;
877
937
  FaustWebAudioDspVoice: Poly extends true ? typeof FaustWebAudioDspVoice : undefined;
878
938
  FaustWasmInstantiator: typeof FaustWasmInstantiator;
939
+ FaustAudioWorkletProcessorCommunicator: typeof FaustAudioWorkletProcessorCommunicator;
879
940
  }
880
941
  export interface FaustAudioWorkletNodeOptions<Poly extends boolean = false> extends AudioWorkletNodeOptions {
881
942
  processorOptions: Poly extends true ? FaustPolyAudioWorkletProcessorOptions : FaustMonoAudioWorkletProcessorOptions;
@@ -926,6 +987,7 @@ export interface FaustFFTAudioWorkletProcessorDependencies {
926
987
  FaustBaseWebAudioDsp: typeof FaustBaseWebAudioDsp;
927
988
  FaustMonoWebAudioDsp: typeof FaustMonoWebAudioDsp;
928
989
  FaustWasmInstantiator: typeof FaustWasmInstantiator;
990
+ FaustAudioWorkletProcessorCommunicator: typeof FaustAudioWorkletProcessorCommunicator;
929
991
  FFTUtils: typeof FFTUtils;
930
992
  }
931
993
  export interface FaustFFTAudioWorkletNodeOptions extends AudioWorkletNodeOptions {
@@ -1132,6 +1194,8 @@ export declare class FaustOfflineProcessor<Poly extends boolean = false> {
1132
1194
  propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>, invert?: boolean): void;
1133
1195
  get hasGyrInput(): boolean;
1134
1196
  propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">): void;
1197
+ startSensors(): void;
1198
+ stopSensors(): void;
1135
1199
  /**
1136
1200
  * Render frames in an array.
1137
1201
  *
@@ -1288,7 +1352,9 @@ export declare class FaustAudioWorkletNode<Poly extends boolean = false> extends
1288
1352
  protected fPlotHandler: PlotHandler | null;
1289
1353
  protected fUICallback: UIHandler;
1290
1354
  protected fDescriptor: FaustUIInputItem[];
1355
+ protected fCommunicator: FaustAudioWorkletNodeCommunicator;
1291
1356
  constructor(context: BaseAudioContext, name: string, factory: LooseFaustDspFactory, options?: Partial<FaustAudioWorkletNodeOptions<Poly>>);
1357
+ protected handleMessageAux: (e: MessageEvent) => void;
1292
1358
  private handleDeviceMotion;
1293
1359
  private handleDeviceOrientation;
1294
1360
  /** Setup accelerometer and gyroscope handlers */
@@ -1358,8 +1424,9 @@ export declare class FaustScriptProcessorNode<Poly extends boolean = false> exte
1358
1424
  protected handleDeviceMotion: any;
1359
1425
  protected handleDeviceOrientation: any;
1360
1426
  init(instance: Poly extends true ? FaustPolyWebAudioDsp : FaustMonoWebAudioDsp): void;
1361
- /** Setup accelerometer and gyroscope handlers */
1427
+ /** Start accelerometer and gyroscope handlers */
1362
1428
  startSensors(): Promise<void>;
1429
+ /** Stop accelerometer and gyroscope handlers */
1363
1430
  stopSensors(): void;
1364
1431
  compute(input: Float32Array[], output: Float32Array[]): boolean;
1365
1432
  setOutputParamHandler(handler: OutputParamHandler): void;