@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.
- package/README.md +1 -1
- package/assets/standalone/faust-ui/index.css +17 -0
- package/assets/standalone/faust-ui/index.css.map +1 -1
- package/assets/standalone/faust-ui/index.js +294 -47
- package/assets/standalone/faust-ui/index.js.map +1 -1
- package/assets/standalone/faustwasm/index.d.ts +145 -4
- package/assets/standalone/faustwasm/index.js +449 -73
- package/assets/standalone/faustwasm/index.js.map +3 -3
- package/assets/standalone/index-poly.js +9 -6
- package/assets/standalone/index.js +9 -6
- package/dist/cjs/index.d.ts +145 -4
- package/dist/cjs/index.js +449 -73
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs-bundle/index.d.ts +145 -4
- package/dist/cjs-bundle/index.js +550 -174
- package/dist/cjs-bundle/index.js.map +3 -3
- package/dist/esm/index.d.ts +145 -4
- package/dist/esm/index.js +449 -73
- package/dist/esm/index.js.map +3 -3
- package/dist/esm-bundle/index.d.ts +145 -4
- package/dist/esm-bundle/index.js +550 -174
- package/dist/esm-bundle/index.js.map +3 -3
- package/libfaust-wasm/libfaust-wasm.data +0 -0
- package/libfaust-wasm/libfaust-wasm.js +1 -1
- package/libfaust-wasm/libfaust-wasm.wasm +0 -0
- package/package.json +2 -2
- package/src/FaustAudioWorkletProcessor.ts +16 -3
- package/src/FaustDspGenerator.ts +18 -3
- package/src/FaustWasmInstantiator.ts +62 -26
- package/src/FaustWebAudioDsp.ts +682 -51
- package/src/faust2wasmFiles.js +6 -4
- package/test/faustlive-wasm/faust-ui/index.css +17 -0
- package/test/faustlive-wasm/faust-ui/index.css.map +1 -1
- package/test/faustlive-wasm/faust-ui/index.js +294 -47
- package/test/faustlive-wasm/faust-ui/index.js.map +1 -1
- package/test/faustlive-wasm/faustwasm/index.d.ts +145 -4
- package/test/faustlive-wasm/faustwasm/index.js +449 -73
- package/test/faustlive-wasm/faustwasm/index.js.map +3 -3
- package/TODO.md +0 -5
- package/test/tp0.dsp +0 -17
- package/test/tp0.dsp.json +0 -30
- package/test/tp0_local.dsp +0 -5
package/src/FaustWebAudioDsp.ts
CHANGED
|
@@ -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
|
|
239
|
-
protected
|
|
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.
|
|
268
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
476
|
-
const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|
|
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.
|
|
775
|
-
this.fAudioMixing = this.fAudioOutputs + this.getNumOutputs() * this.
|
|
776
|
-
this.fAudioMixingHalf = this.fAudioMixing + this.getNumOutputs() * this.
|
|
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.
|
|
780
|
-
const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.
|
|
781
|
-
const $audioMixing = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
800
|
-
HEAP32[(this.fAudioMixing >> 2) + chan] = $audioMixing + this.fBufferSize * this.
|
|
801
|
-
HEAP32[(this.fAudioMixingHalf >> 2) + chan] = $audioMixing + this.fBufferSize * this.
|
|
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.
|
|
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.
|
|
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
|
}
|