Add smooth vocal gesture curves

This commit is contained in:
Meta Vens 2026-05-28 23:19:57 +01:00
commit 7936912a1a
6 changed files with 39 additions and 4 deletions

View file

@ -275,6 +275,14 @@ fixed-frequency floor underneath it. Valve displacement shapes aperture; it is
not a second hidden audio oscillator. Source flow comes from pressure through
dynamic aperture, modal tissue motion, and acoustic load.
Birdsong and speech gestures are continuous motor acts. Linear control ramps are
valid when the author wants mechanical straight-line motion, but the default
mental model for vocal articulation should be smooth pressure, tension,
stiffness, aperture, and radiation trajectories. Use smooth gesture
interpolation for vocal motifs before blaming morphology or adding spectral
compensators; a straight spectrogram trace is often a straight control curve
leaking through the physics.
## Source Distillations
- PortAudio callback guidance: callbacks are delicate realtime contexts; avoid

View file

@ -43,6 +43,8 @@ public static class FaustEmitter
source.AppendLine("release_start(a,d,g) = max(g, a + d);");
source.AppendLine("oneshot_adsr(a,d,s,r,g) = select2(age < a, select2(age < a + d, select2(age < release_start(a,d,g), select2(age < release_start(a,d,g) + r, 0.0, s * (1.0 - (age - release_start(a,d,g)) / max(0.0001, r))), s), 1.0 - (1.0 - s) * ((age - a) / max(0.0001, d))), age / max(0.0001, a));");
source.AppendLine("seg(t,t0,d,a,b) = a + (b - a) * clip01((t - t0) / max(0.0001, d));");
source.AppendLine("smooth01(x) = clip01(x) * clip01(x) * (3.0 - 2.0 * clip01(x));");
source.AppendLine("seg_smooth(t,t0,d,a,b) = a + (b - a) * smooth01((t - t0) / max(0.0001, d));");
source.AppendLine("seg_exp(t,t0,d,a,b) = exp(log(max(0.00001, a)) + (log(max(0.00001, b)) - log(max(0.00001, a))) * clip01((t - t0) / max(0.0001, d)));");
source.AppendLine("seg_curve(c,t,t0,d,a,b) = select2(c < 0.5, seg_exp(t,t0,d,a,b), seg(t,t0,d,a,b));");
source.AppendLine("rl_release_start(r1,r2,r3,g) = max(g, r1 + r2 + r3);");
@ -160,9 +162,12 @@ public static class FaustEmitter
{
var from = points[i];
var to = points[i + 1];
var segment = interpolation == ControlCurveInterpolation.Hold
? F(from.Value)
: $"seg({curveId}_time, {F(from.TimeSeconds)}, {F(Math.Max(0.0001f, to.TimeSeconds - from.TimeSeconds))}, {F(from.Value)}, {F(to.Value)})";
var segment = interpolation switch
{
ControlCurveInterpolation.Hold => F(from.Value),
ControlCurveInterpolation.Smooth => $"seg_smooth({curveId}_time, {F(from.TimeSeconds)}, {F(Math.Max(0.0001f, to.TimeSeconds - from.TimeSeconds))}, {F(from.Value)}, {F(to.Value)})",
_ => $"seg({curveId}_time, {F(from.TimeSeconds)}, {F(Math.Max(0.0001f, to.TimeSeconds - from.TimeSeconds))}, {F(from.Value)}, {F(to.Value)})"
};
expression = $"select2({curveId}_time < {F(to.TimeSeconds)}, {expression}, {segment})";
}

View file

@ -559,7 +559,8 @@ public enum ControlCurveMode
public enum ControlCurveInterpolation
{
Linear,
Hold
Hold,
Smooth
}
public sealed record ControlCurvePoint(float TimeSeconds, float Value);

View file

@ -2903,6 +2903,7 @@ public static class PatchScript
{
"linear" or "lin" => ControlCurveInterpolation.Linear,
"hold" or "step" or "sample_hold" => ControlCurveInterpolation.Hold,
"smooth" or "smoothstep" or "ease" or "eased" => ControlCurveInterpolation.Smooth,
_ => throw new PatchScriptException(line, $"unknown control curve interpolation `{value}`")
};

View file

