AquaSynth/tools/FaustNativeRender/Program.cs
Aqua 4e6a64b311 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 <noreply@anthropic.com>
2026-11-10 06:44:09 +00:00

55 lines
2.7 KiB
C#

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 <patch.dsp> <out.wav> <seconds> [control=value ...]
const int Rate = 48000, Frames = 512;
if (args.Length < 3) { Console.Error.WriteLine("usage: FaustNativeRender <patch.dsp> <out.wav> <seconds> [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);
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!;
if (patch.Manifest.OutputCount != 1) { Console.Error.WriteLine($"expected 1 output, patch has {patch.Manifest.OutputCount}"); return 1; }
var ctl = new Dictionary<string, float>();
foreach (var spec in args.Skip(3))
{
var eq = spec.IndexOf('=');
var key = spec[..eq];
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);
}
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 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;