use super::{init, state, synthesis};
use crate::{
audio::AudioBuffer,
config::PipelineConfig,
error::Result,
traits::{AcousticModel, G2p, Vocoder, VoiceManager},
types::{LanguageCode, SynthesisConfig, VoiceConfig},
VoirsError,
};
use std::sync::Arc;
use tokio::sync::RwLock;
use init::PipelineInitializer;
use state::{ComponentStates, PipelineState, PipelineStateManager};
use synthesis::SynthesisOrchestrator;
pub use state::{
ComponentState as PublicComponentState, ComponentStates as PublicComponentStates,
ComponentType as PublicComponentType, PipelineState as PublicPipelineState,
};
#[derive(Clone)]
pub struct VoirsPipeline {
orchestrator: SynthesisOrchestrator,
state_manager: PipelineStateManager,
config: Arc<RwLock<PipelineConfig>>,
current_voice: Arc<RwLock<Option<VoiceConfig>>>,
}
impl VoirsPipeline {
pub fn builder() -> super::VoirsPipelineBuilder {
super::VoirsPipelineBuilder::new()
}
pub fn new(
g2p: Arc<dyn G2p>,
acoustic: Arc<dyn AcousticModel>,
vocoder: Arc<dyn Vocoder>,
config: PipelineConfig,
) -> Self {
let orchestrator = SynthesisOrchestrator::new(g2p, acoustic, vocoder);
let state_manager = PipelineStateManager::new(config.clone());
Self {
orchestrator,
state_manager,
config: Arc::new(RwLock::new(config)),
current_voice: Arc::new(RwLock::new(None)),
}
}
pub fn with_test_mode(
g2p: Arc<dyn G2p>,
acoustic: Arc<dyn AcousticModel>,
vocoder: Arc<dyn Vocoder>,
config: PipelineConfig,
test_mode: bool,
) -> Self {
let orchestrator = SynthesisOrchestrator::with_test_mode(g2p, acoustic, vocoder, test_mode);
let state_manager = PipelineStateManager::with_test_mode(config.clone(), test_mode);
Self {
orchestrator,
state_manager,
config: Arc::new(RwLock::new(config)),
current_voice: Arc::new(RwLock::new(None)),
}
}
pub async fn from_builder(builder: super::VoirsPipelineBuilder) -> Result<Self> {
Self::from_builder_core(&builder).await
}
pub async fn from_builder_core(builder: &super::VoirsPipelineBuilder) -> Result<Self> {
let config = builder.get_config();
let test_mode = builder.get_test_mode();
let initializer = PipelineInitializer::new(config.clone());
let (g2p, acoustic, vocoder) = initializer.initialize_components().await?;
let pipeline = Self::with_test_mode(g2p, acoustic, vocoder, config, test_mode);
if let Some(voice_id) = builder.get_voice_id() {
pipeline.set_voice(&voice_id).await?;
}
pipeline
.state_manager
.set_state(PipelineState::Ready)
.await?;
Ok(pipeline)
}
pub async fn synthesize(&self, text: &str) -> Result<AudioBuffer> {
self.synthesize_with_config(text, &SynthesisConfig::default())
.await
}
pub async fn synthesize_with_config(
&self,
text: &str,
config: &SynthesisConfig,
) -> Result<AudioBuffer> {
if self.state_manager.get_state().await != PipelineState::Ready {
return Err(VoirsError::PipelineNotReady);
}
self.state_manager.set_state(PipelineState::Busy).await?;
let result = self.orchestrator.synthesize(text, config).await;
match result {
Ok(_) => {
self.state_manager.set_state(PipelineState::Ready).await?;
}
Err(_) => {
self.state_manager.set_state(PipelineState::Error).await?;
}
}
result
}
pub async fn synthesize_ssml(&self, ssml: &str) -> Result<AudioBuffer> {
let config = SynthesisConfig::default();
if self.state_manager.get_state().await != PipelineState::Ready {
return Err(VoirsError::PipelineNotReady);
}
self.state_manager.set_state(PipelineState::Busy).await?;
let result = self.orchestrator.synthesize_ssml(ssml, &config).await;
match result {
Ok(_) => {
self.state_manager.set_state(PipelineState::Ready).await?;
}
Err(_) => {
self.state_manager.set_state(PipelineState::Error).await?;
}
}
result
}
pub async fn synthesize_stream(
self: Arc<Self>,
text: &str,
) -> Result<impl futures::Stream<Item = Result<AudioBuffer>>> {
if self.state_manager.get_state().await != PipelineState::Ready {
return Err(VoirsError::PipelineNotReady);
}
let config = SynthesisConfig::default();
self.orchestrator.synthesize_stream(text, &config).await
}
pub async fn set_voice(&self, voice_id: &str) -> Result<()> {
let voice_config = VoiceConfig {
id: voice_id.to_string(),
name: voice_id.to_string(),
language: LanguageCode::EnUs, characteristics: Default::default(),
model_config: Default::default(),
metadata: Default::default(),
};
self.state_manager
.set_current_voice(Some(voice_config.clone()))
.await?;
let mut current = self.current_voice.write().await;
*current = Some(voice_config);
Ok(())
}
pub async fn current_voice(&self) -> Option<VoiceConfig> {
self.current_voice.read().await.clone()
}
pub async fn list_voices(&self) -> Result<Vec<VoiceConfig>> {
let registry = crate::voice::discovery::VoiceRegistry::new();
let mut voices: Vec<VoiceConfig> = registry.list_voices().into_iter().cloned().collect();
let config = self.config.read().await;
let cache_dir = config.effective_cache_dir();
let voice_manager = crate::voice::DefaultVoiceManager::new(&cache_dir);
for voice in &mut voices {
if voice_manager.is_voice_available(&voice.id) {
voice
.metadata
.insert("status".to_string(), "available".to_string());
voice
.metadata
.insert("location".to_string(), "local".to_string());
} else {
voice
.metadata
.insert("status".to_string(), "downloadable".to_string());
voice
.metadata
.insert("location".to_string(), "remote".to_string());
}
}
voices.sort_by(|a, b| a.language.cmp(&b.language).then(a.name.cmp(&b.name)));
tracing::debug!("Discovered {} voices", voices.len());
Ok(voices)
}
pub async fn get_state(&self) -> PipelineState {
self.state_manager.get_state().await
}
pub async fn get_config(&self) -> PipelineConfig {
self.config.read().await.clone()
}
pub async fn update_config(&self, new_config: PipelineConfig) -> Result<()> {
self.state_manager.update_config(new_config.clone()).await?;
let mut config = self.config.write().await;
*config = new_config;
Ok(())
}
pub async fn get_component_states(&self) -> ComponentStates {
self.state_manager.get_component_states().await
}
pub async fn synchronize_components(&self) -> Result<()> {
self.state_manager.synchronize_components().await
}
pub async fn cleanup(&self) -> Result<()> {
self.state_manager.cleanup().await
}
pub async fn set_ready(&self) -> Result<()> {
self.state_manager.set_state(PipelineState::Ready).await
}
}