Drive closure reservoir from path pressure

This commit is contained in:
Meta Vens 2026-05-28 12:59:28 +01:00
commit 497bf72bbc
5 changed files with 55 additions and 5 deletions

View file

@ -444,3 +444,24 @@ metrics did not move:
Keep the sign fix. The non-result strengthens the diagnosis: the real missing
primitive is still a contact-aware upstream/downstream closure state, not a
source-local scalar reservoir.
## 2026-05-28 Directed Reservoir Drive
For turbulence/release sources attached to ordinary two-port path nodes, the
reservoir now charges from directed upstream-minus-downstream pressure:
`max(0, upstream_incoming - downstream_incoming)`
Boundary or non-two-port source nodes fall back to positive local incident
pressure. This lets a source-local plosive at least read the graph's traveling
wave direction instead of only a node-average pressure.
Latest utterance artifact:
`artifacts/parity/pink-trombone-utterance-logmel/20260528T115452268`.
Latest static artifact:
`artifacts/parity/pink-trombone-logmel/20260528T115719882`.
Metrics still do not move. That is useful and damning: the source-local
reservoir sign/direction is no longer the main missing piece. The graph needs a
contact/closure primitive that participates in scattering and flow, not a
separate source pulse hoping the node will turn it into articulation.

View file

@ -721,7 +721,7 @@ 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);
var nodeSourceExpression = EmitNodeSourceExpression(source, patch, name, node, incident[node.Name], 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, -2, 2)};");
debugProbeKeepAlive.Add(incidentName);
@ -1800,6 +1800,7 @@ public static class FaustEmitter
SynthPatch patch,
string voiceName,
AcousticGraphNode node,
IReadOnlyList<AcousticGraphPort> nodePorts,
IReadOnlyDictionary<string, AcousticSourcePort> sources,
string frequency,
ParameterMap parameters)
@ -1811,7 +1812,7 @@ public static class FaustEmitter
var sourceName = terminal.Port.Length == 0 ? terminal.Name : terminal.Port;
var port = sources[sourceName];
return port.Kind == AcousticSourceKind.TurbulenceJet
? EmitGraphTurbulenceSourceExpression(source, patch, voiceName, node, sourceName, port, parameters)
? EmitGraphTurbulenceSourceExpression(source, patch, voiceName, node, nodePorts, sourceName, port, parameters)
: $"{voiceName}_graph_source_{SafeIdentifier(sourceName)}";
})
.ToList();
@ -1823,6 +1824,7 @@ public static class FaustEmitter
SynthPatch patch,
string voiceName,
AcousticGraphNode node,
IReadOnlyList<AcousticGraphPort> nodePorts,
string sourceName,
AcousticSourcePort port,
ParameterMap parameters)
@ -1838,10 +1840,12 @@ public static class FaustEmitter
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)})";
var reservoirDrive = ClosureReservoirDriveExpression(voiceName, node, nodePorts, port, localPressure);
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 = (max(0.0, {localPressure}) * {prefix}_closure) : + ~ *(0.992);");
source.AppendLine($" {prefix}_reservoir_drive = {reservoirDrive};");
source.AppendLine($" {prefix}_reservoir = {prefix}_reservoir_drive * {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)));");
@ -1854,6 +1858,29 @@ public static class FaustEmitter
return $"{prefix}_out";
}
private static string ClosureReservoirDriveExpression(
string voiceName,
AcousticGraphNode node,
IReadOnlyList<AcousticGraphPort> nodePorts,
AcousticSourcePort sourcePort,
string fallbackPressure)
{
var samePathPorts = nodePorts
.Where(port => port.Segment.Path.Name.Equals(sourcePort.Path, StringComparison.OrdinalIgnoreCase))
.ToList();
var upstream = samePathPorts
.Where(port => !port.AtStart && port.Segment.EndPosition <= node.Position + 0.0001f)
.OrderByDescending(port => port.Segment.StartPosition)
.FirstOrDefault();
var downstream = samePathPorts
.Where(port => port.AtStart && port.Segment.StartPosition >= node.Position - 0.0001f)
.OrderBy(port => port.Segment.EndPosition)
.FirstOrDefault();
return upstream is not null && downstream is not null
? $"max(0.0, {GraphIncoming(voiceName, upstream)} - {GraphIncoming(voiceName, downstream)})"
: $"max(0.0, {fallbackPressure})";
}
private static string GraphIncoming(string voiceName, AcousticGraphPort port) =>
port.AtStart
? $"{voiceName}_graph_in_{port.Segment.Index}_start"

View file

@ -200,3 +200,4 @@
{"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"}
{"evidence":"Changed graph source-local closure reservoir charging from `abs(incident_pressure) * closure` to `max(0, incident_pressure) * closure`, so alternating negative pressure no longer charges a plosive reservoir. Accepted utterance smoke and static PT graph smoke passed. Latest utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T114903720`; latest static artifact `artifacts/parity/pink-trombone-logmel/20260528T114953503`. Metrics did not move from the reservoir-naming pass.","claim":"Plosive storage should charge from compression, not rectified vibration. The lack of metric movement says the current audible failure is not the reservoir sign alone; Aqua still needs a contact-aware upstream/downstream closure state and source impedance instead of only a source-local scalar reservoir.","timestamp":"2026-05-28T12:55:00+01:00","topic":"acoustic-graph-positive-pressure-reservoir"}
{"evidence":"Changed graph turbulence/release reservoir drive to use directed upstream-minus-downstream pressure at ordinary two-port path nodes (`max(0, upstream_incoming - downstream_incoming)`), with positive local incident pressure as the fallback for boundaries or more complex nodes. Focused structural/Faust checks, accepted utterance smoke, and static PT graph smoke passed. Latest utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T115452268`; latest static artifact `artifacts/parity/pink-trombone-logmel/20260528T115719882`. Metrics still did not move.","claim":"Source-local reservoir direction is no longer the main explanation for weak plosives. The graph needs a contact/closure primitive that participates in scattering and flow, rather than a separate source pulse injected after the node law.","timestamp":"2026-05-28T13:05:00+01:00","topic":"acoustic-graph-directed-reservoir-drive"}

View file

@ -1,6 +1,6 @@
project: AquaSynth
spine_version: 1
updated: 2026-05-28T12:55:00+01:00
updated: 2026-05-28T13:05: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. 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. Reservoir charging now uses positive local pressure (`max(0, incident_pressure)`) instead of `abs(incident_pressure)`; latest artifacts `artifacts/parity/pink-trombone-utterance-logmel/20260528T114903720` and `artifacts/parity/pink-trombone-logmel/20260528T114953503` show no metric movement, which points away from sign convention and toward the lack of a true upstream/downstream closure-contact reservoir. Remaining suspect owners are unowned segment loss constant, still-empirical radiation/source constants, nearest-node source attachment, and missing contact-aware closure state.
- `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.
- 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

@ -727,6 +727,7 @@ public sealed class PatchScriptTests
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_drive", export.Source);
Assert.Contains("_reservoir", export.Source);
Assert.Contains("_release_pulse", export.Source);
Assert.DoesNotContain("wg_diameter_target_", export.Source);