381 lines
10 KiB
Python
381 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Emit tiny v0.1 utterance handoff fixtures for AquaSynth speech training.
|
|
|
|
This is intentionally not a Weksa runtime. It is a deterministic artifact
|
|
emitter for the first crawl-stage curriculum: simple IPA syllables with the
|
|
PanPhon 22-feature basis, fixed-width Weksa/Epiphany input vectors, and pending
|
|
AquaSynth-owned learned outputs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
OUT = ROOT / "examples" / "speech-training" / "tiny-panphon-v0.1"
|
|
VECTORS = OUT / "vectors"
|
|
|
|
SCHEMA_VERSION = "weksa.utterance_embedding_handoff.v0.1"
|
|
DECIDED_AT_UTC = "2026-05-20T00:00:00Z"
|
|
|
|
FEATURE_ORDER = [
|
|
"syl",
|
|
"son",
|
|
"cons",
|
|
"cont",
|
|
"delrel",
|
|
"lat",
|
|
"nas",
|
|
"strid",
|
|
"voi",
|
|
"sg",
|
|
"cg",
|
|
"ant",
|
|
"cor",
|
|
"distr",
|
|
"lab",
|
|
"hi",
|
|
"lo",
|
|
"back",
|
|
"round",
|
|
"velaric",
|
|
"tense",
|
|
"long",
|
|
]
|
|
|
|
PROSODY_HINT_LABELS = [
|
|
"mean_pitch_pressure",
|
|
"pitch_rise",
|
|
"pitch_fall",
|
|
"pitch_range",
|
|
"loudness_pressure",
|
|
"loudness_attack",
|
|
"loudness_release",
|
|
"speaking_rate",
|
|
"articulation_precision",
|
|
"pause_pressure",
|
|
"phrase_final_lengthening",
|
|
"stress_contrast",
|
|
"clippedness",
|
|
"breathiness",
|
|
"creak_pressure",
|
|
"smoothness",
|
|
"warmth",
|
|
"threat",
|
|
"urgency",
|
|
"hesitation",
|
|
"irony_dryness",
|
|
"tenderness",
|
|
"contempt",
|
|
"command_force",
|
|
"question_contour",
|
|
"exclamation_pressure",
|
|
"secrecy_low_projection",
|
|
"ritual_formality_pressure",
|
|
"playful_pressure",
|
|
"fatigue",
|
|
"emotional_containment",
|
|
"reserved",
|
|
]
|
|
|
|
CHARACTER_STATE_LABELS = [
|
|
"valence",
|
|
"arousal",
|
|
"dominance",
|
|
"urgency",
|
|
"anger",
|
|
"despair",
|
|
"sadness",
|
|
"fear",
|
|
"anxiety",
|
|
"disgust",
|
|
"contempt",
|
|
"annoyance",
|
|
"dismissal",
|
|
"flippancy",
|
|
"playfulness",
|
|
"irony",
|
|
"tenderness",
|
|
"warmth",
|
|
"joy",
|
|
"excitement",
|
|
"fatigue",
|
|
"guardedness",
|
|
"confidence",
|
|
"shame",
|
|
"pride",
|
|
"threat",
|
|
"secrecy",
|
|
"hesitation",
|
|
"emotionalContainment",
|
|
"thoughtPressure",
|
|
"reactionIntensity",
|
|
"commandForce",
|
|
"currentLoad",
|
|
"initiativeHeat",
|
|
"pendingTurn",
|
|
"cooldownPressure",
|
|
"reactionBias",
|
|
"interruptThreshold",
|
|
"initiativeSpeed",
|
|
"voiceBubbly",
|
|
"voiceDryness",
|
|
"voiceFormality",
|
|
"voiceDirectness",
|
|
"voiceIntensity",
|
|
"voiceWarmth",
|
|
"voicePrecision",
|
|
"voiceRitualRegister",
|
|
"presentationPushiness",
|
|
"presentationSelfFocus",
|
|
"presentationPlay",
|
|
"presentationCareDemand",
|
|
"behaviorWorkDrive",
|
|
"behaviorBoundaryDefense",
|
|
"behaviorPurityDrive",
|
|
"behaviorPatience",
|
|
"stableAgreeableness",
|
|
"stableConscientiousness",
|
|
"stableNeuroticism",
|
|
"stableOpenness",
|
|
"organizationCoherence",
|
|
"organizationRigidity",
|
|
"slowTraitReserved62",
|
|
"slowTraitReserved63",
|
|
"slowTraitReserved64",
|
|
]
|
|
|
|
PHONE_FEATURES = {
|
|
"a": {
|
|
"syl": 1,
|
|
"son": 1,
|
|
"cons": -1,
|
|
"cont": 1,
|
|
"voi": 1,
|
|
"lo": 1,
|
|
"round": -1,
|
|
"tense": -1,
|
|
},
|
|
"p": {
|
|
"syl": -1,
|
|
"son": -1,
|
|
"cons": 1,
|
|
"cont": -1,
|
|
"voi": -1,
|
|
"lab": 1,
|
|
},
|
|
"t": {
|
|
"syl": -1,
|
|
"son": -1,
|
|
"cons": 1,
|
|
"cont": -1,
|
|
"voi": -1,
|
|
"ant": 1,
|
|
"cor": 1,
|
|
},
|
|
"k": {
|
|
"syl": -1,
|
|
"son": -1,
|
|
"cons": 1,
|
|
"cont": -1,
|
|
"voi": -1,
|
|
"back": 1,
|
|
},
|
|
"s": {
|
|
"syl": -1,
|
|
"son": -1,
|
|
"cons": 1,
|
|
"cont": 1,
|
|
"strid": 1,
|
|
"voi": -1,
|
|
"ant": 1,
|
|
"cor": 1,
|
|
},
|
|
"m": {
|
|
"syl": -1,
|
|
"son": 1,
|
|
"cons": 1,
|
|
"cont": -1,
|
|
"nas": 1,
|
|
"voi": 1,
|
|
"lab": 1,
|
|
},
|
|
}
|
|
|
|
UTTERANCES = [
|
|
("open-vowel-a", "a", ["a"], "open low vowel baseline"),
|
|
("bilabial-pa", "pa", ["p", "a"], "voiceless bilabial stop into open vowel"),
|
|
("alveolar-ta", "ta", ["t", "a"], "voiceless alveolar stop into open vowel"),
|
|
("velar-ka", "ka", ["k", "a"], "voiceless velar stop into open vowel"),
|
|
("sibilant-sa", "sa", ["s", "a"], "voiceless alveolar sibilant into open vowel"),
|
|
("nasal-ma", "ma", ["m", "a"], "voiced bilabial nasal into open vowel"),
|
|
]
|
|
|
|
|
|
def feature_vector(phone: str) -> list[float]:
|
|
features = PHONE_FEATURES[phone]
|
|
return [float(features.get(name, 0)) for name in FEATURE_ORDER]
|
|
|
|
|
|
def frame(phone: str, index: int, count: int) -> dict:
|
|
is_vowel = PHONE_FEATURES[phone].get("syl") == 1
|
|
duration_ms = 180 if is_vowel else 95
|
|
return {
|
|
"index": index,
|
|
"phone": phone,
|
|
"features": feature_vector(phone),
|
|
"markers": {
|
|
"stress": 1.0 if is_vowel else 0.0,
|
|
"length": 0.0,
|
|
"tone": 0.0,
|
|
"boundary": 1.0 if index == count - 1 else 0.0,
|
|
"duration_ms": duration_ms,
|
|
},
|
|
}
|
|
|
|
|
|
def write_json(path: Path, value: object) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
|
|
|
|
def packet(packet_id: str, ipa: str, phones: list[str], note: str) -> dict:
|
|
prosody_values = [0.0] * 32
|
|
prosody_values[7] = -0.15
|
|
prosody_values[8] = 0.35
|
|
prosody_values[11] = 0.2
|
|
prosody_values[15] = 0.25
|
|
|
|
return {
|
|
"schema_version": SCHEMA_VERSION,
|
|
"packet_id": f"tiny-panphon-v0.1/{packet_id}",
|
|
"source_output_ref": "examples/speech-training/tiny-panphon-v0.1/README.md",
|
|
"speaker_agent_id": "neutral-human-baseline",
|
|
"text": {
|
|
"spoken_text": ipa,
|
|
"normalized_text": ipa,
|
|
"channel_note": "Pure IPA curriculum seed; semantic text embedding is intentionally zero.",
|
|
},
|
|
"inputs": {
|
|
"speech_text_embedding": {
|
|
"model_id": "bge-m3:latest",
|
|
"dimensionality": 1024,
|
|
"values_ref": "vectors/zero-semantic-1024.json",
|
|
},
|
|
"panphon_sequence": {
|
|
"model_id": "panphon.v0.1.seed",
|
|
"source": "hand-authored seed phones for AquaSynth crawl-stage parity tests",
|
|
"ipa": ipa,
|
|
"feature_order": FEATURE_ORDER,
|
|
"frames": [frame(phone, i, len(phones)) for i, phone in enumerate(phones)],
|
|
},
|
|
"prosody_emphasis_hints": {
|
|
"dimensionality": 32,
|
|
"labels": PROSODY_HINT_LABELS,
|
|
"values": prosody_values,
|
|
},
|
|
"character_state_vector": {
|
|
"source": "epiphany.agent_utterance_state.v0 neutral baseline",
|
|
"schema_ref": "E:/Projects/EpiphanyAgent/schemas/agent-utterance-state-schema.md",
|
|
"dimensionality": 64,
|
|
"values_ref": "vectors/neutral-character-64.json",
|
|
},
|
|
},
|
|
"phonetic_realization_embedding": {
|
|
"owner": "AquaSynth",
|
|
"model_id": "aquasynth.panphon_sequence_encoder.v0.1",
|
|
"dimensionality": 256,
|
|
"status": "pending_aquasynth_training_or_encoding",
|
|
"values_ref": None,
|
|
},
|
|
"utterance_embedding": {
|
|
"owner": "AquaSynth",
|
|
"model_id": "aquasynth.utterance_embedding_encoder.v0.1",
|
|
"version": None,
|
|
"training_set": "tiny-panphon-v0.1",
|
|
"dimensionality": 64,
|
|
"status": "pending_aquasynth_training_or_encoding",
|
|
"values_ref": None,
|
|
},
|
|
"audit_projection": {
|
|
"semantic_preservation": "No lexical semantics; preserve the requested IPA sound shape.",
|
|
"emphasis": "Single syllable baseline stress where a vowel exists.",
|
|
"prosody": "Neutral, slow, precise, no expressive contour.",
|
|
"character_pressure": "Neutral speaker baseline.",
|
|
"delivery_shape": note,
|
|
},
|
|
"training_refs": {
|
|
"positive_examples": [
|
|
{
|
|
"kind": "reference_synth_prompt",
|
|
"engine": "eSpeak NG or equivalent baseline speech oracle",
|
|
"text": ipa,
|
|
}
|
|
],
|
|
"negative_examples": [],
|
|
},
|
|
"trace": {
|
|
"pipeline_receipts": [
|
|
{
|
|
"stage": "weksa_emit_tiny_panphon_handoff",
|
|
"decided_at_utc": DECIDED_AT_UTC,
|
|
"latency_budget_ms": 10,
|
|
"observed_latency_ms": 0,
|
|
"confidence": 0.72,
|
|
}
|
|
],
|
|
"notes": [
|
|
"This fixture is a crawl-stage training seed, not a full English lowering artifact.",
|
|
"The schema uses PanPhon's 22-feature basis; these seed values are hand-authored for the six seed phones until Weksa wires a canonical PanPhon dependency.",
|
|
"AquaSynth owns the learned 256-float phonetic realization embedding and 64-float utterance embedding.",
|
|
],
|
|
"uncertainties": [
|
|
"Feature values should be regenerated from the canonical PanPhon library before broad phoneme coverage.",
|
|
"Durations are seed timing hints, not measured reference audio.",
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def emit() -> None:
|
|
write_json(
|
|
VECTORS / "zero-semantic-1024.json",
|
|
{
|
|
"model_id": "bge-m3:latest",
|
|
"dimensionality": 1024,
|
|
"values": [0.0] * 1024,
|
|
"reason": "Pure IPA fixture; semantic text channel is intentionally silent.",
|
|
},
|
|
)
|
|
write_json(
|
|
VECTORS / "neutral-character-64.json",
|
|
{
|
|
"source": "epiphany.agent_utterance_state.v0 neutral baseline",
|
|
"dimensionality": 64,
|
|
"labels": CHARACTER_STATE_LABELS,
|
|
"values": [0.0] * 64,
|
|
},
|
|
)
|
|
|
|
packet_refs = []
|
|
for packet_id, ipa, phones, note in UTTERANCES:
|
|
path = OUT / f"{packet_id}.json"
|
|
write_json(path, packet(packet_id, ipa, phones, note))
|
|
packet_refs.append(path.name)
|
|
|
|
write_json(
|
|
OUT / "batch.json",
|
|
{
|
|
"schema_version": "weksa.utterance_handoff_batch.v0.1",
|
|
"batch_id": "tiny-panphon-v0.1",
|
|
"packet_schema_version": SCHEMA_VERSION,
|
|
"packet_refs": packet_refs,
|
|
"purpose": "Initial AquaSynth parity curriculum for simple IPA sounds.",
|
|
},
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
emit()
|