@grame/faustwasm 0.18.0 → 0.18.2
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 +28 -1
- package/assets/standalone/faustwasm/index.d.ts +22 -5
- package/assets/standalone/faustwasm/index.js +35 -5
- package/assets/standalone/faustwasm/index.js.map +2 -2
- package/dist/cjs/index.d.ts +22 -5
- package/dist/cjs/index.js +35 -5
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs-bundle/index.d.ts +22 -5
- package/dist/cjs-bundle/index.js +36 -6
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm/index.d.ts +22 -5
- package/dist/esm/index.js +35 -5
- package/dist/esm/index.js.map +2 -2
- package/dist/esm-bundle/index.d.ts +22 -5
- package/dist/esm-bundle/index.js +36 -6
- package/dist/esm-bundle/index.js.map +2 -2
- package/libfaust-wasm/libfaust-wasm.wasm +0 -0
- package/package.json +1 -1
- package/src/FaustWebAudioDsp.ts +41 -5
- package/test/faustlive-wasm/faustwasm/index.d.ts +22 -5
- package/test/faustlive-wasm/faustwasm/index.js +35 -5
- package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
- package/test/unit/voice-steal-fade.test.mjs +139 -0
- package/test/web/poly-schedule.html +97 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FaustWasm
|
|
2
2
|
|
|
3
|
-
The FaustWasm library presents a convenient, high-level API that wraps around [Faust](https://faust.grame.fr) compiler. This library's interface is primarily designed for [TypeScript](https://www.typescriptlang.org/) usage, although it also provides API descriptions and documentation for pure JavaScript. The WebAssembly version of the Faust Compiler, compatible with both [Node.js](https://nodejs.org) and web browsers, has been compiled using [Emscripten](https://emscripten.org/)
|
|
3
|
+
The FaustWasm library presents a convenient, high-level API that wraps around [Faust](https://faust.grame.fr) compiler. This library's interface is primarily designed for [TypeScript](https://www.typescriptlang.org/) usage, although it also provides API descriptions and documentation for pure JavaScript. The WebAssembly version of the Faust Compiler, compatible with both [Node.js](https://nodejs.org) and web browsers, has been compiled using [Emscripten](https://emscripten.org/) 6.0.3.
|
|
4
4
|
|
|
5
5
|
The library offers functionality for compiling Faust DSP code into WebAssembly, enabling its utilization as WebAudio nodes within a standard WebAudio node graph. Moreover, it supports offline rendering scenarios. Furthermore, supplementary tools can be employed for generating SVGs from Faust DSP programs.
|
|
6
6
|
|
|
@@ -281,6 +281,33 @@ process = ba.pulsen(1, 10000) : pm.djembe(60, 0.3, 0.4, 1);
|
|
|
281
281
|
})();
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
### Sample-accurate scheduling
|
|
285
|
+
|
|
286
|
+
Every control method on a Faust AudioWorklet node takes an optional `time` argument as its last parameter: `setParamValue(path, value, time)`, `keyOn(channel, pitch, velocity, time)`, `keyOff(channel, pitch, velocity, time)`, `midiMessage(data, time)`, `ctrlChange(channel, ctrl, value, time)`, `pitchWheel(channel, wheel, time)`. `time` is in AudioContext seconds — the same clock as `audioCtx.currentTime` and `AudioParam.setValueAtTime` — and the action lands on the exact sample that instant names, inside the audio block that contains it. The unit is the second (a JavaScript double), the granularity is the sample: the processor converts it with `Math.round(time * sampleRate)` to a frame on the audio clock, so an instant is honoured to the nearest sample (about 21 µs at 48 kHz):
|
|
287
|
+
|
|
288
|
+
```JavaScript
|
|
289
|
+
const t0 = audioCtx.currentTime + 0.1;
|
|
290
|
+
|
|
291
|
+
// A parameter change on the exact sample
|
|
292
|
+
node.setParamValue("/mydsp/gate", 1, t0);
|
|
293
|
+
node.setParamValue("/mydsp/gate", 0, t0 + 0.05);
|
|
294
|
+
|
|
295
|
+
// A polyphonic pattern scheduled ahead of time
|
|
296
|
+
node.keyOn(0, 60, 100, t0);
|
|
297
|
+
node.keyOff(0, 60, 0, t0 + 0.5);
|
|
298
|
+
node.keyOn(0, 63, 100, t0 + 0.25);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Regular `AudioParam` automation on the node's parameters (`setValueAtTime`, `linearRampToValueAtTime`, ...) is followed sample-accurately as well, so a note and a parameter ramp can be scheduled against one another. Timestamps carried by Web Audio Modules (WAM) events are honoured the same way.
|
|
302
|
+
|
|
303
|
+
Left out, `time` behaves as before: the message is applied on arrival, at the start of the next audio block. A `time` already in the past is applied as soon as possible, in order. Notes:
|
|
304
|
+
|
|
305
|
+
- the FFT processor honours `time` at block granularity rather than sample granularity;
|
|
306
|
+
- ScriptProcessor nodes (`sp: true`) accept the argument and apply the message on arrival;
|
|
307
|
+
- `setParamValue` with a negative `time` throws (it is refused by `AudioParam.setValueAtTime`).
|
|
308
|
+
|
|
309
|
+
The page `test/web/poly-schedule.html` demonstrates the API, and `test/timing/measure.mjs` measures the sample accuracy (see below).
|
|
310
|
+
|
|
284
311
|
### Running the tests
|
|
285
312
|
|
|
286
313
|
Unit tests live in `test/unit` and run in plain Node (no browser needed). This builds the ESM bundle first, then runs every `*.test.mjs` file with Node's built-in test runner:
|
|
@@ -1099,6 +1099,8 @@ export declare class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implement
|
|
|
1099
1099
|
private fAudioMixing;
|
|
1100
1100
|
private fAudioMixingHalf;
|
|
1101
1101
|
private fMixingBase;
|
|
1102
|
+
/** Float view of the memory, kept from `initMemory` like `fHEAP32`. */
|
|
1103
|
+
private fHEAPF;
|
|
1102
1104
|
private fVoiceTable;
|
|
1103
1105
|
private fSampleRate;
|
|
1104
1106
|
/** Voices whose crossfade this block already rendered in full. */
|
|
@@ -1123,14 +1125,29 @@ export declare class FaustPolyWebAudioDsp extends FaustBaseWebAudioDsp implement
|
|
|
1123
1125
|
/**
|
|
1124
1126
|
* Render the stolen voices, each across the whole block.
|
|
1125
1127
|
*
|
|
1126
|
-
* A steal is a
|
|
1128
|
+
* A steal is a hand-over: the voice plays the note it is losing over the
|
|
1127
1129
|
* first half of the buffer, that half fades out, and the new note plays
|
|
1128
|
-
* the second half
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1130
|
+
* the second half, faded in. Both fades are needed. `fadeOut` scales the
|
|
1131
|
+
* buffer, not the voice: after `keyOn` the DSP carries on from its live
|
|
1132
|
+
* state -- an envelope that never reached silence is still up -- so
|
|
1133
|
+
* without the fade-in the second half started at full level, a step on
|
|
1134
|
+
* every stolen note. On a monophonic patch whose voice never falls
|
|
1135
|
+
* silent that is a click on every note after the first (measured on a
|
|
1136
|
+
* long-release bass: 0, 0, 0, 0.1485, 0.2618 across the split).
|
|
1137
|
+
*
|
|
1138
|
+
* The fades have to be half a block -- 64 frames -- rather than half a
|
|
1139
|
+
* slice, which late in the block would be a frame or two, or nothing at
|
|
1140
|
+
* all. So this runs before the slicing, and `fRenderSlice` skips these
|
|
1141
|
+
* voices.
|
|
1132
1142
|
*/
|
|
1133
1143
|
private renderStolenVoices;
|
|
1144
|
+
/**
|
|
1145
|
+
* Scale `count` frames of each channel at `$tables` from 1/count up to 1.
|
|
1146
|
+
*
|
|
1147
|
+
* The mixer has `fadeOut` in wasm; this is its counterpart on the JS
|
|
1148
|
+
* side, over the channel-pointer table the same way the mixer reads it.
|
|
1149
|
+
*/
|
|
1150
|
+
private fadeIn;
|
|
1134
1151
|
/**
|
|
1135
1152
|
* Move the mixing tables along with the input and output ones.
|
|
1136
1153
|
*
|
|
@@ -3960,12 +3960,20 @@ var FaustPolyWebAudioDsp = class _FaustPolyWebAudioDsp extends FaustBaseWebAudio
|
|
|
3960
3960
|
/**
|
|
3961
3961
|
* Render the stolen voices, each across the whole block.
|
|
3962
3962
|
*
|
|
3963
|
-
* A steal is a
|
|
3963
|
+
* A steal is a hand-over: the voice plays the note it is losing over the
|
|
3964
3964
|
* first half of the buffer, that half fades out, and the new note plays
|
|
3965
|
-
* the second half
|
|
3966
|
-
*
|
|
3967
|
-
*
|
|
3968
|
-
*
|
|
3965
|
+
* the second half, faded in. Both fades are needed. `fadeOut` scales the
|
|
3966
|
+
* buffer, not the voice: after `keyOn` the DSP carries on from its live
|
|
3967
|
+
* state -- an envelope that never reached silence is still up -- so
|
|
3968
|
+
* without the fade-in the second half started at full level, a step on
|
|
3969
|
+
* every stolen note. On a monophonic patch whose voice never falls
|
|
3970
|
+
* silent that is a click on every note after the first (measured on a
|
|
3971
|
+
* long-release bass: 0, 0, 0, 0.1485, 0.2618 across the split).
|
|
3972
|
+
*
|
|
3973
|
+
* The fades have to be half a block -- 64 frames -- rather than half a
|
|
3974
|
+
* slice, which late in the block would be a frame or two, or nothing at
|
|
3975
|
+
* all. So this runs before the slicing, and `fRenderSlice` skips these
|
|
3976
|
+
* voices.
|
|
3969
3977
|
*/
|
|
3970
3978
|
renderStolenVoices() {
|
|
3971
3979
|
const stolen = this.fStolen;
|
|
@@ -3984,6 +3992,11 @@ var FaustPolyWebAudioDsp = class _FaustPolyWebAudioDsp extends FaustBaseWebAudio
|
|
|
3984
3992
|
this.getNumOutputs(),
|
|
3985
3993
|
this.fAudioMixing
|
|
3986
3994
|
);
|
|
3995
|
+
this.fadeIn(
|
|
3996
|
+
this.fBufferSize - (this.fBufferSize >> 1),
|
|
3997
|
+
this.getNumOutputs(),
|
|
3998
|
+
this.fAudioMixingHalf
|
|
3999
|
+
);
|
|
3987
4000
|
voice.fLevel = this.fInstance.mixerAPI.mixCheckVoice(
|
|
3988
4001
|
this.fBufferSize,
|
|
3989
4002
|
this.getNumOutputs(),
|
|
@@ -3992,6 +4005,22 @@ var FaustPolyWebAudioDsp = class _FaustPolyWebAudioDsp extends FaustBaseWebAudio
|
|
|
3992
4005
|
);
|
|
3993
4006
|
});
|
|
3994
4007
|
}
|
|
4008
|
+
/**
|
|
4009
|
+
* Scale `count` frames of each channel at `$tables` from 1/count up to 1.
|
|
4010
|
+
*
|
|
4011
|
+
* The mixer has `fadeOut` in wasm; this is its counterpart on the JS
|
|
4012
|
+
* side, over the channel-pointer table the same way the mixer reads it.
|
|
4013
|
+
*/
|
|
4014
|
+
fadeIn(count, chans, $tables) {
|
|
4015
|
+
const shift = Math.log2(this.fSampleSize);
|
|
4016
|
+
const HEAPF = this.fHEAPF;
|
|
4017
|
+
for (let chan = 0; chan < chans; chan++) {
|
|
4018
|
+
const start = this.fHEAP32[($tables >> 2) + chan] >> shift;
|
|
4019
|
+
for (let i = 0; i < count; i++) {
|
|
4020
|
+
HEAPF[start + i] *= (i + 1) / count;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
3995
4024
|
/**
|
|
3996
4025
|
* Move the mixing tables along with the input and output ones.
|
|
3997
4026
|
*
|
|
@@ -4090,6 +4119,7 @@ var FaustPolyWebAudioDsp = class _FaustPolyWebAudioDsp extends FaustBaseWebAudio
|
|
|
4090
4119
|
for (let chan = 0; chan < this.getNumInputs(); chan++) {
|
|
4091
4120
|
this.fInBase[chan] = HEAP32[(this.fAudioInputs >> 2) + chan];
|
|
4092
4121
|
}
|
|
4122
|
+
this.fHEAPF = HEAPF;
|
|
4093
4123
|
this.fOutBase = [];
|
|
4094
4124
|
this.fMixingBase = [];
|
|
4095
4125
|
for (let chan = 0; chan < this.getNumOutputs(); chan++) {
|