@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grame/faustwasm",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "WebAssembly version of Faust Compiler",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/esm/index.d.ts",
@@ -139,9 +139,13 @@ class FaustWasmInstantiator {
139
139
  }
140
140
 
141
141
  static async createAsyncMonoDSPInstance(factory: LooseFaustDspFactory) {
142
- const parsedJson = JSON.parse(factory.json);
143
- // If the JSON contains a soundfile UI element, we need to create a memory object
144
- if (Array.isArray(parsedJson.ui) && parsedJson.ui.some((group: { items: any[]; }) => group.items?.some(item => item.type === "soundfile"))) {
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
- const parsedJson = JSON.parse(factory.json);
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 (Array.isArray(parsedJson.ui) && parsedJson.ui.some((group: { items: any[]; }) => group.items?.some(item => item.type === "soundfile"))) {
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);
@@ -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, ptrSize: number, sampleSize: number, curChan: number, length: number, maxChan: number, totalParts: number) {
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
- this.fPtrSize = ptrSize;
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 * 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);
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 + ptrSize) >> 2] = this.fLength;
152
- HEAP32[(this.fPtr + 2 * ptrSize) >> 2] = this.fSR;
153
- HEAP32[(this.fPtr + 3 * ptrSize) >> 2] = this.fOffset;
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
- 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;
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 outputReal: Float32Array = HEAPF.subarray((output + offset * this.fSampleSize) >> Math.log2(this.fSampleSize),
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 outputReal: Float64Array = HEAPF.subarray((output + offset * this.fSampleSize) >> Math.log2(this.fSampleSize),
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
- // 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;
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, ptrSize: number, sampleSize: number) {
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
- 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)) {
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, isDouble: boolean): Promise<Soundfile | null> {
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.fPtrSize, this.fSampleSize, curChan, totalLength, maxChan, pathNameList.length);
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, this.fSampleSize === 8);
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.fPtrSize, this.fSampleSize);
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.fPtrSize, this.fSampleSize);
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
@@ -0,0 +1,5 @@
1
+ import("stdfaust.lib");
2
+
3
+ ns = no.noise;
4
+ filter = fi.lowpass;
5
+ process = (ns , 2 , 800) : filter;
@@ -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, ptrSize: number, sampleSize: number, curChan: number, length: number, maxChan: number, totalParts: number);
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, ptrSize: number, sampleSize: number);
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, isDouble: boolean): Promise<Soundfile | null>;
493
+ createSoundfile(pathNameList: string[], maxChan: number): Promise<Soundfile | null>;
482
494
  getHEAP32(): Int32Array;
483
495
  }
484
496
  /**