From fa8c6e2fe900d3bb2b90adfe37202fdf1fd2bf90 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 27 May 2026 18:51:23 +0100 Subject: [PATCH] Expose graph probes through Faust UI --- docs/acoustic-graph-flow-audit.md | 5 ++ src/AquaSynth.Core/FaustExport.cs | 57 ++++++++++++------- state/evidence.jsonl | 2 + state/spine.yaml | 3 +- tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs | 7 ++- 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/docs/acoustic-graph-flow-audit.md b/docs/acoustic-graph-flow-audit.md index c811df5..68d041a 100644 --- a/docs/acoustic-graph-flow-audit.md +++ b/docs/acoustic-graph-flow-audit.md @@ -168,6 +168,11 @@ the only loudness fault. Ordinary two-port area discontinuities now expose the same area-weighted energy probes, so vowel-body scattering can be inspected at the layer where tract resonances should actually form. +By default those probes remain ordinary named Faust locals. When +`FaustExportOptions.DebugProbeUi` is enabled, the same signals are wrapped in +Faust `vbargraph` UI probes under `/debug/...`, exposing passivity and radiation +telemetry without changing the patch's audio output arity. + The next coherent implementation target is to make that law passive and radiation-aware: diff --git a/src/AquaSynth.Core/FaustExport.cs b/src/AquaSynth.Core/FaustExport.cs index 4851e76..ca444fd 100644 --- a/src/AquaSynth.Core/FaustExport.cs +++ b/src/AquaSynth.Core/FaustExport.cs @@ -4,7 +4,7 @@ using System.Text; namespace AquaSynth.Dsl; -public sealed record FaustExportOptions(string Name = "aquasynth_patch", bool Stereo = false); +public sealed record FaustExportOptions(string Name = "aquasynth_patch", bool Stereo = false, bool DebugProbeUi = false); public sealed record FaustExport(string Source, IReadOnlyList Warnings); @@ -77,13 +77,13 @@ public static class FaustEmitter for (var i = 0; i < patch.Voices.Count; i++) { var name = $"voice_{i}"; - EmitVoice(source, patch, patch.Voices[i], VoicePath(i), name, parameters, warnings); + EmitVoice(source, patch, patch.Voices[i], VoicePath(i), name, parameters, warnings, options); voices.Add(name); } for (var i = 0; i < patch.SpectralBanks.Count; i++) { var name = $"spectral_{i}"; - EmitSpectralBank(source, patch, patch.SpectralBanks[i], i, name, parameters, warnings); + EmitSpectralBank(source, patch, patch.SpectralBanks[i], i, name, parameters, warnings, options); voices.Add(name); } for (var i = 0; i < patch.OperatorGraphs.Count; i++) @@ -140,6 +140,7 @@ public static class FaustEmitter string name, ParameterMap parameters, List warnings, + FaustExportOptions options, string? oscillatorOverride = null) { if (voice.Filter.LowPassResonance != 0 || @@ -183,12 +184,12 @@ public static class FaustEmitter var dutyExpression = $"clip01({parameters.Expression(OwnerField(ownerPath, "osc/duty"), voice.Oscillator.Duty)} + {parameters.Expression(OwnerField(ownerPath, "duty/ramp"), voice.Duty.RampPerSecond)} * age + patch_mod_duty + {duty})"; var fmIndex = $"max(0.0, {parameters.Expression(OwnerField(ownerPath, "fm/index"), voice.Fm.Index)} + patch_mod_fm_index + {fmIndexMod}) * {FmDecay(parameters.Expression(OwnerField(ownerPath, "fm/decay"), voice.Fm.IndexDecaySeconds), voice.Fm.IndexDecaySeconds, parameters.IsBound(OwnerField(ownerPath, "fm/decay")))}"; var oscillator = oscillatorOverride ?? - (voice.Tract is { Propagation: TractPropagationMode.Graph } && voice.AcousticNetwork is { } tractGraph - ? AcousticNetworkExpression(source, patch, tractGraph, ownerPath, name, frequency, parameters, warnings) + (voice.Tract is { Propagation: TractPropagationMode.Graph } && voice.AcousticNetwork is { } tractGraph + ? AcousticNetworkExpression(source, patch, tractGraph, ownerPath, name, frequency, parameters, warnings, options) : voice.Tract is { } tract ? TractExpression(source, tract, ownerPath, name, frequency, noteGate, parameters) : voice.AcousticNetwork is { } acousticNetwork - ? AcousticNetworkExpression(source, patch, acousticNetwork, ownerPath, name, frequency, parameters, warnings) + ? AcousticNetworkExpression(source, patch, acousticNetwork, ownerPath, name, frequency, parameters, warnings, options) : OscillatorExpression(patch, voice, ownerPath, frequency, dutyExpression, fmIndex, parameters)); var envelope = voice.RateLevelEnvelope is not null ? RateLevelEnvelopeExpression(voice.RateLevelEnvelope, noteGate) @@ -299,7 +300,8 @@ public static class FaustEmitter int bankIndex, string name, ParameterMap parameters, - List warnings) + List warnings, + FaustExportOptions options) { const int tableSize = 131072; var table = PadSynthWaveform.Generate(bank, tableSize); @@ -311,7 +313,7 @@ public static class FaustEmitter source.AppendLine($"{name}_read_frac = {name}_read_pos - float({name}_read_index);"); source.AppendLine($"{name}_read_next = ({name}_read_index + 1) % {tableSize};"); source.AppendLine($"{name}_wavetable = ({name}_wave, {name}_read_index : rdtable) * (1 - {name}_read_frac) + ({name}_wave, {name}_read_next : rdtable) * {name}_read_frac;"); - EmitVoice(source, patch, bank.Treatment, SpectralPath(bankIndex), name, parameters, warnings, $"{name}_wavetable"); + EmitVoice(source, patch, bank.Treatment, SpectralPath(bankIndex), name, parameters, warnings, options, $"{name}_wavetable"); } private static string NoteFrequencyExpression(StringBuilder source, Playback playback, Note note, string name, string fieldPath, ParameterMap parameters) @@ -394,7 +396,8 @@ public static class FaustEmitter string name, string frequency, ParameterMap parameters, - List warnings) + List warnings, + FaustExportOptions options) { var paths = patch.AcousticPaths.ToDictionary(path => path.Name, StringComparer.OrdinalIgnoreCase); var sources = patch.AcousticSourcePorts.ToDictionary(port => port.Name, StringComparer.OrdinalIgnoreCase); @@ -411,7 +414,7 @@ public static class FaustEmitter if (network.Terminals.Count > 0) { - var graph = AcousticGraphExpression(source, patch, network, name, frequency, parameters, warnings, paths, sources, radiation, terminals, connections, waveClocks); + var graph = AcousticGraphExpression(source, patch, network, name, frequency, parameters, warnings, paths, sources, radiation, terminals, connections, waveClocks, options); if (graph.Length > 0) { return graph; @@ -518,7 +521,8 @@ public static class FaustEmitter IReadOnlyDictionary radiation, IReadOnlyDictionary terminals, IReadOnlyDictionary connections, - IReadOnlyDictionary waveClocks) + IReadOnlyDictionary waveClocks, + FaustExportOptions options) { var waveClock = ResolveWaveClock(network, waveClocks, warnings, name); var networkTerminals = new List(); @@ -719,7 +723,7 @@ public static class FaustEmitter } var safeConnection = SafeIdentifier(connection.Name); - if (TryEmitThreePortBranchScatter(source, name, connection, safeConnection, ports)) + if (TryEmitThreePortBranchScatter(source, name, connection, safeConnection, ports, options)) { continue; } @@ -760,7 +764,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)) + if (TryEmitTwoPortPathScatter(source, name, node, ports, sourceInjection, options)) { // Emitted as a local area-discontinuity junction. } @@ -800,8 +804,8 @@ public static class FaustEmitter 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} = sqrt(clip01(({terminalArea}) / max(0.000001, ({terminalArea}) + 1.0)));"); - source.AppendLine($" {flowName} = {flow};"); + source.AppendLine($" {admittance} = {ProbeSignal(options, $"/debug/{name}/radiation/{terminal.Name}/admittance", $"sqrt(clip01(({terminalArea}) / max(0.000001, ({terminalArea}) + 1.0)))", 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) * {opening} * {loss} * {admittance})"); } } @@ -1620,7 +1624,8 @@ public static class FaustEmitter string voiceName, AcousticConnection connection, string safeConnection, - IReadOnlyList<(AcousticGraphNode node, AcousticTerminal terminal, AcousticGraphPort port, int nodePortCount)> ports) + IReadOnlyList<(AcousticGraphNode node, AcousticTerminal terminal, AcousticGraphPort port, int nodePortCount)> ports, + FaustExportOptions options) { if (ports.Count != 3 || connection.Law != AcousticConnectionLaw.AreaScattering) { @@ -1664,8 +1669,10 @@ public static class FaustEmitter var sourceInjection = NodeSourceIdentifier(voiceName, item.node); source.AppendLine($" {outgoing} = ({voiceName}_graph_connection_pressure_{safeConnection} - {incoming} + ({sourceInjection}) / {F(Math.Max(1, item.nodePortCount))}) * {voiceName}_graph_connection_loss_{safeConnection};"); } - source.AppendLine($" {voiceName}_graph_connection_energy_in_{safeConnection} = {string.Join(" + ", portItems.Select(item => $"({areaExpressions[item.port]}) * pow({GraphIncoming(voiceName, item.port)}, 2.0)"))};"); - source.AppendLine($" {voiceName}_graph_connection_energy_out_{safeConnection} = {string.Join(" + ", portItems.Select(item => $"({areaExpressions[item.port]}) * pow({GraphOutgoing(voiceName, item.port)}, 2.0)"))};"); + var energyIn = string.Join(" + ", portItems.Select(item => $"({areaExpressions[item.port]}) * pow({GraphIncoming(voiceName, item.port)}, 2.0)")); + 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)};"); return true; } @@ -1675,7 +1682,8 @@ public static class FaustEmitter string voiceName, AcousticGraphNode node, IReadOnlyList ports, - string sourceInjection) + string sourceInjection, + FaustExportOptions options) { if (ports.Count != 2 || !ports[0].Segment.Path.Name.Equals(ports[1].Segment.Path.Name, StringComparison.OrdinalIgnoreCase) || @@ -1697,11 +1705,18 @@ public static class FaustEmitter source.AppendLine($" {voiceName}_graph_area_reflection_{node.Name} = {reflection};"); source.AppendLine($" {firstOutgoing} = {secondIncoming} + {voiceName}_graph_area_reflection_{node.Name} * ({firstIncoming} + {secondIncoming}) + ({sourceInjection}) / 2;"); source.AppendLine($" {secondOutgoing} = {firstIncoming} - {voiceName}_graph_area_reflection_{node.Name} * ({firstIncoming} + {secondIncoming}) + ({sourceInjection}) / 2;"); - source.AppendLine($" {voiceName}_graph_area_energy_in_{node.Name} = ({firstArea}) * pow({firstIncoming}, 2.0) + ({secondArea}) * pow({secondIncoming}, 2.0);"); - source.AppendLine($" {voiceName}_graph_area_energy_out_{node.Name} = ({firstArea}) * pow({firstOutgoing}, 2.0) + ({secondArea}) * pow({secondOutgoing}, 2.0);"); + var energyIn = $"({firstArea}) * pow({firstIncoming}, 2.0) + ({secondArea}) * pow({secondIncoming}, 2.0)"; + 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)};"); return true; } + private static string ProbeSignal(FaustExportOptions options, string label, string expression, float min, float max) => + options.DebugProbeUi + ? $"(({expression}) : vbargraph(\"{Escape(label)}\", {F(min)}, {F(max)}))" + : expression; + private static string MaxExpression(IEnumerable expressions) { using var enumerator = expressions.GetEnumerator(); diff --git a/state/evidence.jsonl b/state/evidence.jsonl index b3b8d3a..41aef29 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -185,3 +185,5 @@ {"evidence":"Moved graph source evaluation into recursive node context by emitting per-node incident pressure and node-local source sums, and moved generated tract injection source banks from graph nodes to cell centers with `1 / sections` index scaling. Focused structural and PT parity tests passed. Latest utterance artifact 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. Latest static artifact artifacts/parity/pink-trombone-logmel/20260527T172843945: closure-release rmsRatio 1.0078, cosine 0.6505.","claim":"Moving turbulence/release sources into graph node context and cell-centered geometry is architecturally correct even though papa articulation still worsens. PT-class source injection is cell-local wave excitation, not a click born on the radiation terminal; the remaining plosive failure points to missing upstream pressure storage rather than source placement.","timestamp":"2026-05-27T17:32:00+01:00","topic":"acoustic-graph-cell-centered-sources"} {"evidence":"Added a release-timed burst-noise term to `TurbulenceJet` and softened radiation from an over-bright 15/85 raw/highpassed boundary-flow blend to 30/70 after local admittance weighting. Focused PT parity tests passed. Latest utterance artifact 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. Latest static artifact artifacts/parity/pink-trombone-logmel/20260527T174139661: closure-release rmsRatio 0.9900, speechBandRatio 0.0842.","claim":"Plosive release should include turbulent burst energy, and radiation should not be an almost pure differentiator when tract body is weak. The kept change is modest because stronger source burst did not solve papa; remaining speech failure is tract resonance/scattering and pressure storage, not source loudness.","timestamp":"2026-05-27T17:45:00+01:00","topic":"acoustic-graph-release-burst-radiation-slope"} {"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":"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"} diff --git a/state/spine.yaml b/state/spine.yaml index 03d71c5..2a7e09a 100644 --- a/state/spine.yaml +++ b/state/spine.yaml @@ -1,6 +1,6 @@ project: AquaSynth spine_version: 1 -updated: 2026-05-27T17:50:00+01:00 +updated: 2026-05-27T17:58:00+01:00 prime_directive: > Build coherent synth machinery. References exist to reveal missing structure, @@ -124,6 +124,7 @@ live_invariants: - 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. + - `FaustExportOptions.DebugProbeUi` wraps graph probe signals in Faust `vbargraph` UI controls under `/debug/...` without changing audio output arity. Use it for acoustic graph passivity/radiation inspection; leave it off for normal parity renders. - 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. diff --git a/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs b/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs index 7de9fbd..e586e3f 100644 --- a/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs +++ b/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs @@ -329,6 +329,11 @@ public sealed class PatchScriptTests 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); + + var debugExport = FaustEmitter.Emit(patch, new FaustExportOptions("tract_graph_boundaries_debug", DebugProbeUi: true)); + Assert.Contains("vbargraph(\"/debug/voice_0/connection/voices_0_nose_connection/energy_in\"", debugExport.Source); + Assert.Contains("vbargraph(\"/debug/voice_0/radiation/voices_0_lip/flow\"", debugExport.Source); + Assert.Contains("process = ", debugExport.Source); } [Fact] @@ -1282,7 +1287,7 @@ public sealed class PatchScriptTests wave_clock name=continuous strategy=thiran order=1 max_delay=4096 smoothing_ms=3 acoustic_network name=humanish path=oral wave_clock=continuous sources=folds radiation=mouth,nostril terminals=trachea_bottom,oral_back,nasal_gate connections=velopharynx acoustic network=humanish freq=130 gain=.1 - """, new FaustExportOptions("acoustic_graph_validation")); + """, new FaustExportOptions("acoustic_graph_validation", DebugProbeUi: true)); var validation = await FaustCompiler.ValidateAsync(export.Source); if (validation is null)