Name graph closure source reservoir

This commit is contained in:
Meta Vens 2026-05-28 12:48:09 +01:00
commit 57a62aa035
5 changed files with 90 additions and 13 deletions

View file

@ -198,19 +198,25 @@ transient.
Current state: partially coherent. Sustained turbulence now has a local
pressure-drive term, and release pressure can observe node incident pressure.
Graph turbulence lowering now emits named closure, release, reservoir,
pressure-drive, gate, burst, and release-pulse locals instead of hiding the
whole pressure-flow model inside one source expression.
Smells:
- The aperture gate constants `0.85`, `0.04`, source gains `0.62`, `1.2`,
`0.58`, and pressure-drive `0.50 + 1.50 * pressure` are not owned by a named
physical model.
- Release still detects local control opening, not stored upstream pressure
behind a sealed constriction.
- There is no graph-native reservoir state.
physical model. They are now at least localized in graph source lowering.
- Release still detects local control opening and stores pressure at the source
owner, not as a true two-sided constriction/contact state inside the scatter
graph.
- There is no explicit upstream/downstream reservoir split across a sealed
constriction.
Verdict: keep as a pressure-informed source, but do not call this plosive
modeling yet. The next real primitive is closure reservoir state connected to
the scattering graph.
Verdict: keep as a named pressure-informed source. Do not call this plosive
modeling yet. The next real primitive is a closure/contact owner that can store
pressure on one side of a severe constriction and release it through the
scattering graph.
### Segment Loss
@ -388,3 +394,32 @@ with peak `0.000011`.
The conclusion is plain: radiation needed an owner, but the voice still needs
graph-native closure pressure storage and source impedance before it will stop
leaning on boundary color.
## 2026-05-28 Graph Source Reservoir Naming
The third audit cut did not change the intended source math. It moved graph
turbulence/release lowering out of one dense expression and into emitted
per-source locals:
- `_closure`
- `_release`
- `_reservoir`
- `_pressure_drive`
- `_release_pressure`
- `_noise_gate`
- `_release_gate`
- `_sustained`
- `_burst_noise`
- `_release_pulse`
Latest utterance artifact:
`artifacts/parity/pink-trombone-utterance-logmel/20260528T114618213`.
Latest static artifact:
`artifacts/parity/pink-trombone-logmel/20260528T114618202`.
Metrics match the prior radiation-primitive pass, which is the expected result:
this was an ownership/observability cut, not a gain change.
The next change can now target the reservoir/source-impedance law directly.
The remaining bad smell is not that the source code is unreadable; it is that
the model still has only a source-local reservoir instead of a contact-aware
upstream/downstream pressure state.

View file

