@grame/faustwasm 0.2.2 → 0.3.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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grame/faustwasm",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "WebAssembly version of Faust Compiler",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/esm/index.d.ts",
@@ -15,6 +15,8 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
15
15
  protected fPlotHandler: PlotHandler | null;
16
16
  protected fUICallback: UIHandler;
17
17
  protected fDescriptor: FaustUIInputItem[];
18
+ #hasAccInput = false;
19
+ #hasGyrInput = false;
18
20
 
19
21
  constructor(context: BaseAudioContext, name: string, factory: LooseFaustDspFactory, options: FaustAudioWorkletNodeOptions<Poly>["processorOptions"], nodeOptions: Partial<FaustAudioWorkletNodeOptions> = {}) {
20
22
 
@@ -47,8 +49,15 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
47
49
  // Keep inputs adresses
48
50
  this.fInputsItems.push(item.address);
49
51
  this.fDescriptor.push(item);
52
+ if (!item.meta) return;
53
+ item.meta.forEach((meta) => {
54
+ const { midi, acc, gyr } = meta;
55
+ if (acc) this.#hasAccInput = true;
56
+ if (gyr) this.#hasGyrInput = true;
57
+ });
50
58
  }
51
59
  }
60
+
52
61
  FaustBaseWebAudioDsp.parseUI(this.fJSONDsp.ui, this.fUICallback);
53
62
 
54
63
  // Patch it with additional functions
@@ -62,6 +71,55 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
62
71
  }
63
72
 
64
73
  // Public API
74
+
75
+ /** Setup accelerometer and gyroscope handlers */
76
+ async listenMotion() {
77
+ if (this.hasAccInput) {
78
+ const handleDeviceMotion = ({ accelerationIncludingGravity }: DeviceMotionEvent) => {
79
+ if (!accelerationIncludingGravity) return;
80
+ const { x, y, z } = accelerationIncludingGravity;
81
+ this.propagateAcc({ x, y, z });
82
+ };
83
+ if (window.DeviceMotionEvent) {
84
+ if (typeof (window.DeviceMotionEvent as any).requestPermission === "function") { // for iOS 13+
85
+ try {
86
+ const response = await (window.DeviceMotionEvent as any).requestPermission();
87
+ if (response !== "granted") throw new Error("Unable to access the accelerometer.");
88
+ window.addEventListener("devicemotion", handleDeviceMotion, true);
89
+ } catch (error) {
90
+ console.error(error);
91
+ }
92
+ } else {
93
+ window.addEventListener("devicemotion", handleDeviceMotion, true);
94
+ }
95
+ } else {
96
+ // Browser doesn't support DeviceMotionEvent
97
+ console.log("Cannot set the accelerometer handler.");
98
+ }
99
+ }
100
+ if (this.hasGyrInput) {
101
+ const handleDeviceOrientation = ({ alpha, beta, gamma }: DeviceOrientationEvent) => {
102
+ this.propagateGyr({ alpha, beta, gamma });
103
+ };
104
+ if (window.DeviceMotionEvent) {
105
+ if (typeof (window.DeviceOrientationEvent as any).requestPermission === "function") { // for iOS 13+
106
+ try {
107
+ const response = await (window.DeviceOrientationEvent as any).requestPermission();
108
+ if (response !== "granted") throw new Error("Unable to access the gyroscope.");
109
+ window.addEventListener("deviceorientation", handleDeviceOrientation, true);
110
+ } catch (error) {
111
+ console.error(error);
112
+ }
113
+ } else {
114
+ window.addEventListener("deviceorientation", handleDeviceOrientation, true);
115
+ }
116
+ } else {
117
+ // Browser doesn't support DeviceMotionEvent
118
+ console.log("Cannot set the gyroscope handler.");
119
+ }
120
+ }
121
+ }
122
+
65
123
  setOutputParamHandler(handler: OutputParamHandler | null) {
66
124
  this.fOutputHandler = handler;
67
125
  }
@@ -126,6 +184,20 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
126
184
  this.port.postMessage(e);
127
185
  }
128
186
 
