Faust on Linux: FaustRender probe, toolchain notes, thruster v5 manifest #5
4 changed files with 167 additions and 0 deletions
|
|
@ -91,6 +91,25 @@ search; `FaustHome` then supplies `share/faust` if the prefix is not the file's
|
|||
grandparent. The loader prepends `lib` and `bin` to `PATH` using the platform
|
||||
path separator.
|
||||
|
||||
## Linux Native Toolchain
|
||||
|
||||
Built and run on Debian 13 (2026-11-03). Faust `aa30fd7` (2.90.20), LLVM 19
|
||||
(`LLVM_CONFIG=/usr/lib/llvm-19/bin/llvm-config`, `INCLUDE_DYNAMIC=ON`,
|
||||
`backends/most.cmake`), installed to a prefix; `make -j4`. The first link
|
||||
failed with `cannot find -lPolly -lPollyISL -lzstd`; the install prefix that
|
||||
resulted has `lib/libfaust.so` but `share/faust` holds only the architecture
|
||||
files, not `stdfaust.lib` and the other `.lib` files (they live in the separate
|
||||
faustlibraries repo). Compiling any patch that imports `stdfaust.lib` then
|
||||
fails with `unable to open file stdfaust.lib`. Point `AQUASYNTH_FAUST_HOME` at a
|
||||
home whose `lib/` has `libfaust.so` and whose `share/faust` has both.
|
||||
|
||||
`tools/FaustRender` renders one `.dsp` with an `rpm` control to WAVs plus
|
||||
`manifest.json` (hashes, peak, rms, non-finite flag). It measures; it does not
|
||||
listen. `docs/renders/thruster-v5-manifest.json` is its output for
|
||||
`patches/thruster/thruster.dsp` at PR #3 head `c6561da`, 4 s per case. The render is
|
||||
deterministic (two reruns byte-identical); the 2026-10-31 sweep file is a 6 s
|
||||
render, which is why its hash differs from a 4 s sweep.
|
||||
|
||||
## Invariants
|
||||
|
||||
- AquaSynth remains the authority for what a patch means.
|
||||
|
|
|
|||
46
docs/renders/thruster-v5-manifest.json
Normal file
46
docs/renders/thruster-v5-manifest.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"patch": "thruster",
|
||||
"dspSha256": "8afc045eab94f396e21d96a262c5d71910103acd51da5913d3eb3036b48a5809",
|
||||
"sampleRate": 48000,
|
||||
"blockFrames": 512,
|
||||
"seconds": 4,
|
||||
"rpmControl": "rpm",
|
||||
"os": "Debian GNU/Linux 13 (trixie)",
|
||||
"faustHome": "/var/lib/mind-aqua-self/faust-home-overlay-20261031",
|
||||
"faustLibrary": "/var/lib/mind-aqua-self/faust-home-overlay-20261031/lib/libfaust.so",
|
||||
"cases": [
|
||||
{
|
||||
"label": "low-rpm600",
|
||||
"file": "thruster-low-rpm600.wav",
|
||||
"rpmStart": 600,
|
||||
"rpmEnd": 600,
|
||||
"frames": 192000,
|
||||
"peak": 0.18832043,
|
||||
"rms": 0.07348131804614891,
|
||||
"nonFinite": false,
|
||||
"sha256": "4aeaa1f4d9aa5d63025dd8b3d45b25fc6b030a366ba05237c0021b508308e0ba"
|
||||
},
|
||||
{
|
||||
"label": "high-rpm4800",
|
||||
"file": "thruster-high-rpm4800.wav",
|
||||
"rpmStart": 4800,
|
||||
"rpmEnd": 4800,
|
||||
"frames": 192000,
|
||||
"peak": 0.3635283,
|
||||
"rms": 0.14323418081979195,
|
||||
"nonFinite": false,
|
||||
"sha256": "390874aa1d5c4d50f251fa0735b82f859bba43ffee4236fd24d5b06a4733ef7d"
|
||||
},
|
||||
{
|
||||
"label": "sweep-600-4800",
|
||||
"file": "thruster-sweep-600-4800.wav",
|
||||
"rpmStart": 600,
|
||||
"rpmEnd": 4800,
|
||||
"frames": 192000,
|
||||
"peak": 0.34473976,
|
||||
"rms": 0.11363349353245127,
|
||||
"nonFinite": false,
|
||||
"sha256": "ad8d1508aa2b37b5038a6acfcfb21fc46ae04f0180adcd6ea60edc7e918e4068"
|
||||
}
|
||||
]
|
||||
}
|
||||
12
tools/FaustRender/FaustRender.csproj
Normal file
12
tools/FaustRender/FaustRender.csproj
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\AquaSynth.Faust\AquaSynth.Faust.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
90
tools/FaustRender/Program.cs
Normal file
90
tools/FaustRender/Program.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using AquaSynth.Faust;
|
||||
|
||||
// Offline probe: render one Faust source with a swept `rpm` control to mono 16-bit WAVs through the
|
||||
// native libfaust path, and write manifest.json (hashes, levels, toolchain). It measures peak, rms and
|
||||
// NaN only; it cannot say how anything sounds.
|
||||
// usage: FaustRender <patch.dsp> <outDir> <seconds> <label>=<rpmA>[:<rpmB>] ...
|
||||
const int Rate = 48000, Frames = 512;
|
||||
if (args.Length < 4) { Console.Error.WriteLine("usage: FaustRender <patch.dsp> <outDir> <seconds> <label>=<rpmA>[:<rpmB>] ..."); return 2; }
|
||||
var dspPath = Path.GetFullPath(args[0]);
|
||||
var outDir = Path.GetFullPath(args[1]);
|
||||
var seconds = double.Parse(args[2]);
|
||||
var source = File.ReadAllText(dspPath);
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
var home = AquaSynthFaustToolchain.Find();
|
||||
Console.WriteLine($"faust home: {home ?? "(not found)"}");
|
||||
using var compiler = new AquaSynthPatchCompiler(new AquaSynthNativeOptions(SampleRate: Rate));
|
||||
var name = Path.GetFileNameWithoutExtension(dspPath);
|
||||
if (!compiler.TryCompileSource(new AquaSynthCompileIdentity(name, name, ""), source, (float)seconds, out var patch0, out var cerr))
|
||||
{
|
||||
Console.Error.WriteLine("COMPILE FAILED: " + cerr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
using var patch = patch0!;
|
||||
var rpmPath = patch.ControlPaths.First(p => p.EndsWith("rpm"));
|
||||
var cases = new List<object>();
|
||||
foreach (var spec in args.Skip(3))
|
||||
{
|
||||
var eq = spec.IndexOf('=');
|
||||
var label = spec[..eq];
|
||||
var range = spec[(eq + 1)..].Split(':');
|
||||
var a = float.Parse(range[0]);
|
||||
var b = range.Length > 1 ? float.Parse(range[1]) : a;
|
||||
|
||||
using var stream = patch.CreateStreamingPatch();
|
||||
var buf = GC.AllocateArray<float>(Frames, pinned: true);
|
||||
var ptrs = GC.AllocateArray<IntPtr>(1, pinned: true);
|
||||
unsafe { ptrs[0] = (IntPtr)Unsafe.AsPointer(ref buf[0]); }
|
||||
var outP = Marshal.UnsafeAddrOfPinnedArrayElement(ptrs, 0);
|
||||
var blocks = (int)(seconds * Rate) / Frames;
|
||||
var ctl = new Dictionary<string, float>();
|
||||
var pcm = new short[blocks * Frames];
|
||||
double sum = 0; float peak = 0; var nan = false;
|
||||
for (var blk = 0; blk < blocks; blk++)
|
||||
{
|
||||
ctl[rpmPath] = a + (b - a) * blk / Math.Max(1, blocks - 1);
|
||||
stream.ProcessBlock(Frames, IntPtr.Zero, outP, ctl);
|
||||
for (var i = 0; i < Frames; i++)
|
||||
{
|
||||
var v = buf[i];
|
||||
nan |= float.IsNaN(v) || float.IsInfinity(v);
|
||||
peak = Math.Max(peak, Math.Abs(v));
|
||||
sum += (double)v * v;
|
||||
pcm[blk * Frames + i] = (short)(Math.Clamp(v, -1f, 1f) * 32767);
|
||||
}
|
||||
}
|
||||
|
||||
var wav = Path.Combine(outDir, $"{name}-{label}.wav");
|
||||
using (var w = new BinaryWriter(File.Create(wav)))
|
||||
{
|
||||
var db = pcm.Length * 2;
|
||||
w.Write("RIFF"u8); w.Write(36 + db); w.Write("WAVEfmt "u8); w.Write(16); w.Write((short)1); w.Write((short)1);
|
||||
w.Write(Rate); w.Write(Rate * 2); w.Write((short)2); w.Write((short)16); w.Write("data"u8); w.Write(db);
|
||||
foreach (var s in pcm) w.Write(s);
|
||||
}
|
||||
|
||||
cases.Add(new
|
||||
{
|
||||
label, file = Path.GetFileName(wav), rpmStart = a, rpmEnd = b, frames = pcm.Length,
|
||||
peak, rms = Math.Sqrt(sum / pcm.Length), nonFinite = nan,
|
||||
sha256 = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(wav))).ToLowerInvariant()
|
||||
});
|
||||
Console.WriteLine($"{wav} rpm={a}->{b} peak={peak:F3} rms={Math.Sqrt(sum / pcm.Length):F4} nonFinite={nan}");
|
||||
}
|
||||
|
||||
var manifest = new
|
||||
{
|
||||
patch = name, dspSha256 = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(dspPath))).ToLowerInvariant(),
|
||||
sampleRate = Rate, blockFrames = Frames, seconds, rpmControl = rpmPath,
|
||||
os = RuntimeInformation.OSDescription, faustHome = home,
|
||||
faustLibrary = home is null ? null : AquaSynthFaustToolchain.FindLibrary(home),
|
||||
cases
|
||||
};
|
||||
File.WriteAllText(Path.Combine(outDir, "manifest.json"), JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }));
|
||||
return 0;
|
||||
Loading…
Reference in a new issue