1use crate::error::{IoError, IoResult};
13use crate::stream::{SignalStream, StreamConfig};
14use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
15use scirs2_core::ndarray::{Array1, Array2};
16use serde::{Deserialize, Serialize};
17use std::sync::{Arc, Mutex};
18use tracing::{debug, info, warn};
19
20#[cfg(feature = "file")]
21use crate::file::WavReader;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
25pub enum AudioBackend {
26 #[default]
28 Default,
29 #[cfg(target_os = "windows")]
31 Asio,
32 #[cfg(any(target_os = "linux", target_os = "macos"))]
34 Jack,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct AudioConfig {
40 pub device_name: Option<String>,
42
43 #[serde(default = "default_sample_rate")]
45 pub sample_rate: u32,
46
47 #[serde(default = "default_channels")]
49 pub channels: u16,
50
51 #[serde(default = "default_buffer_size")]
53 pub buffer_size: u32,
54
55 #[serde(default)]
57 pub output: bool,
58
59 #[serde(default)]
61 pub backend: AudioBackend,
62}
63
64fn default_sample_rate() -> u32 {
65 44100
66}
67
68fn default_channels() -> u16 {
69 1
70}
71
72fn default_buffer_size() -> u32 {
73 1024
74}
75
76impl Default for AudioConfig {
77 fn default() -> Self {
78 Self {
79 device_name: None,
80 sample_rate: 44100,
81 channels: 1,
82 buffer_size: 1024,
83 output: false,
84 backend: AudioBackend::Default,
85 }
86 }
87}
88
89impl AudioConfig {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn new_output() -> Self {
97 Self {
98 output: true,
99 ..Default::default()
100 }
101 }
102
103 pub fn sample_rate(mut self, rate: u32) -> Self {
105 self.sample_rate = rate;
106 self
107 }
108
109 pub fn channels(mut self, n: u16) -> Self {
111 self.channels = n;
112 self
113 }
114
115 pub fn buffer_size(mut self, size: u32) -> Self {
117 self.buffer_size = size;
118 self
119 }
120
121 pub fn device(mut self, name: &str) -> Self {
123 self.device_name = Some(name.to_string());
124 self
125 }
126
127 pub fn backend(mut self, backend: AudioBackend) -> Self {
129 self.backend = backend;
130 self
131 }
132}
133
134fn get_host(backend: AudioBackend) -> IoResult<cpal::Host> {
136 match backend {
137 AudioBackend::Default => Ok(cpal::default_host()),
138 #[cfg(target_os = "windows")]
139 AudioBackend::Asio => {
140 let available_hosts = cpal::available_hosts();
142 if available_hosts.contains(&cpal::HostId::Asio) {
143 Ok(cpal::host_from_id(cpal::HostId::Asio))
144 } else {
145 Err(IoError::ConfigError(
146 "ASIO backend not available. Ensure ASIO drivers are installed.".into(),
147 ))
148 }
149 }
150 #[cfg(any(target_os = "linux", target_os = "macos"))]
151 AudioBackend::Jack => {
152 info!("JACK support requested - using default host (ensure JACK is configured as default)");
156 Ok(cpal::default_host())
157 }
158 }
159}
160
161pub struct AudioInput {
163 config: StreamConfig,
164 audio_config: AudioConfig,
165 buffer: Arc<Mutex<Vec<f32>>>,
166 multi_channel_buffer: Arc<Mutex<Vec<Vec<f32>>>>,
167 #[allow(dead_code)]
168 stream: Option<cpal::Stream>,
169 active: bool,
170}
171
172impl AudioInput {
173 pub fn new(audio_config: AudioConfig) -> IoResult<Self> {
175 let stream_config = StreamConfig {
176 sample_rate: audio_config.sample_rate as f32,
177 channels: audio_config.channels as usize,
178 buffer_size: audio_config.buffer_size as usize,
179 timeout: None,
180 };
181
182 let num_channels = audio_config.channels as usize;
183
184 Ok(Self {
185 config: stream_config,
186 audio_config,
187 buffer: Arc::new(Mutex::new(Vec::new())),
188 multi_channel_buffer: Arc::new(Mutex::new(vec![Vec::new(); num_channels])),
189 stream: None,
190 active: false,
191 })
192 }
193
194 pub fn start(&mut self) -> IoResult<()> {
196 let host = get_host(self.audio_config.backend)?;
197
198 let device = if let Some(ref name) = self.audio_config.device_name {
199 host.input_devices()
200 .map_err(|e| IoError::ConfigError(e.to_string()))?
201 .find(|d| d.name().map(|n| n == *name).unwrap_or(false))
202 .ok_or_else(|| IoError::ConfigError(format!("Device not found: {}", name)))?
203 } else {
204 host.default_input_device()
205 .ok_or_else(|| IoError::ConfigError("No default input device".into()))?
206 };
207
208 let config = cpal::StreamConfig {
209 channels: self.audio_config.channels,
210 sample_rate: cpal::SampleRate(self.audio_config.sample_rate),
211 buffer_size: cpal::BufferSize::Fixed(self.audio_config.buffer_size),
212 };
213
214 info!(
215 "Starting audio input: {}Hz, {} channels, buffer={}",
216 self.audio_config.sample_rate,
217 self.audio_config.channels,
218 self.audio_config.buffer_size
219 );
220
221 let buffer = self.buffer.clone();
222 let multi_buffer = self.multi_channel_buffer.clone();
223 let num_channels = self.audio_config.channels as usize;
224
225 let stream = device
226 .build_input_stream(
227 &config,
228 move |data: &[f32], _: &cpal::InputCallbackInfo| {
229 if let Ok(mut buf) = buffer.lock() {
231 buf.extend_from_slice(data);
232 }
233
234 if let Ok(mut multi_buf) = multi_buffer.lock() {
236 for (i, &sample) in data.iter().enumerate() {
237 let channel = i % num_channels;
238 multi_buf[channel].push(sample);
239 }
240 }
241 },
242 |err| {
243 warn!("Audio stream error: {}", err);
244 },
245 None,
246 )
247 .map_err(|e| IoError::StreamError(e.to_string()))?;
248
249 stream
250 .play()
251 .map_err(|e| IoError::StreamError(e.to_string()))?;
252
253 self.stream = Some(stream);
254 self.active = true;
255
256 info!("Audio input started");
257 Ok(())
258 }
259
260 pub fn stop(&mut self) -> IoResult<()> {
262 self.stream = None;
263 self.active = false;
264 info!("Audio input stopped");
265 Ok(())
266 }
267
268 pub fn read_channels(&mut self) -> IoResult<Array2<f32>> {
270 let mut multi_buffer = self
271 .multi_channel_buffer
272 .lock()
273 .map_err(|_| IoError::StreamError("Buffer lock failed".into()))?;
274
275 let min_len = multi_buffer
277 .iter()
278 .map(|ch| ch.len())
279 .min()
280 .unwrap_or(0)
281 .min(self.config.buffer_size);
282
283 if min_len == 0 {
284 return Ok(Array2::zeros((
285 self.config.buffer_size,
286 self.config.channels,
287 )));
288 }
289
290 let mut result = Array2::zeros((min_len, self.config.channels));
291 for (ch_idx, channel_data) in multi_buffer.iter_mut().enumerate() {
292 let samples: Vec<f32> = channel_data.drain(..min_len).collect();
293 for (i, &sample) in samples.iter().enumerate() {
294 result[[i, ch_idx]] = sample;
295 }
296 }
297
298 debug!(
299 "Read {} frames from {} channels",
300 min_len, self.config.channels
301 );
302 Ok(result)
303 }
304
305 pub fn list_devices_with_backend(backend: AudioBackend) -> IoResult<Vec<String>> {
307 let host = get_host(backend)?;
308 let devices = host
309 .input_devices()
310 .map_err(|e| IoError::ConfigError(e.to_string()))?;
311
312 let names: Vec<String> = devices.filter_map(|d| d.name().ok()).collect();
313 Ok(names)
314 }
315
316 pub fn list_devices() -> IoResult<Vec<String>> {
318 Self::list_devices_with_backend(AudioBackend::Default)
319 }
320}
321
322impl SignalStream for AudioInput {
323 fn read(&mut self) -> IoResult<Array1<f32>> {
324 let mut buffer = self
325 .buffer
326 .lock()
327 .map_err(|_| IoError::StreamError("Buffer lock failed".into()))?;
328
329 let size = self.config.buffer_size.min(buffer.len());
330 if size == 0 {
331 return Ok(Array1::zeros(self.config.buffer_size));
332 }
333
334 let data: Vec<f32> = buffer.drain(..size).collect();
335 let mut result = Array1::zeros(self.config.buffer_size);
336 for (i, val) in data.into_iter().enumerate() {
337 result[i] = val;
338 }
339 Ok(result)
340 }
341
342 fn is_active(&self) -> bool {
343 self.active
344 }
345
346 fn config(&self) -> &StreamConfig {
347 &self.config
348 }
349
350 fn close(&mut self) -> IoResult<()> {
351 self.stop()
352 }
353}
354
355pub struct AudioOutput {
357 #[allow(dead_code)]
358 config: StreamConfig,
359 audio_config: AudioConfig,
360 buffer: Arc<Mutex<Vec<f32>>>,
361 #[allow(dead_code)]
362 stream: Option<cpal::Stream>,
363 active: bool,
364 underrun_count: Arc<Mutex<usize>>,
365}
366
367impl AudioOutput {
368 pub fn new(audio_config: AudioConfig) -> IoResult<Self> {
370 let stream_config = StreamConfig {
371 sample_rate: audio_config.sample_rate as f32,
372 channels: audio_config.channels as usize,
373 buffer_size: audio_config.buffer_size as usize,
374 timeout: None,
375 };
376
377 Ok(Self {
378 config: stream_config,
379 audio_config,
380 buffer: Arc::new(Mutex::new(Vec::new())),
381 stream: None,
382 active: false,
383 underrun_count: Arc::new(Mutex::new(0)),
384 })
385 }
386
387 pub fn start(&mut self) -> IoResult<()> {
389 let host = get_host(self.audio_config.backend)?;
390
391 let device = if let Some(ref name) = self.audio_config.device_name {
392 host.output_devices()
393 .map_err(|e| IoError::ConfigError(e.to_string()))?
394 .find(|d| d.name().map(|n| n == *name).unwrap_or(false))
395 .ok_or_else(|| IoError::ConfigError(format!("Device not found: {}", name)))?
396 } else {
397 host.default_output_device()
398 .ok_or_else(|| IoError::ConfigError("No default output device".into()))?
399 };
400
401 let config = cpal::StreamConfig {
402 channels: self.audio_config.channels,
403 sample_rate: cpal::SampleRate(self.audio_config.sample_rate),
404 buffer_size: cpal::BufferSize::Fixed(self.audio_config.buffer_size),
405 };
406
407 info!(
408 "Starting audio output: {}Hz, {} channels, buffer={}",
409 self.audio_config.sample_rate,
410 self.audio_config.channels,
411 self.audio_config.buffer_size
412 );
413
414 let buffer = self.buffer.clone();
415 let underrun_count = self.underrun_count.clone();
416
417 let stream = device
418 .build_output_stream(
419 &config,
420 move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
421 let mut buf = match buffer.lock() {
422 Ok(b) => b,
423 Err(_) => return,
424 };
425
426 if buf.len() >= data.len() {
427 for sample in data.iter_mut() {
429 *sample = buf.remove(0);
430 }
431 } else {
432 if let Ok(mut count) = underrun_count.lock() {
434 *count += 1;
435 }
436 for sample in data.iter_mut() {
437 *sample = 0.0;
438 }
439 }
440 },
441 |err| {
442 warn!("Audio output error: {}", err);
443 },
444 None,
445 )
446 .map_err(|e| IoError::StreamError(e.to_string()))?;
447
448 stream
449 .play()
450 .map_err(|e| IoError::StreamError(e.to_string()))?;
451
452 self.stream = Some(stream);
453 self.active = true;
454
455 info!("Audio output started");
456 Ok(())
457 }
458
459 pub fn stop(&mut self) -> IoResult<()> {
461 self.stream = None;
462 self.active = false;
463 info!("Audio output stopped");
464 Ok(())
465 }
466
467 pub fn write(&mut self, samples: &Array1<f32>) -> IoResult<()> {
469 let mut buffer = self
470 .buffer
471 .lock()
472 .map_err(|_| IoError::StreamError("Buffer lock failed".into()))?;
473
474 buffer.extend(samples.iter());
475 debug!("Wrote {} samples to output buffer", samples.len());
476 Ok(())
477 }
478
479 pub fn write_channels(&mut self, samples: &Array2<f32>) -> IoResult<()> {
481 let mut buffer = self
482 .buffer
483 .lock()
484 .map_err(|_| IoError::StreamError("Buffer lock failed".into()))?;
485
486 for row in samples.outer_iter() {
488 buffer.extend(row.iter());
489 }
490
491 debug!(
492 "Wrote {} frames from {} channels to output buffer",
493 samples.nrows(),
494 samples.ncols()
495 );
496 Ok(())
497 }
498
499 pub fn buffer_level(&self) -> usize {
501 self.buffer.lock().map(|b| b.len()).unwrap_or(0)
502 }
503
504 pub fn underrun_count(&self) -> usize {
506 self.underrun_count.lock().map(|c| *c).unwrap_or(0)
507 }
508
509 pub fn clear_buffer(&mut self) -> IoResult<()> {
511 let mut buffer = self
512 .buffer
513 .lock()
514 .map_err(|_| IoError::StreamError("Buffer lock failed".into()))?;
515
516 buffer.clear();
517 Ok(())
518 }
519
520 pub fn list_devices_with_backend(backend: AudioBackend) -> IoResult<Vec<String>> {
522 let host = get_host(backend)?;
523 let devices = host
524 .output_devices()
525 .map_err(|e| IoError::ConfigError(e.to_string()))?;
526
527 let names: Vec<String> = devices.filter_map(|d| d.name().ok()).collect();
528 Ok(names)
529 }
530
531 pub fn list_devices() -> IoResult<Vec<String>> {
533 Self::list_devices_with_backend(AudioBackend::Default)
534 }
535
536 #[cfg(feature = "file")]
538 pub async fn play_wav_file(&mut self, path: &str) -> IoResult<()> {
539 let reader = WavReader::open(path).await?;
540 let spec = reader.spec();
541
542 if spec.sample_rate != self.audio_config.sample_rate {
544 warn!(
545 "WAV sample rate ({}) differs from output config ({})",
546 spec.sample_rate, self.audio_config.sample_rate
547 );
548 }
549
550 if spec.channels != self.audio_config.channels {
551 return Err(IoError::ConfigError(format!(
552 "WAV channels ({}) differ from output config ({})",
553 spec.channels, self.audio_config.channels
554 )));
555 }
556
557 let samples = reader.read_all().await?;
559 self.write(&samples)?;
560
561 info!("Loaded WAV file: {} samples", samples.len());
562 Ok(())
563 }
564
565 pub fn is_active(&self) -> bool {
567 self.active
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn test_audio_config() {
577 let config = AudioConfig::new()
578 .sample_rate(48000)
579 .channels(2)
580 .buffer_size(2048);
581
582 assert_eq!(config.sample_rate, 48000);
583 assert_eq!(config.channels, 2);
584 assert_eq!(config.buffer_size, 2048);
585 assert!(!config.output);
586 }
587
588 #[test]
589 fn test_audio_config_output() {
590 let config = AudioConfig::new_output().sample_rate(44100).channels(1);
591
592 assert_eq!(config.sample_rate, 44100);
593 assert_eq!(config.channels, 1);
594 assert!(config.output);
595 }
596
597 #[test]
598 fn test_list_input_devices() {
599 let result = AudioInput::list_devices();
600 assert!(result.is_ok());
601 }
602
603 #[test]
604 fn test_list_output_devices() {
605 let result = AudioOutput::list_devices();
606 assert!(result.is_ok());
607 }
608}