sources.rs (6684B)
1 //! The full catalog of input *sources* on the device — every signal that can be 2 //! metered, named, faded into the monitor mix, and routed: analogue preamps, 3 //! ADAT, S/PDIF, and the DAW (PCM) returns. 4 //! 5 //! The old Inputs page only listed the 8 analogue preamps, so ADAT/SPDIF/PCM 6 //! sources were invisible. This module is the single source of truth for "what 7 //! inputs exist and what each can do", built from the device descriptor. 8 9 use crate::model::{DeviceInfo, PortType}; 10 11 /// One input source: its kind, zero-based index within that kind, and which 12 /// preamp features it supports (only analogue preamps have these). 13 #[derive(Debug, Clone, PartialEq, Eq)] 14 pub struct Source { 15 pub kind: PortType, 16 /// 0-based index within the kind (analogue 0 = "Analogue 1"). 17 pub index: u16, 18 /// Default display name, e.g. "Analogue 1", "ADAT 3", "PCM 12". 19 pub name: String, 20 /// Preamp capabilities (false for everything except analogue preamps). 21 pub has_air: bool, 22 pub has_pad: bool, 23 pub has_inst: bool, 24 /// 48 V phantom group this source belongs to, if any (analogue only). 25 pub phantom_group: Option<u8>, 26 } 27 28 impl Source { 29 /// True if this source has any preamp switch worth showing. 30 pub fn has_preamp(&self) -> bool { 31 self.has_air || self.has_pad || self.has_inst || self.phantom_group.is_some() 32 } 33 34 /// The device hardware ID for this source (kind base | index), matching the 35 /// mux/routing encoding. Used to find where the source is routed. 36 pub fn hw_id(&self) -> u16 { 37 let base = match self.kind { 38 PortType::Analogue => 0x080, 39 PortType::Spdif => 0x180, 40 PortType::Adat => 0x200, 41 PortType::Mix => 0x300, 42 PortType::Pcm => 0x600, 43 }; 44 base | self.index 45 } 46 } 47 48 fn kind_word(kind: PortType) -> &'static str { 49 match kind { 50 PortType::Analogue => "Analogue", 51 PortType::Spdif => "S/PDIF", 52 PortType::Adat => "ADAT", 53 PortType::Pcm => "PCM", 54 PortType::Mix => "Mix", 55 } 56 } 57 58 /// Build the ordered source catalog for a device: analogue (incl. talkback as 59 /// the last analogue), then ADAT, S/PDIF, then PCM/DAW returns. 60 pub fn catalog(info: &DeviceInfo) -> Vec<Source> { 61 let mut out = Vec::new(); 62 63 // Analogue sources (the descriptor's analogue *source* count includes the 64 // talkback mic as the last one on the 18i20 g3). 65 let analogue_srcs = info.port_count(PortType::Analogue).0 as u16; 66 for i in 0..analogue_srcs { 67 let is_talkback = info.has_talkback && i + 1 == analogue_srcs; 68 let name = if is_talkback { 69 "Talkback".to_string() 70 } else { 71 format!("Analogue {}", i + 1) 72 }; 73 out.push(Source { 74 kind: PortType::Analogue, 75 index: i, 76 name, 77 has_air: i < info.air_input_count as u16 && !is_talkback, 78 has_pad: i < info.pad_input_count as u16 && !is_talkback, 79 has_inst: i < info.level_input_count as u16 && !is_talkback, 80 phantom_group: if !is_talkback && info.inputs_per_phantom > 0 { 81 let g = (i / info.inputs_per_phantom as u16) as u8; 82 (g < info.phantom_count).then_some(g) 83 } else { 84 None 85 }, 86 }); 87 } 88 89 // ADAT, then S/PDIF, then PCM — meter/route/name only, no preamp. 90 for kind in [PortType::Adat, PortType::Spdif, PortType::Pcm] { 91 let n = info.port_count(kind).0 as u16; 92 for i in 0..n { 93 out.push(Source { 94 kind, 95 index: i, 96 name: format!("{} {}", kind_word(kind), i + 1), 97 has_air: false, 98 has_pad: false, 99 has_inst: false, 100 phantom_group: None, 101 }); 102 } 103 } 104 105 out 106 } 107 108 /// A stereo pairing of catalog entries: consecutive odd/even within the same 109 /// kind (1-2, 3-4 …). An odd trailing source becomes a mono pair. 110 #[derive(Debug, Clone, PartialEq, Eq)] 111 pub struct Pair { 112 pub left: usize, 113 /// `None` for a leftover mono source (or talkback). 114 pub right: Option<usize>, 115 } 116 117 /// Group catalog indices into stereo pairs by kind. Talkback stays mono. 118 pub fn stereo_pairs(catalog: &[Source]) -> Vec<Pair> { 119 let mut pairs = Vec::new(); 120 let mut i = 0; 121 while i < catalog.len() { 122 let a = &catalog[i]; 123 let mono = a.name == "Talkback"; 124 if !mono 125 && i + 1 < catalog.len() 126 && catalog[i + 1].kind == a.kind 127 && a.index % 2 == 0 128 && catalog[i + 1].index == a.index + 1 129 { 130 pairs.push(Pair { left: i, right: Some(i + 1) }); 131 i += 2; 132 } else { 133 pairs.push(Pair { left: i, right: None }); 134 i += 1; 135 } 136 } 137 pairs 138 } 139 140 #[cfg(test)] 141 mod tests { 142 use super::*; 143 use crate::model::S18I20_GEN3; 144 145 #[test] 146 fn catalog_includes_adat_spdif_pcm_not_just_analogue() { 147 let c = catalog(&S18I20_GEN3); 148 let kinds: std::collections::HashSet<_> = c.iter().map(|s| s.kind).collect(); 149 assert!(kinds.contains(&PortType::Analogue)); 150 assert!(kinds.contains(&PortType::Adat)); // the user's missing inputs 151 assert!(kinds.contains(&PortType::Spdif)); 152 assert!(kinds.contains(&PortType::Pcm)); 153 // 9 analogue + 8 adat + 2 spdif + 20 pcm = 39 154 assert_eq!(c.len(), 9 + 8 + 2 + 20); 155 } 156 157 #[test] 158 fn talkback_is_last_analogue_and_has_no_preamp() { 159 let c = catalog(&S18I20_GEN3); 160 let tb = c.iter().find(|s| s.name == "Talkback").unwrap(); 161 assert_eq!(tb.kind, PortType::Analogue); 162 assert!(!tb.has_preamp()); 163 } 164 165 #[test] 166 fn analogue_1_has_full_preamp_adat_has_none() { 167 let c = catalog(&S18I20_GEN3); 168 let a1 = &c[0]; 169 assert_eq!(a1.name, "Analogue 1"); 170 assert!(a1.has_air && a1.has_pad && a1.has_inst); 171 assert_eq!(a1.phantom_group, Some(0)); 172 173 let adat = c.iter().find(|s| s.kind == PortType::Adat).unwrap(); 174 assert!(!adat.has_preamp()); 175 } 176 177 #[test] 178 fn stereo_pairs_group_odd_even_within_kind() { 179 let c = catalog(&S18I20_GEN3); 180 let pairs = stereo_pairs(&c); 181 // Analogue 1-2 should be a stereo pair. 182 let first = &pairs[0]; 183 assert_eq!(c[first.left].name, "Analogue 1"); 184 assert_eq!(first.right.map(|r| c[r].name.clone()), Some("Analogue 2".into())); 185 // Talkback (9th analogue, odd one out) must be mono. 186 let tb_pair = pairs.iter().find(|p| c[p.left].name == "Talkback").unwrap(); 187 assert_eq!(tb_pair.right, None); 188 } 189 }