Report graph scatter passivity probes
This commit is contained in:
parent
c1b653ed4c
commit
95366b05e5
5 changed files with 129 additions and 6 deletions
|
|
@ -176,6 +176,12 @@ patches can read those bargraph zones after processing blocks, so passivity
|
|||
checks can sample probe values over time without turning probes into audio
|
||||
channels.
|
||||
|
||||
The first native probe report on the nasal-vowel graph shows same-path and
|
||||
nasal branch energy ratios essentially at 1.0. That is useful: the current
|
||||
tract does not appear to be exploding at the basic scatter sites. The audible
|
||||
failure is more likely missing frequency-dependent tract losses/radiation color
|
||||
and closure pressure storage than a gross non-passive junction.
|
||||
|
||||
The next coherent implementation target is to make that law passive and
|
||||
radiation-aware:
|
||||
|
||||
|
|
|
|||
|
|
@ -683,6 +683,7 @@ public static class FaustEmitter
|
|||
.Concat(segments.Select(segment => $"l{segment.Index}"))
|
||||
.ToList();
|
||||
var nextStates = new List<string>(stateCount);
|
||||
var debugProbeKeepAlive = new List<string>();
|
||||
source.AppendLine($"{name}_graph(x) = {name}_graph_loop ~ si.bus({stateCount}) : (si.block({stateCount}), _) with {{");
|
||||
source.AppendLine($" {name}_graph_loop({string.Join(", ", stateInputs)}) = {string.Join(", ", NextStatesPlaceholder(stateCount))}, {name}_graph_out with {{");
|
||||
|
||||
|
|
@ -723,7 +724,7 @@ public static class FaustEmitter
|
|||
}
|
||||
|
||||
var safeConnection = SafeIdentifier(connection.Name);
|
||||
if (TryEmitThreePortBranchScatter(source, name, connection, safeConnection, ports, options))
|
||||
if (TryEmitThreePortBranchScatter(source, name, connection, safeConnection, ports, options, debugProbeKeepAlive))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -764,7 +765,7 @@ public static class FaustEmitter
|
|||
|
||||
var sourceInjection = NodeSourceIdentifier(name, node);
|
||||
var reflection = $"{name}_graph_node_reflection_{node.Name}";
|
||||
if (TryEmitTwoPortPathScatter(source, name, node, ports, sourceInjection, options))
|
||||
if (TryEmitTwoPortPathScatter(source, name, node, ports, sourceInjection, options, debugProbeKeepAlive))
|
||||
{
|
||||
// Emitted as a local area-discontinuity junction.
|
||||
}
|
||||
|
|
@ -822,7 +823,12 @@ public static class FaustEmitter
|
|||
}
|
||||
nextStates.AddRange(segments.Select(segment => $"{name}_graph_next_l{segment.Index}"));
|
||||
source.Replace(string.Join(", ", NextStatesPlaceholder(stateCount)), string.Join(", ", nextStates));
|
||||
source.AppendLine($" {name}_graph_out = ({(radiated.Count == 0 ? "0.0" : string.Join(" + ", radiated))}) * {name}_graph_radiation_gain;");
|
||||
var graphOutput = $"({(radiated.Count == 0 ? "0.0" : string.Join(" + ", radiated))}) * {name}_graph_radiation_gain";
|
||||
if (options.DebugProbeUi && debugProbeKeepAlive.Count > 0)
|
||||
{
|
||||
graphOutput = $"attach(({graphOutput}), {string.Join(" + ", debugProbeKeepAlive)})";
|
||||
}
|
||||
source.AppendLine($" {name}_graph_out = {graphOutput};");
|
||||
source.AppendLine(" };");
|
||||
source.AppendLine("};");
|
||||
source.AppendLine($"{name}_acoustic_graph_radiated = {name}_graph({name}_graph_drive);");
|
||||
|
|
@ -1625,7 +1631,8 @@ public static class FaustEmitter
|
|||
AcousticConnection connection,
|
||||
string safeConnection,
|
||||
IReadOnlyList<(AcousticGraphNode node, AcousticTerminal terminal, AcousticGraphPort port, int nodePortCount)> ports,
|
||||
FaustExportOptions options)
|
||||
FaustExportOptions options,
|
||||
List<string> debugProbeKeepAlive)
|
||||
{
|
||||
if (ports.Count != 3 || connection.Law != AcousticConnectionLaw.AreaScattering)
|
||||
{
|
||||
|
|
@ -1673,6 +1680,8 @@ public static class FaustEmitter
|
|||
var energyOut = string.Join(" + ", portItems.Select(item => $"({areaExpressions[item.port]}) * pow({GraphOutgoing(voiceName, item.port)}, 2.0)"));
|
||||
source.AppendLine($" {voiceName}_graph_connection_energy_in_{safeConnection} = {ProbeSignal(options, $"/debug/{voiceName}/connection/{connection.Name}/energy_in", energyIn, 0, 4)};");
|
||||
source.AppendLine($" {voiceName}_graph_connection_energy_out_{safeConnection} = {ProbeSignal(options, $"/debug/{voiceName}/connection/{connection.Name}/energy_out", energyOut, 0, 4)};");
|
||||
debugProbeKeepAlive.Add($"{voiceName}_graph_connection_energy_in_{safeConnection}");
|
||||
debugProbeKeepAlive.Add($"{voiceName}_graph_connection_energy_out_{safeConnection}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1683,7 +1692,8 @@ public static class FaustEmitter
|
|||
AcousticGraphNode node,
|
||||
IReadOnlyList<AcousticGraphPort> ports,
|
||||
string sourceInjection,
|
||||
FaustExportOptions options)
|
||||
FaustExportOptions options,
|
||||
List<string> debugProbeKeepAlive)
|
||||
{
|
||||
if (ports.Count != 2 ||
|
||||
!ports[0].Segment.Path.Name.Equals(ports[1].Segment.Path.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||
|
|
@ -1709,6 +1719,8 @@ public static class FaustEmitter
|
|||
var energyOut = $"({firstArea}) * pow({firstOutgoing}, 2.0) + ({secondArea}) * pow({secondOutgoing}, 2.0)";
|
||||
source.AppendLine($" {voiceName}_graph_area_energy_in_{node.Name} = {ProbeSignal(options, $"/debug/{voiceName}/area/{node.Name}/energy_in", energyIn, 0, 4)};");
|
||||
source.AppendLine($" {voiceName}_graph_area_energy_out_{node.Name} = {ProbeSignal(options, $"/debug/{voiceName}/area/{node.Name}/energy_out", energyOut, 0, 4)};");
|
||||
debugProbeKeepAlive.Add($"{voiceName}_graph_area_energy_in_{node.Name}");
|
||||
debugProbeKeepAlive.Add($"{voiceName}_graph_area_energy_out_{node.Name}");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -187,4 +187,5 @@
|
|||
{"evidence":"Added generated Faust `graph_area_energy_in_*` and `graph_area_energy_out_*` probes for ordinary same-path two-port area discontinuities and fenced them with structural tests.","claim":"Vowel-body failures need observability at the tract scatter sites, not only at radiation output. Area-weighted energy probes now exist for both same-path discontinuities and three-port branches before changing the scattering law again.","timestamp":"2026-05-27T17:50:00+01:00","topic":"acoustic-graph-area-energy-probes"}
|
||||
{"evidence":"Added `FaustExportOptions.DebugProbeUi`, wrapping graph radiation flow/admittance and area/connection energy probes in Faust `vbargraph` UI signals under `/debug/...`. Focused structural and Faust validation tests passed with debug probes enabled.","claim":"Graph passivity probes need a telemetry surface, not extra audio outputs. Faust UI bargraphs expose internal signal witnesses without changing normal render arity or contaminating parity audio.","timestamp":"2026-05-27T17:58:00+01:00","topic":"acoustic-graph-debug-probe-ui"}
|
||||
{"evidence":"Extended Mimir's native streaming Faust path to split writable controls from read-only bargraph probe zones. `AquaSynthCompiledPatch.ProbePaths`, `AquaSynthStreamingPatch.ProbePaths`, `ReadProbe`, and `SnapshotProbes` now expose debug probe telemetry after processing blocks. Focused native streaming, debug-probe, graph, and Faust-validation tests passed.","claim":"Graph probe UI is now measurable through the native streaming host without changing DSP audio outputs. This is the right observability layer for passivity and radiation debugging: process blocks, sample `/debug/...` bargraphs, then compare scatter-site energy instead of inferring everything from rendered WAVs.","timestamp":"2026-05-27T18:14:00+01:00","topic":"native-faust-debug-probe-sampling"}
|
||||
{"evidence":"Added `PinkTromboneGraphDebugProbesWritePassivityReportWhenNativeFaustIsInstalled`; focused probe and native tests passed. Latest artifact `artifacts/parity/pink-trombone-graph-probes/20260527T200828013/passivity-report.txt` shows same-path area scatter and `voices_0_nose_connection` energy out/in ratios at 1.0 for nasal-vowel.","claim":"The current graph's basic same-path and nasal branch scatter are not grossly non-passive under the nasal-vowel probe. The audible speech failure is more likely missing frequency-dependent tract losses/radiation color and closure pressure storage than scatter-site energy explosion.","timestamp":"2026-05-27T20:10:00+01:00","topic":"acoustic-graph-passivity-probe-report"}
|
||||
{"evidence":"Added `AquaSynthStreamingPatch` on top of the existing native Faust factory/runtime. Compiled patches can now keep a DSP instance alive, cache Faust control zones, report input/output counts, set controls without rebuilding the UI map, and process either raw channel-pointer blocks or managed float[][] blocks. `AquaSynth.Faust` is bumped to 0.1.0-alpha.4 for downstream Fensalir consumption. `dotnet build AquaSynth.sln --no-restore` passed before the existing dirty `src/AquaSynth.Core/FaustExport.cs` worktree edit blocked focused test rebuilds with unrelated compile errors.","claim":"AquaSynth owns streaming Faust DSP sessions as the reusable compiler/runtime boundary; host engines should use the persistent session for audio-rate processing instead of one-shot offline render. The pointer-based API is the realtime surface; the managed array helper is a test/convenience lane.","timestamp":"2026-05-27T18:49:28+01:00","topic":"native-faust-streaming-session"}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
project: AquaSynth
|
||||
spine_version: 1
|
||||
updated: 2026-05-27T18:14:00+01:00
|
||||
updated: 2026-05-27T20:10:00+01:00
|
||||
|
||||
prime_directive: >
|
||||
Build coherent synth machinery. References exist to reveal missing structure,
|
||||
|
|
@ -125,6 +125,7 @@ live_invariants:
|
|||
- 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.
|
||||
- `FaustExportOptions.DebugProbeUi` wraps graph probe signals in Faust `vbargraph` UI controls under `/debug/...` without changing audio output arity. Native streaming Faust patches now separate writable controls from read-only probe zones and can read/snapshot probe values after `ProcessBlock`; use this for acoustic graph passivity/radiation inspection, leave it off for normal parity renders.
|
||||
- `PinkTromboneGraphDebugProbesWritePassivityReportWhenNativeFaustIsInstalled` streams the `nasal-vowel` graph with debug probes and writes `artifacts/parity/pink-trombone-graph-probes/<timestamp>/passivity-report.txt`. Latest run `20260527T200828013` shows same-path area and nasal-branch energy out/in ratios at 1.0, so current gross failure is not basic scatter energy explosion; next cuts should inspect frequency-dependent loss/radiation color and closure pressure storage.
|
||||
- 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; `AcousticAreaControl` owns live sampled morphology controls on a path; `WaveClockPolicy` owns the legal delay floor instead of forcing every segment to one sample; generated graph tracts now use `HalfSampleGrid` to match the PT/Kelly-Lochbaum half-sample regime; ordinary same-path graph interiors now own signed two-port area-discontinuity scattering; `glottis` owns excitation controls with mild harmonic waveshaping and a small higher-harmonic brightness term for graph sources; `tract_injection` owns positioned frication/burst controls; `AcousticSourcePort.Transient` owns graph-native release/click energy as a pressure pulse rather than filtered noise; sustained `TurbulenceJet` lowering owns a narrow-open aperture gate so closed constrictions no longer hiss; `AcousticSourcePositionControl` owns differentiable runtime weights over fixed source terminals so moving articulators can move source energy without recompiling the graph; `AcousticRadiationPort` owns aperture-dependent boundary reflection, flow radiation, and current post-flow normalization; `nasal_branch` owns a second diameter/area tube and branch aperture while `AcousticConnection.Coupling` owns structural max admittance; `tract_motion` owns control slew and obstruction threshold; `AcousticPortNetwork` owns graph lowering. `PinkTromboneReferenceRenderer` ports the pinned MIT `chdh/pink-trombone-mod` DSP source (`external/pink-trombone-mod` at 359c2d3b42b10280404c1650dc601902112b4c90), and reference WAVs before `artifacts/parity/pink-trombone-utterances/20260526T211902375` are invalid. The accepted PT utterance curriculum is mama, papa, and thrombosis; lulek was cut because its control sketch taught a bad artifact. Latest graph-only utterance smoke under `artifacts/parity/pink-trombone-utterance-logmel/20260527T150836912`: mama/papa/thrombosis remain `not-accepted-articulation` with cosine 0.8337/0.8893/0.5317 and articulation scores 0.2118/0.1890/0.2594. `AudioComparison.Articulation` reports envelope cosine, active-frame ratio, silence mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio, and an articulation score so log-mel cannot forge a listening receipt. A direct LF coefficient source was tested and cut because it overdrove the graph and broke static open vowel; a broader harmonic-tail source was tested and cut because it worsened voiced-band behavior. A naive midpoint admittance scatter was tested and cut because it collapsed `mama`; the kept area-discontinuity scatter needed the opposite reflection sign for this graph state convention, and re-testing the literal SndKit sign after the clock fix made `thrombosis` loud/unstable. A long noisy release transient was tested and cut because it turned papa into motor-band energy; deterministic pressure release with PT-scale decay is structurally kept but still not sufficient. Next pressure is true graph pressure storage/release at severe closures; output coloring alone is not allowed to impersonate articulation.
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -130,6 +130,84 @@ public sealed class PinkTromboneLogMelParityTests
|
|||
File.WriteAllText(Path.Combine(artifactDir, "summary.txt"), string.Join(Environment.NewLine + Environment.NewLine, reports));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PinkTromboneGraphDebugProbesWritePassivityReportWhenNativeFaustIsInstalled()
|
||||
{
|
||||
var fixture = PinkTromboneParityFixtures.ById("nasal-vowel");
|
||||
var export = FaustEmitter.EmitScript(
|
||||
fixture.AquaScript,
|
||||
new FaustExportOptions("pt_probe_nasal_vowel", DebugProbeUi: true));
|
||||
var artifactDir = ArtifactPath("parity", "pink-trombone-graph-probes", DateTimeOffset.UtcNow.ToString("yyyyMMddTHHmmssfff", CultureInfo.InvariantCulture));
|
||||
|
||||
using var compiler = new AquaSynthPatchCompiler();
|
||||
if (!compiler.TryCompileSource(
|
||||
new AquaSynthCompileIdentity("pt_probe_nasal_vowel", "pt_probe_nasal_vowel", export.Source),
|
||||
export.Source,
|
||||
0.57f,
|
||||
out var patch,
|
||||
out var error))
|
||||
{
|
||||
if (error?.Contains("Faust toolchain not found", StringComparison.OrdinalIgnoreCase) == true ||
|
||||
error?.Contains("Faust DLL not found", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Fail($"Native Faust graph probe compile failed: {error}{Environment.NewLine}artifacts: {artifactDir}");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(artifactDir);
|
||||
File.WriteAllText(Path.Combine(artifactDir, "candidate-debug.dsp"), export.Source);
|
||||
|
||||
using (patch)
|
||||
using (var stream = patch!.CreateStreamingPatch())
|
||||
{
|
||||
Assert.Contains(stream.ProbePaths, path => path.Contains("/energy_in", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains(stream.ProbePaths, path => path.Contains("/energy_out", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains(stream.ProbePaths, path => path.Contains("/radiation/", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var peaks = new Dictionary<string, ProbePeak>(StringComparer.Ordinal);
|
||||
var blockSize = 128;
|
||||
var frames = Math.Max(1, (int)MathF.Ceiling(0.57f * patch.Manifest.SampleRate));
|
||||
var inputs = Enumerable.Range(0, stream.InputCount).Select(_ => new float[blockSize]).ToArray();
|
||||
var outputs = Enumerable.Range(0, stream.OutputCount).Select(_ => new float[blockSize]).ToArray();
|
||||
for (var offset = 0; offset < frames; offset += blockSize)
|
||||
{
|
||||
var count = Math.Min(blockSize, frames - offset);
|
||||
stream.ProcessBlock(inputs, outputs, count);
|
||||
foreach (var (path, value) in stream.SnapshotProbes())
|
||||
{
|
||||
if (!TryProbeBase(path, out var basePath, out var kind))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
peaks.TryGetValue(basePath, out var peak);
|
||||
peak = kind == "energy_in"
|
||||
? peak with { EnergyIn = Math.Max(peak.EnergyIn, MathF.Abs(value)) }
|
||||
: peak with { EnergyOut = Math.Max(peak.EnergyOut, MathF.Abs(value)) };
|
||||
peaks[basePath] = peak;
|
||||
}
|
||||
}
|
||||
|
||||
var lines = peaks
|
||||
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
|
||||
.Select(pair =>
|
||||
{
|
||||
var ratio = pair.Value.EnergyOut / Math.Max(1e-6f, pair.Value.EnergyIn);
|
||||
return string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"{pair.Key},energy_in_peak={pair.Value.EnergyIn:0.000000},energy_out_peak={pair.Value.EnergyOut:0.000000},out_in_ratio={ratio:0.000000}");
|
||||
})
|
||||
.ToArray();
|
||||
File.WriteAllLines(Path.Combine(artifactDir, "passivity-report.txt"), lines);
|
||||
|
||||
Assert.NotEmpty(lines);
|
||||
Assert.Contains(lines, line => line.Contains("/connection/", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains(lines, line => line.Contains("/area/", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
private static string Report(PinkTromboneParityFixture fixture, AudioComparison comparison, string candidate) =>
|
||||
string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
|
|
@ -153,6 +231,31 @@ public sealed class PinkTromboneLogMelParityTests
|
|||
return "smoke-only";
|
||||
}
|
||||
|
||||
private static bool TryProbeBase(string path, out string basePath, out string kind)
|
||||
{
|
||||
const string energyIn = "/energy_in";
|
||||
const string energyOut = "/energy_out";
|
||||
if (path.EndsWith(energyIn, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
basePath = path[..^energyIn.Length];
|
||||
kind = "energy_in";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path.EndsWith(energyOut, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
basePath = path[..^energyOut.Length];
|
||||
kind = "energy_out";
|
||||
return true;
|
||||
}
|
||||
|
||||
basePath = "";
|
||||
kind = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly record struct ProbePeak(float EnergyIn, float EnergyOut);
|
||||
|
||||
private static string AutomatedGraphSource(PinkTromboneUtteranceFixture fixture)
|
||||
{
|
||||
var source = FaustEmitter.EmitScript(UtteranceGraphScript(fixture), new FaustExportOptions($"pt_utterance_{fixture.Id}")).Source;
|
||||
|
|
|
|||
Loading…
Reference in a new issue