Expose public gesture timeline API

This commit is contained in:
Meta Vens 2026-05-29 20:21:16 +01:00
commit 1bf220027d
9 changed files with 198 additions and 9 deletions

View file

@ -133,4 +133,45 @@ public sealed class NativeFaustRuntimeTests
Assert.Contains(stream.SnapshotProbes(), pair => pair.Key == "debug/level" && pair.Value >= 0.0f);
}
}
[Fact]
public void NativeFaustCompiledPatchExposesPrimitiveControlSurfaceCatalogWhenToolchainIsAvailable()
{
const string script = """
morphology name=oral length_cm=17 diameters=.6,.8,1.2,1.5,.9
waveguide_path name=oral_path morphology=oral loss=.998
source_port name=folds path=oral_path pressure=.7 tension=.55 opening=.45 noise=.05 impedance=.3
radiation_load name=mouth path=oral_path aperture=.8 reflection=-.82 impedance=.28
vocal_network name=voice paths=oral_path sources=folds radiation=mouth
vocal network=voice freq=150 gain=.2 sustain=.05 decay=.04
""";
using var compiler = new AquaSynthPatchCompiler();
if (!compiler.TryCompileScript(new AquaSynthCompileIdentity("native_surface_smoke", "native_surface_smoke", script), out var patch, out var error))
{
if (error?.Contains("Faust toolchain not found", StringComparison.OrdinalIgnoreCase) == true ||
error?.Contains("Faust DLL not found", StringComparison.OrdinalIgnoreCase) == true)
{
return;
}
Assert.Fail($"AquaSynth native Faust surface compile failed: {error}");
}
using (patch)
{
Assert.Contains("/vocal/sources/0/pressure", patch!.ControlSurfaces.SurfacePaths);
Assert.Contains("vocal/sources/0/pressure", patch.ControlPaths);
var timeline = patch.ControlSurfaces.CreateTimeline(includePatchSplines: false);
timeline.SetFuturePoint("/vocal/sources/0/pressure", timeSeconds: .01f, normalizedValue: .9f, nowSeconds: 0);
var controls = patch.ControlValuesAt(timeline, .01f);
var samples = patch.Render(controls);
Assert.Contains("/vocal/sources/0/pressure", controls.Keys);
Assert.DoesNotContain("/vocal/radiation/0/reflection", controls.Keys);
Assert.True(samples.Length > 1024);
Assert.Contains(samples, sample => MathF.Abs(sample) > 0.0001f);
}
}
}

View file

@ -374,6 +374,39 @@ public sealed class PatchScriptTests
Assert.InRange(last.Value, .34f, .36f);
}
[Fact]
public void ControlSurfaceCatalogIsThePublicGestureAuthoringApi()
{
var patch = PatchScript.Parse("""
morphology name=oral length_cm=17 diameters=.6,.8,1.2,1.6,.9
waveguide_path name=oral_path morphology=oral loss=.998
source_port name=folds path=oral_path pressure=.7 tension=.55 opening=.45 noise=.05 impedance=.3
radiation_load name=mouth path=oral_path aperture=.8 reflection=-.82 impedance=.28
vocal_network name=voice paths=oral_path sources=folds radiation=mouth
vocal network=voice freq=150 gain=.2 sustain=.2
""");
var catalog = ControlSurfaceCatalog.FromPatch(patch);
Assert.Contains("/vocal/sources/0/pressure", catalog.SurfacePaths);
Assert.Contains("/vocal/radiation/0/aperture", catalog.SurfacePaths);
var pressure = catalog.Surface("/vocal/sources/0/pressure");
Assert.Equal("folds", pressure.Owner);
Assert.Equal("pressure", pressure.Field);
Assert.InRange(pressure.DefaultNormalized, .69f, .71f);
var timeline = catalog.CreateTimeline(includePatchSplines: false);
timeline.SetFuturePoint("/vocal/sources/0/pressure", timeSeconds: .016f, normalizedValue: .9f, nowSeconds: .0f);
timeline.SetFuturePoint("/vocal/radiation/0/aperture", timeSeconds: .016f, normalizedValue: .4f, nowSeconds: .0f);
var controls = timeline.ControlValuesAt(.016f);
Assert.InRange(controls["/vocal/sources/0/pressure"], .89f, .91f);
Assert.InRange(controls["/vocal/radiation/0/aperture"], .39f, .41f);
Assert.Contains(controls, pair => pair.Key == "/vocal/sources/0/tension");
Assert.Throws<KeyNotFoundException>(() =>
timeline.SetFuturePoint("/vocal/nope", timeSeconds: .02f, normalizedValue: .5f, nowSeconds: .0f));
}
[Fact]
public void PrimitiveProbeTimelineReportsFlowBeforeAudioParity()
{