rhotic / tts.py
IndianChess's picture
Deploy Rivet R Coach to Build Small org
b6ba91b verified
Raw
History Blame Contribute Delete
2.47 kB
"""Kyutai pocket-tts streaming TTS wrapper.
API verified against pocket-tts==2.1.0:
model = TTSModel.load_model() # ~6s, ~150MB
state = model.get_state_for_audio_prompt("alba") # voice prompt
for chunk in model.generate_audio_stream(state, text):
chunk: torch.float32 1D tensor, 1920 samples at 24kHz (80ms)
Voices: alba, marius, javert, jean, fantine, cosette, eponine, azelma.
"alba" is a warm female English voice.
"""
from __future__ import annotations
import os
from functools import lru_cache
from typing import Iterator
import numpy as np
DEFAULT_VOICE = os.environ.get("POCKET_TTS_VOICE", "alba")
@lru_cache(maxsize=1)
def _load_engine():
from pocket_tts import TTSModel
print("[tts] Loading pocket-tts...")
model = TTSModel.load_model()
print(f"[tts] Loaded. sample_rate={model.sample_rate}Hz")
return model
@lru_cache(maxsize=10)
def _load_state(voice_name: str):
return _load_engine().get_state_for_audio_prompt(voice_name)
def sample_rate() -> int:
return _load_engine().sample_rate
def _chunk_to_int16(chunk) -> np.ndarray:
audio = chunk.detach().cpu().numpy().astype(np.float32)
audio = np.clip(audio, -1.0, 1.0)
return (audio * 32767.0).astype(np.int16)
def synthesize_full(text: str, voice: str = DEFAULT_VOICE) -> tuple[int, np.ndarray]:
"""One-shot synthesis. Returns (sample_rate, int16 mono audio)."""
model = _load_engine()
state = _load_state(voice)
chunks = []
for chunk in model.generate_audio_stream(state, text):
chunks.append(_chunk_to_int16(chunk))
audio = (
np.concatenate(chunks)
if chunks
else np.zeros(0, dtype=np.int16)
)
return model.sample_rate, audio
def speak_stream(text: str, voice: str = DEFAULT_VOICE) -> Iterator[tuple[int, np.ndarray]]:
"""Yield (sample_rate, int16 mono) chunks as they decode (~80ms each)."""
model = _load_engine()
state = _load_state(voice)
sr = model.sample_rate
for chunk in model.generate_audio_stream(state, text):
yield (sr, _chunk_to_int16(chunk))
if __name__ == "__main__":
import time
text = "Hi! I am Wren. Let us try the word 'red' together."
t0 = time.time()
for i, (sr, chunk) in enumerate(speak_stream(text)):
if i == 0:
print(f"first chunk after {time.time()-t0:.2f}s, sr={sr}, samples={len(chunk)}")
print(f"total {time.time()-t0:.2f}s, {i+1} chunks")