Files
mission-control-v2/client/hermes-voice/audio_output.py
T
Hitonabi 8881d65c8d Fix: audio_output Hang + Stimme auf Seraphina
- speak() hing weil pygame.time.wait() die asyncio Event-Loop blockierte.
  Fix: time.sleep(0.05) statt pygame.time.wait() im Playback-Loop.
- asyncio.new_event_loop() statt asyncio.run() — thread-sicher, kein
  "event loop already running" in Worker-Threads.
- _speak_lock verhindert parallele TTS-Aufrufe.
- Stimme: de-DE-SeraphinaMultilingualNeural als Default (modern, weiblich,
  multilingual neural). Dropdown in Settings mit allen DE/EN Optionen.
- speak()-Signatur: voice + rate als Parameter (statt config-Import).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:25:53 +02:00

71 lines
2.0 KiB
Python

import asyncio
import os
import tempfile
import threading
import time
import pygame
import edge_tts
pygame.mixer.init()
_stop_event = threading.Event()
_speak_lock = threading.Lock()
def stop_speaking():
_stop_event.set()
pygame.mixer.music.stop()
def speak(text: str, voice: str = "de-DE-SeraphinaMultilingualNeural", rate: str = "+0%"):
"""Text → Edge TTS → Lautsprecher. Blockiert bis fertig oder unterbrochen."""
with _speak_lock:
_stop_event.clear()
tmp_path = None
try:
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
tmp_path = f.name
# Edge TTS in eigenem Event-Loop (Thread-sicher)
loop = asyncio.new_event_loop()
try:
communicate = edge_tts.Communicate(text, voice, rate=rate)
loop.run_until_complete(communicate.save(tmp_path))
finally:
loop.close()
if _stop_event.is_set():
return
pygame.mixer.music.load(tmp_path)
pygame.mixer.music.play()
# Polling ohne pygame.time.wait (blockiert Event-Loop nicht)
while pygame.mixer.music.get_busy():
if _stop_event.is_set():
pygame.mixer.music.stop()
break
time.sleep(0.05)
except Exception:
pass
finally:
if tmp_path and os.path.exists(tmp_path):
try:
os.unlink(tmp_path)
except OSError:
pass
def play_ding():
"""Kurzer Aktivierungs-Ton (440 Hz, 120ms)."""
import numpy as np
sample_rate = 44100
duration = 0.12
t = np.linspace(0, duration, int(sample_rate * duration), False)
wave = (np.sin(2 * np.pi * 440 * t) * 0.3 * 32767).astype(np.int16)
stereo = np.column_stack([wave, wave])
sound = pygame.sndarray.make_sound(stereo)
sound.play()
time.sleep(duration + 0.02)