@ -721,8 +721,9 @@ public static class FaustEmitter
: $"({string.Join(" + ", ports.Select(port => GraphIncoming(name, port)))}) / {F(ports.Count)}";
var incidentName = $"{name}_graph_node_incident_pressure_{node.Name}";
var nodeSourceName = NodeSourceIdentifier(name, node);
var nodeSourceExpression = EmitNodeSourceExpression(source, patch, name, node, sources, frequency, parameters);
source.AppendLine($" {incidentName} = {ProbeSignal(options, $"/debug/{name}/node/{node.Name}/incident_pressure", incidentPressure, -2, 2)};");
source.AppendLine($" {nodeSourceName} = {ProbeSignal(options, $"/debug/{name}/node/{node.Name}/source", NodeSourceExpression(patch, name, node, sources, frequency, parameters), -2, 2)};");
source.AppendLine($" {nodeSourceName} = {ProbeSignal(options, $"/debug/{name}/node/{node.Name}/source", nodeSourceExpression, -2, 2)};");
debugProbeKeepAlive.Add(incidentName);
debugProbeKeepAlive.Add(nodeSourceName);
}
@ -1794,7 +1795,8 @@ public static class FaustEmitter
private static string NodeSourceIdentifier(string voiceName, AcousticGraphNode node) =>
$"{voiceName}_graph_node_source_{node.Name}";
private static string NodeSourceExpression(
private static string EmitNodeSourceExpression(
StringBuilder source,
SynthPatch patch,
string voiceName,
AcousticGraphNode node,
@ -1809,13 +1811,49 @@ public static class FaustEmitter
var sourceName = terminal.Port.Length == 0 ? terminal.Name : terminal.Port;
var port = sources[sourceName];
return port.Kind == AcousticSourceKind.TurbulenceJet
? AcousticSourceExpression(patch, port, frequency, parameters, $"{voiceName}_graph_node_incident_pressure_{node.Name}")
? EmitGraphTurbulenceSourceExpression(source, patch, voiceName, node, sourceName, port, parameters)
: $"{voiceName}_graph_source_{SafeIdentifier(sourceName)}";
})
.ToList();
return sourceTerms.Count == 0 ? "0.0" : string.Join(" + ", sourceTerms);
}
private static string EmitGraphTurbulenceSourceExpression(
StringBuilder source,
SynthPatch patch,
string voiceName,
AcousticGraphNode node,
string sourceName,
AcousticSourcePort port,
ParameterMap parameters)
{
var index = AcousticSourcePortIndex(patch, port);
var path = $"/acoustic/sources/{index}";
var safeSource = SafeIdentifier(sourceName);
var safeNode = SafeIdentifier(node.Name);
var prefix = $"{voiceName}_graph_source_{safeSource}_{safeNode}";
var localPressure = $"{voiceName}_graph_node_incident_pressure_{node.Name}";
var pressure = $"clip01({parameters.Expression(OwnerField(path, "pressure"), port.Pressure)})";
var opening = $"max(0.0, {parameters.Expression(OwnerField(path, "opening"), port.Opening)})";
var noise = $"clip01({parameters.Expression(OwnerField(path, "noise"), port.Noise)})";
var transient = $"clip01({parameters.Expression(OwnerField(path, "transient"), port.Transient)})";
var balance = $"({parameters.Expression(OwnerField(path, "balance"), port.Balance)}) * ({SourcePositionWeightExpression(port, path, parameters)})";
source.AppendLine($" {prefix}_closure = clip01((0.12 - ({opening})) / 0.12);");
source.AppendLine($" {prefix}_release = (max(0.0, ({prefix}_closure : mem) - {prefix}_closure) : + ~ *(0.995));");
source.AppendLine($" {prefix}_reservoir = (abs({localPressure}) * {prefix}_closure) : + ~ *(0.992);");
source.AppendLine($" {prefix}_pressure_drive = 0.50 + 1.50 * clip01(abs({localPressure}));");
source.AppendLine($" {prefix}_release_pressure = 0.20 + 2.80 * clip01({prefix}_reservoir);");
source.AppendLine($" {prefix}_noise_gate = min(clip01((0.85 - ({opening})) / 0.85), clip01(25.0 * (({opening}) - 0.04)));");
source.AppendLine($" {prefix}_release_gate = clip01(({opening}) / 0.8) * {prefix}_release;");
source.AppendLine($" {prefix}_turbulence = no.noise : fi.highpass(2, 900.0 + 2600.0 * clip01(1.0 - ({opening})));");
source.AppendLine($" {prefix}_sustained = {prefix}_turbulence * {noise} * {pressure} * {prefix}_pressure_drive * {prefix}_noise_gate * 0.62;");
source.AppendLine($" {prefix}_burst_noise = {prefix}_turbulence * {noise} * {transient} * {prefix}_pressure_drive * {prefix}_release_gate * 1.2;");
source.AppendLine($" {prefix}_release_pulse = {transient} * {pressure} * {prefix}_release * {prefix}_release_pressure * 0.58 * (0.8 + 0.8 * clip01(({opening}) / 0.8));");
source.AppendLine($" {prefix}_out = ({prefix}_sustained + {prefix}_burst_noise + {prefix}_release_pulse) * {balance};");
return $"{prefix}_out";
}
private static string GraphIncoming(string voiceName, AcousticGraphPort port) =>
port.AtStart
? $"{voiceName}_graph_in_{port.Segment.Index}_start"

View file

@ -198,3 +198,4 @@
{"evidence":"Added `docs/acoustic-graph-dsp-audit.md`, auditing each graph-voice component by asking what it simulates and whether the implementation makes sense. The audit keeps path graphs with typed terminals, KL/PT two-port and nasal branch scatter, source/topology separation, velum side-port area, generated lip geometry ownership, area-dependent segment loss, and utterance separability. It marks the generic connection fallback pressure/admittance blend, radiation color/admittance constants, the segment loss constant, turbulence/release scalar constants, and nearest-node source attachment as Jenga. It recommends replacing the generic fallback with a passive general N-port junction, adding graph-native closure reservoir state, and replacing radiation constants with a named radiation impedance primitive.","claim":"The current graph voice is viable but not clean. Further tuning should start by cutting unowned fallback laws and constants, not by adding more source gain or metric-specific compensation.","timestamp":"2026-05-28T07:45:00+01:00","topic":"acoustic-graph-dsp-audit"}
{"evidence":"Replaced the graph connection fallback in `FaustExport` with one general passive N-port area-scattering law for all non-bypass acoustic connections, removed the PT-shaped three-port branch recognizer, and updated structural tests to assert `graph_connection_reflection_*`/`graph_connection_energy_*` instead of `graph_connection_pressure_*`. Focused structural, utterance, and static PT graph tests passed. Latest utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T113334390`: mama/papa/thrombosis cosine 0.7799/0.8554/0.4555 with mama/papa candidate separability 0.2469 versus reference 0.3791. Latest static artifact `artifacts/parity/pink-trombone-logmel/20260528T113334415` passes with sealed-static closure peak 0.000009.","claim":"Generic connection pressure/admittance fallback was unowned DSP authority. Non-bypass graph connections now share the same traveling-wave N-port area law, leaving closure storage, radiation impedance, and source impedance as the next real smells instead of topology-shaped compatibility code.","timestamp":"2026-05-28T11:45:00+01:00","topic":"acoustic-graph-nport-connection-cut"}
{"evidence":"Moved graph radiation output coloring and admittance constants into a named local radiation primitive in `FaustExport`: emitted graph Faust now names `graph_radiation_reference_area_*`, `graph_radiation_differentiation_*`, `graph_radiation_highpass_*`, `graph_radiation_admittance_*`, and `graph_radiation_flow_*` instead of inlining the old `flow * 0.30 + highpass(flow) * 0.70` and `area + 1.0` expressions. Focused structural/Faust checks, accepted utterance smoke, and static PT graph smoke passed. Latest utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T113949841`: mama/papa/thrombosis cosine 0.7648/0.8550/0.4556, mama/papa candidate separability 0.2044 versus reference 0.3791. Latest static artifact `artifacts/parity/pink-trombone-logmel/20260528T113949813` passes with sealed-static closure peak 0.000011.","claim":"Radiation color was unowned inline math. It now has a single local primitive, but articulation did not materially improve, so the live audible failure is still closure pressure storage/source impedance rather than boundary coloring alone.","timestamp":"2026-05-28T12:05:00+01:00","topic":"acoustic-graph-radiation-primitive-cut"}
{"evidence":"Hoisted graph turbulence/release source lowering into named Faust locals per source/node: `_closure`, `_release`, `_reservoir`, `_pressure_drive`, `_release_pressure`, `_noise_gate`, `_release_gate`, `_sustained`, `_burst_noise`, and `_release_pulse`. Fixed the emission order so helper locals are written before the node-source assignment. Focused structural/Faust checks, accepted utterance smoke, and static PT graph smoke passed. Latest utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T114618213`; latest static artifact `artifacts/parity/pink-trombone-logmel/20260528T114618202`. Metrics match the prior radiation pass.","claim":"Closure/release pressure is now inspectable at the graph-source layer instead of buried in one expression. This is not yet true contact-aware plosive modeling; the next real change is to replace the source-local reservoir/source-impedance law with a graph/contact-owned upstream-downstream pressure model.","timestamp":"2026-05-28T12:20:00+01:00","topic":"acoustic-graph-source-reservoir-naming"}

