gled-core 2.32.1

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! Names for audio input devices that a user can tell apart, from the
//! metadata cpal reports about them.

use cpal::{DeviceDescription, DeviceId, InterfaceType};
use std::collections::HashMap;

pub(crate) fn audio_device_labels(
    devices: &[(DeviceId, DeviceDescription)],
) -> Vec<(DeviceId, String)> {
    let mut duplicate_names = HashMap::<&str, usize>::new();
    for (_, description) in devices {
        *duplicate_names.entry(description.name()).or_default() += 1;
    }

    let preliminary_labels = devices
        .iter()
        .map(|(device_id, description)| {
            (
                device_id.clone(),
                format_audio_device_label(
                    description.name(),
                    description.manufacturer(),
                    description.address(),
                    description.interface_type(),
                    description.driver(),
                    &description.extended().collect::<Vec<_>>(),
                    duplicate_names[description.name()] > 1,
                ),
            )
        })
        .collect::<Vec<_>>();

    let mut duplicate_labels = HashMap::<String, usize>::new();
    for (_, label) in &preliminary_labels {
        *duplicate_labels.entry(label.clone()).or_default() += 1;
    }

    let mut duplicate_index = HashMap::<String, usize>::new();
    preliminary_labels
        .into_iter()
        .map(|(device_id, label)| {
            if duplicate_labels[&label] <= 1 {
                return (device_id, label);
            }

            let index = duplicate_index
                .entry(label.clone())
                .and_modify(|count| *count += 1)
                .or_insert(1);
            (device_id, format!("{} ({})", label, index))
        })
        .collect()
}

pub(crate) fn audio_device_label(_device_id: &DeviceId, description: &DeviceDescription) -> String {
    format_audio_device_label(
        description.name(),
        description.manufacturer(),
        description.address(),
        description.interface_type(),
        description.driver(),
        &description.extended().collect::<Vec<_>>(),
        false,
    )
}

fn format_audio_device_label(
    name: &str,
    manufacturer: Option<&str>,
    address: Option<&str>,
    interface_type: InterfaceType,
    driver: Option<&str>,
    extended: &[&str],
    duplicate_name: bool,
) -> String {
    let primary = preferred_primary_label(name, manufacturer, extended);

    let detail = label_detail(
        &primary,
        manufacturer,
        address,
        interface_type,
        driver,
        extended,
        duplicate_name,
    );

    if let Some(detail) = detail {
        format!("{} ({})", primary, detail)
    } else {
        primary
    }
}

fn preferred_primary_label(name: &str, manufacturer: Option<&str>, extended: &[&str]) -> String {
    let normalized_name = normalize_label(name);
    let best_extended = preferred_extended_line(extended, &normalized_name);

    if is_generic_device_name(&normalized_name) {
        if let Some(best_extended) = best_extended {
            return best_extended;
        }

        if let Some(manufacturer) = manufacturer.and_then(sanitize_metadata_line) {
            if manufacturer.to_lowercase().contains("audio") {
                return manufacturer;
            }
            return format!("{} Audio", manufacturer);
        }
    }

    normalized_name
}

fn label_detail(
    primary_label: &str,
    manufacturer: Option<&str>,
    address: Option<&str>,
    interface_type: InterfaceType,
    driver: Option<&str>,
    extended: &[&str],
    duplicate_name: bool,
) -> Option<String> {
    if duplicate_name {
        if let Some(manufacturer) =
            manufacturer
                .and_then(sanitize_metadata_line)
                .filter(|manufacturer| {
                    !primary_label
                        .to_lowercase()
                        .contains(&manufacturer.to_lowercase())
                })
        {
            return Some(manufacturer);
        }

        if let Some(detail) = secondary_extended_line(extended, primary_label) {
            return Some(detail);
        }

        if let Some(address) = address.and_then(pretty_address) {
            return Some(address);
        }

        if interface_type != InterfaceType::Unknown {
            return Some(interface_type.to_string());
        }

        if let Some(driver) = driver.and_then(sanitize_metadata_line) {
            return Some(driver);
        }
    }

    None
}

fn sanitize_metadata_line(text: &str) -> Option<String> {
    let normalized = normalize_label(text);
    if normalized.is_empty() {
        None
    } else {
        Some(normalized)
    }
}

