@grame/faustwasm 0.0.67 → 0.1.1

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 +146 -4
  7. package/assets/standalone/faustwasm/index.js +460 -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 +146 -4
  12. package/dist/cjs/index.js +460 -73
  13. package/dist/cjs/index.js.map +3 -3
  14. package/dist/cjs-bundle/index.d.ts +146 -4
  15. package/dist/cjs-bundle/index.js +561 -174
  16. package/dist/cjs-bundle/index.js.map +3 -3
  17. package/dist/esm/index.d.ts +146 -4
  18. package/dist/esm/index.js +460 -73
  19. package/dist/esm/index.js.map +3 -3
  20. package/dist/esm-bundle/index.d.ts +146 -4
  21. package/dist/esm-bundle/index.js +561 -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 +696 -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 +146 -4
  37. package/test/faustlive-wasm/faustwasm/index.js +460 -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,102 @@ 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
+ private extractURLsFromJSON(): string[] {
835
+ // Find the entry with the "soundfiles" key
836
+ const soundfilesEntry = this.fJSONDsp.meta.find(entry => entry.soundfiles !== undefined);
837
+ // If the entry is found, split the string by semicolon to get the URLs
838
+ if (soundfilesEntry) {
839
+ return soundfilesEntry.soundfiles.split(';').filter(url => url !== '');
840
+ } else {
841
+ return [];
842
+ }
843
+ }
844
+
845
+ /**
846
+ * Load a soundfile possibly containing several parts.
847
+ *
848
+ * @param sfReader : the soundfile reader
849
+ * @param sfOffset : the offset in the wasm memory
850
+ * @param name : the name of the soundfile
851
+ * @param url : the url of the soundfile
852
+ */
853
+ private async loadSoundfile(sfReader: SoundfileReader, sfOffset: number, name: string, url: string): Promise<void> {
854
+
855
+ console.log(`Soundfile ${name} paths: ${url}`);
856
+
857
+ const sfReaderURLs = this.extractURLsFromJSON();
858
+ console.log(`sfReaderURLs ${sfReaderURLs}`);
859
+
860
+ const sfDirectories: string[] = ["", ".", "http://127.0.0.1:8000"];
861
+ sfDirectories.push(...sfReaderURLs);
862
+ console.log(`sfDirectories ${sfDirectories}`);
863
+
864
+ // Check if the soundfile exists in the given directories and return the real path
865
+ const sfPathNames: string[] = await sfReader.checkFiles(sfDirectories, FaustBaseWebAudioDsp.splitNames(url));
866
+
867
+ console.log(`Soundfile ${name} paths: ${sfPathNames}`);
868
+
869
+ const item = this.fSoundfiles.find((element: SoundfileItem) => element.url === url);
870
+ if (item) {
871
+ // Use the cached Soundfile
872
+ if (item.basePtr !== -1) {
873
+ // Update HEAP32 after soundfile creation
874
+ const HEAP32 = sfReader.getHEAP32();
875
+
876
+ // Fill the soundfile structure in wasm memory, sounfiles are at the beginning of the DSP memory
877
+ console.log(`Soundfile ${name} loaded at ${item.basePtr} in wasm memory with sfOffset ${sfOffset}`);
878
+ console.log(`Soundfile CACHE ${url}}`);
879
+
880
+ HEAP32[sfOffset >> 2] = item.basePtr;
881
+ } else {
882
+
883
+ // Create the soundfiles
884
+ const soundfile = await sfReader.createSoundfile(sfPathNames, MAX_CHAN, this.fSampleSize === 8);
885
+ if (soundfile) {
886
+
887
+ //soundfile.displayMemory("After createSoundfile");
888
+
889
+ // Update HEAP32 after soundfile creation
890
+ const HEAP32 = soundfile.getHEAP32();
891
+
892
+ // Fill the soundfile structure in wasm memory, sounfiles are at the beginning of the DSP memory
893
+ item.basePtr = soundfile.getPtr();
894
+ console.log(`Soundfile ${name} loaded at ${item.basePtr} in wasm memory with sfOffset ${sfOffset}`);
895
+
896
+ HEAP32[sfOffset >> 2] = item.basePtr;
897
+
898
+ } else {
899
+ console.log(`Soundfile ${name} for ${url} cannot be created !}`);
900
+ }
901
+ }
902
+ } else {
903
+ console.log(`Soundfile with ${url} cannot be found !}`);
904
+ }
905
+ }
906
+
907
+ /**
908
+ * Init soundfiles memory.
909
+ *
910
+ * Soundfile pointers are located at the beginning of the DSP struct memory (one after the other),
911
+ * so that the TS scode can setup them easily.
912
+ */
913
+ protected async initSoundfileMemory(allocator: WasmAllocator, sfReader: SoundfileReader, baseDSP: number): Promise<void> {
914
+ // Create and fill the soundfile structure
915
+ let sfOffset: number = baseDSP;
916
+ for (const { name, url } of this.fSoundfiles) {
917
+ await this.loadSoundfile(sfReader, sfOffset, name, url);
918
+ sfOffset += this.fPtrSize;
919
+ };
920
+ }
921
+
337
922
  protected updateOutputs() {
338
923
  if (this.fOutputsItems.length > 0 && this.fOutputHandler && this.fOutputsTimer-- === 0) {
339
924
  this.fOutputsTimer = 5;
@@ -420,6 +1005,8 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
420
1005
  getUI() { return this.fJSONDsp.ui; }
421
1006
  getDescriptors() { return this.fDescriptor; }
422
1007
 
1008
+ hasSoundfiles() { return this.fSoundfiles.length > 0; }
1009
+
423
1010
  start() {
424
1011
  this.fProcessing = true;
425
1012
  }
@@ -453,13 +1040,29 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
453
1040
  FaustBaseWebAudioDsp.parseUI(this.fJSONDsp.ui, this.fUICallback);
454
1041
 
455
1042
  // Setup wasm memory
456
- this.initMemory();
1043
+ this.fEndMemory = this.initMemory();
457
1044
 
458
1045
  // Init DSP
459
1046
  this.fInstance.api.init(this.fDSP, sampleRate);
460
1047
  }
461
1048
 
462
- private initMemory() {
1049
+ async init(context: BaseAudioContext | null): Promise<void> {
1050
+
1051
+ // Init soundfiles memory is needed
1052
+ if (this.fSoundfiles.length > 0 && context) {
1053
+
1054
+ // Create memory allocator for soundfiles in wasm memory, starting at the end of DSP memory
1055
+ const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
1056
+
1057
+ // Create soundfile reader
1058
+ const sfReader = new SoundfileReader(allocator, context, this.fPtrSize, this.fSampleSize);
1059
+
1060
+ // Init soundfiles memory
1061
+ await this.initSoundfileMemory(allocator, sfReader, this.fDSP);
1062
+ }
1063
+ }
1064
+
1065
+ private initMemory(): number {
463
1066
 
464
1067
  // Start of DSP memory: Mono DSP is placed first with index 0
465
1068
  this.fDSP = 0;
@@ -469,45 +1072,50 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
469
1072
 
470
1073
  // Setup audio pointers offset
471
1074
  this.fAudioInputs = $audio;
472
- this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.gPtrSize;
1075
+ this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.fPtrSize;
473
1076
 
474
1077
  // Prepare wasm memory layout
475
- const $audioInputs = this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize;
476
- const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.gSampleSize;
1078
+ const $audioInputs = this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize;
1079
+ const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.fSampleSize;
1080
+ // Compute memory end in bytes
1081
+ const endMemory = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.fSampleSize;
477
1082
 
1083
+ // Setup Int32 and Real views of the memory
478
1084
  const HEAP = this.fInstance.memory.buffer;
479
1085
  const HEAP32 = new Int32Array(HEAP);
480
- const HEAPF = (this.gSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
1086
+ const HEAPF = (this.fSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
481
1087
 
482
1088
  if (this.getNumInputs() > 0) {
483
1089
  for (let chan = 0; chan < this.getNumInputs(); chan++) {
484
- HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.gSampleSize * chan;
1090
+ HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.fSampleSize * chan;
485
1091
  }
486
1092
  // Prepare Ins buffer tables
487
- const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.gPtrSize) >> 2);
1093
+ const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.fPtrSize) >> 2);
488
1094
  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));
