Skip to main content

rustriff_lib/commands/
settings.rs

1use crate::domain::dto::audio_device_dto::AudioDeviceDto;
2use crate::services::audio_service::AudioService;
3use crate::services::device_service::DeviceService;
4use cpal::traits::DeviceTrait;
5use std::sync::Mutex;
6use tracing::info;
7
8/// Normalizes channel count to a supported value (1 or 2).
9///
10/// Some devices report unusual channel counts that can cause audio issues.
11/// This function ensures we use either mono (1) or stereo (2) channels.
12///
13/// # Arguments
14///
15/// * `channels` - The reported channel count from the device
16///
17/// # Returns
18///
19/// A normalized channel count (1 or 2)
20fn normalize_channels(channels: u16) -> u16 {
21    match channels {
22        1 => 1,
23        _ => 2, // Default to stereo for any other count
24    }
25}
26
27/// Retrieves a list of all available input devices.
28///
29/// Queries the [`DeviceService`] for all detected input devices and returns
30/// them as [`AudioDeviceDto`] objects suitable for frontend display and selection.
31///
32/// # Arguments
33///
34/// * `device_service` - The shared [`DeviceService`] state, accessed via Tauri's state management.
35///
36/// # Returns
37///
38/// A [`Vec`] of [`AudioDeviceDto`] representing available input devices.
39#[tauri::command]
40pub(crate) fn get_input_device_list(
41    device_service: tauri::State<DeviceService>,
42) -> Vec<AudioDeviceDto> {
43    device_service.get_input_devices()
44}
45
46/// Retrieves a list of all available output devices.
47///
48/// Queries the [`DeviceService`] for all detected output devices and returns
49/// them as [`AudioDeviceDto`] objects suitable for frontend display and selection.
50///
51/// # Arguments
52///
53/// * `device_service` - The shared [`DeviceService`] state, accessed via Tauri's state management.
54///
55/// # Returns
56///
57/// A [`Vec`] of [`AudioDeviceDto`] representing available output devices.
58#[tauri::command]
59pub(crate) fn get_output_device_list(
60    device_service: tauri::State<DeviceService>,
61) -> Vec<AudioDeviceDto> {
62    device_service.get_output_devices()
63}
64
65/// Switches the active input device.
66///
67/// Looks up the device by ID in the [`DeviceService`], then delegates to
68/// [`AudioService::set_input_device`] to perform the hot-swap without interrupting
69/// playback longer than necessary.
70///
71/// # Arguments
72///
73/// * `device_service` - The shared [`DeviceService`] state for device lookup.
74/// * `audio_service` - The shared [`AudioService`] state for performing the switch.
75/// * `device_id` - The ID of the input device to activate.
76///
77/// # Returns
78///
79/// Returns `Ok(())` on success, or `Err(String)` if the device ID is not found
80/// or the service state cannot be locked.
81#[tauri::command]
82pub fn set_input_device(
83    device_service: tauri::State<DeviceService>,
84    audio_service: tauri::State<'_, Mutex<AudioService>>,
85    device_id: String,
86) -> Result<(), String> {
87    let device = device_service
88        .find_input_device_by_id(&device_id)
89        .ok_or("Device not found")?;
90
91    let device_name = device
92        .description()
93        .map(|d| d.name().to_string())
94        .unwrap_or_else(|_| "Unknown".to_string());
95    let supported_config = device.default_input_config().map_err(|e| {
96        format!(
97            "Failed to get default input config for '{}': {}",
98            device_name, e
99        )
100    })?;
101
102    let mut input_config = supported_config.config();
103    let normalized_channels = normalize_channels(input_config.channels);
104
105    if input_config.channels != normalized_channels {
106        info!(
107            "Input device '{}' reported {} channels, normalizing to {}",
108            device_name, input_config.channels, normalized_channels
109        );
110        input_config.channels = normalized_channels;
111    }
112
113    info!(
114        "Switching to input device '{}' - {} ch @ {} Hz",
115        device_name, input_config.channels, input_config.sample_rate
116    );
117
118    let mut audio = audio_service
119        .lock()
120        .map_err(|_| "Failed to lock audio service".to_string())?;
121    audio.set_input_device(device, input_config);
122
123    Ok(())
124}
125
126/// Switches the active output device.
127///
128/// Looks up the device by ID in the [`DeviceService`], then delegates to
129/// [`AudioService::set_output_device`] to perform the hot-swap without interrupting
130/// playback longer than necessary.
131///
132/// # Arguments
133///
134/// * `device_service` - The shared [`DeviceService`] state for device lookup.
135/// * `audio_service` - The shared [`AudioService`] state for performing the switch.
136/// * `device_id` - The ID of the output device to activate.
137///
138/// # Returns
139///
140/// Returns `Ok(())` on success, or `Err(String)` if the device ID is not found
141/// or the service state cannot be locked.
142#[tauri::command]
143pub fn set_output_device(
144    device_service: tauri::State<DeviceService>,
145    audio_service: tauri::State<'_, Mutex<AudioService>>,
146    device_id: String,
147) -> Result<(), String> {
148    let device = device_service
149        .find_output_device_by_id(&device_id)
150        .ok_or("Device not found")?;
151
152    let device_name = device
153        .description()
154        .map(|d| d.name().to_string())
155        .unwrap_or_else(|_| "Unknown".to_string());
156    let supported_config = device.default_output_config().map_err(|e| {
157        format!(
158            "Failed to get default output config for '{}': {}",
159            device_name, e
160        )
161    })?;
162
163    let mut output_config = supported_config.config();
164    let normalized_channels = normalize_channels(output_config.channels);
165
166    if output_config.channels != normalized_channels {
167        info!(
168            "Output device '{}' reported {} channels, normalizing to {}",
169            device_name, output_config.channels, normalized_channels
170        );
171        output_config.channels = normalized_channels;
172    }
173
174    info!(
175        "Switching to output device '{}' - {} ch @ {} Hz",
176        device_name, output_config.channels, output_config.sample_rate
177    );
178
179    let mut audio = audio_service
180        .lock()
181        .map_err(|_| "Failed to lock audio service".to_string())?;
182    audio.set_output_device(device, output_config);
183
184    Ok(())
185}
186
187#[tauri::command]
188pub fn get_buffer_size_frames(
189    audio_service: tauri::State<'_, Mutex<AudioService>>,
190) -> Result<u32, String> {
191    let audio = audio_service
192        .lock()
193        .map_err(|_| "Failed to lock audio service".to_string())?;
194    Ok(audio.buffer_size_frames())
195}
196
197#[tauri::command]
198pub fn set_buffer_size_frames(
199    audio_service: tauri::State<'_, Mutex<AudioService>>,
200    frames: u32,
201) -> Result<(), String> {
202    let mut audio = audio_service
203        .lock()
204        .map_err(|_| "Failed to lock audio service".to_string())?;
205    audio.set_buffer_size_frames(frames)
206}