Compact generated tract graphs
This commit is contained in:
parent
04f08e1fe7
commit
72ae6cafdb
5 changed files with 63 additions and 11 deletions
|
|
@ -120,6 +120,41 @@ 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`.
|
||||
|
||||
## 2026-05-29 Compact Generated Tract Graph
|
||||
|
||||
Owner: generated `tract` authoring owns the translation from high-resolution
|
||||
tract morphology into a Faust-sized graph. `VocalTract.Sections` still owns
|
||||
morphology and index semantics; generated acoustic terminals/source ports own
|
||||
only the compiled approximation.
|
||||
|
||||
Inputs: full tract area function, tract section count, live tongue/constriction
|
||||
and lip controls, nasal branch position, and injection position/width.
|
||||
|
||||
Outputs: at most ten generated oral area junctions, at most eight generated
|
||||
injection source ports, and at most four generated contact terminals. Source
|
||||
position weighting is widened to the compact source spacing so a moving
|
||||
constriction remains represented without one source port per tract cell.
|
||||
|
||||
Derived state: generated `voices_0_area_*` and `voices_0_inj_*` records are no
|
||||
longer a one-record-per-section discretization. They are backend lowering
|
||||
samples of the continuous tract owner.
|
||||
|
||||
Forbidden writers: the Faust backend must not be forced to compile the full
|
||||
authoring grid. If a future change needs more articulatory precision, it should
|
||||
improve the continuous area/source/contact heuristics or add a named compact
|
||||
primitive, not restore a 44-node/43-source generated graph by default.
|
||||
|
||||
Evidence: latest opt-in thrombosis debug DSP
|
||||
`artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T125048965`
|
||||
dropped from `1652` lines to `532`: source-related graph lines `687 -> 162`,
|
||||
segment-area lines `211 -> 56`, next-state lines `89 -> 27`, and area-reflection
|
||||
lines `74 -> 12`. The opt-in native graph probe passed in about `1m15s`.
|
||||
Accepted utterance diagnostic
|
||||
`artifacts/parity/pink-trombone-utterance-logmel/20260529T125227926` passed in
|
||||
about `1m33s`; `mama/papa/thrombosis` cosine is `0.8324/0.7730/0.3176`.
|
||||
Thrombosis RMS improved to `0.1715`, but active ratio fell to `0.1598` and
|
||||
silence mismatch rose to `0.7811`, so articulation remains the live failure.
|
||||
|
||||
## Component Audit
|
||||
|
||||
### Topology Construction
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ public sealed class PatchScriptException(int line, string message) : Exception($
|
|||
}
|
||||
public static class PatchScript
|
||||
{
|
||||
private const int GeneratedTractInteriorTerminalLimit = 10;
|
||||
private const int GeneratedTractInjectionSourceLimit = 8;
|
||||
private const int GeneratedTractContactLimit = 4;
|
||||
|
||||
public static SynthPatch Parse(string script)
|
||||
{
|
||||
var compiler = new Compiler();
|
||||
|
|
@ -1425,7 +1429,8 @@ public static class PatchScript
|
|||
MirrorParameterBinding(OwnerField(tractPath, "glottal_reflection"), $"/acoustic/terminals/{_acousticTerminals.Count - 1}/reflection");
|
||||
sourceNames.Add(sourceName);
|
||||
|
||||
for (var section = 1; section < Math.Max(2, tract.Sections - 1); section++)
|
||||
var areaSections = SparseSectionRange(1, tract.Sections - 2, GeneratedTractInteriorTerminalLimit);
|
||||
foreach (var section in areaSections)
|
||||
{
|
||||
var position = section / (float)Math.Max(1, tract.Sections - 1);
|
||||
var terminal = new AcousticTerminal(
|
||||
|
|
@ -1460,13 +1465,16 @@ public static class PatchScript
|
|||
{
|
||||
var injectionName = string.IsNullOrWhiteSpace(injection.Name) ? $"{prefix}_injection" : $"{prefix}_{injection.Name}";
|
||||
var sourceWidth = Math.Max(0.25f, injection.Width);
|
||||
var sourceSections = SparseSectionRange(1, tract.Sections - 1, GeneratedTractInjectionSourceLimit);
|
||||
var sourceSectionSpacing = tract.Sections / (float)Math.Max(1, sourceSections.Count);
|
||||
var sourceControlWidth = Math.Max(sourceWidth, sourceSectionSpacing * 1.15f);
|
||||
var sourceIndexScale = tract.IndexScale / Math.Max(1, tract.Sections);
|
||||
for (var section = 1; section < tract.Sections; section++)
|
||||
foreach (var section in sourceSections)
|
||||
{
|
||||
AddTractInjectionSource(
|
||||
$"{injectionName}_{section}",
|
||||
(section + 0.5f) / Math.Max(1, tract.Sections),
|
||||
new AcousticSourcePositionControl(tract.ConstrictionIndex, sourceWidth, sourceIndexScale));
|
||||
new AcousticSourcePositionControl(tract.ConstrictionIndex, sourceControlWidth, sourceIndexScale));
|
||||
}
|
||||
|
||||
void AddTractInjectionSource(string name, float position, AcousticSourcePositionControl? positionControl)
|
||||
|
|
@ -1607,24 +1615,27 @@ public static class PatchScript
|
|||
sections <= 0 ? 0 : Math.Clamp(index / Math.Max(1, sections - 1), 0, 1);
|
||||
|
||||
private static IReadOnlyList<int> SparseContactSections(int sections)
|
||||
=> SparseSectionRange(1, sections - 2, GeneratedTractContactLimit);
|
||||
|
||||
private static IReadOnlyList<int> SparseSectionRange(int first, int last, int maxCount)
|
||||
{
|
||||
var interior = Math.Max(0, sections - 2);
|
||||
if (interior == 0)
|
||||
if (last < first || maxCount <= 0)
|
||||
{
|
||||
return Array.Empty<int>();
|
||||
}
|
||||
|
||||
var contactCount = Math.Min(4, interior);
|
||||
if (contactCount == 1)
|
||||
var available = last - first + 1;
|
||||
var count = Math.Min(maxCount, available);
|
||||
if (count == 1)
|
||||
{
|
||||
return [1];
|
||||
return [first];
|
||||
}
|
||||
|
||||
var result = new SortedSet<int>();
|
||||
for (var i = 0; i < contactCount; i++)
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var section = 1 + (int)MathF.Round(i * (interior - 1) / (float)(contactCount - 1));
|
||||
result.Add(Math.Clamp(section, 1, sections - 2));
|
||||
var section = first + (int)MathF.Round(i * (available - 1) / (float)(count - 1));
|
||||
result.Add(Math.Clamp(section, first, last));
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
|
|
|
|||
|
|
@ -221,3 +221,4 @@
|
|||
{"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"}
|
||||
{"date":"2026-05-29","area":"acoustic-graph","summary":"Generated tract lowering now compacts the Faust graph: declared morphology sections remain intact, but generated oral area terminals are capped at 10, generated injection source ports at 8, and generated contacts at 4. The thrombosis debug DSP dropped from 1652 to 532 lines, source graph lines from 687 to 162, and next-state lines from 89 to 27; opt-in native debug compile/render and accepted utterance diagnostics pass faster. Metrics confirm this is a backend coherence cut, not articulation parity: thrombosis RMS improved to 0.1715 but active ratio/silence mismatch remain bad at 0.1598/0.7811.","evidence":"docs/acoustic-graph-dsp-audit.md; artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T125048965; artifacts/parity/pink-trombone-utterance-logmel/20260529T125227926"}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ live_invariants:
|
|||
- `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 `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`.
|
||||
- Generated PT-style `tract` lowering now compacts the Faust graph without changing user-facing morphology resolution: `VocalTract.Sections` and `AcousticPath.AreaFunction.Sections` still preserve the declared tract grid, but generated oral area terminals are capped at 10, generated moving injection source ports at 8, and generated contacts at 4. Source position weighting widens to the compact source spacing so the source bank approximates continuous placement instead of restoring one port per cell. Latest debug evidence `artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T125048965` drops generated debug DSP size from 1652 to 532 lines, with source graph lines 687->162 and next-state lines 89->27; accepted utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260529T125227926` still fails articulation, so this is backend coherence/speed, not parity victory.
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -548,6 +548,10 @@ public sealed class PatchScriptTests
|
|||
Assert.Equal(32, tract.Injection?.Position);
|
||||
Assert.Equal(1, tract.Injection?.Width);
|
||||
Assert.Equal(WaveClockDelayStrategy.FractionalLinear, Assert.Single(patch.WaveClocks).Strategy);
|
||||
Assert.Equal(10, patch.AcousticTerminals.Count(terminal => terminal.Name.StartsWith("voices_0_area_", StringComparison.Ordinal)));
|
||||
Assert.Equal(4, patch.AcousticTerminals.Count(terminal => terminal.Name.StartsWith("voices_0_contact_", StringComparison.Ordinal)));
|
||||
Assert.Equal(8, patch.AcousticSourcePorts.Count(port => port.Name.StartsWith("voices_0_inj_", StringComparison.Ordinal)));
|
||||
Assert.Equal(44, patch.AcousticPaths.Single(path => path.Name == "voices_0_oral").AreaFunction.Sections);
|
||||
|
||||
var faust = FaustEmitter.Emit(patch, new FaustExportOptions("graph_declared_grid")).Source;
|
||||
Assert.Contains("graph_loop ~ si.bus", faust);
|
||||
|
|
|
|||
Loading…
Reference in a new issue