From a55757dc808e0fab241d1b0c8e7f04c027b7757e Mon Sep 17 00:00:00 2001 From: Liam Pettigrew Date: Thu, 5 Feb 2026 14:43:17 +1100 Subject: [PATCH] warmup and queue buffer thread for qwen3 tts stream --- .gitignore | 1 + core/tts.py | 80 +++++++++++++++++++++++++++++++++++--------- data/voices/cori.txt | 2 +- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index b381f99..efee886 100755 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ Thumbs.db # ============================================================================= 0ld/ dev/ +data/voices/private # ============================================================================= # Keep Example Configs (negation patterns) diff --git a/core/tts.py b/core/tts.py index 7b952ee..e2a865a 100644 --- a/core/tts.py +++ b/core/tts.py @@ -4,7 +4,9 @@ Text-to-Speech module using Optimised Qwen3 TTS with two-phase latency & Hann cr Handles loading and running the Kokoro text-to-speech model. """ import logging +import queue import re +import threading import torch import sounddevice as sd from qwen_tts import Qwen3TTSModel @@ -66,6 +68,23 @@ prompt = model.create_voice_clone_prompt( ref_text=ref_text, ) +# Warmup: run dummy generation to initialize torch.compile and CUDA graphs +logger.info("Warming up TTS model...") +for _ in model.stream_generate_voice_clone( + # Reference text must be longer to properly initialise model + text="A rainbow is a meteorological phenomenon that is caused by reflection, refraction and dispersion of light in water droplets resulting in a spectrum of light appearing in the sky.", + language="english", + voice_clone_prompt=prompt, + overlap_samples=512, + emit_every_frames=12, + decode_window_frames=80, + first_chunk_emit_every=5, + first_chunk_decode_window=48, + first_chunk_frames=48, +): + pass # Discard output +logger.info("TTS model ready") + def speak_stream(text: str, voice: str = "cori", speed: float = 1.0): """ Generate speech from text using stream optimised Qwen3 TTS from rekuenkdr @@ -75,19 +94,48 @@ def speak_stream(text: str, voice: str = "cori", speed: float = 1.0): voice: Not used speed: Not used """ - # Stream audio with two-phase settings - for audio_chunk, sample_rate in model.stream_generate_voice_clone( - text=text, - language="english", - voice_clone_prompt=prompt, - overlap_samples=512, - # Phase 2 settings (stable) - emit_every_frames=12, - decode_window_frames=80, - # Phase 1 settings (fast first chunk) - first_chunk_emit_every=5, - first_chunk_decode_window=48, - first_chunk_frames=48, - ): - - sd.play(audio_chunk, samplerate=sample_rate, blocking=True) \ No newline at end of file + audio_queue = queue.Queue(maxsize=5) # Buffer chunks ahead + sample_rate_holder = [None] # To capture sample rate from producer + + def producer(): + """Generate audio chunks into queue.""" + try: + for audio_chunk, sample_rate in model.stream_generate_voice_clone( + text=text, + language="english", + voice_clone_prompt=prompt, + overlap_samples=512, + # Phase 2 settings (stable) + emit_every_frames=12, + decode_window_frames=80, + # Phase 1 settings (fast first chunk) + first_chunk_emit_every=5, + first_chunk_decode_window=48, + first_chunk_frames=48, + ): + if sample_rate_holder[0] is None: + sample_rate_holder[0] = sample_rate + audio_queue.put(audio_chunk) + except Exception as e: + logger.exception(f"TTS generation error for text '{text[:50]}': {type(e).__name__}: {e}") + finally: + audio_queue.put(None) # Sentinel to signal completion + + # Start generation in background thread + gen_thread = threading.Thread(target=producer, daemon=True) + gen_thread.start() + + # Wait for first chunk to get sample rate + first_chunk = audio_queue.get() + if first_chunk is None: + return + + # Use a single continuous output stream + with sd.OutputStream( + samplerate=sample_rate_holder[0], + channels=1, + dtype="float32", + ) as stream: + stream.write(first_chunk) + while (chunk := audio_queue.get()) is not None: + stream.write(chunk) \ No newline at end of file diff --git a/data/voices/cori.txt b/data/voices/cori.txt index 4f46634..cb55fdf 100644 --- a/data/voices/cori.txt +++ b/data/voices/cori.txt @@ -1 +1 @@ - A rainbow is a meteorological phenomenon that is caused by reflection, refraction and dispersion of light in water droplets resulting in a spectrum of light appearing in the sky. +A rainbow is a meteorological phenomenon that is caused by reflection, refraction and dispersion of light in water droplets resulting in a spectrum of light appearing in the sky. \ No newline at end of file