use crate::VoirsError;
use std::sync::Arc;
use tokio::sync::RwLock;
pub use voirs_emotion::prelude::*;
#[derive(Debug, Clone)]
pub struct EmotionController {
processor: Arc<voirs_emotion::EmotionProcessor>,
config: Arc<RwLock<EmotionControllerConfig>>,
}
#[derive(Debug, Clone)]
pub struct EmotionControllerConfig {
pub enabled: bool,
pub default_intensity: f32,
pub auto_sentiment_detection: bool,
pub cache_emotions: bool,
}
impl EmotionController {
pub async fn new() -> crate::Result<Self> {
let processor = voirs_emotion::EmotionProcessor::new()
.map_err(|e| VoirsError::model_error(format!("Emotion processor: {}", e)))?;
Ok(Self {
processor: Arc::new(processor),
config: Arc::new(RwLock::new(EmotionControllerConfig::default())),
})
}
pub async fn with_config(emotion_config: EmotionConfig) -> crate::Result<Self> {
let processor = voirs_emotion::EmotionProcessor::with_config(emotion_config)
.map_err(|e| VoirsError::model_error(format!("Emotion processor: {}", e)))?;
Ok(Self {
processor: Arc::new(processor),
config: Arc::new(RwLock::new(EmotionControllerConfig::default())),
})
}
pub async fn set_emotion(&self, emotion: Emotion, intensity: Option<f32>) -> crate::Result<()> {
let config = self.config.read().await;
if !config.enabled {
return Ok(());
}
let intensity = intensity.unwrap_or(config.default_intensity);
self.processor
.set_emotion(emotion, Some(intensity))
.await
.map_err(|e| VoirsError::audio_error(format!("Set emotion: {}", e)))
}
pub async fn set_emotion_mix(
&self,
emotions: std::collections::HashMap<Emotion, f32>,
) -> crate::Result<()> {
let config = self.config.read().await;
if !config.enabled {
return Ok(());
}
self.processor
.set_emotion_mix(emotions)
.await
.map_err(|e| VoirsError::audio_error(format!("Set emotion mix: {}", e)))
}
pub async fn apply_preset(
&self,
preset_name: &str,
intensity: Option<f32>,
) -> crate::Result<()> {
let config = self.config.read().await;
if !config.enabled {
return Ok(());
}
let library = EmotionPresetLibrary::with_defaults();
let params = library
.get_preset_parameters(preset_name, intensity)
.ok_or_else(|| VoirsError::ConfigError {
field: "preset".to_string(),
message: format!("Emotion preset: {}", preset_name),
})?;
self.processor
.apply_emotion_parameters(params)
.await
.map_err(|e| VoirsError::audio_error(format!("Apply preset: {}", e)))
}
pub async fn get_current_parameters(&self) -> crate::Result<EmotionParameters> {
Ok(self.processor.get_current_parameters().await)
}
pub async fn reset_to_neutral(&self) -> crate::Result<()> {
self.processor
.reset_to_neutral()
.await
.map_err(|e| VoirsError::audio_error(format!("Reset emotion: {}", e)))
}
pub async fn update(&self, delta_time_ms: f64) -> crate::Result<()> {
self.processor
.update_transition(delta_time_ms)
.await
.map_err(|e| VoirsError::audio_error(format!("Update emotion: {}", e)))
}
pub async fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
let mut config = self.config.write().await;
config.enabled = enabled;
Ok(())
}
pub async fn is_enabled(&self) -> bool {
let config = self.config.read().await;
config.enabled
}
pub fn list_presets(&self) -> Vec<String> {
let library = EmotionPresetLibrary::with_defaults();
library.list_presets()
}
pub fn get_presets_by_category(&self, category: &str) -> Vec<String> {
let library = EmotionPresetLibrary::with_defaults();
library
.get_category(category)
.iter()
.map(|preset| preset.name.clone())
.collect()
}
pub async fn auto_detect_emotion(&self, text: &str) -> crate::Result<Option<(Emotion, f32)>> {
let config = self.config.read().await;
if !config.auto_sentiment_detection {
return Ok(None);
}
let emotion = if text.contains('!') {
Some((Emotion::Excited, 0.7))
} else if text.contains('?') {
Some((Emotion::Neutral, 0.5))
} else if text.to_lowercase().contains("sad") || text.to_lowercase().contains("sorry") {
Some((Emotion::Sad, 0.6))
} else if text.to_lowercase().contains("happy") || text.to_lowercase().contains("joy") {
Some((Emotion::Happy, 0.7))
} else {
None
};
Ok(emotion)
}
pub async fn get_statistics(&self) -> crate::Result<EmotionStatistics> {
let history = self.processor.get_history().await;
Ok(EmotionStatistics {
total_emotions_applied: history.len(),
current_emotion: self
.processor
.get_current_parameters()
.await
.emotion_vector
.dominant_emotion(),
processing_enabled: self.is_enabled().await,
})
}
}
impl Default for EmotionControllerConfig {
fn default() -> Self {
Self {
enabled: true,
default_intensity: 0.7,
auto_sentiment_detection: false,
cache_emotions: true,
}
}
}
#[derive(Debug, Clone)]
pub struct EmotionStatistics {
pub total_emotions_applied: usize,
pub current_emotion: Option<(Emotion, EmotionIntensity)>,
pub processing_enabled: bool,
}
#[derive(Debug, Clone)]
pub struct EmotionControllerBuilder {
config: EmotionControllerConfig,
emotion_config: Option<EmotionConfig>,
}
impl EmotionControllerBuilder {
pub fn new() -> Self {
Self {
config: EmotionControllerConfig::default(),
emotion_config: None,
}
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.config.enabled = enabled;
self
}
pub fn default_intensity(mut self, intensity: f32) -> Self {
self.config.default_intensity = intensity.clamp(0.0, 1.0);
self
}
pub fn auto_sentiment_detection(mut self, enabled: bool) -> Self {
self.config.auto_sentiment_detection = enabled;
self
}
pub fn emotion_config(mut self, config: EmotionConfig) -> Self {
self.emotion_config = Some(config);
self
}
pub async fn build(self) -> crate::Result<EmotionController> {
let controller = if let Some(emotion_config) = self.emotion_config {
EmotionController::with_config(emotion_config).await?
} else {
EmotionController::new().await?
};
{
let mut config = controller.config.write().await;
*config = self.config;
}
Ok(controller)
}
}
impl Default for EmotionControllerBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_emotion_controller_creation() {
let controller = EmotionController::new().await.unwrap();
assert!(controller.is_enabled().await);
}
#[tokio::test]
async fn test_emotion_setting() {
let emotion_config = EmotionConfig::builder()
.transition_smoothing(1.0)
.build()
.unwrap();
let controller = EmotionController::with_config(emotion_config)
.await
.unwrap();
controller
.set_emotion(Emotion::Happy, Some(0.8))
.await
.unwrap();
let params = controller.get_current_parameters().await.unwrap();
let dominant = params.emotion_vector.dominant_emotion();
assert!(dominant.is_some());
}
#[tokio::test]
async fn test_preset_application() {
let emotion_config = EmotionConfig::builder()
.transition_smoothing(1.0)
.build()
.unwrap();
let controller = EmotionController::with_config(emotion_config)
.await
.unwrap();
controller.apply_preset("happy", Some(0.7)).await.unwrap();
let params = controller.get_current_parameters().await.unwrap();
assert!(!params.emotion_vector.emotions.is_empty());
}
#[tokio::test]
async fn test_emotion_builder() {
let controller = EmotionControllerBuilder::new()
.enabled(true)
.default_intensity(0.8)
.auto_sentiment_detection(true)
.build()
.await
.unwrap();
assert!(controller.is_enabled().await);
}
#[tokio::test]
async fn test_auto_emotion_detection() {
let controller = EmotionControllerBuilder::new()
.auto_sentiment_detection(true)
.build()
.await
.unwrap();
let emotion = controller
.auto_detect_emotion("I'm so happy!")
.await
.unwrap();
assert!(emotion.is_some());
let emotion = controller
.auto_detect_emotion("This is neutral text.")
.await
.unwrap();
}
#[tokio::test]
async fn test_preset_listing() {
let controller = EmotionController::new().await.unwrap();
let presets = controller.list_presets();
assert!(!presets.is_empty());
let positive_presets = controller.get_presets_by_category("positive");
assert!(!positive_presets.is_empty());
}
}