@grame/faustwasm 0.0.67 → 0.1.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 (42) hide show
  1. package/README.md +1 -1
  2. package/assets/standalone/faust-ui/index.css +17 -0
  3. package/assets/standalone/faust-ui/index.css.map +1 -1
  4. package/assets/standalone/faust-ui/index.js +294 -47
  5. package/assets/standalone/faust-ui/index.js.map +1 -1
  6. package/assets/standalone/faustwasm/index.d.ts +145 -4
  7. package/assets/standalone/faustwasm/index.js +449 -73
  8. package/assets/standalone/faustwasm/index.js.map +3 -3
  9. package/assets/standalone/index-poly.js +9 -6
  10. package/assets/standalone/index.js +9 -6
  11. package/dist/cjs/index.d.ts +145 -4
  12. package/dist/cjs/index.js +449 -73
  13. package/dist/cjs/index.js.map +3 -3
  14. package/dist/cjs-bundle/index.d.ts +145 -4
  15. package/dist/cjs-bundle/index.js +550 -174
  16. package/dist/cjs-bundle/index.js.map +3 -3
  17. package/dist/esm/index.d.ts +145 -4
  18. package/dist/esm/index.js +449 -73
  19. package/dist/esm/index.js.map +3 -3
  20. package/dist/esm-bundle/index.d.ts +145 -4
  21. package/dist/esm-bundle/index.js +550 -174
  22. package/dist/esm-bundle/index.js.map +3 -3
  23. package/libfaust-wasm/libfaust-wasm.data +0 -0
  24. package/libfaust-wasm/libfaust-wasm.js +1 -1
  25. package/libfaust-wasm/libfaust-wasm.wasm +0 -0
  26. package/package.json +2 -2
  27. package/src/FaustAudioWorkletProcessor.ts +16 -3
  28. package/src/FaustDspGenerator.ts +18 -3
  29. package/src/FaustWasmInstantiator.ts +62 -26
  30. package/src/FaustWebAudioDsp.ts +682 -51
  31. package/src/faust2wasmFiles.js +6 -4
  32. package/test/faustlive-wasm/faust-ui/index.css +17 -0
  33. package/test/faustlive-wasm/faust-ui/index.css.map +1 -1
  34. package/test/faustlive-wasm/faust-ui/index.js +294 -47
  35. package/test/faustlive-wasm/faust-ui/index.js.map +1 -1
  36. package/test/faustlive-wasm/faustwasm/index.d.ts +145 -4
  37. package/test/faustlive-wasm/faustwasm/index.js +449 -73
  38. package/test/faustlive-wasm/faustwasm/index.js.map +3 -3
  39. package/TODO.md +0 -5
  40. package/test/tp0.dsp +0 -17
  41. package/test/tp0.dsp.json +0 -30
  42. package/test/tp0_local.dsp +0 -5
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grame/faustwasm",
3
- "version": "0.0.67",
3
+ "version": "0.1.0",
4
4
  "description": "WebAssembly version of Faust Compiler",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/esm/index.d.ts",
@@ -39,7 +39,7 @@
39
39
  "homepage": "https://github.com/grame-cncm/faustwasm#readme",
40
40
  "devDependencies": {
41
41
  "@aws-crypto/sha256-js": "^2.0.1",
42
- "@shren/faust-ui": "^1.1.8",
42
+ "@shren/faust-ui": "^1.1.9",
43
43
  "@types/emscripten": "^1.39.5",
44
44
  "@types/node": "^16.11.7",
45
45
  "@types/webmidi": "^2.0.6",
@@ -42,7 +42,6 @@ export interface FaustPolyAudioWorkletProcessorOptions extends FaustAudioWorklet
42
42
  effectFactory?: LooseFaustDspFactory;
43
43
  }
44
44
 
45
-
46
45
  // Dynamic AudioWorkletProcessor code generator
47
46
  const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencies: FaustAudioWorkletProcessorDependencies<Poly>, faustData: FaustData, register = true): typeof AudioWorkletProcessor => {
48
47
  const { registerProcessor, AudioWorkletProcessor, sampleRate } = globalThis as unknown as AudioWorkletGlobalScope;
@@ -51,7 +50,7 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
51
50
  FaustBaseWebAudioDsp,
52
51
  FaustWasmInstantiator
53
52
  } = dependencies;