187
+ get hasAccInput() { return this.#hasAccInput; }
188
+ propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>) {
189
+ if (!accelerationIncludingGravity) return;
190
+ const e = { type: "acc", data: accelerationIncludingGravity };
191
+ this.port.postMessage(e);
192
+ }
193
+
194
+ get hasGyrInput() { return this.#hasGyrInput; }
195
+ propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">) {
196
+ if (!event) return;
197
+ const e = { type: "gyr", data: event };
198
+ this.port.postMessage(e);
199
+ }
200
+
129
201
  setParamValue(path: string, value: number) {
130
202
  const e = { type: "param", data: { path, value } };
131
203
  this.port.postMessage(e);
@@ -158,6 +230,7 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
158
230
  this.port.postMessage({ type: "destroy" });
159
231
  this.port.close();
160
232
  }
233
+
161
234
  }
162
235
 
163
236
  /**
@@ -125,13 +125,34 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
125
125
  const msg = e.data;
126
126
 
127
127
  switch (msg.type) {
128
+ // Sensors messages
129
+ case "acc": {
130
+ this.propagateAcc(msg.data);
131
+ break;
132
+ }
133
+ case "gyr": {
134
+ this.propagateGyr(msg.data);
135
+ break;
136
+ }
128
137
  // Generic MIDI message
129
- case "midi": this.midiMessage(msg.data); break;
138
+ case "midi": {
139
+ this.midiMessage(msg.data);
140
+ break;
141
+ }
130
142
  // Typed MIDI message
131
- case "ctrlChange": this.ctrlChange(msg.data[0], msg.data[1], msg.data[2]); break;
132
- case "pitchWheel": this.pitchWheel(msg.data[0], msg.data[1]); break;
143
+ case "ctrlChange": {
144
+ this.ctrlChange(msg.data[0], msg.data[1], msg.data[2]);
145
+ break;
146
+ }
147
+ case "pitchWheel": {
148
+ this.pitchWheel(msg.data[0], msg.data[1]);
149
+ break;
150
+ }
133
151
  // Generic data message
134
- case "param": this.setParamValue(msg.data.path, msg.data.value); break;
152
+ case "param": {
153
+ this.setParamValue(msg.data.path, msg.data.value);
154
+ break;
155
+ }
135
156
  // Plot handler set on demand
136
157
  case "setPlotHandler": {
137
158
  if (msg.data) {
@@ -175,6 +196,14 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
175
196
  protected pitchWheel(channel: number, wheel: number) {
176
197
  this.fDSPCode.pitchWheel(channel, wheel);
177
198
  }
199
+
200
+ protected propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>) {
201
+ this.fDSPCode.propagateAcc(accelerationIncludingGravity);
202
+ }
203
+
204
+ protected propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">) {
205
+ this.fDSPCode.propagateGyr(event);
206
+ }
178
207
  }
179
208
 
180
209
  /**
@@ -6,9 +6,11 @@ import FaustWasmInstantiator from "./FaustWasmInstantiator";
6
6
  import { FaustMonoOfflineProcessor, FaustPolyOfflineProcessor, IFaustMonoOfflineProcessor, IFaustPolyOfflineProcessor } from "./FaustOfflineProcessor";
7
7
  import { FaustMonoScriptProcessorNode, FaustPolyScriptProcessorNode } from "./FaustScriptProcessorNode";
8
8
  import { FaustBaseWebAudioDsp, FaustMonoWebAudioDsp, FaustPolyWebAudioDsp, FaustWebAudioDspVoice, IFaustMonoWebAudioNode, IFaustPolyWebAudioNode, Soundfile, WasmAllocator } from "./FaustWebAudioDsp";
9
+ import SoundfileReader from "./SoundfileReader";
10
+ import FaustSensors from "./FaustSensors";
9
11
  import type { IFaustCompiler } from "./FaustCompiler";
10
12
  import type { FaustDspFactory, FaustUIDescriptor, FaustDspMeta, FFTUtils, LooseFaustDspFactory, AudioData } from "./types";
11
- import SoundfileReader from "./SoundfileReader";
13
+
12
14
 
13
15
  export interface GeneratorSupportingSoundfiles {
14
16
  /**
@@ -252,6 +254,7 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
252
254
  if (sp) {
253
255
  const instance = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
254
256
  const monoDsp = new FaustMonoWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize, factory.soundfiles);
257
+
255
258
  const sp = context.createScriptProcessor(bufferSize, monoDsp.getNumInputs(), monoDsp.getNumOutputs()) as FaustMonoScriptProcessorNode;
256
259
  Object.setPrototypeOf(sp, FaustMonoScriptProcessorNode.prototype);
257
260
  sp.init(monoDsp);
@@ -282,6 +285,8 @@ var ${Soundfile.name} = ${Soundfile.toString()}
282
285
  var Soundfile = ${Soundfile.name};
283
286
  var ${WasmAllocator.name} = ${WasmAllocator.toString()}
284
287
  var WasmAllocator = ${WasmAllocator.name};
288
+ var ${FaustSensors.name} = ${FaustSensors.toString()}
289
+ var FaustSensors = ${FaustSensors.name};
285
290
  // Put them in dependencies
286
291
  const dependencies = {
287
292
  FaustBaseWebAudioDsp,
@@ -302,7 +307,8 @@ const dependencies = {
302
307
  }
303
308
  }
304
309
  // Create the AWN
305
- const node = new FaustMonoAudioWorkletNode(context, processorName, factory, sampleSize)
310
+ const node = new FaustMonoAudioWorkletNode(context, processorName, factory, sampleSize);
311
+
306
312
  return node as SP extends true ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
307
313
  }
308
314
  }
@@ -345,6 +351,8 @@ var ${Soundfile.name} = ${Soundfile.toString()}
345
351
  var Soundfile = ${Soundfile.name};
346
352
  var ${WasmAllocator.name} = ${WasmAllocator.toString()}
347
353
  var WasmAllocator = ${WasmAllocator.name};
354
+ var ${FaustSensors.name} = ${FaustSensors.toString()}
355
+ var FaustSensors = ${FaustSensors.name};
348
356
  var FFTUtils = ${fftUtils.toString()}
349
357
  // Put them in dependencies
350
358
  const dependencies = {
@@ -578,6 +586,7 @@ process = adaptorIns(dsp_code.process) : dsp_code.effect : adaptorOuts;
578
586
  const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
579
587
  const soundfiles = { ...effectFactory?.soundfiles, ...voiceFactory.soundfiles };
580
588
  const polyDsp = new FaustPolyWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize, soundfiles);
589
+
581
590
  const sp = context.createScriptProcessor(bufferSize, polyDsp.getNumInputs(), polyDsp.getNumOutputs()) as FaustPolyScriptProcessorNode;
582
591
  Object.setPrototypeOf(sp, FaustPolyScriptProcessorNode.prototype);
583
592
  sp.init(polyDsp);
@@ -611,6 +620,8 @@ var ${Soundfile.name} = ${Soundfile.toString()}
611
620
  var Soundfile = ${Soundfile.name};
612
621
  var ${WasmAllocator.name} = ${WasmAllocator.toString()}
613
622
  var WasmAllocator = ${WasmAllocator.name};
623
+ var ${FaustSensors.name} = ${FaustSensors.toString()}
624
+ var FaustSensors = ${FaustSensors.name};
614
625
  // Put them in dependencies
615
626
  const dependencies = {
616
627
  FaustBaseWebAudioDsp,
@@ -632,6 +643,7 @@ const dependencies = {
632
643
  }
633
644
  // Create the AWN
634
645
  const node = new FaustPolyAudioWorkletNode(context, processorName, voiceFactory, mixerModule, voices, sampleSize, effectFactory || undefined);
646
+
635
647
  return node as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
636
648
  }
637
649
  }
@@ -81,6 +81,16 @@ export class FaustOfflineProcessor<Poly extends boolean = false> {
81
81
 
82
82
  destroy() { this.fDSPCode.destroy(); }
83
83
 
84
+ get hasAccInput() { return this.fDSPCode.hasAccInput; }
85
+ propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>) {
86
+ this.fDSPCode.propagateAcc(accelerationIncludingGravity);
87
+ }
88
+
89
+ get hasGyrInput() { return this.fDSPCode.hasGyrInput; }
90
+ propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">) {
91
+ this.fDSPCode.propagateGyr(event);
92
+ }
93
+
84
94
  /**
85
95
  * Render frames in an array.
86
96
  *
@@ -15,7 +15,7 @@ export class FaustScriptProcessorNode<Poly extends boolean = false> extends (glo
15
15
 
16
16
  this.fInputs = new Array(this.fDSPCode.getNumInputs());
17
17
  this.fOutputs = new Array(this.fDSPCode.getNumOutputs());
18
-
18
+
19
19
  this.onaudioprocess = (e) => {
20
20
 
21
21
  // Read inputs
@@ -33,7 +33,57 @@ export class FaustScriptProcessorNode<Poly extends boolean = false> extends (glo
33
33
 
34
34
  this.start();
35
35
  }
36
+
36
37
  // Public API
38
+
39
+ /** Setup accelerometer and gyroscope handlers */
40
+ async listenMotion() {
41
+ if (this.hasAccInput) {
42
+ const handleDeviceMotion = ({ accelerationIncludingGravity }: DeviceMotionEvent) => {
43
+ if (!accelerationIncludingGravity) return;
44
+ const { x, y, z } = accelerationIncludingGravity;
45
+ this.propagateAcc({ x, y, z });
46
+ };
47
+ if (window.DeviceMotionEvent) {
48
+ if (typeof (window.DeviceMotionEvent as any).requestPermission === "function") { // for iOS 13+
49
+ try {
50
+ const response = await (window.DeviceMotionEvent as any).requestPermission();
51
+ if (response !== "granted") throw new Error("Unable to access the accelerometer.");
52
+ window.addEventListener("devicemotion", handleDeviceMotion, true);
53
+ } catch (error) {
54
+ console.error(error);
55
+ }
56
+ } else {
57
+ window.addEventListener("devicemotion", handleDeviceMotion, true);
58
+ }
59
+ } else {
60
+ // Browser doesn't support DeviceMotionEvent
61
+ console.log("Cannot set the accelerometer handler.");
62
+ }
63
+ }
64
+ if (this.hasGyrInput) {
65
+ const handleDeviceOrientation = ({ alpha, beta, gamma }: DeviceOrientationEvent) => {
66
+ this.propagateGyr({ alpha, beta, gamma });
67
+ };
68
+ if (window.DeviceMotionEvent) {
69
+ if (typeof (window.DeviceOrientationEvent as any).requestPermission === "function") { // for iOS 13+
70
+ try {
71
+ const response = await (window.DeviceOrientationEvent as any).requestPermission();
72
+ if (response !== "granted") throw new Error("Unable to access the gyroscope.");
73
+ window.addEventListener("deviceorientation", handleDeviceOrientation, true);
74
+ } catch (error) {
75
+ console.error(error);
76
+ }
77
+ } else {
78
+ window.addEventListener("deviceorientation", handleDeviceOrientation, true);
79
+ }
80
+ } else {
81
+ // Browser doesn't support DeviceMotionEvent
82
+ console.log("Cannot set the gyroscope handler.");
83
+ }
84
+ }
85
+ }
86
+
37
87
  compute(input: Float32Array[], output: Float32Array[]) { return this.fDSPCode.compute(input, output); }
38
88
 
39
89
  setOutputParamHandler(handler: OutputParamHandler) { this.fDSPCode.setOutputParamHandler(handler); }
@@ -68,6 +118,16 @@ export class FaustScriptProcessorNode<Poly extends boolean = false> extends (glo
68
118
  stop() { this.fDSPCode.stop(); }
69
119
 
70
120
  destroy() { this.fDSPCode.destroy(); }
121
+
122
+ get hasAccInput() { return this.fDSPCode.hasAccInput; }
123
+ propagateAcc(accelerationIncludingGravity: NonNullable<DeviceMotionEvent["accelerationIncludingGravity"]>) {
124
+ this.fDSPCode.propagateAcc(accelerationIncludingGravity);
125
+ }
126
+
127
+ get hasGyrInput() { return this.fDSPCode.hasGyrInput; }
128
+ propagateGyr(event: Pick<DeviceOrientationEvent, "alpha" | "beta" | "gamma">) {
129
+ this.fDSPCode.propagateGyr(event);
130
+ }
71
131
  }
72
132
 
73
133
  export class FaustMonoScriptProcessorNode extends FaustScriptProcessorNode<false> implements IFaustMonoWebAudioDsp {