Files
InkFlow/backend/inkflow/tts/base.py

49 lines
1.5 KiB
Python

"""Abstraction des moteurs TTS (backend pluggable).
Deux implementations : Kokoro (rapide, voix preglees -> previews) et Qwen3-TTS
(qualite + clonage par audio de reference -> rendu final). Toutes deux renvoient
de l'audio mono float32 + une frequence d'echantillonnage.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
import numpy as np
@dataclass
class VoiceSpec:
"""Decrit la voix a utiliser pour une synthese.
- `preset` : nom d'une voix preglee (Kokoro: "ff_siwis" ; Qwen3: "Chelsie").
- `ref_audio` / `ref_text` : clip de reference pour le clonage (Qwen3).
"""
preset: Optional[str] = None
ref_audio: Optional[str] = None
ref_text: Optional[str] = None
speed: float = 1.0
class TTSBackend(ABC):
"""Interface commune a tous les moteurs TTS."""
name: str = "base"
@abstractmethod
def synthesize(self, text: str, voice: VoiceSpec) -> tuple[np.ndarray, int]:
"""Synthetise `text` et renvoie (audio mono float32, sample_rate)."""
def default_voice(self) -> VoiceSpec:
return VoiceSpec()
def to_mono_float32(audio) -> np.ndarray:
"""Normalise une sortie de modele (mx.array / np / list) en mono float32."""
arr = np.asarray(audio, dtype=np.float32)
if arr.ndim > 1:
# (channels, n) ou (n, channels) -> moyenne sur l'axe des canaux.
arr = arr.mean(axis=0) if arr.shape[0] < arr.shape[-1] else arr.mean(axis=-1)
return np.ascontiguousarray(arr.reshape(-1))