sonar/audio/
mod.rs

1// src/audio/mod.rs
2
3use cpal::traits::{DeviceTrait, HostTrait};
4use dev_utils::{format::*, read_input};
5use std::{error::Error, time::Duration};
6
7// * mod.rs
8pub mod capture;
9pub mod config;
10pub mod playback;
11pub mod signal;
12
13pub fn list_audio_devices() -> Result<(), Box<dyn std::error::Error>> {
14    let host = cpal::default_host();
15
16    println!("Input devices:");
17    let input_devices = host.input_devices()?;
18    for (i, device) in input_devices.enumerate() {
19        println!("{}: {}", i, device.name()?);
20    }
21
22    println!("Output devices:");
23    let output_devices = host.output_devices()?;
24    for (i, device) in output_devices.enumerate() {
25        println!("{}: {}", i, device.name()?);
26    }
27
28    Ok(())
29}
30
31pub fn select_device(input: bool) -> Result<cpal::Device, Box<dyn Error>> {
32    let host = cpal::default_host();
33    let mut devices = if input {
34        host.input_devices()?
35    } else {
36        host.output_devices()?
37    };
38
39    println!(
40        "\n{}",
41        format!(
42            "Available {} Devices:",
43            if input { "Input" } else { "Output" }
44        )
45        .color(BLUE)
46        .style(Style::Bold)
47    );
48
49    let device_list: Vec<_> = devices.collect();
50    for (idx, device) in device_list.iter().enumerate() {
51        println!(
52            "{}. {}",
53            idx.to_string().color(GREEN),
54            device.name().unwrap_or_default().color(WHITE)
55        );
56    }
57
58    loop {
59        let choice = read_input::<usize>(Some("Select device number: "))?;
60        if choice < device_list.len() {
61            return Ok(device_list[choice].clone());
62        }
63        println!("Invalid selection. Try again.");
64    }
65}
66
67// ? FORMAT RELATED FUNCTIONS
68pub fn interpolate_color(value: f32, min: f32, max: f32) -> Color {
69    let t = ((value - min) / (max - min)).clamp(0.0, 1.0);
70
71    let colors = [
72        (0.0, (0, 0, 255)),   // Blue
73        (0.3, (0, 255, 0)),   // Green
74        (0.6, (255, 255, 0)), // Yellow
75        (1.0, (255, 0, 0)),   // Red
76    ];
77
78    let mut color1 = colors[0];
79    let mut color2 = colors[1];
80
81    for window in colors.windows(2) {
82        if t >= window[0].0 && t <= window[1].0 {
83            color1 = window[0];
84            color2 = window[1];
85            break;
86        }
87    }
88
89    let factor = (t - color1.0) / (color2.0 - color1.0);
90
91    let r = (color1.1.0 as f32 * (1.0 - factor) + color2.1.0 as f32 * factor) as u8;
92    let g = (color1.1.1 as f32 * (1.0 - factor) + color2.1.1 as f32 * factor) as u8;
93    let b = (color1.1.2 as f32 * (1.0 - factor) + color2.1.2 as f32 * factor) as u8;
94
95    Color::from((r, g, b))
96}
97
98pub fn format_time(duration: Duration) -> String {
99    let total_secs = duration.as_secs();
100    let hours = total_secs / 3600;
101    let minutes = (total_secs % 3600) / 60;
102    let seconds = total_secs % 60;
103    let millis = duration.subsec_millis();
104
105    format!("[{:02}:{:02}:{:02}.{:03}]", hours, minutes, seconds, millis)
106        .style(Style::Dim)
107        .style(Style::Italic)
108}
109
110pub fn create_gradient_meter(value: f32, width: usize, peak_pos: Option<usize>) -> String {
111    let meter_width = (value * width as f32 * 2.0).min(width as f32) as usize;
112    let mut meter = String::with_capacity(width * 3);
113
114    for i in 0..width {
115        if i < meter_width {
116            let segment_value = i as f32 / width as f32;
117            let color = interpolate_color(segment_value, 0.0, 1.0);
118            meter.push_str(&"█".color(color));
119        } else if Some(i) == peak_pos {
120            meter.push_str(&"╎".color(WHITE).style(Style::Bold)); // Peak indicator
121        } else {
122            meter.push(' ');
123        }
124    }
125    format!("│{}│", meter)
126}
127
128pub fn format_signal_value(value: f32) -> String {
129    format!("{:>10.8}", value)
130        .color(interpolate_color(value, 0.0, 0.1))
131        .style(Style::Bold)
132        .to_string()
133}