diff --git a/src/AquaSynth.Core/FaustExport.cs b/src/AquaSynth.Core/FaustExport.cs index ff8dbe3..65ab6b0 100644 --- a/src/AquaSynth.Core/FaustExport.cs +++ b/src/AquaSynth.Core/FaustExport.cs @@ -653,7 +653,7 @@ public static class FaustEmitter source.AppendLine($"{name}_graph_connection_coupling_{SafeIdentifier(connection.Name)} = clip01({parameters.Expression(OwnerField(connectionPath, "coupling"), connection.Coupling)});"); source.AppendLine($"{name}_graph_connection_loss_{SafeIdentifier(connection.Name)} = clip01({parameters.Expression(OwnerField(connectionPath, "loss"), connection.Loss)});"); } - source.AppendLine($"{name}_graph_radiation_gain = 24.0;"); + source.AppendLine($"{name}_graph_radiation_gain = 3.0;"); foreach (var sourceName in network.SourcePorts) { @@ -754,7 +754,9 @@ public static class FaustEmitter : radiationPort.Kind == AcousticRadiationKind.Nostril ? "40.0" : "20.0"; - radiated.Add($"((({string.Join(" + ", ports.Select(port => GraphIncoming(name, port)))}) : fi.highpass(1, {highpass})) * {opening} * {loss})"); + var filter = $"fi.highpass(1, {highpass})"; + var kindGain = radiationPort.Kind == AcousticRadiationKind.Nostril ? "0.75" : "1.55"; + radiated.Add($"((({string.Join(" + ", ports.Select(port => GraphIncoming(name, port)))}) : {filter}) * {opening} * {loss} * {kindGain})"); } } diff --git a/src/AquaSynth.Core/PatchScript.cs b/src/AquaSynth.Core/PatchScript.cs index ef86d68..acecb3a 100644 --- a/src/AquaSynth.Core/PatchScript.cs +++ b/src/AquaSynth.Core/PatchScript.cs @@ -1291,7 +1291,7 @@ public static class PatchScript branch.ToPosition, AcousticTerminalKind.Junction, branch.Name, - Math.Max(0, branch.Opening))); + MathF.Pow(Math.Max(0, branch.Opening), 2))); AddAcousticConnectionRecord(new AcousticConnection( BranchConnectionName(branch), [BranchFromTerminalName(branch), BranchToTerminalName(branch)], @@ -1302,7 +1302,7 @@ public static class PatchScript nasalPathName, 1, AcousticRadiationKind.Nostril, - nasal.Velum, + 1, nasal.Reflection, nasal.Loss); AddAcousticRadiationPortRecord(nasalRadiation); diff --git a/src/AquaSynth.Core/PinkTromboneParityFixtures.cs b/src/AquaSynth.Core/PinkTromboneParityFixtures.cs index b5fc8a6..1d3bb2e 100644 --- a/src/AquaSynth.Core/PinkTromboneParityFixtures.cs +++ b/src/AquaSynth.Core/PinkTromboneParityFixtures.cs @@ -64,6 +64,24 @@ public static class PinkTromboneParityFixtures Velum: 0.33f, LipOpening: 1.35f), ["nose_waveguide_cells", "nose_junction"]), + Fixture( + "bilabial-nasal-ma", + "Playground `ma` pressure: velum-open voiced nasal control set without nasal-path overdrive.", + new PinkTromboneFixtureControls( + Frequency: 132, + Intensity: 0.66f, + Tenseness: 0.52f, + TongueIndex: 14, + TongueDiameter: 2.2f, + ConstrictionIndex: 18, + ConstrictionDiameter: 0.8f, + Turbulence: 0.12f, + Velum: 0.33f, + LipOpening: 1.35f, + GlottalReflection: 0.78f, + LipReflection: -0.84f, + Gain: 0.72f), + ["nose_waveguide_cells", "nose_junction", "playground_ma_level_balance"]), Fixture( "sibilant", "High-turbulence front constriction with positioned waveguide injection.", @@ -109,12 +127,12 @@ public static class PinkTromboneParityFixtures name=human diameters=0.6,0.6,0.6,0.6,0.6,0.7,0.8,1.0,1.1,1.1,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.5,1.4,1.3,1.2,1.15,1.5 - glottis name=modal intensity=.72 tenseness=.6 aspiration=.12 reflection=.75 skew=.42 + glottis name=modal intensity={{F(controls.Intensity)}} tenseness={{F(controls.Tenseness)}} aspiration=.12 reflection={{F(controls.GlottalReflection)}} skew=.42 tract_injection name=inj position=32 diameter=1 turbulence=.1 burst=.25 width=1 nasal_branch name=nose junction=17 velum=.01 reflection=-.85 loss=.999 diameters=0.01,0.35,0.5,0.65,0.8,0.95,1.1,1.25,1.4,1.55,1.7,1.8,1.9,1.9,1.85,1.75,1.65,1.55,1.45,1.35,1.25,1.15,1.05,0.95,0.85,0.75,0.65,0.55 tract_motion name=motion diameter_slew=18 shape_return=8 constriction_slew=24 velum_slew=16 obstruction_threshold=.05 - tract shape=human glottis=modal injection=inj nasal_branch=nose motion=motion propagation={{propagation}} waveguide_loss=.999 freq={{F(controls.Frequency)}} gain={{F(controls.Gain)}} sustain=.45 decay=.12 tongue_index={{F(controls.TongueIndex)}} tongue_diameter={{F(controls.TongueDiameter)}} constriction_index={{F(controls.ConstrictionIndex)}} constriction_diameter={{F(controls.ConstrictionDiameter)}} turbulence={{F(controls.Turbulence)}} velum={{F(controls.Velum)}} lip={{F(controls.LipOpening)}} burst={{F(controls.Burst)}} glottal_reflection={{F(controls.GlottalReflection)}} lip_reflection={{F(controls.LipReflection)}} + tract shape=human glottis=modal injection=inj nasal_branch=nose motion=motion propagation={{propagation}} waveguide_loss=.999 freq={{F(controls.Frequency)}} gain={{F(controls.Gain)}} intensity={{F(controls.Intensity)}} tenseness={{F(controls.Tenseness)}} sustain=.45 decay=.12 tongue_index={{F(controls.TongueIndex)}} tongue_diameter={{F(controls.TongueDiameter)}} constriction_index={{F(controls.ConstrictionIndex)}} constriction_diameter={{F(controls.ConstrictionDiameter)}} turbulence={{F(controls.Turbulence)}} velum={{F(controls.Velum)}} lip={{F(controls.LipOpening)}} burst={{F(controls.Burst)}} glottal_reflection={{F(controls.GlottalReflection)}} lip_reflection={{F(controls.LipReflection)}} """; private static string F(float value) => diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 0245544..a2ac062 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -157,3 +157,4 @@ {"timestamp":"2026-05-26T21:20:00+01:00","topic":"physical-voice-side-branch-admittance-progress","claim":"Side-branch graph scattering now uses terminal-specific admittance plus a floored area-ratio pressure normalization for outgoing branch waves. This is not final power parity, but it improves nasal cosine/RMS without erasing the oral vowel cosine win. Raw per-port coupling, squared velum mapping, and lip/beak radiation gain were tested and cut.","evidence":"Latest optional PT log-mel run: graph open 0.6202 RMS 0.1703, graph front 0.6417 RMS 0.1752, graph nasal 0.5159 RMS 1.4815, graph sibilant 0.2268 RMS 1.0643, graph closure 0.1117 RMS 1.8085. Previous source/radiation baseline was open 0.6393 RMS 0.3559, front 0.6596 RMS 0.3661, nasal 0.4607 RMS 1.6914, sibilant 0.2320 RMS 0.9158, closure 0.1170 RMS 1.6650."} {"timestamp":"2026-05-26T21:35:00+01:00","topic":"physical-voice-frication-source-pressure","claim":"Generated graph `TurbulenceJet` ports now treat sustained turbulence and burst as separate pressure authorities: continuous frication uses max(turbulence, burst) for source pressure instead of being throttled by burst alone, while turbulence still gates noise amount. Fully ungated noise was tested and cut because it hurt closure-release.","evidence":"Optional PT log-mel after pressure fix: sibilant graph cosine 0.2302 / RMS 1.1387 and closure graph cosine 0.1145 / RMS 1.6627. Setting noise to 1 barely changed sibilant (0.2303) and worsened closure (0.1113 / RMS 1.8777), so that part was reverted."} {"timestamp":"2026-05-26T21:07:36+01:00","topic":"vocal-tract-playground-actual-graph-audition","claim":"The vocal tract playground no longer owns a browser-native source/filter audio witness. Audition audio is now rendered from the actual Aqua DSL tract graph path by posting controls to a .NET renderer that emits tract propagation=graph, lowers through FaustEmitter, renders with FaustCompiler, writes a WAV, and serves it back to the browser.","evidence":"Built tools/TractGraphRenderer successfully with disabled build servers. Direct renderer smoke wrote artifacts/vocal-tract-playground/smoke.wav with peak 0.15414488, RMS 0.066947475, and no warnings. Live POST http://127.0.0.1:5126/render returned a rendered WAV and /renders/... served HTTP 200. node --check passed for playground app.js and serve.mjs."} +{"timestamp":"2026-05-26T21:38:50+01:00","topic":"pink-trombone-graph-nasal-level-balance","claim":"The graph tract nasal path no longer reuses velum as both branch valve and nostril aperture. Generated tract graphs now square velum at the nasal branch entrance, keep nostril radiation independent, and use a lower graph radiation gain so corrected topology does not drive the patch into soft clipping. Playground Ma is now level-matched with Open instead of overdriven.","evidence":"PT log-mel graph run passed with open cosine 0.6219 RMS 0.9743, front 0.6407 RMS 1.0024, nasal 0.5375 RMS 1.1476, new bilabial-nasal-ma 0.4743 RMS 1.2206, sibilant 0.3233 RMS 1.0008, closure 0.0817 RMS 1.5534. Live renderer smoke after lowering patch gain reported Open peak/RMS 0.498/0.257 and Ma 0.480/0.243."} diff --git a/state/scratch.md b/state/scratch.md index e19ee50..6bbcb9e 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -2,6 +2,35 @@ Current slice: +- Golfed the actual graph tract path against the PT log-mel harness after the + playground `Ma` preset sounded like amplified buzzing. The failure was not a + playground-only gain problem: generated graph topology reused velum as both + branch valve and nostril aperture, while closed-nose leakage was carrying too + much oral loudness. +- `EnsureTractAcousticNetwork` now treats velum as branch entrance area + control by squaring the nasal branch terminal opening, while nostril radiation + remains an independent aperture. Graph radiation was then cut from `24.0` to + `3.0` because the previous value was a compensator for the leaky topology and + drove the corrected graph into soft clipping. +- Added a `bilabial-nasal-ma` PT fixture using the playground `Ma` controls, and + fixed PT fixture script generation so intensity/tenseness/glottal reflection + actually reach both `glottis` and `tract`. +- The playground renderer patch gain is now `0.48`, keeping audition renders out + of the clipped-buzz regime while still rendering the actual Aqua DSL/Faust + graph. +- Verification: + `dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --filter "PinkTromboneFixturesReportLogMelParityWhenFaustIsInstalled" --disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false -v minimal` + passed. Latest graph reports: + `open-vowel` cosine `0.6219` RMS ratio `0.9743`; + `front-vowel` `0.6407` / `1.0024`; + `nasal-vowel` `0.5375` / `1.1476`; + `bilabial-nasal-ma` `0.4743` / `1.2206`; + `sibilant` `0.3233` / `1.0008`; + `closure-release` `0.0817` / `1.5534`. + Focused graph/parser tests and `tools\TractGraphRenderer` build also passed. + Live playground render check after this pass: Open peak/RMS about + `0.498/0.257`; Ma about `0.480/0.243`. + - Replaced the vocal tract playground's browser-native source/filter witness with actual Aqua DSL graph rendering. The page now posts the visible controls to `/render`; `tools/TractGraphRenderer` emits `tract propagation=graph`, diff --git a/tests/AquaSynth.Dsl.Tests/PinkTromboneLogMelParityTests.cs b/tests/AquaSynth.Dsl.Tests/PinkTromboneLogMelParityTests.cs index d8b9311..c1c50e4 100644 --- a/tests/AquaSynth.Dsl.Tests/PinkTromboneLogMelParityTests.cs +++ b/tests/AquaSynth.Dsl.Tests/PinkTromboneLogMelParityTests.cs @@ -15,6 +15,7 @@ public sealed class PinkTromboneLogMelParityTests ["open-vowel"] = 0.55f, ["front-vowel"] = 0.57f, ["nasal-vowel"] = 0.48f, + ["bilabial-nasal-ma"] = 0.47f, ["sibilant"] = 0.18f, ["closure-release"] = 0.05f }; diff --git a/tools/TractGraphRenderer/Program.cs b/tools/TractGraphRenderer/Program.cs index 2552cd9..16c947a 100644 --- a/tools/TractGraphRenderer/Program.cs +++ b/tools/TractGraphRenderer/Program.cs @@ -49,7 +49,7 @@ return 0; static string Script(RenderRequest controls) => $$""" - patch gain=0.82 soft_clip=true + patch gain=0.48 soft_clip=true tract_shape name=human