use crate::traits::{EvaluationResult, QualityMetric, QualityScore};
use crate::EvaluationError;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use voirs_sdk::{AudioBuffer, LanguageCode};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageSpecificConfig {
pub primary_language: LanguageCode,
pub secondary_languages: Vec<LanguageCode>,
pub phonemic_adaptation: bool,
pub prosody_evaluation: bool,
pub cultural_preferences: bool,
pub accent_awareness: bool,
pub code_switching_threshold: f32,
pub language_confidence_threshold: f32,
}
impl Default for LanguageSpecificConfig {
fn default() -> Self {
Self {
primary_language: LanguageCode::EnUs,
secondary_languages: vec![LanguageCode::EsEs, LanguageCode::FrFr],
phonemic_adaptation: true,
prosody_evaluation: true,
cultural_preferences: true,
accent_awareness: true,
code_switching_threshold: 0.3,
language_confidence_threshold: 0.7,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageSpecificResult {
pub primary_score: QualityScore,
pub language_segments: Vec<LanguageSegment>,
pub phonemic_results: PhonemicEvaluationResult,
pub prosody_results: ProsodyEvaluationResult,
pub cultural_scores: CulturalPreferenceResult,
pub accent_results: AccentEvaluationResult,
pub code_switching: CodeSwitchingResult,
pub overall_language_score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageSegment {
pub language: LanguageCode,
pub start_time: f32,
pub end_time: f32,
pub confidence: f32,
pub quality_score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhonemicEvaluationResult {
pub phoneme_coverage: f32,
pub phoneme_accuracy: HashMap<String, f32>,
pub allophone_variation: f32,
pub phonotactic_compliance: f32,
pub sound_changes: Vec<SoundChangeAnalysis>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SoundChangeAnalysis {
pub rule: String,
pub frequency: f32,
pub accuracy: f32,
pub context: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProsodyEvaluationResult {
pub stress_patterns: f32,
pub intonation_contours: f32,
pub rhythm_timing: f32,
pub prosodic_features: HashMap<String, f32>,
pub sentence_prosody: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CulturalPreferenceResult {
pub accent_preferences: HashMap<String, f32>,
pub rate_preferences: f32,
pub formality_appropriateness: f32,
pub cultural_patterns: HashMap<String, f32>,
pub social_context: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccentEvaluationResult {
pub accent_type: String,
pub accent_strength: f32,
pub accent_consistency: f32,
pub native_likeness: f32,
pub regional_features: HashMap<String, f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeSwitchingResult {
pub switch_count: usize,
pub switch_points: Vec<LanguageSwitch>,
pub switching_naturalness: f32,
pub matrix_language: Option<LanguageCode>,
pub embedded_languages: Vec<LanguageCode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageSwitch {
pub time: f32,
pub from_language: LanguageCode,
pub to_language: LanguageCode,
pub switch_type: SwitchType,
pub naturalness: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SwitchType {
Intrasentential,
Intersentential,
Tag,
Emblematic,
}
pub struct LanguageSpecificEvaluator {
config: LanguageSpecificConfig,
phoneme_inventories: HashMap<LanguageCode, Vec<String>>,
prosody_models: HashMap<LanguageCode, ProsodyModel>,
cultural_models: HashMap<LanguageCode, CulturalModel>,
accent_models: HashMap<LanguageCode, AccentModel>,
}
#[derive(Debug, Clone)]
pub struct ProsodyModel {
pub stress_patterns: Vec<String>,
pub intonation_templates: Vec<Vec<f32>>,
pub rhythm_characteristics: RhythmCharacteristics,
pub prosodic_features: HashMap<String, f32>,
}
#[derive(Debug, Clone)]
pub struct RhythmCharacteristics {
pub timing_type: TimingType,
pub typical_rate: f32,
pub pause_patterns: Vec<f32>,
pub syllable_complexity: f32,
}
#[derive(Debug, Clone)]
pub enum TimingType {
StressTimed,
SyllableTimed,
MoraTimed,
Mixed,
}
#[derive(Debug, Clone)]
pub struct CulturalModel {
pub accent_preferences: HashMap<String, f32>,
pub rate_preferences: HashMap<String, f32>,
pub formality_markers: Vec<String>,
pub speech_patterns: HashMap<String, Vec<f32>>,
}
#[derive(Debug, Clone)]
pub struct AccentModel {
pub standard_features: Vec<AccentFeature>,
pub regional_variations: HashMap<String, Vec<AccentFeature>>,
pub strength_indicators: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct AccentFeature {
pub name: String,
pub feature_type: AccentFeatureType,
pub value: f32,
pub weight: f32,
}
#[derive(Debug, Clone)]
pub enum AccentFeatureType {
Phonemic,
Prosodic,
Articulatory,
Timing,
}
impl LanguageSpecificEvaluator {
pub fn new(config: LanguageSpecificConfig) -> Self {
let mut evaluator = Self {
config,
phoneme_inventories: HashMap::new(),
prosody_models: HashMap::new(),
cultural_models: HashMap::new(),
accent_models: HashMap::new(),
};
evaluator.initialize_language_models();
evaluator
}
fn initialize_language_models(&mut self) {
self.initialize_phoneme_inventories();
self.initialize_prosody_models();
self.initialize_cultural_models();
self.initialize_accent_models();
}
fn initialize_phoneme_inventories(&mut self) {
let english_phonemes = vec![
"i", "ɪ", "e", "ɛ", "æ", "ɑ", "ɔ", "o", "ʊ", "u", "ʌ", "ə", "ɝ", "ɚ",
"aɪ", "aʊ", "ɔɪ", "eɪ", "oʊ", "p", "b", "t", "d", "k", "g", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "h", "m", "n",
"ŋ", "l", "r", "w", "j", "tʃ", "dʒ",
]
.into_iter()
.map(String::from)
.collect();
let spanish_phonemes = vec![
"a", "e", "i", "o", "u", "p", "b", "β", "t", "d", "ð", "k", "g", "ɣ", "f", "θ", "s", "x", "tʃ", "m", "n", "ɲ",
"ŋ", "l", "ʎ", "r", "rr", "w", "j",
]
.into_iter()
.map(String::from)
.collect();
let french_phonemes = vec![
"i", "e", "ɛ", "a", "ɑ", "ɔ", "o", "u", "y", "ø", "œ", "ə", "ɛ̃", "ɑ̃", "ɔ̃", "œ̃",
"p", "b", "t", "d", "k", "g", "f", "v", "s", "z", "ʃ", "ʒ", "m", "n", "ɲ", "ŋ", "l",
"r", "ʁ", "w", "ɥ", "j",
]
.into_iter()
.map(String::from)
.collect();
let german_phonemes = vec![
"i", "ɪ", "e", "ɛ", "a", "ɑ", "ɔ", "o", "u", "ʊ", "y", "ʏ", "ø", "œ", "ə",
"aɪ", "aʊ", "ɔɪ", "p", "b", "t", "d", "k", "g", "f", "v", "s", "z", "ʃ", "ʒ", "ç", "x", "h", "m", "n",
"ŋ", "l", "r", "ʁ", "w", "j", "pf", "ts", "tʃ", "dʒ",
]
.into_iter()
.map(String::from)
.collect();
let japanese_phonemes = vec![
"a",
"i",
"u",
"e",
"o",
"k",
"g",
"s",
"z",
"t",
"d",
"n",
"h",
"b",
"p",
"m",
"y",
"r",
"w",
"ʔ",
"N",
"Q",
"palatalized",
]
.into_iter()
.map(String::from)
.collect();
let chinese_phonemes = vec![
"a", "o", "e", "i", "u", "ü", "b", "p", "m", "f", "d", "t", "n", "l", "g", "k", "h", "j", "q", "x", "z", "c", "s",
"zh", "ch", "sh", "r", "w", "y",
]
.into_iter()
.map(String::from)
.collect();
self.phoneme_inventories
.insert(LanguageCode::EnUs, english_phonemes);
self.phoneme_inventories
.insert(LanguageCode::EsEs, spanish_phonemes);
self.phoneme_inventories
.insert(LanguageCode::FrFr, french_phonemes);
self.phoneme_inventories
.insert(LanguageCode::DeDe, german_phonemes);
self.phoneme_inventories
.insert(LanguageCode::JaJp, japanese_phonemes);
self.phoneme_inventories
.insert(LanguageCode::ZhCn, chinese_phonemes);
}
fn initialize_prosody_models(&mut self) {
let english_prosody = ProsodyModel {
stress_patterns: vec![
"S-u".to_string(), "S-u-u".to_string(), "u-S".to_string(), ],
intonation_templates: vec![
vec![1.0, 0.8, 0.6, 0.4], vec![0.4, 0.6, 0.8, 1.0], vec![0.5, 1.0, 0.3, 0.7], ],
rhythm_characteristics: RhythmCharacteristics {
timing_type: TimingType::StressTimed,
typical_rate: 4.5, pause_patterns: vec![0.2, 0.5, 1.0], syllable_complexity: 0.7, },
prosodic_features: [
("stress_prominence".to_string(), 0.8),
("pitch_range".to_string(), 0.7),
("timing_variation".to_string(), 0.8),
]
.iter()
.cloned()
.collect(),
};
let spanish_prosody = ProsodyModel {
stress_patterns: vec![
"u-S".to_string(), "u-u-S".to_string(), "S".to_string(), ],
intonation_templates: vec![
vec![0.6, 0.8, 0.4, 0.2], vec![0.4, 0.5, 0.9, 1.0], ],
rhythm_characteristics: RhythmCharacteristics {
timing_type: TimingType::SyllableTimed,
typical_rate: 5.2, pause_patterns: vec![0.15, 0.4, 0.8],
syllable_complexity: 0.4, },
prosodic_features: [
("stress_prominence".to_string(), 0.6),
("pitch_range".to_string(), 0.8),
("timing_variation".to_string(), 0.3),
]
.iter()
.cloned()
.collect(),
};
let french_prosody = ProsodyModel {
stress_patterns: vec![
"u-u-S".to_string(), "u-S".to_string(), ],
intonation_templates: vec![
vec![0.5, 0.6, 0.8, 0.4], vec![0.4, 0.7, 1.0, 0.6], ],
rhythm_characteristics: RhythmCharacteristics {
timing_type: TimingType::SyllableTimed,
typical_rate: 4.8,
pause_patterns: vec![0.18, 0.45, 0.9],
syllable_complexity: 0.5,
},
prosodic_features: [
("stress_prominence".to_string(), 0.4),
("pitch_range".to_string(), 0.6),
("timing_variation".to_string(), 0.2),
]
.iter()
.cloned()
.collect(),
};
self.prosody_models
.insert(LanguageCode::EnUs, english_prosody);
self.prosody_models
.insert(LanguageCode::EsEs, spanish_prosody);
self.prosody_models
.insert(LanguageCode::FrFr, french_prosody);
}
fn initialize_cultural_models(&mut self) {
let english_cultural = CulturalModel {
accent_preferences: [
("general_american".to_string(), 0.8),
("southern_american".to_string(), 0.6),
("new_york".to_string(), 0.7),
("california".to_string(), 0.7),
]
.iter()
.cloned()
.collect(),
rate_preferences: [
("formal".to_string(), 0.6),
("informal".to_string(), 0.8),
("presentation".to_string(), 0.5),
]
.iter()
.cloned()
.collect(),
formality_markers: vec![
"clear_articulation".to_string(),
"full_vowels".to_string(),
"precise_consonants".to_string(),
],
speech_patterns: [
("uptalk".to_string(), vec![0.2, 0.4, 0.6, 0.8]),
("vocal_fry".to_string(), vec![0.1, 0.1, 0.1, 0.2]),
]
.iter()
.cloned()
.collect(),
};
let spanish_cultural = CulturalModel {
accent_preferences: [
("castilian".to_string(), 0.8),
("latin_american".to_string(), 0.9),
("argentinian".to_string(), 0.7),
]
.iter()
.cloned()
.collect(),
rate_preferences: [("formal".to_string(), 0.7), ("informal".to_string(), 0.9)]
.iter()
.cloned()
.collect(),
formality_markers: vec![
"theta_pronunciation".to_string(),
"formal_pronouns".to_string(),
],
speech_patterns: HashMap::new(),
};
self.cultural_models
.insert(LanguageCode::EnUs, english_cultural);
self.cultural_models
.insert(LanguageCode::EsEs, spanish_cultural);
}
fn initialize_accent_models(&mut self) {
let english_accent = AccentModel {
standard_features: vec![
AccentFeature {
name: "rhoticity".to_string(),
feature_type: AccentFeatureType::Phonemic,
value: 1.0, weight: 0.8,
},
AccentFeature {
name: "trap_bath_split".to_string(),
feature_type: AccentFeatureType::Phonemic,
value: 1.0, weight: 0.6,
},
AccentFeature {
name: "stress_timing".to_string(),
feature_type: AccentFeatureType::Prosodic,
value: 1.0,
weight: 0.9,
},
],
regional_variations: HashMap::new(),
strength_indicators: vec![
"vowel_shifts".to_string(),
"consonant_substitutions".to_string(),
"intonation_patterns".to_string(),
],
};
self.accent_models
.insert(LanguageCode::EnUs, english_accent);
}
pub async fn evaluate_language_specific(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<LanguageSpecificResult, EvaluationError> {
let language_segments = self.detect_language_segments(audio).await?;
let phonemic_results = self
.evaluate_phonemic_adaptation(audio, &language_segments)
.await?;
let prosody_results = self.evaluate_prosody_language_specific(audio).await?;
let cultural_scores = self.evaluate_cultural_preferences(audio).await?;
let accent_results = self.evaluate_accent_awareness(audio).await?;
let code_switching = self.analyze_code_switching(&language_segments).await?;
let overall_language_score = self.calculate_overall_language_score(
&phonemic_results,
&prosody_results,
&cultural_scores,
&accent_results,
&code_switching,
);
let primary_score = QualityScore {
overall_score: overall_language_score,
component_scores: [
("phonemic".to_string(), phonemic_results.phoneme_coverage),
("prosody".to_string(), prosody_results.stress_patterns),
(
"cultural".to_string(),
cultural_scores.formality_appropriateness,
),
("accent".to_string(), accent_results.native_likeness),
]
.iter()
.cloned()
.collect(),
recommendations: vec![],
confidence: 0.8,
processing_time: Some(Duration::from_millis(100)),
};
Ok(LanguageSpecificResult {
primary_score,
language_segments,
phonemic_results,
prosody_results,
cultural_scores,
accent_results,
code_switching,
overall_language_score,
})
}
async fn detect_language_segments(
&self,
audio: &AudioBuffer,
) -> Result<Vec<LanguageSegment>, EvaluationError> {
let duration = audio.len() as f32 / audio.sample_rate() as f32;
let mut segments = Vec::new();
segments.push(LanguageSegment {
language: self.config.primary_language.clone(),
start_time: 0.0,
end_time: duration * 0.8,
confidence: 0.9,
quality_score: 0.8,
});
if self.config.secondary_languages.len() > 0 && duration > 2.0 {
segments.push(LanguageSegment {
language: self.config.secondary_languages[0].clone(),
start_time: duration * 0.8,
end_time: duration,
confidence: 0.6,
quality_score: 0.7,
});
}
Ok(segments)
}
async fn evaluate_phonemic_adaptation(
&self,
_audio: &AudioBuffer,
_language_segments: &[LanguageSegment],
) -> Result<PhonemicEvaluationResult, EvaluationError> {
let phoneme_inventory = self
.phoneme_inventories
.get(&self.config.primary_language)
.ok_or_else(|| EvaluationError::ConfigurationError {
message: format!(
"No phoneme inventory for language: {:?}",
self.config.primary_language
),
})?;
let phoneme_coverage = 0.85;
let mut phoneme_accuracy = HashMap::new();
phoneme_accuracy.insert("vowels".to_string(), 0.87);
phoneme_accuracy.insert("consonants".to_string(), 0.83);
phoneme_accuracy.insert("clusters".to_string(), 0.78);
let sound_changes = vec![SoundChangeAnalysis {
rule: "Final devoicing".to_string(),
frequency: 0.3,
accuracy: 0.9,
context: "word_final".to_string(),
}];
Ok(PhonemicEvaluationResult {
phoneme_coverage,
phoneme_accuracy,
allophone_variation: 0.8,
phonotactic_compliance: 0.85,
sound_changes,
})
}
async fn evaluate_prosody_language_specific(
&self,
_audio: &AudioBuffer,
) -> Result<ProsodyEvaluationResult, EvaluationError> {
let prosody_model = self
.prosody_models
.get(&self.config.primary_language)
.ok_or_else(|| EvaluationError::ConfigurationError {
message: format!(
"No prosody model for language: {:?}",
self.config.primary_language
),
})?;
let stress_patterns = match prosody_model.rhythm_characteristics.timing_type {
TimingType::StressTimed => 0.85,
TimingType::SyllableTimed => 0.82,
TimingType::MoraTimed => 0.80,
TimingType::Mixed => 0.75,
};
Ok(ProsodyEvaluationResult {
stress_patterns,
intonation_contours: 0.83,
rhythm_timing: 0.87,
prosodic_features: prosody_model.prosodic_features.clone(),
sentence_prosody: 0.85,
})
}
async fn evaluate_cultural_preferences(
&self,
_audio: &AudioBuffer,
) -> Result<CulturalPreferenceResult, EvaluationError> {
let cultural_model = self
.cultural_models
.get(&self.config.primary_language)
.ok_or_else(|| EvaluationError::ConfigurationError {
message: format!(
"No cultural model for language: {:?}",
self.config.primary_language
),
})?;
Ok(CulturalPreferenceResult {
accent_preferences: cultural_model.accent_preferences.clone(),
rate_preferences: 0.8,
formality_appropriateness: 0.75,
cultural_patterns: [
("speaking_style".to_string(), 0.8),
("politeness_markers".to_string(), 0.7),
]
.iter()
.cloned()
.collect(),
social_context: 0.78,
})
}
async fn evaluate_accent_awareness(
&self,
_audio: &AudioBuffer,
) -> Result<AccentEvaluationResult, EvaluationError> {
let accent_model = self
.accent_models
.get(&self.config.primary_language)
.ok_or_else(|| EvaluationError::ConfigurationError {
message: format!(
"No accent model for language: {:?}",
self.config.primary_language
),
})?;
let accent_type = match self.config.primary_language {
LanguageCode::EnUs => "General American",
LanguageCode::EsEs => "Castilian",
LanguageCode::FrFr => "Standard French",
_ => "Standard",
};
Ok(AccentEvaluationResult {
accent_type: accent_type.to_string(),
accent_strength: 0.6,
accent_consistency: 0.85,
native_likeness: 0.75,
regional_features: [
("vowel_system".to_string(), 0.8),
("consonant_features".to_string(), 0.7),
("prosodic_patterns".to_string(), 0.85),
]
.iter()
.cloned()
.collect(),
})
}
async fn analyze_code_switching(
&self,
language_segments: &[LanguageSegment],
) -> Result<CodeSwitchingResult, EvaluationError> {
let mut switch_points = Vec::new();
let mut switch_count = 0;
for window in language_segments.windows(2) {
if window[0].language != window[1].language {
switch_count += 1;
switch_points.push(LanguageSwitch {
time: window[1].start_time,
from_language: window[0].language.clone(),
to_language: window[1].language.clone(),
switch_type: SwitchType::Intersentential, naturalness: 0.7,
});
}
}
let matrix_language = if !language_segments.is_empty() {
Some(language_segments[0].language.clone())
} else {
None
};
let embedded_languages: Vec<LanguageCode> = language_segments
.iter()
.map(|seg| seg.language.clone())
.collect::<std::collections::HashSet<_>>()
.into_iter()
.filter(|lang| Some(lang) != matrix_language.as_ref())
.collect();
Ok(CodeSwitchingResult {
switch_count,
switch_points,
switching_naturalness: 0.75,
matrix_language,
embedded_languages,
})
}
fn calculate_overall_language_score(
&self,
phonemic: &PhonemicEvaluationResult,
prosody: &ProsodyEvaluationResult,
cultural: &CulturalPreferenceResult,
accent: &AccentEvaluationResult,
code_switching: &CodeSwitchingResult,
) -> f32 {
let weights = [0.25, 0.25, 0.2, 0.2, 0.1];
let scores = [
phonemic.phoneme_coverage,
prosody.stress_patterns,
cultural.formality_appropriateness,
accent.native_likeness,
code_switching.switching_naturalness,
];
let weighted_sum: f32 = scores
.iter()
.zip(weights.iter())
.map(|(score, weight)| score * weight)
.sum();
weighted_sum.max(0.0).min(1.0)
}
}
#[async_trait]
pub trait LanguageSpecificEvaluationTrait {
async fn evaluate_language_specific(
&self,
audio: &AudioBuffer,
config: &LanguageSpecificConfig,
reference: Option<&AudioBuffer>,
) -> EvaluationResult<LanguageSpecificResult>;
fn supported_languages(&self) -> Vec<LanguageCode>;
fn is_language_supported(&self, language: &LanguageCode) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_language_specific_evaluator_creation() {
let config = LanguageSpecificConfig::default();
let evaluator = LanguageSpecificEvaluator::new(config);
assert!(!evaluator.phoneme_inventories.is_empty());
assert!(!evaluator.prosody_models.is_empty());
}
#[tokio::test]
async fn test_language_detection() {
let config = LanguageSpecificConfig::default();
let evaluator = LanguageSpecificEvaluator::new(config);
let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
let segments = evaluator.detect_language_segments(&audio).await.unwrap();
assert!(!segments.is_empty());
assert_eq!(segments[0].language, LanguageCode::EnUs);
}
#[tokio::test]
async fn test_phonemic_evaluation() {
let config = LanguageSpecificConfig::default();
let evaluator = LanguageSpecificEvaluator::new(config);
let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
let segments = evaluator.detect_language_segments(&audio).await.unwrap();
let result = evaluator
.evaluate_phonemic_adaptation(&audio, &segments)
.await
.unwrap();
assert!(result.phoneme_coverage > 0.0);
assert!(result.phoneme_coverage <= 1.0);
assert!(!result.phoneme_accuracy.is_empty());
}
#[tokio::test]
async fn test_prosody_evaluation() {
let config = LanguageSpecificConfig::default();
let evaluator = LanguageSpecificEvaluator::new(config);
let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
let result = evaluator
.evaluate_prosody_language_specific(&audio)
.await
.unwrap();
assert!(result.stress_patterns > 0.0);
assert!(result.stress_patterns <= 1.0);
assert!(!result.prosodic_features.is_empty());
}
#[tokio::test]
async fn test_full_language_specific_evaluation() {
let config = LanguageSpecificConfig::default();
let evaluator = LanguageSpecificEvaluator::new(config);
let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
let result = evaluator
.evaluate_language_specific(&audio, None)
.await
.unwrap();
assert!(result.overall_language_score > 0.0);
assert!(result.overall_language_score <= 1.0);
assert!(!result.language_segments.is_empty());
assert!(!result.phonemic_results.phoneme_accuracy.is_empty());
}
#[tokio::test]
async fn test_code_switching_analysis() {
let config = LanguageSpecificConfig {
secondary_languages: vec![LanguageCode::EsEs],
..Default::default()
};
let evaluator = LanguageSpecificEvaluator::new(config);
let audio = AudioBuffer::new(vec![0.1; 32000], 16000, 1);
let segments = evaluator.detect_language_segments(&audio).await.unwrap();
let result = evaluator.analyze_code_switching(&segments).await.unwrap();
assert_eq!(result.matrix_language, Some(LanguageCode::EnUs));
if segments.len() > 1 {
assert!(!result.embedded_languages.is_empty());
}
if segments.len() > 1 {
assert!(result.switch_count > 0);
}
}
#[tokio::test]
async fn test_accent_evaluation() {
let config = LanguageSpecificConfig::default();
let evaluator = LanguageSpecificEvaluator::new(config);
let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
let result = evaluator.evaluate_accent_awareness(&audio).await.unwrap();
assert_eq!(result.accent_type, "General American");
assert!(result.native_likeness > 0.0);
assert!(result.native_likeness <= 1.0);
assert!(!result.regional_features.is_empty());
}
}