Name graph radiation impedance primitive

This commit is contained in:
Meta Vens 2026-05-28 12:41:51 +01:00
commit 0cc03f8b75
5 changed files with 103 additions and 31 deletions

View file

@ -235,21 +235,25 @@ own contact loss scale and frequency-dependent wall loss.
What it simulates: boundary flow emitted from lip, nostril, beak, or other
openings.
Current state: less wrong after cutting generated lip double gates. Radiation
now reads boundary flow and generated tract lip opening is path geometry, not
also output aperture.
Current state: less wrong after cutting generated lip double gates and moving
the graph output constants into one named radiation impedance expression.
Radiation now reads boundary flow, generated tract lip opening is path
geometry, and emitted graph Faust names per-port reference area,
differentiation, high-pass cutoff, admittance, and flow.
Smells:
- The 30/70 raw/high-pass blend is a voicing scar. It approximates radiation
color, but it is not a clear radiation impedance model.
- The admittance expression `sqrt(area / (area + 1.0))` contains an unexplained
unit constant.
- The graph no longer has inline `30/70` raw/high-pass blend or `area + 1.0`
admittance denominator, but the helper still uses compact empirical kind
constants. Those are now in one organ, not scattered through graph lowering.
- `RadiationBoundaryReflectionExpression` is useful for standalone radiation
openings, but dangerous when the path end area already represents aperture.
- Radiation still has no frequency-dependent load matched to tube radius and
characteristic impedance.
Verdict: keep boundary-flow readout. Replace color/admittance constants with a
named radiation impedance primitive.
Verdict: keep boundary-flow readout and the named primitive. Replace the
empirical primitive with a radius/characteristic-impedance model once source
and closure storage stop dominating the audible failure.
### Generated PT-Style Tract
@ -292,16 +296,20 @@ faster than global scores.
Cut or replace these before piling on more tuning:
1. Unowned radiation constants: 30/70 high-pass blend and `area + 1.0`.
2. Unowned segment contact-loss constant `0.02`.
3. Turbulence/release scalar constants without a named pressure-flow model.
4. Nearest-node source attachment as the only interior source placement model.
1. Unowned segment contact-loss constant `0.02`.
2. Turbulence/release scalar constants without a named pressure-flow model.
3. Nearest-node source attachment as the only interior source placement model.
Cut in the 2026-05-28 N-port pass:
- Generic connection fallback pressure/admittance blend.
- Shape-specific three-port branch scattering path.
Cut in the 2026-05-28 radiation pass:
- Inline graph radiation `30/70` raw/high-pass blend.
- Inline graph radiation `area + 1.0` admittance denominator.
## Keep List
These foundations deserve to stay:
@ -323,8 +331,8 @@ radiation impedance:
- Closure reservoir: a stateful pressure store attached to a constriction owner,
charged while local area is sealed and released through the graph when area
opens.
- Radiation impedance: replace output coloring constants with a named local
radiation model.
- Radiation impedance: upgrade the named empirical local model to a
radius/characteristic-impedance model.
Until those exist, the graph can imitate some Pink Trombone pressure, but it is
not yet a clean general vocal-acoustic machine.
@ -350,3 +358,33 @@ with peak `0.000009`.
This is not a victory lap. It is a floor repair. The next real smells are still
radiation impedance constants, graph-native closure reservoir state, and source
impedance at junction nodes.
## 2026-05-28 Radiation Primitive Cut
The second audit cut moved graph radiation coloring and admittance into a named
local primitive. The emitter now names per-radiation-terminal:
- `graph_radiation_reference_area_*`
- `graph_radiation_differentiation_*`
- `graph_radiation_highpass_*`
- `graph_radiation_admittance_*`
- `graph_radiation_flow_*`
This removes the inline `flow * 0.30 + highpass(flow) * 0.70` expression and
the inline `area + 1.0` admittance denominator from graph lowering. The current
primitive is still empirical, but it has one owner.
Latest utterance artifact:
`artifacts/parity/pink-trombone-utterance-logmel/20260528T113949841`.
`mama/papa/thrombosis` cosine is `0.7648/0.8550/0.4556`. `mama/papa`
candidate separability is `0.2044` versus reference `0.3791`, so this cleanup
does not fix articulation.
Latest static artifact:
`artifacts/parity/pink-trombone-logmel/20260528T113949813`.
Static fixtures still pass; `closure-release` remains sealed-static evidence
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.

View file

