feat(voice-bridge): /voice/ws Piper fallback
The /tts HTTP handler already chained cache -> F5 -> Piper Tower. The /voice/ws path was missing the Piper branch — on F5 timeout or load failure it sent an empty speak_end and the firmware got silence. Wire the same _piper_fallback() helper after the F5 path, mirror the wav-to-pcm conversion done on the cache hit branch. Best effort: any error in the fallback leaves pcm=None and the WS behaves as before. backend tag distinguishes 'f5' / 'cache' / 'piper_fallback' in the ws_tts_* log events. Note: Piper on Tower is EN-only today (per root CLAUDE.md), so a FR utterance falls back to EN pronunciation. Deploying a Piper FR model is the real follow-up; this just plugs the silence.
This commit is contained in:
@@ -1628,6 +1628,25 @@ async def voice_ws(ws: WebSocket) -> None:
|
||||
_jlog("ws_tts_f5_not_loaded", request_id=request_id,
|
||||
load_err=_F5_LOAD_ERR)
|
||||
|
||||
# 5c. Piper fallback on F5 failure — mirrors the /tts handler.
|
||||
# Same wav-to-pcm conversion; we resample on output only if the
|
||||
# Piper sample-rate diverges from the WS contract (24 kHz). The
|
||||
# fallback is intentionally best-effort: any error here just
|
||||
# leaves pcm=None and the WS sends an empty speak_end below.
|
||||
if pcm is None and speak_text:
|
||||
wav_fallback = await _piper_fallback(speak_text)
|
||||
if wav_fallback is not None:
|
||||
try:
|
||||
pcm = _wav_to_pcm16(wav_fallback)
|
||||
tts_backend_used = "piper_fallback"
|
||||
tts_err = None
|
||||
_jlog("ws_tts_piper_fallback",
|
||||
request_id=request_id, bytes=len(pcm))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_jlog("ws_tts_piper_decode_err",
|
||||
request_id=request_id,
|
||||
err=type(exc).__name__, msg=str(exc))
|
||||
|
||||
tts_stage_latency_ms = int(
|
||||
(time.monotonic() - tts_started) * 1000
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user