@ -212,3 +212,4 @@
{"evidence":"Demoted Pink Trombone log-mel parity tests from hard acceptance thresholds to diagnostic artifact generation plus bounded/nonzero render checks. Full-suite parity still reports weak articulation for the graph voice after glottal lowering moved onto tissue valves, but those scores now live in artifacts instead of pretending PT parity is the current gate.","claim":"Pink Trombone parity is no longer the owner of this architecture phase. It remains a diagnostic lane to inspect artifacts after structural source/morphology/loss/radiation work, and hard acceptance should return only after the generalized graph model has its render smoke and passivity behavior under control.","timestamp":"2026-05-28T20:25:00+01:00","topic":"parity-test-authority"}
{"evidence":"Listening/spectrogram audit showed the first tissue-valve batches were mostly faint noise and DC-like startup energy. Replaced the non-self-sustaining noise-resonator seed with a pressure-gated tissue oscillator whose frequency is derived from stiffness/mass/tension and bounded load detune, and bounded source-load pressure feedback with `tanh` so the tract can perturb instead of fully quench the source. Added an explicit subglottal wall terminal to `human-larynx-graph.aqua` so folds at the larynx junction have an upstream trachea segment instead of a source node with no incident wave port. Human modal, bird, and alien graph patches now render sustained harmonic stacks in spectrograms under `artifacts/listening/vocal-core-squawk-20260528T211222944`.","claim":"The graph machine can now produce audible vocalizations through reusable tissue-valve semantics. The next work is no longer basic audibility; it is shaping morphology, gestures, and source coupling into credible speech and bird articulation.","timestamp":"2026-05-28T21:16:00+01:00","topic":"tissue-valve-squawk-audit"}
{"evidence":"Warbling White-eye visual audition exposed two source-ownership errors: graph tissue-valve lowering still let `freq=` impose a stiffness floor even when stiffness was explicit/parameter-bound, and direct displacement voicing injected low-frequency junk instead of only shaping aperture. Fixed graph lowering so explicit stiffness owns effective stiffness, lifted the graph tissue modal ceiling to 10 kHz for small syrinx/alien sources, and reduced modal/displacement flow drive to avoid square-wave broadband saturation. New audition artifacts under `artifacts/listening/warbling-white-eye-20260528T220500961` include five WAVs plus reference/candidate spectrogram comparisons; `05-single-trace.wav` is the clearest Warbling White-eye-shaped candidate by eye.","claim":"Bird-like graph vocalization now depends on visible phrase gestures, realtime stiffness/pressure/opening curves, and source-flow ownership rather than metrics or a fixed `freq=` rail. Future syrinx fitting should start from single-dominant-labium traces, then add bilateral behavior only when the spectrogram needs it.","timestamp":"2026-05-28T22:32:00+01:00","topic":"warbling-white-eye-visual-audition"}
{"evidence":"A visual review of the Warbling White-eye comparison showed the previous best candidate still failed the reference: its contours were piecewise straight and its harmonic ladder was too strong. Added `interp=smooth` control curves lowered to Faust `seg_smooth` smoothstep segments, then rerendered the Warbling White-eye audition with smooth pressure/opening/tension/stiffness gestures and a low-drive single-labium candidate. `artifacts/listening/warbling-white-eye-20260528T220500961/06-smooth-low-drive.compare.png` now shows curved motif contours and much weaker harmonics, though it is still only a rough birdlike gesture, not a species-quality match.","claim":"Smooth realtime gesture semantics are required for vocal modeling. Straight spectral contours are often authored linear control curves leaking through the physical model, so future bird/human fitting should use smooth motor gestures before adding morphology changes or spectral compensators.","timestamp":"2026-05-28T22:50:00+01:00","topic":"smooth-vocal-gestures"}

View file

@ -687,6 +687,25 @@ public sealed class PatchScriptTests
Assert.DoesNotContain("_stiffness_hint * (0.35 + 1.65", export.Source);
}
[Fact]
public void SmoothControlCurvesLowerToSmoothFaustSegments()
{
var patch = PatchScript.Parse("""
patch gain=.2
param path=/voice/tension default=.4 min=0 max=1 step=.001
curve name=bird_motor path=/voice/tension points=0:.2,.12:.8,.24:.35 interp=smooth depth=.9
v wave=sine freq=440 tension=@/voice/tension sustain=.3
""");
var curve = Assert.Single(patch.ControlCurves);
Assert.Equal(ControlCurveInterpolation.Smooth, curve.Interpolation);
var export = FaustEmitter.Emit(patch, new FaustExportOptions("smooth_curve"));
Assert.Contains("seg_smooth", export.Source);
Assert.Contains("smooth01", export.Source);
Assert.Contains("hslider(\"/curves/bird_motor/depth\"", export.Source);
}
[Fact]
public void ParserRejectsUnknownParameterReferences()
{