From b0dcedb4029b08bf8f9fec1d4342b3e9c134ac0d Mon Sep 17 00:00:00 2001 From: Denis Defolie Date: Sun, 11 Oct 2026 19:07:40 +0200 Subject: [PATCH] Replace eSpeak NG with Piper neural voice for dictation audio Manual test reported the eSpeak NG dictation voice as unintelligible. Formant synthesis is inherently mechanical; slowing it down doesn't fix that. Switched tools/generate_dictation_audio.py to piper-tts (fr_FR-siwis-medium neural voice, MIT/CC-BY 4.0, attribution in assets/CREDITS.md). The voice model (~60MB) downloads once on demand into the untracked .cache/piper-voices/, never at game runtime. Adds piper-tts to tools/setup.sh's venv and a pyrightconfig.json pointing pyright at .venv, needed once tools/ imports a real third-party package (`make lint` was silently checking against the wrong interpreter before). Regenerated ce1_accord_adjectif_dictee_01.ogg with the new voice. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 3 + CLAUDE.md | 2 +- assets/CREDITS.md | 4 +- .../ce1_accord_adjectif_dictee_01.ogg | Bin 12546 -> 12380 bytes docs/specs/decisions.md | 12 +++ docs/testing/manual-checklist.md | 4 +- pyrightconfig.json | 4 + tools/generate_dictation_audio.py | 93 +++++++++++------- tools/setup.sh | 8 +- 9 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 pyrightconfig.json diff --git a/.gitignore b/.gitignore index dd36eaa..2643d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ build/ addons/gut/ .venv/ +# Modeles de voix telecharges par tools/generate_dictation_audio.py, non versionnes +.cache/ + # Python __pycache__/ *.pyc diff --git a/CLAUDE.md b/CLAUDE.md index f8aab7b..3f3c736 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ Le `Makefile` est le seul point d'entrée pour les opérations système. Tenir c - `make test` : tests en mode headless - `make check` : lint + validate-data + test (à passer avant chaque commit) - `make content-review` : régénère `docs/review/content-review.md` à partir de `data/` -- `make generate-dictation-audio` : génère l'audio provisoire des dictées (eSpeak-ng + ffmpeg, requis sur la machine) dans `assets/audio/dictations/`, sans écraser l'existant (`--force` pour régénérer) +- `make generate-dictation-audio` : génère l'audio provisoire des dictées (voix neuronale Piper via `.venv`, `ffmpeg` requis sur la machine) dans `assets/audio/dictations/`, sans écraser l'existant (`--force` pour régénérer) - `make export-linux` / `make export-windows` : exports dans `build/` ## Conventions de code diff --git a/assets/CREDITS.md b/assets/CREDITS.md index 22fa2e3..706ac98 100644 --- a/assets/CREDITS.md +++ b/assets/CREDITS.md @@ -5,6 +5,8 @@ Tous les graphismes et sons actuels sont provisoires, crees pour ce projet, sans | Fichier | Origine | Licence | |---|---|---| | `icon.svg` | Forme geometrique simple, creee pour ce projet | AGPL-3.0 (code et assets du depot) | -| `audio/dictations/ce1_accord_adjectif_dictee_01.ogg` | Voix de synthese generee hors ligne avec eSpeak NG 1.52 (voix `fr-fr`), via `tools/generate_dictation_audio.py` | GPL-3.0 (eSpeak NG) ; fichier genere verse en AGPL-3.0 avec le reste du depot | +| `audio/dictations/ce1_accord_adjectif_dictee_01.ogg` | Voix de synthese neuronale generee hors ligne avec Piper (modele `fr_FR-siwis-medium`, entraine sur le corpus SIWIS de l'EPFL), via `tools/generate_dictation_audio.py` | MIT (Piper) ; dataset SIWIS en CC-BY 4.0 (attribution requise) ; fichier genere verse en AGPL-3.0 avec le reste du depot | Chaque nouvelle dictee ajoutee a `data/dictations/` doit avoir son audio genere avec `make generate-dictation-audio`, puis une ligne ajoutee ici. Toutes les voix actuelles sont provisoires (`audio_provisional: true` dans la donnee, badge « voix provisoire » affiche a l'enfant) : a remplacer par une vraie voix humaine avant diffusion. + +Attribution requise par la licence CC-BY 4.0 du corpus SIWIS : Honnet, P.-E., Lazaridis, A., Garner, P. N., & Yamagishi, J. (2017). *The SIWIS French Speech Synthesis Database*. Idiap Research Institute / EPFL. diff --git a/assets/audio/dictations/ce1_accord_adjectif_dictee_01.ogg b/assets/audio/dictations/ce1_accord_adjectif_dictee_01.ogg index 746997d19474d812ca26e8915d33282905c19c40..55034868ae930d41242681170564e0247f8165f3 100644 GIT binary patch delta 9077 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 1.0 ralentit legerement le debit (duree des phonemes etiree), plus +# confortable pour un enfant de 7-9 ans qu'une diction par defaut pensee +# pour un adulte. +LENGTH_SCALE = 1.1 def _require_tool(name: str, install_hint: str) -> str: @@ -32,6 +52,20 @@ def _require_tool(name: str, install_hint: str) -> str: return path +def _ensure_voice() -> Path: + VOICE_CACHE_DIR.mkdir(parents=True, exist_ok=True) + model_path = VOICE_CACHE_DIR / f"{VOICE_NAME}.onnx" + config_path = VOICE_CACHE_DIR / f"{VOICE_NAME}.onnx.json" + + if not model_path.exists(): + print(f"Telechargement de la voix {VOICE_NAME} (environ 60 Mo, une seule fois)...") + urlretrieve(f"{VOICE_BASE_URL}/{VOICE_NAME}.onnx", model_path) + if not config_path.exists(): + urlretrieve(f"{VOICE_BASE_URL}/{VOICE_NAME}.onnx.json", config_path) + + return model_path + + def load_dictations() -> list[dict[str, Any]]: return [ json.loads(path.read_text(encoding="utf-8")) @@ -39,26 +73,16 @@ def load_dictations() -> list[dict[str, Any]]: ] -def generate_one(espeak_bin: str, ffmpeg_bin: str, dictation: dict[str, Any]) -> Path: +def generate_one(voice: PiperVoice, ffmpeg_bin: str, dictation: dict[str, Any]) -> Path: audio_id: str = dictation["audio_id"] target_text: str = dictation["target_text"] output_path = AUDIO_DIR / f"{audio_id}.ogg" + syn_config = SynthesisConfig(length_scale=LENGTH_SCALE) with tempfile.TemporaryDirectory() as tmp_dir: tmp_wav = Path(tmp_dir) / f"{audio_id}.wav" - subprocess.run( - [ - espeak_bin, - "-v", - ESPEAK_VOICE, - "-s", - ESPEAK_SPEED_WPM, - "-w", - str(tmp_wav), - target_text, - ], - check=True, - ) + with wave.open(str(tmp_wav), "wb") as wav_file: + voice.synthesize_wav(target_text, wav_file, syn_config=syn_config) subprocess.run( [ ffmpeg_bin, @@ -87,27 +111,26 @@ def main() -> None: ) args = parser.parse_args() - espeak_bin = _require_tool( - "espeak-ng", - "A installer via le gestionnaire de paquets du systeme (ex. " - "`sudo apt install espeak-ng` sous Debian/Ubuntu, `brew install espeak-ng` sous macOS).", - ) ffmpeg_bin = _require_tool("ffmpeg", "A installer via le gestionnaire de paquets du systeme.") AUDIO_DIR.mkdir(parents=True, exist_ok=True) - generated: list[str] = [] + provisional_dictations = [d for d in load_dictations() if d.get("audio_provisional", False)] + dictations_to_generate: list[dict[str, Any]] = [] skipped: list[str] = [] - for dictation in load_dictations(): - if not dictation.get("audio_provisional", False): - continue - audio_id: str = dictation["audio_id"] - output_path = AUDIO_DIR / f"{audio_id}.ogg" + for dictation in provisional_dictations: + output_path = AUDIO_DIR / f"{dictation['audio_id']}.ogg" if output_path.exists() and not args.force: - skipped.append(audio_id) - continue - generate_one(espeak_bin, ffmpeg_bin, dictation) - generated.append(audio_id) + skipped.append(dictation["audio_id"]) + else: + dictations_to_generate.append(dictation) + + generated: list[str] = [] + if dictations_to_generate: + voice = PiperVoice.load(str(_ensure_voice())) + for dictation in dictations_to_generate: + generate_one(voice, ffmpeg_bin, dictation) + generated.append(dictation["audio_id"]) print(f"Audio genere : {len(generated)} fichier(s) ({', '.join(generated) or 'aucun'}).") if skipped: diff --git a/tools/setup.sh b/tools/setup.sh index b32d7b6..e79e119 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash # Installe l'outillage de developpement : binaire Godot pinne, addon GUT, -# environnement Python pour gdtoolkit. Rien de ceci n'est commite (voir .gitignore) : -# ce script est la seule source de verite pour reconstruire l'outillage. +# environnement Python pour gdtoolkit/pyright/piper-tts (ce dernier pour +# tools/generate_dictation_audio.py). Rien de ceci n'est commite (voir +# .gitignore) : ce script est la seule source de verite pour reconstruire +# l'outillage. set -euo pipefail cd "$(dirname "$0")/.." @@ -66,7 +68,7 @@ if [ ! -d ".venv" ]; then python3 -m venv .venv fi ./.venv/bin/pip install --quiet --upgrade pip -./.venv/bin/pip install --quiet "gdtoolkit==${GDTOOLKIT_VERSION}" "pyright==${PYRIGHT_VERSION}" +./.venv/bin/pip install --quiet "gdtoolkit==${GDTOOLKIT_VERSION}" "pyright==${PYRIGHT_VERSION}" piper-tts echo "Import du projet (enregistre les class_name globaux : SchemaValidator, DataLoader...)." "${GODOT_BIN}" --headless --path . --import >/dev/null