fn preferred_extended_line(extended: &[&str], name: &str) -> Option<String> {
    extended
        .iter()
        .filter_map(|line| sanitize_metadata_line(line))
        .find(|line| is_useful_extended_line(line, name))
}

fn secondary_extended_line(extended: &[&str], primary_label: &str) -> Option<String> {
    extended
        .iter()
        .filter_map(|line| sanitize_metadata_line(line))
        .find(|line| {
            !line.eq_ignore_ascii_case(primary_label)
                && !line.eq_ignore_ascii_case("Direct hardware device without any conversions")
                && !line.eq_ignore_ascii_case("Default Audio Device")
        })
}

fn is_useful_extended_line(line: &str, name: &str) -> bool {
    !line.eq_ignore_ascii_case(name)
        && !line.eq_ignore_ascii_case("Direct hardware device without any conversions")
        && !line.eq_ignore_ascii_case("Default Audio Device")
}

fn is_generic_device_name(name: &str) -> bool {
    let lower = name.to_lowercase();
    lower == "default"
        || lower == "default audio device"
        || lower == "audio"
        || lower == "usb audio"
        || lower.starts_with("monitor of")
}

fn normalize_label(text: &str) -> String {
    text.split_whitespace().collect::<Vec<_>>().join(" ")
}

fn pretty_address(address: &str) -> Option<String> {
    let normalized = normalize_label(address);
    if normalized.is_empty() {
        return None;
    }

    if let Some(value) = normalized.strip_prefix("hw:") {
        let parts = value.split(',').collect::<Vec<_>>();
        if parts.len() == 2 {
            return Some(format!("Card {}, Device {}", parts[0], parts[1]));
        }
    }

    (normalized.len() <= 24).then_some(normalized)
}

#[cfg(test)]
mod tests {
    use super::*;
    use cpal::DeviceDescriptionBuilder;
    use std::str::FromStr;

    #[test]
    fn format_audio_device_label_prefers_manufacturer_and_address() {
        let label = format_audio_device_label(
            "USB Audio",
            Some("Focusrite"),
            Some("hw:2,0"),
            InterfaceType::Usb,
            Some("ALSA"),
            &[],
            true,
        );

        assert_eq!(label, "Focusrite Audio (Card 2, Device 0)");
    }

    #[test]
    fn format_audio_device_label_uses_pretty_address_for_duplicate_names() {
        let label = format_audio_device_label(
            "USB Audio",
            None,
            Some("hw:2,0"),
            InterfaceType::Unknown,
            None,
            &[],
            true,
        );

        assert_eq!(label, "USB Audio (Card 2, Device 0)");
    }

    #[test]
    fn format_audio_device_label_uses_driver_when_other_metadata_is_missing() {
        let label = format_audio_device_label(
            "USB Audio",
            None,
            None,
            InterfaceType::Unknown,
            Some("ALSA"),
            &[],
            true,
        );

        assert_eq!(label, "USB Audio (ALSA)");
    }

    #[test]
    fn format_audio_device_label_prefers_extended_human_description() {
        let description = DeviceDescriptionBuilder::new("USB Audio")
            .manufacturer("Focusrite Audio")
            .address("hw:2,0")
            .extended(vec![
                "USB Audio".to_string(),
                "Scarlett 2i2 USB Audio Interface".to_string(),
            ])
            .build();

        let label = format_audio_device_label(
            description.name(),
            description.manufacturer(),
            description.address(),
            description.interface_type(),
            description.driver(),
            &description.extended().collect::<Vec<_>>(),
            true,
        );

        assert_eq!(label, "Scarlett 2i2 USB Audio Interface (Focusrite Audio)");
    }

    #[test]
    fn audio_device_labels_add_number_suffix_for_identical_final_labels() {
        let devices = vec![
            (
                DeviceId::from_str("ALSA://device/1").expect("valid device id"),
                DeviceDescriptionBuilder::new("USB Audio").build(),
            ),
            (
                DeviceId::from_str("ALSA://device/2").expect("valid device id"),
                DeviceDescriptionBuilder::new("USB Audio").build(),
            ),
        ];

        let labels = audio_device_labels(&devices)
            .into_iter()
            .map(|(_, label)| label)
            .collect::<Vec<_>>();

        assert_eq!(labels, vec!["USB Audio (1)", "USB Audio (2)"]);
    }
}