1095
+ this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.fSampleSize), (dspInChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
490
1096
  }
491
1097
  }
492
1098
  if (this.getNumOutputs() > 0) {
493
1099
  for (let chan = 0; chan < this.getNumOutputs(); chan++) {
494
- HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.gSampleSize * chan;
1100
+ HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.fSampleSize * chan;
495
1101
  }
496
1102
  // Prepare Out buffer tables
497
- const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize) >> 2);
1103
+ const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize) >> 2);
498
1104
  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));
1105
+ this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.fSampleSize), (dspOutChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
500
1106
  }
501
1107
  }
1108
+
1109
+ return endMemory;
502
1110
  }
503
1111
 
504
1112
  toString() {
505
1113
  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}`;
1114
+ this.fBufferSize: ${this.fBufferSize}
1115
+ this.fJSONDsp.size: ${this.fJSONDsp.size}
1116
+ this.fAudioInputs: ${this.fAudioInputs}
1117
+ this.fAudioOutputs: ${this.fAudioOutputs}
1118
+ this.fDSP: ${this.fDSP}`;
511
1119
  }
512
1120
 
513
1121
  // Public API
@@ -519,6 +1127,12 @@ this.fDSP: ${this.fDSP}`;
519
1127
  // Check Processing state: the node returns 'true' to stay in the graph, even if not processing
520
1128
  if (!this.fProcessing) return true;
521
1129
 
1130
+ // Init memory again on first call (since WebAssembly.memory.grow() may have been called)
1131
+ if (this.fFirstCall) {
1132
+ this.initMemory();
1133
+ this.fFirstCall = false;
1134
+ }
1135
+
522
1136
  if (typeof input === "function") {
523
1137
  // Call input callback to avoid array copy
524
1138
  input(this.fInChannels);
@@ -528,20 +1142,20 @@ this.fDSP: ${this.fDSP}`;
528
1142
  // console.log("Process input error");