54
-
53
+
55
54
  const {
56
55
  processorName,
57
56
  dspName,
@@ -88,7 +87,7 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
88
87
 
89
88
  // Setup port message handling
90
89
  this.port.onmessage = (e: MessageEvent) => this.handleMessageAux(e);
91
-
90
+
92
91
  const { parameterDescriptors } = (this.constructor as typeof AudioWorkletProcessor);
93
92
  parameterDescriptors.forEach((pd) => {
94
93
  this.paramValuesCache[pd.name] = pd.defaultValue || 0;
@@ -189,9 +188,16 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
189
188
  const { factory, sampleSize } = options.processorOptions;
190
189
 
191
190
  const instance = FaustWasmInstantiator.createSyncMonoDSPInstance(factory);
191
+
192
192
  // Create Monophonic DSP
193
193
  this.fDSPCode = new FaustMonoWebAudioDsp(instance, sampleRate, sampleSize, 128);
194
194
 
195
+ // Check for soundfile support
196
+ if (this.fDSPCode.hasSoundfiles()) {
197
+ console.error("FaustAudioWorkletProcessor: Soundfile support is not implemented yet, switch to ScriptProcessorNode for now");
198
+ return;
199
+ }
200
+
195
201
  // Setup output handler
196
202
  this.fDSPCode.setOutputParamHandler((path, value) => this.port.postMessage({ path, value, type: "param" }));
197
203
 
@@ -211,9 +217,16 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
211
217
  const { voiceFactory, mixerModule, voices, effectFactory, sampleSize } = options.processorOptions;
212
218
 
213
219
  const instance = FaustWasmInstantiator.createSyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory);
220
+
214
221
  // Create Polyphonic DSP
215
222
  this.fDSPCode = new FaustPolyWebAudioDsp(instance, sampleRate, sampleSize, 128);
216
223
 
224
+ // Check for soundfile support
225
+ if (this.fDSPCode.hasSoundfiles()) {
226
+ console.error("FaustAudioWorkletProcessor: Soundfile support is not implemented yet, switch to ScriptProcessorNode for now");
227
+ return;
228
+ }
229
+
217
230
  // Setup port message handling
218
231
  this.port.onmessage = (e: MessageEvent) => this.handleMessageAux(e);
219
232
 
@@ -112,6 +112,7 @@ export interface IFaustPolyDspGenerator {
112
112
  voiceFactory: FaustDspFactory | null;
113
113
  effectFactory?: FaustDspFactory | null;
114
114
  } | null>;
115
+
115
116
  /**
116
117
  * Create a polyphonic WebAudio node (either ScriptProcessorNode or AudioWorkletNode).
117
118
  *
@@ -189,9 +190,12 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
189
190
  }
190
191
  async compile(compiler: IFaustCompiler, name: string, code: string, args: string) {
191
192
  this.factory = await compiler.createMonoDSPFactory(name, code, args);
192
- if (!this.factory) return null;
193
- this.name = name;
194
- return this;
193
+ if (this.factory) {
194
+ this.name = name;
195
+ return this;
196
+ } else {
197
+ return null;
198
+ }
195
199
  }
196
200
 
197
201
  async createNode<SP extends boolean = false>(
@@ -209,6 +213,8 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
209
213
  if (sp) {
210
214
  const instance = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
211
215
  const monoDsp = new FaustMonoWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize);
216
+ // Initialize the DSP instance, possibly loading soundfiles
217
+ await monoDsp.init(context);
212
218
  const sp = context.createScriptProcessor(bufferSize, monoDsp.getNumInputs(), monoDsp.getNumOutputs()) as FaustMonoScriptProcessorNode;
213
219
  Object.setPrototypeOf(sp, FaustMonoScriptProcessorNode.prototype);
214
220
  sp.init(monoDsp);
@@ -255,6 +261,7 @@ const dependencies = {
255
261
  return node as SP extends true ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
256
262
  }
257
263
  }
264
+
258
265
  async createFFTNode(
259
266
  context: BaseAudioContext,
260
267
  fftUtils: typeof FFTUtils,
@@ -325,6 +332,7 @@ const dependencies = {
325
332
  }
326
333
  return node;
327
334
  }
335
+
328
336
  async createAudioWorkletProcessor(
329
337
  name = this.name,
330
338
  factory = this.factory as LooseFaustDspFactory,
@@ -359,6 +367,7 @@ const dependencies = {
359
367
  throw e;
360
368
  }
361
369
  }
370
+
362
371
  async createOfflineProcessor(
363
372
  sampleRate: number,
364
373
  bufferSize: number,
@@ -370,6 +379,8 @@ const dependencies = {
370
379
  const instance = await FaustWasmInstantiator.createAsyncMonoDSPInstance(factory);
371
380
  const sampleSize = meta.compile_options.match("-double") ? 8 : 4;
372
381
  const monoDsp = new FaustMonoWebAudioDsp(instance, sampleRate, sampleSize, bufferSize);
382
+ // Initialize the DSP instance, no soundfiles loading for now (TODO ?)
383
+ await monoDsp.init(null);
373
384
  return new FaustMonoOfflineProcessor(monoDsp, bufferSize);
374
385
  }
375
386
 
@@ -492,6 +503,8 @@ export class FaustPolyDspGenerator implements IFaustPolyDspGenerator {
492
503
  if (sp) {
493
504
  const instance = await FaustWasmInstantiator.createAsyncPolyDSPInstance(voiceFactory, mixerModule, voices, effectFactory || undefined);
494
505
  const polyDsp = new FaustPolyWebAudioDsp(instance, context.sampleRate, sampleSize, bufferSize);
506
+ // Initialize the DSP instance, possibly loading soundfiles
507
+ await polyDsp.init(context);
495
508
  const sp = context.createScriptProcessor(bufferSize, polyDsp.getNumInputs(), polyDsp.getNumOutputs()) as FaustPolyScriptProcessorNode;
496
509
  Object.setPrototypeOf(sp, FaustPolyScriptProcessorNode.prototype);
497
510
  sp.init(polyDsp);
@@ -540,6 +553,7 @@ const dependencies = {
540
553
  return node as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
541
554
  }
542
555
  }
556
+
543
557
  async createAudioWorkletProcessor(
544
558
  name = this.name,
545
559
  voiceFactory = this.voiceFactory as LooseFaustDspFactory,
@@ -577,6 +591,7 @@ const dependencies = {
577
591
  throw e;
578
592
  }
579
593
  }
594
+
580
595
  async createOfflineProcessor(
581
596
  sampleRate: number,
582
597
  bufferSize: number,
@@ -21,7 +21,7 @@ class FaustWasmInstantiator {
21
21
  _coshf: Math.cosh, _sinhf: Math.sinh, _tanhf: Math.tanh,
22
22
  _isnanf: Number.isNaN, _isinff: (x: number) => !isFinite(x),
23
23
  _copysignf: (x: number, y: number) => (Math.sign(x) === Math.sign(y) ? x : -x),
24
-
24
+
25
25
  // Double version
26
26
  _acos: Math.acos, _asin: Math.asin, _atan: Math.atan, _atan2: Math.atan2,
27
27
  _ceil: Math.ceil, _cos: Math.cos, _exp: Math.exp, _floor: Math.floor,
@@ -33,12 +33,12 @@ class FaustWasmInstantiator {
33
33
  _cosh: Math.cosh, _sinh: Math.sinh, _tanh: Math.tanh,
34
34
  _isnan: Number.isNaN, _isinf: (x: number) => !isFinite(x),
35
35
  _copysign: (x: number, y: number) => (Math.sign(x) === Math.sign(y) ? x : -x),
36
-
36
+
37
37
  table: new WebAssembly.Table({ initial: 0, element: "anyfunc" })
38
38
  }
39
39
  };
40
40
  }
41
- private static createWasmMemory(voicesIn: number, sampleSize: number, dspMeta: FaustDspMeta, effectMeta: FaustDspMeta, bufferSize: number) {
41
+ private static createWasmMemoryPoly(voicesIn: number, sampleSize: number, dspMeta: FaustDspMeta, effectMeta: FaustDspMeta, bufferSize: number) {
42
42
  // Hack : at least 4 voices (to avoid weird wasm memory bug?)
43
43
  const voices = Math.max(4, voicesIn);
44
44
  // Memory allocator
@@ -52,26 +52,43 @@ class FaustWasmInstantiator {
52
52
  let memorySize = pow2limit(
53
53
  effectSize
54
54
  + dspMeta.size * voices
55
- + (dspMeta.inputs + dspMeta.outputs * 2)
55
+ + (dspMeta.inputs + dspMeta.outputs * 2) // + 2 for effect
56
56
  * (ptrSize + bufferSize * sampleSize)
57
57
  ) / 65536;
58
58
  memorySize = Math.max(2, memorySize); // At least 2
59
- return new WebAssembly.Memory({ initial: memorySize, maximum: memorySize });
59
+ return new WebAssembly.Memory({ initial: memorySize });
60
60
  };
61
- private static createMonoDSPInstanceAux(instance: WebAssembly.Instance, json: string) {
61
+
62
+ private static createWasmMemoryMono(sampleSize: number, dspMeta: FaustDspMeta, bufferSize: number) {
63
+ // Memory allocator
64
+ const ptrSize = sampleSize; // Done on wast/wasm backend side
65
+ const memorySize = (dspMeta.size + (dspMeta.inputs + dspMeta.outputs) * (ptrSize + bufferSize * sampleSize)) / 65536;
66
+ return new WebAssembly.Memory({ initial: memorySize * 2 }); // Safer to have a bit more memory
67
+ }
68
+
69
+ private static createMonoDSPInstanceAux(instance: WebAssembly.Instance, json: string, mem: WebAssembly.Memory | null = null) {
62
70
  const functions = instance.exports as IFaustDspInstance & WebAssembly.Exports;
63
71
  const api = new FaustDspInstance(functions);
64
- const memory: any = instance.exports.memory;
72
+ const memory: any = (mem) ? mem : instance.exports.memory;
65
73
  return { memory, api, json } as FaustMonoDspInstance;
66
74
  }
67
- private static createMemoryAux(voices: number, voiceFactory: LooseFaustDspFactory, effectFactory?: LooseFaustDspFactory) {
75
+
76
+ private static createMemoryMono(monoFactory: LooseFaustDspFactory) {
77
+ // Parse JSON to get 'size' and 'inputs/outputs' infos
78
+ const monoMeta: FaustDspMeta = JSON.parse(monoFactory.json);
79
+ const sampleSize = monoMeta.compile_options.match("-double") ? 8 : 4;
80
+ return this.createWasmMemoryMono(sampleSize, monoMeta, 8192);
81
+
82
+ }
83
+ private static createMemoryPoly(voices: number, voiceFactory: LooseFaustDspFactory, effectFactory?: LooseFaustDspFactory) {
68
84
  // Parse JSON to get 'size' and 'inputs/outputs' infos
69
85
  const voiceMeta: FaustDspMeta = JSON.parse(voiceFactory.json);
70
86
  const effectMeta: FaustDspMeta = (effectFactory && effectFactory.json) ? JSON.parse(effectFactory.json) : null;
71
87
  const sampleSize = voiceMeta.compile_options.match("-double") ? 8 : 4;
72
88
  // Memory will be shared by voice, mixer and (possibly) effect instances
73
- return this.createWasmMemory(voices, sampleSize, voiceMeta, effectMeta, 8192);
89
+ return this.createWasmMemoryPoly(voices, sampleSize, voiceMeta, effectMeta, 8192);
74
90
  }
91
+
75
92
  private static createMixerAux(mixerModule: WebAssembly.Module, memory: WebAssembly.Memory) {
76
93
  // Create mixer instance
77
94
  const mixerImport = {
@@ -82,6 +99,7 @@ class FaustWasmInstantiator {
82
99
  const mixerFunctions = mixerInstance.exports as IFaustMixerInstance & WebAssembly.Exports;
83
100
  return mixerFunctions;
84
101
  }
102
+
85
103
  // Public API
86
104
  static async loadDSPFactory(wasmPath: string, jsonPath: string) {
87
105
  const wasmFile = await fetch(wasmPath);
@@ -102,7 +120,7 @@ class FaustWasmInstantiator {
102
120
  throw e;
103
121
  }
104
122
  }
105
-
123
+
106
124
  static async loadDSPMixer(mixerPath: string, fs?: typeof FS) {
107
125
  try {
108
126
  let mixerBuffer = null;
@@ -119,26 +137,44 @@ class FaustWasmInstantiator {
119
137
  throw e;
120
138
  }
121
139
  }
122
-
140
+
123
141
  static async createAsyncMonoDSPInstance(factory: LooseFaustDspFactory) {
124
- const instance = await WebAssembly.instantiate(factory.module, this.createWasmImport());
125
- return this.createMonoDSPInstanceAux(instance, factory.json);
142
+ const parsedJson = JSON.parse(factory.json);
143
+ // If the JSON contains a soundfile UI element, we need to create a memory object
144
+ if (Array.isArray(parsedJson.ui) && parsedJson.ui.some((group: { items: any[]; }) => group.items?.some(item => item.type === "soundfile"))) {
145
+ const memory = this.createMemoryMono(factory);
146
+ const instance = await WebAssembly.instantiate(factory.module, this.createWasmImport(memory));
147
+ return this.createMonoDSPInstanceAux(instance, factory.json, memory);
148
+ } else {
149
+ // Otherwise, we can create the instance using the wasm internal memory allocated by the wasm module
150
+ const instance = await WebAssembly.instantiate(factory.module, this.createWasmImport());
151
+ return this.createMonoDSPInstanceAux(instance, factory.json);
152
+ }
126
153
  }
127
-
154
+
128
155
  static createSyncMonoDSPInstance(factory: LooseFaustDspFactory) {
129
- const instance = new WebAssembly.Instance(factory.module, this.createWasmImport());
130
- return this.createMonoDSPInstanceAux(instance, factory.json);
156
+ const parsedJson = JSON.parse(factory.json);
157
+ // If the JSON contains a soundfile UI element, we need to create a memory object
158
+ if (Array.isArray(parsedJson.ui) && parsedJson.ui.some((group: { items: any[]; }) => group.items?.some(item => item.type === "soundfile"))) {
159
+ const memory = this.createMemoryMono(factory);
160
+ const instance = new WebAssembly.Instance(factory.module, this.createWasmImport(memory));
161
+ return this.createMonoDSPInstanceAux(instance, factory.json, memory);
162
+ } else {
163
+ // Otherwise, we can create the instance using the wasm internal memory allocated by the wasm module
164
+ const instance = new WebAssembly.Instance(factory.module, this.createWasmImport());
165
+ return this.createMonoDSPInstanceAux(instance, factory.json);
166
+ }
131
167
  }
132
-
168
+
133
169
  static async createAsyncPolyDSPInstance(voiceFactory: LooseFaustDspFactory, mixerModule: WebAssembly.Module, voices: number, effectFactory?: LooseFaustDspFactory): Promise<FaustPolyDspInstance> {
134
- const memory = this.createMemoryAux(voices, voiceFactory, effectFactory);
170
+ const memory = this.createMemoryPoly(voices, voiceFactory, effectFactory);
135
171
  // Create voice
136
172
  const voiceInstance = await WebAssembly.instantiate(voiceFactory.module, this.createWasmImport(memory));
137
173
  const voiceFunctions = voiceInstance.exports as IFaustDspInstance & WebAssembly.Exports;
138
174
  const voiceAPI = new FaustDspInstance(voiceFunctions);
139
175
  // Create mixer
140
176
  const mixerAPI = this.createMixerAux(mixerModule, memory);
141
-
177
+
142
178
  // Possibly create effect instance
143
179
  if (effectFactory) {
144
180
  const effectInstance = await WebAssembly.instantiate(effectFactory.module, this.createWasmImport(memory));
@@ -152,7 +188,7 @@ class FaustWasmInstantiator {
152
188
  mixerAPI,
153
189
  voiceJSON: voiceFactory.json,
154
190
  effectJSON: effectFactory.json
155
- };
191
+ } as FaustPolyDspInstance;
156
192
  } else {
157
193
  return {
158
194
  memory,
@@ -160,19 +196,19 @@ class FaustWasmInstantiator {
160
196
  voiceAPI,
161
197
  mixerAPI,
162
198
  voiceJSON: voiceFactory.json
163
- };
199
+ } as FaustPolyDspInstance;
164
200
  }
165
201
  }
166
-
202
+
167
203
  static createSyncPolyDSPInstance(voiceFactory: LooseFaustDspFactory, mixerModule: WebAssembly.Module, voices: number, effectFactory?: LooseFaustDspFactory): FaustPolyDspInstance {
168
- const memory = this.createMemoryAux(voices, voiceFactory, effectFactory);
204
+ const memory = this.createMemoryPoly(voices, voiceFactory, effectFactory);
169
205
  // Create voice
170
206
  const voiceInstance = new WebAssembly.Instance(voiceFactory.module, this.createWasmImport(memory));
171
207
  const voiceFunctions = voiceInstance.exports as IFaustDspInstance & WebAssembly.Exports;
172
208
  const voiceAPI = new FaustDspInstance(voiceFunctions);
173
209
  // Create mixer
174
210
  const mixerAPI = this.createMixerAux(mixerModule, memory);
175
-
211
+
176
212
  // Possibly create effect instance
177
213
  if (effectFactory) {
178
214
  const effectInstance = new WebAssembly.Instance(effectFactory.module, this.createWasmImport(memory));
@@ -186,7 +222,7 @@ class FaustWasmInstantiator {
186
222
  mixerAPI,
187
223
  voiceJSON: voiceFactory.json,
188
224
  effectJSON: effectFactory.json
189
- };
225
+ } as FaustPolyDspInstance;
190
226
  } else {
191
227
  return {
192
228
  memory,
@@ -194,7 +230,7 @@ class FaustWasmInstantiator {
194
230
  voiceAPI,
195
231
  mixerAPI,
196
232
  voiceJSON: voiceFactory.json
197
- };
233
+ } as FaustPolyDspInstance;
198
234
  }
199
235
  }
200
236
  }