@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.
- 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 +146 -4
- package/assets/standalone/faustwasm/index.js +460 -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 +146 -4
- package/dist/cjs/index.js +460 -73
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs-bundle/index.d.ts +146 -4
- package/dist/cjs-bundle/index.js +561 -174
- package/dist/cjs-bundle/index.js.map +3 -3
- package/dist/esm/index.d.ts +146 -4
- package/dist/esm/index.js +460 -73
- package/dist/esm/index.js.map +3 -3
- package/dist/esm-bundle/index.d.ts +146 -4
- package/dist/esm-bundle/index.js +561 -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 +696 -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 +146 -4
- package/test/faustlive-wasm/faustwasm/index.js +460 -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,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
|
-
|
|
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.
|
|
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.
|
|
476
|
-
const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|
|
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.
|
|
775
|
-
this.fAudioMixing = this.fAudioOutputs + this.getNumOutputs() * this.
|
|
776
|
-
this.fAudioMixingHalf = this.fAudioMixing + this.getNumOutputs() * this.
|
|
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.
|
|
780
|
-
const $audioOutputs = $audioInputs + this.getNumInputs() * this.fBufferSize * this.
|
|
781
|
-
const $audioMixing = $audioOutputs + this.getNumOutputs() * this.fBufferSize * this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
800
|
-
HEAP32[(this.fAudioMixing >> 2) + chan] = $audioMixing + this.fBufferSize * this.
|
|
801
|
-
HEAP32[(this.fAudioMixingHalf >> 2) + chan] = $audioMixing + this.fBufferSize * this.
|
|
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.
|
|
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.
|
|
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
|
}
|