Use half-sample graph tract clock

This commit is contained in:
Meta Vens 2026-05-27 14:38:32 +01:00
commit 67581dd2fa
9 changed files with 67 additions and 6 deletions

View file

@ -163,6 +163,15 @@ For very short sections below a stable allpass delay bound, either:
- use a half-sample KL structure where the delay model is explicitly designed
for that regime.
The graph lowering now follows that rule. Segment delay is derived from
physical length without forcing a one-sample minimum; the wave-clock strategy
owns the legal lower bound. Generated graph tracts use `HalfSampleGrid`, which
matches the PT/Kelly-Lochbaum half-sample regime for roughly 44 oral samples
over a 17 cm tract. A canonical reflection-sign retest was rejected because
Aqua's current right/left port ordering already encodes the opposite local
orientation; changing the sign made `thrombosis` unstable/loud instead of more
speech-like.
### Separate Fast Training From Final Rendering
Aqua should support at least two lowerings from the same acoustic morphology:

View file

@ -322,6 +322,19 @@ and an articulation score. The utterance report prints a verdict. Latest run:
`artifacts/parity/pink-trombone-utterance-logmel/20260527T130026316`, despite
their old cosine values. That is the harness telling the truth again.
The next physically grounded cut fixed graph tract timing. Segment delay is no
longer clamped to a full sample before wave-clock lowering; the wave-clock
strategy owns the minimum legal delay. Generated `tract propagation=graph`
patches now use `HalfSampleGrid`, matching the half-sample Kelly-Lochbaum
regime that PT approximates by updating the tract twice per audio sample. This
removed an accidental overlong-tract crutch and improved several witnesses,
especially `thrombosis` motor-band ratio. Latest accepted utterance smoke is
`mama` 0.5982 / articulation 0.1952, `papa` 0.8615 / 0.1979, and `thrombosis`
0.5217 / 0.2640 under
`artifacts/parity/pink-trombone-utterance-logmel/20260527T133539554`. The
voice is still not accepted; the remaining failure is body/radiation and
pressure-release behavior, not tract length.
## Cut Line
The current `Voice.Tract` scalar proxy may survive only as an adapter over real

View file

@ -101,13 +101,17 @@ Current AquaSynth can express:
Current AquaSynth cannot exactly express:
- exact Pink Trombone wall update timing and obstruction history;
- exact Pink Trombone obstruction history;
- fully continuous area modulation inside a delay segment between graph sample
points. Generated tract graphs now sample live area along the path, but the
delay segment between two sampled nodes remains uniform.
- exact Pink Trombone block timing and source/noise/transient behavior inside
the Faust-lowered Aqua graph.
Graph tract timing is no longer intentionally overlong: physical segment delay
can be sub-sample, and generated graph tracts use a half-sample clock rather
than clamping every segment to one sample.
## Reference Renderer Authority
`PinkTromboneReferenceRenderer` is a source-port of the pinned MIT TypeScript

View file

@ -1536,7 +1536,7 @@ public static class FaustEmitter
private static string SegmentDelayExpression(AcousticGraphSegment segment)
{
var segmentLengthMeters = Math.Max(0.000001f, (segment.EndPosition - segment.StartPosition) * segment.Path.AreaFunction.LengthMeters);
return $"max(1.0, {F(segmentLengthMeters / segment.Path.PropagationSpeedMetersPerSecond)} * ma.SR)";
return $"max(0.000001, {F(segmentLengthMeters / segment.Path.PropagationSpeedMetersPerSecond)} * ma.SR)";
}
private static int SegmentMaxDelay(AcousticGraphSegment segment, WaveClockPolicy waveClock)
@ -1547,7 +1547,7 @@ public static class FaustEmitter
private static string WaveClockDelayExpression(WaveClockPolicy waveClock, int maxDelay, string delayExpression)
{
var delay = $"min({F(maxDelay - 1)}, max(1.0, {delayExpression}))";
var delay = $"min({F(maxDelay - 1)}, max({F(WaveClockMinimumDelay(waveClock))}, {delayExpression}))";
return waveClock.Strategy switch
{
WaveClockDelayStrategy.UnitGrid => $"de.delay({maxDelay}, int({delay}))",
@ -1559,6 +1559,17 @@ public static class FaustEmitter
};
}
private static float WaveClockMinimumDelay(WaveClockPolicy waveClock) =>
waveClock.Strategy switch
{
WaveClockDelayStrategy.UnitGrid => 1.0f,
WaveClockDelayStrategy.HalfSampleGrid => 0.5f,
WaveClockDelayStrategy.FractionalThiran => Math.Clamp(waveClock.FractionalOrder, 1, 4) - 0.5f,
WaveClockDelayStrategy.FractionalLagrange => (Math.Clamp(waveClock.FractionalOrder, 1, 5) - 1) * 0.5f,
WaveClockDelayStrategy.CrossfadedVariable => (Math.Clamp(waveClock.FractionalOrder, 1, 5) - 1) * 0.5f,
_ => 0.000001f
};
private static string ConnectionPressureExpression(
string voiceName,
AcousticConnection connection,

View file

@ -1498,7 +1498,7 @@ public static class PatchScript
tract.Propagation == TractPropagationMode.Waveguide
? WaveClockDelayStrategy.UnitGrid
: tract.Propagation == TractPropagationMode.Graph
? WaveClockDelayStrategy.FractionalThiran
? WaveClockDelayStrategy.HalfSampleGrid
: WaveClockDelayStrategy.FractionalLinear));
var network = new AcousticPortNetwork(

View file

@ -176,3 +176,4 @@
{"evidence":"Added a small higher-harmonic brightness term to graph glottal source lowering after radiation-flow normalization exposed weak 1-2.5 kHz voiced energy. Focused utterance parity passed with artifacts/parity/pink-trombone-utterance-logmel/20260527T123628148: mama cosine 0.5705 RMS 0.3355, papa 0.8622 RMS 0.1326, thrombosis 0.4355 RMS 0.2504. Static parity passed with artifacts/parity/pink-trombone-logmel/20260527T123734700: open/front/nasal/ma/sibilant/closure cosine 0.6081/0.6823/0.6495/0.6162/0.4158/0.6193.","claim":"Glottal brightness belongs in the reusable source primitive, not in a PT-only post-filter. The tweak slightly improves voiced utterance body without destabilizing static fixtures, but it does not solve the remaining articulation failure.","timestamp":"2026-05-27T12:40:00+01:00","topic":"acoustic-glottal-brightness"}
{"evidence":"After switching graph radiation from raw pressure to boundary flow, recalibrated the graph radiation gain from 3.0 to 4.0. A 4.5 trial improved utterance RMS but pushed static fixtures closer to soft-clip influence, so 4.0 was kept. Focused utterance parity passed with artifacts/parity/pink-trombone-utterance-logmel/20260527T124230804: mama cosine 0.5712 RMS 0.4431, papa 0.8659 RMS 0.1768, thrombosis 0.4379 RMS 0.3324. Static parity passed with artifacts/parity/pink-trombone-logmel/20260527T124311305: open/front/nasal/ma/sibilant/closure cosine 0.5942/0.6710/0.6369/0.5955/0.4201/0.6143.","claim":"Radiation gain is part of the post-flow normalization contract, not a substitute for articulation. The kept value improves underpowered utterance output while preserving static guardrails; pressure storage/release remains the actual consonant problem.","timestamp":"2026-05-27T12:45:00+01:00","topic":"acoustic-radiation-flow-normalization"}
{"evidence":"Added `AudioArticulationComparison` to `AudioComparison` and updated Pink Trombone parity reports with envelope cosine, active-frame ratio, silence mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio, articulation score, and utterance verdicts. Focused tests passed. Latest utterance artifact artifacts/parity/pink-trombone-utterance-logmel/20260527T130026316 reports mama/papa/thrombosis as `not-accepted-articulation` with articulation scores 0.1930/0.1829/0.1848 despite log-mel cosine 0.5712/0.8659/0.4379. Static reports also expose weak speech-band ratios instead of hiding behind cosine.","claim":"Log-mel cosine is a spectral witness, not a speech-articulation authority. The parity harness must surface silence-map, envelope, flux, motor-band, and speech-band failures so filtered buzz cannot forge a listening receipt.","timestamp":"2026-05-27T13:05:00+01:00","topic":"voice-articulation-metric-witness"}
{"evidence":"Segment delay lowering no longer clamps physical acoustic graph segments to one sample before wave-clock lowering; `WaveClockPolicy` now owns the legal delay floor. Generated `tract propagation=graph` patches use `HalfSampleGrid` instead of first-order Thiran, matching the PT/Kelly-Lochbaum half-sample regime. Focused graph, Faust validation, utterance, and static parity tests passed. Latest utterance artifact artifacts/parity/pink-trombone-utterance-logmel/20260527T133539554: mama cosine/articulation 0.5982/0.1952, papa 0.8615/0.1979, thrombosis 0.5217/0.2640. Latest static artifact artifacts/parity/pink-trombone-logmel/20260527T133424894. Re-testing the literal SndKit reflection sign made thrombosis loud/unstable; adding a broader glottal harmonic tail worsened voiced-band behavior, so both were cut.","claim":"Physical tract length must lower to sub-sample graph delays; forcing each section to one sample made the tract acoustically overlong and hid failures behind a low resonance crutch. Half-sample graph timing is the correct next foundation, but articulation still fails because body/radiation and pressure-release behavior are incomplete.","timestamp":"2026-05-27T13:40:00+01:00","topic":"acoustic-graph-half-sample-clock"}

View file

@ -2,6 +2,26 @@
Current slice:
- Research-guided machine cut: graph segment delay no longer clamps physical
segment delay to a full sample. `WaveClockPolicy` now owns the minimum legal
delay: unit grid stays at 1 sample, half-sample grid and first-order Thiran
bottom at 0.5, and Lagrange/crossfaded policies use their documented lower
bound. Generated `tract propagation=graph` now uses `HalfSampleGrid` to match
PT's half-sample Kelly-Lochbaum regime instead of first-order Thiran.
- Rejected experiments in this pass:
- Flipping area-scatter reflection to the literal SndKit/PT sign made
`thrombosis` loud/unstable under Aqua's current port orientation, so the
previous local sign convention remains.
- Adding more high glottal harmonics worsened voiced-band behavior; the
failure is not a missing sparkle source.
- Verification:
`dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false --filter "TractVoiceCanLowerThroughAcousticGraph|ParserSupportsTypedAcousticPathGraph|FaustCompilerValidatesAcousticPathGraphWhenInstalled|PinkTromboneAcceptedUtterancesReportGraphLogMelParityWhenFaustIsInstalled|PinkTromboneFixturesReportLogMelParityWhenFaustIsInstalled" -v minimal`
passed. Latest utterance artifact:
`artifacts/parity/pink-trombone-utterance-logmel/20260527T133539554`;
`mama` cosine/articulation `0.5982/0.1952`, `papa` `0.8615/0.1979`,
`thrombosis` `0.5217/0.2640`. Latest static artifact:
`artifacts/parity/pink-trombone-logmel/20260527T133424894`.
- User listening correctly rejected the log-mel witness: `thrombosis` was
mostly silence plus breathy/squeaky bursts, and `papa`/`mama` still read as
phone-vibration buzz. `AudioComparison` now includes articulation diagnostics

View file

@ -120,7 +120,7 @@ live_invariants:
- Physical voice implementation has started moving PT pressure into neutral acoustic owners: `AcousticPath`, `AcousticTerminal`, `AcousticConnection`, `AcousticSourcePort`, `AcousticBranch`, `AcousticRadiationPort`, `WaveClockPolicy`, and `AcousticPortNetwork`. Parser commands `path`, `terminal`, `connect`, `source_port`, `branch`, `radiation_port`, `wave_clock`, and `acoustic_network` now exist. Existing `tract_shape`, `glottis`, `tract_injection`, `nasal_branch`, and `tract` commands populate acoustic records as compatibility aliases while the current `Voice.Tract` Faust renderer still owns audio.
- `acoustic` / `acoustic_voice` can now render an `AcousticPortNetwork` through a compact Faust response proxy or, when typed terminals are present, a compiled bidirectional Faust segment graph. Graph lowering splits paths at terminal positions, aggregates co-located terminals into graph nodes, orders connection ports deterministically, scatters area-weighted/pressure/bypass connection groups, injects source terminals even at connected nodes, and sums radiation terminals. Segment delay follows physical segment length and the network `wave_clock` strategy through unit-grid, half-sample, linear fractional, Lagrange, or Thiran/allpass Faust delay primitives. Source, branch, radiation, terminal, and connection controls can bind through ordinary Aqua parameters into emitted Faust. PT-shaped `tract` authoring now routes accepted parity through `propagation=graph`; the old waveguide parity lane is not a comparison baseline or historical pressure lane.
- Pink Trombone log-mel parity now writes graph candidates only. Generated tract graphs mirror live `/pink/...` tract bindings into acoustic source, terminal, connection, radiation, and path-area fields. Graph generation stopped baking first-frame tongue/constriction/lip morphology into the path; `AcousticPath.AreaControl` now turns tongue/constriction/lip controls into live area expressions at graph sample points. Velum owns nasal branch coupling/aperture, and the first nasal tube sample is no longer allowed to decide the velum opening. Graph radiation now uses aperture as a boundary impedance owner: lip/beak opening blends boundary reflection from near-closed termination to declared open-end reflection, and radiation reads boundary flow (`incoming - outgoing`) through a stronger radiation slope instead of raw incoming pressure. Latest static graph evidence: open/front/nasal/ma/sibilant/closure cosine 0.6088/0.6826/0.6468/0.6153/0.4158/0.6198.
- PT expressive parity should decompose into reusable Aqua primitives before audio golf: `tract_shape` owns continuous diameter/area geometry over normalized position, physical length (`length_cm`), interpolation/resampling, derived reflection coefficients, and per-cell acoustic delay; `AcousticAreaControl` owns live sampled morphology controls on a path; ordinary same-path graph interiors now own signed two-port area-discontinuity scattering; `glottis` owns excitation controls with mild harmonic waveshaping and a small higher-harmonic brightness term for graph sources; `tract_injection` owns positioned frication/burst controls; `AcousticSourcePort.Transient` owns graph-native release/click energy; sustained `TurbulenceJet` lowering owns a narrow-open aperture gate so closed constrictions no longer hiss; `AcousticSourcePositionControl` owns differentiable runtime weights over fixed source terminals so moving articulators can move source energy without recompiling the graph; `AcousticRadiationPort` owns aperture-dependent boundary reflection, flow radiation, and current post-flow normalization; `nasal_branch` owns a second diameter/area tube and branch aperture; `tract_motion` owns control slew and obstruction threshold; `AcousticPortNetwork` owns graph lowering. `PinkTromboneReferenceRenderer` ports the pinned MIT `chdh/pink-trombone-mod` DSP source (`external/pink-trombone-mod` at 359c2d3b42b10280404c1650dc601902112b4c90), and reference WAVs before `artifacts/parity/pink-trombone-utterances/20260526T211902375` are invalid. The accepted PT utterance curriculum is mama, papa, and thrombosis; lulek was cut because its control sketch taught a bad artifact. Latest graph-only utterance smoke under `artifacts/parity/pink-trombone-utterance-logmel/20260527T130026316`: mama/papa/thrombosis are all `not-accepted-articulation` with articulation scores 0.1930/0.1829/0.1848 even though log-mel cosine remains 0.5712/0.8659/0.4379. `AudioComparison.Articulation` now reports envelope cosine, active-frame ratio, silence mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio, and an articulation score so log-mel cannot forge a listening receipt. A direct LF coefficient source was tested and cut because it overdrove the graph and broke static open vowel. A naive midpoint admittance scatter was tested and cut because it collapsed `mama`; the kept area-discontinuity scatter needed the opposite reflection sign for this graph state convention. Next pressure is graph-native storage/release around severe constrictions plus nasal/oral branch coupling; output coloring alone is not allowed to impersonate articulation.
- PT expressive parity should decompose into reusable Aqua primitives before audio golf: `tract_shape` owns continuous diameter/area geometry over normalized position, physical length (`length_cm`), interpolation/resampling, derived reflection coefficients, and per-cell acoustic delay; `AcousticAreaControl` owns live sampled morphology controls on a path; `WaveClockPolicy` owns the legal delay floor instead of forcing every segment to one sample; generated graph tracts now use `HalfSampleGrid` to match the PT/Kelly-Lochbaum half-sample regime; ordinary same-path graph interiors now own signed two-port area-discontinuity scattering; `glottis` owns excitation controls with mild harmonic waveshaping and a small higher-harmonic brightness term for graph sources; `tract_injection` owns positioned frication/burst controls; `AcousticSourcePort.Transient` owns graph-native release/click energy; sustained `TurbulenceJet` lowering owns a narrow-open aperture gate so closed constrictions no longer hiss; `AcousticSourcePositionControl` owns differentiable runtime weights over fixed source terminals so moving articulators can move source energy without recompiling the graph; `AcousticRadiationPort` owns aperture-dependent boundary reflection, flow radiation, and current post-flow normalization; `nasal_branch` owns a second diameter/area tube and branch aperture; `tract_motion` owns control slew and obstruction threshold; `AcousticPortNetwork` owns graph lowering. `PinkTromboneReferenceRenderer` ports the pinned MIT `chdh/pink-trombone-mod` DSP source (`external/pink-trombone-mod` at 359c2d3b42b10280404c1650dc601902112b4c90), and reference WAVs before `artifacts/parity/pink-trombone-utterances/20260526T211902375` are invalid. The accepted PT utterance curriculum is mama, papa, and thrombosis; lulek was cut because its control sketch taught a bad artifact. Latest graph-only utterance smoke under `artifacts/parity/pink-trombone-utterance-logmel/20260527T133539554`: mama/papa/thrombosis remain `not-accepted-articulation` with articulation scores 0.1952/0.1979/0.2640, but the half-sample clock improves `thrombosis` motor-band behavior and removes an overlong-tract crutch. `AudioComparison.Articulation` now reports envelope cosine, active-frame ratio, silence mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio, and an articulation score so log-mel cannot forge a listening receipt. A direct LF coefficient source was tested and cut because it overdrove the graph and broke static open vowel; a broader harmonic-tail source was tested and cut because it worsened voiced-band behavior. A naive midpoint admittance scatter was tested and cut because it collapsed `mama`; the kept area-discontinuity scatter needed the opposite reflection sign for this graph state convention, and re-testing the literal SndKit sign after the clock fix made `thrombosis` loud/unstable. Next pressure is graph-native storage/release around severe constrictions plus nasal/oral branch coupling; output coloring alone is not allowed to impersonate articulation.
- 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.

View file

@ -258,6 +258,7 @@ public sealed class PatchScriptTests
Assert.Contains("graph_next_r", export.Source);
Assert.Contains("graph_next_l", export.Source);
Assert.Contains("de.fdelay", export.Source);
Assert.Contains("max(0.000001, 0.000", export.Source);
Assert.Contains("patch_param_0", export.Source);
}
@ -275,10 +276,12 @@ public sealed class PatchScriptTests
Assert.NotEmpty(network.Terminals);
Assert.Contains("voices_0_source", network.Terminals);
Assert.Contains("voices_0_lip", network.Terminals);
Assert.Equal(WaveClockDelayStrategy.HalfSampleGrid, Assert.Single(patch.WaveClocks).Strategy);
var export = FaustEmitter.Emit(patch, new FaustExportOptions("tract_graph"));
Assert.Contains("acoustic_graph_radiated", export.Source);
Assert.Contains("de.fdelay1a", export.Source);
Assert.Contains("de.fdelay", export.Source);
Assert.Contains("max(0.5,", export.Source);
Assert.DoesNotContain("tract_lf", export.Source);
}