529
1143
  return true;
530
1144
  }
531
-
1145
+
532
1146
  // Check outputs
533
1147
  if (this.getNumOutputs() > 0 && typeof output !== "function" && (!output || !output[0] || output[0].length === 0)) {
534
1148
  // console.log("Process output error");
535
1149
  return true;
536
1150
  }
537
-
1151
+
538
1152
  // Copy inputs
539
1153
  if (input !== undefined) {
540
1154
  for (let chan = 0; chan < Math.min(this.getNumInputs(), input.length); chan++) {
541
1155
  const dspInput = this.fInChannels[chan];
542
1156
  dspInput.set(input[chan]);
543
1157
  }
544
- }
1158
+ }
545
1159
  }
546
1160
  // Possibly call an externally given callback (for instance to synchronize playing a MIDIFile...)
547
1161
  if (this.fComputeHandler) this.fComputeHandler(this.fBufferSize);
@@ -610,7 +1224,7 @@ export class FaustWebAudioDspVoice {
610
1224
  private fGainLabel: number[];
611
1225
  private fKeyLabel: number[];
612
1226
  private fVelLabel: number[];
613
- private fDSP: number; // Voice DSP location in wasm memory
1227
+ private fDSP: number; // Voice DSP location in wasm memory
614
1228
  private fAPI: IFaustDspInstance; // Voice DSP code
615
1229
  // Accessed by PolyDSPImp class
616
1230
  fCurNote: number;
@@ -743,7 +1357,7 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
743
1357
  if (this.fJSONEffect) FaustBaseWebAudioDsp.parseUI(this.fJSONEffect.ui, this.fUICallback);
744
1358
 
745
1359
  // Setup wasm memory
746
- this.initMemory();
1360
+ this.fEndMemory = this.initMemory();
747
1361
 
748
1362
  // Init DSP voices
749
1363
  this.fVoiceTable = [];
@@ -761,6 +1375,24 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
761
1375
  if (this.fInstance.effectAPI) this.fInstance.effectAPI.init(this.fEffect, sampleRate);
762
1376
  }
763
1377
 
