ports.rs (3969B)
1 //! Hardware port identity: decode the 12-bit port IDs used in mux (routing) 2 //! entries into a human-readable (port-type, index, name) — the data the kernel 3 //! driver carries in `scarlett2_ports[]`. 4 //! 5 //! Every routing assignment is `dest_id | (src_id << 12)` (see [`crate::matrix`]). 6 //! Each `*_id` is a 12-bit value `base | index`, where the bases are spaced far 7 //! enough apart that the type is recoverable by range — no direction-count math 8 //! needed, which makes this layer pure and fully testable. 9 10 /// A category of physical/virtual port, with its ID base and naming. 11 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 12 pub struct PortKind { 13 pub base: u16, 14 /// How a *source* of this kind is named (e.g. "Analogue", "Mix", "PCM"). 15 pub src_word: &'static str, 16 /// How a *sink* of this kind is named (e.g. "Analogue Output"). 17 pub sink_word: &'static str, 18 } 19 20 /// The 18i20 g3 port kinds, in mux-enumeration order (matches the kernel's 21 /// `scarlett2_ports[]`). Order matters: source flat-indexing walks these in turn. 22 pub const PORT_KINDS: &[PortKind] = &[ 23 PortKind { base: 0x000, src_word: "Off", sink_word: "Off" }, 24 PortKind { base: 0x080, src_word: "Analogue", sink_word: "Analogue Out" }, 25 PortKind { base: 0x180, src_word: "S/PDIF", sink_word: "S/PDIF Out" }, 26 PortKind { base: 0x200, src_word: "ADAT", sink_word: "ADAT Out" }, 27 PortKind { base: 0x300, src_word: "Mix", sink_word: "Mixer In" }, 28 PortKind { base: 0x600, src_word: "PCM", sink_word: "PCM" }, 29 ]; 30 31 /// Find the port kind whose ID range contains `id`, plus the index within it. 32 /// Returns `(kind, index)`. The ranges are the half-open spans between 33 /// consecutive bases (the last extends to 0xFFF). 34 fn locate(id: u16) -> Option<(&'static PortKind, u16)> { 35 let id = id & 0x0fff; 36 // NONE/Off is exactly the 0x000 region but only index 0 is meaningful. 37 let mut best: Option<&'static PortKind> = None; 38 for k in PORT_KINDS { 39 if id >= k.base { 40 best = Some(k); 41 } 42 } 43 best.map(|k| (k, id - k.base)) 44 } 45 46 /// Human name for a **source** hardware id (e.g. 0x088 → "Analogue 9", 47 /// 0x300 → "Mix A", 0x000 → "Off"). 48 pub fn source_name(id: u16) -> String { 49 name(id, true) 50 } 51 52 /// Human name for a **sink/destination** hardware id (e.g. 0x080 → 53 /// "Analogue Out 1", 0x30a → "Mixer In 11"). 54 pub fn sink_name(id: u16) -> String { 55 name(id, false) 56 } 57 58 fn name(id: u16, source: bool) -> String { 59 match locate(id) { 60 None => format!("?{id:#05x}"), 61 Some((k, idx)) => { 62 if k.base == 0x000 { 63 return "Off".to_string(); 64 } 65 let word = if source { k.src_word } else { k.sink_word }; 66 if k.base == 0x300 && source { 67 // Mix sources are lettered: Mix A, Mix B, … 68 let letter = (b'A' + (idx as u8 % 26)) as char; 69 format!("{word} {letter}") 70 } else { 71 format!("{word} {}", idx + 1) 72 } 73 } 74 } 75 } 76 77 #[cfg(test)] 78 mod tests { 79 use super::*; 80 81 #[test] 82 fn decodes_analogue_source_including_talkback() { 83 assert_eq!(source_name(0x080), "Analogue 1"); 84 assert_eq!(source_name(0x088), "Analogue 9"); // talkback mic 85 } 86 87 #[test] 88 fn decodes_off() { 89 assert_eq!(source_name(0x000), "Off"); 90 assert_eq!(sink_name(0x000), "Off"); 91 } 92 93 #[test] 94 fn decodes_mix_sources_as_letters() { 95 assert_eq!(source_name(0x300), "Mix A"); 96 assert_eq!(source_name(0x309), "Mix J"); 97 } 98 99 #[test] 100 fn decodes_pcm_and_adat_and_spdif() { 101 assert_eq!(source_name(0x600), "PCM 1"); 102 assert_eq!(source_name(0x200), "ADAT 1"); 103 assert_eq!(source_name(0x181), "S/PDIF 2"); 104 } 105 106 #[test] 107 fn decodes_sinks() { 108 assert_eq!(sink_name(0x080), "Analogue Out 1"); 109 assert_eq!(sink_name(0x30a), "Mixer In 11"); 110 assert_eq!(sink_name(0x600), "PCM 1"); 111 } 112 }