fix(audio): expand ALSA bit-perfect detection for front:CARD devices

- Add front:CARD=X,DEV=Y device enumeration from aplay -L output
- Recognize front: devices as bit-perfect in AlsaDirectStream
- Group front:CARD and "bit-perfect" named devices correctly in UI
- Add tooltip to device dropdown showing full device name
- Add current selection indicator in device dropdown
- Extend dropdown idle timeout to 10 seconds for long device names

Addresses issue #30 for USB DACs that expose front: but not hw: devices
This commit is contained in:
vicrodh committed 2026-02-01 05:55:37 -06:00
1 parent 9680bbb9cd
commit 7a76973920
3 files changed
+83 -9

No files matched your search

+48
View File
@@ -256,6 +256,54 @@ impl AlsaBackend {
});
}
// Fifth: Add front:CARD=X,DEV=Y devices from aplay -L (issue #30)
// Some USB DACs (e.g., SMSL AD18) don't expose hw: devices but have working front: devices
// DeaDBeef and Audacious use these for bit-perfect playback
for (device_id, desc) in &description_map {
// Match front:CARD=X,DEV=Y pattern
if device_id.starts_with("front:CARD=") {
// Check if we already have this device
let already_exists = devices.iter().any(|d| d.name == *device_id);
if already_exists {
continue;
}
// Extract card name for friendly description
// front:CARD=AMP,DEV=0 -> "AMP"
let card_name = device_id
.strip_prefix("front:CARD=")
.and_then(|s| s.split(',').next())
.unwrap_or("Unknown");
// Use card_map description if available, otherwise use aplay -L description
let friendly_desc = card_map
.iter()
.find(|(_, card_desc)| {
// Match by card short name in the description
card_desc.to_lowercase().contains(&card_name.to_lowercase())
})
.map(|(_, card_desc)| card_desc.clone())
.unwrap_or_else(|| desc.clone());
log::info!(
"[ALSA Backend] Adding front: device for bit-perfect: {} ({})",
device_id,
friendly_desc
);
devices.push(AudioDevice {
id: device_id.clone(),
name: device_id.clone(),
description: Some(format!("{} (Direct Hardware - Bit-perfect)", friendly_desc)),
is_default: false,
max_sample_rate: Some(384000), // Assume high sample rate capability
supported_sample_rates: None,
device_bus: None,
is_hardware: true,
});
}
}
log::info!("[ALSA Backend] Enumerated {} ALSA devices", devices.len());
for (idx, dev) in devices.iter().enumerate() {
log::info!(
+6 -2
View File
@@ -285,9 +285,12 @@ impl AlsaDirectStream {
Err(format!("No volume control found for {}. DAC may not support hardware mixer.", self.device_id))
}
/// Check if device is hw: device
/// Check if device is a bit-perfect hardware device
/// Includes: hw:X,Y, plughw:X,Y, and front:CARD=X,DEV=Y
pub fn is_hw_device(device_id: &str) -> bool {
device_id.starts_with("hw:") || device_id.starts_with("plughw:")
device_id.starts_with("hw:")
|| device_id.starts_with("plughw:")
|| device_id.starts_with("front:CARD=")
}
}
@@ -317,6 +320,7 @@ impl AlsaDirectStream {
2
}
/// Check if device is a bit-perfect hardware device (always false on non-Linux)
pub fn is_hw_device(_device_id: &str) -> bool {
false
}