@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
@@ -10,6 +10,478 @@ export type MetadataHandler = (key: string, value: string) => void;
10
10
  // Implementation API
11
11
  export type UIHandler = (item: FaustUIItem) => void;
12
12
 
13
+ /**
14
+ * WasmAllocator is a basic memory management class designed to allocate
15
+ * blocks of memory within a WebAssembly.Memory object. It provides a simple
16
+ * alloc method to allocate a contiguous block of memory of a specified size.
17
+ *
18
+ * The allocator operates by keeping a linear progression through the memory,
19
+ * always allocating the next block at the end of the last. This approach does not
20
+ * handle freeing of memory or reuse of memory spaces.
21
+ */
22
+ class WasmAllocator {
23
+ // The WebAssembly.Memory object this allocator will manage.
24
+ private readonly memory: WebAssembly.Memory;
25
+ // The number of bytes currently allocated. This serves as the "pointer" to the
26
+ // next free byte in the memory.
27
+ private allocatedBytes: number;
28
+
29
+ constructor(memory: WebAssembly.Memory, offset: number) {
30
+ this.memory = memory;
31
+ // Initialize the allocator with offset allocated bytes.
32
+ this.allocatedBytes = offset;
33
+ }
34
+
35
+ /**
36
+ * Allocates a block of memory of the specified size, returning the pointer to the
37
+ * beginning of the block. The block is allocated at the current offset and the
38
+ * offset is incremented by the size of the block.
39
+ *
40
+ * @param sizeInBytes The size of the block to allocate in bytes.
41
+ * @returns The offset (pointer) to the beginning of the allocated block.
42
+ */
43
+ alloc(sizeInBytes: number): number {
44
+ // Store the current offset as the start of the new block.
45
+ const currentOffset = this.allocatedBytes;
46
+ // Calculate the new offset after allocating the requested block.
47
+ const newOffset = currentOffset + sizeInBytes;
48
+ // Get the total size of the WebAssembly memory in bytes.
49
+ const totalMemoryBytes = this.memory.buffer.byteLength;
50
+
51
+ // If the new offset exceeds the total size of the memory, grow the memory.
52
+ if (newOffset > totalMemoryBytes) {
53
+ // Calculate the number of WebAssembly pages needed to fit the new allocation.
54
+ // WebAssembly memory pages are 64KiB each.
55
+ const neededPages = Math.ceil((newOffset - totalMemoryBytes) / 65536);
56
+ // Grow the memory by the required number of pages.
57
+ console.log(`GROW: ${neededPages} pages`);
58
+ this.memory.grow(neededPages);
59
+ }
60
+
61
+ // Update the allocated bytes to the new offset.
62
+ this.allocatedBytes = newOffset;
63
+ // Return the offset at which the allocated block starts.
64
+ return currentOffset;
65
+ }
66
+
67
+ /**
68
+ * Returns the underlying buffer object.
69
+ *
70
+ * @returns The buffer object.
71
+ */
72
+ getBuffer(): ArrayBuffer {
73
+ return this.memory.buffer;
74
+ }
75
+
76
+ /**
77
+ * Returns the Int32 view of the underlying buffer object.
78
+ *
79
+ * @returns The view of the memory buffer as Int32Array.
80
+ */
81
+ getInt32Array(): Int32Array {
82
+ return new Int32Array(this.memory.buffer);
83
+ }
84
+
85
+ /**
86
+ * Returns the Float32 view of the underlying buffer object.
87
+ *
88
+ * @returns The view of the memory buffer as Float32Array.
89
+ */
90
+ getFloat32Array(): Float32Array {
91
+ return new Float32Array(this.memory.buffer);
92
+ }
93
+
94
+ /**
95
+ * Returns the Float64 view of the underlying buffer object..
96
+ *
97
+ * @returns The view of the memory buffer as Float64Array.
98
+ */
99
+ getFloat64Array(): Float64Array {
100
+ return new Float64Array(this.memory.buffer);
101
+ }
102
+ }
103
+
104
+ // Maximum number of soundfile parts.
105
+ const MAX_SOUNDFILE_PARTS = 256;
106
+
107
+ // Maximum number of channels.
108
+ const MAX_CHAN = 64;
109
+
110
+ // Maximum buffer size in frames.
111
+ const BUFFER_SIZE = 1024;
112
+
113
+ // Default sample rate.
114
+ const SAMPLE_RATE = 44100;
115
+
116
+ // Size of an integer in bytes.
117
+ const intSize = 4;
118
+
119
+ /**
120
+ * Soundfile class to handle soundfile data in wasm memory.
121
+ */
122
+ class Soundfile {
123
+ private readonly fPtr: number; // Pointer to the soundfile structure in wasm memory
124
+ private readonly fBuffers: number;
125
+ private readonly fLength: number;
126
+ private readonly fSR: number;
127
+ private readonly fOffset: number;
128
+ private readonly fSampleSize: number;
129
+ private readonly fPtrSize: number;
130
+ private readonly fAllocator: WasmAllocator;
131
+
132
+ constructor(allocator: WasmAllocator, ptrSize: number, sampleSize: number, curChan: number, length: number, maxChan: number, totalParts: number) {
133
+ // Keep the soundfile structure parameters
134
+
135
+ this.fSampleSize = sampleSize;
136
+ this.fPtrSize = ptrSize;
137
+ this.fAllocator = allocator;
138
+
139
+ // Allocate wasm memory for the soundfile structure
140
+ this.fPtr = allocator.alloc(4 * ptrSize); // 4 ptrSize: fBuffers, fLength, fSR, fOffset
141
+ this.fLength = allocator.alloc(MAX_SOUNDFILE_PARTS * intSize);
142
+ this.fSR = allocator.alloc(MAX_SOUNDFILE_PARTS * intSize);
143
+ this.fOffset = allocator.alloc(MAX_SOUNDFILE_PARTS * intSize);
144
+ this.fBuffers = this.allocBuffers(curChan, length, maxChan);
145
+
146
+ //this.displayMemory("Allocated soundfile structure 1");
147
+
148
+ // Set the soundfile structure in wasm memory
149
+ const HEAP32 = this.fAllocator.getInt32Array();
150
+ HEAP32[this.fPtr >> 2] = this.fBuffers;
151
+ HEAP32[(this.fPtr + ptrSize) >> 2] = this.fLength;
152
+ HEAP32[(this.fPtr + 2 * ptrSize) >> 2] = this.fSR;
153
+ HEAP32[(this.fPtr + 3 * ptrSize) >> 2] = this.fOffset;
154
+
155
+ for (let chan = 0; chan < curChan; chan++) {
156
+ const buffer: number = HEAP32[(this.fBuffers >> 2) + chan];
157
+ console.log(`allocBuffers AFTER: ${chan} - ${buffer}`);
158
+ }
159
+
160
+ //this.displayMemory("Allocated soundfile structure 2");
161
+ }
162
+
163
+ private allocBuffers(curChan: number, length: number, maxChan: number): number {
164
+ const buffers = this.fAllocator.alloc(maxChan * this.fPtrSize);
165
+
166
+ console.log(`allocBuffers buffers: ${buffers}`);
167
+
168
+ for (let chan = 0; chan < curChan; chan++) {
169
+ const buffer: number = this.fAllocator.alloc(length * this.fSampleSize);
170
+ // HEAP32 is the Int32Array view of the memory buffer which can change after grow in `alloc` method
171
+ // so we need to recompute the buffer address
172
+ const HEAP32 = this.fAllocator.getInt32Array();
173
+ HEAP32[(buffers >> 2) + chan] = buffer;
174
+ }
175
+ //this.displayMemory("Allocated soundfile buffers");
176
+ return buffers;
177
+ }
178
+
179
+ shareBuffers(curChan: number, maxChan: number) {
180
+ // Share the same buffers for all other channels so that we have maxChan channels available
181
+ const HEAP32 = this.fAllocator.getInt32Array();
182
+ for (let chan = curChan; chan < maxChan; chan++) {
183
+ HEAP32[(this.fBuffers >> 2) + chan] = HEAP32[(this.fBuffers >> 2) + chan % curChan];
184
+ }
185
+ }
186
+
187
+ copyToOut(part: number, maxChannels: number, offset: number, buffer: AudioBuffer) {
188
+ const HEAP32 = this.fAllocator.getInt32Array();
189
+ HEAP32[(this.fLength >> 2) + part] = buffer.length;
190
+ HEAP32[(this.fSR >> 2) + part] = buffer.sampleRate;
191
+ HEAP32[(this.fOffset >> 2) + part] = offset;
192
+
193
+ //this.displayMemory("IN copyToOut, BEFORE copyToOutReal", true);
194
+ // Copy the soundfile data to the buffer
195
+ if (this.fSampleSize === 8) {
196
+ this.copyToOutReal64(maxChannels, offset, buffer);
197
+ } else {
198
+ this.copyToOutReal32(maxChannels, offset, buffer);
199
+ }
200
+ //this.displayMemory("IN copyToOut, AFTER copyToOutReal");
201
+ }
202
+
203
+ copyToOutReal32(maxChannels: number, offset: number, buffer: AudioBuffer) {
204
+ const HEAP32 = this.fAllocator.getInt32Array();
205
+ const HEAPF = this.fAllocator.getFloat32Array();
206
+ for (let chan = 0; chan < buffer.numberOfChannels; chan++) {
207
+ const input: Float32Array = buffer.getChannelData(chan);
208
+ const output: number = HEAP32[(this.fBuffers >> 2) + chan];
209
+ const outputReal: Float32Array = HEAPF.subarray((output + offset * this.fSampleSize) >> Math.log2(this.fSampleSize),
210
+ (output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize));
211
+ for (let sample = 0; sample < input.length; sample++) {
212
+ outputReal[sample] = input[sample];
213
+ }
214
+ }
215
+ }
216
+
217
+ copyToOutReal64(maxChannels: number, offset: number, buffer: AudioBuffer) {
218
+ const HEAP32 = this.fAllocator.getInt32Array();
219
+ const HEAPF = this.fAllocator.getFloat64Array();
220
+ for (let chan = 0; chan < buffer.numberOfChannels; chan++) {
221
+ const input: Float32Array = buffer.getChannelData(chan);
222
+ const output: number = HEAP32[(this.fBuffers >> 2) + chan];
223
+ const outputReal: Float64Array = HEAPF.subarray((output + offset * this.fSampleSize) >> Math.log2(this.fSampleSize),
224
+ (output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize));
225
+ for (let sample = 0; sample < input.length; sample++) {
226
+ outputReal[sample] = input[sample];
227
+ }
228
+ }
229
+ }
230
+
231
+ emptyFile(part: number, offset: number): number {
232
+ // Set the soundfile buffer in wasm memory
233
+ const HEAP32 = this.fAllocator.getInt32Array();
234
+ HEAP32[(this.fLength >> 2) + part] = BUFFER_SIZE;
235
+ HEAP32[(this.fSR >> 2) + part] = SAMPLE_RATE;
236
+ HEAP32[(this.fOffset >> 2) + part] = offset;
237
+
238
+ // Update and return the new offset
239
+ return offset + BUFFER_SIZE;
240
+ }
241
+
242
+ displayMemory(where: string = "", mem: boolean = false) {
243
+ console.log("Soundfile memory: " + where);
244
+ console.log(`fPtr: ${this.fPtr}`);
245
+ console.log(`fBuffers: ${this.fBuffers}`);
246
+ console.log(`fLength: ${this.fLength}`);
247
+ console.log(`fSR: ${this.fSR}`);
248
+ console.log(`fOffset: ${this.fOffset}`);
249
+ const HEAP32 = this.fAllocator.getInt32Array();
250
+ if (mem) console.log(`HEAP32: ${HEAP32}`);
251
+ console.log(`HEAP32[this.fPtr >> 2]: ${HEAP32[this.fPtr >> 2]}`);
252
+ console.log(`HEAP32[(this.fPtr + ptrSize) >> 2]: ${HEAP32[(this.fPtr + this.fPtrSize) >> 2]}`);
253
+ console.log(`HEAP32[(this.fPtr + 2 * ptrSize) >> 2]: ${HEAP32[(this.fPtr + 2 * this.fPtrSize) >> 2]}`);
254
+ console.log(`HEAP32[(this.fPtr + 3 * ptrSize) >> 2]: ${HEAP32[(this.fPtr + 3 * this.fPtrSize) >> 2]}`);
255
+ }
256
+
257
+ // Return the pointer to the soundfile structure in wasm memory
258
+ getPtr(): number {
259
+ return this.fPtr;
260
+ }
261
+
262
+ getHEAP32(): Int32Array {
263
+ return this.fAllocator.getInt32Array();
264
+ }
265
+ getHEAPFloat32(): Float32Array {
266
+ return this.fAllocator.getFloat32Array();
267
+ }
268
+
269
+ getHEAPFloat64(): Float64Array {
270
+ return this.fAllocator.getFloat64Array();
271
+ }
272
+
273
+ }
274
+
275
+ // Definition of the AudioBufferItem type
276
+ type AudioBufferItem = {
277
+ pathName: string;
278
+ audioBuffer: AudioBuffer;
279
+ };
280
+
281
+ /**
282
+ * SoundfileReader class to read soundfile data and copy it to the soundfile buffer.
283
+ */
284
+ class SoundfileReader {
285
+
286
+ private readonly fAllocator: WasmAllocator;
287
+ private readonly fPtrSize: number;
288
+ private readonly fSampleSize: number;
289
+ private readonly fContext;
290
+ private readonly fAudioBuffers: AudioBufferItem[];
291
+
292
+ constructor(allocator: WasmAllocator, context: BaseAudioContext, ptrSize: number, sampleSize: number) {
293
+ this.fAllocator = allocator;
294
+ this.fPtrSize = ptrSize;
295
+ this.fSampleSize = sampleSize;
296
+ this.fContext = context;
297
+ this.fAudioBuffers = [];
298
+ }
299
+
300
+ /**
301
+ * Check if the file exists in the given directories.
302
+ *
303
+ * @param directories : the list of directories to search for the file
304
+ * @param fileName : the name of the file to search for
305
+ * @returns : the path of the file if found, otherwise an empty string
306
+ */
307
+ private async checkFile(directories: string[], fileName: string): Promise<string> {
308
+
309
+ async function checkFileExists(url: string): Promise<boolean> {
310
+ try {
311
+ console.log(`"checkFileExists" url: ${url}`);
312
+ const response = await fetch(url, { method: 'HEAD' });
313
+ return response.ok; // Will be true if the status code is 200-299
314
+ } catch (error) {
315
+ console.error('Fetch error:', error);
316
+ return false;
317
+ }
318
+ }
319
+
320
+ if (await checkFileExists(fileName)) {
321
+ return fileName;
322
+ } else {
323
+ for (let i = 0; i < directories.length; i++) {
324
+ const pathName = directories[i] + "/" + fileName;
325
+ if (await checkFileExists(pathName)) {
326
+ return pathName;
327
+ }
328
+ }
329
+ return "";
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Check if all soundfiles exist and return their real path_name.
335
+ *
336
+ * @param directories : the list of directories to search for the file
337
+ * @param fileNameList : the list of file names to search for
338
+ * @returns : the list of path names of the files if found, otherwise an empty string
339
+ */
340
+ async checkFiles(directories: string[], fileNameList: string[]): Promise<string[]> {
341
+ const pathNameList: string[] = [];
342
+ for (let i = 0; i < fileNameList.length; i++) {
343
+ const pathName: string = await this.checkFile(directories, fileNameList[i]);
344
+ console.log(`checkFiles pathName: ${pathName}`);
345
+ // If 'pathName' is not found, it is replaced by an identifier for an empty sound (e.g., silence)
346
+ pathNameList.push(pathName === "" ? "__empty_sound__" : pathName);
347
+ }
348
+ return pathNameList;
349
+ }
350
+
351
+ /**
352
+ * Get the channels and length values of the given sound resource.
353
+ *
354
+ * @param pathName : the name of the file, or sound resource identified this way
355
+ * @returns channels and length of the soundfile
356
+ */
357
+ private async getParamsFile(pathName: string): Promise<{ channels: number, length: number }> {
358
+ console.log(`Loading sound file from ${pathName}`);
359
+
360
+ const item = this.fAudioBuffers.find((element: AudioBufferItem) => element.pathName === pathName);
361
+ if (item) {
362
+ console.log(`getItemByPathName FOUND`);
363
+ return { channels: item.audioBuffer.numberOfChannels, length: item.audioBuffer.length };
364
+ } else {
365
+ const response = await fetch(pathName);
366
+ if (!response.ok) {
367
+ console.log(`Failed to load sound file from ${pathName}: ${response.statusText}`);
368
+ return { channels: 1, length: BUFFER_SIZE };
369
+ } else {
370
+
371
+ // Decode the audio data
372
+ const arrayBuffer = await response.arrayBuffer();
373
+ const audioBuffer = await this.fContext.decodeAudioData(arrayBuffer);
374
+ const { numberOfChannels, length } = audioBuffer;
375
+
376
+ // Keep the audio buffer for later use
377
+ this.fAudioBuffers.push({ pathName, audioBuffer });
378
+
379
+ // Ensure the returned object keys match what's being returned
380
+ return { channels: numberOfChannels, length };
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Read one sound resource and fill the 'soundfile' structure accordingly
387
+ *
388
+ * @param soundfile - the soundfile to be filled
389
+ * @param pathName - the name of the file, or sound resource identified this way
390
+ * @param part - the part number to be filled in the soundfile
391
+ * @param maxChan - the maximum number of mono channels to fill
392
+ *
393
+ * @returns the offset in the soundfile buffer
394
+ *
395
+ */
396
+ private readFile(soundfile: Soundfile, pathName: string, part: number, offset: number, maxChan: number): number {
397
+ // Read the soundfile
398
+ const item = this.fAudioBuffers.find(entry => entry.pathName === pathName);
399
+ // Copy the soundfile data to the buffer
400
+ if (item) {
401
+ //soundfile.displayMemory("BEFORE copyToOut");
402
+ soundfile.copyToOut(part, maxChan, offset, item.audioBuffer);
403
+ //soundfile.displayMemory("AFTER copyToOut");
404
+ return offset + item.audioBuffer.length;
405
+ } else {
406
+ console.error(`Failed to access sound file from ${pathName}`);
407
+ return offset + BUFFER_SIZE;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Crate a soundfile, load all parts and copy audio data to the wasm soundfile buffer.
413
+ * @param pathNameList : list of soundfile paths
414
+ * @param maxChan : maximum number of channels
415
+ * @param isDouble : whether the soundfile will be copied as double
416
+ */
417
+ async createSoundfile(pathNameList: string[], maxChan: number, isDouble: boolean): Promise<Soundfile | null> {
418
+ try {
419
+ let curChan = 1; // At least one channel
420
+ let totalLength = 0;
421
+
422
+ // Compute total length and channels max of all files
423
+ for (const pathName of pathNameList) {
424
+ let chan: number = 0, len: number = 0;
425
+ if (pathName === "__empty_sound__") {
426
+ length = BUFFER_SIZE;
427
+ chan = 1;
428
+ } else {
429
+ const { channels, length } = await this.getParamsFile(pathName);
430
+ chan = channels;
431
+ len = length;
432
+ }
433
+ curChan = Math.max(curChan, chan);
434
+ totalLength += len;
435
+ }
436
+
437
+ // Complete with empty parts
438
+ totalLength += (MAX_SOUNDFILE_PARTS - pathNameList.length) * BUFFER_SIZE;
439
+
440
+ // Create the soundfile
441
+ let soundfile = new Soundfile(this.fAllocator, this.fPtrSize, this.fSampleSize, curChan, totalLength, maxChan, pathNameList.length);
442
+
443
+ //soundfile.displayMemory("After soundfile creation");
444
+ // Init offset
445
+ let offset = 0;
446
+
447
+ // Read all files
448
+ for (let part = 0; part < pathNameList.length; part++) {
449
+ if (pathNameList[part] === "__empty_sound__") {
450
+ // Empty sound
451
+ offset = soundfile.emptyFile(part, offset);
452
+ } else {
453
+ // Read the soundfile and update the offset
454
+ offset = await this.readFile(soundfile, pathNameList[part], part, offset, maxChan);
455
+ }
456
+ }
457
+
458
+ //soundfile.displayMemory("After reading soundfiles");
459
+
460
+ // Complete with empty parts
461
+ for (let part = pathNameList.length; part < MAX_SOUNDFILE_PARTS; part++) {
462
+ offset = soundfile.emptyFile(part, offset);
463
+ }
464
+
465
+ //soundfile.displayMemory("After emptyFile");
466
+
467
+ // Share the same buffers for all other channels so that we have maxChan channels available
468
+ soundfile.shareBuffers(curChan, maxChan);
469
+
470
+ //soundfile.displayMemory("After shareBuffers");
471
+
472
+ return soundfile;
473
+
474
+ } catch (error) {
475
+ console.error("Failed to create soundfile:", error);
476
+ return null;
477
+ }
478
+ }
479
+
480
+ getHEAP32(): Int32Array {
481
+ return this.fAllocator.getInt32Array();
482
+ }
483
+ }
484
+
13
485
  /**
14
486
  * DSP implementation: mimic the C++ 'dsp' class:
15
487
  * - adding MIDI control: metadata are decoded and incoming MIDI messages will control the associated controllers
@@ -180,8 +652,8 @@ export interface IFaustBaseWebAudioDsp {
180
652
  destroy(): void;
181
653
  }
182
654
 
183
- export interface IFaustMonoWebAudioDsp extends IFaustBaseWebAudioDsp {}
184
- export interface IFaustMonoWebAudioNode extends IFaustMonoWebAudioDsp, AudioNode {}
655
+ export interface IFaustMonoWebAudioDsp extends IFaustBaseWebAudioDsp { }
656
+ export interface IFaustMonoWebAudioNode extends IFaustMonoWebAudioDsp, AudioNode { }
185
657
 
186
658
  export interface IFaustPolyWebAudioDsp extends IFaustBaseWebAudioDsp {
187
659
  /**
@@ -209,7 +681,14 @@ export interface IFaustPolyWebAudioDsp extends IFaustBaseWebAudioDsp {
209
681
  */
210
682
  allNotesOff(hard: boolean): void;
211
683
  }
212
- export interface IFaustPolyWebAudioNode extends IFaustPolyWebAudioDsp, AudioNode {}
684
+ export interface IFaustPolyWebAudioNode extends IFaustPolyWebAudioDsp, AudioNode { }
685
+
686
+ // Definition of the SoundfileItem type
687
+ type SoundfileItem = {
688
+ name: string; // Name of the soundfile
689
+ url: string; // URL of the soundfile
690
+ basePtr: number; // Base pointer in wasm memory
691
+ };
213
692
 
214
693
  export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
215
694
  protected fOutputHandler: OutputParamHandler | null;
@@ -230,13 +709,17 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
230
709
  protected fOutputsItems: string[];
231
710
  protected fDescriptor: FaustUIInputItem[];
232
711
 
712
+ // Soundfile handling
713
+ protected fSoundfiles: SoundfileItem[];
714
+ protected fEndMemory: number; // Keep the end of memory offset before soundfiles
715
+
233
716
  // Buffers in wasm memory
234
717
  protected fAudioInputs!: number;
235
718
  protected fAudioOutputs!: number;
236
719
 
237
720
  protected fBufferSize: number;
238
- protected gPtrSize: number;
239
- protected gSampleSize: number;
721
+ protected fPtrSize: number;
722
+ protected fSampleSize: number;
240
723
 
241
724
  // MIDI handling
242
725
  protected fPitchwheelLabel: { path: string; min: number; max: number }[];
@@ -244,9 +727,10 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
244
727
  protected fPathTable: { [address: string]: number };
245
728
  protected fUICallback: UIHandler;
246
729
 
730
+ // Audio callback
247
731
  protected fProcessing: boolean;
248
-
249
732
  protected fDestroyed: boolean;
733
+ protected fFirstCall: boolean;
250
734
 
251
735
  protected fJSONDsp!: FaustDspMeta;
252
736
 
@@ -264,20 +748,23 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
264
748
  this.fInChannels = [];
265
749
  this.fOutChannels = [];
266
750
 
267
- this.gPtrSize = sampleSize; // Done on wast/wasm backend side
268
- this.gSampleSize = sampleSize;
751
+ this.fPtrSize = sampleSize; // Done on wast/wasm backend side
752
+ this.fSampleSize = sampleSize;
269
753
 
270
754
  this.fOutputsTimer = 5;
271
755
  this.fInputsItems = [];
272
756
  this.fOutputsItems = [];
273
757
  this.fDescriptor = [];
274
758
 
759
+ this.fSoundfiles = [];
760
+
275
761
  this.fPitchwheelLabel = [];
276
762
  this.fCtrlLabel = new Array(128).fill(null).map(() => []);
277
763
  this.fPathTable = {};
278
764
 
279
765
  this.fProcessing = false;
280
766
  this.fDestroyed = false;
767
+ this.fFirstCall = true;
281
768
 
282
769
  this.fUICallback = (item: FaustUIItem) => {
283
770
  if (item.type === "hbargraph" || item.type === "vbargraph") {
@@ -303,6 +790,8 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
303
790
  this.fCtrlLabel[parseInt(matched[1])].push({ path: item.address, min: item.min as number, max: item.max as number });
304
791
  }
305
792
  });
793
+ } else if (item.type === "soundfile") {
794
+ this.fSoundfiles.push({ name: item.label, url: item.url, basePtr: -1 });
306
795
  }
307
796
  }
308
797
  }
@@ -334,6 +823,88 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
334
823
  }
335
824
  }