View file

@ -1,6 +1,6 @@
project: AquaSynth
spine_version: 1
updated: 2026-05-28T12:05:00+01:00
updated: 2026-05-28T12:20:00+01:00
prime_directive: >
Build coherent synth machinery. References exist to reveal missing structure,
@ -125,7 +125,7 @@ live_invariants:
- PT utterance metrics now include cross-utterance separability because log-mel parity alone hid the user's actual listening failure. Artifact `artifacts/parity/pink-trombone-utterance-logmel/20260527T225833392` shows `mama/papa` candidate collapse: reference log-mel similarity 0.3791 versus candidate 0.6069, with candidate envelope similarity 0.8885. Interior source terminals no longer decide topology or node area/reflection; they attach to existing graph nodes and inject energy, while non-source terminals own geometry. Keep this source/topology cut even if other broken layers get louder.
- Generated lip radiation terminals no longer bake first-frame lip opening into terminal `area_scale`; lip opening owns live path area plus radiation aperture. This removes a stale initial-state authority where words starting with closed lips kept closed-lip admittance. Static PT graph artifact `artifacts/parity/pink-trombone-logmel/20260527T231611586` passes, while utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260527T231056242` still fails `mama/papa` collapse and silent `thrombosis`. Next work remains source/reservoir/radiation coupling, not restoring terminal-scale double gates.
- 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. Latest artifacts after radiation primitive extraction are `artifacts/parity/pink-trombone-utterance-logmel/20260528T113949841` and `artifacts/parity/pink-trombone-logmel/20260528T113949813`; smoke passes, but `mama/papa` separability and articulation did not materially improve. Remaining suspect owners are unowned segment loss constant, turbulence/release scalar constants, nearest-node source attachment, and the still-empirical radiation primitive. Next coherent build is graph-native closure reservoir/source impedance.
- `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`, `_pressure_drive`, `_release_pressure`, `_noise_gate`, `_release_gate`, `_sustained`, `_burst_noise`, and `_release_pulse` locals. Latest artifacts after reservoir naming are `artifacts/parity/pink-trombone-utterance-logmel/20260528T114618213` and `artifacts/parity/pink-trombone-logmel/20260528T114618202`; metrics match the prior pass because this was an observability/ownership cut. Remaining suspect owners are unowned segment loss constant, still-empirical radiation/source constants, nearest-node source attachment, and the lack of a true upstream/downstream closure-contact reservoir. Next coherent build is changing that reservoir/source-impedance law now that it is inspectable.
- 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.
- 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.

View file

@ -726,6 +726,9 @@ public sealed class PatchScriptTests
Assert.Contains("graph_connection_reflection_voices_0_nose_connection", export.Source);
Assert.Contains("acoustic_graph_radiated", export.Source);
Assert.Contains("graph_terminal_area_voices_0_area_", export.Source);
Assert.Contains("graph_source_", export.Source);
Assert.Contains("_reservoir", export.Source);
Assert.Contains("_release_pulse", export.Source);
Assert.DoesNotContain("wg_diameter_target_", export.Source);
Assert.NotEmpty(fixture.ReferenceFeatures);
}