Add speech timing receipts
This commit is contained in:
parent
cdcfc15a9b
commit
eb2156ab37
8 changed files with 128 additions and 13 deletions
|
|
@ -168,6 +168,12 @@ and cheap to render. It is timing and intelligibility pressure, not anatomy
|
|||
authority. Use it to populate loss surfaces and train controller weights; do not
|
||||
mistake matching its formant shortcuts for building a vocal tract.
|
||||
|
||||
Speech training receipts must carry timing and confidence, not just loss. For
|
||||
each metadata-to-embedding, embedding-to-automation, render, score, and
|
||||
backpropagation step, record the decision time, observed latency, intended
|
||||
latency budget, confidence estimate, and artifact/model identifiers. A correct
|
||||
sound from an unbounded or unobservable path is not a reliable witness.
|
||||
|
||||
## Metrics
|
||||
|
||||
Analysis exists to catch regressions and support search, not to crown winners.
|
||||
|
|
|
|||
|
|
@ -497,6 +497,12 @@ Ground truth means "reference pressure," not "the truth about throats." eSpeak
|
|||
can teach early intelligibility, timing, and rough spectral targets. It cannot
|
||||
decide AquaSynth's anatomy, coarticulation, or Weksa morphology.
|
||||
|
||||
Training receipts must keep time as a witness. Every request, result, and
|
||||
checkpoint should record when each stage made its decision, how much latency it
|
||||
spent, what latency budget it was meant to respect, and how confident the
|
||||
mapping was. A tiny audible witness that sounds right but arrives from a
|
||||
clockless swamp is already teaching the model to lie with a lovely voice.
|
||||
|
||||
## Optional eSpeak NG Workout
|
||||
|
||||
The first IPA-adjacent parity lane uses eSpeak NG as a development reference.
|
||||
|
|
|
|||
|
|
@ -105,6 +105,19 @@ Keep the curriculum brutally small:
|
|||
5. Only move to intonation, prosody, emotional context, and personality after
|
||||
the harness can show phoneme-level loss improvements without handwaving.
|
||||
|
||||
Keep training receipts as first-class evidence:
|
||||
|
||||
- when each stage decided;
|
||||
- how much latency it spent;
|
||||
- what latency budget it was trying to fit;
|
||||
- how confident the mapping was;
|
||||
- which request, result, checkpoint, artifact, and model version produced the
|
||||
evidence.
|
||||
|
||||
Time is not bookkeeping garnish here. If the mapping is correct only after an
|
||||
unbounded wait, cache accident, or hidden retry, the system has learned a
|
||||
beautiful falsehood.
|
||||
|
||||
The universal utterance schema should stay boring at first:
|
||||
|
||||
- segment inventory and source spans;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using GameCult.Caching;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using MessagePack;
|
||||
|
|
@ -20,7 +21,8 @@ public sealed record SpeechRenderRequest(
|
|||
[property: Key(8)] SpeechUtteranceEmbeddingInputSnapshot UtteranceInput,
|
||||
[property: Key(9)] SpeechPhoneticEventSnapshot PhoneticEvent,
|
||||
[property: Key(10)] float[] CandidateControlVector,
|
||||
[property: Key(11)] float[] ReferenceControlVector);
|
||||
[property: Key(11)] float[] ReferenceControlVector,
|
||||
[property: Key(12)] SpeechTimingReceipt[] TimingReceipts);
|
||||
|
||||
[CultDocument("aquasynth.speech_render_result", "aquasynth.speech_render_result.v1")]
|
||||
[MessagePackObject]
|
||||
|
|
@ -39,8 +41,9 @@ public sealed record SpeechRenderResult(
|
|||
[property: Key(7)] float[] OutputGradient,
|
||||
[property: Key(8)] SpeechScoreMetric[] Metrics,
|
||||
[property: Key(9)] SpeechRenderArtifact[] Artifacts,
|
||||
[property: Key(10)] string FailureCode = "",
|
||||
[property: Key(11)] string FailureMessage = "");
|
||||
[property: Key(10)] SpeechTimingReceipt[] TimingReceipts,
|
||||
[property: Key(11)] string FailureCode = "",
|
||||
[property: Key(12)] string FailureMessage = "");
|
||||
|
||||
[CultDocument("aquasynth.speech_training_checkpoint", "aquasynth.speech_training_checkpoint.v1")]
|
||||
[MessagePackObject]
|
||||
|
|
@ -53,7 +56,17 @@ public sealed record SpeechTrainingCheckpoint(
|
|||
[property: Key(3)] int AppliedResultCount,
|
||||
[property: Key(4)] float MeanAppliedLoss,
|
||||
[property: Key(5)] string[] AppliedResultIds,
|
||||
[property: Key(6)] string Notes);
|
||||
[property: Key(6)] string Notes,
|
||||
[property: Key(7)] SpeechTimingReceipt[] TimingReceipts);
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed record SpeechTimingReceipt(
|
||||
[property: Key(0)] string StageId,
|
||||
[property: Key(1)] string DecidedAtUtc,
|
||||
[property: Key(2)] double LatencyMilliseconds,
|
||||
[property: Key(3)] double BudgetMilliseconds,
|
||||
[property: Key(4)] float Confidence,
|
||||
[property: Key(5)] string Notes);
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed record SpeechUtteranceEmbeddingInputSnapshot(
|
||||
|
|
@ -182,15 +195,17 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
SpeechDistributedTrainingOptions? options = null)
|
||||
{
|
||||
options ??= new SpeechDistributedTrainingOptions();
|
||||
var createdAt = DateTimeOffset.UtcNow.ToString("O");
|
||||
var createdAt = DateTimeOffset.UtcNow;
|
||||
var createdAtText = createdAt.ToString("O");
|
||||
var requests = new SpeechRenderRequest[examples.Count];
|
||||
for (var index = 0; index < examples.Count; index++)
|
||||
{
|
||||
var example = examples[index];
|
||||
var candidate = pipeline.Predict(example).ToVector(pipeline.SynthDriver.MelBandCount);
|
||||
requests[index] = new SpeechRenderRequest(
|
||||
$"{batchId}:{index:0000}",
|
||||
batchId,
|
||||
createdAt,
|
||||
createdAtText,
|
||||
options.CurriculumTier,
|
||||
options.TargetReferenceId,
|
||||
options.MorphologyId,
|
||||
|
|
@ -198,8 +213,17 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
options.ScoringRecipeId,
|
||||
SpeechUtteranceEmbeddingInputSnapshot.From(example.UtteranceInput),
|
||||
SpeechPhoneticEventSnapshot.From(example.Event),
|
||||
pipeline.Predict(example).ToVector(pipeline.SynthDriver.MelBandCount),
|
||||
example.Target.ToVector(pipeline.SynthDriver.MelBandCount));
|
||||
candidate,
|
||||
example.Target.ToVector(pipeline.SynthDriver.MelBandCount),
|
||||
[
|
||||
new SpeechTimingReceipt(
|
||||
"utterance-to-candidate-controls",
|
||||
createdAtText,
|
||||
0,
|
||||
0,
|
||||
CandidateConfidence(candidate),
|
||||
"Local request creation captured the current utterance encoder and synth-driver decision.")
|
||||
]);
|
||||
}
|
||||
|
||||
return requests;
|
||||
|
|
@ -214,9 +238,12 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
var results = new SpeechRenderResult[requests.Count];
|
||||
for (var index = 0; index < requests.Count; index++)
|
||||
{
|
||||
var startedAt = DateTimeOffset.UtcNow;
|
||||
var startedStamp = Stopwatch.GetTimestamp();
|
||||
var request = requests[index];
|
||||
if (request.CandidateControlVector.Length != request.ReferenceControlVector.Length)
|
||||
{
|
||||
var latency = Stopwatch.GetElapsedTime(startedStamp).TotalMilliseconds;
|
||||
results[index] = new SpeechRenderResult(
|
||||
$"{request.RequestId}:failed",
|
||||
request.RequestId,
|
||||
|
|
@ -228,6 +255,15 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
[],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
new SpeechTimingReceipt(
|
||||
"score-render-result",
|
||||
startedAt.ToString("O"),
|
||||
latency,
|
||||
0,
|
||||
0,
|
||||
"Rejected before scoring because the candidate/reference control vector sizes differ.")
|
||||
],
|
||||
"control_vector_size_mismatch",
|
||||
"Candidate and reference control vectors must have the same length.");
|
||||
continue;
|
||||
|
|
@ -243,6 +279,7 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
}
|
||||
|
||||
loss /= gradient.Length;
|
||||
var scoreLatency = Stopwatch.GetElapsedTime(startedStamp).TotalMilliseconds;
|
||||
results[index] = new SpeechRenderResult(
|
||||
$"{request.RequestId}:score",
|
||||
request.RequestId,
|
||||
|
|
@ -253,7 +290,16 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
loss,
|
||||
gradient,
|
||||
[new SpeechScoreMetric("control_mse", loss, 1)],
|
||||
[new SpeechRenderArtifact("score-report", $"cultcache://speech-render-result/{request.RequestId}", "")]);
|
||||
[new SpeechRenderArtifact("score-report", $"cultcache://speech-render-result/{request.RequestId}", "")],
|
||||
[
|
||||
new SpeechTimingReceipt(
|
||||
"score-render-result",
|
||||
startedAt.ToString("O"),
|
||||
scoreLatency,
|
||||
0,
|
||||
LossConfidence(loss),
|
||||
"Local control-vector proof scored candidate output against reference output.")
|
||||
]);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
|
@ -295,14 +341,50 @@ public static class SpeechDistributedTrainingCoordinator
|
|||
throw new InvalidOperationException("no successful speech render results matched the request batch");
|
||||
}
|
||||
|
||||
var checkpointCreatedAt = DateTimeOffset.UtcNow.ToString("O");
|
||||
var checkpoint = new SpeechTrainingCheckpoint(
|
||||
checkpointId,
|
||||
requests.FirstOrDefault()?.BatchId ?? "",
|
||||
DateTimeOffset.UtcNow.ToString("O"),
|
||||
checkpointCreatedAt,
|
||||
applied.Count,
|
||||
loss / applied.Count,
|
||||
[.. appliedIds],
|
||||
"Applied remote-render score gradients to the utterance encoder and synth driver.");
|
||||
"Applied remote-render score gradients to the utterance encoder and synth driver.",
|
||||
[
|
||||
new SpeechTimingReceipt(
|
||||
"apply-render-gradients",
|
||||
checkpointCreatedAt,
|
||||
0,
|
||||
0,
|
||||
LossConfidence(loss / applied.Count),
|
||||
"Checkpoint records the training step that consumed successful render results.")
|
||||
]);
|
||||
return new SpeechDistributedTrainingApplyResult(checkpoint, applied);
|
||||
}
|
||||
|
||||
private static float CandidateConfidence(IReadOnlyList<float> candidate)
|
||||
{
|
||||
if (candidate.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var sum = 0f;
|
||||
for (var index = 0; index < candidate.Count; index++)
|
||||
{
|
||||
sum += Math.Abs(candidate[index] - 0.5f) * 2f;
|
||||
}
|
||||
|
||||
return Math.Clamp(sum / candidate.Count, 0f, 1f);
|
||||
}
|
||||
|
||||
private static float LossConfidence(float loss)
|
||||
{
|
||||
if (!float.IsFinite(loss))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.Clamp(1f / (1f + loss), 0f, 1f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,3 +121,4 @@
|
|||
{"date":"2026-05-19","topic":"distributed-speech-training-cache-contract","lesson":"The speech-learning machine should distribute expensive render evidence instead of renderer authority. AquaSynth emits typed speech render requests, Faust workers write typed score results, and the trainer consumes synced gradients to update the utterance encoder and synth driver; CultCache/CultMesh carry the documents between machines.","evidence":"Added SpeechDistributedTraining.cs with SpeechRenderRequest, SpeechRenderResult, SpeechTrainingCheckpoint, CultCache store helpers, and a coordinator proof. SpeechDistributedTrainingTests round-trip request/result/checkpoint .cc stores and prove synced score gradients reduce local pipeline loss."}
|
||||
{"date":"2026-05-20","topic":"utterance-parity-current-objective","lesson":"AquaSynth's active objective is now the utterance parity curriculum: structured utterance metadata lowers into a learned utterance embedding, then into learned synth automation for the vocal tract and associated envelopes/LFOs/filters/spectral lanes, then into rendered speech loss. Broad DX7/Zyn synth digestion is backburner work unless it directly improves this harness.","evidence":"Updated README, docs/audio-doctrine.md, docs/reference-synth-roadmap.md, docs/ipa-vocal-tract-roadmap.md, state/map.yaml, state/spine.yaml, and state/memory.json to make the speech parity harness the current center."}
|
||||
{"date":"2026-05-20","topic":"utterance-parity-prior-work","lesson":"Prior work supports AquaSynth's architecture when kept in its lane: Kelly-Lochbaum/Pink Trombone and Story area functions support tract/area authority; TADA/articulatory phonology supports overlapping gestures; eSpeak/Klatt-style formant synthesis is useful fast ground truth but not anatomy; DDSP/neural source-filter work supports learned controllers over interpretable DSP rather than black-box throat replacement.","evidence":"Added docs/utterance-parity-research.md with source ledger covering Pink Trombone, VocalTractLab, Story, TADA, eSpeak NG, PanPhon, PHOIBLE, DDSP, DDSP articulatory vocoders, neural source-filter models, and LF voice-source references."}
|
||||
{"date":"2026-05-20","topic":"speech-training-time-witness","lesson":"Speech training receipts need time and confidence as evidence, not only loss. Each curriculum step should record when the stage decided, observed latency, intended latency budget, confidence, and artifact/model identifiers; a good-sounding result from an unbounded clockless path is not trustworthy.","evidence":"Added SpeechTimingReceipt to SpeechRenderRequest, SpeechRenderResult, and SpeechTrainingCheckpoint; seeded timing/confidence receipts in the local distributed-training proof; updated audio doctrine, IPA roadmap, utterance parity research notes, spine, and memory."}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
"SFXR atoms and parameter mapping",
|
||||
"analysis/comparison metrics",
|
||||
"Faust-generated C# candidate rendering",
|
||||
"speech timing/confidence receipts for render requests, results, and checkpoints",
|
||||
"script scoring",
|
||||
"preset catalogs",
|
||||
"Faust emission and compiler wrapper"
|
||||
|
|
@ -91,7 +92,7 @@
|
|||
"ipa_vocal_tract_path": {
|
||||
"status": "This is now the active project center. Roadmap and research notes align the curriculum around structured utterance metadata -> learned utterance embedding -> learned synth automation -> vocal tract/envelope/LFO/filter/spectral controls -> rendered audio loss. The first Core contracts still defend the boundary: PhoneticIntent is the Weksa/language-owned handoff, ArticulatoryPlan is the AquaSynth realization layer, VocalTractMorphology declares anatomy capabilities, ArticulatoryConstraintReport is the host-visible veto surface, and VocalTractPlanResult is the tiny end-to-end proof artifact for host branching. IPA is an authoring input only: tokenization and feature mapping lower into language phonology profiles, timed articulatory gestures, morphology-specific tract/excitation curves, and Faust DSP. The staged support plan starts with tokenization and vowels, then core pulmonic consonants, expanded human IPA, airstream/phonation, and Weksa/alien morphologies. Optional eSpeak NG tests provide the first generated speech workout when ESPEAK_NG or espeak-ng is installed; eSpeak is intelligibility/timing/log-mel pressure, not anatomy truth. AquaSynth owns the learned utterance embedding encoder and synth driver: speech text embeddings plus prosody/emphasis hints plus Ghostlight/Epiphany-shaped character state vectors compress into utterance embeddings, and phonetic features plus that embedding feed a packed-buffer native C# backprop/Adam mapper that predicts tract, AM/FM, LFO, filter, and mel spectral-envelope controls. SpeechBackpropagationPipeline chains the two models: synth-driver target loss returns gradients with respect to its input, slices the utterance-embedding gradient, and updates the utterance encoder from downstream speech loss. The generated ground-truth training fixture renders eSpeak syllables, extracts log-mel evidence, and trains that chained pipeline toward eSpeak-seeded mel/control targets.",
|
||||
"lesson": "Do not build a complete IPA parser before the tract can say basic syllables. The durable machine is tokenizer -> feature mapper -> phonology profile -> gesture planner -> morphology -> tract plan -> Faust renderer, with dependency-injected seams and mocks at each boundary. Weksa should use IPA-like phonetic input without letting human IPA semantics own alien anatomy. Anatomy has veto power: if a morphology lacks bilabial closure or another required capability, AquaSynth reports that failure instead of smearing over it.",
|
||||
"next": "Tighten the parity harness before expanding expressivity: render tiny utterance fixtures, sweep controller outputs through compiled Faust controls, score log-mel/timing/envelope loss, backpropagate into the utterance encoder plus synth driver, then connect Stage 0/1 IPA token spans, vowel feature bundles, and human baseline tract-area targets to that loop."
|
||||
"next": "Tighten the parity harness before expanding expressivity: render tiny utterance fixtures, sweep controller outputs through compiled Faust controls, score log-mel/timing/envelope loss, record per-stage decision time/latency/budget/confidence receipts, backpropagate into the utterance encoder plus synth driver, then connect Stage 0/1 IPA token spans, vowel feature bundles, and human baseline tract-area targets to that loop."
|
||||
},
|
||||
"dx7_reference_path": {
|
||||
"status": "DX7 SysEx parser extracts voice edit buffers, packed voices, packed 32-voice banks, six operators, envelopes, algorithm, feedback, oscillator sync, LFO, transpose, voice name, checksum validation, ReferencePatch features, algorithm topology from the reverse-engineered OPS algorithm ROM table, envelope-to-ADSR approximations, measured carrier output amplitude, isolated two-op modulation route scale, summed/cascaded route scale, calibrated feedback amount table, ratio detune lowering, voice transpose lowering, fixed-frequency operator lowering, ROM COM output compensation cut, operator key/rate scaling at the effective played note, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering with optional sustain floor, two AquaSynth DSL rebuild attempts, first-class OperatorGraph support with graph-level vibrato, public-domain SysEx fixture coverage, dexed-py reference rendering, project-authored Dexed patch rendering, AquaSynth candidate rendering through Faust-generated C#, one thresholded rendered parity test for analog1.syx voice 13, a hard PRC SYNTH1 parity probe with listening WAV artifacts and log-mel <= 0.145, project-authored algorithm-8 summed-stack/cascade/summed-pair parity, rendered community parity for Mooger, Piano Bass, RES SYNTH1, and ANLGSYN 1 with a sustained high-band gate, a MELLOWSOLO pressure artifact test, and renderable delayed operator feedback.",
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ live_invariants:
|
|||
- Every learned speech arrow must stay inspectable: utterance metadata -> embedding, embedding plus phonetic features -> automation, automation -> rendered audio, rendered audio -> loss/gradient.
|
||||
- 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.
|
||||
- Speech training receipts must keep time as a witness: every request, render result, and checkpoint should record stage decision time, latency spent, latency budget, confidence, and artifact/model identifiers.
|
||||
|
||||
reference_targets:
|
||||
ipa_vocal_tract_weksa:
|
||||
|
|
@ -119,7 +120,7 @@ reference_targets:
|
|||
sources:
|
||||
- "docs/ipa-vocal-tract-roadmap.md"
|
||||
- "docs/utterance-parity-research.md"
|
||||
status: "Boundary contracts, packed neural networks, utterance encoder, synth-driver mapper, chained backprop, optional eSpeak rendered fixtures, Faust finite-difference loss surface, compiled Faust speech-loss controls, distributed render request/result/checkpoint contracts, and sound-claim cards exist. The active need is a fast parity harness that can test many controller weights as rendered speech output against simple ground truth."
|
||||
status: "Boundary contracts, packed neural networks, utterance encoder, synth-driver mapper, chained backprop, optional eSpeak rendered fixtures, Faust finite-difference loss surface, compiled Faust speech-loss controls, distributed render request/result/checkpoint contracts, timing/confidence receipts, and sound-claim cards exist. The active need is a fast parity harness that can test many controller weights as rendered speech output against simple ground truth."
|
||||
first_slice:
|
||||
- "Keep the utterance schema small and testable: segments, spans, duration/stress/boundary hints, speaker/morphology identity, pitch/loudness/rate/breath controls, and named context vectors."
|
||||
- "Use eSpeak NG fixtures for generated ground truth while remembering that formant parity is not anatomy parity."
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ public sealed class SpeechDistributedTrainingTests
|
|||
Assert.Equal(examples.Count, loadedResults.Count);
|
||||
Assert.All(loadedResults, result => Assert.Equal(SpeechRenderStatus.Succeeded, result.Status));
|
||||
Assert.All(loadedResults, result => Assert.Equal(pipeline.SynthDriver.OutputSize, result.OutputGradient.Length));
|
||||
Assert.All(loadedRequests, request => Assert.Contains(request.TimingReceipts, receipt => receipt.StageId == "utterance-to-candidate-controls"));
|
||||
Assert.All(loadedResults, result => Assert.Contains(result.TimingReceipts, receipt => receipt.StageId == "score-render-result"));
|
||||
Assert.All(loadedResults, result => Assert.All(result.TimingReceipts, receipt => Assert.InRange(receipt.Confidence, 0, 1)));
|
||||
Assert.Contains(loadedRequests, request => request.RendererProfileId == "compiled-faust-worker");
|
||||
}
|
||||
finally
|
||||
|
|
@ -73,6 +76,8 @@ public sealed class SpeechDistributedTrainingTests
|
|||
Assert.Equal(examples.Count, applied.Checkpoint.AppliedResultCount);
|
||||
Assert.Single(checkpoints);
|
||||
Assert.Equal("checkpoint-0001", checkpoints[0].CheckpointId);
|
||||
Assert.Contains(checkpoints[0].TimingReceipts, receipt => receipt.StageId == "apply-render-gradients");
|
||||
Assert.All(checkpoints[0].TimingReceipts, receipt => Assert.InRange(receipt.Confidence, 0, 1));
|
||||
Assert.All(applied.Backpropagations, backprop => Assert.True(backprop.InputGradient.Length > 0));
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
Loading…
Reference in a new issue