From 04f08e1fe791f20e1daed21bb488e0b2ed958d56 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 29 May 2026 13:31:53 +0100 Subject: [PATCH] Limit generated tract contact owners --- docs/acoustic-graph-dsp-audit.md | 39 ++++++++++++++++-- src/AquaSynth.Core/PatchScript.cs | 40 +++++++++++++++++++ state/evidence.jsonl | 1 + state/spine.yaml | 2 +- tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs | 6 ++- 5 files changed, 82 insertions(+), 6 deletions(-) diff --git a/docs/acoustic-graph-dsp-audit.md b/docs/acoustic-graph-dsp-audit.md index d010741..d0dbd69 100644 --- a/docs/acoustic-graph-dsp-audit.md +++ b/docs/acoustic-graph-dsp-audit.md @@ -72,9 +72,10 @@ Outputs: load-aware source flow terms, named `_load_pressure` locals for non-turbulence sources, and named `graph_contact_*` closure/reservoir/release signals for contact terminals. -Derived state: generated tract area terminals are contact-capable graph -terminals. They still derive area from the live tract path geometry; they do -not become a separate morphology owner. +Derived state: generated tract area terminals are topology junctions that +derive area from the live tract path geometry; generated contact terminals are +a sparse overlay that owns closure pressure storage and release. Area samples +are no longer contact owners just because they define tube geometry. Forbidden writers: plosive release is not allowed to live only inside a TurbulenceJet source expression. A source-local burst can remain as excitation, @@ -87,6 +88,38 @@ kind, now load-aware through the same source impedance path. The checked-in bronchial paths, one tracheal path, a three-terminal area-scattering merge, and a beak radiation port. No species module was added. +## 2026-05-29 Sparse Contact Owner + +Owner: generated `tract` authoring owns a sparse contact overlay; graph path +lowering owns contact release injection through ordinary two-port scattering. + +Inputs: full area-junction grid for tube geometry, up to four generated contact +terminals spread across the tract interior, local terminal area, node incident +pressure, and upstream/downstream traveling waves. + +Outputs: named `graph_contact_*` closure/reservoir/release signals only at the +sparse contact owners, while every generated `voices_0_area_*` terminal remains +a `Junction` for geometry/scattering. + +Derived state: `voices_0_contact_*` is no longer a second morphology grid. It is +a small collision/pressure-storage overlay derived from tract section count. + +Forbidden writers: ordinary area terminals must not decide plosive contact +state. Inline contact occlusion in the scattering equation is not shipped; the +first attempt still overflowed the native Faust debug compiler at eight contact +owners, so contact count is capped at four until occlusion can be expressed as a +smaller compiled primitive or a lower-pressure diagnostic path. + +Evidence: opt-in thrombosis probe +`artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T122217729` +passes with lip-end contact closure peak `0.936876`, contact output peak +`0.547741`, lip boundary load `0.516196`, lip flow `0.585704`, and modal source +out `1.235997`. Accepted utterance diagnostic +`artifacts/parity/pink-trombone-utterance-logmel/20260529T122409541` still fails +articulation: `mama/papa/thrombosis` cosine `0.8302/0.7865/0.3218`, with +thrombosis RMS ratio `0.0770`, active ratio `0.3151`, and silence mismatch +`0.7205`. + ## Component Audit ### Topology Construction diff --git a/src/AquaSynth.Core/PatchScript.cs b/src/AquaSynth.Core/PatchScript.cs index 685bdae..2bbd827 100644 --- a/src/AquaSynth.Core/PatchScript.cs +++ b/src/AquaSynth.Core/PatchScript.cs @@ -1432,6 +1432,22 @@ public static class PatchScript $"{prefix}_area_{section}", primaryPathName, position, + AcousticTerminalKind.Junction, + "", + 1, + 0); + AddAcousticTerminalRecord(terminal); + terminalNames.Add(terminal.Name); + } + + var contactSections = SparseContactSections(tract.Sections); + foreach (var section in contactSections) + { + var position = section / (float)Math.Max(1, tract.Sections - 1); + var terminal = new AcousticTerminal( + $"{prefix}_contact_{section}", + primaryPathName, + position, AcousticTerminalKind.Contact, "", 1, @@ -1590,6 +1606,30 @@ public static class PatchScript private static float NormalizeTractIndex(float index, int sections) => sections <= 0 ? 0 : Math.Clamp(index / Math.Max(1, sections - 1), 0, 1); + private static IReadOnlyList SparseContactSections(int sections) + { + var interior = Math.Max(0, sections - 2); + if (interior == 0) + { + return Array.Empty(); + } + + var contactCount = Math.Min(4, interior); + if (contactCount == 1) + { + return [1]; + } + + var result = new SortedSet(); + for (var i = 0; i < contactCount; i++) + { + var section = 1 + (int)MathF.Round(i * (interior - 1) / (float)(contactCount - 1)); + result.Add(Math.Clamp(section, 1, sections - 2)); + } + + return result.ToArray(); + } + private void AddModBus(IReadOnlyDictionary fields, int line) { var name = GetAny(fields, ["name", "n"], "mod"); diff --git a/state/evidence.jsonl b/state/evidence.jsonl index e9c71c9..10a19b6 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -220,3 +220,4 @@ {"evidence":"Moved contact release out of the generic node source sum and into ordinary two-port path scattering. The new graph-owned closure primitive emits closure/reservoir/release/out probes and injects release directionally into downstream/upstream traveling waves. Latest opt-in thrombosis probe `artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T021934812`: lip contact out rose from prior 0.032413 to 0.548231, reservoir drive 0.519667, reservoir 6.024680, release 0.485894, lip radiation flow 0.581880, modal source out 1.233802, sibilant area-34 source out 0.099287. Latest accepted-utterance diagnostic `artifacts/parity/pink-trombone-utterance-logmel/20260529T022214016` still fails articulation: thrombosis RMS improves to 0.0761 but cosine stays about 0.331 and silence mismatch worsens to 0.7306. Focused structural graph tests, opt-in thrombosis probe, and accepted-utterance diagnostic passed under normal verbosity.","claim":"Closure pressure now participates in the graph path instead of acting as a decorative source pulse. This is the research-backed ownership cut, but it is not speech parity: contact release reaches radiation, while active-frame duration, output balance, and articulation shape remain weak. Next work should inspect contact filtering/duration or explicit utterance/radiation balance, not restore generic drive or global gain.","timestamp":"2026-05-29T02:30:00+01:00","topic":"graph-owned-closure-reservoir"} {"date":"2026-05-29","area":"acoustic-graph","summary":"After graph-owned closure release moved into two-port scattering, thrombosis probe energy reaches lip radiation but accepted utterance parity still fails. The missing contracts are not more release gain: boundary/source impedance remain heuristic rather than port laws, contact is still a scalar pulse instead of constrained-flow history, a node pressure fallback survives for unconnected multi-port nodes, and loss/gesture motion are not yet graph-owned enough.","evidence":"docs/acoustic-graph-missing-pieces-audit.md; artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T021934812; artifacts/parity/pink-trombone-utterance-logmel/20260529T022214016"} {"date":"2026-05-29","area":"acoustic-graph","summary":"Radiation boundaries now feed load back into the graph by filtering reflected boundary waves through a named boundary-load term before the outgoing wave returns to the delay line. The remaining unconnected multi-port node pressure fallback was cut and replaced with area-scattering plus node area-energy probes. Native PT probes still fail accepted utterance articulation, so this is a coherence repair: contact constrained-flow history and source impedance port laws remain the next missing owners.","evidence":"docs/acoustic-graph-dsp-audit.md; artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T112805836; artifacts/parity/pink-trombone-utterance-logmel/20260529T112806745"} +{"date":"2026-05-29","area":"acoustic-graph","summary":"Generated tract area terminals are geometry Junctions again, while generated contact terminals are a sparse four-section overlay that owns closure pressure storage/release. The eight-contact plus inline-occlusion trial overflowed native Faust debug compile; the four-contact release-only owner passes structural graph tests, the opt-in thrombosis graph probe, and accepted-utterance diagnostics. This exposes rather than fixes the remaining gap: only the lip-end contact closes in thrombosis, and accepted utterances still fail articulation/silence metrics.","evidence":"docs/acoustic-graph-dsp-audit.md; artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T122217729; artifacts/parity/pink-trombone-utterance-logmel/20260529T122409541"} diff --git a/state/spine.yaml b/state/spine.yaml index 29f2f1c..b7151b6 100644 --- a/state/spine.yaml +++ b/state/spine.yaml @@ -128,7 +128,7 @@ live_invariants: - Graph voice repair pass on 2026-05-28 fixed four model-level ownership leaks: glottal excitation is source-normalized with local `tanh`; segment propagation loss is area-dependent so closures absorb instead of ringing forever; generated nasal side-port area maps velum diameter squared directly instead of through the old hidden 0.15 damper; generated tract lips use live path-end geometry as the aperture owner and no longer double-gate radiation opening/boundary flow. Utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T070759312` passes smoke and no longer collapses `mama/papa` (`referenceLogMel=0.3791`, `candidateLogMel=0.3651`); `thrombosis` improves to cosine 0.4366 but remains weak/quiet. Static artifact `artifacts/parity/pink-trombone-logmel/20260528T071240442` passes with `closure-release` demoted to sealed-static evidence (`peak=0.000005`). Next pressure is true graph-native closure reservoir/release through the scattering path. - `docs/acoustic-graph-dsp-audit.md` is the current Jenga audit for graph voice DSP. It says the path graph, typed terminals, KL/PT two-port scatter, source/topology separation, velum side-port area, generated lip geometry ownership, area-dependent segment loss, and utterance separability should stay. The first audit cut replaced the generic connection fallback pressure/admittance blend and PT-shaped three-port branch recognizer with one passive N-port area-scattering law for all non-bypass connection groups; emitted Faust no longer contains `graph_connection_pressure_*` for graph connections. The second audit cut moved graph radiation color/admittance into one named local primitive with emitted `graph_radiation_reference_area_*`, `graph_radiation_differentiation_*`, `graph_radiation_highpass_*`, `graph_radiation_admittance_*`, and `graph_radiation_flow_*` signals instead of inline `30/70` and `area + 1.0` expressions. The third audit cut hoisted graph turbulence/release into named per-source `_closure`, `_release`, `_reservoir`, `_reservoir_drive`, `_pressure_drive`, `_release_pressure`, `_noise_gate`, `_release_gate`, `_sustained`, `_burst_noise`, and `_release_pulse` locals. Reservoir charging now uses directed upstream-minus-downstream pressure for ordinary two-port path nodes and positive local incident pressure as fallback; latest artifacts `artifacts/parity/pink-trombone-utterance-logmel/20260528T115452268` and `artifacts/parity/pink-trombone-logmel/20260528T115719882` still show no metric movement. This points away from source-local reservoir charging and toward missing contact-aware closure state that participates in scattering/flow. Remaining suspect owners are unowned segment loss constant, still-empirical radiation/source constants, nearest-node source attachment, and missing contact-aware closure state. - Graph source lowering now names per-node incident pressure and node-local source sums inside the recursive graph loop. Generated tract injection source banks now use cell-centered positions and `1 / sections` index scaling, so turbulence/release energy is born inside tube cells rather than directly on graph/radiation nodes. Latest source-position artifact under `artifacts/parity/pink-trombone-utterance-logmel/20260527T172949293`: mama/papa/thrombosis cosine 0.6654/0.7746/0.5522, rmsRatio 3.0010/2.5712/2.4943, articulation 0.1444/0.1177/0.1193. Static closure-release RMS improved to 1.0078 under `artifacts/parity/pink-trombone-logmel/20260527T172843945`, but papa articulation worsened; keep the cell-centered source placement because it is the correct graph-level source geometry, not because metrics liked it. - - Source ports now have an explicit `impedance`/`load` control and graph lowering uses node incident pressure to make glottal, labial/syrinx, reed, click, synthetic, and turbulence sources load-aware. Generated tract area terminals are now `Contact` terminals, with named graph-owned `graph_contact_*_closure`, `_reservoir`, and `_release` signals so closure pressure storage is no longer only a turbulence-source trick. `patches/advanced/bird-syrinx.aqua` is the first checked-in differentiable syrinx patch: paired labial source ports, bronchial paths, a tracheal path, an area-scattering merge, and beak radiation, all through `AcousticPortNetwork`. + - Source ports now have an explicit `impedance`/`load` control and graph lowering uses node incident pressure to make glottal, labial/syrinx, reed, click, synthetic, and turbulence sources load-aware. Generated tract area terminals are `Junction` geometry owners again; generated `voices_0_contact_*` terminals are a sparse contact overlay capped at four interior sections, with named graph-owned `graph_contact_*_closure`, `_reservoir`, and `_release` signals so closure pressure storage is no longer either every area sample or only a turbulence-source trick. Eight generated contacts plus inline occlusion overflowed the native debug Faust compile; four sparse contacts pass the opt-in thrombosis graph probe. `patches/advanced/bird-syrinx.aqua` is the first checked-in differentiable syrinx patch: paired labial source ports, bronchial paths, a tracheal path, an area-scattering merge, and beak radiation, all through `AcousticPortNetwork`. - TurbulenceJet now includes a release-timed burst-noise component in addition to the deterministic pressure pulse, but a stronger trial barely moved papa; the source is not the main remaining articulation bottleneck. Graph radiation now mixes 30% raw boundary flow with 70% high-passed flow after local admittance weighting, down from the over-bright 15/85 blend. Latest radiation/source artifact under `artifacts/parity/pink-trombone-utterance-logmel/20260527T174236049`: mama/papa/thrombosis cosine 0.6578/0.7692/0.5585, rmsRatio 3.0006/2.6153/2.3488, articulation 0.1437/0.1173/0.1191. Static `closure-release` under `artifacts/parity/pink-trombone-logmel/20260527T174139661` has rmsRatio 0.9900 and speechBandRatio 0.0842. This is still not speaking; next pressure is true tract resonance/passive scattering and pressure storage, not more burst gain. - Same-path two-port area discontinuities now emit `graph_area_energy_in_*` and `graph_area_energy_out_*` probes alongside the three-port branch `graph_connection_energy_*` probes. Use these to inspect passivity and formant-body failures at the scatter sites rather than judging only final output loudness. - `FaustExportOptions.DebugProbeUi` wraps graph probe signals in Faust `vbargraph` UI controls under `/debug/...` without changing audio output arity. Native streaming Faust patches now separate writable controls from read-only probe zones and can read/snapshot probe values after `ProcessBlock`; use this for acoustic graph passivity/radiation inspection, leave it off for normal parity renders. diff --git a/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs b/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs index 96fd53e..a732593 100644 --- a/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs +++ b/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs @@ -133,7 +133,9 @@ public sealed class PatchScriptTests Assert.Contains(patch.AcousticSourcePorts, port => port.Name.StartsWith("voices_0_sibilant_", StringComparison.Ordinal) && port.Kind == AcousticSourceKind.TurbulenceJet); Assert.Contains(patch.AcousticBranches, branch => branch.Name == "voices_0_nose" && branch.Kind == AcousticBranchKind.Nasal); Assert.Contains(patch.AcousticRadiationPorts, port => port.Name == "voices_0_lip" && port.Kind == AcousticRadiationKind.Lip); - Assert.Contains(patch.AcousticTerminals, terminal => terminal.Name.StartsWith("voices_0_area_", StringComparison.Ordinal) && terminal.Kind == AcousticTerminalKind.Contact); + Assert.Contains(patch.AcousticTerminals, terminal => terminal.Name.StartsWith("voices_0_area_", StringComparison.Ordinal) && terminal.Kind == AcousticTerminalKind.Junction); + Assert.Contains(patch.AcousticTerminals, terminal => terminal.Name.StartsWith("voices_0_contact_", StringComparison.Ordinal) && terminal.Kind == AcousticTerminalKind.Contact); + Assert.True(patch.AcousticTerminals.Count(terminal => terminal.Name.StartsWith("voices_0_contact_", StringComparison.Ordinal)) <= 4); Assert.Contains(patch.WaveClocks, clock => clock.Name == "voices_0_clock" && clock.Strategy == WaveClockDelayStrategy.FractionalLinear); Assert.Contains(patch.ParameterBindings, binding => binding.FieldPath == "/glottis/0/tenseness"); @@ -144,7 +146,7 @@ public sealed class PatchScriptTests Assert.Contains("graph_source_voices_0_sibilant_", faust); Assert.Contains("graph_terminal_area_voices_0_area_", faust); Assert.Contains("graph_connection_reflection_voices_0_nose_connection", faust); - Assert.Contains("graph_contact_voices_0_area_", faust); + Assert.Contains("graph_contact_voices_0_contact_", faust); Assert.Contains("* 0.80", faust); Assert.Contains("* 0.20", faust); Assert.Contains("de.fdelay", faust);