1use crate::{types::ConversionRequest, Error};
39use std::collections::HashMap;
40use std::sync::{Arc, Mutex};
41use std::time::{Duration, Instant};
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub enum AudioBackend {
46 JACK,
48 ASIO,
50 PortAudio,
52 ALSA,
54 CoreAudio,
56 PulseAudio,
58 Auto,
60}
61
62#[derive(Debug, Clone)]
64pub struct RealtimeConfig {
65 pub preferred_backend: AudioBackend,
67 pub target_latency: f32,
69 pub buffer_size: usize,
71 pub sample_rate: u32,
73 pub channels: usize,
75 pub zero_copy: bool,
77 pub lock_free: bool,
79 pub thread_priority: u8,
81 pub adaptive_latency: bool,
83}
84
85impl Default for RealtimeConfig {
86 fn default() -> Self {
87 Self {
88 preferred_backend: AudioBackend::Auto,
89 target_latency: 20.0,
90 buffer_size: 512,
91 sample_rate: 44100,
92 channels: 2,
93 zero_copy: true,
94 lock_free: true,
95 thread_priority: 80,
96 adaptive_latency: true,
97 }
98 }
99}
100
101impl RealtimeConfig {
102 pub fn with_preferred_backend(mut self, backend: AudioBackend) -> Self {
104 self.preferred_backend = backend;
105 self
106 }
107
108 pub fn with_target_latency(mut self, latency_ms: f32) -> Self {
110 self.target_latency = latency_ms.max(1.0);
111 self
112 }
113
114 pub fn with_buffer_size(mut self, size: usize) -> Self {
116 self.buffer_size = size.clamp(64, 8192);
117 self
118 }
119
120 pub fn with_sample_rate(mut self, rate: u32) -> Self {
122 self.sample_rate = rate;
123 self
124 }
125
126 pub fn with_zero_copy(mut self, enable: bool) -> Self {
128 self.zero_copy = enable;
129 self
130 }
131
132 pub fn with_adaptive_latency(mut self, enable: bool) -> Self {
134 self.adaptive_latency = enable;
135 self
136 }
137}
138
139#[derive(Debug, Clone, Default)]
141pub struct RealtimeStats {
142 pub current_latency: f32,
144 pub average_latency: f32,
146 pub peak_latency: f32,
148 pub underruns: u64,
150 pub overruns: u64,
152 pub cpu_usage: f32,
154 pub memory_usage: f32,
156 pub frames_processed: u64,
158 pub success_rate: f32,
160}
161
162#[derive(Debug, Clone)]
164pub struct BackendCapabilities {
165 pub min_latency: f32,
167 pub max_latency: f32,
169 pub supported_buffer_sizes: Vec<usize>,
171 pub supported_sample_rates: Vec<u32>,
173 pub max_channels: usize,
175 pub zero_copy_support: bool,
177 pub lock_free_support: bool,
179 pub platform_available: bool,
181}
182
183#[derive(Debug)]
185pub struct RealtimeBuffer {
186 pub data: Vec<f32>,
188 pub sample_rate: u32,
190 pub channels: usize,
192 pub timestamp: Instant,
194 pub buffer_id: u64,
196}
197
198impl RealtimeBuffer {
199 pub fn new(data: Vec<f32>, sample_rate: u32, channels: usize) -> Self {
201 static BUFFER_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
202
203 Self {
204 data,
205 sample_rate,
206 channels,
207 timestamp: Instant::now(),
208 buffer_id: BUFFER_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
209 }
210 }
211
212 pub fn samples_per_channel(&self) -> usize {
214 self.data.len() / self.channels
215 }
216
217 pub fn duration_ms(&self) -> f32 {
219 (self.samples_per_channel() as f32 / self.sample_rate as f32) * 1000.0
220 }
221
222 pub fn is_within_latency(&self, target_latency: f32) -> bool {
224 self.timestamp.elapsed().as_secs_f32() * 1000.0 <= target_latency
225 }
226}
227
228pub struct RealtimeLibraryManager {
230 config: RealtimeConfig,
232 active_backend: Option<AudioBackend>,
234 backend_capabilities: HashMap<AudioBackend, BackendCapabilities>,
236 stats: Arc<Mutex<RealtimeStats>>,
238 buffer_pool: Arc<Mutex<Vec<RealtimeBuffer>>>,
240 processing_thread: Option<std::thread::JoinHandle<()>>,
242 shutdown: Arc<std::sync::atomic::AtomicBool>,
244 latency_history: Arc<Mutex<Vec<f32>>>,
246}
247
248impl RealtimeLibraryManager {
249 pub fn new(config: RealtimeConfig) -> Result<Self, Error> {
251 let backend_capabilities = Self::detect_available_backends();
252
253 Ok(Self {
254 config,
255 active_backend: None,
256 backend_capabilities,
257 stats: Arc::new(Mutex::new(RealtimeStats::default())),
258 buffer_pool: Arc::new(Mutex::new(Vec::new())),
259 processing_thread: None,
260 shutdown: Arc::new(std::sync::atomic::AtomicBool::new(false)),
261 latency_history: Arc::new(Mutex::new(Vec::new())),
262 })
263 }
264
265 pub fn initialize(&mut self) -> Result<(), Error> {
267 let backend = self.select_optimal_backend()?;
269
270 self.initialize_backend(&backend)?;
272
273 self.start_processing_thread()?;
275
276 self.active_backend = Some(backend);
277
278 Ok(())
279 }
280
281 pub fn process_realtime(&self, audio: &[f32]) -> Result<Vec<f32>, Error> {
283 let start_time = Instant::now();
284
285 let buffer = RealtimeBuffer::new(
287 audio.to_vec(),
288 self.config.sample_rate,
289 self.config.channels,
290 );
291
292 if !buffer.is_within_latency(self.config.target_latency) {
294 return Err(Error::validation(
295 "Buffer exceeds target latency".to_string(),
296 ));
297 }
298
299 let processed = self.apply_realtime_processing(&buffer)?;
301
302 let processing_time = start_time.elapsed().as_secs_f32() * 1000.0;
304 self.update_stats(processing_time, true);
305
306 if self.config.adaptive_latency {
308 self.adjust_latency_adaptively(processing_time);
309 }
310
311 Ok(processed.data)
312 }
313
314 pub fn process_stream(
316 &self,
317 audio_stream: &[f32],
318 chunk_size: usize,
319 ) -> Result<Vec<f32>, Error> {
320 let mut processed_output = Vec::new();
321
322 for chunk in audio_stream.chunks(chunk_size) {
323 let processed_chunk = self.process_realtime(chunk)?;
324 processed_output.extend_from_slice(&processed_chunk);
325 }
326
327 Ok(processed_output)
328 }
329
330 pub fn get_current_latency(&self) -> f32 {
332 self.stats.lock().expect("Lock poisoned").current_latency
333 }
334
335 pub fn get_stats(&self) -> RealtimeStats {
337 self.stats.lock().expect("Lock poisoned").clone()
338 }
339
340 pub fn get_active_backend(&self) -> Option<AudioBackend> {
342 self.active_backend.clone()
343 }
344
345 pub fn get_backend_capabilities(&self, backend: &AudioBackend) -> Option<BackendCapabilities> {
347 self.backend_capabilities.get(backend).cloned()
348 }
349
350 pub fn shutdown(&mut self) -> Result<(), Error> {
352 self.shutdown
353 .store(true, std::sync::atomic::Ordering::Relaxed);
354
355 if let Some(thread) = self.processing_thread.take() {
356 thread
357 .join()
358 .map_err(|_| Error::processing("Failed to join processing thread".to_string()))?;
359 }
360
361 self.active_backend = None;
362
363 Ok(())
364 }
365
366 fn detect_available_backends() -> HashMap<AudioBackend, BackendCapabilities> {
370 let mut capabilities = HashMap::new();
371
372 #[cfg(any(target_os = "linux", target_os = "macos"))]
374 capabilities.insert(
375 AudioBackend::JACK,
376 BackendCapabilities {
377 min_latency: 2.0,
378 max_latency: 100.0,
379 supported_buffer_sizes: vec![64, 128, 256, 512, 1024],
380 supported_sample_rates: vec![44100, 48000, 96000, 192000],
381 max_channels: 256,
382 zero_copy_support: true,
383 lock_free_support: true,
384 platform_available: Self::is_jack_available(),
385 },
386 );
387
388 #[cfg(target_os = "windows")]
390 capabilities.insert(
391 AudioBackend::ASIO,
392 BackendCapabilities {
393 min_latency: 1.0,
394 max_latency: 50.0,
395 supported_buffer_sizes: vec![64, 128, 256, 512],
396 supported_sample_rates: vec![44100, 48000, 96000, 192000],
397 max_channels: 64,
398 zero_copy_support: true,
399 lock_free_support: true,
400 platform_available: Self::is_asio_available(),
401 },
402 );
403
404 capabilities.insert(
406 AudioBackend::PortAudio,
407 BackendCapabilities {
408 min_latency: 5.0,
409 max_latency: 200.0,
410 supported_buffer_sizes: vec![128, 256, 512, 1024, 2048],
411 supported_sample_rates: vec![22050, 44100, 48000, 96000],
412 max_channels: 32,
413 zero_copy_support: false,
414 lock_free_support: false,
415 platform_available: true, },
417 );
418
419 #[cfg(target_os = "linux")]
421 capabilities.insert(
422 AudioBackend::ALSA,
423 BackendCapabilities {
424 min_latency: 3.0,
425 max_latency: 150.0,
426 supported_buffer_sizes: vec![128, 256, 512, 1024],
427 supported_sample_rates: vec![44100, 48000, 96000],
428 max_channels: 16,
429 zero_copy_support: true,
430 lock_free_support: false,
431 platform_available: Self::is_alsa_available(),
432 },
433 );
434
435 #[cfg(target_os = "macos")]
437 capabilities.insert(
438 AudioBackend::CoreAudio,
439 BackendCapabilities {
440 min_latency: 2.5,
441 max_latency: 100.0,
442 supported_buffer_sizes: vec![64, 128, 256, 512, 1024],
443 supported_sample_rates: vec![44100, 48000, 96000, 192000],
444 max_channels: 64,
445 zero_copy_support: true,
446 lock_free_support: true,
447 platform_available: true, },
449 );
450
451 #[cfg(target_os = "linux")]
453 capabilities.insert(
454 AudioBackend::PulseAudio,
455 BackendCapabilities {
456 min_latency: 10.0,
457 max_latency: 500.0,
458 supported_buffer_sizes: vec![256, 512, 1024, 2048],
459 supported_sample_rates: vec![44100, 48000],
460 max_channels: 8,
461 zero_copy_support: false,
462 lock_free_support: false,
463 platform_available: Self::is_pulseaudio_available(),
464 },
465 );
466
467 capabilities
468 }
469
470 fn select_optimal_backend(&self) -> Result<AudioBackend, Error> {
472 match self.config.preferred_backend {
473 AudioBackend::Auto => {
474 let mut best_backend = AudioBackend::PortAudio; let mut best_score = 0.0;
477
478 for (backend, capabilities) in &self.backend_capabilities {
479 if !capabilities.platform_available {
480 continue;
481 }
482
483 let score = self.calculate_backend_score(capabilities);
484 if score > best_score {
485 best_score = score;
486 best_backend = backend.clone();
487 }
488 }
489
490 Ok(best_backend)
491 }
492 ref backend => {
493 if let Some(capabilities) = self.backend_capabilities.get(backend) {
495 if capabilities.platform_available {
496 Ok(backend.clone())
497 } else {
498 Err(Error::validation(format!(
499 "Preferred backend {backend:?} is not available"
500 )))
501 }
502 } else {
503 Err(Error::validation(format!("Unknown backend: {:?}", backend)))
504 }
505 }
506 }
507 }
508
509 fn calculate_backend_score(&self, capabilities: &BackendCapabilities) -> f32 {
511 let mut score = 0.0;
512
513 score += (100.0 - capabilities.min_latency) / 100.0 * 40.0;
515
516 if capabilities.zero_copy_support {
518 score += 20.0;
519 }
520
521 if capabilities.lock_free_support {
523 score += 15.0;
524 }
525
526 score += (capabilities.max_channels as f32 / 256.0) * 10.0;
528
529 score += (capabilities.supported_buffer_sizes.len() as f32 / 10.0) * 5.0;
531
532 #[cfg(target_os = "linux")]
534 {
535 match capabilities {
537 caps if std::ptr::eq(
538 caps,
539 self.backend_capabilities
540 .get(&AudioBackend::JACK)
541 .expect("operation should succeed"),
542 ) =>
543 {
544 score += 10.0
545 }
546 _ => {}
547 }
548 }
549
550 #[cfg(target_os = "macos")]
551 {
552 match capabilities {
554 caps if std::ptr::eq(
555 caps,
556 self.backend_capabilities
557 .get(&AudioBackend::CoreAudio)
558 .expect("operation should succeed"),
559 ) =>
560 {
561 score += 10.0
562 }
563 _ => {}
564 }
565 }
566
567 #[cfg(target_os = "windows")]
568 {
569 match capabilities {
571 caps if caps as *const _
572 == self
573 .backend_capabilities
574 .get(&AudioBackend::ASIO)
575 .expect("operation should succeed") as *const _ =>
576 {
577 score += 10.0
578 }
579 _ => {}
580 }
581 }
582
583 score
584 }
585
586 fn initialize_backend(&self, backend: &AudioBackend) -> Result<(), Error> {
588 match backend {
592 AudioBackend::JACK => {
593 }
596 AudioBackend::ASIO => {
597 }
600 AudioBackend::PortAudio => {
601 }
604 AudioBackend::ALSA => {
605 }
608 AudioBackend::CoreAudio => {
609 }
612 AudioBackend::PulseAudio => {
613 }
616 AudioBackend::Auto => {
617 return Err(Error::validation(
618 "Auto backend should be resolved to specific backend".to_string(),
619 ));
620 }
621 }
622
623 Ok(())
624 }
625
626 fn start_processing_thread(&mut self) -> Result<(), Error> {
628 let stats = Arc::clone(&self.stats);
629 let shutdown = Arc::clone(&self.shutdown);
630 let config = self.config.clone();
631
632 let thread = std::thread::Builder::new()
633 .name("realtime-audio-processor".to_string())
634 .spawn(move || {
635 Self::processing_thread_main(stats, shutdown, config);
636 })
637 .map_err(|e| Error::processing(format!("Failed to start processing thread: {}", e)))?;
638
639 self.processing_thread = Some(thread);
640
641 Ok(())
642 }
643
644 fn processing_thread_main(
646 stats: Arc<Mutex<RealtimeStats>>,
647 shutdown: Arc<std::sync::atomic::AtomicBool>,
648 config: RealtimeConfig,
649 ) {
650 let mut frame_count = 0u64;
651 let mut cpu_usage_accumulator = 0.0;
652 let sleep_duration = Duration::from_micros(
653 (config.buffer_size as f64 / config.sample_rate as f64 * 1_000_000.0) as u64,
654 );
655
656 while !shutdown.load(std::sync::atomic::Ordering::Relaxed) {
657 let start_time = Instant::now();
658
659 let cpu_usage = Self::simulate_cpu_work(&config);
661 cpu_usage_accumulator += cpu_usage;
662 frame_count += 1;
663
664 if frame_count.is_multiple_of(100) {
666 if let Ok(mut stats) = stats.lock() {
667 stats.frames_processed = frame_count;
668 stats.cpu_usage = cpu_usage_accumulator / 100.0;
669 stats.success_rate = 0.98; cpu_usage_accumulator = 0.0;
671 }
672 }
673
674 let elapsed = start_time.elapsed();
676 if elapsed < sleep_duration {
677 std::thread::sleep(sleep_duration - elapsed);
678 }
679 }
680 }
681
682 fn simulate_cpu_work(config: &RealtimeConfig) -> f32 {
684 let work_factor = config.buffer_size as f32 / 1024.0;
686 let quality_factor = if config.zero_copy { 0.5 } else { 1.0 };
687
688 (work_factor * quality_factor * 0.4 + 0.1).min(0.6)
690 }
691
692 fn apply_realtime_processing(&self, buffer: &RealtimeBuffer) -> Result<RealtimeBuffer, Error> {
694 let start_time = Instant::now();
695
696 let mut processed_data = buffer.data.clone();
698
699 if self.config.zero_copy {
701 for sample in &mut processed_data {
703 *sample *= 0.95; }
705 } else {
706 processed_data = processed_data.iter().map(|&s| s * 0.95).collect();
708 }
709
710 if self.config.lock_free {
712 }
715
716 let processing_time = start_time.elapsed().as_secs_f32() * 1000.0;
717 self.update_stats(processing_time, true);
718
719 Ok(RealtimeBuffer::new(
720 processed_data,
721 buffer.sample_rate,
722 buffer.channels,
723 ))
724 }
725
726 fn update_stats(&self, processing_time: f32, success: bool) {
728 if let Ok(mut stats) = self.stats.lock() {
729 stats.current_latency = processing_time;
730
731 stats.average_latency = (stats.average_latency * 0.9) + (processing_time * 0.1);
733
734 if processing_time > stats.peak_latency {
736 stats.peak_latency = processing_time;
737 }
738
739 let total_processed = stats.frames_processed + 1;
741 let successful_frames = if success {
742 (stats.success_rate * stats.frames_processed as f32) + 1.0
743 } else {
744 stats.success_rate * stats.frames_processed as f32
745 };
746 stats.success_rate = successful_frames / total_processed as f32;
747
748 stats.memory_usage = (self.config.buffer_size as f32 * 4.0) / (1024.0 * 1024.0) * 10.0;
750 }
751 }
752
753 fn adjust_latency_adaptively(&self, processing_time: f32) {
755 if let Ok(mut history) = self.latency_history.lock() {
756 history.push(processing_time);
757
758 if history.len() > 100 {
760 history.remove(0);
761 }
762
763 if history.len() >= 10 {
765 let recent_avg = history.iter().rev().take(10).sum::<f32>() / 10.0;
766 let overall_avg = history.iter().sum::<f32>() / history.len() as f32;
767
768 if recent_avg > overall_avg * 1.2 {
770 }
773 }
774 }
775 }
776
777 #[cfg(any(target_os = "linux", target_os = "macos"))]
780 fn is_jack_available() -> bool {
781 std::process::Command::new("jack_lsp")
784 .output()
785 .map(|output| output.status.success())
786 .unwrap_or(false)
787 }
788
789 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
790 fn is_jack_available() -> bool {
791 false
792 }
793
794 #[cfg(target_os = "windows")]
795 fn is_asio_available() -> bool {
796 true }
800
801 #[cfg(not(target_os = "windows"))]
802 fn is_asio_available() -> bool {
803 false
804 }
805
806 #[cfg(target_os = "linux")]
807 fn is_alsa_available() -> bool {
808 std::path::Path::new("/proc/asound/cards").exists()
810 }
811
812 #[cfg(not(target_os = "linux"))]
813 fn is_alsa_available() -> bool {
814 false
815 }
816
817 #[cfg(target_os = "linux")]
818 fn is_pulseaudio_available() -> bool {
819 std::process::Command::new("pulseaudio")
821 .arg("--check")
822 .output()
823 .map(|output| output.status.success())
824 .unwrap_or(false)
825 }
826
827 #[cfg(not(target_os = "linux"))]
828 fn is_pulseaudio_available() -> bool {
829 false
830 }
831}
832
833impl Drop for RealtimeLibraryManager {
834 fn drop(&mut self) {
835 let _ = self.shutdown();
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842
843 #[test]
844 fn test_realtime_config_creation() {
845 let config = RealtimeConfig::default();
846 assert_eq!(config.preferred_backend, AudioBackend::Auto);
847 assert_eq!(config.target_latency, 20.0);
848 assert_eq!(config.buffer_size, 512);
849 assert_eq!(config.sample_rate, 44100);
850 assert!(config.zero_copy);
851 assert!(config.adaptive_latency);
852 }
853
854 #[test]
855 fn test_realtime_config_builder() {
856 let config = RealtimeConfig::default()
857 .with_preferred_backend(AudioBackend::JACK)
858 .with_target_latency(10.0)
859 .with_buffer_size(256)
860 .with_sample_rate(48000)
861 .with_zero_copy(false)
862 .with_adaptive_latency(false);
863
864 assert_eq!(config.preferred_backend, AudioBackend::JACK);
865 assert_eq!(config.target_latency, 10.0);
866 assert_eq!(config.buffer_size, 256);
867 assert_eq!(config.sample_rate, 48000);
868 assert!(!config.zero_copy);
869 assert!(!config.adaptive_latency);
870 }
871
872 #[test]
873 fn test_audio_backend_types() {
874 let backends = vec![
875 AudioBackend::JACK,
876 AudioBackend::ASIO,
877 AudioBackend::PortAudio,
878 AudioBackend::ALSA,
879 AudioBackend::CoreAudio,
880 AudioBackend::PulseAudio,
881 AudioBackend::Auto,
882 ];
883
884 for backend in backends {
885 assert_ne!(format!("{:?}", backend), "");
886 }
887 }
888
889 #[test]
890 fn test_realtime_buffer_creation() {
891 let data = vec![0.1, 0.2, -0.1, 0.05];
892 let buffer = RealtimeBuffer::new(data.clone(), 44100, 2);
893
894 assert_eq!(buffer.data, data);
895 assert_eq!(buffer.sample_rate, 44100);
896 assert_eq!(buffer.channels, 2);
897 assert_eq!(buffer.samples_per_channel(), 2);
898 assert!(buffer.duration_ms() > 0.0);
899 }
900
901 #[test]
902 fn test_realtime_buffer_timing() {
903 let data = vec![0.1; 4410]; let buffer = RealtimeBuffer::new(data, 44100, 1);
905
906 let duration = buffer.duration_ms();
907 assert!((duration - 100.0).abs() < 1.0); assert!(buffer.is_within_latency(200.0)); }
911
912 #[test]
913 fn test_manager_creation() {
914 let config = RealtimeConfig::default();
915 let manager = RealtimeLibraryManager::new(config);
916
917 assert!(manager.is_ok());
918 let manager = manager.unwrap();
919 assert!(manager.active_backend.is_none());
920 assert!(!manager.backend_capabilities.is_empty());
921 }
922
923 #[test]
924 fn test_backend_capabilities_detection() {
925 let capabilities = RealtimeLibraryManager::detect_available_backends();
926
927 assert!(!capabilities.is_empty());
928 assert!(capabilities.contains_key(&AudioBackend::PortAudio)); for (backend, caps) in capabilities {
932 assert!(caps.min_latency > 0.0);
933 assert!(caps.max_latency > caps.min_latency);
934 assert!(!caps.supported_buffer_sizes.is_empty());
935 assert!(!caps.supported_sample_rates.is_empty());
936 assert!(caps.max_channels > 0);
937
938 println!(
939 "Backend {:?}: min_lat={:.1}ms, zero_copy={}, available={}",
940 backend, caps.min_latency, caps.zero_copy_support, caps.platform_available
941 );
942 }
943 }
944
945 #[test]
946 fn test_backend_scoring() {
947 let config = RealtimeConfig::default();
948 let manager = RealtimeLibraryManager::new(config).unwrap();
949
950 let test_caps = BackendCapabilities {
951 min_latency: 5.0,
952 max_latency: 100.0,
953 supported_buffer_sizes: vec![128, 256, 512],
954 supported_sample_rates: vec![44100, 48000],
955 max_channels: 8,
956 zero_copy_support: true,
957 lock_free_support: true,
958 platform_available: true,
959 };
960
961 let score = manager.calculate_backend_score(&test_caps);
962 assert!(score > 0.0);
963 assert!(score < 100.0);
964 }
965
966 #[test]
967 fn test_realtime_stats_default() {
968 let stats = RealtimeStats::default();
969
970 assert_eq!(stats.current_latency, 0.0);
971 assert_eq!(stats.average_latency, 0.0);
972 assert_eq!(stats.peak_latency, 0.0);
973 assert_eq!(stats.underruns, 0);
974 assert_eq!(stats.overruns, 0);
975 assert_eq!(stats.cpu_usage, 0.0);
976 assert_eq!(stats.memory_usage, 0.0);
977 assert_eq!(stats.frames_processed, 0);
978 assert_eq!(stats.success_rate, 0.0);
979 }
980
981 #[test]
982 fn test_processing_simulation() {
983 let config = RealtimeConfig::default().with_target_latency(50.0);
984 let manager = RealtimeLibraryManager::new(config).unwrap();
985
986 let audio = vec![0.1, 0.2, -0.1, 0.05];
987 let result = manager.process_realtime(&audio);
988
989 assert!(result.is_ok());
990 let processed = result.unwrap();
991 assert_eq!(processed.len(), audio.len());
992
993 for (original, processed) in audio.iter().zip(processed.iter()) {
995 assert!((processed - original * 0.95).abs() < 0.001);
996 }
997 }
998
999 #[test]
1000 fn test_stream_processing() {
1001 let config = RealtimeConfig::default();
1002 let manager = RealtimeLibraryManager::new(config).unwrap();
1003
1004 let audio_stream = vec![0.1; 1000]; let chunk_size = 256;
1006
1007 let result = manager.process_stream(&audio_stream, chunk_size);
1008 assert!(result.is_ok());
1009
1010 let processed = result.unwrap();
1011 assert_eq!(processed.len(), audio_stream.len());
1012 }
1013
1014 #[test]
1015 fn test_latency_validation() {
1016 let config = RealtimeConfig::default().with_target_latency(1.0); let manager = RealtimeLibraryManager::new(config).unwrap();
1018
1019 let large_audio = vec![0.1; 44100]; let result = manager.process_realtime(&large_audio);
1022
1023 match result {
1025 Ok(_) => {
1026 }
1028 Err(e) => {
1029 assert!(e.to_string().contains("latency"));
1031 }
1032 }
1033 }
1034}