@ -490,12 +490,10 @@ public static class FaustEmitter
var opening = $"max(0.0, {parameters.Expression(OwnerField(radiationPath, "opening"), port.Opening)})";
var reflection = $"min(1.0, abs({parameters.Expression(OwnerField(radiationPath, "reflection"), port.Reflection)}))";
var portLoss = $"clip01({parameters.Expression(OwnerField(radiationPath, "loss"), port.Loss)})";
var highpass = port.Kind is AcousticRadiationKind.Lip or AcousticRadiationKind.Beak
? "80.0"
: port.Kind == AcousticRadiationKind.Nostril
? "40.0"
: "20.0";
radiationExpressions.Add($"(({name}_acoustic_body : fi.highpass(1, {highpass})) * {opening} * (0.55 + 0.45 * {reflection}) * {portLoss})");
var aperture = RadiationApertureExpression(patch, parameters, port);
var highpass = RadiationHighpassExpression(port.Kind, aperture, false);
var reflectionWeight = $"(0.55 + 0.45 * {reflection})";
radiationExpressions.Add($"(({name}_acoustic_body : fi.highpass(1, {highpass})) * {opening} * {reflectionWeight} * {portLoss})");
}
if (radiationExpressions.Count == 0)
{
@ -798,20 +796,20 @@ public static class FaustEmitter
var radiationPath = $"/acoustic/radiation/{radiationIndex}";
var opening = RadiationApertureExpression(patch, parameters, radiationPort);
var loss = $"clip01({parameters.Expression(OwnerField(radiationPath, "loss"), radiationPort.Loss)})";
var highpass = radiationPort.Kind is AcousticRadiationKind.Lip or AcousticRadiationKind.Beak
? "220.0"
: radiationPort.Kind == AcousticRadiationKind.Nostril
? "90.0"
: "20.0";
var filter = $"fi.highpass(1, {highpass})";
var safeTerminal = SafeIdentifier(terminal.Name);
var terminalArea = $"{name}_graph_terminal_area_{safeTerminal}";
var referenceArea = $"{name}_graph_radiation_reference_area_{safeTerminal}";
var differentiation = $"{name}_graph_radiation_differentiation_{safeTerminal}";
var highpass = $"{name}_graph_radiation_highpass_{safeTerminal}";
var admittance = $"{name}_graph_radiation_admittance_{safeTerminal}";
var flow = $"({string.Join(" + ", ports.Select(port => $"({GraphIncoming(name, port)} - {GraphOutgoing(name, port)})"))})";
var flowName = $"{name}_graph_radiation_flow_{safeTerminal}";
source.AppendLine($" {admittance} = {ProbeSignal(options, $"/debug/{name}/radiation/{terminal.Name}/admittance", $"sqrt(clip01(({terminalArea}) / max(0.000001, ({terminalArea}) + 1.0)))", 0, 1)};");
source.AppendLine($" {referenceArea} = {RadiationReferenceAreaExpression(radiationPort.Kind, opening)};");
source.AppendLine($" {differentiation} = {RadiationDifferentiationExpression(radiationPort.Kind, opening)};");
source.AppendLine($" {highpass} = {RadiationHighpassExpression(radiationPort.Kind, opening, true)};");
source.AppendLine($" {admittance} = {ProbeSignal(options, $"/debug/{name}/radiation/{terminal.Name}/admittance", $"sqrt(clip01(({terminalArea}) / max(0.000001, ({terminalArea}) + ({referenceArea}))))", 0, 1)};");
source.AppendLine($" {flowName} = {ProbeSignal(options, $"/debug/{name}/radiation/{terminal.Name}/flow", flow, -2, 2)};");
radiated.Add($"(({flowName} * 0.30 + ({flowName} : {filter}) * 0.70) * {loss} * {admittance})");
radiated.Add($"(({flowName} * (1.0 - ({differentiation})) + ({flowName} : fi.highpass(1, {highpass})) * ({differentiation})) * {loss} * {admittance})");
}
}
@ -865,6 +863,39 @@ public static class FaustEmitter
return $"(({closedReflection}) * (1.0 - clip01({aperture})) + ({openReflection}) * clip01({aperture}))";
}
private static string RadiationReferenceAreaExpression(AcousticRadiationKind kind, string aperture) => kind switch
{
AcousticRadiationKind.Lip or AcousticRadiationKind.Beak => $"max(0.18, 1.50 * 1.50 * (0.35 + 0.65 * clip01({aperture})))",
AcousticRadiationKind.Nostril => $"max(0.08, 0.70 * 0.70 * (0.50 + 0.50 * clip01({aperture})))",
AcousticRadiationKind.Membrane => "0.35",
AcousticRadiationKind.Vent => "0.80",
_ => "1.0"
};
private static string RadiationDifferentiationExpression(AcousticRadiationKind kind, string aperture) => kind switch
{
AcousticRadiationKind.Lip or AcousticRadiationKind.Beak => $"(0.55 + 0.25 * clip01({aperture}))",
AcousticRadiationKind.Nostril => $"(0.45 + 0.20 * clip01({aperture}))",
AcousticRadiationKind.Membrane => "0.20",
AcousticRadiationKind.Vent => $"(0.30 + 0.20 * clip01({aperture}))",
_ => "0.50"
};
private static string RadiationHighpassExpression(AcousticRadiationKind kind, string aperture, bool graph) => kind switch
{
AcousticRadiationKind.Lip or AcousticRadiationKind.Beak => graph
? $"(120.0 + 180.0 * clip01({aperture}))"
: $"(60.0 + 50.0 * clip01({aperture}))",
AcousticRadiationKind.Nostril => graph
? $"(55.0 + 75.0 * clip01({aperture}))"
: $"(30.0 + 30.0 * clip01({aperture}))",
AcousticRadiationKind.Membrane => "15.0",
AcousticRadiationKind.Vent => graph
? $"(20.0 + 35.0 * clip01({aperture}))"
: $"(15.0 + 20.0 * clip01({aperture}))",
_ => "20.0"
};
private static string AcousticSourceExpression(
SynthPatch patch,
AcousticSourcePort port,

View file

@ -197,3 +197,4 @@
{"evidence":"Repaired graph voice sample-flow ownership: bounded the glottal source with local tanh normalization, added area-dependent segment propagation loss, removed the hidden 0.15 generated nasal side-port damper so velum diameter squared owns nasal admittance, stopped generated tract lips from binding lip opening into radiation-port opening, and stopped multiplying graph boundary flow by aperture after boundary reflection/path geometry already handle opening. Focused structural tests, utterance smoke, and static smoke passed. Latest utterance artifact `artifacts/parity/pink-trombone-utterance-logmel/20260528T070759312`: mama/papa/thrombosis cosine 0.7918/0.8540/0.4366, candidate mama/papa separability 0.3651 versus reference 0.3791. Latest static artifact `artifacts/parity/pink-trombone-logmel/20260528T071240442` treats closure-release as sealed static with peak 0.000005.","claim":"The phone-vibration collapse came from multiple model ownership leaks, not one missing gain knob: unbounded source dominance, immortal closed-section wave storage, damped nasal admittance, and lip radiation double-gating. The graph is still quiet and under-articulated, but different articulations now drive different sample-flow paths. Next fix is true closure reservoir/release through the scattering graph.","timestamp":"2026-05-28T07:15:00+01:00","topic":"acoustic-graph-voice-model-repair"}
{"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"}

View file

@ -1,6 +1,6 @@
project: AquaSynth
spine_version: 1
updated: 2026-05-28T11:45:00+01:00
updated: 2026-05-28T12: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. Latest artifacts after the cut are `artifacts/parity/pink-trombone-utterance-logmel/20260528T113334390` and `artifacts/parity/pink-trombone-logmel/20260528T113334415`; smoke passes, `thrombosis` improves slightly, and `mama/papa` remain differentiated but quiet/under-articulated. Remaining suspect owners are unowned radiation constants, unowned segment loss constant, turbulence/release scalar constants, and nearest-node source attachment. Next coherent build is graph-native closure reservoir plus a named radiation impedance primitive.
- `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.
- 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

@ -331,6 +331,8 @@ public sealed class PatchScriptTests
Assert.Contains("graph_area_reflection_", export.Source);
Assert.Contains("graph_area_energy_in_", export.Source);
Assert.Contains("graph_area_energy_out_", export.Source);
Assert.Contains("graph_radiation_reference_area_voices_0_lip", export.Source);
Assert.Contains("graph_radiation_differentiation_voices_0_lip", export.Source);
Assert.Contains("graph_radiation_admittance_voices_0_lip", export.Source);
Assert.Contains("graph_radiation_flow_voices_0_lip", export.Source);
Assert.Contains("patch_param_0) * (patch_param_0", export.Source);