@grame/faustwasm 0.1.3 → 0.1.5
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/assets/standalone/faustwasm/index.d.ts +17 -5
- package/assets/standalone/faustwasm/index.js +69 -47
- package/assets/standalone/faustwasm/index.js.map +2 -2
- package/dist/cjs/index.d.ts +17 -5
- package/dist/cjs/index.js +69 -47
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs-bundle/index.d.ts +17 -5
- package/dist/cjs-bundle/index.js +70 -48
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm/index.d.ts +17 -5
- package/dist/esm/index.js +69 -47
- package/dist/esm/index.js.map +2 -2
- package/dist/esm-bundle/index.d.ts +17 -5
- package/dist/esm-bundle/index.js +70 -48
- package/dist/esm-bundle/index.js.map +2 -2
- package/libfaust-wasm/libfaust-wasm.js +1 -1
- package/libfaust-wasm/libfaust-wasm.wasm +0 -0
- package/out/clarinetMIDI.html +50 -0
- package/out/clarinetMIDI.js +63 -0
- package/out/clarinetMIDI.json +431 -0
- package/out/clarinetMIDI.wasm +0 -0
- package/out/faustwasm/index.d.ts +1495 -0
- package/out/faustwasm/index.js +4470 -0
- package/out/faustwasm/index.js.map +7 -0
- package/package.json +1 -1
- package/src/FaustWasmInstantiator.ts +14 -5
- package/src/FaustWebAudioDsp.ts +97 -46
- package/test/Timestrech.dsp +150 -0
- package/test/emsc.dsp +5 -0
- package/test/faustlive-wasm/faustwasm/index.d.ts +17 -5
- package/test/faustlive-wasm/faustwasm/index.js +69 -47
- package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
- package/test/tp1-mono.dsp +20 -0
- package/test/tp1.dsp +20 -0
package/package.json
CHANGED
|
@@ -139,9 +139,13 @@ class FaustWasmInstantiator {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
static async createAsyncMonoDSPInstance(factory: LooseFaustDspFactory) {
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
142
|
+
|
|
143
|
+
// Regular expression to match the 'type: soundfile' pattern
|
|
144
|
+
const pattern = /"type":\s*"soundfile"/;
|
|
145
|
+
// Check if the pattern exists in the JSON string
|
|
146
|
+
const isDetected = pattern.test(factory.json);
|
|
147
|
+
|
|
148
|
+
if (isDetected) {
|
|
145
149
|
const memory = this.createMemoryMono(factory);
|
|
146
150
|
const instance = await WebAssembly.instantiate(factory.module, this.createWasmImport(memory));
|
|
147
151
|
return this.createMonoDSPInstanceAux(instance, factory.json, memory);
|
|
@@ -153,9 +157,14 @@ class FaustWasmInstantiator {
|
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
static createSyncMonoDSPInstance(factory: LooseFaustDspFactory) {
|
|
156
|
-
|
|
160
|
+
|
|
161
|
+
// Regular expression to match the 'type: soundfile' pattern
|
|
162
|
+
const pattern = /"type":\s*"soundfile"/;
|
|
163
|
+
// Check if the pattern exists in the JSON string
|
|
164
|
+
const isDetected = pattern.test(factory.json);
|
|
165
|
+
|
|
157
166
|
// If the JSON contains a soundfile UI element, we need to create a memory object
|
|
158
|
-
if (
|
|
167
|
+
if (isDetected) {
|
|
159
168
|
const memory = this.createMemoryMono(factory);
|
|
160
169
|
const instance = new WebAssembly.Instance(factory.module, this.createWasmImport(memory));
|
|
161
170
|
return this.createMonoDSPInstanceAux(instance, factory.json, memory);
|
package/src/FaustWebAudioDsp.ts
CHANGED
|
@@ -82,6 +82,15 @@ class WasmAllocator {
|
|
|
82
82
|
return new Int32Array(this.memory.buffer);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Returns the Int64 view of the underlying buffer object.
|
|
87
|
+
*
|
|
88
|
+
* @returns The view of the memory buffer as BigInt64Array.
|
|
89
|
+
*/
|
|
90
|
+
getInt64Array(): BigInt64Array {
|
|
91
|
+
return new BigInt64Array(this.memory.buffer);
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
/**
|
|
86
95
|
* Returns the Float32 view of the underlying buffer object.
|
|
87
96
|
*
|
|
@@ -113,9 +122,6 @@ const BUFFER_SIZE = 1024;
|
|
|
113
122
|
// Default sample rate.
|
|
114
123
|
const SAMPLE_RATE = 44100;
|
|
115
124
|
|
|
116
|
-
// Size of an integer in bytes.
|
|
117
|
-
const intSize = 4;
|
|
118
|
-
|
|
119
125
|
/**
|
|
120
126
|
* Soundfile class to handle soundfile data in wasm memory.
|
|
121
127
|
*/
|
|
@@ -127,20 +133,32 @@ class Soundfile {
|
|
|
127
133
|
private readonly fOffset: number;
|
|
128
134
|
private readonly fSampleSize: number;
|
|
129
135
|
private readonly fPtrSize: number;
|
|
136
|
+
private readonly fIntSize: number;
|
|
130
137
|
private readonly fAllocator: WasmAllocator;
|
|
131
138
|
|
|
132
|
-
constructor(allocator: WasmAllocator,
|
|
133
|
-
// Keep the soundfile structure parameters
|
|
139
|
+
constructor(allocator: WasmAllocator, sampleSize: number, curChan: number, length: number, maxChan: number, totalParts: number) {
|
|
134
140
|
|
|
135
141
|
this.fSampleSize = sampleSize;
|
|
136
|
-
|
|
142
|
+
|
|
143
|
+
// To be coherent with the code generated by the wast/wasm backends:
|
|
144
|
+
// - that uses 4 bytes for int when float is used
|
|
145
|
+
// - that uses 8 bytes for int when double is used (to simplify the code generation)
|
|
146
|
+
this.fIntSize = this.fSampleSize;
|
|
147
|
+
|
|
148
|
+
this.fPtrSize = 4; // Not related to float/double choice, so always 4
|
|
149
|
+
|
|
137
150
|
this.fAllocator = allocator;
|
|
138
151
|
|
|
152
|
+
console.log(`Soundfile constructor: curChan: ${curChan}, length: ${length}, maxChan: ${maxChan}, totalParts: ${totalParts}`);
|
|
153
|
+
|
|
139
154
|
// Allocate wasm memory for the soundfile structure
|
|
140
|
-
this.fPtr = allocator.alloc(4 *
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this.
|
|
155
|
+
this.fPtr = allocator.alloc(4 * this.fPtrSize); // 4 fPtrSize: fBuffers, fLength, fSR, fOffset
|
|
156
|
+
|
|
157
|
+
// Use the 4 or 8 bytes size for int. The access are then adapted in copyToOut and emptyFile methods
|
|
158
|
+
this.fLength = allocator.alloc(MAX_SOUNDFILE_PARTS * this.fIntSize);
|
|
159
|
+
this.fSR = allocator.alloc(MAX_SOUNDFILE_PARTS * this.fIntSize);
|
|
160
|
+
this.fOffset = allocator.alloc(MAX_SOUNDFILE_PARTS * this.fIntSize);
|
|
161
|
+
|
|
144
162
|
this.fBuffers = this.allocBuffers(curChan, length, maxChan);
|
|
145
163
|
|
|
146
164
|
//this.displayMemory("Allocated soundfile structure 1");
|
|
@@ -148,9 +166,9 @@ class Soundfile {
|
|
|
148
166
|
// Set the soundfile structure in wasm memory
|
|
149
167
|
const HEAP32 = this.fAllocator.getInt32Array();
|
|
150
168
|
HEAP32[this.fPtr >> 2] = this.fBuffers;
|
|
151
|
-
HEAP32[(this.fPtr +
|
|
152
|
-
HEAP32[(this.fPtr + 2 *
|
|
153
|
-
HEAP32[(this.fPtr + 3 *
|
|
169
|
+
HEAP32[(this.fPtr + this.fPtrSize) >> 2] = this.fLength;
|
|
170
|
+
HEAP32[(this.fPtr + (2 * this.fPtrSize)) >> 2] = this.fSR;
|
|
171
|
+
HEAP32[(this.fPtr + (3 * this.fPtrSize)) >> 2] = this.fOffset;
|
|
154
172
|
|
|
155
173
|
for (let chan = 0; chan < curChan; chan++) {
|
|
156
174
|
const buffer: number = HEAP32[(this.fBuffers >> 2) + chan];
|
|
@@ -185,10 +203,21 @@ class Soundfile {
|
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
copyToOut(part: number, maxChannels: number, offset: number, buffer: AudioBuffer) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
206
|
+
|
|
207
|
+
// Set the soundfile fields in wasm memory
|
|
208
|
+
if (this.fIntSize === 4) {
|
|
209
|
+
const HEAP32 = this.fAllocator.getInt32Array();
|
|
210
|
+
HEAP32[(this.fLength >> Math.log2(this.fIntSize)) + part] = buffer.length;
|
|
211
|
+
HEAP32[(this.fSR >> Math.log2(this.fIntSize)) + part] = buffer.sampleRate;
|
|
212
|
+
HEAP32[(this.fOffset >> Math.log2(this.fIntSize)) + part] = offset;
|
|
213
|
+
} else {
|
|
214
|
+
const HEAP64 = this.fAllocator.getInt64Array();
|
|
215
|
+
HEAP64[(this.fLength >> Math.log2(this.fIntSize)) + part] = BigInt(buffer.length);
|
|
216
|
+
HEAP64[(this.fSR >> Math.log2(this.fIntSize)) + part] = BigInt(buffer.sampleRate);
|
|
217
|
+
HEAP64[(this.fOffset >> Math.log2(this.fIntSize)) + part] = BigInt(offset);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`copyToOut: part: ${part}, maxChannels: ${maxChannels}, offset: ${offset}, buffer: ${buffer}`);
|
|
192
221
|
|
|
193
222
|
//this.displayMemory("IN copyToOut, BEFORE copyToOutReal", true);
|
|
194
223
|
// Copy the soundfile data to the buffer
|
|
@@ -206,7 +235,10 @@ class Soundfile {
|
|
|
206
235
|
for (let chan = 0; chan < buffer.numberOfChannels; chan++) {
|
|
207
236
|
const input: Float32Array = buffer.getChannelData(chan);
|
|
208
237
|
const output: number = HEAP32[(this.fBuffers >> 2) + chan];
|
|
209
|
-
const
|
|
238
|
+
const begin: number = (output + (offset * this.fSampleSize)) >> Math.log2(this.fSampleSize);
|
|
239
|
+
const end: number = (output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize);
|
|
240
|
+
console.log(`copyToOutReal32 begin: ${begin}, end: ${end}, delta: ${end - begin}`);
|
|
241
|
+
const outputReal: Float32Array = HEAPF.subarray((output + (offset * this.fSampleSize)) >> Math.log2(this.fSampleSize),
|
|
210
242
|
(output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize));
|
|
211
243
|
for (let sample = 0; sample < input.length; sample++) {
|
|
212
244
|
outputReal[sample] = input[sample];
|
|
@@ -220,7 +252,10 @@ class Soundfile {
|
|
|
220
252
|
for (let chan = 0; chan < buffer.numberOfChannels; chan++) {
|
|
221
253
|
const input: Float32Array = buffer.getChannelData(chan);
|
|
222
254
|
const output: number = HEAP32[(this.fBuffers >> 2) + chan];
|
|
223
|
-
const
|
|
255
|
+
const begin: number = (output + (offset * this.fSampleSize)) >> Math.log2(this.fSampleSize);
|
|
256
|
+
const end: number = (output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize);
|
|
257
|
+
console.log(`copyToOutReal64 begin: ${begin}, end: ${end}, delta: ${end - begin}`);
|
|
258
|
+
const outputReal: Float64Array = HEAPF.subarray((output + (offset * this.fSampleSize)) >> Math.log2(this.fSampleSize),
|
|
224
259
|
(output + (offset + input.length) * this.fSampleSize) >> Math.log2(this.fSampleSize));
|
|
225
260
|
for (let sample = 0; sample < input.length; sample++) {
|
|
226
261
|
outputReal[sample] = input[sample];
|
|
@@ -229,11 +264,19 @@ class Soundfile {
|
|
|
229
264
|
}
|
|
230
265
|
|
|
231
266
|
emptyFile(part: number, offset: number): number {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
|
|
268
|
+
// Set the soundfile fields in wasm memory
|
|
269
|
+
if (this.fIntSize === 4) {
|
|
270
|
+
const HEAP32 = this.fAllocator.getInt32Array();
|
|
271
|
+
HEAP32[(this.fLength >> Math.log2(this.fIntSize)) + part] = BUFFER_SIZE;
|
|
272
|
+
HEAP32[(this.fSR >> Math.log2(this.fIntSize)) + part] = SAMPLE_RATE;
|
|
273
|
+
HEAP32[(this.fOffset >> Math.log2(this.fIntSize)) + part] = offset;
|
|
274
|
+
} else {
|
|
275
|
+
const HEAP64 = this.fAllocator.getInt64Array();
|
|
276
|
+
HEAP64[(this.fLength >> Math.log2(this.fIntSize)) + part] = BigInt(BUFFER_SIZE);
|
|
277
|
+
HEAP64[(this.fSR >> Math.log2(this.fIntSize)) + part] = BigInt(SAMPLE_RATE);
|
|
278
|
+
HEAP64[(this.fOffset >> Math.log2(this.fIntSize)) + part] = BigInt(offset);
|
|
279
|
+
}
|
|
237
280
|
|
|
238
281
|
// Update and return the new offset
|
|
239
282
|
return offset + BUFFER_SIZE;
|
|
@@ -284,19 +327,34 @@ type AudioBufferItem = {
|
|
|
284
327
|
class SoundfileReader {
|
|
285
328
|
|
|
286
329
|
private readonly fAllocator: WasmAllocator;
|
|
287
|
-
private readonly fPtrSize: number;
|
|
288
330
|
private readonly fSampleSize: number;
|
|
289
331
|
private readonly fContext;
|
|
290
332
|
private readonly fAudioBuffers: AudioBufferItem[];
|
|
291
333
|
|
|
292
|
-
constructor(allocator: WasmAllocator, context: BaseAudioContext,
|
|
334
|
+
constructor(allocator: WasmAllocator, context: BaseAudioContext, sampleSize: number) {
|
|
293
335
|
this.fAllocator = allocator;
|
|
294
|
-
this.fPtrSize = ptrSize;
|
|
295
336
|
this.fSampleSize = sampleSize;
|
|
296
337
|
this.fContext = context;
|
|
297
338
|
this.fAudioBuffers = [];
|
|
298
339
|
}
|
|
299
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Check if the file exists.
|
|
343
|
+
*
|
|
344
|
+
* @param url : the url of the file to check
|
|
345
|
+
* @returns : true if the file exists, otherwise false
|
|
346
|
+
*/
|
|
347
|
+
private async checkFileExists(url: string): Promise<boolean> {
|
|
348
|
+
try {
|
|
349
|
+
console.log(`"checkFileExists" url: ${url}`);
|
|
350
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
351
|
+
return response.ok; // Will be true if the status code is 200-299
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('Fetch error:', error);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
300
358
|
/**
|
|
301
359
|
* Check if the file exists in the given directories.
|
|
302
360
|
*
|
|
@@ -306,23 +364,12 @@ class SoundfileReader {
|
|
|
306
364
|
*/
|
|
307
365
|
private async checkFile(directories: string[], fileName: string): Promise<string> {
|
|
308
366
|
|
|
309
|
-
|
|
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)) {
|
|
367
|
+
if (await this.checkFileExists(fileName)) {
|
|
321
368
|
return fileName;
|
|
322
369
|
} else {
|
|
323
370
|
for (let i = 0; i < directories.length; i++) {
|
|
324
371
|
const pathName = directories[i] + "/" + fileName;
|
|
325
|
-
if (await checkFileExists(pathName)) {
|
|
372
|
+
if (await this.checkFileExists(pathName)) {
|
|
326
373
|
return pathName;
|
|
327
374
|
}
|
|
328
375
|
}
|
|
@@ -412,9 +459,8 @@ class SoundfileReader {
|
|
|
412
459
|
* Crate a soundfile, load all parts and copy audio data to the wasm soundfile buffer.
|
|
413
460
|
* @param pathNameList : list of soundfile paths
|
|
414
461
|
* @param maxChan : maximum number of channels
|
|
415
|
-
* @param isDouble : whether the soundfile will be copied as double
|
|
416
462
|
*/
|
|
417
|
-
async createSoundfile(pathNameList: string[], maxChan: number
|
|
463
|
+
async createSoundfile(pathNameList: string[], maxChan: number): Promise<Soundfile | null> {
|
|
418
464
|
try {
|
|
419
465
|
let curChan = 1; // At least one channel
|
|
420
466
|
let totalLength = 0;
|
|
@@ -438,7 +484,7 @@ class SoundfileReader {
|
|
|
438
484
|
totalLength += (MAX_SOUNDFILE_PARTS - pathNameList.length) * BUFFER_SIZE;
|
|
439
485
|
|
|
440
486
|
// Create the soundfile
|
|
441
|
-
let soundfile = new Soundfile(this.fAllocator, this.
|
|
487
|
+
let soundfile = new Soundfile(this.fAllocator, this.fSampleSize, curChan, totalLength, maxChan, pathNameList.length);
|
|
442
488
|
|
|
443
489
|
//soundfile.displayMemory("After soundfile creation");
|
|
444
490
|
// Init offset
|
|
@@ -887,7 +933,7 @@ export class FaustBaseWebAudioDsp implements IFaustBaseWebAudioDsp {
|
|
|
887
933
|
} else {
|
|
888
934
|
|
|
889
935
|
// Create the soundfiles
|
|
890
|
-
const soundfile = await sfReader.createSoundfile(sfPathNames, MAX_CHAN
|
|
936
|
+
const soundfile = await sfReader.createSoundfile(sfPathNames, MAX_CHAN);
|
|
891
937
|
if (soundfile) {
|
|
892
938
|
|
|
893
939
|
// Update HEAP32 after soundfile creation
|
|
@@ -1037,6 +1083,8 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
|
|
|
1037
1083
|
super(sampleSize, bufferSize);
|
|
1038
1084
|
this.fInstance = instance;
|
|
1039
1085
|
|
|
1086
|
+
console.log("sampleSize: " + sampleSize + " bufferSize: " + bufferSize);
|
|
1087
|
+
|
|
1040
1088
|
// Create JSON object
|
|
1041
1089
|
this.fJSONDsp = JSON.parse(this.fInstance.json);
|
|
1042
1090
|
|
|
@@ -1059,7 +1107,7 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
|
|
|
1059
1107
|
const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
|
|
1060
1108
|
|
|
1061
1109
|
// Create soundfile reader
|
|
1062
|
-
const sfReader = new SoundfileReader(allocator, context, this.
|
|
1110
|
+
const sfReader = new SoundfileReader(allocator, context, this.fSampleSize);
|
|
1063
1111
|
|
|
1064
1112
|
// Init soundfiles memory
|
|
1065
1113
|
await this.initSoundfileMemory(allocator, sfReader, this.fDSP);
|
|
@@ -1179,6 +1227,7 @@ export class FaustMonoWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
|
|
|
1179
1227
|
for (let chan = 0; chan < Math.min(this.getNumOutputs(), output.length); chan++) {
|
|
1180
1228
|
const dspOutput = this.fOutChannels[chan];
|
|
1181
1229
|
output[chan].set(dspOutput);
|
|
1230
|
+
// console.log("chan: " + chan + " output: " + dspOutput[0]);
|
|
1182
1231
|
}
|
|
1183
1232
|
forPlot = output;
|
|
1184
1233
|
}
|
|
@@ -1350,6 +1399,8 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
|
|
|
1350
1399
|
super(sampleSize, bufferSize);
|
|
1351
1400
|
this.fInstance = instance;
|
|
1352
1401
|
|
|
1402
|
+
console.log("sampleSize: " + sampleSize + " bufferSize: " + bufferSize);
|
|
1403
|
+
|
|
1353
1404
|
// Create JSON for voice
|
|
1354
1405
|
this.fJSONDsp = JSON.parse(this.fInstance.voiceJSON);
|
|
1355
1406
|
|
|
@@ -1388,7 +1439,7 @@ export class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implements IFaust
|
|
|
1388
1439
|
const allocator = new WasmAllocator(this.fInstance.memory, this.fEndMemory);
|
|
1389
1440
|
|
|
1390
1441
|
// Create soundfile reader
|
|
1391
|
-
const sfReader = new SoundfileReader(allocator, context, this.
|
|
1442
|
+
const sfReader = new SoundfileReader(allocator, context, this.fSampleSize);
|
|
1392
1443
|
|
|
1393
1444
|
// Init soundfiles memory for all voices
|
|
1394
1445
|
for (let voice = 0; voice < this.fInstance.voices; voice++) {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Time Stretch Sampler
|
|
2
|
+
// written in Faust by David Braun
|
|
3
|
+
// Based on
|
|
4
|
+
// https://github.com/dar-io-p/potenza-time-stretch/blob/main/TimeStretch.h
|
|
5
|
+
// License: GNU GPLv3
|
|
6
|
+
// https://github.com/dar-io-p/potenza-time-stretch/blob/main/LICENSE
|
|
7
|
+
|
|
8
|
+
// todo:
|
|
9
|
+
// * Figure out why the various Loop modes aren't working
|
|
10
|
+
// * Add Portamento glide
|
|
11
|
+
// * Use Lagrange interpolation of soundfile
|
|
12
|
+
|
|
13
|
+
import("stdfaust.lib");
|
|
14
|
+
|
|
15
|
+
s = IGNORE(soundfile("sound[url:{'SixMillionVox.wav']", 2));
|
|
16
|
+
|
|
17
|
+
Sampler(s) = (resetState : advance) ~ init : si.bus(4), totalPhase, direction : ro.interleave(2,2) : grainPlayer, grainPlayer :> si.bus(2)
|
|
18
|
+
with {
|
|
19
|
+
totalPhase = !;
|
|
20
|
+
direction = !;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
ENVELOPE(x) = vgroup("[0] Envelope", x);
|
|
24
|
+
Markers(x) = vgroup("[1] Markers", x);
|
|
25
|
+
PLAYMODES(x) = vgroup("[2] Modes", x);
|
|
26
|
+
STRETCH(x) = vgroup("[3] Stretch", x);
|
|
27
|
+
PITCH(x) = vgroup("[4] Pitch", x);
|
|
28
|
+
IGNORE(x) = vgroup("[99] Ignore", x);
|
|
29
|
+
|
|
30
|
+
rootNote = PITCH(hslider("Root Note [style:knob]", 36, 20, 127, 1));
|
|
31
|
+
fineTune = PITCH(hslider("Fine Tune [style:knob]", 0, -99, 99, 1));
|
|
32
|
+
|
|
33
|
+
ratio = IGNORE(hslider("freq [style:knob]", 440, 20, 20000, 1)) : ba.hz2midikey : _-rootNote + (fineTune/100) : ba.semi2ratio;
|
|
34
|
+
gain = IGNORE(hslider("gain [style:knob]", 0, 0, 1, .01));
|
|
35
|
+
gate = IGNORE(button("gate"));
|
|
36
|
+
|
|
37
|
+
envEnable = ENVELOPE(checkbox("[0] Enable")); // todo: default to ON somehow
|
|
38
|
+
envAttack = ENVELOPE(hslider("[1] Attack [unit:seconds]", 0, 0, 5, .01)); // todo: better scale
|
|
39
|
+
envRelease = ENVELOPE(hslider("[2] Release [unit:seconds]", 0.005, 0, 5, .01)); // todo: better scale
|
|
40
|
+
|
|
41
|
+
envGain = ba.if(envEnable, en.asr(envAttack, 1, envRelease, gate), 1);
|
|
42
|
+
|
|
43
|
+
c = STRETCH(hslider("Crossfade [style:knob]", 0.4, 0.01, 0.5, .01));
|
|
44
|
+
grainSize = STRETCH(hslider("Grain Size [style:knob]", 1000, 20, 2000, 10));
|
|
45
|
+
stretchFactor = STRETCH(hslider("Stretch [style:knob]", 1, 1, 20, .1)
|
|
46
|
+
// : ba.if(stretchComp, _, 1) // todo: I want to do this but can't until stretchComp can default to ON.
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
pitchCompensator = ba.if(PLAYMODES(checkbox("Pitch Comp")), stretchFactor, 1);
|
|
50
|
+
stretchComp = PLAYMODES(checkbox("Stretch Enable"));
|
|
51
|
+
reverse = PLAYMODES(checkbox("[0] Reverse"));
|
|
52
|
+
loop = PLAYMODES(checkbox("[1] Loop Enable"));
|
|
53
|
+
pong = PLAYMODES(checkbox("[2] Pong Enable")); // pong only matters if loop is ON
|
|
54
|
+
|
|
55
|
+
cPrime = 1-c;
|
|
56
|
+
|
|
57
|
+
MAX_AUDIO_LENGTH = 44100*10; // 10 seconds
|
|
58
|
+
|
|
59
|
+
sampleStart = Markers(hslider("[0] Sample Start", 0, 0, MAX_AUDIO_LENGTH, 1));
|
|
60
|
+
loopPos = Markers(hslider("[1] Loop Position", 0, 0, MAX_AUDIO_LENGTH, 1));
|
|
61
|
+
sampleEnd = Markers(hslider("[2] Sample End", MAX_AUDIO_LENGTH, 0, MAX_AUDIO_LENGTH, 1));
|
|
62
|
+
|
|
63
|
+
resetState(_phase1, _phase2, _grain1, _grain2, _totalPhase, _direction) = result
|
|
64
|
+
with {
|
|
65
|
+
doReset = gate & (gate'==0); // noteOn
|
|
66
|
+
samplePos = ba.if(reverse, sampleEnd, sampleStart);
|
|
67
|
+
resultIfResetForward = 0, -1, samplePos, samplePos, samplePos, 1;
|
|
68
|
+
resultIfResetBackward = grainSize, -1, (samplePos-grainSize), (samplePos-grainSize), samplePos, -1;
|
|
69
|
+
resultIfNotReset = _phase1, _phase2, _grain1, _grain2, _totalPhase, _direction;
|
|
70
|
+
result = ba.selectmulti(0, (resultIfNotReset, resultIfResetForward, resultIfResetBackward), doReset*ba.if(reverse, 2, 1));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
advance(_phase1, _phase2, _grain1, _grain2, _totalPhase, _direction) = ba.selectmulti(0, (resultBackward, resultForward), ba.if(_direction>0, 1, 0))
|
|
74
|
+
with {
|
|
75
|
+
// local vars
|
|
76
|
+
f1 = grainSize * c;
|
|
77
|
+
f2 = grainSize * cPrime;
|
|
78
|
+
stretchFactorInverse = 1. / stretchFactor;
|
|
79
|
+
grainOffset = grainSize*cPrime*stretchFactorInverse;
|
|
80
|
+
|
|
81
|
+
pitchDelta = ratio * pitchCompensator;
|
|
82
|
+
|
|
83
|
+
resultForward = ba.selectmulti(0, (resultIfNoNewGrain, resultIfNewGrain), (phase1 >= f2)), totalPhase, direction
|
|
84
|
+
with {
|
|
85
|
+
totalPhase = _totalPhase + pitchDelta*stretchFactorInverse;
|
|
86
|
+
phase1 = _phase1 + ba.if((_grain1 + _phase1) < sampleEnd, pitchDelta, 0);
|
|
87
|
+
|
|
88
|
+
phase2a = _phase2 + ba.if((_phase2 > -1) & (_grain2 + _phase2 < sampleEnd), pitchDelta, 0);
|
|
89
|
+
|
|
90
|
+
phase2 = ba.if(phase2a >= grainSize, -1, phase2a);
|
|
91
|
+
|
|
92
|
+
doLoop = loop & (_grain1 + phase1 >= sampleEnd); // todo: or use totalPhase >= sampleEnd?
|
|
93
|
+
doPong = pong & doLoop;
|
|
94
|
+
direction = ba.if(doPong, -1, 1);
|
|
95
|
+
grain1 = ba.if(doPong, sampleEnd, ba.if(doLoop, loopPos, _grain1+grainOffset));
|
|
96
|
+
|
|
97
|
+
resultIfNoNewGrain = phase1, phase2, _grain1, _grain2;
|
|
98
|
+
resultIfNewGrain = ba.if(doPong, grainSize, 0), phase1, grain1, _grain1;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
resultBackward = ba.selectmulti(0, (resultIfNoNewGrain, resultIfNewGrain), (phase1 <= f1)), totalPhase, direction
|
|
102
|
+
with {
|
|
103
|
+
totalPhase = _totalPhase - pitchDelta*stretchFactorInverse;
|
|
104
|
+
phase1 = _phase1 - ba.if((_grain1 + _phase1) > sampleStart, pitchDelta, 0);
|
|
105
|
+
|
|
106
|
+
phase2a = _phase2 - ba.if((_phase2 > -1) & (_grain2 + _phase2 > sampleStart), pitchDelta, 0);
|
|
107
|
+
|
|
108
|
+
phase2 = ba.if(phase2a <= 0, -1, phase2a);
|
|
109
|
+
|
|
110
|
+
doLoop = loop & (_grain1+phase1 <= loopPos); // todo: or use totalPhase < loopPos?
|
|
111
|
+
doPong = pong & doLoop;
|
|
112
|
+
direction = ba.if(doPong, 1, -1);
|
|
113
|
+
grain1 = ba.if(doPong, loopPos, ba.if(doLoop, sampleEnd, _grain1-grainOffset));
|
|
114
|
+
|
|
115
|
+
resultIfNoNewGrain = phase1, phase2, _grain1, _grain2;
|
|
116
|
+
resultIfNewGrain = ba.if(doPong, 0, grainSize), phase1, grain1, _grain1;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
grainPlayer(phase, grain) = (part, pos) : outs(s) : sp.stereoize(_* windowGain * gain * envGain * safeGain)
|
|
121
|
+
with {
|
|
122
|
+
pos = phase+grain;
|
|
123
|
+
windowGain = phase : ba.bpf.start(0, 0) : ba.bpf.point(grainSize*c, 1) : ba.bpf.point(grainSize*cPrime, 1) : ba.bpf.end(grainSize, 0)
|
|
124
|
+
: _*ma.PI*.5 : sin <: _*_ :>_ // enable this line for constant-powered window blending
|
|
125
|
+
;
|
|
126
|
+
safeGain = ba.if(pos>=sampleEnd, 0, 1);
|
|
127
|
+
part = 0;
|
|
128
|
+
// get file's properties
|
|
129
|
+
length(s) = part,0 : s : _,si.block(outputs(s)-1);
|
|
130
|
+
srate(s) = part,0 : s : !,_,si.block(outputs(s)-2);
|
|
131
|
+
// play sample
|
|
132
|
+
outs(s) = s : si.block(2), si.bus(outputs(s)-2);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
init(_phase1, _phase2, _grain1, _grain2, _totalPhase, _direction) = _phase1, _phase2, _grain1, _grain2, _totalPhase, ba.if(ba.time==0, 1, _direction);
|
|
136
|
+
|
|
137
|
+
phaseEffect = sp.stereoize((+ ~ (de.fdelayltv(N, MAX_DELAY_SAMPLES, delayAmt)*fbGain)))
|
|
138
|
+
with {
|
|
139
|
+
fbGain = ba.if(phaseAmt == 0, 0, 0.6);
|
|
140
|
+
phaseAmt = hslider("Phase", 0, 0, 30, .01);
|
|
141
|
+
N = 2;
|
|
142
|
+
MAX_DELAY_SAMPLES = 30/1000:ba.sec2samp;
|
|
143
|
+
delayAmt = phaseAmt: si.smoo : _/1000:ba.sec2samp;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Remember to use ScriptProcessor instead of AudioWorklet!
|
|
147
|
+
process = hgroup("Amigo", Sampler(s));
|
|
148
|
+
|
|
149
|
+
effect = phaseEffect;
|
|
150
|
+
// effect = _, _;
|
package/test/emsc.dsp
ADDED
|
@@ -395,6 +395,12 @@ declare class WasmAllocator {
|
|
|
395
395
|
* @returns The view of the memory buffer as Int32Array.
|
|
396
396
|
*/
|
|
397
397
|
getInt32Array(): Int32Array;
|
|
398
|
+
/**
|
|
399
|
+
* Returns the Int64 view of the underlying buffer object.
|
|
400
|
+
*
|
|
401
|
+
* @returns The view of the memory buffer as BigInt64Array.
|
|
402
|
+
*/
|
|
403
|
+
getInt64Array(): BigInt64Array;
|
|
398
404
|
/**
|
|
399
405
|
* Returns the Float32 view of the underlying buffer object.
|
|
400
406
|
*
|
|
@@ -416,8 +422,9 @@ declare class Soundfile {
|
|
|
416
422
|
private readonly fOffset;
|
|
417
423
|
private readonly fSampleSize;
|
|
418
424
|
private readonly fPtrSize;
|
|
425
|
+
private readonly fIntSize;
|
|
419
426
|
private readonly fAllocator;
|
|
420
|
-
constructor(allocator: WasmAllocator,
|
|
427
|
+
constructor(allocator: WasmAllocator, sampleSize: number, curChan: number, length: number, maxChan: number, totalParts: number);
|
|
421
428
|
private allocBuffers;
|
|
422
429
|
shareBuffers(curChan: number, maxChan: number): void;
|
|
423
430
|
copyToOut(part: number, maxChannels: number, offset: number, buffer: AudioBuffer): void;
|
|
@@ -432,11 +439,17 @@ declare class Soundfile {
|
|
|
432
439
|
}
|
|
433
440
|
declare class SoundfileReader {
|
|
434
441
|
private readonly fAllocator;
|
|
435
|
-
private readonly fPtrSize;
|
|
436
442
|
private readonly fSampleSize;
|
|
437
443
|
private readonly fContext;
|
|
438
444
|
private readonly fAudioBuffers;
|
|
439
|
-
constructor(allocator: WasmAllocator, context: BaseAudioContext,
|
|
445
|
+
constructor(allocator: WasmAllocator, context: BaseAudioContext, sampleSize: number);
|
|
446
|
+
/**
|
|
447
|
+
* Check if the file exists.
|
|
448
|
+
*
|
|
449
|
+
* @param url : the url of the file to check
|
|
450
|
+
* @returns : true if the file exists, otherwise false
|
|
451
|
+
*/
|
|
452
|
+
private checkFileExists;
|
|
440
453
|
/**
|
|
441
454
|
* Check if the file exists in the given directories.
|
|
442
455
|
*
|
|
@@ -476,9 +489,8 @@ declare class SoundfileReader {
|
|
|
476
489
|
* Crate a soundfile, load all parts and copy audio data to the wasm soundfile buffer.
|
|
477
490
|
* @param pathNameList : list of soundfile paths
|
|
478
491
|
* @param maxChan : maximum number of channels
|
|
479
|
-
* @param isDouble : whether the soundfile will be copied as double
|
|
480
492
|
*/
|
|
481
|
-
createSoundfile(pathNameList: string[], maxChan: number
|
|
493
|
+
createSoundfile(pathNameList: string[], maxChan: number): Promise<Soundfile | null>;
|
|
482
494
|
getHEAP32(): Int32Array;
|
|
483
495
|
}
|
|
484
496
|
/**
|