use crate::audio::playback::{AudioData, AudioPlayer, PlaybackConfig};
use crate::error::{Result, VoirsCliError};
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct SynthesisEngine {
pipeline: Option<Arc<RwLock<voirs_sdk::VoirsPipeline>>>,
audio_player: AudioPlayer,
current_speed: f32,
current_pitch: f32,
current_volume: f32,
available_voices: Vec<String>,
current_voice: Option<String>,
}
impl SynthesisEngine {
pub async fn new() -> Result<Self> {
let config = PlaybackConfig::default();
let audio_player = AudioPlayer::new(config).map_err(|e| {
VoirsCliError::AudioError(format!("Failed to initialize audio player: {}", e))
})?;
let available_voices = Self::load_available_voices().await?;
Ok(Self {
pipeline: None,
audio_player,
current_speed: 1.0,
current_pitch: 0.0,
current_volume: 1.0,
available_voices,
current_voice: None,
})
}
async fn load_available_voices() -> Result<Vec<String>> {
Ok(vec![
"en-us-female-01".to_string(),
"en-us-male-01".to_string(),
"en-gb-female-01".to_string(),
"ja-jp-female-01".to_string(),
])
}
pub async fn list_voices(&self) -> Result<Vec<String>> {
Ok(self.available_voices.clone())
}
pub async fn set_voice(&mut self, voice: &str) -> Result<()> {
if !self.available_voices.contains(&voice.to_string()) {
return Err(VoirsCliError::VoiceError(format!(
"Voice '{}' not found. Available voices: {}",
voice,
self.available_voices.join(", ")
)));
}
if self.pipeline.is_none() {
self.pipeline = Some(Arc::new(RwLock::new(self.create_pipeline(voice).await?)));
} else {
if let Some(ref pipeline) = self.pipeline {
let mut pipeline_guard = pipeline.write().await;
pipeline_guard.set_voice(voice).await.map_err(|e| {
VoirsCliError::SynthesisError(format!("Failed to set voice: {}", e))
})?;
}
}
self.current_voice = Some(voice.to_string());
println!("✓ Voice set to: {}", voice);
Ok(())
}
async fn create_pipeline(&self, voice: &str) -> Result<voirs_sdk::VoirsPipeline> {
let pipeline = voirs_sdk::VoirsPipeline::builder()
.with_quality(voirs_sdk::QualityLevel::High)
.with_voice(voice)
.build()
.await
.map_err(|e| {
VoirsCliError::SynthesisError(format!(
"Failed to create VoiRS pipeline for voice '{}': {}",
voice, e
))
})?;
Ok(pipeline)
}
pub async fn synthesize(&self, text: &str) -> Result<Vec<f32>> {
if self.pipeline.is_none() {
return Err(VoirsCliError::SynthesisError(
"No voice selected. Use ':voice <voice_name>' to set a voice.".to_string(),
));
}
if let Some(pipeline) = &self.pipeline {
let mut config = voirs_sdk::types::SynthesisConfig::default();
config.speaking_rate = self.current_speed;
config.pitch_shift = self.current_pitch;
config.volume_gain = self.current_volume;
let pipeline_guard = pipeline.read().await;
match pipeline_guard.synthesize_with_config(text, &config).await {
Ok(audio_buffer) => {
Ok(audio_buffer.samples().to_vec())
}
Err(e) => {
tracing::warn!("Synthesis failed, falling back to placeholder: {}", e);
let sample_rate = 22050;
let duration_ms = text.len() as f32 * 50.0; let num_samples = (sample_rate as f32 * duration_ms / 1000.0) as usize;
let frequency = 440.0; let mut samples = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / sample_rate as f32;
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin()
* 0.1
* self.current_volume;
samples.push(sample);
}
tokio::time::sleep(tokio::time::Duration::from_millis(
(text.len() as u64 * 10).min(500),
))
.await;
Ok(samples)
}
}
} else {
Err(VoirsCliError::SynthesisError(
"No pipeline available for synthesis".to_string(),
))
}
}
pub async fn play_audio(&mut self, audio_data: &[f32]) -> Result<()> {
let samples_i16: Vec<i16> = audio_data
.iter()
.map(|&sample| (sample * i16::MAX as f32) as i16)
.collect();
let audio_data = AudioData {
samples: samples_i16,
sample_rate: 22050,
channels: 1,
};
self.audio_player
.play(&audio_data)
.await
.map_err(|e| VoirsCliError::AudioError(format!("Failed to play audio: {}", e)))?;
Ok(())
}
pub async fn set_speed(&mut self, speed: f32) -> Result<()> {
self.current_speed = speed.clamp(0.1, 3.0);
if let Some(ref pipeline) = self.pipeline {
println!("✓ Speed set to: {:.1}x", self.current_speed);
}
Ok(())
}
pub async fn set_pitch(&mut self, pitch: f32) -> Result<()> {
self.current_pitch = pitch.clamp(-12.0, 12.0);
if let Some(ref pipeline) = self.pipeline {
println!("✓ Pitch set to: {:.1} semitones", self.current_pitch);
}
Ok(())
}
pub async fn set_volume(&mut self, volume: f32) -> Result<()> {
self.current_volume = volume.clamp(0.0, 2.0);
self.audio_player
.set_volume(self.current_volume)
.map_err(|e| VoirsCliError::AudioError(format!("Failed to set volume: {}", e)))?;
println!("✓ Volume set to: {:.1}", self.current_volume);
Ok(())
}
pub fn current_params(&self) -> (f32, f32, f32) {
(self.current_speed, self.current_pitch, self.current_volume)
}
pub fn current_voice(&self) -> Option<&str> {
self.current_voice.as_deref()
}
pub fn is_ready(&self) -> bool {
self.pipeline.is_some()
}
}