1378
+ async init(context: BaseAudioContext | null): Promise<void> {
1379
+
1380
+ // Init soundfiles memory is needed
1381
+ if (this.fSoundfiles.length > 0 && context) {
1382
+
1383
+ // Create memory allocator for soundfiles in wasm memory, starting at the end of DSP memory
1384
+ const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
1385
+
1386
+ // Create soundfile reader
1387
+ const sfReader = new SoundfileReader(allocator, context, this.fPtrSize, this.fSampleSize);
1388
+
1389
+ // Init soundfiles memory for all voices
1390
+ for (let voice = 0; voice < this.fInstance.voices; voice++) {
1391
+ await this.initSoundfileMemory(allocator, sfReader, this.fJSONDsp.size * voice);
1392
+ }
1393
+ }
1394
+ }
1395
+
764
1396
  private initMemory() {
765
1397
 
766
1398
  // Effet start at the end of all DSP voices
@@ -771,51 +1403,57 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
771
1403
 
772
1404
  // Setup audio pointers offset
773
1405
  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;
1406
+ this.fAudioOutputs = this.fAudioInputs + this.getNumInputs() * this.fPtrSize;
1407
+ this.fAudioMixing = this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize;
1408
+ this.fAudioMixingHalf = this.fAudioMixing + this.getNumOutputs() * this.fPtrSize;
777
1409
 
778
1410
  // 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;
1411
+ const $audioInputs = this.fAudioMixingHalf + this.getNumOutputs() * this.fPtrSize;
1412
+ const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.fSampleSize;
1413
+ const $audioMixing = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.fSampleSize;
1414
+
1415
+ // Compute memory end in bytes
1416
+ const endMemory = $audioMixing + this.getNumOutputs() * this.fBufferSize * this.fSampleSize;
782
1417
 
1418
+ // Setup Int32 and Real views of the memory
783
1419
  const HEAP = this.fInstance.memory.buffer;
784
1420
  const HEAP32 = new Int32Array(HEAP);
785
- const HEAPF = (this.gSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
1421
+ const HEAPF = (this.fSampleSize === 4) ? new Float32Array(HEAP) : new Float64Array(HEAP);
786
1422
 
787
1423
  if (this.getNumInputs() > 0) {
788
1424
  for (let chan = 0; chan < this.getNumInputs(); chan++) {
789
- HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.gSampleSize * chan;
1425
+ HEAP32[(this.fAudioInputs >> 2) + chan] = $audioInputs + this.fBufferSize * this.fSampleSize * chan;
790
1426
  }
791
1427
  // Prepare Ins buffer tables
792
- const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.gPtrSize) >> 2);
1428
+ const dspInChans = HEAP32.subarray(this.fAudioInputs >> 2, (this.fAudioInputs + this.getNumInputs() * this.fPtrSize) >> 2);
793
1429
  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));
1430
+ this.fInChannels[chan] = HEAPF.subarray(dspInChans[chan] >> Math.log2(this.fSampleSize), (dspInChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
795
1431
  }
796
1432
  }
797
1433
  if (this.getNumOutputs() > 0) {
798
1434
  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;
1435
+ HEAP32[(this.fAudioOutputs >> 2) + chan] = $audioOutputs + this.fBufferSize * this.fSampleSize * chan;
1436
+ HEAP32[(this.fAudioMixing >> 2) + chan] = $audioMixing + this.fBufferSize * this.fSampleSize * chan;
1437
+ HEAP32[(this.fAudioMixingHalf >> 2) + chan] = $audioMixing + this.fBufferSize * this.fSampleSize * chan + this.fBufferSize / 2 * this.fSampleSize;
802
1438
  }
803
1439
  // Prepare Out buffer tables
804
- const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.gPtrSize) >> 2);
1440
+ const dspOutChans = HEAP32.subarray(this.fAudioOutputs >> 2, (this.fAudioOutputs + this.getNumOutputs() * this.fPtrSize) >> 2);
805
1441
  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));
1442
+ this.fOutChannels[chan] = HEAPF.subarray(dspOutChans[chan] >> Math.log2(this.fSampleSize), (dspOutChans[chan] + this.fBufferSize * this.fSampleSize) >> Math.log2(this.fSampleSize));
807
1443
  }
808
1444
  }
1445
+
1446
+ return endMemory;
809
1447
  }
810
1448
 
811
1449
  toString() {
812
1450
  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}`;
1451
+ this.fBufferSize: ${this.fBufferSize}
1452
+ this.fJSONDsp.size: ${this.fJSONDsp.size}
1453
+ this.fAudioInputs: ${this.fAudioInputs}
1454
+ this.fAudioOutputs: ${this.fAudioOutputs}
1455
+ this.fAudioMixing: ${this.fAudioMixing}
1456
+ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
819
1457
  }
820
1458
 
821
1459
  private allocVoice(voice: number, type: number) {
@@ -883,6 +1521,12 @@ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
883
1521
  // Check DSP state
884
1522
  if (this.fDestroyed) return false;
885
1523
 
1524
+ // Init memory again on first call (since WebAssembly.memory.grow() may have been called)
1525
+ if (this.fFirstCall) {
1526
+ this.initMemory();
1527
+ this.fFirstCall = false;
1528
+ }
1529
+
886
1530
  // Check Processing state: the node returns 'true' to stay in the graph, even if not processing
887
1531
  if (!this.fProcessing) return true;
888
1532
 
@@ -952,6 +1596,7 @@ this.fAudioMixingHalf: ${this.fAudioMixingHalf}`;
952
1596
 
953
1597
  return true;
954
1598
  }
1599
+
955
1600
  getNumInputs() {
956
1601
  return this.fInstance.voiceAPI.getNumInputs(0);
957
1602
  }