@grame/faustwasm 0.1.7 → 0.2.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 (35) hide show
  1. package/assets/standalone/faustwasm/index.d.ts +132 -99
  2. package/assets/standalone/faustwasm/index.js +907 -1230
  3. package/assets/standalone/faustwasm/index.js.map +4 -4
  4. package/dist/cjs/index.d.ts +132 -99
  5. package/dist/cjs/index.js +909 -1212
  6. package/dist/cjs/index.js.map +4 -4
  7. package/dist/cjs-bundle/index.d.ts +132 -99
  8. package/dist/cjs-bundle/index.js +1012 -1317
  9. package/dist/cjs-bundle/index.js.map +4 -4
  10. package/dist/esm/index.d.ts +132 -99
  11. package/dist/esm/index.js +907 -1230
  12. package/dist/esm/index.js.map +4 -4
  13. package/dist/esm-bundle/index.d.ts +132 -99
  14. package/dist/esm-bundle/index.js +1012 -1321
  15. package/dist/esm-bundle/index.js.map +4 -4
  16. package/libfaust-wasm/libfaust-wasm.js +1 -1
  17. package/libfaust-wasm/libfaust-wasm.wasm +0 -0
  18. package/package.json +15 -15
  19. package/src/FaustAudioWorkletProcessor.ts +3 -14
  20. package/src/FaustCmajor.ts +6 -6
  21. package/src/FaustCompiler.ts +2 -2
  22. package/src/FaustDspGenerator.ts +109 -75
  23. package/src/FaustDspInstance.ts +2 -2
  24. package/src/FaustFFTAudioWorkletProcessor.ts +3 -1
  25. package/src/FaustWebAudioDsp.ts +224 -449
  26. package/src/SoundfileReader.ts +117 -0
  27. package/src/exports.ts +1 -0
  28. package/src/faust2wasmFiles.js +1 -1
  29. package/src/types.ts +15 -7
  30. package/test/faustlive-wasm/faustwasm/index.d.ts +132 -99
  31. package/test/faustlive-wasm/faustwasm/index.js +907 -1230
  32. package/test/faustlive-wasm/faustwasm/index.js.map +4 -4
  33. package/test/soundfile.dsp +15 -0
  34. package/test/soundfile1.dsp +23 -0
  35. package/test/web/soundfile.html +69 -0
@@ -1,5 +1,5 @@
1
1
  import type { FaustMonoDspInstance, FaustPolyDspInstance, IFaustDspInstance } from "./FaustDspInstance";
2
- import type { FaustDspMeta, FaustUIDescriptor, FaustUIGroup, FaustUIInputItem, FaustUIItem } from "./types";
2
+ import type { AudioData, FaustDspMeta, FaustUIDescriptor, FaustUIGroup, FaustUIInputItem, FaustUIItem, LooseFaustDspFactory } from "./types";
3
3
 
4
4
  // Public API
5
5
  export type OutputParamHandler = (path: string, value: number) => void;
@@ -10,6 +10,24 @@ export type MetadataHandler = (key: string, value: string) => void;
10
10
  // Implementation API
11
11
  export type UIHandler = (item: FaustUIItem) => void;
12
12
 
13
+ /** Definition of the AudioBufferItem type */
14
+ export interface AudioBufferItem {
15
+ pathName: string;
16
+ audioBuffer: AudioBuffer;
17
+ };
18
+
19
+ /** Definition of the SoundfileItem type */
20
+ export interface SoundfileItem {
21
+ /** Name of the soundfile */
22
+ name: string;
23
+ /** URL of the soundfile */
24
+ url: string;
25
+ /** Index in the DSP struct */
26
+ index: number;
27
+ /** Base pointer in wasm memory */
28
+ basePtr: number;
29
+ };
30
+
13
31
  /**
14
32
  * WasmAllocator is a basic memory management class designed to allocate
15
33
  * blocks of memory within a WebAssembly.Memory object. It provides a simple
@@ -19,7 +37,7 @@ export type UIHandler = (item: FaustUIItem) => void;
19
37
  * always allocating the next block at the end of the last. This approach does not
20
38
  * handle freeing of memory or reuse of memory spaces.
21
39
  */
