Represent tract waveguide substep clock
This commit is contained in:
parent
c85c213992
commit
da4be187a2
7 changed files with 28 additions and 15 deletions
|
|
@ -71,7 +71,8 @@ named failure against Pink Trombone, but it does not own the anatomy:
|
|||
- Waveguide lowering now derives live per-section diameter targets, areas,
|
||||
and reflection coefficients from the tract shape plus tongue, constriction,
|
||||
and lip controls before scattering.
|
||||
True substep execution remains missing.
|
||||
Substep clock intent is represented and lowering consumes it for drive/loss
|
||||
scaling, but exact intra-sample recursive state updates remain missing.
|
||||
|
||||
6. `tract_motion`: control-rate slew and obstruction history.
|
||||
- Owns diameter/constriction/velum slew rates and obstruction threshold.
|
||||
|
|
@ -97,11 +98,11 @@ pressure instead of silently inventing all of them inside one helper. The
|
|||
waveguide lowering consumes the derived oral reflection field as right/left
|
||||
section state equations.
|
||||
|
||||
The next cut is timing and proof: the oral/nasal tube now derives moving
|
||||
The next cut is timing exactness and proof: the oral/nasal tube now derives moving
|
||||
per-section diameter/area/reflection fields, distributes frication into section
|
||||
updates, and derives release bursts from obstruction history. PT's two tract
|
||||
steps per output sample and rendered parity fixtures still need low-level
|
||||
owners.
|
||||
updates, derives release bursts from obstruction history, and carries substep
|
||||
clock intent. Exact two recursive tract state updates per output sample and
|
||||
rendered parity fixtures still need proof.
|
||||
|
||||
## Cut Line
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ Current AquaSynth can express:
|
|||
|
||||
Current AquaSynth cannot exactly express:
|
||||
|
||||
- a subgraph that steps twice per output sample;
|
||||
- exact two recursive tract state updates inside one output sample;
|
||||
- exact Pink Trombone LF glottal coefficient updates.
|
||||
|
||||
## Current Parity Rung
|
||||
|
|
@ -104,6 +104,9 @@ Trombone anatomy:
|
|||
pressure from obstruction history.
|
||||
- waveguide lowering derives live per-section diameter targets, areas, and
|
||||
reflection coefficients from shape and gesture controls.
|
||||
- `substeps` expresses waveguide clock intent; current Faust lowering consumes
|
||||
it through drive/loss scaling but does not yet prove exact intra-sample state
|
||||
updates.
|
||||
- The proxy script parses and emits Faust with live controls, but its missing
|
||||
features explicitly name the waveguide authorities AquaSynth does not own.
|
||||
- `tools/vocal-tract-playground` exposes the same control surface for fast
|
||||
|
|
|
|||
|
|
@ -514,8 +514,11 @@ public static class FaustEmitter
|
|||
{
|
||||
var sections = areaFunction.Sections;
|
||||
var loss = F(Math.Clamp(tract.WaveguideLoss, 0, 1));
|
||||
var substeps = Math.Max(1, tract.Substeps);
|
||||
source.AppendLine($"{name}_wg_loss = {loss};");
|
||||
source.AppendLine($"{name}_wg_substeps = {Math.Max(1, tract.Substeps)};");
|
||||
source.AppendLine($"{name}_wg_substeps = {substeps};");
|
||||
source.AppendLine($"{name}_wg_substep_drive = 1.0 / {name}_wg_substeps;");
|
||||
source.AppendLine($"{name}_wg_substep_loss = pow({name}_wg_loss, {name}_wg_substeps);");
|
||||
source.AppendLine($"{name}_wg_injection_cell = {name}_tract_injection_pos * {F(sections)};");
|
||||
for (var i = 0; i < sections; i++)
|
||||
{
|
||||
|
|
@ -529,7 +532,7 @@ public static class FaustEmitter
|
|||
source.AppendLine($"{name}_wg_diameter_target_{i} = max(0.0, {target});");
|
||||
source.AppendLine($"{name}_wg_diameter_{i} = slew({shapeReturn}, {name}_wg_diameter_target_{i});");
|
||||
source.AppendLine($"{name}_wg_area_{i} = max(0.000001, {name}_wg_diameter_{i} * {name}_wg_diameter_{i});");
|
||||
source.AppendLine($"{name}_wg_inject_{i} = {name}_tract_injection_pressure * clip01(1.0 - abs({name}_wg_injection_cell - {F(i)}) / {injectionWidth});");
|
||||
source.AppendLine($"{name}_wg_inject_{i} = {name}_tract_injection_pressure * {name}_wg_substep_drive * clip01(1.0 - abs({name}_wg_injection_cell - {F(i)}) / {injectionWidth});");
|
||||
}
|
||||
for (var i = 1; i < sections; i++)
|
||||
{
|
||||
|
|
@ -538,7 +541,7 @@ public static class FaustEmitter
|
|||
|
||||
var nasal = tract.Nasal is { AreaFunction: { } nasalShape } ? tract.Nasal : null;
|
||||
var junction = nasal is null ? -1 : Math.Clamp(nasal.JunctionIndex, 1, sections - 2);
|
||||
source.AppendLine($"{name}_wg_r_0 = ({name}_tract_excitation + {name}_wg_inject_0 * 0.5 + {name}_wg_l_0 * {glottalReflection}) : mem;");
|
||||
source.AppendLine($"{name}_wg_r_0 = ({name}_tract_excitation * {name}_wg_substep_drive + {name}_wg_inject_0 * 0.5 + {name}_wg_l_0 * {glottalReflection}) : mem;");
|
||||
source.AppendLine($"{name}_wg_l_{sections - 1} = ({name}_wg_r_{sections - 1} * {lipReflection}) : mem;");
|
||||
for (var i = 1; i < sections; i++)
|
||||
{
|
||||
|
|
@ -547,8 +550,8 @@ public static class FaustEmitter
|
|||
continue;
|
||||
}
|
||||
source.AppendLine($"{name}_wg_scatter_{i} = {name}_wg_k_{i} * ({name}_wg_r_{i - 1} + {name}_wg_l_{i});");
|
||||
source.AppendLine($"{name}_wg_r_{i} = (({name}_wg_r_{i - 1} - {name}_wg_scatter_{i} + {name}_wg_inject_{i} * 0.5) * {name}_wg_loss) : mem;");
|
||||
source.AppendLine($"{name}_wg_l_{i - 1} = (({name}_wg_l_{i} + {name}_wg_scatter_{i} + {name}_wg_inject_{i - 1} * 0.5) * {name}_wg_loss) : mem;");
|
||||
source.AppendLine($"{name}_wg_r_{i} = (({name}_wg_r_{i - 1} - {name}_wg_scatter_{i} + {name}_wg_inject_{i} * 0.5) * {name}_wg_substep_loss) : mem;");
|
||||
source.AppendLine($"{name}_wg_l_{i - 1} = (({name}_wg_l_{i} + {name}_wg_scatter_{i} + {name}_wg_inject_{i - 1} * 0.5) * {name}_wg_substep_loss) : mem;");
|
||||
}
|
||||
|
||||
var noseOutput = "0.0";
|
||||
|
|
@ -568,8 +571,8 @@ public static class FaustEmitter
|
|||
source.AppendLine($"{name}_nose_reflect_left = (2.0 * {name}_wg_area_{junction - 1} - {name}_nose_sum) / {name}_nose_sum;");
|
||||
source.AppendLine($"{name}_nose_reflect_right = (2.0 * {name}_wg_area_{junction} - {name}_nose_sum) / {name}_nose_sum;");
|
||||
source.AppendLine($"{name}_nose_reflect_nose = (2.0 * {name}_nose_area - {name}_nose_sum) / {name}_nose_sum;");
|
||||
source.AppendLine($"{name}_wg_l_{junction - 1} = ((({name}_nose_reflect_left * {name}_wg_r_{junction - 1}) + (1.0 + {name}_nose_reflect_left) * ({name}_wg_l_{junction} + {name}_nose_l_0) + {name}_wg_inject_{junction - 1} * 0.5) * {name}_wg_loss) : mem;");
|
||||
source.AppendLine($"{name}_wg_r_{junction} = ((({name}_nose_reflect_right * {name}_wg_l_{junction}) + (1.0 + {name}_nose_reflect_right) * ({name}_wg_r_{junction - 1} + {name}_nose_l_0) + {name}_wg_inject_{junction} * 0.5) * {name}_wg_loss) : mem;");
|
||||
source.AppendLine($"{name}_wg_l_{junction - 1} = ((({name}_nose_reflect_left * {name}_wg_r_{junction - 1}) + (1.0 + {name}_nose_reflect_left) * ({name}_wg_l_{junction} + {name}_nose_l_0) + {name}_wg_inject_{junction - 1} * 0.5) * {name}_wg_substep_loss) : mem;");
|
||||
source.AppendLine($"{name}_wg_r_{junction} = ((({name}_nose_reflect_right * {name}_wg_l_{junction}) + (1.0 + {name}_nose_reflect_right) * ({name}_wg_r_{junction - 1} + {name}_nose_l_0) + {name}_wg_inject_{junction} * 0.5) * {name}_wg_substep_loss) : mem;");
|
||||
source.AppendLine($"{name}_nose_r_0 = (({name}_nose_reflect_nose * {name}_nose_l_0) + (1.0 + {name}_nose_reflect_nose) * ({name}_wg_l_{junction} + {name}_wg_r_{junction - 1})) * {name}_nose_loss : mem;");
|
||||
source.AppendLine($"{name}_nose_l_{noseSections - 1} = ({name}_nose_r_{noseSections - 1} * {noseReflection}) : mem;");
|
||||
for (var i = 1; i < noseSections; i++)
|
||||
|
|
|
|||
|
|
@ -73,13 +73,14 @@ public static class PinkTromboneReference
|
|||
new("tract_shape_motion", "tract_motion slew semantics", "AquaSynth can author tract control slew and obstruction thresholds for moving diameter targets."),
|
||||
new("diameter_authority", "dynamic waveguide section diameters", "Waveguide lowering derives live per-section diameter targets before recalculating section areas and reflection coefficients."),
|
||||
new("closure_transients", "obstruction-history burst", "Waveguide lowering derives burst pressure from prior obstruction state and current opening."),
|
||||
new("tract_sample_rate", "substeps field", "AquaSynth can author waveguide substep count and lowering scales drive/loss by that clock intent."),
|
||||
new("runtime_controls", "/pink/frequency,/pink/intensity,/pink/tenseness,/pink/tongue/index,/pink/tongue/diameter,/pink/velum,/pink/turbulence", "AquaSynth can expose stable runtime parameter paths."),
|
||||
new("glottal_pitch_pressure_proxy", "tract LF-style source", "AquaSynth can drive pitch/loudness-like controls without recompiling."),
|
||||
new("turbulence_proxy", "position-aware frication controls", "The tract voice has constriction index/diameter and turbulence controls."),
|
||||
new("nasal_proxy", "velum-controlled nasal resonator", "The tract voice has a velum/nasal output lane.")
|
||||
],
|
||||
[
|
||||
new("twice_rate_tract_stepping", "two tract steps per audio sample", "AquaSynth voice graphs do not express a subgraph with its own stepping rate."),
|
||||
new("exact_twice_rate_state_update", "two recursive tract state updates per audio sample", "AquaSynth records and uses substep intent, but Faust lowering still approximates intra-sample state updates with drive/loss scaling."),
|
||||
new("lf_glottal_exactness", "Pink Trombone LF-style coefficients", "The proxy uses an LF-like glottal source but does not yet match Pink Trombone coefficient updates exactly.")
|
||||
],
|
||||
"This is a deliberately non-passing rebuild: it keeps the current source-filter proxy parseable while naming the tract-DSP authority AquaSynth lacks.");
|
||||
|
|
|
|||
|
|
@ -136,3 +136,4 @@
|
|||
{"date":"2026-05-26","topic":"waveguide-cell-injection","lesson":"A positioned injection primitive is not enough; frication has to enter the traveling-wave section updates. AquaSynth waveguide lowering now computes a section-space injection cell and distributes injection pressure across oral waveguide right/left updates by position and width. Remaining PT DSP gaps are true substep execution, obstruction-history transients, diameter target slew, and rendered parity fixtures.","evidence":"Changed Faust waveguide lowering to split glottal excitation from tract injection pressure, emit `wg_injection_cell` and `wg_inject_N` terms, and add those terms to oral waveguide section and nasal-junction updates. Verification required disabling build servers/shared compilation/parallel build after normal dotnet builds hung: `dotnet build tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false -v minimal`; focused `dotnet test --no-build` passed 3 tests."}
|
||||
{"date":"2026-05-26","topic":"tract-motion-obstruction-history","lesson":"PT-style movement and release bursts are temporal DSP semantics, not preset flavor. AquaSynth now has `tract_motion` to own slew rates and obstruction threshold; waveguide lowering smooths tract controls and derives burst pressure from previous obstruction state opening into current non-obstruction. Remaining PT expression gaps are true two-steps-per-sample execution, dynamic per-section target diameter arrays, and rendered parity fixtures.","evidence":"Added `TractMotion`, parser command `tract_motion`, voice references/inline overrides, Faust `slew(rate,x)`, smoothed tongue/constriction/velum controls, `tract_prev_injection_close`, `tract_obstructed`, and `tract_release_memory` burst generation. Verification: no-hang build/test command with `--disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false`; focused tests passed 3."}
|
||||
{"date":"2026-05-26","topic":"dynamic-tract-reflection-field","lesson":"A PT-class waveguide cannot keep reflection coefficients as compile-time constants. AquaSynth waveguide lowering now derives live per-section diameter targets from `tract_shape`, tongue, constriction, and lip controls, slews them through `tract_motion`, squares them into section areas, and recomputes reflection coefficients and nasal junction areas as DSP expressions.","evidence":"Replaced static `wg_k_N` constants in `FaustExport.TractWaveguideExpression` with generated `wg_diameter_target_N`, `wg_diameter_N`, `wg_area_N`, and dynamic `wg_k_N` expressions. Focused no-hang build/test command passed 3 tests."}
|
||||
{"date":"2026-05-26","topic":"waveguide-substep-clock","lesson":"PT's twice-rate tract clock must be represented explicitly. AquaSynth already had `substeps`; lowering now consumes it through `wg_substep_drive` and `wg_substep_loss` so the field is not decorative. Exact intra-sample recursive state updates remain named debt until the Faust lowering or another backend can prove them.","evidence":"Added substep drive/loss scaling in `TractWaveguideExpression`, updated parity docs/reference metadata, and kept `exact_twice_rate_state_update` as missing. Focused no-hang build/test command passed 3 tests."}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ live_invariants:
|
|||
- The speech lane is the active center: IPA parity, utterance embeddings, synth-driver automation, rendered loss, and compiled Faust control sweeps take priority over new DX7/Zyn digestion.
|
||||
- Every learned speech arrow must stay inspectable: utterance metadata -> embedding, embedding plus phonetic features -> automation, automation -> rendered audio, rendered audio -> loss/gradient.
|
||||
- Pink Trombone parity is structural waveguide pressure, not permission to add more formant proxy knobs. AquaSynth now has a `tract` DSL command that lowers into `Voice.Tract` with PT-shaped runtime controls and a Faust tract-voice proxy; it is still an ordinary voice treatment, not a separate graph owner and not an exact 44-cell/28-cell bidirectional waveguide.
|
||||
- PT expressive parity should decompose into reusable Aqua primitives before audio golf: `tract_shape` owns section diameters/areas and derived reflection coefficients; `glottis` owns excitation controls; `tract_injection` owns positioned frication/burst controls and now distributes frication pressure into waveguide section updates; `nasal_branch` owns a second diameter/area tube; `tract_motion` owns control slew and obstruction threshold; `propagation=waveguide` lowers tract voices into generated oral/nasal right-left waveguide equations with live per-section diameter/area/reflection fields and obstruction-history burst pressure. Remaining low-level PT gaps are true two-steps-per-sample execution and rendered parity fixtures.
|
||||
- PT expressive parity should decompose into reusable Aqua primitives before audio golf: `tract_shape` owns section diameters/areas and derived reflection coefficients; `glottis` owns excitation controls; `tract_injection` owns positioned frication/burst controls and now distributes frication pressure into waveguide section updates; `nasal_branch` owns a second diameter/area tube; `tract_motion` owns control slew and obstruction threshold; `propagation=waveguide` lowers tract voices into generated oral/nasal right-left waveguide equations with live per-section diameter/area/reflection fields, obstruction-history burst pressure, and substep clock intent. Remaining low-level PT gaps are exact intra-sample recursive substep state updates and rendered parity fixtures.
|
||||
- If dotnet builds hang in this workspace, stop orphan dotnet workers and retry with `--disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false`; plain parallel/shared builds can stall without useful diagnostics.
|
||||
- eSpeak NG is an early parity reference for timing, IPA coverage, and log-mel pressure, not an anatomy oracle or final target.
|
||||
- Do not expand into prosody, emotional context, personality, or broad Weksa expressivity until the tiny IPA utterance harness can show useful loss movement.
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ public sealed class PatchScriptTests
|
|||
Assert.Contains("wg_inject_6", faust);
|
||||
Assert.Contains("wg_diameter_target_3", faust);
|
||||
Assert.Contains("wg_area_3", faust);
|
||||
Assert.Contains("wg_substep_drive", faust);
|
||||
Assert.Contains("wg_substep_loss", faust);
|
||||
Assert.Contains("tract_release_memory", faust);
|
||||
Assert.Contains("tract_obstructed", faust);
|
||||
Assert.Contains("slew(", faust);
|
||||
|
|
@ -383,6 +385,8 @@ public sealed class PatchScriptTests
|
|||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "tract_shape_motion");
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "diameter_authority");
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "closure_transients");
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "tract_sample_rate");
|
||||
Assert.Contains(rebuild.MissingFeatures, feature => feature.Name == "exact_twice_rate_state_update");
|
||||
Assert.NotEmpty(patch.TractShapes);
|
||||
Assert.NotEmpty(patch.GlottalSources);
|
||||
Assert.NotEmpty(patch.TractInjections);
|
||||
|
|
|
|||
Loading…
Reference in a new issue