From e849c624f94c48b40fd64313e521aa21eccad528 Mon Sep 17 00:00:00 2001 From: Aqua Date: Tue, 10 Nov 2026 06:23:08 +0000 Subject: [PATCH 1/4] aqua-feel: diff faustwasm against the native libfaust path Add FaustNativeRender and native-diff.mjs; render thruster, two sfxr patches and a tract vowel through both and record the numbers. Co-Authored-By: Claude Sonnet 5.5 --- .../aqua-feel-native-diff-2026-11-10.json | 269 ++++++++++++++++++ .../aqua-feel-native-diff-2026-11-10.md | 46 +++ .../FaustNativeRender.csproj | 12 + tools/FaustNativeRender/Program.cs | 51 ++++ tools/aqua-feel/README.md | 4 +- tools/aqua-feel/native-diff.mjs | 42 +++ 6 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 docs/renders/aqua-feel-native-diff-2026-11-10.json create mode 100644 docs/renders/aqua-feel-native-diff-2026-11-10.md create mode 100644 tools/FaustNativeRender/FaustNativeRender.csproj create mode 100644 tools/FaustNativeRender/Program.cs create mode 100644 tools/aqua-feel/native-diff.mjs diff --git a/docs/renders/aqua-feel-native-diff-2026-11-10.json b/docs/renders/aqua-feel-native-diff-2026-11-10.json new file mode 100644 index 0000000..23ab820 --- /dev/null +++ b/docs/renders/aqua-feel-native-diff-2026-11-10.json @@ -0,0 +1,269 @@ +{ + "libfaustWasm": "2.90.19", + "sampleRate": 48000, + "rows": [ + { + "id": "thruster@rpm300", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.026072267442941666, + "maxErrAt": 142707, + "rmsErr": 0.002385837227503057, + "relRmsErrDb": -28.770505442344437, + "wasm": { + "samplePeak": 0.14264661073684692, + "truePeakDb": -16.90120405295226, + "loudnessLufs": -30.226833288468896, + "centroidMedianHz": 751.9185607592701, + "overSamples": 0, + "rmsDb": -23.676437109698263, + "crestDb": 6.761666256511239, + "noiseToToneDb": -6.937988717513285, + "noiseToToneADb": 13.092512355240387, + "lowMotion": false + }, + "native": { + "samplePeak": 0.14242461323738098, + "truePeakDb": -16.915261179402926, + "loudnessLufs": -30.216031113774466, + "centroidMedianHz": 751.528437557621, + "overSamples": 0, + "rmsDb": -23.670746205832152, + "crestDb": 6.74244718138185, + "noiseToToneDb": -6.888378522251635, + "noiseToToneADb": 13.056342933565631, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [] + }, + { + "id": "thruster@rpm600", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.049423644319176674, + "maxErrAt": 142945, + "rmsErr": 0.004812282166272209, + "relRmsErrDb": -23.51217894411906, + "wasm": { + "samplePeak": 0.15958738327026367, + "truePeakDb": -15.926835268189564, + "loudnessLufs": -28.69474712970973, + "centroidMedianHz": 1227.9568144573402, + "overSamples": 0, + "rmsDb": -22.840558133782523, + "crestDb": 6.900529208325618, + "noiseToToneDb": -0.7035305515542589, + "noiseToToneADb": 5.044756311984671, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1601162701845169, + "truePeakDb": -15.90057398970821, + "loudnessLufs": -28.709339901369557, + "centroidMedianHz": 1224.4350816353904, + "overSamples": 0, + "rmsDb": -22.846021449137904, + "crestDb": 6.934730747403392, + "noiseToToneDb": -0.6166079485198495, + "noiseToToneADb": 6.053873560512358, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [] + }, + { + "id": "thruster@rpm1200", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.08972565084695816, + "maxErrAt": 143228, + "rmsErr": 0.012448641233006897, + "relRmsErrDb": -16.610480431926955, + "wasm": { + "samplePeak": 0.19161102175712585, + "truePeakDb": -14.343339234769212, + "loudnessLufs": -26.051033400616078, + "centroidMedianHz": 2114.222071076734, + "overSamples": 0, + "rmsDb": -21.48680915202533, + "crestDb": 7.1352188868162605, + "noiseToToneDb": -4.949059402813134, + "noiseToToneADb": 12.652391835948997, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1893547624349594, + "truePeakDb": -14.423961460001248, + "loudnessLufs": -26.05070520432609, + "centroidMedianHz": 2115.6363534197994, + "overSamples": 0, + "rmsDb": -21.484457233344667, + "crestDb": 7.029981882815694, + "noiseToToneDb": -4.965083035181173, + "noiseToToneADb": 12.716143920958068, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [] + }, + { + "id": "thruster@rpm4800", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.2870774045586586, + "maxErrAt": 142979, + "rmsErr": 0.054912511748541046, + "relRmsErrDb": -8.246006212467824, + "wasm": { + "samplePeak": 0.36383503675460815, + "truePeakDb": -8.715719927350808, + "loudnessLufs": -17.418922989049733, + "centroidMedianHz": 4914.23686401765, + "overSamples": 0, + "rmsDb": -16.959964506391177, + "crestDb": 8.17805487903365, + "noiseToToneDb": -2.834032055202698, + "noiseToToneADb": 9.67560044209106, + "lowMotion": false + }, + "native": { + "samplePeak": 0.3622153401374817, + "truePeakDb": -8.742560302886037, + "loudnessLufs": -17.424886445264, + "centroidMedianHz": 4908.148280531137, + "overSamples": 0, + "rmsDb": -16.961323502311725, + "crestDb": 8.140660284709858, + "noiseToToneDb": -2.9540607279645457, + "noiseToToneADb": 9.40924191267517, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [] + }, + { + "id": "sfxr-classic-classic-sfxr-abstract-golf", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000002171844244003296, + "maxErrAt": 117685, + "rmsErr": 9.912912629276112e-8, + "relRmsErrDb": -134.45251378934543, + "wasm": { + "samplePeak": 0.9895820021629333, + "truePeakDb": 0.9076630707842905, + "loudnessLufs": -5.327743141800993, + "centroidMedianHz": 8147.614236982103, + "overSamples": 0, + "rmsDb": -5.623460642702886, + "crestDb": 5.532496403571967, + "noiseToToneDb": -0.9960950126656059, + "noiseToToneADb": 7.586387572827178, + "lowMotion": true + }, + "native": { + "samplePeak": 0.9895820021629333, + "truePeakDb": 0.9076628030277624, + "loudnessLufs": -5.327743184793801, + "centroidMedianHz": 8147.614175634302, + "overSamples": 0, + "rmsDb": -5.623460696287697, + "crestDb": 5.5324964571567765, + "noiseToToneDb": -0.9960947920917818, + "noiseToToneADb": 7.5863877077205615, + "lowMotion": true + }, + "flagsWasm": [ + "waived truePeakDb", + "waived lowMotion", + "waived loudnessLufs", + "warn truePeakDb" + ], + "flagsNative": [ + "waived truePeakDb", + "waived lowMotion", + "waived loudnessLufs", + "warn truePeakDb" + ] + }, + { + "id": "sfxr-explosion", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000002158805727958679, + "maxErrAt": 129292, + "rmsErr": 1.1522412234189382e-7, + "relRmsErrDb": -127.13848106038596, + "wasm": { + "samplePeak": 0.6732206344604492, + "truePeakDb": -3.0561513953602306, + "loudnessLufs": -8.634121016036506, + "centroidMedianHz": 9795.322296183533, + "overSamples": 0, + "rmsDb": -11.630620604104497, + "crestDb": 8.193768979977403, + "noiseToToneDb": 60, + "noiseToToneADb": 60, + "lowMotion": true + }, + "native": { + "samplePeak": 0.6732206344604492, + "truePeakDb": -3.0561512376276347, + "loudnessLufs": -8.634121064908708, + "centroidMedianHz": 9795.322310734966, + "overSamples": 0, + "rmsDb": -11.630620652864625, + "crestDb": 8.193769028737531, + "noiseToToneDb": 60, + "noiseToToneADb": 60, + "lowMotion": true + }, + "flagsWasm": [ + "waived lowMotion" + ], + "flagsNative": [ + "waived lowMotion" + ] + }, + { + "id": "tract/vowel-a", + "lenWasm": 34560, + "lenNative": 34560, + "maxErr": 0.0001921914517879486, + "maxErrAt": 28187, + "rmsErr": 0.000046111049670587934, + "relRmsErrDb": -52.60441410619507, + "wasm": { + "samplePeak": 0.08378415554761887, + "truePeakDb": -21.518327105526673, + "loudnessLufs": -34.251568232392565, + "centroidMedianHz": 1937.1220888731689, + "overSamples": 0, + "rmsDb": -33.80275018649363, + "crestDb": 12.265988122620389, + "noiseToToneDb": -10.436050344670264, + "noiseToToneADb": -12.885220061403881, + "lowMotion": false + }, + "native": { + "samplePeak": 0.08390921354293823, + "truePeakDb": -21.50539262673788, + "loudnessLufs": -34.23471535099468, + "centroidMedianHz": 1937.2296872141594, + "overSamples": 0, + "rmsDb": -33.78684837329708, + "crestDb": 12.263041385142849, + "noiseToToneDb": -10.447393994101596, + "noiseToToneADb": -12.89873810612136, + "lowMotion": false + }, + "flagsWasm": [ + "FAIL onsetMs" + ], + "flagsNative": [ + "FAIL onsetMs" + ] + } + ] +} \ No newline at end of file diff --git a/docs/renders/aqua-feel-native-diff-2026-11-10.md b/docs/renders/aqua-feel-native-diff-2026-11-10.md new file mode 100644 index 0000000..89e782c --- /dev/null +++ b/docs/renders/aqua-feel-native-diff-2026-11-10.md @@ -0,0 +1,46 @@ +# aqua-feel: faustwasm vs the daemon's native libfaust path + +**Nothing here was heard.** Numbers from offline buffers, same Faust source, 48 kHz, mono, 512-frame blocks, same params. + +- wasm side: `tools/aqua-feel/render.js` (faustwasm 0.20.0, libfaust 2.90.x, `-ftz 2`). +- native side: `tools/FaustNativeRender` over `AquaSynthPatchCompiler` (`NativeFaustRuntime.cs:530`: `-single -ftz 2 -vec -lv 1`), libfaust 2.90.20 + LLVM built in the Linux sandbox (`AQUASYNTH_FAUST_HOME`, overlay of the 2026-10-31 build). +- driver: `node tools/aqua-feel/native-diff.mjs --tract `; raw numbers in `aqua-feel-native-diff-2026-11-10.json`. +- tract voice: `vowel-a` Faust source from the 2026-11-08 render kit (0.72 s, default controls). + +Columns are wasm / native. + +| item | max abs err (at sample) | err vs signal RMS | sample peak | centroid Hz | LUFS | true peak dBTP | gate flags | +|---|---|---|---|---|---|---|---| +| thruster rpm300 | 2.6e-2 (142707) | -28.8 dB | .1426/.1424 | 751.9/751.5 | -30.227/-30.216 | -16.90/-16.92 | none / none | +| thruster rpm600 | 4.9e-2 | -23.5 dB | .1596/.1601 | 1228.0/1224.4 | -28.695/-28.709 | -15.93/-15.90 | none / none | +| thruster rpm1200 | 9.0e-2 | -16.6 dB | .1916/.1894 | 2114.2/2115.6 | -26.051/-26.051 | -14.34/-14.42 | none / none | +| thruster rpm4800 | 2.9e-1 | -8.2 dB | .3638/.3622 | 4914.2/4908.1 | -17.419/-17.425 | -8.72/-8.74 | none / none | +| sfxr classic abstract-golf | 2.2e-6 | -134.5 dB | .9896/.9896 | 8147.6/8147.6 | -5.328/-5.328 | +0.908/+0.908 | waived truePeakDb, lowMotion, loudnessLufs; warn truePeakDb / identical | +| sfxr explosion | 2.2e-6 | -127.1 dB | .6732/.6732 | 9795.3/9795.3 | -8.634/-8.634 | -3.06/-3.06 | waived lowMotion / identical | +| tract vowel-a | 1.9e-4 | -52.6 dB | .08378/.08391 | 1937.1/1937.2 | -34.252/-34.235 | -21.52/-21.51 | FAIL onsetMs / identical | + +Hiss caps (noiseToToneADb, wasm / native; cap): rpm300 13.09/13.06 (16), rpm600 5.04/6.05 (8), rpm1200 12.65/12.72 (15). rpm4800 9.68/9.41 (no cap). + +## Verdict + +**sfxr: the two paths agree** to float rounding (2e-6). The sfxr flags and waivers (+0.908 dBTP, -5.33 LUFS, held noise) are properties of the Faust source, not of the wasm host. + +**Tract voice: agrees to -52.6 dB** (max 1.9e-4 on a 0.084 peak); every metric within 0.02 dB or 0.01% centroid. Its `onsetMs` gate failure appears identically on both sides. + +**Thruster: waveforms diverge, metrics agree.** The error grows from ~1e-4 in the first 0.25 s to the size of the signal by 2 s at rpm 4800 (relative RMS error -8 dB), so the two renders are not the same waveform at high rpm. Level and spectrum metrics stay close: LUFS within 0.014 dB, true peak within 0.09 dB, sample peak within 1.2 %, centroid within 0.3 %, crest within 0.1 dB (rpm1200 0.11), hiss within 0.3 dB except rpm600 A-weighted, which reads 1.0 dB higher native. + +### Cause (measured, one part not isolated) + +The thruster takes its rotor frequency from `rpm = hslider(...) : si.smooth(ba.tau2pole(0.2))`, and every oscillator (`os.sawtooth`, `os.square`, `os.osc`) integrates that frequency into phase. Rendering `rpm/6000` alone at rpm=4800: the first 1000 samples match to 0.001 rpm, then wasm **stalls at 4796.632** from about 2.5 s on while native converges to **4799.999**. A float32 simulation of the one-pole without fused multiply-add reproduces 4796.6318 exactly, so the wasm value is plain float32 rounding of the smoother (the increment drops under half an ulp). Native does not stall, so it evaluates the smoother with different arithmetic; whether that is `-vec`, `-lv 1` or LLVM JIT contraction was **not isolated** (a fused simulation stalls at 4797.66, not 4799.999, so FMA alone does not explain it). The ~3.4 rpm difference moves the rotor tone by ~0.085 Hz, a phase drift that grows to a full cycle in seconds; discontinuous saw edges then differ by up to 2.0 sample-wise (isolated `os.sawtooth(shaft)`: max err 1.99). Removing the `wander` noise changes nothing (rpm300 2.62e-2, rpm4800 2.89e-1 unchanged), so the random wander is not the cause. Noise-only parts (`rattle` 6.7e-4, `rpm/6000` 5.6e-4) agree closely, consistent with the sfxr result: the LCG noise is the same on both paths. + +### What this invalidates + +- **Thruster v5 mix / hiss caps / loudness / true-peak / centroid claims:** stand. They are statistical and agree within the numbers above. The rpm600 hiss cap (8 dB) has 1.95 dB headroom natively, not the 2.96 dB the threshold note assumes; still passes. +- **Anything that compares thruster sample-by-sample or by phase** (before/after waveform diffs, null tests, onset or envelope timing at sub-ms resolution on the rotor tone, `correlate` against ratings tied to exact waveforms) is wasm-specific. A listener on the daemon hears the same level and spectrum, a different phase trajectory. +- **A listening trial on wasm renders of the thruster** heard rpm settling 0.07 % short of the target at rpm 4800. Not a timbre difference by these metrics, but the trial did not hear the daemon's path. +- **sfxr flags:** not invalidated. They hold on the native path. +- **Not tested here:** the other 75 aqua-feel items; the Ding mixes; the faust-vec flag itself in isolation; block sizes other than 512; live (non-offline) host behaviour. + +## Sandbox note + +Native libfaust is buildable here: libfaust 2.90.20 with LLVM, built 2026-10-31 (`faust-llvm-build-20261031`, `faust-home-overlay-20261031`); `dotnet` 10.0.403 builds `FaustNativeRender` with `env-review.sh`. diff --git a/tools/FaustNativeRender/FaustNativeRender.csproj b/tools/FaustNativeRender/FaustNativeRender.csproj new file mode 100644 index 0000000..9dd3508 --- /dev/null +++ b/tools/FaustNativeRender/FaustNativeRender.csproj @@ -0,0 +1,12 @@ + + + Exe + net10.0 + enable + enable + true + + + + + diff --git a/tools/FaustNativeRender/Program.cs b/tools/FaustNativeRender/Program.cs new file mode 100644 index 0000000..2294abe --- /dev/null +++ b/tools/FaustNativeRender/Program.cs @@ -0,0 +1,51 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using AquaSynth.Faust; + +// Offline render of one Faust source through the daemon's native libfaust path (AquaSynthPatchCompiler, -vec -lv 1) +// to a mono float32 WAV, for diffing against aqua-feel's faustwasm render. 48 kHz, 512-frame blocks, like render.js. +// usage: FaustNativeRender [controlSuffix=value ...] +const int Rate = 48000, Frames = 512; +if (args.Length < 3) { Console.Error.WriteLine("usage: FaustNativeRender [suffix=value ...]"); return 2; } +var dspPath = Path.GetFullPath(args[0]); +var outPath = Path.GetFullPath(args[1]); +var total = (int)Math.Round(double.Parse(args[2]) * Rate); +var source = File.ReadAllText(dspPath); +Directory.CreateDirectory(Path.GetDirectoryName(outPath)!); + +using var compiler = new AquaSynthPatchCompiler(new AquaSynthNativeOptions(SampleRate: Rate)); +var name = Path.GetFileNameWithoutExtension(dspPath); +if (!compiler.TryCompileSource(new AquaSynthCompileIdentity(name, name, ""), source, (float)total / Rate, out var patch0, out var cerr)) +{ + Console.Error.WriteLine("COMPILE FAILED: " + cerr); + return 1; +} +using var patch = patch0!; +var ctl = new Dictionary(); +foreach (var spec in args.Skip(3)) +{ + var eq = spec.IndexOf('='); + var path = patch.ControlPaths.Single(p => p.EndsWith(spec[..eq])); + ctl[path] = float.Parse(spec[(eq + 1)..], System.Globalization.CultureInfo.InvariantCulture); +} + +using var stream = patch.CreateStreamingPatch(); +var buf = GC.AllocateArray(Frames, pinned: true); +var ptrs = GC.AllocateArray(1, pinned: true); +unsafe { ptrs[0] = (IntPtr)Unsafe.AsPointer(ref buf[0]); } +var outP = Marshal.UnsafeAddrOfPinnedArrayElement(ptrs, 0); +var pcm = new float[total]; +for (var at = 0; at < total; at += Frames) +{ + stream.ProcessBlock(Frames, IntPtr.Zero, outP, ctl); + Array.Copy(buf, 0, pcm, at, Math.Min(Frames, total - at)); +} +using (var w = new BinaryWriter(File.Create(outPath))) +{ + var db = total * 4; + w.Write("RIFF"u8); w.Write(36 + db); w.Write("WAVEfmt "u8); w.Write(16); w.Write((short)3); w.Write((short)1); + w.Write(Rate); w.Write(Rate * 4); w.Write((short)4); w.Write((short)32); w.Write("data"u8); w.Write(db); + foreach (var s in pcm) w.Write(s); +} +Console.WriteLine($"{outPath} frames={total} controls={ctl.Count}"); +return 0; diff --git a/tools/aqua-feel/README.md b/tools/aqua-feel/README.md index 87aa094..52bf70b 100644 --- a/tools/aqua-feel/README.md +++ b/tools/aqua-feel/README.md @@ -33,8 +33,8 @@ non-zero on any unwaived failure or stale waiver. No CI runner exists in this re The command gain 0.6 is an assumption (`DingsPlaybackHost` takes it from the caller as 0..1), so the Ding loudness window depends on it. Render path: the same faustwasm offline processor the playground and the listening trial use (libfaust 2.90.x, `-ftz 2`), -mono channel 0. It is **not** the daemon's native libfaust path (`-vec`); the same Faust source should render alike, but -nobody has diffed the two. +mono channel 0. It is **not** the daemon's native libfaust path (`-vec`); the two agree on sfxr and a tract voice, and diverge in waveform on the thruster (float32 rpm smoother) while its level and +spectrum metrics agree: `docs/renders/aqua-feel-native-diff-2026-11-10.md`, `node native-diff.mjs`. ## What it measures (per item, `report.json` `items[].metrics`) diff --git a/tools/aqua-feel/native-diff.mjs b/tools/aqua-feel/native-diff.mjs new file mode 100644 index 0000000..28dc4a3 --- /dev/null +++ b/tools/aqua-feel/native-diff.mjs @@ -0,0 +1,42 @@ +// Diff aqua-feel's faustwasm render against the daemon's native libfaust render (tools/FaustNativeRender) of the same +// Faust source, at the same params, 48 kHz and length. Nothing here was heard. +// usage: node native-diff.mjs [--tract PATH.dsp] [--out DIR] +import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { join, dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { SR, measure } from "./metrics.js"; +import { gate } from "./gate.js"; +import { loadRuntime, compile, renderOnce, writeWav, readText, playground } from "./render.js"; + +const here = dirname(fileURLToPath(import.meta.url)), args = process.argv.slice(2); +const opt = (k) => { const i = args.indexOf(k); return i < 0 ? null : args[i + 1]; }; +const bin = resolve(args[0]), out = resolve(opt("--out") ?? join(here, "out-native-diff")); +const low = (n) => readText(join(playground, "lowered", n)); +const items = [300, 600, 1200, 4800].map((rpm) => ({ id: `thruster@rpm${rpm}`, group: "patch", file: "thruster.dsp", source: low("thruster.dsp"), params: { rpm }, seconds: 3 })); +items.push({ id: "sfxr-classic-classic-sfxr-abstract-golf", group: "patch", file: "sfxr-classic-classic-sfxr-abstract-golf.dsp", source: low("sfxr-classic-classic-sfxr-abstract-golf.dsp"), params: {}, seconds: 3 }); +items.push({ id: "sfxr-explosion", group: "patch", file: "sfxr-explosion.dsp", source: low("sfxr-explosion.dsp"), params: {}, seconds: 3 }); +if (opt("--tract")) items.push({ id: "tract/vowel-a", group: "patch", file: "tract-vowel-a.dsp", source: readText(opt("--tract")), params: {}, seconds: 0.72 }); +mkdirSync(join(out, "dsp"), { recursive: true }); + +const readF32Wav = (p) => { const b = readFileSync(p), n = b.readUInt32LE(40) / 4, x = new Float32Array(n); for (let i = 0; i < n; i++) x[i] = b.readFloatLE(44 + i * 4); return x; }; +const thresholds = JSON.parse(readText(join(here, "thresholds.json"))); +const flagsOf = (id, group, m) => { const g = gate({ items: [{ id, group, metrics: m }] }, thresholds, { partial: true }); return [...g.failures.map((f) => `FAIL ${f.metric}`), ...g.waived.map((f) => `waived ${f.metric}`), ...g.warnings.map((f) => `warn ${f.metric}`)]; }; + +const rt = await loadRuntime(), rows = []; +for (const it of items) { + const w = (await renderOnce(await compile(rt, it.id, it.source), it.params, it.seconds)).samples; + const dsp = join(out, "dsp", it.file); writeFileSync(dsp, it.source); + const wav = join(out, `${it.id.replace(/[^A-Za-z0-9._@-]+/g, "_")}.native.wav`); + const r = spawnSync("dotnet", [bin.endsWith(".dll") ? bin : join(bin, "FaustNativeRender.dll"), dsp, wav, String(it.seconds), ...Object.entries(it.params).map(([k, v]) => `${k}=${v}`)], { encoding: "utf8" }); + if (r.status !== 0) { rows.push({ id: it.id, error: (r.stderr || r.stdout).slice(0, 400) }); continue; } + const n = readF32Wav(wav); writeWav(join(out, `${it.id.replace(/[^A-Za-z0-9._@-]+/g, "_")}.wasm.wav`), w); + const len = Math.min(w.length, n.length); let maxErr = 0, at = 0, sq = 0, sqRef = 0; + for (let i = 0; i < len; i++) { const d = Math.abs(w[i] - n[i]); if (d > maxErr) { maxErr = d; at = i; } sq += d * d; sqRef += w[i] * w[i]; } + const mw = measure(w), mn = measure(n); + rows.push({ id: it.id, lenWasm: w.length, lenNative: n.length, maxErr, maxErrAt: at, rmsErr: Math.sqrt(sq / len), relRmsErrDb: 10 * Math.log10(sq / Math.max(sqRef, 1e-30)), + wasm: pick(mw), native: pick(mn), flagsWasm: flagsOf(it.id, it.group, mw), flagsNative: flagsOf(it.id, it.group, mn) }); +} +function pick(m) { return { samplePeak: m.samplePeak, truePeakDb: m.truePeakDb, loudnessLufs: m.loudnessLufs, centroidMedianHz: m.centroidMedianHz, overSamples: m.overSamples, rmsDb: m.rmsDb, crestDb: m.crestDb, noiseToToneDb: m.noiseToToneDb, noiseToToneADb: m.noiseToToneADb, lowMotion: m.lowMotion }; } +writeFileSync(join(out, "native-diff.json"), JSON.stringify({ libfaustWasm: rt.version, sampleRate: SR, rows }, null, 1)); +console.log(JSON.stringify({ libfaustWasm: rt.version, rows }, null, 1)); -- 2.47.3 From 878f91c06231c692d45e540b223076a9a8cc342e Mon Sep 17 00:00:00 2001 From: Aqua Date: Tue, 10 Nov 2026 06:31:54 +0000 Subject: [PATCH 2/4] native-diff: correct the cause (stdlib text, not the host) and harden the tool A Soul pass found the two sides compile against different stdlib text: the wasm bundle's si.smooth stalls in float32, the sandbox's faustlibraries master does not. Reword the report, README and render.js; fail on length mismatch and render errors; check output count; match controls exactly; default output under the temp dir. Co-Authored-By: Claude Sonnet 5.5 --- .../aqua-feel-native-diff-2026-11-10.md | 43 ++++++++++++------- tools/FaustNativeRender/Program.cs | 8 ++-- tools/aqua-feel/README.md | 3 +- tools/aqua-feel/native-diff.mjs | 10 ++++- tools/aqua-feel/render.js | 2 +- 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/docs/renders/aqua-feel-native-diff-2026-11-10.md b/docs/renders/aqua-feel-native-diff-2026-11-10.md index 89e782c..7e612c9 100644 --- a/docs/renders/aqua-feel-native-diff-2026-11-10.md +++ b/docs/renders/aqua-feel-native-diff-2026-11-10.md @@ -2,10 +2,12 @@ **Nothing here was heard.** Numbers from offline buffers, same Faust source, 48 kHz, mono, 512-frame blocks, same params. -- wasm side: `tools/aqua-feel/render.js` (faustwasm 0.20.0, libfaust 2.90.x, `-ftz 2`). -- native side: `tools/FaustNativeRender` over `AquaSynthPatchCompiler` (`NativeFaustRuntime.cs:530`: `-single -ftz 2 -vec -lv 1`), libfaust 2.90.20 + LLVM built in the Linux sandbox (`AQUASYNTH_FAUST_HOME`, overlay of the 2026-10-31 build). +- wasm side: `tools/aqua-feel/render.js` (faustwasm 0.20.0, libfaust 2.90.19 and the stdlib bundled with it, `-ftz 2`). +- native side: `tools/FaustNativeRender` over `AquaSynthPatchCompiler` (`NativeFaustRuntime.cs:530`: `-single -ftz 2 -vec -lv 1`), libfaust 2.90.20 + LLVM built in the Linux sandbox 2026-10-31, with the stdlib `faustlibraries-20261031` (master 3d8c6ef) from `AQUASYNTH_FAUST_HOME`. **The two sides compile against different standard-library text**, and that decides the thruster result below; "native" here means this sandbox's library, which the repo does not pin (`docs/faust-toolchain-boundary.md`: the host's `share/faust`). - driver: `node tools/aqua-feel/native-diff.mjs --tract `; raw numbers in `aqua-feel-native-diff-2026-11-10.json`. -- tract voice: `vowel-a` Faust source from the 2026-11-08 render kit (0.72 s, default controls). +- tract voice: `vowel-a` Faust source from the 2026-11-08 render kit (0.72 s, default controls). Not in the repo (`--tract` takes a path), not an aqua-feel catalog item, so its `FAIL onsetMs` is the patch-group rule applied by this script, not a repo gate result. +- the script has no tolerance and exits 1 only on a render error or a length mismatch; every other judgement is the reader's. +- Nothing here was heard. Columns are wasm / native. @@ -23,24 +25,33 @@ Hiss caps (noiseToToneADb, wasm / native; cap): rpm300 13.09/13.06 (16), rpm600 ## Verdict -**sfxr: the two paths agree** to float rounding (2e-6). The sfxr flags and waivers (+0.908 dBTP, -5.33 LUFS, held noise) are properties of the Faust source, not of the wasm host. +**sfxr (two items): the paths agree** to float rounding (2e-6). These two patches use no library smoother (`smooth01` and a local `seg_smooth`), so the agreement says nothing about the library difference below. The gate flags are the script's `gate()` applied to each side's metrics for these two items only; they match. -**Tract voice: agrees to -52.6 dB** (max 1.9e-4 on a 0.084 peak); every metric within 0.02 dB or 0.01% centroid. Its `onsetMs` gate failure appears identically on both sides. +**Tract voice: agrees to -52.6 dB** (max 1.9e-4 on a 0.084 peak); metrics within 0.02 dB and 0.01 % centroid. The residual is the same library difference: the source calls `si.smoo` 101 times; with the old smoother form pinned on both sides the error is -61.2 dB, with the new form pinned on both it is 3.1e-7 (-109.3 dB). -**Thruster: waveforms diverge, metrics agree.** The error grows from ~1e-4 in the first 0.25 s to the size of the signal by 2 s at rpm 4800 (relative RMS error -8 dB), so the two renders are not the same waveform at high rpm. Level and spectrum metrics stay close: LUFS within 0.014 dB, true peak within 0.09 dB, sample peak within 1.2 %, centroid within 0.3 %, crest within 0.1 dB (rpm1200 0.11), hiss within 0.3 dB except rpm600 A-weighted, which reads 1.0 dB higher native. +**Thruster: waveforms diverge, level and spectrum metrics stay close.** Relative RMS error -28.8 dB at rpm300 to -8.2 dB at rpm4800. At rpm4800 it grows over time: windowed -53, -33, -22, -15, -10 and -4 dB for 0-0.25, 0.25-0.5, 0.5-1, 1-1.5, 1.5-2 and 2-3 s. Differences in the metrics: LUFS 0.015 dB, true peak 0.081 dB, sample peak 1.2 %, centroid 0.3 %, crest 0.11 dB, hiss within 0.27 dB except rpm600 A-weighted, 1.0 dB higher native. -### Cause (measured, one part not isolated) +### Cause (isolated by a separate Soul pass, 2026-11-10) -The thruster takes its rotor frequency from `rpm = hslider(...) : si.smooth(ba.tau2pole(0.2))`, and every oscillator (`os.sawtooth`, `os.square`, `os.osc`) integrates that frequency into phase. Rendering `rpm/6000` alone at rpm=4800: the first 1000 samples match to 0.001 rpm, then wasm **stalls at 4796.632** from about 2.5 s on while native converges to **4799.999**. A float32 simulation of the one-pole without fused multiply-add reproduces 4796.6318 exactly, so the wasm value is plain float32 rounding of the smoother (the increment drops under half an ulp). Native does not stall, so it evaluates the smoother with different arithmetic; whether that is `-vec`, `-lv 1` or LLVM JIT contraction was **not isolated** (a fused simulation stalls at 4797.66, not 4799.999, so FMA alone does not explain it). The ~3.4 rpm difference moves the rotor tone by ~0.085 Hz, a phase drift that grows to a full cycle in seconds; discontinuous saw edges then differ by up to 2.0 sample-wise (isolated `os.sawtooth(shaft)`: max err 1.99). Removing the `wander` noise changes nothing (rpm300 2.62e-2, rpm4800 2.89e-1 unchanged), so the random wander is not the cause. Noise-only parts (`rattle` 6.7e-4, `rpm/6000` 5.6e-4) agree closely, consistent with the sfxr result: the LCG noise is the same on both paths. +The thruster smooths `rpm` with `si.smooth(ba.tau2pole(0.2))`, and every oscillator integrates that into phase. The two library texts define `si.smooth` differently: -### What this invalidates +- the wasm bundle (libfaust 2.90.19, `/usr/share/faust`): `smooth(s) = *(1.0 - s) : + ~ *(s)`. In float32 it stalls: the rpm freezes at **4796.632** from sample ~72063 (1.5 s) for a target of 4800. A float32 simulation without fused multiply-add reproduces 4796.6318359375. +- the native overlay (faustlibraries master 3d8c6ef, `signals.lib:391`): `smooth = imp(3)`, an error-form recursion whose comment says it cannot stall. It reaches 4800.0001. -- **Thruster v5 mix / hiss caps / loudness / true-peak / centroid claims:** stand. They are statistical and agree within the numbers above. The rpm600 hiss cap (8 dB) has 1.95 dB headroom natively, not the 2.96 dB the threshold note assumes; still passes. -- **Anything that compares thruster sample-by-sample or by phase** (before/after waveform diffs, null tests, onset or envelope timing at sub-ms resolution on the rotor tone, `correlate` against ratings tied to exact waveforms) is wasm-specific. A listener on the daemon hears the same level and spectrum, a different phase trajectory. -- **A listening trial on wasm renders of the thruster** heard rpm settling 0.07 % short of the target at rpm 4800. Not a timbre difference by these metrics, but the trial did not hear the daemon's path. -- **sfxr flags:** not invalidated. They hold on the native path. -- **Not tested here:** the other 75 aqua-feel items; the Ding mixes; the faust-vec flag itself in isolation; block sizes other than 512; live (non-offline) host behaviour. +Evidence that the library text, not `-vec`, `-lv`, `-double` or contraction, decides it: the `faust` CLI emits the same update expression for the same `si.smooth` source under scalar, `-vec -lv 0/1` and `-double`. With the old explicit form pinned in the DSP on both sides, native ends at 4797.6573 (LLVM does contract to FMA, which moves the stall point from 4796.632 to 4797.657, matching the FMA simulation) and wasm at 4796.6319. With the explicit `imp(3)` form pinned on both sides, thruster error falls from 2.61e-2 to 5.7e-7 (rpm300, -120.3 dB) and from 2.87e-1 to 1.13e-4 (rpm4800, -80.5 dB). The stall point is also hypersensitive: one ulp on the pole constant gives 4799.38 or 4798.57. -## Sandbox note +The earlier version of this report said the cause was the float32 rounding of the smoother on a native path that "does not stall for an unisolated reason" and suggested `-vec`; that was wrong. The `wander` term is not the cause (removing it leaves rpm300 2.62e-2, rpm4800 2.89e-1). -Native libfaust is buildable here: libfaust 2.90.20 with LLVM, built 2026-10-31 (`faust-llvm-build-20261031`, `faust-home-overlay-20261031`); `dotnet` 10.0.403 builds `FaustNativeRender` with `env-review.sh`. +The isolation probes are scratch scripts in `/var/lib/mind-aqua-self/soul-nd-1` (not committed); the numbers above are not in the `.json`, which holds only the seven main rows. + +### What this does and does not invalidate + +- Level, true-peak, centroid, crest and hiss claims for the thruster hold on both libraries within the numbers above. The rpm600 hiss cap (8 dB) has 1.95 dB of headroom with the native library, not the 2.96 dB the threshold note assumes; still passes. +- Sample- or phase-level comparisons of the thruster (null tests, exact waveform diffs, sub-ms timing of the rotor tone) are specific to the library that rendered them. +- Which library a given daemon uses depends on its `share/faust`. A daemon shipping a release stdlib with the old `si.smooth` would stall like wasm; this sandbox's would not. The report cannot say what the production daemon does. +- Other sources that import `si.smooth` or `si.smoo` are expected to show the same skew and were **not tested**: 11 sources by count (7 patches, 3 Ding instruments, plus `advanced-human-larynx-graph`), which puts most of the 36 Ding items (27 or more) on the divergent library. +- **Not tested:** the other 75 of the 81 aqua-feel items, the sfxr-family patches beyond the two here, Ding mixes, block sizes other than 512, and any live (non-offline) host. + +## Toolchain + +dotnet 10.0.403 builds `FaustNativeRender` with `/var/lib/mind-aqua-self/env-review.sh`. Re-running the script reproduces the JSON byte for byte. diff --git a/tools/FaustNativeRender/Program.cs b/tools/FaustNativeRender/Program.cs index 2294abe..e6aff0d 100644 --- a/tools/FaustNativeRender/Program.cs +++ b/tools/FaustNativeRender/Program.cs @@ -4,9 +4,9 @@ using AquaSynth.Faust; // Offline render of one Faust source through the daemon's native libfaust path (AquaSynthPatchCompiler, -vec -lv 1) // to a mono float32 WAV, for diffing against aqua-feel's faustwasm render. 48 kHz, 512-frame blocks, like render.js. -// usage: FaustNativeRender [controlSuffix=value ...] +// usage: FaustNativeRender [control=value ...] const int Rate = 48000, Frames = 512; -if (args.Length < 3) { Console.Error.WriteLine("usage: FaustNativeRender [suffix=value ...]"); return 2; } +if (args.Length < 3) { Console.Error.WriteLine("usage: FaustNativeRender [control=value ...]"); return 2; } var dspPath = Path.GetFullPath(args[0]); var outPath = Path.GetFullPath(args[1]); var total = (int)Math.Round(double.Parse(args[2]) * Rate); @@ -21,11 +21,13 @@ if (!compiler.TryCompileSource(new AquaSynthCompileIdentity(name, name, ""), sou return 1; } using var patch = patch0!; +if (patch.Manifest.OutputCount != 1) { Console.Error.WriteLine($"expected 1 output, patch has {patch.Manifest.OutputCount}"); return 1; } var ctl = new Dictionary(); foreach (var spec in args.Skip(3)) { var eq = spec.IndexOf('='); - var path = patch.ControlPaths.Single(p => p.EndsWith(spec[..eq])); + var key = spec[..eq]; + var path = patch.ControlPaths.Single(p => p == key || p == "/" + key || p.EndsWith("/" + key)); ctl[path] = float.Parse(spec[(eq + 1)..], System.Globalization.CultureInfo.InvariantCulture); } diff --git a/tools/aqua-feel/README.md b/tools/aqua-feel/README.md index 52bf70b..dfe80db 100644 --- a/tools/aqua-feel/README.md +++ b/tools/aqua-feel/README.md @@ -33,8 +33,7 @@ non-zero on any unwaived failure or stale waiver. No CI runner exists in this re The command gain 0.6 is an assumption (`DingsPlaybackHost` takes it from the caller as 0..1), so the Ding loudness window depends on it. Render path: the same faustwasm offline processor the playground and the listening trial use (libfaust 2.90.x, `-ftz 2`), -mono channel 0. It is **not** the daemon's native libfaust path (`-vec`); the two agree on sfxr and a tract voice, and diverge in waveform on the thruster (float32 rpm smoother) while its level and -spectrum metrics agree: `docs/renders/aqua-feel-native-diff-2026-11-10.md`, `node native-diff.mjs`. +mono channel 0. It is **not** the daemon's native libfaust path (`-vec`); on a thruster the two diverge in waveform because the wasm bundle's `si.smooth` and the sandbox's native stdlib are different library text (not the host, not `-vec`); level and spectrum metrics stay close: `docs/renders/aqua-feel-native-diff-2026-11-10.md`, `node native-diff.mjs`. ## What it measures (per item, `report.json` `items[].metrics`) diff --git a/tools/aqua-feel/native-diff.mjs b/tools/aqua-feel/native-diff.mjs index 28dc4a3..37cdfa7 100644 --- a/tools/aqua-feel/native-diff.mjs +++ b/tools/aqua-feel/native-diff.mjs @@ -1,17 +1,21 @@ // Diff aqua-feel's faustwasm render against the daemon's native libfaust render (tools/FaustNativeRender) of the same // Faust source, at the same params, 48 kHz and length. Nothing here was heard. +// Both sides compile against their own stdlib (faustwasm bundles libfaust's; native reads AQUASYNTH_FAUST_HOME's share/faust), so a +// difference here can be library text, not the host: the report records the identities. There is no tolerance: the script exits 1 +// only on a render error or a length mismatch; reading the numbers is on the reader. // usage: node native-diff.mjs [--tract PATH.dsp] [--out DIR] import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { join, dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { tmpdir } from "node:os"; import { SR, measure } from "./metrics.js"; import { gate } from "./gate.js"; import { loadRuntime, compile, renderOnce, writeWav, readText, playground } from "./render.js"; const here = dirname(fileURLToPath(import.meta.url)), args = process.argv.slice(2); const opt = (k) => { const i = args.indexOf(k); return i < 0 ? null : args[i + 1]; }; -const bin = resolve(args[0]), out = resolve(opt("--out") ?? join(here, "out-native-diff")); +const bin = resolve(args[0]), out = resolve(opt("--out") ?? join(tmpdir(), "aqua-feel-native-diff")); const low = (n) => readText(join(playground, "lowered", n)); const items = [300, 600, 1200, 4800].map((rpm) => ({ id: `thruster@rpm${rpm}`, group: "patch", file: "thruster.dsp", source: low("thruster.dsp"), params: { rpm }, seconds: 3 })); items.push({ id: "sfxr-classic-classic-sfxr-abstract-golf", group: "patch", file: "sfxr-classic-classic-sfxr-abstract-golf.dsp", source: low("sfxr-classic-classic-sfxr-abstract-golf.dsp"), params: {}, seconds: 3 }); @@ -31,7 +35,8 @@ for (const it of items) { const r = spawnSync("dotnet", [bin.endsWith(".dll") ? bin : join(bin, "FaustNativeRender.dll"), dsp, wav, String(it.seconds), ...Object.entries(it.params).map(([k, v]) => `${k}=${v}`)], { encoding: "utf8" }); if (r.status !== 0) { rows.push({ id: it.id, error: (r.stderr || r.stdout).slice(0, 400) }); continue; } const n = readF32Wav(wav); writeWav(join(out, `${it.id.replace(/[^A-Za-z0-9._@-]+/g, "_")}.wasm.wav`), w); - const len = Math.min(w.length, n.length); let maxErr = 0, at = 0, sq = 0, sqRef = 0; + if (w.length !== n.length) { rows.push({ id: it.id, error: `length mismatch: wasm ${w.length}, native ${n.length}` }); continue; } + const len = w.length; let maxErr = 0, at = 0, sq = 0, sqRef = 0; for (let i = 0; i < len; i++) { const d = Math.abs(w[i] - n[i]); if (d > maxErr) { maxErr = d; at = i; } sq += d * d; sqRef += w[i] * w[i]; } const mw = measure(w), mn = measure(n); rows.push({ id: it.id, lenWasm: w.length, lenNative: n.length, maxErr, maxErrAt: at, rmsErr: Math.sqrt(sq / len), relRmsErrDb: 10 * Math.log10(sq / Math.max(sqRef, 1e-30)), @@ -39,4 +44,5 @@ for (const it of items) { } function pick(m) { return { samplePeak: m.samplePeak, truePeakDb: m.truePeakDb, loudnessLufs: m.loudnessLufs, centroidMedianHz: m.centroidMedianHz, overSamples: m.overSamples, rmsDb: m.rmsDb, crestDb: m.crestDb, noiseToToneDb: m.noiseToToneDb, noiseToToneADb: m.noiseToToneADb, lowMotion: m.lowMotion }; } writeFileSync(join(out, "native-diff.json"), JSON.stringify({ libfaustWasm: rt.version, sampleRate: SR, rows }, null, 1)); +if (rows.some((r) => r.error)) process.exitCode = 1; console.log(JSON.stringify({ libfaustWasm: rt.version, rows }, null, 1)); diff --git a/tools/aqua-feel/render.js b/tools/aqua-feel/render.js index 91cdb11..61a6a38 100644 --- a/tools/aqua-feel/render.js +++ b/tools/aqua-feel/render.js @@ -1,5 +1,5 @@ // Offline render through the same faustwasm runtime the playground and the listening trial load (not the daemon's -// native libfaust path: same Faust source, same compiler version family, but a different host; see README). +// native libfaust path: same Faust source text but a different compiler build and standard library, so not the same arithmetic; see docs/renders/aqua-feel-native-diff-2026-11-10.md). import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; -- 2.47.3 From 4e6a64b31136ed96ad379e6e317949a4ddadcc74 Mon Sep 17 00:00:00 2001 From: Aqua Date: Tue, 10 Nov 2026 06:44:09 +0000 Subject: [PATCH 3/4] native-diff: stdlib identity, 12 smoother sources under three smoothers, tolerance Record which faustlibraries text each host compiles against (Starfire 2.85.5 has the stalling smooth; the sandbox master has imp(3)). Add --items smoother and --smoother old|imp3 (same rewritten source on both sides) and limits fit to the measured deltas; control matching normalises the leading slash. Report extended; faustlibraries pin proposed, not made. Co-Authored-By: Claude Sonnet 5.5 --- .../aqua-feel-native-diff-2026-11-10.json | 23 +- .../aqua-feel-native-diff-2026-11-10.md | 69 ++- ...-native-diff-smoother-imp3-2026-11-10.json | 480 ++++++++++++++++++ ...tive-diff-smoother-library-2026-11-10.json | 480 ++++++++++++++++++ ...l-native-diff-smoother-old-2026-11-10.json | 480 ++++++++++++++++++ tools/FaustNativeRender/Program.cs | 4 +- tools/aqua-feel/native-diff.mjs | 49 +- 7 files changed, 1565 insertions(+), 20 deletions(-) create mode 100644 docs/renders/aqua-feel-native-diff-smoother-imp3-2026-11-10.json create mode 100644 docs/renders/aqua-feel-native-diff-smoother-library-2026-11-10.json create mode 100644 docs/renders/aqua-feel-native-diff-smoother-old-2026-11-10.json diff --git a/docs/renders/aqua-feel-native-diff-2026-11-10.json b/docs/renders/aqua-feel-native-diff-2026-11-10.json index 23ab820..35b0987 100644 --- a/docs/renders/aqua-feel-native-diff-2026-11-10.json +++ b/docs/renders/aqua-feel-native-diff-2026-11-10.json @@ -1,6 +1,8 @@ { "libfaustWasm": "2.90.19", "sampleRate": 48000, + "items": "core", + "smoother": "library", "rows": [ { "id": "thruster@rpm300", @@ -35,7 +37,8 @@ "lowMotion": false }, "flagsWasm": [], - "flagsNative": [] + "flagsNative": [], + "violations": [] }, { "id": "thruster@rpm600", @@ -70,7 +73,8 @@ "lowMotion": false }, "flagsWasm": [], - "flagsNative": [] + "flagsNative": [], + "violations": [] }, { "id": "thruster@rpm1200", @@ -105,7 +109,8 @@ "lowMotion": false }, "flagsWasm": [], - "flagsNative": [] + "flagsNative": [], + "violations": [] }, { "id": "thruster@rpm4800", @@ -140,7 +145,8 @@ "lowMotion": false }, "flagsWasm": [], - "flagsNative": [] + "flagsNative": [], + "violations": [] }, { "id": "sfxr-classic-classic-sfxr-abstract-golf", @@ -185,7 +191,8 @@ "waived lowMotion", "waived loudnessLufs", "warn truePeakDb" - ] + ], + "violations": [] }, { "id": "sfxr-explosion", @@ -224,7 +231,8 @@ ], "flagsNative": [ "waived lowMotion" - ] + ], + "violations": [] }, { "id": "tract/vowel-a", @@ -263,7 +271,8 @@ ], "flagsNative": [ "FAIL onsetMs" - ] + ], + "violations": [] } ] } \ No newline at end of file diff --git a/docs/renders/aqua-feel-native-diff-2026-11-10.md b/docs/renders/aqua-feel-native-diff-2026-11-10.md index 7e612c9..965519d 100644 --- a/docs/renders/aqua-feel-native-diff-2026-11-10.md +++ b/docs/renders/aqua-feel-native-diff-2026-11-10.md @@ -42,16 +42,77 @@ Evidence that the library text, not `-vec`, `-lv`, `-double` or contraction, dec The earlier version of this report said the cause was the float32 rounding of the smoother on a native path that "does not stall for an unisolated reason" and suggested `-vec`; that was wrong. The `wander` term is not the cause (removing it leaves rpm300 2.62e-2, rpm4800 2.89e-1). -The isolation probes are scratch scripts in `/var/lib/mind-aqua-self/soul-nd-1` (not committed); the numbers above are not in the `.json`, which holds only the seven main rows. +The isolation probes are scratch scripts in `/var/lib/mind-aqua-self/soul-nd-1` (not committed); the numbers above are not in the `.json`, which holds the seven main rows plus the tract. ### What this does and does not invalidate - Level, true-peak, centroid, crest and hiss claims for the thruster hold on both libraries within the numbers above. The rpm600 hiss cap (8 dB) has 1.95 dB of headroom with the native library, not the 2.96 dB the threshold note assumes; still passes. - Sample- or phase-level comparisons of the thruster (null tests, exact waveform diffs, sub-ms timing of the rotor tone) are specific to the library that rendered them. -- Which library a given daemon uses depends on its `share/faust`. A daemon shipping a release stdlib with the old `si.smooth` would stall like wasm; this sandbox's would not. The report cannot say what the production daemon does. -- Other sources that import `si.smooth` or `si.smoo` are expected to show the same skew and were **not tested**: 11 sources by count (7 patches, 3 Ding instruments, plus `advanced-human-larynx-graph`), which puts most of the 36 Ding items (27 or more) on the divergent library. -- **Not tested:** the other 75 of the 81 aqua-feel items, the sfxr-family patches beyond the two here, Ding mixes, block sizes other than 512, and any live (non-offline) host. +- Which library a given daemon uses depends on its `share/faust`. A daemon shipping a release stdlib with the old `si.smooth` would stall like wasm; this sandbox's would not. The only Windows Faust install found (Starfire, 2.85.5) has the old form; see the section on the production stdlib below. +- **Not tested:** the other aqua-feel items that do not call `si.smooth`/`si.smoo` (the 81 items are 45 patch renders and 36 Ding event mixes; this report covers 12 sources plus the tract), the Ding event mixes (only one note per instrument is rendered below), block sizes other than 512, and any live (non-offline) host. ## Toolchain dotnet 10.0.403 builds `FaustNativeRender` with `/var/lib/mind-aqua-self/env-review.sh`. Re-running the script reproduces the JSON byte for byte. + +## Which stdlib the production daemon compiles against (2026-11-10) + +The repo neither vendors nor pins faustlibraries (`.gitmodules` holds only zynaddsubfx and pink-trombone-mod). `NativeFaustRuntime.cs:530` passes `-I /share/faust`, and `AquaSynthFaustToolchain.Find` (`NativeFaustRuntime.cs:651`) takes `home` from `AQUASYNTH_FAUST_HOME`, then `AQUARIUM_FAUST_HOME`, then `Tools/Faust` beside the app, then `C:\Program Files\Faust` (Windows) or `/usr/local`, `/usr`. So the daemon compiles against whatever Faust install the host has. + +| host | Faust | `si.smooth` in its `signals.lib` | stalls (float32)? | +|---|---|---|---| +| Starfire `C:\Program Files\Faust` (read over ssh; faust.exe 2.85.5, `signals.lib` 2026-03-20, `smooth = smooth_imp(0)`, line 382) | 2.85.5 | `(1.0 - s) * x + s * y`, the same recursion as wasm's | yes, by the same mechanism | +| aqua-feel's wasm bundle (faustwasm 0.20.0, libfaust 2.90.19; embedded `signals.lib`) | 2.90.19 | `smooth(s) = *(1.0 - s) : + ~ *(s)` | yes: 4796.632 | +| this sandbox (`faustlibraries-20261031`, master 3d8c6ef, a one-commit shallow clone) | 2.90.20 | `smooth = imp(3)` with a comment on the stall | no | + +I read only that Starfire file's `smooth` definition; I did not render on Starfire. On the evidence, the Windows daemon uses the old form, so **aqua-feel's wasm numbers are the closer model of that host than this sandbox's native render is**; the sandbox's imp(3) library is not what any shipped host has. Whether the daemon runs on Starfire in production is not established here; it is the only Windows Faust install found. + +## The twelve sources that call `si.smooth` / `si.smoo` + +The earlier "eleven" was seven patches, three Ding instruments and the larynx graph; the catalog has seven patches besides the thruster **including** the larynx graph, and **four** Ding instruments (`muted-pluck` was missed). Rendered: the thruster at rpm 1200, those seven patches, and one note per instrument (261.6256 Hz, 1.5 s, not the mixed event). Three conditions, each the same Faust text on both sides: as shipped (each side's own stdlib), the old smoother pinned (`nd_smooth(s) = *(1.0 - s) : + ~ *(s)`), and imp(3) pinned. Pinning rewrites `si.smooth` and `si.smoo` in the source text; a smoother called inside another library function is not rewritten, which may account for the `dx7-algorithm-32` residual below. The tract voice rides along as a thirteenth row. Columns are native minus wasm. Raw rows: `aqua-feel-native-diff-smoother-{library,old,imp3}-2026-11-10.json`. + +| source | stdlib as shipped (wasm bundle vs sandbox): max err / rel RMS dB | old smoother pinned | imp3 pinned | ΔLUFS lib / old / imp3 | Δ true peak dB lib / old / imp3 | Δ centroid % lib / old / imp3 | +|---|---|---|---|---|---|---| +| thruster@rpm1200 | 9.0e-2 / -16.6 | 4.5e-2 / -21.2 | 7.1e-6 / -99.9 | 0.000 / 0.001 / -0.000 | -0.081 / -0.152 / 0.000 | 0.07 / -0.08 / 0.00 | | ; | ; | +| advanced-alien-multivalve-graph | 2.9e-5 / -68.4 | 1.0e-5 / -72.5 | 7.9e-7 / -97.9 | -0.000 / -0.000 / -0.000 | -0.001 / -0.000 / -0.000 | -0.00 / 0.00 / 0.00 | | ; | ; | +| advanced-bird-syrinx | 2.8e-3 / -27.3 | 1.6e-3 / -31.7 | 3.2e-6 / -88.3 | -0.001 / -0.001 / -0.000 | -0.000 / -0.000 / -0.000 | -0.00 / -0.00 / 0.00 | | ; | ; | +| advanced-bird-syrinx-graph | 9.4e-4 / -38.4 | 2.7e-4 / -47.1 | 1.5e-5 / -75.4 | 0.000 / -0.000 / 0.000 | 0.000 / 0.000 / -0.000 | -0.01 / 0.00 / -0.00 | | ; | ; | +| advanced-syrinx-voice | 1.3e-3 / -23.9 | 3.6e-4 / -34.6 | 2.3e-6 / -86.3 | 0.001 / 0.000 / 0.000 | 0.003 / 0.003 / -0.000 | -0.00 / -0.00 / 0.00 | | ; | ; | +| advanced-human-larynx-graph | 9.6e-5 / -38.1 | 3.3e-5 / -46.6 | 7.6e-7 / -94.2 | 0.002 / 0.001 / -0.000 | 0.002 / 0.001 / -0.000 | 0.05 / 0.05 / 0.04 | | ; | ; | +| dx7-algorithm-32-additive-organ | 1.7e-4 / -79.3 | 1.6e-4 / -79.5 | 1.6e-4 / -79.5 | 0.000 / -0.000 / 0.000 | -0.000 / -0.000 / -0.000 | -0.00 / -0.00 / -0.00 | | ; | ; | +| dx7-algorithm-8-bright-pair | 4.1e-6 / -94.5 | 4.2e-6 / -94.2 | 3.9e-6 / -94.7 | 0.000 / 0.000 / 0.000 | -0.000 / -0.000 / -0.000 | 0.00 / 0.00 / -0.00 | | ; | ; | +| ding-instrument/glass-chime | 3.8e-2 / -18.4 | 2.5e-2 / -21.9 | 3.3e-7 / -130.0 | 0.000 / 0.000 / 0.000 | -0.000 / 0.000 / -0.000 | 0.02 / 0.01 / -0.00 | | ; | ; | +| ding-instrument/muted-pluck | 6.6e-4 / -49.8 | 6.1e-4 / -50.2 | 3.0e-8 / -141.7 | -0.000 / -0.000 / -0.000 | -0.000 / -0.000 / -0.000 | 0.00 / -0.01 / -0.00 | | ; | ; | +| ding-instrument/soft-triangle | 4.3e-3 / -30.3 | 2.4e-3 / -35.2 | 4.5e-8 / -142.0 | 0.000 / -0.000 / -0.000 | -0.000 / -0.000 / 0.000 | 0.16 / 0.12 / -0.00 | | ; | ; | +| ding-instrument/warm-bell | 5.4e-2 / -18.3 | 3.2e-2 / -22.9 | 2.3e-7 / -134.2 | 0.000 / 0.000 / -0.000 | -0.000 / 0.000 / -0.000 | 0.01 / 0.00 / -0.00 | | ; | ; | +| tract/vowel-a | 1.9e-4 / -52.6 | 6.7e-5 / -61.2 | 3.1e-7 / -109.3 | 0.017 / 0.006 / -0.000 | 0.013 / 0.004 / -0.000 | 0.01 / 0.01 / -0.00 | FAIL onsetMs|FAIL onsetMs ; FAIL onsetMs|FAIL onsetMs ; FAIL onsetMs| + +Largest metric deltas over all sources and the three conditions: |LUFS| 0.017 dB (tract), |true peak| 0.152 dB (thruster, old pinned), |centroid| 0.16 % (soft-triangle; the earlier core run has 0.3 % at thruster rpm600). **No gate flag differs on any source in any condition**; the tract's patch-group `FAIL onsetMs` is on both sides (see above). + +What the data says: + +- With imp(3) pinned on both sides all 13 rows agree to -75 dB or better (Ding instruments -130 to -142 dB, thruster -99.9 dB, tract -109 dB; worst: `advanced-bird-syrinx-graph` -75.4 dB). `dx7-algorithm-32-additive-organ` sits at -79.5 dB under all three conditions, so its residual is not the pinned smoother (a smoother inside a library function, or float rounding; not isolated). +- With the old smoother pinned the thruster still differs (-21.2 dB): native's LLVM contracts the recursion to FMA and stalls at 4797.657 instead of wasm's 4796.632. The old form's stall point depends on the arithmetic; the imp(3) form does not. +- As shipped, nine of the 13 rows differ at waveform level by -16 to -50 dB (thruster, the three syrinx patches, the larynx graph, all four Ding instruments); `advanced-alien-multivalve-graph` (-68 dB), the tract (-52.6 dB) and the two dx7 patches are closer. + +## Tolerance in `native-diff.mjs` + +Added, because the data shows a gap. Across all 13 sources and the 3 conditions the metric deltas stay under |LUFS| 0.017 dB, |true peak| 0.152 dB, centroid 0.3 %. A deliberate +1 dB gain on the native buffer moves LUFS and true peak by 1.0 dB. The limits sit between: 0.1 dB, 0.5 dB, 1 %. Sample-level error has no limit with the library smoother (the waveforms legitimately diverge), but with `--smoother imp3` it has one at -60 dB (the worst row is -75.4 dB; a one-sample shift reads about -4 dB). + +Mutation check on the tool, `--items smoother --smoother imp3`: unmutated, 12 of 12 clean, exit 0; +1 dB native gain, 12 of 12 flagged (`loudnessLufs`, `truePeakDb`, `relRmsErrDb`), exit 1; one-sample native shift, 12 of 12 flagged (`relRmsErrDb`), exit 1. A one-sample shift is not detectable with the library smoother; `loudnessLufs`, `truePeakDb` and centroid also cannot see it. The limits are tripwires fit to these 13 sources, not taste. + +## What changes in aqua-feel's conclusions + +- Nothing in the gate changes: no flag differs on any tested source, and every metric delta is far below any threshold in `thresholds.json` that I looked at except the rpm600 thruster hiss cap noted above (1.0 dB, still under the cap). +- aqua-feel's WAVs are a fair rendering of what a host with the old `si.smooth` produces (the only Windows install found). They are **not** sample-identical to this sandbox's native render, and no sample-level claim about the thruster, the syrinx patches or the Ding instruments should be carried across them. +- The listening trial heard wasm renders; for a Windows daemon that is the closer path. For a daemon built on the sandbox's library, the same sources have the same level and spectrum and a different phase trajectory. +- Nothing was heard. + +## Proposed faustlibraries pin (not made) + +The cause is that `si.smooth` is whatever the host's `share/faust` says. Options, in order of my preference (confidence moderate; none of this is Soul-checked): +1. Vendor a pinned faustlibraries tree under the daemon's `Tools/Faust/share/faust` at build, record its commit in the compile manifest next to `faustLibrary`, and have `AquaSynthNativeOptions` prefer it over the system install. This makes the wasm bundle, Starfire and the sandbox agree only if aqua-feel's wasm path pins the same text too (faustwasm takes a library via its virtual FS), so pin both. +2. Decide the smoother once, in AquaSynth's emitted source: lowering emits a local smoother definition instead of calling `si.smooth`, so no host's stdlib decides it. Smaller, but changes every lowered patch and does not cover library functions that smooth internally. +3. Leave unpinned and record the host library identity in each compile manifest, accepting skew. + +Which smoother is the right one is a sound decision, not a measurement: imp(3) cannot stall (the library's own comment), the old form lands 0.07 % short of an rpm target at 4800. That is the owner's call. diff --git a/docs/renders/aqua-feel-native-diff-smoother-imp3-2026-11-10.json b/docs/renders/aqua-feel-native-diff-smoother-imp3-2026-11-10.json new file mode 100644 index 0000000..70ade89 --- /dev/null +++ b/docs/renders/aqua-feel-native-diff-smoother-imp3-2026-11-10.json @@ -0,0 +1,480 @@ +{ + "libfaustWasm": "2.90.19", + "sampleRate": 48000, + "items": "smoother", + "smoother": "imp3", + "rows": [ + { + "id": "thruster@rpm1200", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000007145106792449951, + "maxErrAt": 129748, + "rmsErr": 8.55413991926508e-7, + "relRmsErrDb": -99.87174472945775, + "wasm": { + "samplePeak": 0.18935100734233856, + "truePeakDb": -14.4241663695795, + "loudnessLufs": -26.05070420011194, + "centroidMedianHz": 2115.6325442720204, + "overSamples": 0, + "rmsDb": -21.48445687766135, + "crestDb": 7.029809275623743, + "noiseToToneDb": -4.965082233659885, + "noiseToToneADb": 12.71614612278843, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1893547624349594, + "truePeakDb": -14.423961460001248, + "loudnessLufs": -26.05070520432609, + "centroidMedianHz": 2115.6363534197994, + "overSamples": 0, + "rmsDb": -21.484457233344667, + "crestDb": 7.029981882815694, + "noiseToToneDb": -4.965083035181173, + "noiseToToneADb": 12.716143920958068, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-alien-multivalve-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 7.934868335723877e-7, + "maxErrAt": 35537, + "rmsErr": 8.335841454269617e-8, + "relRmsErrDb": -97.93819913427402, + "wasm": { + "samplePeak": 0.03596395254135132, + "truePeakDb": -28.873901668042834, + "loudnessLufs": -44.01583944248863, + "centroidMedianHz": 1719.746341068706, + "overSamples": 0, + "rmsDb": -38.86915824557575, + "crestDb": 9.986506563399741, + "noiseToToneDb": 2.2949371593837427, + "noiseToToneADb": -35.26674665881937, + "lowMotion": false + }, + "native": { + "samplePeak": 0.03596379607915878, + "truePeakDb": -28.87393371231402, + "loudnessLufs": -44.01584006251751, + "centroidMedianHz": 1719.7470242831325, + "overSamples": 0, + "rmsDb": -38.86915865101764, + "crestDb": 9.986469180550948, + "noiseToToneDb": 2.2949373787323424, + "noiseToToneADb": -35.266660047022036, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-bird-syrinx", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.0000031944364309310913, + "maxErrAt": 45510, + "rmsErr": 3.8508989002632593e-7, + "relRmsErrDb": -88.25157230135844, + "wasm": { + "samplePeak": 0.07248446345329285, + "truePeakDb": -22.789810284125757, + "loudnessLufs": -39.38587031070514, + "centroidMedianHz": 3246.9416863147353, + "overSamples": 0, + "rmsDb": -35.86910042371045, + "crestDb": 13.073998993865658, + "noiseToToneDb": 3.398154713653433, + "noiseToToneADb": -0.2652457381911617, + "lowMotion": false + }, + "native": { + "samplePeak": 0.07248446345329285, + "truePeakDb": -22.789811642249525, + "loudnessLufs": -39.38587068917686, + "centroidMedianHz": 3246.965668802187, + "overSamples": 0, + "rmsDb": -35.86909904000429, + "crestDb": 13.0739976101595, + "noiseToToneDb": 3.3981546091622192, + "noiseToToneADb": -0.2652490593489034, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-bird-syrinx-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000015079975128173828, + "maxErrAt": 32899, + "rmsErr": 0.000001467630377556357, + "relRmsErrDb": -75.44121356490483, + "wasm": { + "samplePeak": 0.06643311679363251, + "truePeakDb": -23.549215787508704, + "loudnessLufs": -41.27251453792896, + "centroidMedianHz": 3608.1313492575864, + "overSamples": 0, + "rmsDb": -35.83554258887882, + "crestDb": 12.283235156759115, + "noiseToToneDb": 3.2057938097979033, + "noiseToToneADb": -14.047787523198789, + "lowMotion": false + }, + "native": { + "samplePeak": 0.06643310934305191, + "truePeakDb": -23.549216695315987, + "loudnessLufs": -41.27251147778152, + "centroidMedianHz": 3608.0824292245743, + "overSamples": 0, + "rmsDb": -35.83554221039036, + "crestDb": 12.28323380413414, + "noiseToToneDb": 3.205791878004028, + "noiseToToneADb": -14.047777587591895, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-syrinx-voice", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000002266373485326767, + "maxErrAt": 34649, + "rmsErr": 1.2956788367741612e-7, + "relRmsErrDb": -86.30175634080427, + "wasm": { + "samplePeak": 0.01358692068606615, + "truePeakDb": -37.32686366831106, + "loudnessLufs": -43.745499382417364, + "centroidMedianHz": 3723.182853016622, + "overSamples": 0, + "rmsDb": -46.7956174011546, + "crestDb": 9.458038205292446, + "noiseToToneDb": 1.791072654520292, + "noiseToToneADb": 1.7998750278369968, + "lowMotion": false + }, + "native": { + "samplePeak": 0.013587156310677528, + "truePeakDb": -37.32703848354456, + "loudnessLufs": -43.74549926644062, + "centroidMedianHz": 3723.2127010397694, + "overSamples": 0, + "rmsDb": -46.7956172502661, + "crestDb": 9.45818868394479, + "noiseToToneDb": 1.7910715795642531, + "noiseToToneADb": 1.7998739270327846, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-human-larynx-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 7.587950676679611e-7, + "maxErrAt": 23972, + "rmsErr": 3.7846162388541006e-8, + "relRmsErrDb": -94.24621859478574, + "wasm": { + "samplePeak": 0.007788178510963917, + "truePeakDb": -42.17101862717542, + "loudnessLufs": -50.40721974974582, + "centroidMedianHz": 310.7891589769418, + "overSamples": 0, + "rmsDb": -50.08572494872536, + "crestDb": 7.914442895151346, + "noiseToToneDb": -30.816131815537496, + "noiseToToneADb": -27.766650651768487, + "lowMotion": false + }, + "native": { + "samplePeak": 0.0077881766483187675, + "truePeakDb": -42.17102078454992, + "loudnessLufs": -50.40722107125285, + "centroidMedianHz": 310.8999754545593, + "overSamples": 0, + "rmsDb": -50.08572618353941, + "crestDb": 7.914442052620592, + "noiseToToneDb": -30.816127938801678, + "noiseToToneADb": -27.766649805181505, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "dx7-algorithm-32-additive-organ", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000164765864610672, + "maxErrAt": 23021, + "rmsErr": 0.000020540035138682098, + "relRmsErrDb": -79.45828076486667, + "wasm": { + "samplePeak": 0.6862082481384277, + "truePeakDb": -3.2699164206425015, + "loudnessLufs": -9.759124233116152, + "centroidMedianHz": 656.4226191119797, + "overSamples": 0, + "rmsDb": -8.539759068566523, + "crestDb": 5.268877746918222, + "noiseToToneDb": -37.01983113652173, + "noiseToToneADb": -37.788337502316935, + "lowMotion": false + }, + "native": { + "samplePeak": 0.6861962676048279, + "truePeakDb": -3.2700776073676936, + "loudnessLufs": -9.75912403739766, + "centroidMedianHz": 656.4212037746422, + "overSamples": 0, + "rmsDb": -8.53975888452333, + "crestDb": 5.268725914302816, + "noiseToToneDb": -37.0197944928564, + "noiseToToneADb": -37.78830410408929, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "dx7-algorithm-8-bright-pair", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000003874767571687698, + "maxErrAt": 4727, + "rmsErr": 3.855039254064681e-7, + "relRmsErrDb": -94.6889441823849, + "wasm": { + "samplePeak": 0.150354266166687, + "truePeakDb": -16.445140264060527, + "loudnessLufs": -27.707281021900723, + "centroidMedianHz": 570.7301006928219, + "overSamples": 0, + "rmsDb": -25.228220791943087, + "crestDb": 8.770535898454384, + "noiseToToneDb": -33.00376708572004, + "noiseToToneADb": -31.168271339188635, + "lowMotion": false + }, + "native": { + "samplePeak": 0.15035313367843628, + "truePeakDb": -16.44521182640461, + "loudnessLufs": -27.707279746651498, + "centroidMedianHz": 570.7300173443439, + "overSamples": 0, + "rmsDb": -25.228219801277547, + "crestDb": 8.770469484271047, + "noiseToToneDb": -33.00374306131664, + "noiseToToneADb": -31.168260425434468, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/glass-chime", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 3.3155083656311035e-7, + "maxErrAt": 1305, + "rmsErr": 1.7352308119215504e-8, + "relRmsErrDb": -130.0126824087709, + "wasm": { + "samplePeak": 0.17621399462223053, + "truePeakDb": -15.062043767848326, + "loudnessLufs": -21.567272532108692, + "centroidMedianHz": 1602.876747515682, + "overSamples": 0, + "rmsDb": -22.84411377344017, + "crestDb": 7.764921701255961, + "noiseToToneDb": -3.1568108379227766, + "noiseToToneADb": -2.864104015545095, + "lowMotion": false + }, + "native": { + "samplePeak": 0.17621399462223053, + "truePeakDb": -15.062043917300066, + "loudnessLufs": -21.567272049422396, + "centroidMedianHz": 1602.87659074774, + "overSamples": 0, + "rmsDb": -22.844113441684826, + "crestDb": 7.764921369500614, + "noiseToToneDb": -3.156811070401325, + "noiseToToneADb": -2.8641043762574725, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/muted-pluck", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 2.9802322387695312e-8, + "maxErrAt": 39, + "rmsErr": 2.9894375811414372e-9, + "relRmsErrDb": -141.73373986178612, + "wasm": { + "samplePeak": 0.184849813580513, + "truePeakDb": -14.6610699229291, + "loudnessLufs": -27.093755336399102, + "centroidMedianHz": 337.57025931776576, + "overSamples": 0, + "rmsDb": -22.505770402082735, + "crestDb": 7.842150740622795, + "noiseToToneDb": -1.8131372817002074, + "noiseToToneADb": -3.773645767164254, + "lowMotion": false + }, + "native": { + "samplePeak": 0.184849813580513, + "truePeakDb": -14.66107041311351, + "loudnessLufs": -27.093755376725216, + "centroidMedianHz": 337.5700092248225, + "overSamples": 0, + "rmsDb": -22.505770501962672, + "crestDb": 7.842150840502738, + "noiseToToneDb": -1.813137412998607, + "noiseToToneADb": -3.773645881399787, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/soft-triangle", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 4.470348358154297e-8, + "maxErrAt": 8043, + "rmsErr": 4.533743604935639e-9, + "relRmsErrDb": -141.9875633660136, + "wasm": { + "samplePeak": 0.19771063327789307, + "truePeakDb": -14.073779284051348, + "loudnessLufs": -24.40759733605614, + "centroidMedianHz": 432.727373830872, + "overSamples": 0, + "rmsDb": -22.83707655432693, + "crestDb": 8.757677097882045, + "noiseToToneDb": -7.168489439744874, + "noiseToToneADb": -9.027978911866263, + "lowMotion": false + }, + "native": { + "samplePeak": 0.19771063327789307, + "truePeakDb": -14.073779075736933, + "loudnessLufs": -24.407597412236093, + "centroidMedianHz": 432.72721346965216, + "overSamples": 0, + "rmsDb": -22.83707661841455, + "crestDb": 8.757677161969662, + "noiseToToneDb": -7.168489371555516, + "noiseToToneADb": -9.027978838572636, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/warm-bell", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 2.3096799850463867e-7, + "maxErrAt": 4434, + "rmsErr": 1.76394996238211e-8, + "relRmsErrDb": -134.18055622815194, + "wasm": { + "samplePeak": 0.2345581352710724, + "truePeakDb": -12.586384128665326, + "loudnessLufs": -19.507703963974553, + "centroidMedianHz": 930.5955363634018, + "overSamples": 0, + "rmsDb": -19.84892961990138, + "crestDb": 7.25393962687934, + "noiseToToneDb": -4.493021207525722, + "noiseToToneADb": -3.568407049551638, + "lowMotion": false + }, + "native": { + "samplePeak": 0.2345581352710724, + "truePeakDb": -12.586384726471685, + "loudnessLufs": -19.50770471111663, + "centroidMedianHz": 930.5954188145085, + "overSamples": 0, + "rmsDb": -19.84893033283087, + "crestDb": 7.253940339808832, + "noiseToToneDb": -4.49302081177872, + "noiseToToneADb": -3.568406831675894, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "tract/vowel-a", + "lenWasm": 34560, + "lenNative": 34560, + "maxErr": 3.129243850708008e-7, + "maxErrAt": 32270, + "rmsErr": 6.777997120190261e-8, + "relRmsErrDb": -109.27441638992673, + "wasm": { + "samplePeak": 0.08390945941209793, + "truePeakDb": -21.50535980428806, + "loudnessLufs": -34.23468728787301, + "centroidMedianHz": 1937.2312975664222, + "overSamples": 0, + "rmsDb": -33.786820379265095, + "crestDb": 12.26303884229992, + "noiseToToneDb": -10.447396268131833, + "noiseToToneADb": -12.898741047906523, + "lowMotion": false + }, + "native": { + "samplePeak": 0.08390921354293823, + "truePeakDb": -21.50539262673788, + "loudnessLufs": -34.23471535099468, + "centroidMedianHz": 1937.2296872141594, + "overSamples": 0, + "rmsDb": -33.78684837329708, + "crestDb": 12.263041385142849, + "noiseToToneDb": -10.447393994101596, + "noiseToToneADb": -12.89873810612136, + "lowMotion": false + }, + "flagsWasm": [ + "FAIL onsetMs" + ], + "flagsNative": [ + "FAIL onsetMs" + ], + "violations": [] + } + ] +} \ No newline at end of file diff --git a/docs/renders/aqua-feel-native-diff-smoother-library-2026-11-10.json b/docs/renders/aqua-feel-native-diff-smoother-library-2026-11-10.json new file mode 100644 index 0000000..4ec5042 --- /dev/null +++ b/docs/renders/aqua-feel-native-diff-smoother-library-2026-11-10.json @@ -0,0 +1,480 @@ +{ + "libfaustWasm": "2.90.19", + "sampleRate": 48000, + "items": "smoother", + "smoother": "library", + "rows": [ + { + "id": "thruster@rpm1200", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.08972565084695816, + "maxErrAt": 143228, + "rmsErr": 0.012448641233006897, + "relRmsErrDb": -16.610480431926955, + "wasm": { + "samplePeak": 0.19161102175712585, + "truePeakDb": -14.343339234769212, + "loudnessLufs": -26.051033400616078, + "centroidMedianHz": 2114.222071076734, + "overSamples": 0, + "rmsDb": -21.48680915202533, + "crestDb": 7.1352188868162605, + "noiseToToneDb": -4.949059402813134, + "noiseToToneADb": 12.652391835948997, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1893547624349594, + "truePeakDb": -14.423961460001248, + "loudnessLufs": -26.05070520432609, + "centroidMedianHz": 2115.6363534197994, + "overSamples": 0, + "rmsDb": -21.484457233344667, + "crestDb": 7.029981882815694, + "noiseToToneDb": -4.965083035181173, + "noiseToToneADb": 12.716143920958068, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-alien-multivalve-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000028988346457481384, + "maxErrAt": 35885, + "rmsErr": 0.0000024852740234353792, + "relRmsErrDb": -68.44968710695318, + "wasm": { + "samplePeak": 0.03596847131848335, + "truePeakDb": -28.872530673301476, + "loudnessLufs": -44.01558191383624, + "centroidMedianHz": 1719.821403397199, + "overSamples": 0, + "rmsDb": -38.86917357972277, + "crestDb": 9.98761318843934, + "noiseToToneDb": 2.2946722668875523, + "noiseToToneADb": -35.26574829949449, + "lowMotion": false + }, + "native": { + "samplePeak": 0.03596379607915878, + "truePeakDb": -28.87393371231402, + "loudnessLufs": -44.01584006251751, + "centroidMedianHz": 1719.7470242831325, + "overSamples": 0, + "rmsDb": -38.86915865101764, + "crestDb": 9.986469180550948, + "noiseToToneDb": 2.2949373787323424, + "noiseToToneADb": -35.266660047022036, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-bird-syrinx", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.0027769599109888077, + "maxErrAt": 45282, + "rmsErr": 0.0004283705044505341, + "relRmsErrDb": -27.32626534167103, + "wasm": { + "samplePeak": 0.0724848210811615, + "truePeakDb": -22.789532845508607, + "loudnessLufs": -39.384826220237244, + "centroidMedianHz": 3246.9736644772215, + "overSamples": 0, + "rmsDb": -35.86925853361916, + "crestDb": 13.074199958593006, + "noiseToToneDb": 3.395163753535492, + "noiseToToneADb": -0.26769809662280863, + "lowMotion": false + }, + "native": { + "samplePeak": 0.07248446345329285, + "truePeakDb": -22.789811642249525, + "loudnessLufs": -39.38587068917686, + "centroidMedianHz": 3246.965668802187, + "overSamples": 0, + "rmsDb": -35.86909904000429, + "crestDb": 13.0739976101595, + "noiseToToneDb": 3.3981546091622192, + "noiseToToneADb": -0.2652490593489034, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-bird-syrinx-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.0009388141334056854, + "maxErrAt": 33098, + "rmsErr": 0.00010481991412365385, + "relRmsErrDb": -38.36457659477791, + "wasm": { + "samplePeak": 0.06643299758434296, + "truePeakDb": -23.549220126103343, + "loudnessLufs": -41.272964930830874, + "centroidMedianHz": 3608.3773722521782, + "overSamples": 0, + "rmsDb": -35.835637421519465, + "crestDb": 12.28331440320251, + "noiseToToneDb": 3.205981107524842, + "noiseToToneADb": -14.050756471755903, + "lowMotion": false + }, + "native": { + "samplePeak": 0.06643310934305191, + "truePeakDb": -23.549216695315987, + "loudnessLufs": -41.27251147778152, + "centroidMedianHz": 3608.0824292245743, + "overSamples": 0, + "rmsDb": -35.83554221039036, + "crestDb": 12.28323380413414, + "noiseToToneDb": 3.205791878004028, + "noiseToToneADb": -14.047777587591895, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-syrinx-voice", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.0012954180128872395, + "maxErrAt": 33655, + "rmsErr": 0.00017071330356165833, + "relRmsErrDb": -23.90529822473789, + "wasm": { + "samplePeak": 0.013509727083146572, + "truePeakDb": -37.32985445982076, + "loudnessLufs": -43.74666608622773, + "centroidMedianHz": 3723.272760511277, + "overSamples": 0, + "rmsDb": -46.796675488586175, + "crestDb": 9.409607002734177, + "noiseToToneDb": 1.790967089244596, + "noiseToToneADb": 1.7997448212983471, + "lowMotion": false + }, + "native": { + "samplePeak": 0.013587156310677528, + "truePeakDb": -37.32703848354456, + "loudnessLufs": -43.74549926644062, + "centroidMedianHz": 3723.2127010397694, + "overSamples": 0, + "rmsDb": -46.7956172502661, + "crestDb": 9.45818868394479, + "noiseToToneDb": 1.7910715795642531, + "noiseToToneADb": 1.7998739270327846, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-human-larynx-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.00009577462333254516, + "maxErrAt": 34584, + "rmsErr": 0.00002426493085728514, + "relRmsErrDb": -38.10546679970197, + "wasm": { + "samplePeak": 0.007785667199641466, + "truePeakDb": -42.173192592326515, + "loudnessLufs": -50.40895750738575, + "centroidMedianHz": 310.7359227777714, + "overSamples": 0, + "rmsDb": -50.08733256154268, + "crestDb": 7.913249276521016, + "noiseToToneDb": -30.80971405508631, + "noiseToToneADb": -27.762876726971946, + "lowMotion": false + }, + "native": { + "samplePeak": 0.0077881766483187675, + "truePeakDb": -42.17102078454992, + "loudnessLufs": -50.40722107125285, + "centroidMedianHz": 310.8999754545593, + "overSamples": 0, + "rmsDb": -50.08572618353941, + "crestDb": 7.914442052620592, + "noiseToToneDb": -30.816127938801678, + "noiseToToneADb": -27.766649805181505, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "dx7-algorithm-32-additive-organ", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.00016526132822036743, + "maxErrAt": 23020, + "rmsErr": 0.000020994103574012447, + "relRmsErrDb": -79.26822857998425, + "wasm": { + "samplePeak": 0.6861975789070129, + "truePeakDb": -3.270050577733717, + "loudnessLufs": -9.759294906714691, + "centroidMedianHz": 656.4219631461098, + "overSamples": 0, + "rmsDb": -8.539888188883904, + "crestDb": 5.268871817143614, + "noiseToToneDb": -37.019826930432856, + "noiseToToneADb": -37.7883341551492, + "lowMotion": false + }, + "native": { + "samplePeak": 0.6861962676048279, + "truePeakDb": -3.2700776073676936, + "loudnessLufs": -9.75912403739766, + "centroidMedianHz": 656.4212037746422, + "overSamples": 0, + "rmsDb": -8.53975888452333, + "crestDb": 5.268725914302816, + "noiseToToneDb": -37.0197944928564, + "noiseToToneADb": -37.78830410408929, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "dx7-algorithm-8-bright-pair", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000004090368747711182, + "maxErrAt": 4395, + "rmsErr": 3.9566542791342453e-7, + "relRmsErrDb": -94.46295503093202, + "wasm": { + "samplePeak": 0.1503547728061676, + "truePeakDb": -16.445110330444138, + "loudnessLufs": -27.707288476356375, + "centroidMedianHz": 570.7272310041899, + "overSamples": 0, + "rmsDb": -25.22822394507269, + "crestDb": 8.7705683198403, + "noiseToToneDb": -33.00382123099267, + "noiseToToneADb": -31.168321209089953, + "lowMotion": false + }, + "native": { + "samplePeak": 0.15035313367843628, + "truePeakDb": -16.44521182640461, + "loudnessLufs": -27.707279746651498, + "centroidMedianHz": 570.7300173443439, + "overSamples": 0, + "rmsDb": -25.228219801277547, + "crestDb": 8.770469484271047, + "noiseToToneDb": -33.00374306131664, + "noiseToToneADb": -31.168260425434468, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/glass-chime", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.03821572195738554, + "maxErrAt": 19431, + "rmsErr": 0.006593494603898945, + "relRmsErrDb": -18.417512948342896, + "wasm": { + "samplePeak": 0.17621681094169617, + "truePeakDb": -15.062032445423984, + "loudnessLufs": -21.56737037885148, + "centroidMedianHz": 1602.6138671100102, + "overSamples": 0, + "rmsDb": -22.844945161193554, + "crestDb": 7.765891909110638, + "noiseToToneDb": -3.1846833757850774, + "noiseToToneADb": -2.8894771746074213, + "lowMotion": false + }, + "native": { + "samplePeak": 0.17621399462223053, + "truePeakDb": -15.062043917300066, + "loudnessLufs": -21.567272049422396, + "centroidMedianHz": 1602.87659074774, + "overSamples": 0, + "rmsDb": -22.844113441684826, + "crestDb": 7.764921369500614, + "noiseToToneDb": -3.156811070401325, + "noiseToToneADb": -2.8641043762574725, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/muted-pluck", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.000662742939312011, + "maxErrAt": 13239, + "rmsErr": 0.00011858435565109058, + "relRmsErrDb": -49.764983408100534, + "wasm": { + "samplePeak": 0.18485014140605927, + "truePeakDb": -14.661061484678669, + "loudnessLufs": -27.09375077529527, + "centroidMedianHz": 337.55371147245654, + "overSamples": 0, + "rmsDb": -22.505768680170274, + "crestDb": 7.8421644228588985, + "noiseToToneDb": -1.8131427242661857, + "noiseToToneADb": -3.7732237451762884, + "lowMotion": false + }, + "native": { + "samplePeak": 0.184849813580513, + "truePeakDb": -14.66107041311351, + "loudnessLufs": -27.093755376725216, + "centroidMedianHz": 337.5700092248225, + "overSamples": 0, + "rmsDb": -22.505770501962672, + "crestDb": 7.842150840502738, + "noiseToToneDb": -1.813137412998607, + "noiseToToneADb": -3.773645881399787, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/soft-triangle", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.004339124541729689, + "maxErrAt": 27366, + "rmsErr": 0.0017425528838580662, + "relRmsErrDb": -30.292986249468473, + "wasm": { + "samplePeak": 0.19771148264408112, + "truePeakDb": -14.073758822230804, + "loudnessLufs": -24.407607566689737, + "centroidMedianHz": 432.02737524513924, + "overSamples": 0, + "rmsDb": -22.837073455193796, + "crestDb": 8.757711313308086, + "noiseToToneDb": -7.16850817229934, + "noiseToToneADb": -9.027202849078392, + "lowMotion": false + }, + "native": { + "samplePeak": 0.19771063327789307, + "truePeakDb": -14.073779075736933, + "loudnessLufs": -24.407597412236093, + "centroidMedianHz": 432.72721346965216, + "overSamples": 0, + "rmsDb": -22.83707661841455, + "crestDb": 8.757677161969662, + "noiseToToneDb": -7.168489371555516, + "noiseToToneADb": -9.027978838572636, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/warm-bell", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.05384007841348648, + "maxErrAt": 22907, + "rmsErr": 0.010932589918993823, + "relRmsErrDb": -18.335820982841014, + "wasm": { + "samplePeak": 0.23455719649791718, + "truePeakDb": -12.586378660039248, + "loudnessLufs": -19.50785299525781, + "centroidMedianHz": 930.5251827853554, + "overSamples": 0, + "rmsDb": -19.848928933876216, + "crestDb": 7.2539041772066515, + "noiseToToneDb": -4.487748875412059, + "noiseToToneADb": -3.561906168663185, + "lowMotion": false + }, + "native": { + "samplePeak": 0.2345581352710724, + "truePeakDb": -12.586384726471685, + "loudnessLufs": -19.50770471111663, + "centroidMedianHz": 930.5954188145085, + "overSamples": 0, + "rmsDb": -19.84893033283087, + "crestDb": 7.253940339808832, + "noiseToToneDb": -4.49302081177872, + "noiseToToneADb": -3.568406831675894, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "tract/vowel-a", + "lenWasm": 34560, + "lenNative": 34560, + "maxErr": 0.0001921914517879486, + "maxErrAt": 28187, + "rmsErr": 0.000046111049670587934, + "relRmsErrDb": -52.60441410619507, + "wasm": { + "samplePeak": 0.08378415554761887, + "truePeakDb": -21.518327105526673, + "loudnessLufs": -34.251568232392565, + "centroidMedianHz": 1937.1220888731689, + "overSamples": 0, + "rmsDb": -33.80275018649363, + "crestDb": 12.265988122620389, + "noiseToToneDb": -10.436050344670264, + "noiseToToneADb": -12.885220061403881, + "lowMotion": false + }, + "native": { + "samplePeak": 0.08390921354293823, + "truePeakDb": -21.50539262673788, + "loudnessLufs": -34.23471535099468, + "centroidMedianHz": 1937.2296872141594, + "overSamples": 0, + "rmsDb": -33.78684837329708, + "crestDb": 12.263041385142849, + "noiseToToneDb": -10.447393994101596, + "noiseToToneADb": -12.89873810612136, + "lowMotion": false + }, + "flagsWasm": [ + "FAIL onsetMs" + ], + "flagsNative": [ + "FAIL onsetMs" + ], + "violations": [] + } + ] +} \ No newline at end of file diff --git a/docs/renders/aqua-feel-native-diff-smoother-old-2026-11-10.json b/docs/renders/aqua-feel-native-diff-smoother-old-2026-11-10.json new file mode 100644 index 0000000..486b785 --- /dev/null +++ b/docs/renders/aqua-feel-native-diff-smoother-old-2026-11-10.json @@ -0,0 +1,480 @@ +{ + "libfaustWasm": "2.90.19", + "sampleRate": 48000, + "items": "smoother", + "smoother": "old", + "rows": [ + { + "id": "thruster@rpm1200", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.04497168958187103, + "maxErrAt": 141577, + "rmsErr": 0.007318071121139892, + "relRmsErrDb": -21.224986931184873, + "wasm": { + "samplePeak": 0.19161102175712585, + "truePeakDb": -14.343339234769212, + "loudnessLufs": -26.051033400616078, + "centroidMedianHz": 2114.222071076734, + "overSamples": 0, + "rmsDb": -21.48680915202533, + "crestDb": 7.1352188868162605, + "noiseToToneDb": -4.949059402813134, + "noiseToToneADb": 12.652391835948997, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1881352812051773, + "truePeakDb": -14.4953016770333, + "loudnessLufs": -26.05000842555788, + "centroidMedianHz": 2112.509030190081, + "overSamples": 0, + "rmsDb": -21.48554731845163, + "crestDb": 6.974952256138389, + "noiseToToneDb": -4.986880144493831, + "noiseToToneADb": 12.399428225827492, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-alien-multivalve-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000010244548320770264, + "maxErrAt": 35688, + "rmsErr": 0.0000015676294788398326, + "relRmsErrDb": -72.45230428385759, + "wasm": { + "samplePeak": 0.03596847131848335, + "truePeakDb": -28.872530673301476, + "loudnessLufs": -44.01558191383624, + "centroidMedianHz": 1719.821403397199, + "overSamples": 0, + "rmsDb": -38.86917357972277, + "crestDb": 9.98761318843934, + "noiseToToneDb": 2.2946722668875523, + "noiseToToneADb": -35.26574829949449, + "lowMotion": false + }, + "native": { + "samplePeak": 0.03596773371100426, + "truePeakDb": -28.872900426797354, + "loudnessLufs": -44.015640274857304, + "centroidMedianHz": 1719.8495576360149, + "overSamples": 0, + "rmsDb": -38.86920427795782, + "crestDb": 9.987465762817079, + "noiseToToneDb": 2.294718621072647, + "noiseToToneADb": -35.265694920035834, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-bird-syrinx", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.0016491450369358063, + "maxErrAt": 45282, + "rmsErr": 0.00025934850824015885, + "relRmsErrDb": -31.68498145060413, + "wasm": { + "samplePeak": 0.0724848210811615, + "truePeakDb": -22.789532845508607, + "loudnessLufs": -39.384826220237244, + "centroidMedianHz": 3246.9736644772215, + "overSamples": 0, + "rmsDb": -35.86925853361916, + "crestDb": 13.074199958593006, + "noiseToToneDb": 3.395163753535492, + "noiseToToneADb": -0.26769809662280863, + "lowMotion": false + }, + "native": { + "samplePeak": 0.07248431444168091, + "truePeakDb": -22.78984322120366, + "loudnessLufs": -39.38565490955099, + "centroidMedianHz": 3246.899582775747, + "overSamples": 0, + "rmsDb": -35.86920348626081, + "crestDb": 13.074084200179179, + "noiseToToneDb": 3.396666156772234, + "noiseToToneADb": -0.2667876831196381, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-bird-syrinx-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.0002693869173526764, + "maxErrAt": 32700, + "rmsErr": 0.00003826655739568763, + "relRmsErrDb": -47.117064721842894, + "wasm": { + "samplePeak": 0.06643299758434296, + "truePeakDb": -23.549220126103343, + "loudnessLufs": -41.272964930830874, + "centroidMedianHz": 3608.3773722521782, + "overSamples": 0, + "rmsDb": -35.835637421519465, + "crestDb": 12.28331440320251, + "noiseToToneDb": 3.205981107524842, + "noiseToToneADb": -14.050756471755903, + "lowMotion": false + }, + "native": { + "samplePeak": 0.06643303483724594, + "truePeakDb": -23.549216558140902, + "loudnessLufs": -41.27307677467835, + "centroidMedianHz": 3608.449964447863, + "overSamples": 0, + "rmsDb": -35.835623258716865, + "crestDb": 12.283305111089557, + "noiseToToneDb": 3.20619543415809, + "noiseToToneADb": -14.048989011382716, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-syrinx-voice", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.00035776360891759396, + "maxErrAt": 33071, + "rmsErr": 0.00004987960856320729, + "relRmsErrDb": -34.59218482563713, + "wasm": { + "samplePeak": 0.013509727083146572, + "truePeakDb": -37.32985445982076, + "loudnessLufs": -43.74666608622773, + "centroidMedianHz": 3723.272760511277, + "overSamples": 0, + "rmsDb": -46.796675488586175, + "crestDb": 9.409607002734177, + "noiseToToneDb": 1.790967089244596, + "noiseToToneADb": 1.7997448212983471, + "lowMotion": false + }, + "native": { + "samplePeak": 0.013522684574127197, + "truePeakDb": -37.32721733719029, + "loudnessLufs": -43.74636063400179, + "centroidMedianHz": 3723.1202654076387, + "overSamples": 0, + "rmsDb": -46.79638101226304, + "crestDb": 9.41763937099232, + "noiseToToneDb": 1.7908757840446228, + "noiseToToneADb": 1.7996603730072778, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "advanced-human-larynx-graph", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.00003339786780998111, + "maxErrAt": 29445, + "rmsErr": 0.0000091717665877173, + "relRmsErrDb": -46.55598808562419, + "wasm": { + "samplePeak": 0.007785667199641466, + "truePeakDb": -42.173192592326515, + "loudnessLufs": -50.40895750738575, + "centroidMedianHz": 310.7359227777714, + "overSamples": 0, + "rmsDb": -50.08733256154268, + "crestDb": 7.913249276521016, + "noiseToToneDb": -30.80971405508631, + "noiseToToneADb": -27.762876726971946, + "lowMotion": false + }, + "native": { + "samplePeak": 0.00778665067628026, + "truePeakDb": -42.172393196766, + "loudnessLufs": -50.40839338870849, + "centroidMedianHz": 310.88482517283643, + "overSamples": 0, + "rmsDb": -50.086764610534054, + "crestDb": 7.913778447935089, + "noiseToToneDb": -30.8115791514923, + "noiseToToneADb": -27.76354669965494, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "dx7-algorithm-32-additive-organ", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.00016482919454574585, + "maxErrAt": 23021, + "rmsErr": 0.000020542966407864508, + "relRmsErrDb": -79.45691216935256, + "wasm": { + "samplePeak": 0.6861975789070129, + "truePeakDb": -3.270050577733717, + "loudnessLufs": -9.759294906714691, + "centroidMedianHz": 656.4219631461098, + "overSamples": 0, + "rmsDb": -8.539888188883904, + "crestDb": 5.268871817143614, + "noiseToToneDb": -37.019826930432856, + "noiseToToneADb": -37.7883341551492, + "lowMotion": false + }, + "native": { + "samplePeak": 0.6861848831176758, + "truePeakDb": -3.2702216053436386, + "loudnessLufs": -9.759310761718336, + "centroidMedianHz": 656.4199149750565, + "overSamples": 0, + "rmsDb": -8.539903940430642, + "crestDb": 5.268726863889608, + "noiseToToneDb": -37.01979015752506, + "noiseToToneADb": -37.78830074856767, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "dx7-algorithm-8-bright-pair", + "lenWasm": 144000, + "lenNative": 144000, + "maxErr": 0.000004217028617858887, + "maxErrAt": 4395, + "rmsErr": 4.0587175382789543e-7, + "relRmsErrDb": -94.24174055880029, + "wasm": { + "samplePeak": 0.1503547728061676, + "truePeakDb": -16.445110330444138, + "loudnessLufs": -27.707288476356375, + "centroidMedianHz": 570.7272310041899, + "overSamples": 0, + "rmsDb": -25.22822394507269, + "crestDb": 8.7705683198403, + "noiseToToneDb": -33.00382123099267, + "noiseToToneADb": -31.168321209089953, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1503528207540512, + "truePeakDb": -16.44522744967477, + "loudnessLufs": -27.707277125976997, + "centroidMedianHz": 570.7333021952353, + "overSamples": 0, + "rmsDb": -25.228219761996577, + "crestDb": 8.770451367352216, + "noiseToToneDb": -33.00373339984028, + "noiseToToneADb": -31.168249293529612, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/glass-chime", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.0249517853371799, + "maxErrAt": 16135, + "rmsErr": 0.004421638829626474, + "relRmsErrDb": -21.888160741392436, + "wasm": { + "samplePeak": 0.17621681094169617, + "truePeakDb": -15.062032445423984, + "loudnessLufs": -21.56737037885148, + "centroidMedianHz": 1602.6138671100102, + "overSamples": 0, + "rmsDb": -22.844945161193554, + "crestDb": 7.765891909110638, + "noiseToToneDb": -3.1846833757850774, + "noiseToToneADb": -2.8894771746074213, + "lowMotion": false + }, + "native": { + "samplePeak": 0.1762169897556305, + "truePeakDb": -15.06203177829347, + "loudnessLufs": -21.567324293584342, + "centroidMedianHz": 1602.7309843808985, + "overSamples": 0, + "rmsDb": -22.8449443529706, + "crestDb": 7.765899914787472, + "noiseToToneDb": -3.15615732806026, + "noiseToToneADb": -2.8631443461744297, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/muted-pluck", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.0006083704647608101, + "maxErrAt": 11771, + "rmsErr": 0.00011333511339155152, + "relRmsErrDb": -50.15824170095642, + "wasm": { + "samplePeak": 0.18485014140605927, + "truePeakDb": -14.661061484678669, + "loudnessLufs": -27.09375077529527, + "centroidMedianHz": 337.55371147245654, + "overSamples": 0, + "rmsDb": -22.505768680170274, + "crestDb": 7.8421644228588985, + "noiseToToneDb": -1.8131427242661857, + "noiseToToneADb": -3.7732237451762884, + "lowMotion": false + }, + "native": { + "samplePeak": 0.18485012650489807, + "truePeakDb": -14.661062836676077, + "loudnessLufs": -27.093759010723893, + "centroidMedianHz": 337.5238499031714, + "overSamples": 0, + "rmsDb": -22.505770398210146, + "crestDb": 7.842165440710797, + "noiseToToneDb": -1.8131374325665277, + "noiseToToneADb": -3.773589944716136, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/soft-triangle", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.0023703736951574683, + "maxErrAt": 25715, + "rmsErr": 0.0009930534248776234, + "relRmsErrDb": -35.17725332142618, + "wasm": { + "samplePeak": 0.19771148264408112, + "truePeakDb": -14.073758822230804, + "loudnessLufs": -24.407607566689737, + "centroidMedianHz": 432.02737524513924, + "overSamples": 0, + "rmsDb": -22.837073455193796, + "crestDb": 8.757711313308086, + "noiseToToneDb": -7.16850817229934, + "noiseToToneADb": -9.027202849078392, + "lowMotion": false + }, + "native": { + "samplePeak": 0.19771143794059753, + "truePeakDb": -14.07376035946359, + "loudnessLufs": -24.407608271539363, + "centroidMedianHz": 432.56670373115156, + "overSamples": 0, + "rmsDb": -22.837076588859357, + "crestDb": 8.757712483053472, + "noiseToToneDb": -7.168496214436587, + "noiseToToneADb": -9.027680538222803, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "ding-instrument/warm-bell", + "lenWasm": 72000, + "lenNative": 72000, + "maxErr": 0.031899021938443184, + "maxErrAt": 20346, + "rmsErr": 0.0064883595529472505, + "relRmsErrDb": -22.867583977030858, + "wasm": { + "samplePeak": 0.23455719649791718, + "truePeakDb": -12.586378660039248, + "loudnessLufs": -19.50785299525781, + "centroidMedianHz": 930.5251827853554, + "overSamples": 0, + "rmsDb": -19.848928933876216, + "crestDb": 7.2539041772066515, + "noiseToToneDb": -4.487748875412059, + "noiseToToneADb": -3.561906168663185, + "lowMotion": false + }, + "native": { + "samplePeak": 0.23455692827701569, + "truePeakDb": -12.58637491873251, + "loudnessLufs": -19.50777054747485, + "centroidMedianHz": 930.5575374762606, + "overSamples": 0, + "rmsDb": -19.84893016359035, + "crestDb": 7.253895474424504, + "noiseToToneDb": -4.485889851528931, + "noiseToToneADb": -3.56069167084123, + "lowMotion": false + }, + "flagsWasm": [], + "flagsNative": [], + "violations": [] + }, + { + "id": "tract/vowel-a", + "lenWasm": 34560, + "lenNative": 34560, + "maxErr": 0.00006673485040664673, + "maxErrAt": 28187, + "rmsErr": 0.000017135153866313287, + "relRmsErrDb": -61.202754106950046, + "wasm": { + "samplePeak": 0.08378415554761887, + "truePeakDb": -21.518327105526673, + "loudnessLufs": -34.251568232392565, + "centroidMedianHz": 1937.1220888731689, + "overSamples": 0, + "rmsDb": -33.80275018649363, + "crestDb": 12.265988122620389, + "noiseToToneDb": -10.436050344670264, + "noiseToToneADb": -12.885220061403881, + "lowMotion": false + }, + "native": { + "samplePeak": 0.08382102102041245, + "truePeakDb": -21.514509846938733, + "loudnessLufs": -34.245380439304775, + "centroidMedianHz": 1937.234382427543, + "overSamples": 0, + "rmsDb": -33.796727413065646, + "crestDb": 12.263786346131285, + "noiseToToneDb": -10.440493522060173, + "noiseToToneADb": -12.890432956436477, + "lowMotion": false + }, + "flagsWasm": [ + "FAIL onsetMs" + ], + "flagsNative": [ + "FAIL onsetMs" + ], + "violations": [] + } + ] +} \ No newline at end of file diff --git a/tools/FaustNativeRender/Program.cs b/tools/FaustNativeRender/Program.cs index e6aff0d..c533296 100644 --- a/tools/FaustNativeRender/Program.cs +++ b/tools/FaustNativeRender/Program.cs @@ -27,7 +27,9 @@ foreach (var spec in args.Skip(3)) { var eq = spec.IndexOf('='); var key = spec[..eq]; - var path = patch.ControlPaths.Single(p => p == key || p == "/" + key || p.EndsWith("/" + key)); + var hits = patch.ControlPaths.Where(p => p.TrimStart('/') == key.TrimStart('/') || p.EndsWith("/" + key.TrimStart('/'))).ToList(); + if (hits.Count != 1) { Console.Error.WriteLine($"control \"{key}\" matches {hits.Count} of: {string.Join(", ", patch.ControlPaths)}"); return 1; } + var path = hits[0]; ctl[path] = float.Parse(spec[(eq + 1)..], System.Globalization.CultureInfo.InvariantCulture); } diff --git a/tools/aqua-feel/native-diff.mjs b/tools/aqua-feel/native-diff.mjs index 37cdfa7..952d093 100644 --- a/tools/aqua-feel/native-diff.mjs +++ b/tools/aqua-feel/native-diff.mjs @@ -2,8 +2,15 @@ // Faust source, at the same params, 48 kHz and length. Nothing here was heard. // Both sides compile against their own stdlib (faustwasm bundles libfaust's; native reads AQUASYNTH_FAUST_HOME's share/faust), so a // difference here can be library text, not the host: the report records the identities. There is no tolerance: the script exits 1 -// only on a render error or a length mismatch; reading the numbers is on the reader. -// usage: node native-diff.mjs [--tract PATH.dsp] [--out DIR] +// on a render error, a length mismatch, or a LIMITS violation. LIMITS sit between the largest deltas seen across 19 sources and +// three conditions (|LUFS| 0.017, |true peak| 0.152 dB, centroid 0.3 %) and a deliberate +1 dB gain (LUFS and true peak 1.0 dB); +// the sample-level bound applies only with --smoother imp3, where the largest error seen is -75.4 dB (dx7-32 -79.5 aside) and a one-sample +// shift reads about -4 dB. With the library smoother the waveforms legitimately diverge (thruster -8 to -29 dB), so no sample bound. +// usage: node native-diff.mjs [--tract PATH.dsp] [--out DIR] [--items core|smoother] [--smoother library|old|imp3] +// --items smoother: the 12 catalog sources that call si.smooth/si.smoo (thruster at rpm 1200, 7 patches, 4 Ding instruments, one note at 261.6256 Hz for 1.5 s) +// --smoother old|imp3: rewrite si.smooth/si.smoo in the source text to a local definition (old = `*(1-s) : + ~ *(s)`, imp3 = the error-form +// recursion), the same text on both sides, so the standard library's own si.smooth no longer decides. Smoothers called inside other +// library functions are not rewritten. import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { join, dirname, resolve } from "node:path"; @@ -13,14 +20,32 @@ import { SR, measure } from "./metrics.js"; import { gate } from "./gate.js"; import { loadRuntime, compile, renderOnce, writeWav, readText, playground } from "./render.js"; +const LIMITS = { lufsDb: 0.1, truePeakDb: 0.5, centroidPct: 1, relRmsErrDbImp3: -60 }; const here = dirname(fileURLToPath(import.meta.url)), args = process.argv.slice(2); const opt = (k) => { const i = args.indexOf(k); return i < 0 ? null : args[i + 1]; }; const bin = resolve(args[0]), out = resolve(opt("--out") ?? join(tmpdir(), "aqua-feel-native-diff")); const low = (n) => readText(join(playground, "lowered", n)); -const items = [300, 600, 1200, 4800].map((rpm) => ({ id: `thruster@rpm${rpm}`, group: "patch", file: "thruster.dsp", source: low("thruster.dsp"), params: { rpm }, seconds: 3 })); -items.push({ id: "sfxr-classic-classic-sfxr-abstract-golf", group: "patch", file: "sfxr-classic-classic-sfxr-abstract-golf.dsp", source: low("sfxr-classic-classic-sfxr-abstract-golf.dsp"), params: {}, seconds: 3 }); -items.push({ id: "sfxr-explosion", group: "patch", file: "sfxr-explosion.dsp", source: low("sfxr-explosion.dsp"), params: {}, seconds: 3 }); +const mode = opt("--smoother") ?? "library", set = opt("--items") ?? "core"; +const DEFS = { + old: "nd_smooth(s) = *(1.0 - s) : + ~ *(s);", + imp3: "nd_smooth(s) = \\(x).(x + (fb ~ _ with { fb(e) = s * (e + (x' - x : min(3.4e38))); }));", +}; +const pin = (src) => mode === "library" ? src : src.replace(/\bsi\.smoo\b/g, "nd_smoo").replace(/\bsi\.smooth\b/g, "nd_smooth").replace(/import\("stdfaust\.lib"\);/, (m) => `${m}\n${DEFS[mode]}\nnd_smoo = nd_smooth(1 - 44.1/ma.SR);`); +if (!["library", "old", "imp3"].includes(mode) || !["core", "smoother"].includes(set)) { console.error("bad --smoother or --items"); process.exit(2); } +const SMOOTHER_PATCHES = ["thruster", "advanced-alien-multivalve-graph", "advanced-bird-syrinx", "advanced-bird-syrinx-graph", "advanced-syrinx-voice", "advanced-human-larynx-graph", "dx7-algorithm-32-additive-organ", "dx7-algorithm-8-bright-pair"]; +function smootherItems() { + const dir = join(here, "lowered-dings"), d = JSON.parse(readText(join(dir, "dings.json"))); + return [ + ...SMOOTHER_PATCHES.map((n) => ({ id: n === "thruster" ? "thruster@rpm1200" : n, group: "patch", file: `${n}.dsp`, source: low(`${n}.dsp`), params: n === "thruster" ? { rpm: 1200 } : {}, seconds: 3 })), + ...d.instruments.map((i) => ({ id: `ding-instrument/${i.id}`, group: "ding", file: `ding-${i.id}.dsp`, source: readText(join(dir, i.dsp)), params: { [i.controlPath]: i.rootFrequency }, seconds: d.noteSeconds })), + ]; +} +const items = set === "smoother" ? smootherItems() : [ + ...[300, 600, 1200, 4800].map((rpm) => ({ id: `thruster@rpm${rpm}`, group: "patch", file: "thruster.dsp", source: low("thruster.dsp"), params: { rpm }, seconds: 3 })), + ...["sfxr-classic-classic-sfxr-abstract-golf", "sfxr-explosion"].map((n) => ({ id: n, group: "patch", file: `${n}.dsp`, source: low(`${n}.dsp`), params: {}, seconds: 3 })), +]; if (opt("--tract")) items.push({ id: "tract/vowel-a", group: "patch", file: "tract-vowel-a.dsp", source: readText(opt("--tract")), params: {}, seconds: 0.72 }); +for (const it of items) it.source = pin(it.source); mkdirSync(join(out, "dsp"), { recursive: true }); const readF32Wav = (p) => { const b = readFileSync(p), n = b.readUInt32LE(40) / 4, x = new Float32Array(n); for (let i = 0; i < n; i++) x[i] = b.readFloatLE(44 + i * 4); return x; }; @@ -43,6 +68,14 @@ for (const it of items) { wasm: pick(mw), native: pick(mn), flagsWasm: flagsOf(it.id, it.group, mw), flagsNative: flagsOf(it.id, it.group, mn) }); } function pick(m) { return { samplePeak: m.samplePeak, truePeakDb: m.truePeakDb, loudnessLufs: m.loudnessLufs, centroidMedianHz: m.centroidMedianHz, overSamples: m.overSamples, rmsDb: m.rmsDb, crestDb: m.crestDb, noiseToToneDb: m.noiseToToneDb, noiseToToneADb: m.noiseToToneADb, lowMotion: m.lowMotion }; } -writeFileSync(join(out, "native-diff.json"), JSON.stringify({ libfaustWasm: rt.version, sampleRate: SR, rows }, null, 1)); -if (rows.some((r) => r.error)) process.exitCode = 1; -console.log(JSON.stringify({ libfaustWasm: rt.version, rows }, null, 1)); +for (const r of rows) { + if (r.error) continue; + r.violations = []; + if (Math.abs(r.native.loudnessLufs - r.wasm.loudnessLufs) > LIMITS.lufsDb) r.violations.push("loudnessLufs"); + if (Math.abs(r.native.truePeakDb - r.wasm.truePeakDb) > LIMITS.truePeakDb) r.violations.push("truePeakDb"); + if (100 * Math.abs(r.native.centroidMedianHz - r.wasm.centroidMedianHz) / r.wasm.centroidMedianHz > LIMITS.centroidPct) r.violations.push("centroidMedianHz"); + if (mode === "imp3" && r.relRmsErrDb > LIMITS.relRmsErrDbImp3) r.violations.push("relRmsErrDb"); +} +writeFileSync(join(out, "native-diff.json"), JSON.stringify({ libfaustWasm: rt.version, sampleRate: SR, items: set, smoother: mode, rows }, null, 1)); +if (rows.some((r) => r.error || r.violations.length)) process.exitCode = 1; +console.log(JSON.stringify({ libfaustWasm: rt.version, items: set, smoother: mode, rows }, null, 1)); -- 2.47.3 From 46188d13a2717ac6e1933d76d48dd65033216b9b Mon Sep 17 00:00:00 2001 From: Aqua Date: Tue, 10 Nov 2026 14:01:20 +0000 Subject: [PATCH 4/4] native-diff: fix report and header text found in review (tolerance wording, 13 sources, dx7-32 values, stray table cells) Co-Authored-By: Claude Sonnet 5.5 --- .../aqua-feel-native-diff-2026-11-10.md | 30 +++++++++---------- tools/aqua-feel/native-diff.mjs | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/renders/aqua-feel-native-diff-2026-11-10.md b/docs/renders/aqua-feel-native-diff-2026-11-10.md index 965519d..b3c6040 100644 --- a/docs/renders/aqua-feel-native-diff-2026-11-10.md +++ b/docs/renders/aqua-feel-native-diff-2026-11-10.md @@ -6,7 +6,7 @@ - native side: `tools/FaustNativeRender` over `AquaSynthPatchCompiler` (`NativeFaustRuntime.cs:530`: `-single -ftz 2 -vec -lv 1`), libfaust 2.90.20 + LLVM built in the Linux sandbox 2026-10-31, with the stdlib `faustlibraries-20261031` (master 3d8c6ef) from `AQUASYNTH_FAUST_HOME`. **The two sides compile against different standard-library text**, and that decides the thruster result below; "native" here means this sandbox's library, which the repo does not pin (`docs/faust-toolchain-boundary.md`: the host's `share/faust`). - driver: `node tools/aqua-feel/native-diff.mjs --tract `; raw numbers in `aqua-feel-native-diff-2026-11-10.json`. - tract voice: `vowel-a` Faust source from the 2026-11-08 render kit (0.72 s, default controls). Not in the repo (`--tract` takes a path), not an aqua-feel catalog item, so its `FAIL onsetMs` is the patch-group rule applied by this script, not a repo gate result. -- the script has no tolerance and exits 1 only on a render error or a length mismatch; every other judgement is the reader's. +- the script exits 1 on a render error, a length mismatch, or a violation of its `LIMITS` (see "Tolerance in `native-diff.mjs`" below); the verdicts here are the reader's. - Nothing here was heard. Columns are wasm / native. @@ -73,25 +73,25 @@ The earlier "eleven" was seven patches, three Ding instruments and the larynx gr | source | stdlib as shipped (wasm bundle vs sandbox): max err / rel RMS dB | old smoother pinned | imp3 pinned | ΔLUFS lib / old / imp3 | Δ true peak dB lib / old / imp3 | Δ centroid % lib / old / imp3 | |---|---|---|---|---|---|---| -| thruster@rpm1200 | 9.0e-2 / -16.6 | 4.5e-2 / -21.2 | 7.1e-6 / -99.9 | 0.000 / 0.001 / -0.000 | -0.081 / -0.152 / 0.000 | 0.07 / -0.08 / 0.00 | | ; | ; | -| advanced-alien-multivalve-graph | 2.9e-5 / -68.4 | 1.0e-5 / -72.5 | 7.9e-7 / -97.9 | -0.000 / -0.000 / -0.000 | -0.001 / -0.000 / -0.000 | -0.00 / 0.00 / 0.00 | | ; | ; | -| advanced-bird-syrinx | 2.8e-3 / -27.3 | 1.6e-3 / -31.7 | 3.2e-6 / -88.3 | -0.001 / -0.001 / -0.000 | -0.000 / -0.000 / -0.000 | -0.00 / -0.00 / 0.00 | | ; | ; | -| advanced-bird-syrinx-graph | 9.4e-4 / -38.4 | 2.7e-4 / -47.1 | 1.5e-5 / -75.4 | 0.000 / -0.000 / 0.000 | 0.000 / 0.000 / -0.000 | -0.01 / 0.00 / -0.00 | | ; | ; | -| advanced-syrinx-voice | 1.3e-3 / -23.9 | 3.6e-4 / -34.6 | 2.3e-6 / -86.3 | 0.001 / 0.000 / 0.000 | 0.003 / 0.003 / -0.000 | -0.00 / -0.00 / 0.00 | | ; | ; | -| advanced-human-larynx-graph | 9.6e-5 / -38.1 | 3.3e-5 / -46.6 | 7.6e-7 / -94.2 | 0.002 / 0.001 / -0.000 | 0.002 / 0.001 / -0.000 | 0.05 / 0.05 / 0.04 | | ; | ; | -| dx7-algorithm-32-additive-organ | 1.7e-4 / -79.3 | 1.6e-4 / -79.5 | 1.6e-4 / -79.5 | 0.000 / -0.000 / 0.000 | -0.000 / -0.000 / -0.000 | -0.00 / -0.00 / -0.00 | | ; | ; | -| dx7-algorithm-8-bright-pair | 4.1e-6 / -94.5 | 4.2e-6 / -94.2 | 3.9e-6 / -94.7 | 0.000 / 0.000 / 0.000 | -0.000 / -0.000 / -0.000 | 0.00 / 0.00 / -0.00 | | ; | ; | -| ding-instrument/glass-chime | 3.8e-2 / -18.4 | 2.5e-2 / -21.9 | 3.3e-7 / -130.0 | 0.000 / 0.000 / 0.000 | -0.000 / 0.000 / -0.000 | 0.02 / 0.01 / -0.00 | | ; | ; | -| ding-instrument/muted-pluck | 6.6e-4 / -49.8 | 6.1e-4 / -50.2 | 3.0e-8 / -141.7 | -0.000 / -0.000 / -0.000 | -0.000 / -0.000 / -0.000 | 0.00 / -0.01 / -0.00 | | ; | ; | -| ding-instrument/soft-triangle | 4.3e-3 / -30.3 | 2.4e-3 / -35.2 | 4.5e-8 / -142.0 | 0.000 / -0.000 / -0.000 | -0.000 / -0.000 / 0.000 | 0.16 / 0.12 / -0.00 | | ; | ; | -| ding-instrument/warm-bell | 5.4e-2 / -18.3 | 3.2e-2 / -22.9 | 2.3e-7 / -134.2 | 0.000 / 0.000 / -0.000 | -0.000 / 0.000 / -0.000 | 0.01 / 0.00 / -0.00 | | ; | ; | -| tract/vowel-a | 1.9e-4 / -52.6 | 6.7e-5 / -61.2 | 3.1e-7 / -109.3 | 0.017 / 0.006 / -0.000 | 0.013 / 0.004 / -0.000 | 0.01 / 0.01 / -0.00 | FAIL onsetMs|FAIL onsetMs ; FAIL onsetMs|FAIL onsetMs ; FAIL onsetMs| +| thruster@rpm1200 | 9.0e-2 / -16.6 | 4.5e-2 / -21.2 | 7.1e-6 / -99.9 | 0.000 / 0.001 / -0.000 | -0.081 / -0.152 / 0.000 | 0.07 / -0.08 / 0.00 | +| advanced-alien-multivalve-graph | 2.9e-5 / -68.4 | 1.0e-5 / -72.5 | 7.9e-7 / -97.9 | -0.000 / -0.000 / -0.000 | -0.001 / -0.000 / -0.000 | -0.00 / 0.00 / 0.00 | +| advanced-bird-syrinx | 2.8e-3 / -27.3 | 1.6e-3 / -31.7 | 3.2e-6 / -88.3 | -0.001 / -0.001 / -0.000 | -0.000 / -0.000 / -0.000 | -0.00 / -0.00 / 0.00 | +| advanced-bird-syrinx-graph | 9.4e-4 / -38.4 | 2.7e-4 / -47.1 | 1.5e-5 / -75.4 | 0.000 / -0.000 / 0.000 | 0.000 / 0.000 / -0.000 | -0.01 / 0.00 / -0.00 | +| advanced-syrinx-voice | 1.3e-3 / -23.9 | 3.6e-4 / -34.6 | 2.3e-6 / -86.3 | 0.001 / 0.000 / 0.000 | 0.003 / 0.003 / -0.000 | -0.00 / -0.00 / 0.00 | +| advanced-human-larynx-graph | 9.6e-5 / -38.1 | 3.3e-5 / -46.6 | 7.6e-7 / -94.2 | 0.002 / 0.001 / -0.000 | 0.002 / 0.001 / -0.000 | 0.05 / 0.05 / 0.04 | +| dx7-algorithm-32-additive-organ | 1.7e-4 / -79.3 | 1.6e-4 / -79.5 | 1.6e-4 / -79.5 | 0.000 / -0.000 / 0.000 | -0.000 / -0.000 / -0.000 | -0.00 / -0.00 / -0.00 | +| dx7-algorithm-8-bright-pair | 4.1e-6 / -94.5 | 4.2e-6 / -94.2 | 3.9e-6 / -94.7 | 0.000 / 0.000 / 0.000 | -0.000 / -0.000 / -0.000 | 0.00 / 0.00 / -0.00 | +| ding-instrument/glass-chime | 3.8e-2 / -18.4 | 2.5e-2 / -21.9 | 3.3e-7 / -130.0 | 0.000 / 0.000 / 0.000 | -0.000 / 0.000 / -0.000 | 0.02 / 0.01 / -0.00 | +| ding-instrument/muted-pluck | 6.6e-4 / -49.8 | 6.1e-4 / -50.2 | 3.0e-8 / -141.7 | -0.000 / -0.000 / -0.000 | -0.000 / -0.000 / -0.000 | 0.00 / -0.01 / -0.00 | +| ding-instrument/soft-triangle | 4.3e-3 / -30.3 | 2.4e-3 / -35.2 | 4.5e-8 / -142.0 | 0.000 / -0.000 / -0.000 | -0.000 / -0.000 / 0.000 | 0.16 / 0.12 / -0.00 | +| ding-instrument/warm-bell | 5.4e-2 / -18.3 | 3.2e-2 / -22.9 | 2.3e-7 / -134.2 | 0.000 / 0.000 / -0.000 | -0.000 / 0.000 / -0.000 | 0.01 / 0.00 / -0.00 | +| tract/vowel-a | 1.9e-4 / -52.6 | 6.7e-5 / -61.2 | 3.1e-7 / -109.3 | 0.017 / 0.006 / -0.000 | 0.013 / 0.004 / -0.000 | 0.01 / 0.01 / -0.00 | Largest metric deltas over all sources and the three conditions: |LUFS| 0.017 dB (tract), |true peak| 0.152 dB (thruster, old pinned), |centroid| 0.16 % (soft-triangle; the earlier core run has 0.3 % at thruster rpm600). **No gate flag differs on any source in any condition**; the tract's patch-group `FAIL onsetMs` is on both sides (see above). What the data says: -- With imp(3) pinned on both sides all 13 rows agree to -75 dB or better (Ding instruments -130 to -142 dB, thruster -99.9 dB, tract -109 dB; worst: `advanced-bird-syrinx-graph` -75.4 dB). `dx7-algorithm-32-additive-organ` sits at -79.5 dB under all three conditions, so its residual is not the pinned smoother (a smoother inside a library function, or float rounding; not isolated). +- With imp(3) pinned on both sides all 13 rows agree to -75 dB or better (Ding instruments -130 to -142 dB, thruster -99.9 dB, tract -109 dB; worst: `advanced-bird-syrinx-graph` -75.4 dB). `dx7-algorithm-32-additive-organ` sits at -79.3 (library), -79.5 (old) and -79.5 dB (imp3), so its residual is not the pinned smoother (a smoother inside a library function, or float rounding; not isolated). - With the old smoother pinned the thruster still differs (-21.2 dB): native's LLVM contracts the recursion to FMA and stalls at 4797.657 instead of wasm's 4796.632. The old form's stall point depends on the arithmetic; the imp(3) form does not. - As shipped, nine of the 13 rows differ at waveform level by -16 to -50 dB (thruster, the three syrinx patches, the larynx graph, all four Ding instruments); `advanced-alien-multivalve-graph` (-68 dB), the tract (-52.6 dB) and the two dx7 patches are closer. diff --git a/tools/aqua-feel/native-diff.mjs b/tools/aqua-feel/native-diff.mjs index 952d093..3ae628a 100644 --- a/tools/aqua-feel/native-diff.mjs +++ b/tools/aqua-feel/native-diff.mjs @@ -1,10 +1,10 @@ // Diff aqua-feel's faustwasm render against the daemon's native libfaust render (tools/FaustNativeRender) of the same // Faust source, at the same params, 48 kHz and length. Nothing here was heard. // Both sides compile against their own stdlib (faustwasm bundles libfaust's; native reads AQUASYNTH_FAUST_HOME's share/faust), so a -// difference here can be library text, not the host: the report records the identities. There is no tolerance: the script exits 1 -// on a render error, a length mismatch, or a LIMITS violation. LIMITS sit between the largest deltas seen across 19 sources and +// difference here can be library text, not the host: the report records the identities. The script exits 1 +// on a render error, a length mismatch, or a LIMITS violation. LIMITS sit between the largest deltas seen across 13 sources and // three conditions (|LUFS| 0.017, |true peak| 0.152 dB, centroid 0.3 %) and a deliberate +1 dB gain (LUFS and true peak 1.0 dB); -// the sample-level bound applies only with --smoother imp3, where the largest error seen is -75.4 dB (dx7-32 -79.5 aside) and a one-sample +// the sample-level bound applies only with --smoother imp3, where the largest error seen is -75.4 dB (dx7-32, unexplained, is -79.5) and a one-sample // shift reads about -4 dB. With the library smoother the waveforms legitimately diverge (thruster -8 to -29 dB), so no sample bound. // usage: node native-diff.mjs [--tract PATH.dsp] [--out DIR] [--items core|smoother] [--smoother library|old|imp3] // --items smoother: the 12 catalog sources that call si.smooth/si.smoo (thruster at rpm 1200, 7 patches, 4 Ding instruments, one note at 261.6256 Hz for 1.5 s) -- 2.47.3