[add] Load voice parameters from .env file

Configure Piper TTS synthesis via environment variables (speaker_id,
length_scale, noise_scale, noise_w_scale, volume) loaded from .env
with python-dotenv. Includes .env.example as reference template.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 07:36:22 +02:00
parent fc18af576e
commit 19d2721ff2
5 changed files with 60 additions and 6 deletions
+10
View File
@@ -0,0 +1,10 @@
# Piper TTS voice configuration
VOICE_MODEL=en_GB-alan-medium.onnx # Path to Piper .onnx voice model (relative to project root)
VOICE_SPEAKER_ID= # Speaker ID for multi-speaker models (integer, leave empty for default)
VOICE_LENGTH_SCALE= # Speech speed: >1.0 = slower, <1.0 = faster (leave empty for model default)
VOICE_NOISE_SCALE= # Phoneme variability: 0.0 = robotic, 1.0 = expressive (leave empty for model default)
VOICE_NOISE_W_SCALE= # Phoneme width variability (leave empty for model default)
VOICE_VOLUME=1.0 # Playback volume multiplier (default: 1.0)
# Vosk STT configuration
STT_MODEL=vosk-model-small-en-us-0.15 # Vosk model directory name (relative to project root)
+1
View File
@@ -4,3 +4,4 @@ __pycache__/
save.qzl save.qzl
vosk-model-*/ vosk-model-*/
.wolf .wolf
.env
+18 -2
View File
@@ -9,13 +9,29 @@ import wave
from pathlib import Path from pathlib import Path
from piper import PiperVoice from piper import PiperVoice
from piper.config import SynthesisConfig
class TTS: class TTS:
"""Speaks text using Piper TTS and aplay for playback.""" """Speaks text using Piper TTS and aplay for playback."""
def __init__(self, model_path: str | Path) -> None: def __init__(
self,
model_path: str | Path,
speaker_id: int | None = None,
length_scale: float | None = None,
noise_scale: float | None = None,
noise_w_scale: float | None = None,
volume: float = 1.0,
) -> None:
self._voice = PiperVoice.load(str(model_path)) self._voice = PiperVoice.load(str(model_path))
self._syn_config = SynthesisConfig(
speaker_id=speaker_id,
length_scale=length_scale,
noise_scale=noise_scale,
noise_w_scale=noise_w_scale,
volume=volume,
)
self._thread: threading.Thread | None = None self._thread: threading.Thread | None = None
self._last_wav: bytes | None = None self._last_wav: bytes | None = None
@@ -39,7 +55,7 @@ class TTS:
def _synthesize(self, text: str) -> bytes: def _synthesize(self, text: str) -> bytes:
buf = io.BytesIO() buf = io.BytesIO()
with wave.open(buf, "wb") as wav_file: with wave.open(buf, "wb") as wav_file:
self._voice.synthesize_wav(text, wav_file) self._voice.synthesize_wav(text, wav_file, syn_config=self._syn_config)
return buf.getvalue() return buf.getvalue()
def _play(self, wav_data: bytes) -> None: def _play(self, wav_data: bytes) -> None:
+30 -4
View File
@@ -2,8 +2,11 @@
"""The Hitchhiker's Guide to the Galaxy — Python text adventure engine.""" """The Hitchhiker's Guide to the Galaxy — Python text adventure engine."""
import argparse import argparse
import os
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv
from h2g2.engine.clock import Clock from h2g2.engine.clock import Clock
from h2g2.engine.loop import GameLoop from h2g2.engine.loop import GameLoop
from h2g2.engine.output import Output from h2g2.engine.output import Output
@@ -17,9 +20,25 @@ import h2g2.engine.verbs # noqa: F401
# Import content modules # Import content modules
from h2g2.content import globals_content, earth, vogon, heart, unearth, dark from h2g2.content import globals_content, earth, vogon, heart, unearth, dark
# Default model locations (project root) # Load .env from project root
_DEFAULT_VOICE = Path(__file__).resolve().parent.parent / "en_GB-alan-medium.onnx" _PROJECT_ROOT = Path(__file__).resolve().parent.parent
_DEFAULT_STT_MODEL = Path(__file__).resolve().parent.parent / "vosk-model-small-en-us-0.15" load_dotenv(_PROJECT_ROOT / ".env")
# Default model locations (from env or project root)
_DEFAULT_VOICE = _PROJECT_ROOT / os.getenv("VOICE_MODEL", "en_GB-alan-medium.onnx")
_DEFAULT_STT_MODEL = _PROJECT_ROOT / os.getenv("STT_MODEL", "vosk-model-small-en-us-0.15")
def _env_float(key: str) -> float | None:
"""Read an env var as float, returning None if empty/unset."""
val = os.getenv(key, "").strip()
return float(val) if val else None
def _env_int(key: str) -> int | None:
"""Read an env var as int, returning None if empty/unset."""
val = os.getenv(key, "").strip()
return int(val) if val else None
def main() -> None: def main() -> None:
@@ -116,7 +135,14 @@ def main() -> None:
tts = None tts = None
if args.audio: if args.audio:
from h2g2.engine.tts import TTS from h2g2.engine.tts import TTS
tts = TTS(args.voice) tts = TTS(
args.voice,
speaker_id=_env_int("VOICE_SPEAKER_ID"),
length_scale=_env_float("VOICE_LENGTH_SCALE"),
noise_scale=_env_float("VOICE_NOISE_SCALE"),
noise_w_scale=_env_float("VOICE_NOISE_W_SCALE"),
volume=_env_float("VOICE_VOLUME") or 1.0,
)
# Initialize STT if requested # Initialize STT if requested
stt = None stt = None
+1
View File
@@ -8,6 +8,7 @@ dependencies = [
"piper-tts>=1.2.0", "piper-tts>=1.2.0",
"vosk>=0.3.45", "vosk>=0.3.45",
"pyaudio>=0.2.14", "pyaudio>=0.2.14",
"python-dotenv>=1.0.0",
] ]
[build-system] [build-system]