22
- class WasmAllocator {
40
+ export class WasmAllocator {
23
41
  // The WebAssembly.Memory object this allocator will manage.
24
42
  private readonly memory: WebAssembly.Memory;
25
43
  // The number of bytes currently allocated. This serves as the "pointer" to the
@@ -110,23 +128,24 @@ class WasmAllocator {
110
128
  }
111
129
  }
112
130
 
113
- // Maximum number of soundfile parts.
114
- const MAX_SOUNDFILE_PARTS = 256;
115
-
116
- // Maximum number of channels.
117
- const MAX_CHAN = 64;
118
-
119
- // Maximum buffer size in frames.
120
- const BUFFER_SIZE = 1024;
121
-
122
- // Default sample rate.
123
- const SAMPLE_RATE = 44100;
124
-
125
131
  /**
126
132
  * Soundfile class to handle soundfile data in wasm memory.
127
133
  */
128
- class Soundfile {
129
- private readonly fPtr: number; // Pointer to the soundfile structure in wasm memory
134
+ export class Soundfile {
135
+ /** Maximum number of soundfile parts. */
136
+ static get MAX_SOUNDFILE_PARTS() { return 256; }
137
+
138
+ /** Maximum number of channels. */
139
+ static get MAX_CHAN() { return 64; }
140
+
141
+ /** Maximum buffer size in frames. */
142
+ static get BUFFER_SIZE() { return 1024; }
143
+
144
+ /** Default sample rate. */
145
+ static get SAMPLE_RATE() { return 44100; }
146
+
147
+ /** Pointer to the soundfile structure in wasm memory */
148
+ private readonly fPtr: number;
130
149
  private readonly fBuffers: number;
131
150
  private readonly fLength: number;
132
151
  private readonly fSR: number;
@@ -155,9 +174,9 @@ class Soundfile {
155
174
  this.fPtr = allocator.alloc(4 * this.fPtrSize); // 4 fPtrSize: fBuffers, fLength, fSR, fOffset
156
175
 
157
176
  // Use the 4 or 8 bytes size for int. The access are then adapted in copyToOut and emptyFile methods
158
- this.fLength = allocator.alloc(MAX_SOUNDFILE_PARTS * this.fIntSize);
159
- this.fSR = allocator.alloc(MAX_SOUNDFILE_PARTS * this.fIntSize);
160
- this.fOffset = allocator.alloc(MAX_SOUNDFILE_PARTS * this.fIntSize);
177
+ this.fLength = allocator.alloc(Soundfile.MAX_SOUNDFILE_PARTS * this.fIntSize);
178
+ this.fSR = allocator.alloc(Soundfile.MAX_SOUNDFILE_PARTS * this.fIntSize);
179
+ this.fOffset = allocator.alloc(Soundfile.MAX_SOUNDFILE_PARTS * this.fIntSize);
161
180
 
162
181
  this.fBuffers = this.allocBuffers(curChan, length, maxChan);
163
182
 
@@ -202,38 +221,37 @@ class Soundfile {
202
221
  }
203
222
  }
204
223
 
205
- copyToOut(part: number, maxChannels: number, offset: number, buffer: AudioBuffer) {
206
-
224
+ copyToOut(part: number, maxChannels: number, offset: number, audioData: AudioData) {
207
225
  // Set the soundfile fields in wasm memory
208
226
  if (this.fIntSize === 4) {
209
227
  const HEAP32 = this.fAllocator.getInt32Array();
210
- HEAP32[(this.fLength >> Math.log2(this.fIntSize)) + part] = buffer.length;
211
- HEAP32[(this.fSR >> Math.log2(this.fIntSize)) + part] = buffer.sampleRate;
228
+ HEAP32[(this.fLength >> Math.log2(this.fIntSize)) + part] = audioData.audioBuffer[0].length;
229
+ HEAP32[(this.fSR >> Math.log2(this.fIntSize)) + part] = audioData.sampleRate;
212
230
  HEAP32[(this.fOffset >> Math.log2(this.fIntSize)) + part] = offset;
213
231
  } else {
214
232
  const HEAP64 = this.fAllocator.getInt64Array();
215
- HEAP64[(this.fLength >> Math.log2(this.fIntSize)) + part] = BigInt(buffer.length);
216
- HEAP64[(this.fSR >> Math.log2(this.fIntSize)) + part] = BigInt(buffer.sampleRate);
233
+ HEAP64[(this.fLength >> Math.log2(this.fIntSize)) + part] = BigInt(audioData.audioBuffer[0].length);
234
+ HEAP64[(this.fSR >> Math.log2(this.fIntSize)) + part] = BigInt(audioData.sampleRate);
217
235
  HEAP64[(this.fOffset >> Math.log2(this.fIntSize)) + part] = BigInt(offset);
218
236
  }
219
237
 
220
- console.log(`copyToOut: part: ${part}, maxChannels: ${maxChannels}, offset: ${offset}, buffer: ${buffer}`);
238
+ console.log(`copyToOut: part: ${part}, maxChannels: ${maxChannels}, offset: ${offset}, buffer: ${audioData}`);
221
239
 
222
240
  //this.displayMemory("IN copyToOut, BEFORE copyToOutReal", true);
223
241
  // Copy the soundfile data to the buffer
224
242
  if (this.fSampleSize === 8) {
225
- this.copyToOutReal64(maxChannels, offset, buffer);
243
+ this.copyToOutReal64(maxChannels, offset, audioData);
226
244
  } else {
227
- this.copyToOutReal32(maxChannels, offset, buffer);
245
+ this.copyToOutReal32(maxChannels, offset, audioData);
228
246
  }
229
247
  //this.displayMemory("IN copyToOut, AFTER copyToOutReal");
230
248
  }
231
249
 
232
- copyToOutReal32(maxChannels: number, offset: number, buffer: AudioBuffer) {
250
+ copyToOutReal32(maxChannels: number, offset: number, audioData: AudioData) {
233
251
  const HEAP32 = this.fAllocator.getInt32Array();
234
252
  const HEAPF = this.fAllocator.getFloat32Array();
235
- for (let chan = 0; chan < buffer.numberOfChannels; chan++) {
236
- const input: Float32Array = buffer.getChannelData(chan);
253
+ for (let chan = 0; chan < audioData.audioBuffer.length; chan++) {
254
+ const input: Float32Array = audioData.audioBuffer[chan];
237
255
  const output: number = HEAP32[(this.fBuffers >> 2) + chan];
238
256
  const begin: number = (output + (offset * this.fSampleSize)) >> Math.log2(this.fSampleSize);
239
257
  const end: number = (output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize);
@@ -246,11 +264,11 @@ class Soundfile {
246
264
  }
247
265
  }
248
266
 
249
- copyToOutReal64(maxChannels: number, offset: number, buffer: AudioBuffer) {
267
+ copyToOutReal64(maxChannels: number, offset: number, audioData: AudioData) {
250
268
  const HEAP32 = this.fAllocator.getInt32Array();
251
269
  const HEAPF = this.fAllocator.getFloat64Array();
252
- for (let chan = 0; chan < buffer.numberOfChannels; chan++) {
253
- const input: Float32Array = buffer.getChannelData(chan);
270
+ for (let chan = 0; chan < audioData.audioBuffer.length; chan++) {
271
+ const input: Float32Array = audioData.audioBuffer[chan];
254
272
  const output: number = HEAP32[(this.fBuffers >> 2) + chan];
255
273
  const begin: number = (output + (offset * this.fSampleSize)) >> Math.log2(this.fSampleSize);
256
274
  const end: number = (output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize);
@@ -264,22 +282,21 @@ class Soundfile {
264
282
  }
265
283
 
266
284
  emptyFile(part: number, offset: number): number {
267
-
268
285
  // Set the soundfile fields in wasm memory
269
286
  if (this.fIntSize === 4) {
270
287
  const HEAP32 = this.fAllocator.getInt32Array();
271
- HEAP32[(this.fLength >> Math.log2(this.fIntSize)) + part] = BUFFER_SIZE;
272
- HEAP32[(this.fSR >> Math.log2(this.fIntSize)) + part] = SAMPLE_RATE;
288
+ HEAP32[(this.fLength >> Math.log2(this.fIntSize)) + part] = Soundfile.BUFFER_SIZE;
289
+ HEAP32[(this.fSR >> Math.log2(this.fIntSize)) + part] = Soundfile.SAMPLE_RATE;
273
290
  HEAP32[(this.fOffset >> Math.log2(this.fIntSize)) + part] = offset;
274
291
  } else {
275
292
  const HEAP64 = this.fAllocator.getInt64Array();
276
- HEAP64[(this.fLength >> Math.log2(this.fIntSize)) + part] = BigInt(BUFFER_SIZE);
277
- HEAP64[(this.fSR >> Math.log2(this.fIntSize)) + part] = BigInt(SAMPLE_RATE);
293
+ HEAP64[(this.fLength >> Math.log2(this.fIntSize)) + part] = BigInt(Soundfile.BUFFER_SIZE);
294
+ HEAP64[(this.fSR >> Math.log2(this.fIntSize)) + part] = BigInt(Soundfile.SAMPLE_RATE);
278
295
  HEAP64[(this.fOffset >> Math.log2(this.fIntSize)) + part] = BigInt(offset);
279
296
  }
280
297
 
281
298
  // Update and return the new offset
282
- return offset + BUFFER_SIZE;
299
+ return offset + Soundfile.BUFFER_SIZE;
283
300
  }
284
301
 
285
302
  displayMemory(where: string = "", mem: boolean = false) {
@@ -312,220 +329,6 @@ class Soundfile {
312
329
  getHEAPFloat64(): Float64Array {
313
330
  return this.fAllocator.getFloat64Array();
314
331
  }
315
-
316
- }
317
-
318
- // Definition of the AudioBufferItem type
319
- type AudioBufferItem = {
320
- pathName: string;
321
- audioBuffer: AudioBuffer;
322
- };
323
-
324
- /**
325
- * SoundfileReader class to read soundfile data and copy it to the soundfile buffer.
326
- */
327
- class SoundfileReader {
328
-
329
- private readonly fAllocator: WasmAllocator;
330
- private readonly fSampleSize: number;
331
- private readonly fContext;
332
- private readonly fAudioBuffers: AudioBufferItem[];
333
-
334
- constructor(allocator: WasmAllocator, context: BaseAudioContext, sampleSize: number) {
335
- this.fAllocator = allocator;
336
- this.fSampleSize = sampleSize;
337
- this.fContext = context;
338
- this.fAudioBuffers = [];
339
- }
340
-
341
- /**
342
- * Check if the file exists.
343
- *
344
- * @param url : the url of the file to check
345
- * @returns : true if the file exists, otherwise false
346
- */
347
- private async checkFileExists(url: string): Promise<boolean> {
348
- try {
349
- console.log(`"checkFileExists" url: ${url}`);
350
- const response = await fetch(url, { method: 'HEAD' });
351
- return response.ok; // Will be true if the status code is 200-299
352
- } catch (error) {
353
- console.error('Fetch error:', error);
354
- return false;
355
- }
356
- }
357
-
358
- /**
359
- * Check if the file exists in the given directories.
360
- *
361
- * @param directories : the list of directories to search for the file
362
- * @param fileName : the name of the file to search for
363
- * @returns : the path of the file if found, otherwise an empty string
364
- */
365
- private async checkFile(directories: string[], fileName: string): Promise<string> {
366
-
367
- if (await this.checkFileExists(fileName)) {
368
- return fileName;
369
- } else {
370
- for (let i = 0; i < directories.length; i++) {
371
- const pathName = directories[i] + "/" + fileName;
372
- if (await this.checkFileExists(pathName)) {
373
- return pathName;
374
- }
375
- }
376
- return "";
377
- }
378
- }
379
-
380
- /**
381
- * Check if all soundfiles exist and return their real path_name.
382
- *
383
- * @param directories : the list of directories to search for the file
384
- * @param fileNameList : the list of file names to search for
385
- * @returns : the list of path names of the files if found, otherwise an empty string
386
- */
387
- async checkFiles(directories: string[], fileNameList: string[]): Promise<string[]> {
388
- const pathNameList: string[] = [];
389
- for (let i = 0; i < fileNameList.length; i++) {
390
- const pathName: string = await this.checkFile(directories, fileNameList[i]);
391
- console.log(`checkFiles pathName: ${pathName}`);
392
- // If 'pathName' is not found, it is replaced by an identifier for an empty sound (e.g., silence)
393
- pathNameList.push(pathName === "" ? "__empty_sound__" : pathName);
394
- }
395
- return pathNameList;
396
- }
397
-
398
- /**
399
- * Get the channels and length values of the given sound resource.
400
- *
401
- * @param pathName : the name of the file, or sound resource identified this way
402
- * @returns channels and length of the soundfile
403
- */
404
- private async getParamsFile(pathName: string): Promise<{ channels: number, length: number }> {
405
- console.log(`Loading sound file from ${pathName}`);
406
-
407
- const item = this.fAudioBuffers.find((element: AudioBufferItem) => element.pathName === pathName);
408
- if (item) {
409
- console.log(`getItemByPathName FOUND`);
410
- return { channels: item.audioBuffer.numberOfChannels, length: item.audioBuffer.length };
411
- } else {
412
- const response = await fetch(pathName);
413
- if (!response.ok) {
414
- console.log(`Failed to load sound file from ${pathName}: ${response.statusText}`);
415
- return { channels: 1, length: BUFFER_SIZE };
416
- } else {
417
-
418
- // Decode the audio data
419
- const arrayBuffer = await response.arrayBuffer();
420
- const audioBuffer = await this.fContext.decodeAudioData(arrayBuffer);
421
- const { numberOfChannels, length } = audioBuffer;
422
-
423
- // Keep the audio buffer for later use
424
- this.fAudioBuffers.push({ pathName, audioBuffer });
425
-
426
- // Ensure the returned object keys match what's being returned
427
- return { channels: numberOfChannels, length };
428
- }
429
- }
430
- }
431
-
432
- /**
433
- * Read one sound resource and fill the 'soundfile' structure accordingly
434
- *
435
- * @param soundfile - the soundfile to be filled
436
- * @param pathName - the name of the file, or sound resource identified this way
437
- * @param part - the part number to be filled in the soundfile
438
- * @param maxChan - the maximum number of mono channels to fill
439
- *
440
- * @returns the offset in the soundfile buffer
441
- *
442
- */
443
- private readFile(soundfile: Soundfile, pathName: string, part: number, offset: number, maxChan: number): number {
444
- // Read the soundfile
445
- const item = this.fAudioBuffers.find(entry => entry.pathName === pathName);
446
- // Copy the soundfile data to the buffer
447
- if (item) {
448
- //soundfile.displayMemory("BEFORE copyToOut");
449
- soundfile.copyToOut(part, maxChan, offset, item.audioBuffer);
450
- //soundfile.displayMemory("AFTER copyToOut");
451
- return offset + item.audioBuffer.length;
452
- } else {
453
- console.error(`Failed to access sound file from ${pathName}`);
454
- return offset + BUFFER_SIZE;
455
- }
456
- }
457
-
458
- /**
459
- * Crate a soundfile, load all parts and copy audio data to the wasm soundfile buffer.
460
- * @param pathNameList : list of soundfile paths
461
- * @param maxChan : maximum number of channels
462
- */
463
- async createSoundfile(pathNameList: string[], maxChan: number): Promise<Soundfile | null> {
464
- try {
465
- let curChan = 1; // At least one channel
466
- let totalLength = 0;
467
-
468
- // Compute total length and channels max of all files
469
- for (const pathName of pathNameList) {
470
- let chan: number = 0, len: number = 0;
471
- if (pathName === "__empty_sound__") {
472
- length = BUFFER_SIZE;
473
- chan = 1;
474
- } else {
475
- const { channels, length } = await this.getParamsFile(pathName);
476
- chan = channels;
477
- len = length;
478
- }
479
- curChan = Math.max(curChan, chan);
480
- totalLength += len;
481
- }
482
-
483
- // Complete with empty parts
484
- totalLength += (MAX_SOUNDFILE_PARTS - pathNameList.length) * BUFFER_SIZE;
485
-
486
- // Create the soundfile
487
- let soundfile = new Soundfile(this.fAllocator, this.fSampleSize, curChan, totalLength, maxChan, pathNameList.length);
488
-
489
- //soundfile.displayMemory("After soundfile creation");
490
- // Init offset
491
- let offset = 0;
492
-
493
- // Read all files
494
- for (let part = 0; part < pathNameList.length; part++) {
495
- if (pathNameList[part] === "__empty_sound__") {
496
- // Empty sound
497
- offset = soundfile.emptyFile(part, offset);
498
- } else {
499
- // Read the soundfile and update the offset
500
- offset = await this.readFile(soundfile, pathNameList[part], part, offset, maxChan);
501
- }
502
- }
503
-
504
- //soundfile.displayMemory("After reading soundfiles");
505
-
506
- // Complete with empty parts
507
- for (let part = pathNameList.length; part < MAX_SOUNDFILE_PARTS; part++) {
508
- offset = soundfile.emptyFile(part, offset);
509
- }
510
-
511
- //soundfile.displayMemory("After emptyFile");
512
-
513
- // Share the same buffers for all other channels so that we have maxChan channels available
514
- soundfile.shareBuffers(curChan, maxChan);
515
-
516
- //soundfile.displayMemory("After shareBuffers");
517
-
518
- return soundfile;
519
-
520
- } catch (error) {
521
- console.error("Failed to create soundfile:", error);
522
- return null;
523
- }
524
- }
525
-
526
- getHEAP32(): Int32Array {
527
- return this.fAllocator.getInt32Array();
528
- }
529
332
  }
530
333
 
531
334
  /**
@@ -729,36 +532,30 @@ export interface IFaustPolyWebAudioDsp extends IFaustBaseWebAudioDsp {
729
532
  }
730
533
  export interface IFaustPolyWebAudioNode extends IFaustPolyWebAudioDsp, AudioNode { }
731
534
 
732
- // Definition of the SoundfileItem type
733
- type SoundfileItem = {
734
- name: string; // Name of the soundfile
735
- url: string; // URL of the soundfile
736
- index: number; // Index in the DSP struct
737
- basePtr: number; // Base pointer in wasm memory
738
- };
739
-
740
535
  export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
741
- protected fOutputHandler: OutputParamHandler | null;
742
- protected fComputeHandler: ComputeHandler | null;
536
+ protected fOutputHandler: OutputParamHandler | null = null;
537
+ protected fComputeHandler: ComputeHandler | null = null;
743
538
 
744
539
  // To handle MIDI events plot
745
- protected fPlotHandler: PlotHandler | null;
746
- protected fCachedEvents: { type: string; data: any }[];
747
- protected fBufferNum: number;
540
+ protected fPlotHandler: PlotHandler | null = null;
541
+ protected fCachedEvents: { type: string; data: any }[] = [];
542
+ protected fBufferNum = 0;
748
543
 
749
- protected fInChannels: Float32Array[] | Float64Array[];
750
- protected fOutChannels: Float32Array[] | Float64Array[];
544
+ protected fInChannels: Float32Array[] | Float64Array[] = [];
545
+ protected fOutChannels: Float32Array[] | Float64Array[] = [];
751
546
 
752
- protected fOutputsTimer: number;
547
+ protected fOutputsTimer = 5;
753
548
 
754
549
  // UI items path
755
- protected fInputsItems: string[];
756
- protected fOutputsItems: string[];
757
- protected fDescriptor: FaustUIInputItem[];
550
+ protected fInputsItems: string[] = [];
551
+ protected fOutputsItems: string[] = [];
552
+ protected fDescriptor: FaustUIInputItem[] = [];
758
553
 
759
554
  // Soundfile handling
760
- protected fSoundfiles: SoundfileItem[];
761
- protected fEndMemory: number; // Keep the end of memory offset before soundfiles
555
+ protected fSoundfiles: SoundfileItem[] = [];
556
+ protected fSoundfileBuffers: LooseFaustDspFactory["soundfiles"] = {};
557
+ /** Keep the end of memory offset before soundfiles */
558
+ protected fEndMemory: number;
762
559
 
763
560
  // Buffers in wasm memory
764
561
  protected fAudioInputs!: number;
@@ -769,78 +566,50 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
769
566
  protected fSampleSize: number;
770
567
 
771
568
  // MIDI handling
772
- protected fPitchwheelLabel: { path: string; min: number; max: number }[];
773
- protected fCtrlLabel: { path: string; min: number; max: number }[][];
774
- protected fPathTable: { [address: string]: number };
775
- protected fUICallback: UIHandler;
569
+ protected fPitchwheelLabel: { path: string; min: number; max: number }[] = [];
570
+ protected fCtrlLabel: { path: string; min: number; max: number }[][] = new Array(128).fill(null).map(() => []);
571
+ protected fPathTable: { [address: string]: number } = {};
572
+ protected fUICallback: UIHandler = (item: FaustUIItem) => {
573
+ if (item.type === "hbargraph" || item.type === "vbargraph") {
574
+ // Keep bargraph adresses
575
+ this.fOutputsItems.push(item.address);
576
+ this.fPathTable[item.address] = item.index;
577
+ } else if (item.type === "vslider" || item.type === "hslider" || item.type === "button" || item.type === "checkbox" || item.type === "nentry") {
578
+ // Keep inputs adresses
579
+ this.fInputsItems.push(item.address);
580
+ this.fPathTable[item.address] = item.index;
581
+ this.fDescriptor.push(item);
582
+ // Parse 'midi' metadata
583
+ if (!item.meta) return;
584
+ item.meta.forEach((meta) => {
585
+ const { midi } = meta;
586
+ if (!midi) return;
587
+ const strMidi = midi.trim();
588
+ if (strMidi === "pitchwheel") {
589
+ this.fPitchwheelLabel.push({ path: item.address, min: item.min as number, max: item.max as number });
590
+ } else {
591
+ const matched = strMidi.match(/^ctrl\s(\d+)/);
592
+ if (!matched) return;
593
+ this.fCtrlLabel[parseInt(matched[1])].push({ path: item.address, min: item.min as number, max: item.max as number });
594
+ }
595
+ });
596
+ } else if (item.type === "soundfile") {
597
+ this.fSoundfiles.push({ name: item.label, url: item.url, index: item.index, basePtr: -1 });
598
+ }
599
+ };
776
600
 
777
601
  // Audio callback
778
- protected fProcessing: boolean;
779
- protected fDestroyed: boolean;
780
- protected fFirstCall: boolean;
602
+ protected fProcessing = false;
603
+ protected fDestroyed = false;
604
+ protected fFirstCall = true;
781
605
 
782
606
  protected fJSONDsp!: FaustDspMeta;
783
607
 
784
- constructor(sampleSize: number, bufferSize: number) {
785
- this.fOutputHandler = null;
786
- this.fComputeHandler = null;
787
-
788
- // To handle MIDI events plot
789
- this.fCachedEvents = [];
790
- this.fBufferNum = 0;
791
- this.fPlotHandler = null;
792
-
608
+ constructor(sampleSize: number, bufferSize: number, soundfiles: LooseFaustDspFactory["soundfiles"]) {
793
609
  this.fBufferSize = bufferSize;
794
-
795
- this.fInChannels = [];
796
- this.fOutChannels = [];
797
-
798
610
  this.fPtrSize = sampleSize; // Done on wast/wasm backend side
799
611
  this.fSampleSize = sampleSize;
800
-
801
- this.fOutputsTimer = 5;
802
- this.fInputsItems = [];
803
- this.fOutputsItems = [];
804
- this.fDescriptor = [];
805
-
806
- this.fSoundfiles = [];
807
-
808
- this.fPitchwheelLabel = [];
809
- this.fCtrlLabel = new Array(128).fill(null).map(() => []);
810
- this.fPathTable = {};
811
-
812
- this.fProcessing = false;
813
- this.fDestroyed = false;
814
- this.fFirstCall = true;
815
-
816
- this.fUICallback = (item: FaustUIItem) => {
817
- if (item.type === "hbargraph" || item.type === "vbargraph") {
818
- // Keep bargraph adresses
819
- this.fOutputsItems.push(item.address);
820
- this.fPathTable[item.address] = item.index;
821
- } else if (item.type === "vslider" || item.type === "hslider" || item.type === "button" || item.type === "checkbox" || item.type === "nentry") {
822
- // Keep inputs adresses
823
- this.fInputsItems.push(item.address);
824
- this.fPathTable[item.address] = item.index;
825
- this.fDescriptor.push(item);
826
- // Parse 'midi' metadata
827
- if (!item.meta) return;
828
- item.meta.forEach((meta) => {
829
- const { midi } = meta;
830
- if (!midi) return;
831
- const strMidi = midi.trim();
832
- if (strMidi === "pitchwheel") {
833
- this.fPitchwheelLabel.push({ path: item.address, min: item.min as number, max: item.max as number });
834
- } else {
835
- const matched = strMidi.match(/^ctrl\s(\d+)/);
836
- if (!matched) return;
837
- this.fCtrlLabel[parseInt(matched[1])].push({ path: item.address, min: item.min as number, max: item.max as number });
838
- }
839
- });
840
- } else if (item.type === "soundfile") {
841
- this.fSoundfiles.push({ name: item.label, url: item.url, index: item.index, basePtr: -1 });
842
- }
843
- }
612
+ this.fSoundfileBuffers = soundfiles;
844
613
  }
845
614
 
846
615
  // Tools
@@ -870,20 +639,20 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
870
639
  }
871
640
  }
872
641
 
873
- // Split the soundfile names and return an array of names
874
- static splitNames(input: string): string[] {
642
+ /** Split the soundfile names and return an array of names */
643
+ static splitSoundfileNames(input: string): string[] {
875
644
  // Trim off the curly braces at the start and end, if present
876
645
  let trimmed = input.replace(/^\{|\}$/g, '');
877
646
  // Split the string into an array of strings and remove first and last characters
878
647
  return trimmed.split(";").map(str => str.length <= 2 ? '' : str.substring(1, str.length - 1));
879
648
  }
880
649
 
881
- private extractURLsFromJSON(): string[] {
650
+ static extractUrlsFromMeta(dspMeta: FaustDspMeta): string[] {
882
651
  // Find the entry with the "soundfiles" key
883
- const soundfilesEntry = this.fJSONDsp.meta.find(entry => entry.soundfiles !== undefined);
652
+ const soundfilesEntry = dspMeta.meta.find(entry => entry.soundfiles !== undefined);
884
653
  // If the entry is found, split the string by semicolon to get the URLs
885
654
  if (soundfilesEntry) {
886
- return soundfilesEntry.soundfiles.split(';').filter(url => url !== '');
655
+ return soundfilesEntry.soundfiles.split(";").filter(url => url !== "");
887
656
  } else {
888
657
  return [];
889
658
  }
@@ -895,66 +664,102 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
895
664
  * The DSP struct is located at baseDSP in the wasm memory,
896
665
  * either a monophonic DSP, or a voice in a polyphonic context.
897
666
  *
898
- * @param sfReader : the soundfile reader
667
+ * @param allocator : the wasm memory allocator
899
668
  * @param baseDSP : the base DSP in the wasm memory
900
669
  * @param name : the name of the soundfile
901
670
  * @param url : the url of the soundfile
902
671
  */
903
- private async loadSoundfile(sfReader: SoundfileReader, baseDSP: number, name: string, url: string): Promise<void> {
904
-
672
+ private loadSoundfile(allocator: WasmAllocator, baseDSP: number, name: string, url: string) {
905
673
  console.log(`Soundfile ${name} paths: ${url}`);
906
-
907
- const sfReaderURLs = this.extractURLsFromJSON();
908
- console.log(`sfReaderURLs ${sfReaderURLs}`);
909
-
910
- const sfDirectories: string[] = ["", ".", "http://127.0.0.1:8000"];
911
- sfDirectories.push(...sfReaderURLs);
912
- console.log(`sfDirectories ${sfDirectories}`);
913
-
914
- // Check if the soundfile exists in the given directories and return the real path
915
- const sfPathNames: string[] = await sfReader.checkFiles(sfDirectories, FaustBaseWebAudioDsp.splitNames(url));
916
-
917
- console.log(`Soundfile ${name} paths: ${sfPathNames}`);
918
-
919
- const item = this.fSoundfiles.find((element: SoundfileItem) => element.url === url);
920
- if (item) {
921
- // Use the cached Soundfile
922
- if (item.basePtr !== -1) {
923
-
674
+ const soundfileIds = FaustBaseWebAudioDsp.splitSoundfileNames(url);
675
+ const item = this.fSoundfiles.find(item => item.url === url);
676
+ if (!item) throw new Error(`Soundfile with ${url} cannot be found !}`);
677
+ // Use the cached Soundfile
678
+ if (item.basePtr !== -1) {
679
+ // Update HEAP32 after soundfile creation
680
+ const HEAP32 = allocator.getInt32Array();
681
+ // Fill the soundfile structure in wasm memory, soundfiles are at the beginning of the DSP memory
682
+ console.log(`Soundfile CACHE ${url}} : ${name} loaded at ${item.basePtr} in wasm memory with index ${item.index}`);
683
+ // Soundfile is located at 'index' in the DSP struct, to be added with baseDSP in the wasm memory
684
+ HEAP32[(baseDSP + item.index) >> 2] = item.basePtr;
685
+ } else {
686
+ // Create the soundfiles
687
+ const soundfile = this.createSoundfile(allocator, soundfileIds, this.fSoundfileBuffers);
688
+ if (soundfile) {
924
689
  // Update HEAP32 after soundfile creation
925
- const HEAP32 = sfReader.getHEAP32();
926
-
927
- // Fill the soundfile structure in wasm memory, sounfiles are at the beginning of the DSP memory
928
- console.log(`Soundfile CACHE ${url}} : ${name} loaded at ${item.basePtr} in wasm memory with index ${item.index}`);
929
-
690
+ const HEAP32 = soundfile.getHEAP32();
691
+ // Get the soundfile pointer in wasm memory
692
+ item.basePtr = soundfile.getPtr();
693
+ console.log(`Soundfile ${name} loaded at ${item.basePtr} in wasm memory with index ${item.index}`);
930
694
  // Soundfile is located at 'index' in the DSP struct, to be added with baseDSP in the wasm memory
931
695
  HEAP32[(baseDSP + item.index) >> 2] = item.basePtr;
932
-
933
696
  } else {
697
+ console.log(`Soundfile ${name} for ${url} cannot be created !}`);
698
+ }
699
+ }
700
+ }
934
701
 
935
- // Create the soundfiles
936
- const soundfile = await sfReader.createSoundfile(sfPathNames, MAX_CHAN);
937
- if (soundfile) {
702
+ createSoundfile(allocator: WasmAllocator, soundfileIdList: string[], soundfiles: LooseFaustDspFactory["soundfiles"], maxChan = Soundfile.MAX_CHAN) {
703
+ let curChan = 1; // At least one channel
704
+ let totalLength = 0;
938
705
 
939
- // Update HEAP32 after soundfile creation
940
- const HEAP32 = soundfile.getHEAP32();
706
+ // Compute total length and channels max of all files
707
+ for (const soundfileId of soundfileIdList) {
708
+ let chan = 0;
709
+ let len = 0;
710
+ const audioData = soundfiles[soundfileId];
711
+ if (audioData) {
712
+ chan = audioData.audioBuffer.length;
713
+ len = audioData.audioBuffer[0].length;
714
+ } else {
715
+ len = Soundfile.BUFFER_SIZE;
716
+ chan = 1;
717
+ }
718
+ curChan = Math.max(curChan, chan);
719
+ totalLength += len;
720
+ }
941
721
 
942
- // Get the soundfile pointer in wasm memory
943
- item.basePtr = soundfile.getPtr();
944
- console.log(`Soundfile ${name} loaded at ${item.basePtr} in wasm memory with index ${item.index}`);
722
+ // Complete with empty parts
723
+ totalLength += (Soundfile.MAX_SOUNDFILE_PARTS - soundfileIdList.length) * Soundfile.BUFFER_SIZE;
724
+
725
+ // Create the soundfile
726
+ const soundfile = new Soundfile(allocator, this.fSampleSize, curChan, totalLength, maxChan, soundfileIdList.length);
727
+
728
+ //soundfile.displayMemory("After soundfile creation");
729
+ // Init offset
730
+ let offset = 0;
731
+
732
+ // Read all files
733
+ for (let part = 0; part < soundfileIdList.length; part++) {
734
+ const soundfileId = soundfileIdList[part];
735
+ const audioData = soundfiles[soundfileId];
736
+ if (audioData) {
737
+ //soundfile.displayMemory("BEFORE copyToOut");
738
+ soundfile.copyToOut(part, maxChan, offset, audioData);
739
+ //soundfile.displayMemory("AFTER copyToOut");
740
+ offset += audioData.audioBuffer[0].length;
741
+ } else {
742
+ // Empty sound
743
+ offset = soundfile.emptyFile(part, offset);
744
+ }
745
+ }
945
746
 
946
- // Soundfile is located at 'index' in the DSP struct, to be added with baseDSP in the wasm memory
947
- HEAP32[(baseDSP + item.index) >> 2] = item.basePtr;
747
+ //soundfile.displayMemory("After reading soundfiles");
948
748
 
949
- } else {
950
- console.log(`Soundfile ${name} for ${url} cannot be created !}`);
951
- }
952
- }
953
- } else {
954
- console.log(`Soundfile with ${url} cannot be found !}`);
749
+ // Complete with empty parts
750
+ for (let part = soundfileIdList.length; part < Soundfile.MAX_SOUNDFILE_PARTS; part++) {
751
+ offset = soundfile.emptyFile(part, offset);
955
752
  }
956
- }
957
753
 
754
+ //soundfile.displayMemory("After emptyFile");
755
+
756
+ // Share the same buffers for all other channels so that we have maxChan channels available
757
+ soundfile.shareBuffers(curChan, maxChan);
758
+
759
+ //soundfile.displayMemory("After shareBuffers");
760
+
761
+ return soundfile;
762
+ }
958
763
  /**
959
764
  * Init soundfiles memory.
960
765
  *
@@ -962,10 +767,10 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
962
767
  * @param sfReader : the soundfile reader
963
768
  * @param baseDSP : the DSP struct (either a monophonic DSP of polyphonic voice) base DSP in the wasm memory
964
769
  */
965
- protected async initSoundfileMemory(allocator: WasmAllocator, sfReader: SoundfileReader, baseDSP: number): Promise<void> {
770
+ protected initSoundfileMemory(allocator: WasmAllocator, baseDSP: number) {
966
771
  // Create and fill the soundfile structure
967
772
  for (const { name, url } of this.fSoundfiles) {
968
- await this.loadSoundfile(sfReader, baseDSP, name, url);
773
+ this.loadSoundfile(allocator, baseDSP, name, url);
969
774
  };
970
775
  }
971
776
 
@@ -1078,12 +883,12 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
1078
883
  private fInstance: FaustMonoDspInstance;
1079
884
  private fDSP!: number;
1080
885
 
1081
- constructor(instance: FaustMonoDspInstance, sampleRate: number, sampleSize: number, bufferSize: number) {
886
+ constructor(instance: FaustMonoDspInstance, sampleRate: number, sampleSize: number, bufferSize: number, soundfiles: LooseFaustDspFactory["soundfiles"]) {
1082
887
 
1083
- super(sampleSize, bufferSize);
888
+ super(sampleSize, bufferSize, soundfiles);
1084
889
  this.fInstance = instance;
1085
890
 
1086
- console.log("sampleSize: " + sampleSize + " bufferSize: " + bufferSize);
891
+ console.log(`sampleSize: ${sampleSize} bufferSize: ${bufferSize}`);
1087
892
 
1088
893
  // Create JSON object
1089
894
  this.fJSONDsp = JSON.parse(this.fInstance.json);
@@ -1096,21 +901,15 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
1096
901
 
1097
902
  // Init DSP
1098
903
  this.fInstance.api.init(this.fDSP, sampleRate);
1099
- }
1100
-
1101
- async init(context: BaseAudioContext | null): Promise<void> {
1102
904
 
1103
905
  // Init soundfiles memory is needed
1104
- if (this.fSoundfiles.length > 0 && context) {
906
+ if (this.fSoundfiles.length > 0) {
1105
907
 
1106
908
  // Create memory allocator for soundfiles in wasm memory, starting at the end of DSP memory
1107
909
  const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
1108
910
 
1109
- // Create soundfile reader
1110
- const sfReader = new SoundfileReader(allocator, context, this.fSampleSize);
1111
-
1112
911
  // Init soundfiles memory
1113
- await this.initSoundfileMemory(allocator, sfReader, this.fDSP);
912
+ this.initSoundfileMemory(allocator, this.fDSP);
1114
913
  }
1115
914
  }
1116
915
 
@@ -1265,48 +1064,32 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
1265
1064
  }
1266
1065
 
1267
1066
  export class FaustWebAudioDspVoice {
1268
-
1269
- static kActiveVoice: number;
1270
- static kFreeVoice: number;
1271
- static kReleaseVoice: number;
1272
- static kLegatoVoice: number;
1273
- static kNoVoice: number;
1274
- static VOICE_STOP_LEVEL: number;
1275
- private fFreqLabel: number[];
1276
- private fGateLabel: number[];
1277
- private fGainLabel: number[];
1278
- private fKeyLabel: number[];
1279
- private fVelLabel: number[];
1067
+ // Voice state
1068
+ static get kActiveVoice() { return 0; }
1069
+ static get kFreeVoice() { return -1; }
1070
+ static get kReleaseVoice() { return -2; }
1071
+ static get kLegatoVoice() { return -3; }
1072
+ static get kNoVoice() { return -4; }
1073
+ static get VOICE_STOP_LEVEL() { return 0.0005; }
1074
+
1075
+ private fFreqLabel: number[] = [];
1076
+ private fGateLabel: number[] = [];
1077
+ private fGainLabel: number[] = [];
1078
+ private fKeyLabel: number[] = [];
1079
+ private fVelLabel: number[] = [];
1280
1080
  private fDSP: number; // Voice DSP location in wasm memory
1281
1081
  private fAPI: IFaustDspInstance; // Voice DSP code
1282
1082
  // Accessed by PolyDSPImp class
1283
- fCurNote: number;
1284
- fNextNote: number;
1285
- fNextVel: number;
1286
- fDate: number;
1287
- fLevel: number;
1288
- fRelease: number;
1083
+ fCurNote = FaustWebAudioDspVoice.kFreeVoice;
1084
+ fNextNote = -1;
1085
+ fNextVel = -1;
1086
+ fDate = 0;
1087
+ fLevel = 0;
1088
+ fRelease = 0;
1289
1089
 
1290
1090
  constructor($dsp: number, api: IFaustDspInstance, inputItems: string[], pathTable: { [address: string]: number }, sampleRate: number) {
1291
- // Voice state
1292
- FaustWebAudioDspVoice.kActiveVoice = 0;
1293
- FaustWebAudioDspVoice.kFreeVoice = -1;
1294
- FaustWebAudioDspVoice.kReleaseVoice = -2;
1295
- FaustWebAudioDspVoice.kLegatoVoice = -3;
1296
- FaustWebAudioDspVoice.kNoVoice = -4;
1297
- FaustWebAudioDspVoice.VOICE_STOP_LEVEL = 0.0005;
1298
-
1299
- this.fCurNote = FaustWebAudioDspVoice.kFreeVoice;
1300
- this.fNextNote = this.fNextVel = -1;
1301
- this.fLevel = 0;
1302
- this.fDate = this.fRelease = 0;
1303
1091
  this.fDSP = $dsp;
1304
1092
  this.fAPI = api;
1305
- this.fGateLabel = [];
1306
- this.fGainLabel = [];
1307
- this.fFreqLabel = [];
1308
- this.fKeyLabel = [];
1309
- this.fVelLabel = [];
1310
1093
  this.fAPI.init(this.fDSP, sampleRate);
1311
1094
  this.extractPaths(inputItems, pathTable);
1312
1095
  }
@@ -1395,11 +1178,11 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
1395
1178
  private fAudioMixingHalf!: number;
1396
1179
  private fVoiceTable: FaustWebAudioDspVoice[];
1397
1180
 
1398
- constructor(instance: FaustPolyDspInstance, sampleRate: number, sampleSize: number, bufferSize: number) {
1399
- super(sampleSize, bufferSize);
1181
+ constructor(instance: FaustPolyDspInstance, sampleRate: number, sampleSize: number, bufferSize: number, soundfiles: LooseFaustDspFactory["soundfiles"]) {
1182
+ super(sampleSize, bufferSize, soundfiles);
1400
1183
  this.fInstance = instance;
1401
1184
 
1402
- console.log("sampleSize: " + sampleSize + " bufferSize: " + bufferSize);
1185
+ console.log(`sampleSize: ${sampleSize} bufferSize: ${bufferSize}`);
1403
1186
 
1404
1187
  // Create JSON for voice
1405
1188
  this.fJSONDsp = JSON.parse(this.fInstance.voiceJSON);
@@ -1428,22 +1211,14 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
1428
1211
 
1429
1212
  // Init effect
1430
1213
  if (this.fInstance.effectAPI) this.fInstance.effectAPI.init(this.fEffect, sampleRate);
1431
- }
1432
-
1433
- async init(context: BaseAudioContext | null): Promise<void> {
1434
1214
 
1435
1215
  // Init soundfiles memory is needed
1436
- if (this.fSoundfiles.length > 0 && context) {
1437
-
1216
+ if (this.fSoundfiles.length > 0) {
1438
1217
  // Create memory allocator for soundfiles in wasm memory, starting at the end of DSP memory
1439
1218
  const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
1440
-
1441
- // Create soundfile reader
1442
- const sfReader = new SoundfileReader(allocator, context, this.fSampleSize);
1443
-
1444
1219
  // Init soundfiles memory for all voices
1445
1220
  for (let voice = 0; voice < this.fInstance.voices; voice++) {
1446
- await this.initSoundfileMemory(allocator, sfReader, this.fJSONDsp.size * voice);
1221
+ this.initSoundfileMemory(allocator, this.fJSONDsp.size * voice);
1447
1222
  }
1448
1223
  }
1449
1224
  }