Expose voice articulation failures in parity reports
This commit is contained in:
parent
8d49e13d2a
commit
d990610107
8 changed files with 214 additions and 15 deletions
|
|
@ -311,6 +311,17 @@ smoke remains passing under
|
|||
`artifacts/parity/pink-trombone-logmel/20260527T124311305`. This helps the
|
||||
voiced fixtures a little, but it is not the missing articulation mechanism.
|
||||
|
||||
Log-mel cosine is no longer allowed to impersonate a speech-parity verdict.
|
||||
It rewards spectral overlap even when the candidate is mostly silence, filtered
|
||||
motor buzz, or a few breathy/squeaky bursts. `AudioComparison` now includes an
|
||||
`AudioArticulationComparison` with envelope cosine, active-frame ratio, silence
|
||||
mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio,
|
||||
and an articulation score. The utterance report prints a verdict. Latest run:
|
||||
`mama`, `papa`, and `thrombosis` are all
|
||||
`not-accepted-articulation` under
|
||||
`artifacts/parity/pink-trombone-utterance-logmel/20260527T130026316`, despite
|
||||
their old cosine values. That is the harness telling the truth again.
|
||||
|
||||
## Cut Line
|
||||
|
||||
The current `Voice.Tract` scalar proxy may survive only as an adapter over real
|
||||
|
|
|
|||
|
|
@ -183,10 +183,13 @@ Trombone anatomy:
|
|||
PT-like 500-1000 Hz body band and pulled `thrombosis` RMS under reference
|
||||
level. Radiation aperture now also owns boundary reflection, and graph
|
||||
radiation reads boundary flow (`incoming - outgoing`) through a stronger
|
||||
radiation high-pass slope instead of acting as a raw pressure tap. That
|
||||
reduces pressure-tap vocoder behavior and gives `mama`/`papa` better
|
||||
500-1000 Hz body, but `thrombosis` still loses too much voiced body during
|
||||
articulation. Pressure storage/release remains the next live cut.
|
||||
radiation high-pass slope instead of acting as a raw pressure tap. The
|
||||
log-mel witness still overrated the result, so the parity harness now reports
|
||||
articulation diagnostics: envelope cosine, active-frame ratio, silence
|
||||
mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio,
|
||||
and an articulation verdict. The latest utterances are explicitly
|
||||
`not-accepted-articulation`; pressure storage/release remains the next live
|
||||
cut.
|
||||
- `tools/vocal-tract-playground` exposes the same control surface for fast
|
||||
knob-twiddling through a small WebAudio witness.
|
||||
- `PinkTromboneReferenceDeclaresMissingWaveguideAuthority` prevents graph
|
||||
|
|
@ -206,9 +209,9 @@ of these:
|
|||
stops are released by the acoustic graph instead of only by a source burst.
|
||||
2. Improve nasal/oral branch and radiation behavior so `mama` gets vowel body
|
||||
without reverting to low-band motor buzz.
|
||||
3. Use the log-mel artifact loop to golf `mama`, `papa`, and `thrombosis`
|
||||
against the source-ported PT renderer without reintroducing a parallel
|
||||
PT-shaped backend.
|
||||
3. Use the artifact loop to golf `mama`, `papa`, and `thrombosis` against the
|
||||
source-ported PT renderer, but treat log-mel as spectral pressure only.
|
||||
Articulation diagnostics own the "does this behave like speech?" verdict.
|
||||
|
||||
The new owner should be explainable as:
|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,19 @@ public sealed record AudioComparison(
|
|||
float EnvelopeDistance,
|
||||
float LogMelDistance,
|
||||
float LogMelCosineSimilarity,
|
||||
AudioArticulationComparison Articulation,
|
||||
float Score);
|
||||
|
||||
public sealed record AudioArticulationComparison(
|
||||
float EnvelopeCosineSimilarity,
|
||||
float ActiveFrameRatio,
|
||||
float SilenceMismatch,
|
||||
float EnvelopeFluxRatio,
|
||||
float SpectralFluxRatio,
|
||||
float MotorBandRatio,
|
||||
float SpeechBandRatio,
|
||||
float ArticulationScore);
|
||||
|
||||
public sealed class AudioAnalyzer
|
||||
{
|
||||
private readonly AudioAnalysisConfig _config;
|
||||
|
|
@ -68,7 +79,7 @@ public sealed class AudioAnalyzer
|
|||
}
|
||||
|
||||
public AudioComparison Compare(ReadOnlySpan<float> referenceSamples, ReadOnlySpan<float> candidateSamples) =>
|
||||
FromAnalysis(Analyze(referenceSamples), Analyze(candidateSamples));
|
||||
FromAnalysis(Analyze(referenceSamples), Analyze(candidateSamples), CompareArticulation(referenceSamples, candidateSamples));
|
||||
|
||||
private AudioFeatures ExtractFeatures(ReadOnlySpan<float> samples)
|
||||
{
|
||||
|
|
@ -176,7 +187,7 @@ public sealed class AudioAnalyzer
|
|||
public static AudioComparison CompareAudio(ReadOnlySpan<float> referenceSamples, ReadOnlySpan<float> candidateSamples, AudioAnalysisConfig? config = null) =>
|
||||
new AudioAnalyzer(config).Compare(referenceSamples, candidateSamples);
|
||||
|
||||
private static AudioComparison FromAnalysis(AudioAnalysis reference, AudioAnalysis candidate)
|
||||
private static AudioComparison FromAnalysis(AudioAnalysis reference, AudioAnalysis candidate, AudioArticulationComparison articulation)
|
||||
{
|
||||
var durationRatio = SafeRatio(candidate.Features.DurationSeconds, reference.Features.DurationSeconds);
|
||||
var rmsRatio = SafeRatio(candidate.Features.Rms, reference.Features.Rms);
|
||||
|
|
@ -187,7 +198,91 @@ public sealed class AudioAnalyzer
|
|||
var logMelCosine = CosineSimilarity(reference.LogMelSpectrogram.Values, candidate.LogMelSpectrogram.Values);
|
||||
var ratioPenalty = RatioDistance(durationRatio) + RatioDistance(rmsRatio) + RatioDistance(zeroCrossingRatio) * 0.5f + RatioDistance(centroidRatio) * 0.5f;
|
||||
var score = 1 / (1 + envelopeDistance * 0.7f + logMelDistance + ratioPenalty);
|
||||
return new AudioComparison(reference, candidate, durationRatio, rmsRatio, zeroCrossingRatio, centroidRatio, envelopeDistance, logMelDistance, logMelCosine, score);
|
||||
return new AudioComparison(reference, candidate, durationRatio, rmsRatio, zeroCrossingRatio, centroidRatio, envelopeDistance, logMelDistance, logMelCosine, articulation, score);
|
||||
}
|
||||
|
||||
private AudioArticulationComparison CompareArticulation(ReadOnlySpan<float> referenceSamples, ReadOnlySpan<float> candidateSamples)
|
||||
{
|
||||
var reference = ArticulationFrames(referenceSamples);
|
||||
var candidate = ArticulationFrames(candidateSamples);
|
||||
var envelopeCosine = CosineSimilarity(reference.Envelope, candidate.Envelope);
|
||||
var activeFrameRatio = SafeRatio(candidate.ActiveRatio, reference.ActiveRatio);
|
||||
var silenceMismatch = BinaryMismatch(reference.Active, candidate.Active);
|
||||
var envelopeFluxRatio = SafeRatio(candidate.EnvelopeFlux, reference.EnvelopeFlux);
|
||||
var spectralFluxRatio = SafeRatio(candidate.SpectralFlux, reference.SpectralFlux);
|
||||
var motorBandRatio = SafeRatio(candidate.MotorBandShare, reference.MotorBandShare);
|
||||
var speechBandRatio = SafeRatio(candidate.SpeechBandShare, reference.SpeechBandShare);
|
||||
var penalty =
|
||||
(1 - Math.Clamp(envelopeCosine, 0, 1)) * 1.4f +
|
||||
silenceMismatch * 1.7f +
|
||||
RatioDistance(activeFrameRatio) * 0.9f +
|
||||
RatioDistance(envelopeFluxRatio) * 0.7f +
|
||||
RatioDistance(spectralFluxRatio) * 0.7f +
|
||||
RatioDistance(motorBandRatio) * 0.6f +
|
||||
RatioDistance(speechBandRatio) * 0.8f;
|
||||
var score = 1 / (1 + penalty);
|
||||
return new AudioArticulationComparison(
|
||||
envelopeCosine,
|
||||
activeFrameRatio,
|
||||
silenceMismatch,
|
||||
envelopeFluxRatio,
|
||||
spectralFluxRatio,
|
||||
motorBandRatio,
|
||||
speechBandRatio,
|
||||
score);
|
||||
}
|
||||
|
||||
private ArticulationFrameSet ArticulationFrames(ReadOnlySpan<float> samples)
|
||||
{
|
||||
const int frameSize = 1024;
|
||||
const int hopSize = 256;
|
||||
if (samples.IsEmpty) return new ArticulationFrameSet([0], [false], 0, 0, 0, 0, 0);
|
||||
|
||||
var envelope = new List<float>();
|
||||
var active = new List<bool>();
|
||||
var speechBand = 0f;
|
||||
var motorBand = 0f;
|
||||
var totalBand = 0f;
|
||||
var spectralFlux = 0f;
|
||||
var previousSpectrum = Array.Empty<float>();
|
||||
var peak = 0f;
|
||||
foreach (var sample in samples) peak = Math.Max(peak, Math.Abs(sample));
|
||||
var gate = Math.Max(peak * 0.035f, _config.GateFloor);
|
||||
|
||||
for (var start = 0; start < samples.Length; start += hopSize)
|
||||
{
|
||||
WriteFramePowerSpectrum(samples, start);
|
||||
var end = Math.Min(start + frameSize, samples.Length);
|
||||
var rms = MathF.Sqrt(MeanSquare(samples[start..end]));
|
||||
envelope.Add(MathF.Log(1e-7f + rms));
|
||||
active.Add(rms >= gate);
|
||||
|
||||
var current = _spectrumBuffer.ToArray();
|
||||
if (previousSpectrum.Length == current.Length)
|
||||
{
|
||||
spectralFlux += PositiveSpectralFlux(previousSpectrum, current);
|
||||
}
|
||||
previousSpectrum = current;
|
||||
|
||||
for (var bin = 0; bin < current.Length; bin++)
|
||||
{
|
||||
var frequency = bin * _config.SampleRate / Math.Max(1, _fftSize);
|
||||
var energy = current[bin];
|
||||
totalBand += energy;
|
||||
if (frequency is >= 80 and < 200) motorBand += energy;
|
||||
if (frequency is >= 500 and < 2500) speechBand += energy;
|
||||
}
|
||||
}
|
||||
|
||||
var fluxFrames = Math.Max(1, envelope.Count - 1);
|
||||
return new ArticulationFrameSet(
|
||||
envelope.ToArray(),
|
||||
active.ToArray(),
|
||||
active.Count(item => item) / (float)Math.Max(1, active.Count),
|
||||
EnvelopeFlux(envelope),
|
||||
spectralFlux / fluxFrames,
|
||||
motorBand / Math.Max(float.Epsilon, totalBand),
|
||||
speechBand / Math.Max(float.Epsilon, totalBand));
|
||||
}
|
||||
|
||||
private static void Fft(Complex[] buffer)
|
||||
|
|
@ -302,6 +397,50 @@ public sealed class AudioAnalyzer
|
|||
return dot / MathF.Max(0.000001f, MathF.Sqrt(left) * MathF.Sqrt(right));
|
||||
}
|
||||
|
||||
private static float BinaryMismatch(IReadOnlyList<bool> reference, IReadOnlyList<bool> candidate)
|
||||
{
|
||||
var length = Math.Max(1, Math.Max(reference.Count, candidate.Count));
|
||||
var mismatches = 0;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (BoolResampledAt(reference, i, length) != BoolResampledAt(candidate, i, length)) mismatches++;
|
||||
}
|
||||
|
||||
return mismatches / (float)length;
|
||||
}
|
||||
|
||||
private static bool BoolResampledAt(IReadOnlyList<bool> values, int index, int targetLength)
|
||||
{
|
||||
if (values.Count == 0) return false;
|
||||
if (values.Count == 1 || targetLength <= 1) return values[0];
|
||||
var position = index * (values.Count - 1f) / (targetLength - 1);
|
||||
return values[(int)MathF.Round(position)];
|
||||
}
|
||||
|
||||
private static float EnvelopeFlux(IReadOnlyList<float> envelope)
|
||||
{
|
||||
if (envelope.Count < 2) return 0;
|
||||
var sum = 0f;
|
||||
for (var i = 1; i < envelope.Count; i++) sum += MathF.Abs(envelope[i] - envelope[i - 1]);
|
||||
return sum / (envelope.Count - 1);
|
||||
}
|
||||
|
||||
private static float PositiveSpectralFlux(IReadOnlyList<float> previous, IReadOnlyList<float> current)
|
||||
{
|
||||
var sum = 0f;
|
||||
var scale = 0f;
|
||||
var length = Math.Min(previous.Count, current.Count);
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var previousLog = MathF.Log(1e-12f + previous[i]);
|
||||
var currentLog = MathF.Log(1e-12f + current[i]);
|
||||
sum += MathF.Max(0, currentLog - previousLog);
|
||||
scale += MathF.Abs(previousLog) + MathF.Abs(currentLog);
|
||||
}
|
||||
|
||||
return sum / Math.Max(1e-6f, scale);
|
||||
}
|
||||
|
||||
private static float ResampledAt(IReadOnlyList<float> values, int index, int targetLength)
|
||||
{
|
||||
if (values.Count == 0) return 0;
|
||||
|
|
@ -335,6 +474,15 @@ public sealed class AudioAnalyzer
|
|||
private static float SafeRatio(float candidate, float reference) => candidate / Math.Max(float.Epsilon, reference);
|
||||
private static float RatioDistance(float ratio) => MathF.Abs(MathF.Log(Math.Max(float.Epsilon, ratio)));
|
||||
private static int NextPowerOfTwo(int value) => 1 << (int)MathF.Ceiling(MathF.Log2(value));
|
||||
|
||||
private sealed record ArticulationFrameSet(
|
||||
float[] Envelope,
|
||||
bool[] Active,
|
||||
float ActiveRatio,
|
||||
float EnvelopeFlux,
|
||||
float SpectralFlux,
|
||||
float MotorBandShare,
|
||||
float SpeechBandShare);
|
||||
}
|
||||
|
||||
file static class FloatExtensions
|
||||
|
|
|
|||
|
|
@ -175,3 +175,4 @@
|
|||
{"evidence":"Graph radiation aperture now feeds boundary reflection, blending lip/beak ports from near-closed positive reflection to the declared open-end reflection, and radiation output reads boundary flow (`incoming - outgoing`) through a stronger radiation high-pass slope instead of raw incoming pressure. Focused utterance parity passed with artifacts/parity/pink-trombone-utterance-logmel/20260527T122757617: mama cosine 0.5650 RMS 0.3297, papa 0.8613 RMS 0.1282, thrombosis 0.4347 RMS 0.2502. Static parity passed with artifacts/parity/pink-trombone-logmel/20260527T123141625: open/front/nasal/ma/sibilant/closure cosine 0.6088/0.6826/0.6468/0.6153/0.4158/0.6198. A burst-pressure boost was tested and cut because it did not improve release articulation.","claim":"Radiation aperture is a graph boundary impedance owner, not an output-only mute. Reading radiated boundary flow reduces PT-shaped vocoder behavior and improves mama/papa body bands, but thrombosis still exposes missing graph-native pressure storage/release around severe constrictions.","timestamp":"2026-05-27T12:35:00+01:00","topic":"acoustic-radiation-boundary-flow"}
|
||||
{"evidence":"Added a small higher-harmonic brightness term to graph glottal source lowering after radiation-flow normalization exposed weak 1-2.5 kHz voiced energy. Focused utterance parity passed with artifacts/parity/pink-trombone-utterance-logmel/20260527T123628148: mama cosine 0.5705 RMS 0.3355, papa 0.8622 RMS 0.1326, thrombosis 0.4355 RMS 0.2504. Static parity passed with artifacts/parity/pink-trombone-logmel/20260527T123734700: open/front/nasal/ma/sibilant/closure cosine 0.6081/0.6823/0.6495/0.6162/0.4158/0.6193.","claim":"Glottal brightness belongs in the reusable source primitive, not in a PT-only post-filter. The tweak slightly improves voiced utterance body without destabilizing static fixtures, but it does not solve the remaining articulation failure.","timestamp":"2026-05-27T12:40:00+01:00","topic":"acoustic-glottal-brightness"}
|
||||
{"evidence":"After switching graph radiation from raw pressure to boundary flow, recalibrated the graph radiation gain from 3.0 to 4.0. A 4.5 trial improved utterance RMS but pushed static fixtures closer to soft-clip influence, so 4.0 was kept. Focused utterance parity passed with artifacts/parity/pink-trombone-utterance-logmel/20260527T124230804: mama cosine 0.5712 RMS 0.4431, papa 0.8659 RMS 0.1768, thrombosis 0.4379 RMS 0.3324. Static parity passed with artifacts/parity/pink-trombone-logmel/20260527T124311305: open/front/nasal/ma/sibilant/closure cosine 0.5942/0.6710/0.6369/0.5955/0.4201/0.6143.","claim":"Radiation gain is part of the post-flow normalization contract, not a substitute for articulation. The kept value improves underpowered utterance output while preserving static guardrails; pressure storage/release remains the actual consonant problem.","timestamp":"2026-05-27T12:45:00+01:00","topic":"acoustic-radiation-flow-normalization"}
|
||||
{"evidence":"Added `AudioArticulationComparison` to `AudioComparison` and updated Pink Trombone parity reports with envelope cosine, active-frame ratio, silence mismatch, envelope/spectral flux ratios, motor-band ratio, speech-band ratio, articulation score, and utterance verdicts. Focused tests passed. Latest utterance artifact artifacts/parity/pink-trombone-utterance-logmel/20260527T130026316 reports mama/papa/thrombosis as `not-accepted-articulation` with articulation scores 0.1930/0.1829/0.1848 despite log-mel cosine 0.5712/0.8659/0.4379. Static reports also expose weak speech-band ratios instead of hiding behind cosine.","claim":"Log-mel cosine is a spectral witness, not a speech-articulation authority. The parity harness must surface silence-map, envelope, flux, motor-band, and speech-band failures so filtered buzz cannot forge a listening receipt.","timestamp":"2026-05-27T13:05:00+01:00","topic":"voice-articulation-metric-witness"}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,23 @@
|
|||
|
||||
Current slice:
|
||||
|
||||
- User listening correctly rejected the log-mel witness: `thrombosis` was
|
||||
mostly silence plus breathy/squeaky bursts, and `papa`/`mama` still read as
|
||||
phone-vibration buzz. `AudioComparison` now includes articulation diagnostics
|
||||
instead of letting log-mel cosine own speech parity.
|
||||
- New `AudioArticulationComparison` fields: envelope cosine, active-frame
|
||||
ratio, silence mismatch, envelope flux ratio, spectral flux ratio, motor-band
|
||||
ratio, speech-band ratio, and articulation score. Utterance reports now print
|
||||
a verdict. Latest artifact:
|
||||
`artifacts/parity/pink-trombone-utterance-logmel/20260527T130026316`;
|
||||
all three accepted utterance fixtures are `not-accepted-articulation` with
|
||||
articulation scores `mama` `0.1930`, `papa` `0.1829`, `thrombosis` `0.1848`.
|
||||
This is correct: the harness now agrees with the ear instead of laundering
|
||||
filtered buzz through cosine.
|
||||
- Verification:
|
||||
`dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --disable-build-servers -p:UseSharedCompilation=false -p:BuildInParallel=false --filter "AudioAnalyzerComparesSimpleBuffers|PinkTromboneAcceptedUtterancesReportGraphLogMelParityWhenFaustIsInstalled|PinkTromboneFixturesReportLogMelParityWhenFaustIsInstalled" -v minimal`
|
||||
passed.
|
||||
|
||||
- Latest graph radiation pass moved articulation authority into the acoustic
|
||||
boundary instead of the output tap. `AcousticRadiationPort` aperture now
|
||||
blends boundary reflection between near-closed termination and declared
|
||||
|
|
|
|||
|
|
@ -120,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 now routes accepted parity through `propagation=graph`; the old waveguide parity lane is not a comparison baseline or historical pressure lane.
|
||||
- Pink Trombone log-mel parity now writes graph candidates only. Generated tract graphs mirror live `/pink/...` tract bindings into acoustic source, terminal, connection, radiation, and path-area fields. Graph generation stopped baking first-frame tongue/constriction/lip morphology into the path; `AcousticPath.AreaControl` now turns tongue/constriction/lip controls into live area expressions at graph sample points. Velum owns nasal branch coupling/aperture, and the first nasal tube sample is no longer allowed to decide the velum opening. Graph radiation now uses aperture as a boundary impedance owner: lip/beak opening blends boundary reflection from near-closed termination to declared open-end reflection, and radiation reads boundary flow (`incoming - outgoing`) through a stronger radiation slope instead of raw incoming pressure. Latest static graph evidence: open/front/nasal/ma/sibilant/closure cosine 0.6088/0.6826/0.6468/0.6153/0.4158/0.6198.
|
||||
- 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; 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; 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; `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/20260527T124230804`: mama cosine 0.5712 / RMS 0.4431, papa 0.8659 / 0.1768, thrombosis 0.4379 / 0.3324. A direct LF coefficient source was tested and cut because it overdrove the graph and broke static open vowel. 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. Next pressure is graph-native storage/release around severe constrictions plus nasal/oral branch coupling; output coloring alone is not allowed to impersonate articulation.
|
||||
- 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; 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; 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; `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/20260527T130026316`: mama/papa/thrombosis are all `not-accepted-articulation` with articulation scores 0.1930/0.1829/0.1848 even though log-mel cosine remains 0.5712/0.8659/0.4379. `AudioComparison.Articulation` now 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 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. Next pressure is graph-native storage/release around severe constrictions plus nasal/oral branch coupling; 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.
|
||||
- Do not expand into prosody, emotional context, personality, or broad Weksa expressivity until the tiny IPA utterance harness can show useful loss movement.
|
||||
|
|
|
|||
|
|
@ -459,6 +459,9 @@ public sealed class PatchScriptTests
|
|||
|
||||
Assert.True(comparison.Reference.Features.Peak > 0.19f);
|
||||
Assert.True(comparison.Score > 0.99f);
|
||||
Assert.True(comparison.Articulation.ArticulationScore > 0.99f);
|
||||
Assert.True(comparison.Articulation.EnvelopeCosineSimilarity > 0.99f);
|
||||
Assert.Equal(0, comparison.Articulation.SilenceMismatch, precision: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -110,9 +110,7 @@ public sealed class PinkTromboneLogMelParityTests
|
|||
Assert.True(candidate.Samples.Max(MathF.Abs) > 0.00001f, candidate.Stderr);
|
||||
|
||||
var comparison = analyzer.Compare(reference.Samples, candidate.Samples);
|
||||
var report = string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"{fixture.Id}/graph-utterance: cosine={comparison.LogMelCosineSimilarity:0.0000} logMelDistance={comparison.LogMelDistance:0.0000} score={comparison.Score:0.0000} rmsRatio={comparison.RmsRatio:0.0000} centroidRatio={comparison.CentroidRatio:0.0000}");
|
||||
var report = UtteranceReport(fixture, comparison);
|
||||
reports.Add(report);
|
||||
|
||||
var fixtureDir = Path.Combine(artifactDir, fixture.Id);
|
||||
|
|
@ -135,7 +133,25 @@ public sealed class PinkTromboneLogMelParityTests
|
|||
private static string Report(PinkTromboneParityFixture fixture, AudioComparison comparison, string candidate) =>
|
||||
string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"{fixture.Id}/{candidate}: cosine={comparison.LogMelCosineSimilarity:0.0000} logMelDistance={comparison.LogMelDistance:0.0000} score={comparison.Score:0.0000} rmsRatio={comparison.RmsRatio:0.0000} centroidRatio={comparison.CentroidRatio:0.0000}");
|
||||
$"{fixture.Id}/{candidate}: cosine={comparison.LogMelCosineSimilarity:0.0000} logMelDistance={comparison.LogMelDistance:0.0000} score={comparison.Score:0.0000} rmsRatio={comparison.RmsRatio:0.0000} centroidRatio={comparison.CentroidRatio:0.0000} articulation={comparison.Articulation.ArticulationScore:0.0000} envCos={comparison.Articulation.EnvelopeCosineSimilarity:0.0000} silenceMismatch={comparison.Articulation.SilenceMismatch:0.0000} motorBandRatio={comparison.Articulation.MotorBandRatio:0.0000} speechBandRatio={comparison.Articulation.SpeechBandRatio:0.0000}");
|
||||
|
||||
private static string UtteranceReport(PinkTromboneUtteranceFixture fixture, AudioComparison comparison)
|
||||
{
|
||||
var verdict = ArticulationVerdict(comparison);
|
||||
return string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"{fixture.Id}/graph-utterance: verdict={verdict} cosine={comparison.LogMelCosineSimilarity:0.0000} logMelDistance={comparison.LogMelDistance:0.0000} score={comparison.Score:0.0000} rmsRatio={comparison.RmsRatio:0.0000} centroidRatio={comparison.CentroidRatio:0.0000} articulation={comparison.Articulation.ArticulationScore:0.0000} envCos={comparison.Articulation.EnvelopeCosineSimilarity:0.0000} activeRatio={comparison.Articulation.ActiveFrameRatio:0.0000} silenceMismatch={comparison.Articulation.SilenceMismatch:0.0000} envelopeFluxRatio={comparison.Articulation.EnvelopeFluxRatio:0.0000} spectralFluxRatio={comparison.Articulation.SpectralFluxRatio:0.0000} motorBandRatio={comparison.Articulation.MotorBandRatio:0.0000} speechBandRatio={comparison.Articulation.SpeechBandRatio:0.0000}");
|
||||
}
|
||||
|
||||
private static string ArticulationVerdict(AudioComparison comparison)
|
||||
{
|
||||
var articulation = comparison.Articulation;
|
||||
if (articulation.ArticulationScore < 0.45f) return "not-accepted-articulation";
|
||||
if (articulation.SilenceMismatch > 0.22f) return "not-accepted-silence-map";
|
||||
if (articulation.EnvelopeCosineSimilarity < 0.55f) return "not-accepted-envelope";
|
||||
if (articulation.MotorBandRatio > 2.2f || articulation.SpeechBandRatio < 0.45f) return "not-accepted-band-balance";
|
||||
return "smoke-only";
|
||||
}
|
||||
|
||||
private static string AutomatedGraphSource(PinkTromboneUtteranceFixture fixture)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue