5.7 KiB
dx7-algorithm-32-additive-organ: why native vs wasm reads -79.5 dB
Nothing here was heard. Offline buffers, 48 kHz, mono, 3 s, no params, same Faust text on both sides. Same toolchain as aqua-feel-native-diff-2026-11-10.md (wasm: faustwasm 0.20.0 / libfaust 2.90.19; native: FaustNativeRender, libfaust 2.90.20, -single -ftz 2 -vec -lv 1, faustlibraries master 3d8c6ef via AQUASYNTH_FAUST_HOME).
Verdict: cause found, two causes, neither a float32 floor and neither a bug in AquaSynth
The -79.5 dB is a real, deterministic phase drift of the sine voices, not noise, not a quiet signal and not a sample shift.
- Increment rounding in
os.phasor, selected by whether1/ma.SRis shared (-79.3 to -93.0 dB when removed). Lowered patches definetime = ba.time / ma.SR;. With that division in the graph, the native compiler computes the phasor increment asfreq * fround(1/SR); the wasm compiler keepsfreq / SR. For 392 Hz at 48 kHz:fround(392/48000) = 0.008166667074,fround(392 * fround(1/48000)) = 0.008166666143, one 2^-30 step (9.31e-10) apart. Measured effective increments: wasm 0.008166667074 on both, native 0.008166667074 without the division in the graph and 0.008166666143 with it. The phase then drifts linearly (about 1.3e-4 cycles over 3 s between the sides), so the waveform error grows linearly with time (0 to 1.65e-4 over 0.45 s, then follows the envelope down). si.smoolibrary text on/macro/drawbarand/macro/shimmer(-93.0 to -141.2 dB when also pinned). This is the known old-form vsimp(3)difference. The 2026-11-10 imp3 pin did not show it because cause 1 sat 14 dB above it.
With both removed the patch reads -141.2 dB (max 1.8e-7), the same float floor as the Ding instruments (-130 to -142 dB).
Evidence (isolating experiments; each changes one thing)
Baseline reproduced through native-diff.mjs --items smoother: 1.65e-4 max, -79.27 dB. The signal is not quiet: RMS 0.193, peak 0.686. The last 2 s are digital silence and contribute zero error (73 % exactly-equal samples is that silence, not agreement). The error is zero at nothing but silence and rises linearly while the voices sound; lag test: lag 0 error 6.3e-5 vs 18.4 at lag +-1, so no shift.
| experiment | rel RMS err dB | max err |
|---|---|---|
| whole patch, as lowered | -79.3 | 1.65e-4 |
filters stripped (fi.lowpass/fi.highpass) |
-79.3 | 1.66e-4 |
| per voice, no filters: v0 / v1 / v2 / v3 / v4 / v5 | -85.8 / -79.3 / -143.3 / -73.8 / -143.1 / -109.0 | |
sin(2pi*os.phasor(1,392))*0.5 alone |
-163.7 | 3e-8 |
same sine times age+1, a ramp, or any slow signal |
-64 to -74 | 1e-4 to 3e-3 |
os.phasor(1,392) alone |
exact (0) | 0 |
os.phasor(1,392) + (ba.time/ma.SR > 100) (never true) |
-33.2 | 1.0 (one wrap sample) |
same with ba.time > 1e9 or ma.SR > 1e9 instead |
exact (0) | 0 |
same with ba.time / 48000.0 > 100 |
exact (0) | 0 |
| effective phasor increment, native, with / without the division | 0.008166666143 / 0.008166667074 | |
whole patch, time = ba.time / 48000.0; only |
-93.0 | 1.78e-5 |
whole patch, every ma.SR a literal |
-93.0 | 1.78e-5 |
| whole patch, phasor replaced by a float-accumulating one on both sides (library phasor text is fixed point in master, see below) | -84.0 | 9.4e-5 |
age literal + si.smoo pinned to old form / imp3 |
-114.0 / -141.2 | 1.3e-6 / 1.8e-7 |
si.smoo pinned to imp3 only (SR still shared) |
-79.5 | 1.65e-4 |
Voices 2 and 4 (588 and 980 Hz) are clean because their increments round the same either way; 196, 392, 784 and 1176 are not. After cause 1 is removed only voice 1 (the one scaled by patch_param_0, the smoothed drawbar) stays at -91.5 dB, and voice 5 (shimmer) at -109 dB: cause 2.
Arithmetic check: Math.fround(392 * Math.fround(1/48000)) = 0.008166666142642498, matching the measured native increment to 10 digits.
Scope
- Other patches with the same
time = ba.time / ma.SR;line are not affected the same way: with imp3 pinned andtimemade literal,advanced-bird-syrinx-graphstays -75.4,advanced-human-larynx-graph-94.2,dx7-algorithm-8-bright-pair-94.7,advanced-alien-multivalve-graphmoves -97.9 to -110.9. Whether a patch is affected depends on whether its phasor increments round differently under division vs reciprocal multiply. native-diff.mjs --smoother imp3therefore cannot reach the float floor for patches whose phasor increment is affected; its-60 dBlimit still holds with the new reading (-79.5 is explained, not a worse mismatch).
Not verified
- I did not read the generated C++/wasm. "Native uses
freq * fround(1/SR), wasmfreq/SR" is inferred from the measured increments and the arithmetic above (exact match), not from emitted code (the wasm build has no C++ backend; nofaustCLI here). It is also not separated whether the difference is libfaust 2.90.19 vs 2.90.20, or the-vec -lv 1/ LLVM path, or the library's phasor text (fixed point in master, which also looks different from what the wasm bundle holds). Settling it: emit the same DSP with one libfaust version's C++ backend and compare theinstanceConstantslines, or render the wasm side with a 2.90.20 wasm build. - Which side is nearer the ideal is not important: both are within one 2^-30 increment step of 392/48000 and in opposite directions (+4.1e-10, -5.2e-10).
Reproduce
. /var/lib/mind-aqua-self/env-review.sh
export AQUASYNTH_FAUST_HOME=/var/lib/mind-aqua-self/faust-home-overlay-20261031
cd tools/aqua-feel && node native-diff.mjs /var/lib/mind-aqua-self/native-diff-bin --items smoother --out "$TMPDIR/out"
then edit the lowered DSP text on both sides (as the table says) and render with renderOnce and FaustNativeRender. The experiment scripts are scratch in $TMPDIR and not committed.