1use crate::{
42 config::ConversionConfig,
43 core::VoiceConverter,
44 processing::{AudioBuffer, ProcessingPipeline},
45 realtime::{RealtimeConfig, RealtimeConverter},
46 types::{ConversionRequest, ConversionTarget, ConversionType, VoiceCharacteristics},
47 Error, Result,
48};
49use serde::{Deserialize, Serialize};
50use std::collections::HashMap;
51use std::sync::Arc;
52use tokio::sync::RwLock;
53use tracing::{debug, info, warn};
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub enum GameEngine {
58 Unity,
60 Unreal,
62 Godot,
64 Bevy,
66 Custom,
68}
69
70impl GameEngine {
71 pub fn performance_constraints(&self) -> GamePerformanceConstraints {
73 match self {
74 GameEngine::Unity => GamePerformanceConstraints {
75 max_latency_ms: 20.0,
76 max_cpu_usage_percent: 15.0,
77 max_memory_mb: 64,
78 target_fps_impact: 2.0,
79 audio_thread_priority: ThreadPriority::High,
80 },
81 GameEngine::Unreal => GamePerformanceConstraints {
82 max_latency_ms: 16.0, max_cpu_usage_percent: 12.0,
84 max_memory_mb: 128,
85 target_fps_impact: 1.5,
86 audio_thread_priority: ThreadPriority::RealTime,
87 },
88 GameEngine::Godot => GamePerformanceConstraints {
89 max_latency_ms: 25.0,
90 max_cpu_usage_percent: 18.0,
91 max_memory_mb: 48,
92 target_fps_impact: 3.0,
93 audio_thread_priority: ThreadPriority::High,
94 },
95 GameEngine::Bevy => GamePerformanceConstraints {
96 max_latency_ms: 16.0,
97 max_cpu_usage_percent: 10.0,
98 max_memory_mb: 96,
99 target_fps_impact: 1.0,
100 audio_thread_priority: ThreadPriority::High,
101 },
102 GameEngine::Custom => GamePerformanceConstraints {
103 max_latency_ms: 30.0,
104 max_cpu_usage_percent: 20.0,
105 max_memory_mb: 80,
106 target_fps_impact: 4.0,
107 audio_thread_priority: ThreadPriority::Normal,
108 },
109 }
110 }
111
112 pub fn as_str(&self) -> &'static str {
114 match self {
115 GameEngine::Unity => "Unity",
116 GameEngine::Unreal => "Unreal",
117 GameEngine::Godot => "Godot",
118 GameEngine::Bevy => "Bevy",
119 GameEngine::Custom => "Custom",
120 }
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
126pub enum ThreadPriority {
127 Low,
129 Normal,
131 High,
133 RealTime,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct GamePerformanceConstraints {
140 pub max_latency_ms: f32,
142 pub max_cpu_usage_percent: f32,
144 pub max_memory_mb: u32,
146 pub target_fps_impact: f32,
148 pub audio_thread_priority: ThreadPriority,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct GameAudioConfig {
155 pub engine: GameEngine,
157 pub buffer_size: usize,
159 pub sample_rate: u32,
161 pub channels: u32,
163 pub spatial_audio: bool,
165 pub voice_activity_detection: bool,
167 pub automatic_gain_control: bool,
169 pub noise_suppression: bool,
171 pub quality_level: f32,
173 pub adaptive_quality: bool,
175 pub engine_optimizations: HashMap<String, f32>,
177}
178
179impl GameAudioConfig {
180 pub fn unity_optimized() -> Self {
182 let mut engine_optimizations = HashMap::new();
183 engine_optimizations.insert("unity_audio_clip_compatibility".to_string(), 1.0);
184 engine_optimizations.insert("unity_mixer_integration".to_string(), 1.0);
185 engine_optimizations.insert("unity_audio_source_optimization".to_string(), 0.8);
186
187 Self {
188 engine: GameEngine::Unity,
189 buffer_size: 512,
190 sample_rate: 44100,
191 channels: 2,
192 spatial_audio: true,
193 voice_activity_detection: true,
194 automatic_gain_control: true,
195 noise_suppression: true,
196 quality_level: 0.8,
197 adaptive_quality: true,
198 engine_optimizations,
199 }
200 }
201
202 pub fn unreal_optimized() -> Self {
204 let mut engine_optimizations = HashMap::new();
205 engine_optimizations.insert("unreal_sound_cue_integration".to_string(), 1.0);
206 engine_optimizations.insert("unreal_audio_component_optimization".to_string(), 1.0);
207 engine_optimizations.insert("unreal_metasound_compatibility".to_string(), 0.9);
208
209 Self {
210 engine: GameEngine::Unreal,
211 buffer_size: 256, sample_rate: 48000,
213 channels: 2,
214 spatial_audio: true,
215 voice_activity_detection: true,
216 automatic_gain_control: true,
217 noise_suppression: true,
218 quality_level: 0.9,
219 adaptive_quality: true,
220 engine_optimizations,
221 }
222 }
223
224 pub fn godot_optimized() -> Self {
226 let mut engine_optimizations = HashMap::new();
227 engine_optimizations.insert("godot_audio_stream_integration".to_string(), 1.0);
228 engine_optimizations.insert("godot_audio_bus_optimization".to_string(), 0.8);
229
230 Self {
231 engine: GameEngine::Godot,
232 buffer_size: 1024,
233 sample_rate: 44100,
234 channels: 2,
235 spatial_audio: true,
236 voice_activity_detection: true,
237 automatic_gain_control: false, noise_suppression: true,
239 quality_level: 0.7,
240 adaptive_quality: true,
241 engine_optimizations,
242 }
243 }
244
245 pub fn bevy_optimized() -> Self {
247 let mut engine_optimizations = HashMap::new();
248 engine_optimizations.insert("bevy_audio_resource_integration".to_string(), 1.0);
249 engine_optimizations.insert("bevy_ecs_system_optimization".to_string(), 1.0);
250
251 Self {
252 engine: GameEngine::Bevy,
253 buffer_size: 256,
254 sample_rate: 44100,
255 channels: 2,
256 spatial_audio: true,
257 voice_activity_detection: true,
258 automatic_gain_control: true,
259 noise_suppression: true,
260 quality_level: 0.85,
261 adaptive_quality: true,
262 engine_optimizations,
263 }
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
269pub enum GameVoiceMode {
270 PlayerChat,
272 CharacterVoice,
274 EnvironmentalVoice,
276 NarratorVoice,
278 DynamicMorphing,
280}
281
282#[derive(Debug)]
284pub struct GameVoiceProcessor {
285 engine: GameEngine,
287 config: GameAudioConfig,
289 realtime_converter: RealtimeConverter,
291 voice_converter: Arc<VoiceConverter>,
293 constraints: GamePerformanceConstraints,
295 character_presets: Arc<RwLock<HashMap<String, VoiceCharacteristics>>>,
297 active_sessions: Arc<RwLock<HashMap<String, GameVoiceSession>>>,
299 performance_monitor: GamePerformanceMonitor,
301}
302
303impl GameVoiceProcessor {
304 pub fn new(engine: GameEngine, config: GameAudioConfig) -> Result<Self> {
306 let constraints = engine.performance_constraints();
307
308 let realtime_config = RealtimeConfig {
310 buffer_size: config.buffer_size,
311 sample_rate: config.sample_rate,
312 target_latency_ms: constraints.max_latency_ms,
313 overlap_factor: 0.25,
314 adaptive_buffering: config.adaptive_quality,
315 max_threads: 2, enable_lookahead: false, lookahead_size: 0,
318 };
319
320 let realtime_converter = RealtimeConverter::new(realtime_config)?;
321 let voice_converter = Arc::new(VoiceConverter::new()?);
322
323 Ok(Self {
324 engine,
325 config,
326 realtime_converter,
327 voice_converter,
328 constraints,
329 character_presets: Arc::new(RwLock::new(HashMap::new())),
330 active_sessions: Arc::new(RwLock::new(HashMap::new())),
331 performance_monitor: GamePerformanceMonitor::new(),
332 })
333 }
334
335 pub async fn process_game_voice(
337 &mut self,
338 input_audio: &[f32],
339 voice_id: &str,
340 ) -> Result<Vec<f32>> {
341 let start_time = std::time::Instant::now();
342
343 if !self
345 .performance_monitor
346 .check_performance_budget(&self.constraints)
347 {
348 warn!("Performance budget exceeded, using passthrough mode");
349 return Ok(input_audio.to_vec());
350 }
351
352 let voice_characteristics = {
354 let presets = self.character_presets.read().await;
355 presets.get(voice_id).cloned().unwrap_or_default()
356 };
357
358 let target = ConversionTarget::new(voice_characteristics);
360 self.realtime_converter.set_conversion_target(target);
361
362 let result = self.realtime_converter.process_chunk(input_audio).await?;
364
365 let processing_time = start_time.elapsed();
367 self.performance_monitor.record_processing(
368 processing_time,
369 input_audio.len(),
370 &self.constraints,
371 );
372
373 debug!(
374 "Game voice processed: {} samples in {:.2}ms",
375 input_audio.len(),
376 processing_time.as_secs_f32() * 1000.0
377 );
378
379 Ok(result)
380 }
381
382 pub async fn process_character_voice(
384 &mut self,
385 input_audio: &[f32],
386 character_id: &str,
387 mode: GameVoiceMode,
388 ) -> Result<Vec<f32>> {
389 let processed_audio = match mode {
391 GameVoiceMode::PlayerChat => self.apply_player_chat_processing(input_audio).await?,
392 GameVoiceMode::CharacterVoice => {
393 self.apply_character_voice_processing(input_audio, character_id)
394 .await?
395 }
396 GameVoiceMode::EnvironmentalVoice => {
397 self.apply_environmental_processing(input_audio).await?
398 }
399 GameVoiceMode::NarratorVoice => self.apply_narrator_processing(input_audio).await?,
400 GameVoiceMode::DynamicMorphing => {
401 self.apply_dynamic_morphing(input_audio, character_id)
402 .await?
403 }
404 };
405
406 Ok(processed_audio)
407 }
408
409 pub async fn register_character_preset(
411 &self,
412 character_id: String,
413 characteristics: VoiceCharacteristics,
414 ) {
415 let mut presets = self.character_presets.write().await;
416 presets.insert(character_id.clone(), characteristics);
417 info!("Registered character preset: {}", character_id);
418 }
419
420 pub async fn start_voice_session(
422 &self,
423 session_id: String,
424 character_id: String,
425 mode: GameVoiceMode,
426 ) -> Result<()> {
427 let session = GameVoiceSession {
428 session_id: session_id.clone(),
429 character_id,
430 mode,
431 start_time: std::time::Instant::now(),
432 processed_samples: 0,
433 latency_samples: Vec::new(),
434 };
435
436 let mut sessions = self.active_sessions.write().await;
437 sessions.insert(session_id.clone(), session);
438
439 info!("Started voice session: {}", session_id);
440 Ok(())
441 }
442
443 pub async fn stop_voice_session(&self, session_id: &str) -> Result<GameVoiceSession> {
445 let mut sessions = self.active_sessions.write().await;
446 sessions
447 .remove(session_id)
448 .ok_or_else(|| Error::processing(format!("Voice session not found: {session_id}")))
449 }
450
451 pub fn get_performance_metrics(&self) -> GamePerformanceMetrics {
453 self.performance_monitor.get_current_metrics()
454 }
455
456 pub fn is_performance_acceptable(&self) -> bool {
458 self.performance_monitor
459 .check_performance_budget(&self.constraints)
460 }
461
462 pub fn get_engine_integration_info(&self) -> GameEngineIntegration {
464 match self.engine {
465 GameEngine::Unity => GameEngineIntegration::Unity(UnityIntegration {
466 plugin_version: "1.0.0".to_string(),
467 unity_version_compatibility: "2021.3+".to_string(),
468 audio_clip_support: true,
469 mixer_integration: true,
470 blueprint_support: false,
471 }),
472 GameEngine::Unreal => GameEngineIntegration::Unreal(UnrealIntegration {
473 plugin_version: "1.0.0".to_string(),
474 unreal_version_compatibility: "4.27+, 5.0+".to_string(),
475 blueprint_support: true,
476 metasound_support: true,
477 audio_component_integration: true,
478 }),
479 GameEngine::Godot => GameEngineIntegration::Godot(GodotIntegration {
480 plugin_version: "1.0.0".to_string(),
481 godot_version_compatibility: "3.5+, 4.0+".to_string(),
482 gdscript_bindings: true,
483 audio_stream_support: true,
484 }),
485 GameEngine::Bevy => GameEngineIntegration::Bevy(BevyIntegration {
486 plugin_version: "1.0.0".to_string(),
487 bevy_version_compatibility: "0.11+".to_string(),
488 ecs_integration: true,
489 audio_resource_support: true,
490 }),
491 GameEngine::Custom => GameEngineIntegration::Custom(CustomIntegration {
492 c_api_version: "1.0.0".to_string(),
493 supported_platforms: vec![
494 "Windows".to_string(),
495 "macOS".to_string(),
496 "Linux".to_string(),
497 ],
498 callback_support: true,
499 }),
500 }
501 }
502
503 async fn apply_player_chat_processing(&mut self, input_audio: &[f32]) -> Result<Vec<f32>> {
506 let mut processed = input_audio.to_vec();
508
509 if self.config.noise_suppression {
510 processed = self.apply_noise_suppression(&processed)?;
511 }
512
513 if self.config.automatic_gain_control {
514 processed = self.apply_automatic_gain_control(&processed)?;
515 }
516
517 Ok(processed)
518 }
519
520 async fn apply_character_voice_processing(
521 &mut self,
522 input_audio: &[f32],
523 character_id: &str,
524 ) -> Result<Vec<f32>> {
525 self.process_game_voice(input_audio, character_id).await
526 }
527
528 async fn apply_environmental_processing(&mut self, input_audio: &[f32]) -> Result<Vec<f32>> {
529 let mut processed = input_audio.to_vec();
531
532 for sample in processed.iter_mut() {
534 *sample *= 0.7; }
536
537 Ok(processed)
538 }
539
540 async fn apply_narrator_processing(&mut self, input_audio: &[f32]) -> Result<Vec<f32>> {
541 let mut processed = input_audio.to_vec();
543
544 for sample in processed.iter_mut() {
546 *sample = (*sample * 1.2).clamp(-1.0, 1.0);
547 }
548
549 Ok(processed)
550 }
551
552 async fn apply_dynamic_morphing(
553 &mut self,
554 input_audio: &[f32],
555 character_id: &str,
556 ) -> Result<Vec<f32>> {
557 self.process_game_voice(input_audio, character_id).await
559 }
560
561 fn apply_noise_suppression(&self, audio: &[f32]) -> Result<Vec<f32>> {
562 let threshold = 0.01;
564 let mut processed = audio.to_vec();
565
566 for sample in processed.iter_mut() {
567 if sample.abs() < threshold {
568 *sample *= 0.1;
569 }
570 }
571
572 Ok(processed)
573 }
574
575 fn apply_automatic_gain_control(&self, audio: &[f32]) -> Result<Vec<f32>> {
576 let target_level = 0.7;
578 let current_level = audio.iter().map(|&x| x.abs()).sum::<f32>() / audio.len() as f32;
579
580 if current_level > 0.0 {
581 let gain = target_level / current_level;
582 let clamped_gain = gain.clamp(0.5, 2.0);
583
584 Ok(audio
585 .iter()
586 .map(|&x| (x * clamped_gain).clamp(-1.0, 1.0))
587 .collect())
588 } else {
589 Ok(audio.to_vec())
590 }
591 }
592}
593
594#[derive(Debug, Clone)]
596pub struct GameVoiceSession {
597 pub session_id: String,
599 pub character_id: String,
601 pub mode: GameVoiceMode,
603 pub start_time: std::time::Instant,
605 pub processed_samples: usize,
607 pub latency_samples: Vec<f32>,
609}
610
611#[derive(Debug)]
613pub struct GamePerformanceMonitor {
614 processing_times: Vec<std::time::Duration>,
615 cpu_usage_samples: Vec<f32>,
616 memory_usage_samples: Vec<u32>,
617 frame_drops: u32,
618 last_check: std::time::Instant,
619}
620
621impl GamePerformanceMonitor {
622 fn new() -> Self {
623 Self {
624 processing_times: Vec::new(),
625 cpu_usage_samples: Vec::new(),
626 memory_usage_samples: Vec::new(),
627 frame_drops: 0,
628 last_check: std::time::Instant::now(),
629 }
630 }
631
632 fn record_processing(
633 &mut self,
634 processing_time: std::time::Duration,
635 _sample_count: usize,
636 constraints: &GamePerformanceConstraints,
637 ) {
638 self.processing_times.push(processing_time);
639
640 if self.processing_times.len() > 100 {
642 self.processing_times.drain(0..50);
643 }
644
645 let latency_ms = processing_time.as_secs_f32() * 1000.0;
647 if latency_ms > constraints.max_latency_ms {
648 self.frame_drops += 1;
649 }
650 }
651
652 fn check_performance_budget(&self, constraints: &GamePerformanceConstraints) -> bool {
653 if self.processing_times.is_empty() {
654 return true;
655 }
656
657 let avg_latency_ms = self
658 .processing_times
659 .iter()
660 .map(|d| d.as_secs_f32() * 1000.0)
661 .sum::<f32>()
662 / self.processing_times.len() as f32;
663
664 avg_latency_ms <= constraints.max_latency_ms
665 }
666
667 fn get_current_metrics(&self) -> GamePerformanceMetrics {
668 let avg_latency_ms = if self.processing_times.is_empty() {
669 0.0
670 } else {
671 self.processing_times
672 .iter()
673 .map(|d| d.as_secs_f32() * 1000.0)
674 .sum::<f32>()
675 / self.processing_times.len() as f32
676 };
677
678 GamePerformanceMetrics {
679 average_latency_ms: avg_latency_ms,
680 cpu_usage_percent: self.cpu_usage_samples.last().copied().unwrap_or(0.0),
681 memory_usage_mb: self.memory_usage_samples.last().copied().unwrap_or(0),
682 frame_drops: self.frame_drops,
683 uptime_seconds: self.last_check.elapsed().as_secs(),
684 }
685 }
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct GamePerformanceMetrics {
691 pub average_latency_ms: f32,
693 pub cpu_usage_percent: f32,
695 pub memory_usage_mb: u32,
697 pub frame_drops: u32,
699 pub uptime_seconds: u64,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize)]
705pub enum GameEngineIntegration {
706 Unity(UnityIntegration),
708 Unreal(UnrealIntegration),
710 Godot(GodotIntegration),
712 Bevy(BevyIntegration),
714 Custom(CustomIntegration),
716}
717
718#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct UnityIntegration {
721 pub plugin_version: String,
723 pub unity_version_compatibility: String,
725 pub audio_clip_support: bool,
727 pub mixer_integration: bool,
729 pub blueprint_support: bool,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct UnrealIntegration {
736 pub plugin_version: String,
738 pub unreal_version_compatibility: String,
740 pub blueprint_support: bool,
742 pub metasound_support: bool,
744 pub audio_component_integration: bool,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct GodotIntegration {
751 pub plugin_version: String,
753 pub godot_version_compatibility: String,
755 pub gdscript_bindings: bool,
757 pub audio_stream_support: bool,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct BevyIntegration {
764 pub plugin_version: String,
766 pub bevy_version_compatibility: String,
768 pub ecs_integration: bool,
770 pub audio_resource_support: bool,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
776pub struct CustomIntegration {
777 pub c_api_version: String,
779 pub supported_platforms: Vec<String>,
781 pub callback_support: bool,
783}
784
785#[cfg(test)]
786mod tests {
787 use super::*;
788
789 #[test]
790 fn test_game_engine_constraints() {
791 let unity_constraints = GameEngine::Unity.performance_constraints();
792 assert!(unity_constraints.max_latency_ms <= 20.0);
793 assert!(unity_constraints.max_cpu_usage_percent <= 15.0);
794
795 let unreal_constraints = GameEngine::Unreal.performance_constraints();
796 assert!(unreal_constraints.max_latency_ms <= 16.0);
797 assert_eq!(
798 unreal_constraints.audio_thread_priority,
799 ThreadPriority::RealTime
800 );
801 }
802
803 #[test]
804 fn test_game_audio_config_creation() {
805 let unity_config = GameAudioConfig::unity_optimized();
806 assert_eq!(unity_config.engine, GameEngine::Unity);
807 assert!(unity_config.spatial_audio);
808 assert!(unity_config.adaptive_quality);
809
810 let unreal_config = GameAudioConfig::unreal_optimized();
811 assert_eq!(unreal_config.engine, GameEngine::Unreal);
812 assert_eq!(unreal_config.buffer_size, 256);
813 assert_eq!(unreal_config.sample_rate, 48000);
814 }
815
816 #[tokio::test]
817 async fn test_game_voice_processor_creation() {
818 let config = GameAudioConfig::unity_optimized();
819 let processor = GameVoiceProcessor::new(GameEngine::Unity, config);
820 assert!(processor.is_ok());
821
822 let processor = processor.unwrap();
823 assert_eq!(processor.engine, GameEngine::Unity);
824 }
825
826 #[tokio::test]
827 async fn test_character_preset_registration() {
828 let config = GameAudioConfig::unity_optimized();
829 let processor = GameVoiceProcessor::new(GameEngine::Unity, config).unwrap();
830
831 let characteristics = VoiceCharacteristics::default();
832 processor
833 .register_character_preset("hero".to_string(), characteristics)
834 .await;
835
836 let presets = processor.character_presets.read().await;
837 assert!(presets.contains_key("hero"));
838 }
839
840 #[tokio::test]
841 async fn test_voice_session_management() {
842 let config = GameAudioConfig::unity_optimized();
843 let processor = GameVoiceProcessor::new(GameEngine::Unity, config).unwrap();
844
845 let result = processor
847 .start_voice_session(
848 "session1".to_string(),
849 "hero".to_string(),
850 GameVoiceMode::CharacterVoice,
851 )
852 .await;
853 assert!(result.is_ok());
854
855 let sessions = processor.active_sessions.read().await;
857 assert!(sessions.contains_key("session1"));
858 }
859
860 #[test]
861 fn test_game_voice_modes() {
862 let modes = [
863 GameVoiceMode::PlayerChat,
864 GameVoiceMode::CharacterVoice,
865 GameVoiceMode::EnvironmentalVoice,
866 GameVoiceMode::NarratorVoice,
867 GameVoiceMode::DynamicMorphing,
868 ];
869
870 for mode in &modes {
871 let serialized = serde_json::to_string(mode).unwrap();
873 let deserialized: GameVoiceMode = serde_json::from_str(&serialized).unwrap();
874 assert_eq!(*mode, deserialized);
875 }
876 }
877
878 #[test]
879 fn test_performance_monitor() {
880 let mut monitor = GamePerformanceMonitor::new();
881 let constraints = GameEngine::Unity.performance_constraints();
882
883 monitor.record_processing(std::time::Duration::from_millis(10), 1024, &constraints);
885
886 let metrics = monitor.get_current_metrics();
887 assert!(metrics.average_latency_ms > 0.0);
888 assert!(monitor.check_performance_budget(&constraints));
889 }
890
891 #[test]
892 fn test_engine_integration_info() {
893 let config = GameAudioConfig::unity_optimized();
894 let processor = GameVoiceProcessor::new(GameEngine::Unity, config).unwrap();
895
896 let integration = processor.get_engine_integration_info();
897 match integration {
898 GameEngineIntegration::Unity(unity) => {
899 assert!(unity.audio_clip_support);
900 assert!(unity.mixer_integration);
901 }
902 _ => panic!("Expected Unity integration"),
903 }
904 }
905
906 #[test]
907 fn test_thread_priority_ordering() {
908 assert!(ThreadPriority::RealTime > ThreadPriority::High);
909 assert!(ThreadPriority::High > ThreadPriority::Normal);
910 assert!(ThreadPriority::Normal > ThreadPriority::Low);
911 }
912
913 #[tokio::test]
914 async fn test_noise_suppression() {
915 let config = GameAudioConfig::unity_optimized();
916 let processor = GameVoiceProcessor::new(GameEngine::Unity, config).unwrap();
917
918 let noisy_audio = vec![0.001, 0.5, 0.002, -0.7, 0.003]; let processed = processor.apply_noise_suppression(&noisy_audio).unwrap();
920
921 assert!(processed[0].abs() < noisy_audio[0].abs());
923 assert!(processed[2].abs() < noisy_audio[2].abs());
924 assert!(processed[4].abs() < noisy_audio[4].abs());
925
926 assert_eq!(processed[1], noisy_audio[1]);
928 assert_eq!(processed[3], noisy_audio[3]);
929 }
930
931 #[tokio::test]
932 async fn test_automatic_gain_control() {
933 let config = GameAudioConfig::unity_optimized();
934 let processor = GameVoiceProcessor::new(GameEngine::Unity, config).unwrap();
935
936 let quiet_audio = vec![0.1, -0.1, 0.05, -0.05];
937 let processed = processor
938 .apply_automatic_gain_control(&quiet_audio)
939 .unwrap();
940
941 let original_level =
943 quiet_audio.iter().map(|&x| x.abs()).sum::<f32>() / quiet_audio.len() as f32;
944 let processed_level =
945 processed.iter().map(|&x| x.abs()).sum::<f32>() / processed.len() as f32;
946 assert!(processed_level > original_level);
947 }
948}