336
825
 
826
+ // Split the soundfile names and return an array of names
827
+ static splitNames(input: string): string[] {
828
+ // Trim off the curly braces at the start and end, if present
829
+ let trimmed = input.replace(/^\{|\}$/g, '');
830
+ // Split the string into an array of strings and remove first and last characters
831
+ return trimmed.split(";").map(str => str.length <= 2 ? '' : str.substring(1, str.length - 1));
832
+ }
833
+ /**
834
+ * Load a soundfile possibly containing several parts.
835
+ *
836
+ * @param sfReader : the soundfile reader
837
+ * @param sfOffset : the offset in the wasm memory
838
+ * @param name : the name of the soundfile
839
+ * @param url : the url of the soundfile
840
+ */
841
+ private async loadSoundfile(sfReader: SoundfileReader, sfOffset: number, name: string, url: string): Promise<void> {
842
+
843
+ console.log(`Soundfile ${name} paths: ${url}`);
844
+
845
+ // Add standard directories to look for soundfiles
846
+ const sfDirectories: string[] = ["", ".", "http://127.0.0.1:8000"];
847
+
848
+ console.log(`sfDirectories ${sfDirectories}`);
849
+
850
+ // Check if the soundfile exists in the given directories and return the real path
851
+ const sfPathNames: string[] = await sfReader.checkFiles(sfDirectories, FaustBaseWebAudioDsp.splitNames(url));
852
+
853
+ console.log(`Soundfile ${name} paths: ${sfPathNames}`);
854
+
855
+ const item = this.fSoundfiles.find((element: SoundfileItem) => element.url === url);
856
+ if (item) {
857
+ // Use the cached Soundfile
858
+ if (item.basePtr !== -1) {
859
+ // Update HEAP32 after soundfile creation
860
+ const HEAP32 = sfReader.getHEAP32();
861
+
862
+ // Fill the soundfile structure in wasm memory, sounfiles are at the beginning of the DSP memory
863
+ console.log(`Soundfile ${name} loaded at ${item.basePtr} in wasm memory with sfOffset ${sfOffset}`);
864
+ console.log(`Soundfile CACHE ${url}}`);
865
+
866
+ HEAP32[sfOffset >> 2] = item.basePtr;
867
+ } else {
868
+
869
+ // Create the soundfiles
870
+ const soundfile = await sfReader.createSoundfile(sfPathNames, MAX_CHAN, this.fSampleSize === 8);
871
+ if (soundfile) {
872
+
873
+ //soundfile.displayMemory("After createSoundfile");
874
+
875
+ // Update HEAP32 after soundfile creation
876
+ const HEAP32 = soundfile.getHEAP32();
877
+
878
+ // Fill the soundfile structure in wasm memory, sounfiles are at the beginning of the DSP memory
879
+ item.basePtr = soundfile.getPtr();
880
+ console.log(`Soundfile ${name} loaded at ${item.basePtr} in wasm memory with sfOffset ${sfOffset}`);
881
+
882
+ HEAP32[sfOffset >> 2] = item.basePtr;
883
+
884
+ } else {
885
+ console.log(`Soundfile ${name} for ${url} cannot be created !}`);
886
+ }
887
+ }
888
+ } else {
889
+ console.log(`Soundfile with ${url} cannot be found !}`);
890
+ }
891
+ }
892
+
893
+ /**
894
+ * Init soundfiles memory.
895
+ *
896
+ * Soundfile pointers are located at the beginning of the DSP struct memory (one after the other),
897
+ * so that the TS scode can setup them easily.
898
+ */
899
+ protected async initSoundfileMemory(allocator: WasmAllocator, sfReader: SoundfileReader, baseDSP: number): Promise<void> {
900
+ // Create and fill the soundfile structure
901
+ let sfOffset: number = baseDSP;
902
+ for (const { name, url } of this.fSoundfiles) {
903
+ await this.loadSoundfile(sfReader, sfOffset, name, url);
904
+ sfOffset += this.fPtrSize;
905
+ };
906
+ }
907
+
337
908
  protected updateOutputs() {
338
909
  if (this.fOutputsItems.length > 0 && this.fOutputHandler && this.fOutputsTimer-- === 0) {
339
910
  this.fOutputsTimer = 5;
@@ -420,6 +991,8 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
420
991
  getUI() { return this.fJSONDsp.ui; }
421
992
  getDescriptors() { return this.fDescriptor; }
422
993
 
994
+ hasSoundfiles() { return this.fSoundfiles.length > 0; }
995
+
423
996
  start() {
424
997
  this.fProcessing = true;
425
998
  }
@@ -453,13 +1026,29 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
453
1026
  FaustBaseWebAudioDsp.parseUI(this.fJSONDsp.ui, this.fUICallback);
454
1027
 
455
1028
  // Setup wasm memory
456
- this.initMemory();
1029
+ this.fEndMemory = this.initMemory();
457
1030
 
458
1031
  // Init DSP
459
1032
  this.fInstance.api.init(this.fDSP, sampleRate);
460
1033
  }
461
1034
 
462
- private initMemory() {
1035
+ async init(context: BaseAudioContext | null): Promise<void> {
1036
+
1037
+ // Init soundfiles memory is needed
1038
+ if (this.fSoundfiles.length > 0 && context) {
1039
+
1040
+ // Create memory allocator for soundfiles in wasm memory, starting at the end of DSP memory
1041
+ const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
1042
+
1043
+ // Create soundfile reader
1044
+ const sfReader = new SoundfileReader(allocator, context, this.fPtrSize, this.fSampleSize);
1045
+
1046
+ // Init soundfiles memory
1047
+ await this.initSoundfileMemory(allocator, sfReader, this.fDSP);
1048
+ }
1049
+ }
1050
+
1051
+ private initMemory(): number {
463
1052
 
464
1053
  // Start of DSP memory: Mono DSP is placed first with index 0
465
1054
  this.fDSP = 0;
@@ -469,45 +1058,50 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
469
1058
 
470
1059
  // Setup audio pointers offset
471
1060
  this.fAudioInputs = $audio;
472
- this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.gPtrSize;
1061
+ this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.fPtrSize;
473
1062
 
474
1063
  // Prepare wasm memory layout
475
- const $audioInputs = this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize;
476
- const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.gSampleSize;
1064
+ const $audioInputs = this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize;
1065
+ const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.fSampleSize;
1066
+ // Compute memory end in bytes
1067
+ const endMemory = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.fSampleSize;
477
1068
 
1069
+ // Setup Int32 and Real views of the memory
478
1070
  const HEAP = this.fInstance.memory.buffer;
479
1071
  const HEAP32 = new Int32Array(HEAP);
480
- const HEAPF = (this.gSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
1072
+ const HEAPF = (this.fSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
481
1073
 
482
1074
  if (this.getNumInputs() > 0) {
483
1075
  for (let chan = 0; chan < this.getNumInputs(); chan++) {
484
- HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.gSampleSize * chan;
1076
+ HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.fSampleSize * chan;
485
1077
  }
486
1078
  // Prepare Ins buffer tables
487
- const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.gPtrSize) >> 2);
1079
+ const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.fPtrSize) >> 2);
488
1080
  for (let chan = 0; chan < this.getNumInputs(); chan++) {
489
- this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.gSampleSize), (dspInChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
1081
+ this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.fSampleSize), (dspInChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
490
1082
  }
491
1083
  }
492
1084
  if (this.getNumOutputs() > 0) {
493
1085
  for (let chan = 0; chan < this.getNumOutputs(); chan++) {
494
- HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.gSampleSize * chan;
1086
+ HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.fSampleSize * chan;
495
1087
  }
496
1088
  // Prepare Out buffer tables
497
- const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize) >> 2);
1089
+ const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize) >> 2);
498
1090
  for (let chan = 0; chan < this.getNumOutputs(); chan++) {
499
- this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.gSampleSize), (dspOutChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
1091
+ this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.fSampleSize), (dspOutChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
500
1092
  }
