pub mod misaki_g2p;
pub mod mock;
pub mod onnx;
pub mod phonemes;
pub use mock::MockTts;
pub use onnx::OnnxTts;
use crate::config::Config;
use crate::error::Result;
pub const TTS_SAMPLE_RATE: u32 = 24_000;
#[derive(Debug, Clone)]
pub struct TtsClip {
pub samples: Vec<f32>,
pub sample_rate: u32,
}
pub trait TtsEngine: Send {
fn synthesize(&mut self, text: &str) -> Result<TtsClip>;
}
pub(crate) fn concat_clip_samples(clips: &[TtsClip]) -> Vec<f32> {
let total: usize = clips.iter().map(|c| c.samples.len()).sum();
let mut pcm = Vec::with_capacity(total);
for clip in clips {
pcm.extend_from_slice(&clip.samples);
}
pcm
}
pub fn build_engine(cfg: &Config) -> Result<Box<dyn TtsEngine>> {
if !cfg.mock_tts {
match (&cfg.tts_model, &cfg.tts_voices) {
(Some(model), Some(voices)) if model.exists() && voices.exists() => {
match OnnxTts::load(model, voices, &cfg.tts_voice, cfg.tts_speed) {
Ok(engine) => return Ok(Box::new(engine)),
Err(e) => {
tracing::warn!(
error = %e,
"failed to load Kokoro TTS; falling back to sine-wave MockTts"
);
}
}
}
(model, voices) => {
tracing::warn!(
tts_model = ?model,
tts_voices = ?voices,
"Kokoro TTS files absent or incomplete; falling back to sine-wave \
MockTts (run scripts/download_models.sh --with-kokoro)"
);
}
}
}
Ok(Box::new(MockTts::new()))
}