Port Pink Trombone reference renderer

This commit is contained in:
Meta Vens 2026-05-26 22:20:42 +01:00
commit df70a4f2f3
7 changed files with 767 additions and 173 deletions

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "external/zynaddsubfx"]
path = external/zynaddsubfx
url = https://github.com/zynaddsubfx/zynaddsubfx
[submodule "external/pink-trombone-mod"]
path = external/pink-trombone-mod
url = https://github.com/chdh/pink-trombone-mod

View file

@ -16,6 +16,8 @@ Primary source for this packet:
- `chdh/pink-trombone-mod`: MIT TypeScript modularization of Neil Thapen's Pink
Trombone, <https://github.com/chdh/pink-trombone-mod>
- Pinned local source: `external/pink-trombone-mod` at
`359c2d3b42b10280404c1650dc601902112b4c90`
- Source files inspected:
- `src/Synthesizer.ts`
- `src/Glottis.ts`
@ -86,7 +88,29 @@ Current AquaSynth cannot exactly express:
- fractional-delay tract propagation whose clock derives from physical length,
propagation speed, and sample rate;
- exact Pink Trombone LF glottal coefficient updates.
- exact Pink Trombone block timing and source/noise/transient behavior inside
the Faust-lowered Aqua graph.
## Reference Renderer Authority
`PinkTromboneReferenceRenderer` is a source-port of the pinned MIT TypeScript
implementation, not a Pink-Trombone-flavored proxy. Its inner graph mirrors the
upstream DSP authorities:
- `Synthesizer`: 512-sample blocks, tract stepping twice per output sample;
- `Glottis`: LF waveform coefficient solve, aspiration noise, vibrato/wobble,
and intensity/tenseness/frequency smoothing;
- `Tract`: 44 oral cells, 28 nasal cells, area-derived reflections, three-way
nasal junction, lip/nose radiation sum, filtered frication, and closure
transients;
- `TractShaper`: tongue/rest diameter targets, touch-style constriction
reduction, wall slew, velum slew, and obstruction release detection.
The renderer keeps AquaSynth's fixture control record as the authoring surface,
but those controls are now translated into the source graph rather than into a
hand-rolled approximation. Reference WAVs written before
`20260526T211902375` were produced by the old approximation and are invalid for
parity golf.
## Current Parity Rung

1
external/pink-trombone-mod vendored Submodule

@ -0,0 +1 @@
Subproject commit 359c2d3b42b10280404c1650dc601902112b4c90

File diff suppressed because it is too large Load diff

View file

@ -159,3 +159,4 @@
{"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."}
{"timestamp":"2026-05-26T21:52:30+01:00","topic":"pink-trombone-time-varying-utterance-reference-fixtures","claim":"AquaSynth now has explicit time-varying Pink Trombone reference utterance fixtures for mama, papa, lulek, and thrombosis. The PT renderer can interpolate timestamped control points sample-by-sample and write reference WAVs plus control CSVs before Aqua parity golf begins.","evidence":"Focused test PinkTromboneUtteranceFixturesWriteReferenceWavs passed and wrote artifacts/parity/pink-trombone-utterances/20260526T205230009. PT reference summaries: mama peak/RMS 0.7033/0.4213, papa 0.7012/0.3618, lulek 0.7200/0.4087, thrombosis 0.7600/0.4658."}
{"evidence":"Pinned external/pink-trombone-mod at 359c2d3b42b10280404c1650dc601902112b4c90; focused dotnet test passed and wrote artifacts/parity/pink-trombone-utterances/20260526T211902375 with mama/papa/lulek/thrombosis reference WAVs.","claim":"The previous Pink Trombone reference renderer was invalid as a parity oracle because it hand-rolled PT-shaped DSP instead of porting the source graph. The renderer now ports the pinned MIT chdh/pink-trombone-mod DSP source: LF glottis, filtered noise, 512-sample block updates, 2x tract stepping, 44 oral/28 nasal waveguide cells, nasal junction, target-diameter slew, touch-style constriction, frication, and obstruction release transients. Earlier utterance WAVs are invalid for parity golf.","timestamp":"2026-05-26T22:20:18+01:00","topic":"pink-trombone-reference-renderer-authority"}

View file