501
1093
  }
1094
+
1095
+ return endMemory;
502
1096
  }
503
1097
 
504
1098
  toString() {
505
1099
  return `============== Mono Memory layout ==============
506
- this.fBufferSize: ${this.fBufferSize}
507
- this.fJSONDsp.size: ${this.fJSONDsp.size}
508
- this.fAudioInputs: ${this.fAudioInputs}
509
- this.fAudioOutputs: ${this.fAudioOutputs}
510
- this.fDSP: ${this.fDSP}`;
1100
+ this.fBufferSize: ${this.fBufferSize}
1101
+ this.fJSONDsp.size: ${this.fJSONDsp.size}
1102
+ this.fAudioInputs: ${this.fAudioInputs}
1103
+ this.fAudioOutputs: ${this.fAudioOutputs}
1104
+ this.fDSP: ${this.fDSP}`;
511
1105
  }
512
1106
 
513
1107
  // Public API
@@ -519,6 +1113,12 @@ this.fDSP: ${this.fDSP}`;
519
1113
  // Check Processing state: the node returns 'true' to stay in the graph, even if not processing
520
1114
  if (!this.fProcessing) return true;
521
1115
 
1116
+ // Init memory again on first call (since WebAssembly.memory.grow() may have been called)
1117
+ if (this.fFirstCall) {
1118
+ this.initMemory();
1119
+ this.fFirstCall = false;
1120
+ }
1121
+
522
1122
  if (typeof input === "function") {
523
1123
  // Call input callback to avoid array copy
524
1124
  input(this.fInChannels);
@@ -528,20 +1128,20 @@ this.fDSP: ${this.fDSP}`;
528
1128
  // console.log("Process input error");
529
1129
  return true;
530
1130
  }
531
-
1131
+
532
1132
  // Check outputs
533
1133
  if (this.getNumOutputs() > 0 && typeof output !== "function" && (!output || !output[0] || output[0].length === 0)) {
534
1134
  // console.log("Process output error");
535
1135
  return true;
536
1136
  }
537
-
1137
+
538
1138
  // Copy inputs
539
1139
  if (input !== undefined) {
540
1140
  for (let chan = 0; chan < Math.min(this.getNumInputs(), input.length); chan++) {
541
1141
  const dspInput = this.fInChannels[chan];
542
1142
  dspInput.set(input[chan]);
543
1143
  }
544
- }
1144
+ }
545
1145
  }
546
1146
  // Possibly call an externally given callback (for instance to synchronize playing a MIDIFile...)
547
1147
  if (this.fComputeHandler) this.fComputeHandler(this.fBufferSize);
@@ -610,7 +1210,7 @@ export class FaustWebAudioDspVoice {
610
1210
  private fGainLabel: number[];
611
1211
  private fKeyLabel: number[];
612
1212
  private fVelLabel: number[];
613
- private fDSP: number; // Voice DSP location in wasm memory
1213
+ private fDSP: number; // Voice DSP location in wasm memory
614
1214
  private fAPI: IFaustDspInstance; // Voice DSP code
615
1215
  // Accessed by PolyDSPImp class
616
1216
  fCurNote: number;
@@ -743,7 +1343,7 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
743
1343
  if (this.fJSONEffect) FaustBaseWebAudioDsp.parseUI(this.fJSONEffect.ui, this.fUICallback);
744
1344
 
745
1345
  // Setup wasm memory
746
- this.initMemory();
1346
+ this.fEndMemory = this.initMemory();
747
1347
 
748
1348
  // Init DSP voices
749
1349
  this.fVoiceTable = [];
@@ -761,6 +1361,24 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
761
1361
  if (this.fInstance.effectAPI) this.fInstance.effectAPI.init(this.fEffect, sampleRate);
762
1362
  }
763
1363
 
1364
+ async init(context: BaseAudioContext | null): Promise<void> {
1365
+
1366
+ // Init soundfiles memory is needed
1367
+ if (this.fSoundfiles.length > 0 && context) {
1368
+
1369
+ // Create memory allocator for soundfiles in wasm memory, starting at the end of DSP memory
1370
+ const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
1371
+
1372
+ // Create soundfile reader
1373
+ const sfReader = new SoundfileReader(allocator, context, this.fPtrSize, this.fSampleSize);
1374
+
1375
+ // Init soundfiles memory for all voices
1376
+ for (let voice = 0; voice < this.fInstance.voices; voice++) {
1377
+ await this.initSoundfileMemory(allocator, sfReader, this.fJSONDsp.size * voice);
1378
+ }
1379
+ }
1380
+ }
1381
+
764
1382
  private initMemory() {
765
1383
 
766
1384
  // Effet start at the end of all DSP voices
@@ -771,51 +1389,57 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
771
1389
 
772
1390
  // Setup audio pointers offset
773
1391
  this.fAudioInputs = $audio;
774
- this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.gPtrSize;
775
- this.fAudioMixing = this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize;
776
- this.fAudioMixingHalf = this.fAudioMixing + this.getNumOutputs() * this.gPtrSize;
1392
+ this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.fPtrSize;
1393
+ this.fAudioMixing = this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize;
1394
+ this.fAudioMixingHalf = this.fAudioMixing + this.getNumOutputs() * this.fPtrSize;
777
1395
 
778
1396
  // Prepare wasm memory layout
779
- const $audioInputs = this.fAudioMixingHalf + this.getNumOutputs() * this.gPtrSize;
780
- const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.gSampleSize;
781
- const $audioMixing = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.gSampleSize;
1397
+ const $audioInputs = this.fAudioMixingHalf + this.getNumOutputs() * this.fPtrSize;
1398
+ const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.fSampleSize;
1399
+ const $audioMixing = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.fSampleSize;
1400
+
1401
+ // Compute memory end in bytes
1402
+ const endMemory = $audioMixing + this.getNumOutputs() * this.fBufferSize * this.fSampleSize;
782
1403
 
1404
+ // Setup Int32 and Real views of the memory
783
1405
  const HEAP = this.fInstance.memory.buffer;
784
1406
  const HEAP32 = new Int32Array(HEAP);
785
- const HEAPF = (this.gSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
1407
+ const HEAPF = (this.fSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
786
1408
 
787
1409
  if (this.getNumInputs() > 0) {
788
1410
  for (let chan = 0; chan < this.getNumInputs(); chan++) {
789
- HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.gSampleSize * chan;
1411
+ HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.fSampleSize * chan;
790
1412
  }
791
1413
  // Prepare Ins buffer tables
792
- const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.gPtrSize) >> 2);
1414
+ const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.fPtrSize) >> 2);
793
1415
  for (let chan = 0; chan < this.getNumInputs(); chan++) {
794
- this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.gSampleSize), (dspInChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
1416
+ this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.fSampleSize), (dspInChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
795
1417
  }
796
1418
  }
797
1419
  if (this.getNumOutputs() > 0) {
798
1420
  for (let chan = 0; chan < this.getNumOutputs(); chan++) {
799
- HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.gSampleSize * chan;
800
- HEAP32[(this.fAudioMixing >> 2) + chan] = $audioMixing + this.fBufferSize * this.gSampleSize * chan;
801
- HEAP32[(this.fAudioMixingHalf >> 2) + chan] = $audioMixing + this.fBufferSize * this.gSampleSize * chan + this.fBufferSize / 2 * this.gSampleSize;
1421
+ HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.fSampleSize * chan;
1422
+ HEAP32[(this.fAudioMixing >> 2) + chan] = $audioMixing + this.fBufferSize * this.fSampleSize * chan;
1423
+ HEAP32[(this.fAudioMixingHalf >> 2) + chan] = $audioMixing + this.fBufferSize * this.fSampleSize * chan + this.fBufferSize / 2 * this.fSampleSize;
802
1424
  }
803
1425
  // Prepare Out buffer tables
804
- const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize) >> 2);
1426
+ const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize) >> 2);
805
1427
  for (let chan = 0; chan < this.getNumOutputs(); chan++) {
806
- this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.gSampleSize), (dspOutChans[chan] + this.fBufferSize * this.gSampleSize) >> Math.log2(this.gSampleSize));
1428
+ this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.fSampleSize), (dspOutChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
807
1429
  }
808
1430
  }
1431
+
1432
+ return endMemory;
809
1433
  }
810
1434
 
811
1435
  toString() {
812
1436
  return `============== Poly Memory layout ==============
813
- this.fBufferSize: ${this.fBufferSize}
814
- this.fJSONDsp.size: ${this.fJSONDsp.size}
815
- this.fAudioInputs: ${this.fAudioInputs}
816
- this.fAudioOutputs: ${this.fAudioOutputs}
817
- this.fAudioMixing: ${this.fAudioMixing}
818
- this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
1437
+ this.fBufferSize: ${this.fBufferSize}
1438
+ this.fJSONDsp.size: ${this.fJSONDsp.size}
1439
+ this.fAudioInputs: ${this.fAudioInputs}
1440
+ this.fAudioOutputs: ${this.fAudioOutputs}
1441
+ this.fAudioMixing: ${this.fAudioMixing}
1442
+ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
819
1443
  }
820
1444
 
821
1445
  private allocVoice(voice: number, type: number) {
@@ -883,6 +1507,12 @@ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
883
1507
  // Check DSP state
884
1508
  if (this.fDestroyed) return false;
885
1509
 
1510
+ // Init memory again on first call (since WebAssembly.memory.grow() may have been called)
1511
+ if (this.fFirstCall) {
1512
+ this.initMemory();
1513
+ this.fFirstCall = false;
1514
+ }
1515
+
886
1516
  // Check Processing state: the node returns 'true' to stay in the graph, even if not processing
887
1517
  if (!this.fProcessing) return true;
888
1518
 
@@ -952,6 +1582,7 @@ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
952
1582
 
953
1583
  return true;
954
1584
  }
1585
+
955
1586
  getNumInputs() {
956
1587
  return this.fInstance.voiceAPI.getNumInputs(0);
957
1588
  }