Add thrombosis graph timeline probes
This commit is contained in:
parent
d8a33f7e6b
commit
7e3a499593
4 changed files with 159 additions and 21 deletions
|
|
@ -709,7 +709,7 @@ public static class FaustEmitter
|
|||
: $"({string.Join(" + ", ports.Select(port => GraphIncoming(name, port)))}) / {F(ports.Count)}";
|
||||
var incidentName = $"{name}_graph_node_incident_pressure_{node.Name}";
|
||||
var nodeSourceName = NodeSourceIdentifier(name, node);
|
||||
var nodeSourceExpression = EmitNodeSourceExpression(source, patch, name, node, incident[node.Name], sources, frequency, parameters);
|
||||
var nodeSourceExpression = EmitNodeSourceExpression(source, patch, name, node, incident[node.Name], sources, frequency, parameters, options);
|
||||
source.AppendLine($" {incidentName} = {ProbeSignal(options, $"/debug/{name}/node/{node.Name}/incident_pressure", incidentPressure, -2, 2)};");
|
||||
source.AppendLine($" {nodeSourceName} = {ProbeSignal(options, $"/debug/{name}/node/{node.Name}/source", nodeSourceExpression, -2, 2)};");
|
||||
debugProbeKeepAlive.Add(incidentName);
|
||||
|
|
@ -1561,11 +1561,12 @@ public static class FaustEmitter
|
|||
IReadOnlyList<AcousticGraphPort> nodePorts,
|
||||
IReadOnlyDictionary<string, AcousticSourcePort> sources,
|
||||
string frequency,
|
||||
ParameterMap parameters)
|
||||
ParameterMap parameters,
|
||||
FaustExportOptions options)
|
||||
{
|
||||
var contactTerms = node.Terminals
|
||||
.Where(terminal => terminal.Kind == AcousticTerminalKind.Contact)
|
||||
.Select(terminal => EmitGraphContactSourceExpression(source, voiceName, node, nodePorts, terminal))
|
||||
.Select(terminal => EmitGraphContactSourceExpression(source, voiceName, node, nodePorts, terminal, options))
|
||||
.ToList();
|
||||
var sourceTerms = node.Terminals
|
||||
.Where(terminal => terminal.Kind == AcousticTerminalKind.Source && sources.ContainsKey(terminal.Port.Length == 0 ? terminal.Name : terminal.Port))
|
||||
|
|
@ -1574,8 +1575,8 @@ public static class FaustEmitter
|
|||
var sourceName = terminal.Port.Length == 0 ? terminal.Name : terminal.Port;
|
||||
var port = sources[sourceName];
|
||||
return port.Kind == AcousticSourceKind.TurbulenceJet
|
||||
? EmitGraphTurbulenceSourceExpression(source, patch, voiceName, node, nodePorts, sourceName, port, parameters)
|
||||
: EmitGraphLoadedSourceExpression(source, patch, voiceName, node, sourceName, port, frequency, parameters);
|
||||
? EmitGraphTurbulenceSourceExpression(source, patch, voiceName, node, nodePorts, sourceName, port, parameters, options)
|
||||
: EmitGraphLoadedSourceExpression(source, patch, voiceName, node, sourceName, port, frequency, parameters, options);
|
||||
})
|
||||
.ToList();
|
||||
sourceTerms.AddRange(contactTerms);
|
||||
|
|
@ -1590,7 +1591,8 @@ public static class FaustEmitter
|
|||
string sourceName,
|
||||
AcousticSourcePort port,
|
||||
string frequency,
|
||||
ParameterMap parameters)
|
||||
ParameterMap parameters,
|
||||
FaustExportOptions options)
|
||||
{
|
||||
var safeSource = SafeIdentifier(sourceName);
|
||||
var safeNode = SafeIdentifier(node.Name);
|
||||
|
|
@ -1599,7 +1601,7 @@ public static class FaustEmitter
|
|||
source.AppendLine($" {prefix}_load_pressure = {localPressure};");
|
||||
if (IsTissueValveSource(port))
|
||||
{
|
||||
EmitGraphTissueValveSourceExpression(source, patch, voiceName, node, sourceName, port, frequency, parameters, localPressure);
|
||||
EmitGraphTissueValveSourceExpression(source, patch, voiceName, node, sourceName, port, frequency, parameters, localPressure, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1617,7 +1619,8 @@ public static class FaustEmitter
|
|||
AcousticSourcePort port,
|
||||
string frequency,
|
||||
ParameterMap parameters,
|
||||
string localPressure)
|
||||
string localPressure,
|
||||
FaustExportOptions options)
|
||||
{
|
||||
var index = AcousticSourcePortIndex(patch, port);
|
||||
var path = $"/acoustic/sources/{index}";
|
||||
|
|
@ -1661,7 +1664,7 @@ public static class FaustEmitter
|
|||
source.AppendLine($" {prefix}_flow_loss = {flowLoss};");
|
||||
source.AppendLine($" {prefix}_reservoir_pressure = ({pressure}) * ({reservoirPressure});");
|
||||
source.AppendLine($" {prefix}_downstream_pressure = ({downstreamPressure}) + ({loadCoupling}) * ma.tanh({prefix}_load_pressure);");
|
||||
source.AppendLine($" {prefix}_pressure_drive = max(0.0, {prefix}_reservoir_pressure - {prefix}_downstream_pressure);");
|
||||
source.AppendLine($" {prefix}_pressure_drive = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/pressure_drive", $"max(0.0, {prefix}_reservoir_pressure - {prefix}_downstream_pressure)", 0, 1)};");
|
||||
source.AppendLine($" {prefix}_stiffness = {effectiveStiffness};");
|
||||
source.AppendLine($" {prefix}_modal_frequency = max(45.0, min(10000.0, (ma.SR / (2.0 * ma.PI)) * sqrt(max(0.00002, {prefix}_stiffness) / ({mass}))));");
|
||||
source.AppendLine($" {prefix}_load_detune = 1.0 - 0.035 * ma.tanh({prefix}_load_pressure * ({loadCoupling}));");
|
||||
|
|
@ -1671,7 +1674,7 @@ public static class FaustEmitter
|
|||
source.AppendLine($" {prefix}_modal_seed = (no.noise * 0.018 + {prefix}_pressure_drive * max(0.0, 0.22 - clip01({opening})) * 0.16);");
|
||||
source.AppendLine($" {prefix}_modal_ring = {prefix}_modal_seed : fi.resonbp({prefix}_modal_frequency, {prefix}_modal_q, 1);");
|
||||
source.AppendLine($" {prefix}_modal_oscillator = os.osc({prefix}_modal_frequency * {prefix}_load_detune) * {prefix}_oscillation_gate;");
|
||||
source.AppendLine($" {prefix}_modal_tissue = ({prefix}_modal_oscillator * (0.70 + 0.80 * ({tension})) + {prefix}_modal_ring * 0.35) * max(0.0, 1.0 - 0.35 * ({damping}) - 0.45 * {prefix}_tissue_loss);");
|
||||
source.AppendLine($" {prefix}_modal_tissue = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/modal_tissue", $"({prefix}_modal_oscillator * (0.70 + 0.80 * ({tension})) + {prefix}_modal_ring * 0.35) * max(0.0, 1.0 - 0.35 * ({damping}) - 0.45 * {prefix}_tissue_loss)", -1, 1)};");
|
||||
switch (port.Law)
|
||||
{
|
||||
case AcousticValveLaw.TwoMass:
|
||||
|
|
@ -1699,13 +1702,13 @@ public static class FaustEmitter
|
|||
break;
|
||||
}
|
||||
source.AppendLine($" {prefix}_aperture_linear = max(0.0, ({restOpening}) + ({opening}) + {prefix}_displacement - ({saturation}) * pow({prefix}_displacement, 3.0));");
|
||||
source.AppendLine($" {prefix}_aperture = {prefix}_aperture_linear * (1.0 - 0.55 * {prefix}_aperture_shape) + pow({prefix}_aperture_linear, 2.0) * (0.55 * {prefix}_aperture_shape);");
|
||||
source.AppendLine($" {prefix}_aperture = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/aperture", $"{prefix}_aperture_linear * (1.0 - 0.55 * {prefix}_aperture_shape) + pow({prefix}_aperture_linear, 2.0) * (0.55 * {prefix}_aperture_shape)", 0, 2)};");
|
||||
source.AppendLine($" {prefix}_flow_resistance = 1.0 + {prefix}_flow_loss * (0.35 + 1.8 / max(0.035, {prefix}_aperture + ({restOpening})));");
|
||||
source.AppendLine($" {prefix}_turbulence = no.noise : fi.highpass(2, 1200.0 + 2800.0 * clip01(1.0 - {prefix}_aperture));");
|
||||
source.AppendLine($" {prefix}_voicing = {prefix}_modal_tissue * (0.18 + 1.45 * ({tension}));");
|
||||
source.AppendLine($" {prefix}_flow = ma.tanh((({prefix}_pressure_drive * {prefix}_aperture * (1.5 + 3.5 * {prefix}_flow_scale)) + ({prefix}_voicing * {prefix}_pressure_drive * (0.45 + 1.55 * ({tension})))) / {prefix}_flow_resistance);");
|
||||
source.AppendLine($" {prefix}_flow = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/flow", $"ma.tanh((({prefix}_pressure_drive * {prefix}_aperture * (1.5 + 3.5 * {prefix}_flow_scale)) + ({prefix}_voicing * {prefix}_pressure_drive * (0.45 + 1.55 * ({tension})))) / {prefix}_flow_resistance)", -1, 1)};");
|
||||
source.AppendLine($" {prefix}_noise = {prefix}_turbulence * ({noise}) * {prefix}_pressure_drive * clip01({prefix}_aperture) * (1.0 - 0.55 * ({tension})) / {prefix}_flow_resistance;");
|
||||
source.AppendLine($" {prefix}_out = ({prefix}_flow * {prefix}_flow_scale * (0.55 + 2.6 * {prefix}_pressure_drive) + {prefix}_noise) * ({balance});");
|
||||
source.AppendLine($" {prefix}_out = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/out", $"({prefix}_flow * {prefix}_flow_scale * (0.55 + 2.6 * {prefix}_pressure_drive) + {prefix}_noise) * ({balance})", -2, 2)};");
|
||||
}
|
||||
|
||||
private static string EmitGraphContactSourceExpression(
|
||||
|
|
@ -1713,7 +1716,8 @@ public static class FaustEmitter
|
|||
string voiceName,
|
||||
AcousticGraphNode node,
|
||||
IReadOnlyList<AcousticGraphPort> nodePorts,
|
||||
AcousticTerminal terminal)
|
||||
AcousticTerminal terminal,
|
||||
FaustExportOptions options)
|
||||
{
|
||||
var safeTerminal = SafeIdentifier(terminal.Name);
|
||||
var prefix = $"{voiceName}_graph_contact_{safeTerminal}_{node.Name}";
|
||||
|
|
@ -1721,11 +1725,11 @@ public static class FaustEmitter
|
|||
var localPressure = $"{voiceName}_graph_node_incident_pressure_{node.Name}";
|
||||
var drive = ContactReservoirDriveExpression(voiceName, node, nodePorts, localPressure);
|
||||
source.AppendLine($" {prefix}_closure = clip01((0.0144 - ({terminalArea})) / 0.0144);");
|
||||
source.AppendLine($" {prefix}_release = (max(0.0, ({prefix}_closure : mem) - {prefix}_closure) : + ~ *(0.992));");
|
||||
source.AppendLine($" {prefix}_release = {ProbeSignal(options, $"/debug/{voiceName}/contact/{terminal.Name}/{node.Name}/release", $"(max(0.0, ({prefix}_closure : mem) - {prefix}_closure) : + ~ *(0.992))", 0, 1)};");
|
||||
source.AppendLine($" {prefix}_reservoir_drive = {drive};");
|
||||
source.AppendLine($" {prefix}_reservoir = {prefix}_reservoir_drive * {prefix}_closure : + ~ *(0.994);");
|
||||
source.AppendLine($" {prefix}_reservoir = {ProbeSignal(options, $"/debug/{voiceName}/contact/{terminal.Name}/{node.Name}/reservoir", $"{prefix}_reservoir_drive * {prefix}_closure : + ~ *(0.994)", 0, 2)};");
|
||||
source.AppendLine($" {prefix}_release_pressure = 0.12 + 3.20 * clip01({prefix}_reservoir);");
|
||||
source.AppendLine($" {prefix}_out = {prefix}_release * {prefix}_release_pressure * (0.35 + 0.65 * clip01(abs({voiceName}_graph_terminal_reflection_{safeTerminal})));");
|
||||
source.AppendLine($" {prefix}_out = {ProbeSignal(options, $"/debug/{voiceName}/contact/{terminal.Name}/{node.Name}/out", $"{prefix}_release * {prefix}_release_pressure * (0.35 + 0.65 * clip01(abs({voiceName}_graph_terminal_reflection_{safeTerminal})))", -2, 2)};");
|
||||
return $"{prefix}_out";
|
||||
}
|
||||
|
||||
|
|
@ -1737,7 +1741,8 @@ public static class FaustEmitter
|
|||
IReadOnlyList<AcousticGraphPort> nodePorts,
|
||||
string sourceName,
|
||||
AcousticSourcePort port,
|
||||
ParameterMap parameters)
|
||||
ParameterMap parameters,
|
||||
FaustExportOptions options)
|
||||
{
|
||||
var index = AcousticSourcePortIndex(patch, port);
|
||||
var path = $"/acoustic/sources/{index}";
|
||||
|
|
@ -1753,9 +1758,9 @@ public static class FaustEmitter
|
|||
var reservoirDrive = ClosureReservoirDriveExpression(voiceName, node, nodePorts, port, localPressure);
|
||||
|
||||
source.AppendLine($" {prefix}_closure = clip01((0.12 - ({opening})) / 0.12);");
|
||||
source.AppendLine($" {prefix}_release = (max(0.0, ({prefix}_closure : mem) - {prefix}_closure) : + ~ *(0.995));");
|
||||
source.AppendLine($" {prefix}_release = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/release", $"(max(0.0, ({prefix}_closure : mem) - {prefix}_closure) : + ~ *(0.995))", 0, 1)};");
|
||||
source.AppendLine($" {prefix}_reservoir_drive = {reservoirDrive};");
|
||||
source.AppendLine($" {prefix}_reservoir = {prefix}_reservoir_drive * {prefix}_closure : + ~ *(0.992);");
|
||||
source.AppendLine($" {prefix}_reservoir = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/reservoir", $"{prefix}_reservoir_drive * {prefix}_closure : + ~ *(0.992)", 0, 2)};");
|
||||
source.AppendLine($" {prefix}_pressure_drive = 0.50 + 1.50 * clip01(abs({localPressure}));");
|
||||
source.AppendLine($" {prefix}_release_pressure = 0.20 + 2.80 * clip01({prefix}_reservoir);");
|
||||
source.AppendLine($" {prefix}_noise_gate = min(clip01((0.85 - ({opening})) / 0.85), clip01(25.0 * (({opening}) - 0.04)));");
|
||||
|
|
@ -1764,7 +1769,7 @@ public static class FaustEmitter
|
|||
source.AppendLine($" {prefix}_sustained = {prefix}_turbulence * {noise} * {pressure} * {prefix}_pressure_drive * {prefix}_noise_gate * 0.62;");
|
||||
source.AppendLine($" {prefix}_burst_noise = {prefix}_turbulence * {noise} * {transient} * {prefix}_pressure_drive * {prefix}_release_gate * 1.2;");
|
||||
source.AppendLine($" {prefix}_release_pulse = {transient} * {pressure} * {prefix}_release * {prefix}_release_pressure * 0.58 * (0.8 + 0.8 * clip01(({opening}) / 0.8));");
|
||||
source.AppendLine($" {prefix}_out = ({prefix}_sustained + {prefix}_burst_noise + {prefix}_release_pulse) * {balance};");
|
||||
source.AppendLine($" {prefix}_out = {ProbeSignal(options, $"/debug/{voiceName}/source/{sourceName}/{node.Name}/out", $"({prefix}_sustained + {prefix}_burst_noise + {prefix}_release_pulse) * {balance}", -2, 2)};");
|
||||
return $"{prefix}_out";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -216,3 +216,4 @@
|
|||
{"evidence":"Created `research/vocal-source-grounding` with downloaded/captured references on MEAD source physics, syrinx modeling, two-mass vocal fold modeling, self-oscillating vocal fold experiments, Birkholz 2024 pressure losses, and Faust physical modeling primitives. The grounding pass found `drive` is currently split-brained: it scales reservoir pressure, phonation gate, and modal/displacement brightness. Literature points instead to pressure as energy input, tissue mass/stiffness/damping/collision as source morphology, aperture/area-dependent flow loss as pressure-flow shaping, and tract/radiation load as spectral/acoustic coupling.","claim":"The next valve-law cut should demote generic `drive` into a calibration/compatibility alias, then make harmonic richness and ease of oscillation emerge from explicit morphology, pressure-flow loss, collision/contact, damping, and graph load rather than a timbre rescue knob.","timestamp":"2026-05-28T23:12:00+01:00","topic":"vocal-source-grounding"}
|
||||
{"evidence":"Demoted graph tissue-valve `drive` from split-brained vocal authority into a parser compatibility alias for `flow_scale`, added explicit `flow_scale`, `tissue_loss`, `aperture_shape`, and `flow_loss` source-port fields, and changed Faust graph lowering so reservoir pressure is pressure-owned while oscillation threshold/Q, velocity decay, aperture law, and pressure-flow resistance come from physical morphology/load controls. Canonical human, bird, and alien graph patches now use the new fields. Focused parser/export/render tests passed, class-sliced non-PT suites passed, and fresh Warbling White-eye audition artifacts were written under `artifacts/listening/warbling-white-eye-20260529T002500-drive-demotion`. The PT accepted-utterance diagnostic renderer is unstable in this run: the mama artifact writes, then the testhost exits with code -1 before reporting a managed assertion.","claim":"Graph vocal source energy, threshold, and harmonic behavior now have separate inspectable owners. Future bird/human tuning should move through pressure, stiffness/tension, damping/tissue loss, aperture/flow resistance, collision, radiation, and graph load instead of reviving a generic vocal drive knob. The next cleanup should inspect the remaining low-frequency flow floor/radiation normalization and the PT diagnostic testhost crash, not use the crash as permission to restore a generic drive knob.","timestamp":"2026-05-29T00:20:00+01:00","topic":"graph-tissue-valve-drive-demotion"}
|
||||
{"evidence":"Rechecked the PT accepted-utterance diagnostic after clearing stale child `dotnet` processes. The focused test passes with enough wall-clock budget; fresh artifact `artifacts/parity/pink-trombone-utterance-logmel/20260529T005420700` records render timings of about 31s for mama, 45s for papa, and 59s for thrombosis. `PinkTromboneAcceptedUtterancesReportGraphLogMelParityWhenFaustIsInstalled` now writes generated DSP/control artifacts before rendering, applies a three-minute per-utterance cancellation budget, and writes render stdout/stderr/timing. `FaustCompiler.RunAsync` now kills the entire child process tree when cancellation fires. Focused PT utterance plus graph Faust validation/render tests passed: 3 tests in 2m17s.","claim":"The previous PT testhost failure is not currently a reproducible managed graph-source crash; the real process smell was unbounded external Faust/dotnet render work with poor pre-crash artifacts. Current acoustic evidence points to underpowered/weakly articulated radiated flow, not a loud low-frequency floor, so next tuning should use graph probes rather than restore the generic drive authority.","timestamp":"2026-05-29T01:00:00+01:00","topic":"pt-utterance-process-observability"}
|
||||
{"evidence":"Added DebugProbeUi source/contact internals and an opt-in thrombosis graph timeline probe gated by `AQUASYNTH_RUN_GRAPH_PROBES=1`. Latest artifact `artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T011547091` shows modal source out 1.335230, modal flow 0.791726, modal pressure drive 0.741528, lip radiation flow 0.337191, lip radiation admittance 0.710362, sibilant injection source area-34 out 0.099288, and lip contact release out 0.032413. The opt-in probe plus graph Faust validation passed in 2m01s; the default focused PT diagnostic still passes with the probe test returning when the env var is absent.","claim":"The graph voice is alive upstream of the final artifact: source and radiation have real signal, while contact release is small/localized and output balance remains weak. Do not restore generic drive or add a blind gain compensator. The next coherent acoustic cut should be either contact/closure participation in scattering or an explicitly owned utterance/radiation output balance, decided from the timeline rather than final log-mel score alone.","timestamp":"2026-05-29T01:20:00+01:00","topic":"thrombosis-graph-timeline-probe"}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,34 @@
|
|||
|
||||
Current slice:
|
||||
|
||||
- Added graph-native debug probes for vocal source internals when
|
||||
`DebugProbeUi` is enabled: tissue-valve pressure drive, modal tissue,
|
||||
aperture, flow, and output; turbulence-source reservoir/release/output; and
|
||||
contact reservoir/release/output. These are observation only, not audio-law
|
||||
changes.
|
||||
- Added opt-in `PinkTromboneThrombosisGraphDebugProbesWriteTimelineWhenNativeFaustIsInstalled`.
|
||||
It runs only when `AQUASYNTH_RUN_GRAPH_PROBES=1`, compiles the generated
|
||||
`thrombosis` graph through native Faust debug UI, steps the streaming patch
|
||||
block by block, and writes `candidate-debug.dsp`, `controls.csv`,
|
||||
`timeline.csv`, and `probe-peaks.txt`.
|
||||
- Latest probe artifact:
|
||||
`artifacts/parity/pink-trombone-graph-thrombosis-probes/20260529T011547091`.
|
||||
Key peaks: modal source out `1.335230`, modal flow `0.791726`, modal pressure
|
||||
drive `0.741528`, lip radiation flow `0.337191`, lip radiation admittance
|
||||
`0.710362`, sibilant/injection source at area 34 out `0.099288`, lip contact
|
||||
release out `0.032413`. The graph is not dead. The current failure is not a
|
||||
missing generic `drive` knob or a silent source; it is weak articulation and
|
||||
output balance after graph/radiation scaling, with contact release still
|
||||
small and localized.
|
||||
- No acoustic-law cut was made from this evidence. Next real change should use
|
||||
the probe timeline to decide whether to promote contact/closure into the
|
||||
scattering law or adjust the utterance harness/radiation output owner. Do not
|
||||
hide the finding behind a broad gain tweak.
|
||||
- Verification:
|
||||
`AQUASYNTH_RUN_GRAPH_PROBES=1 dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false --filter "PinkTromboneThrombosisGraphDebugProbesWriteTimelineWhenNativeFaustIsInstalled|FaustCompilerValidatesAcousticPathGraphWhenInstalled" -v minimal`
|
||||
passed: 2 tests in 2m01s. Default focused diagnostic without the env var also
|
||||
passed with the probe test returning immediately.
|
||||
|
||||
- PT utterance diagnostic crash recheck: the focused test is reproducible as
|
||||
slow but passing when stale child `dotnet` processes are cleared and the test
|
||||
is run with enough wall-clock budget. Fresh verified artifact:
|
||||
|
|
|
|||
|
|
@ -318,6 +318,104 @@ public sealed class PinkTromboneLogMelParityTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PinkTromboneThrombosisGraphDebugProbesWriteTimelineWhenNativeFaustIsInstalled()
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("AQUASYNTH_RUN_GRAPH_PROBES") != "1")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var fixture = PinkTromboneUtteranceFixtures.ById("thrombosis");
|
||||
var source = AutomatedGraphSource(fixture, DebugProbeUi: true);
|
||||
var artifactDir = ArtifactPath("parity", "pink-trombone-graph-thrombosis-probes", DateTimeOffset.UtcNow.ToString("yyyyMMddTHHmmssfff", CultureInfo.InvariantCulture));
|
||||
|
||||
using var compiler = new AquaSynthPatchCompiler();
|
||||
if (!compiler.TryCompileSource(
|
||||
new AquaSynthCompileIdentity("pt_probe_thrombosis", "pt_probe_thrombosis", source),
|
||||
source,
|
||||
fixture.DurationSeconds,
|
||||
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 thrombosis probe compile failed: {error}{Environment.NewLine}artifacts: {artifactDir}");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(artifactDir);
|
||||
File.WriteAllText(Path.Combine(artifactDir, "candidate-debug.dsp"), source);
|
||||
File.WriteAllText(Path.Combine(artifactDir, "controls.csv"), UtteranceControlCsv(fixture));
|
||||
|
||||
using (patch)
|
||||
using (var stream = patch!.CreateStreamingPatch())
|
||||
{
|
||||
var probePaths = stream.ProbePaths
|
||||
.Where(IsThrombosisTimelineProbe)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
Assert.Contains(probePaths, path => path.Contains("/source/", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains(probePaths, path => path.Contains("/radiation/", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains(probePaths, path => path.Contains("/node/", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var blockSize = 128;
|
||||
var frames = Math.Max(1, (int)MathF.Ceiling(fixture.DurationSeconds * 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();
|
||||
var peaks = probePaths.ToDictionary(path => path, _ => 0.0f, StringComparer.Ordinal);
|
||||
var timeline = new List<string>
|
||||
{
|
||||
"time,output_peak,output_rms," + string.Join(",", probePaths.Select(Escape))
|
||||
};
|
||||
|
||||
for (var offset = 0; offset < frames; offset += blockSize)
|
||||
{
|
||||
var count = Math.Min(blockSize, frames - offset);
|
||||
stream.ProcessBlock(inputs, outputs, count);
|
||||
var snapshot = stream.SnapshotProbes();
|
||||
var outputPeak = 0.0f;
|
||||
var outputEnergy = 0.0f;
|
||||
for (var channel = 0; channel < outputs.Length; channel++)
|
||||
{
|
||||
for (var sample = 0; sample < count; sample++)
|
||||
{
|
||||
outputPeak = Math.Max(outputPeak, MathF.Abs(outputs[channel][sample]));
|
||||
outputEnergy += outputs[channel][sample] * outputs[channel][sample];
|
||||
}
|
||||
}
|
||||
|
||||
var values = new List<string>
|
||||
{
|
||||
F(offset / (float)patch.Manifest.SampleRate),
|
||||
F(outputPeak),
|
||||
F(MathF.Sqrt(outputEnergy / Math.Max(1, count * Math.Max(1, outputs.Length))))
|
||||
};
|
||||
foreach (var path in probePaths)
|
||||
{
|
||||
var value = snapshot.GetValueOrDefault(path);
|
||||
peaks[path] = Math.Max(peaks[path], MathF.Abs(value));
|
||||
values.Add(F(value));
|
||||
}
|
||||
|
||||
timeline.Add(string.Join(",", values));
|
||||
}
|
||||
|
||||
var peakLines = peaks
|
||||
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
|
||||
.Select(pair => string.Create(CultureInfo.InvariantCulture, $"{pair.Key},peak={pair.Value:0.000000}"))
|
||||
.ToArray();
|
||||
File.WriteAllLines(Path.Combine(artifactDir, "timeline.csv"), timeline);
|
||||
File.WriteAllLines(Path.Combine(artifactDir, "probe-peaks.txt"), peakLines);
|
||||
|
||||
Assert.Contains(peakLines, line => line.Contains("/source/", StringComparison.OrdinalIgnoreCase) && !line.EndsWith("peak=0.000000", StringComparison.Ordinal));
|
||||
Assert.Contains(peakLines, line => line.Contains("/radiation/", StringComparison.OrdinalIgnoreCase) && !line.EndsWith("peak=0.000000", StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
private static string Report(PinkTromboneParityFixture fixture, AudioComparison comparison, string candidate) =>
|
||||
string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
|
|
@ -396,6 +494,12 @@ public sealed class PinkTromboneLogMelParityTests
|
|||
|
||||
private readonly record struct ProbePeak(float EnergyIn, float EnergyOut);
|
||||
|
||||
private static bool IsThrombosisTimelineProbe(string path) =>
|
||||
path.Contains("/node/", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.Contains("/source/", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.Contains("/contact/", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.Contains("/radiation/", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string AutomatedGraphSource(PinkTromboneUtteranceFixture fixture, bool DebugProbeUi = false)
|
||||
{
|
||||
var source = FaustEmitter.EmitScript(UtteranceGraphScript(fixture), new FaustExportOptions($"pt_utterance_{fixture.Id}", DebugProbeUi: DebugProbeUi)).Source;
|
||||
|
|
|
|||
Loading…
Reference in a new issue