@ -25,6 +25,7 @@ surfaces:
dx7_public_domain_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
zyn_reference_source: external/zynaddsubfx
zyn_upstream_instrument_bank: external/zynaddsubfx/instruments
pink_trombone_reference_source: external/pink-trombone-mod
zyn_reference_build_script: tools/zyn-reference/build-zyn-reference.ps1
zyn_pad_reference_renderer: tools/zyn-reference/ZynPadReference.cpp
zyn_voice_reference_renderer: tools/zyn-reference/ZynVoiceReference.cpp
@ -121,6 +122,7 @@ checks:
faust_validation: automatic in tests when Faust is installed
dexed_py_reference: optional in tests when AQUASYNTH_DX7_PYTHON or a Python with dexed-py is available
zyn_reference_source: pinned submodule used only for parity tests; never package content
pink_trombone_reference_source: pinned MIT submodule used only for parity renderer source audits and port verification; never package content
espeak_ng_reference: optional command-line development reference, found through ESPEAK_NG or PATH; never package content
zyn_upstream_instrument_bank: nested GPL submodule used only for test/development pressure surveys and oracle renders; never package content

View file

@ -1,6 +1,6 @@
project: AquaSynth
spine_version: 1
updated: 2026-05-26T19:10:00+01:00
updated: 2026-05-26T22:20:00+01:00
prime_directive: >
Build coherent synth machinery. References exist to reveal missing structure,
@ -30,6 +30,7 @@ current_mechanism:
dx7_reference_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
dx7_reference_renderer_tests: tests/AquaSynth.Dsl.Tests/Dx7ReferenceParityTests.cs
zyn_reference_source: external/zynaddsubfx
pink_trombone_reference_source: external/pink-trombone-mod
zyn_upstream_instrument_bank: external/zynaddsubfx/instruments
zyn_reference_build_script: tools/zyn-reference/build-zyn-reference.ps1
zyn_pad_reference_renderer: tools/zyn-reference/ZynPadReference.cpp
@ -119,7 +120,7 @@ live_invariants:
- Physical voice implementation has started moving PT pressure into neutral acoustic owners: `AcousticPath`, `AcousticTerminal`, `AcousticConnection`, `AcousticSourcePort`, `AcousticBranch`, `AcousticRadiationPort`, `WaveClockPolicy`, and `AcousticPortNetwork`. Parser commands `path`, `terminal`, `connect`, `source_port`, `branch`, `radiation_port`, `wave_clock`, and `acoustic_network` now exist. Existing `tract_shape`, `glottis`, `tract_injection`, `nasal_branch`, and `tract` commands populate acoustic records as compatibility aliases while the current `Voice.Tract` Faust renderer still owns audio.
- `acoustic` / `acoustic_voice` can now render an `AcousticPortNetwork` through a compact Faust response proxy or, when typed terminals are present, a compiled bidirectional Faust segment graph. Graph lowering splits paths at terminal positions, aggregates co-located terminals into graph nodes, orders connection ports deterministically, scatters area-weighted/pressure/bypass connection groups, injects source terminals even at connected nodes, and sums radiation terminals. Segment delay follows physical segment length and the network `wave_clock` strategy through unit-grid, half-sample, linear fractional, Lagrange, or Thiran/allpass Faust delay primitives. Source, branch, radiation, terminal, and connection controls can bind through ordinary Aqua parameters into emitted Faust. PT-shaped `tract` authoring can opt into graph audio with `propagation=graph`; legacy `propagation=waveguide` remains the comparison baseline. Remaining pressure: deepen connection-law semantics and run log-mel parity against the graph renderer. Do not expand the proxy into a second fake synthesizer.
- Pink Trombone log-mel parity now writes both legacy waveguide and graph candidates. Latest graph evidence after source/radiation/side-branch/frication cuts: graph open/front cosine is 0.6202/0.6417, still slightly above the legacy waveguide lane at 0.6143/0.6349; nasal graph cosine improved to 0.5159 and RMS ratio dropped from 1.6914 to 1.4815; sibilant graph cosine remains weak at 0.2302; closure-release graph cosine stays positive at 0.1145 and beats the old negative waveguide baseline. The useful changes were LF-style glottal shaping with a modest harmonic pressure blend, graph radiation high-pass leakage by radiation kind, aperture-weighted high-passed `TurbulenceJet` source semantics, terminal-specific admittance in connection pressure, floored area-ratio pressure normalization for branch outputs, and generated frication ports using max(turbulence, burst) as pressure instead of burst alone. Raw per-port outgoing coupling, squared generated velum opening, lip/beak radiation gain, and un-gated turbulence noise were cut as compensators. Remaining pressure is oral RMS/energy normalization and better frication/transient behavior, not more static shape paperwork.
- PT expressive parity should decompose into reusable Aqua primitives before audio golf: `tract_shape` owns continuous diameter/area geometry over normalized position, physical length (`length_cm`), interpolation/resampling, derived reflection coefficients, and per-cell acoustic delay; `tract` may resample that geometry to a chosen compiled `sections` grid without making authoring sample count anatomy; `glottis` owns excitation controls; `tract_injection` owns positioned frication/burst controls and now distributes frication pressure into waveguide section updates; `nasal_branch` owns a second diameter/area tube; `tract_motion` owns control slew and obstruction threshold; `propagation=waveguide` lowers tract voices into a Faust-friendly `wg_loop ~ si.bus(...)` feedback component with compiled right/left traveling-wave state, live per-section diameter/area/reflection fields, obstruction-history burst pressure, and legacy substep clock intent. Waveguide tracts now default to an acoustic unit-delay grid from physical length at the current 44.1 kHz target, so a 17 cm oral tract emits about 22 sections instead of treating PT's 44 half-sample cells as anatomy; `VocalTract.IndexScale` maps runtime PT-index controls onto that emitted grid. PT's 44 oral and 28 nasal cells are reference discretization, not Aqua morphology authority. `PinkTromboneParityFixtures` defines fixed Aqua DSL workouts, `PinkTromboneReferenceRenderer` renders deterministic PT-style fixture references, and `AudioComparison` includes log-mel cosine similarity. Current waveguide baseline is weak but measured: cosine about 0.61 open vowel, 0.63 front vowel, 0.54 nasal, 0.37 sibilant, and -0.08 closure-release. Remaining low-level gap is fractional-delay waveguide lowering whose clock derives from physical length, propagation speed, and sample rate so runtime morphology motion does not require recompilation; closure/release timing is still badly wrong.
- PT expressive parity should decompose into reusable Aqua primitives before audio golf: `tract_shape` owns continuous diameter/area geometry over normalized position, physical length (`length_cm`), interpolation/resampling, derived reflection coefficients, and per-cell acoustic delay; `tract` may resample that geometry to a chosen compiled `sections` grid without making authoring sample count anatomy; `glottis` owns excitation controls; `tract_injection` owns positioned frication/burst controls and now distributes frication pressure into waveguide section updates; `nasal_branch` owns a second diameter/area tube; `tract_motion` owns control slew and obstruction threshold; `propagation=waveguide` lowers tract voices into a Faust-friendly `wg_loop ~ si.bus(...)` feedback component with compiled right/left traveling-wave state, live per-section diameter/area/reflection fields, obstruction-history burst pressure, and legacy substep clock intent. Waveguide tracts now default to an acoustic unit-delay grid from physical length at the current 44.1 kHz target, so a 17 cm oral tract emits about 22 sections instead of treating PT's 44 half-sample cells as anatomy; `VocalTract.IndexScale` maps runtime PT-index controls onto that emitted grid. PT's 44 oral and 28 nasal cells are reference discretization, not Aqua morphology authority. `PinkTromboneParityFixtures` defines fixed Aqua DSL workouts, `PinkTromboneReferenceRenderer` now ports the pinned MIT `chdh/pink-trombone-mod` DSP source (`external/pink-trombone-mod` at 359c2d3b42b10280404c1650dc601902112b4c90), and `AudioComparison` includes log-mel cosine similarity. Reference WAVs before `artifacts/parity/pink-trombone-utterances/20260526T211902375` came from the old fake renderer and are invalid. Do not resume Aqua log-mel golf until the source-ported PT utterance WAVs have been listened to and accepted as a sane oracle. Remaining low-level Aqua gap is fractional-delay waveguide lowering whose clock derives from physical length, propagation speed, and sample rate so runtime morphology motion does not require recompilation; closure/release timing is still badly wrong.
- If dotnet builds hang in this workspace, stop orphan dotnet workers and retry with `--disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false`; plain parallel/shared builds can stall without useful diagnostics.
- eSpeak NG is an early parity reference for timing, IPA coverage, and log-mel pressure, not an anatomy oracle or final target.
- Do not expand into prosody, emotional context, personality, or broad Weksa expressivity until the tiny IPA utterance harness can show useful loss movement.