controls.rs (10299B)
1 //! High-level, named controls built on the config-offset map ([`crate::model`]) 2 //! and the raw primitives ([`crate::protocol`]). This is the layer the TUI talks 3 //! to: "turn on Air for input 3" rather than "write 1 at offset 0x8f, activate 8". 4 //! 5 //! Every setter writes the value then sends the parameter's activation code, so 6 //! the change takes effect immediately (the device's two-step set/activate). 7 8 use crate::model::{Param, S18I20_GEN3}; 9 use crate::protocol::Scarlett; 10 use crate::transport::{Transport, TransportError}; 11 12 /// The device stores signed monitor volume in dB as `dB + VOLUME_BIAS`, giving 13 /// a 0..=127 range where 127 = 0 dB (unity) and 0 = -127 dB (≈ off). 14 pub const VOLUME_BIAS: i16 = 127; 15 16 /// The two monitor hardware buttons, in device index order (`DIM_MUTE` config). 17 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 18 pub enum MonitorButton { 19 Mute = 0, 20 Dim = 1, 21 } 22 23 /// The per-channel boolean switches on the input strip. 24 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 25 pub enum InputSwitch { 26 /// Air (ISA-preamp emulation). Per input (8). 27 Air, 28 /// 10 dB pad. Per input (8). 29 Pad, 30 /// Inst (vs Line) level/impedance. Per input (first 2). 31 Inst, 32 } 33 34 impl InputSwitch { 35 fn param(self) -> Param { 36 match self { 37 InputSwitch::Air => Param::AirSwitch, 38 InputSwitch::Pad => Param::PadSwitch, 39 InputSwitch::Inst => Param::LevelSwitch, 40 } 41 } 42 43 /// How many input channels this switch applies to on the 18i20 g3. 44 pub fn channel_count(self) -> u8 { 45 match self { 46 InputSwitch::Air => S18I20_GEN3.air_input_count, 47 InputSwitch::Pad => S18I20_GEN3.pad_input_count, 48 InputSwitch::Inst => S18I20_GEN3.level_input_count, 49 } 50 } 51 } 52 53 /// A snapshot of the monitor/output section. 54 #[derive(Debug, Clone, Default, PartialEq, Eq)] 55 pub struct MonitorState { 56 /// Master monitor level in dB (0 = unity). 57 pub master_db: i16, 58 pub mute: bool, 59 pub dim: bool, 60 } 61 62 /// A snapshot of the whole input strip, as read from the device. 63 #[derive(Debug, Clone, Default, PartialEq, Eq)] 64 pub struct InputState { 65 pub air: Vec<bool>, 66 pub pad: Vec<bool>, 67 pub inst: Vec<bool>, 68 /// Per phantom *group* (2 groups of 4 inputs each on the 18i20 g3). 69 pub phantom: Vec<bool>, 70 } 71 72 impl<T: Transport> Scarlett<T> { 73 /// Read a single byte-sized switch for `channel` (0-based). 74 pub fn get_input_switch( 75 &mut self, 76 switch: InputSwitch, 77 channel: u8, 78 ) -> Result<bool, TransportError> { 79 let cfg = switch.param().config(); 80 let off = cfg.channel_offset(channel as u32); 81 let data = self.get_data(off, cfg.byte_width())?; 82 Ok(data.first().is_some_and(|&b| b != 0)) 83 } 84 85 /// Set a byte-sized switch for `channel`, then activate it. 86 pub fn set_input_switch( 87 &mut self, 88 switch: InputSwitch, 89 channel: u8, 90 on: bool, 91 ) -> Result<(), TransportError> { 92 let cfg = switch.param().config(); 93 let off = cfg.channel_offset(channel as u32); 94 self.set_data(off, cfg.byte_width(), on as u32)?; 95 self.activate(cfg.activate as u32) 96 } 97 98 /// Read a 48V phantom *group* (0-based). Phantom is bit-sized, one byte per 99 /// group on this model. 100 pub fn get_phantom(&mut self, group: u8) -> Result<bool, TransportError> { 101 let cfg = Param::PhantomSwitch.config(); 102 let off = cfg.channel_offset(group as u32); 103 let data = self.get_data(off, 1)?; 104 Ok(data.first().is_some_and(|&b| b != 0)) 105 } 106 107 /// Set a 48V phantom group, then activate it. 108 pub fn set_phantom(&mut self, group: u8, on: bool) -> Result<(), TransportError> { 109 let cfg = Param::PhantomSwitch.config(); 110 let off = cfg.channel_offset(group as u32); 111 self.set_data(off, 1, on as u32)?; 112 self.activate(cfg.activate as u32) 113 } 114 115 // ---- Monitor / output section ------------------------------------------- 116 117 /// Read the master monitor volume as dB (signed; 0 = unity, negative = quieter). 118 /// `MASTER_VOLUME` is read-only — it tracks the hardware monitor knob. 119 pub fn get_master_volume_db(&mut self) -> Result<i16, TransportError> { 120 let cfg = Param::MasterVolume.config(); 121 let data = self.get_data(cfg.offset as u32, 2)?; 122 let raw = i16::from_le_bytes([ 123 *data.first().unwrap_or(&0), 124 *data.get(1).unwrap_or(&0), 125 ]); 126 Ok(raw) 127 } 128 129 /// Read a monitor button (Mute or Dim) state. 130 pub fn get_monitor_button(&mut self, btn: MonitorButton) -> Result<bool, TransportError> { 131 let cfg = Param::DimMute.config(); 132 let off = cfg.offset as u32 + btn as u32; 133 let data = self.get_data(off, 1)?; 134 Ok(data.first().is_some_and(|&b| b != 0)) 135 } 136 137 /// Set a monitor button (Mute or Dim), then activate it. 138 pub fn set_monitor_button( 139 &mut self, 140 btn: MonitorButton, 141 on: bool, 142 ) -> Result<(), TransportError> { 143 let cfg = Param::DimMute.config(); 144 let off = cfg.offset as u32 + btn as u32; 145 self.set_data(off, 1, on as u32)?; 146 self.activate(cfg.activate as u32) 147 } 148 149 /// A snapshot of the monitor section for a UI refresh. 150 pub fn read_monitor_state(&mut self) -> Result<MonitorState, TransportError> { 151 Ok(MonitorState { 152 master_db: self.get_master_volume_db().unwrap_or(0), 153 mute: self.get_monitor_button(MonitorButton::Mute).unwrap_or(false), 154 dim: self.get_monitor_button(MonitorButton::Dim).unwrap_or(false), 155 }) 156 } 157 158 /// Read the entire input strip in one call (for a UI refresh). 159 pub fn read_input_state(&mut self) -> Result<InputState, TransportError> { 160 let mut s = InputState::default(); 161 for ch in 0..InputSwitch::Air.channel_count() { 162 s.air.push(self.get_input_switch(InputSwitch::Air, ch)?); 163 } 164 for ch in 0..InputSwitch::Pad.channel_count() { 165 s.pad.push(self.get_input_switch(InputSwitch::Pad, ch)?); 166 } 167 for ch in 0..InputSwitch::Inst.channel_count() { 168 s.inst.push(self.get_input_switch(InputSwitch::Inst, ch)?); 169 } 170 for g in 0..S18I20_GEN3.phantom_count { 171 s.phantom.push(self.get_phantom(g)?); 172 } 173 Ok(s) 174 } 175 } 176 177 #[cfg(test)] 178 mod tests { 179 use super::*; 180 use crate::protocol::op; 181 use crate::transport::mock::MockTransport; 182 183 #[test] 184 fn set_air_writes_offset_then_activates() { 185 let mut m = MockTransport::new(); 186 m.push_response(op::SET_DATA, &[]); // the write 187 m.push_response(op::DATA_CMD, &[]); // the activate 188 let mut dev = Scarlett::new(m); 189 190 // Air on input 2 (0-based) -> offset 0x8c + 2 = 0x8e, value 1, activate 8. 191 dev.set_input_switch(InputSwitch::Air, 2, true).unwrap(); 192 193 let m = dev.into_transport(); 194 assert_eq!(m.sent[0].0, op::SET_DATA); 195 // offset 0x8e LE, size 1 LE, value 1 196 assert_eq!(m.sent[0].1, vec![0x8e, 0, 0, 0, 1, 0, 0, 0, 1]); 197 assert_eq!(m.sent[1], (op::DATA_CMD, 8u32.to_le_bytes().to_vec())); 198 } 199 200 #[test] 201 fn get_air_reads_byte_as_bool() { 202 let mut m = MockTransport::new(); 203 m.push_response(op::GET_DATA, &[1]); 204 let mut dev = Scarlett::new(m); 205 assert!(dev.get_input_switch(InputSwitch::Air, 0).unwrap()); 206 207 let mut m2 = MockTransport::new(); 208 m2.push_response(op::GET_DATA, &[0]); 209 let mut dev2 = Scarlett::new(m2); 210 assert!(!dev2.get_input_switch(InputSwitch::Air, 0).unwrap()); 211 } 212 213 #[test] 214 fn phantom_group_offsets_and_activate() { 215 let mut m = MockTransport::new(); 216 m.push_response(op::SET_DATA, &[]); 217 m.push_response(op::DATA_CMD, &[]); 218 let mut dev = Scarlett::new(m); 219 220 // group 1 -> offset 0x9c + 1 = 0x9d, activate 8 221 dev.set_phantom(1, true).unwrap(); 222 223 let m = dev.into_transport(); 224 assert_eq!(m.sent[0].1, vec![0x9d, 0, 0, 0, 1, 0, 0, 0, 1]); 225 assert_eq!(m.sent[1], (op::DATA_CMD, 8u32.to_le_bytes().to_vec())); 226 } 227 228 #[test] 229 fn monitor_button_offsets_and_activate() { 230 let mut m = MockTransport::new(); 231 m.push_response(op::SET_DATA, &[]); 232 m.push_response(op::DATA_CMD, &[]); 233 let mut dev = Scarlett::new(m); 234 235 // Dim = index 1 -> offset 0x31 + 1 = 0x32, activate 2 236 dev.set_monitor_button(MonitorButton::Dim, true).unwrap(); 237 238 let m = dev.into_transport(); 239 assert_eq!(m.sent[0].1, vec![0x32, 0, 0, 0, 1, 0, 0, 0, 1]); 240 assert_eq!(m.sent[1], (op::DATA_CMD, 2u32.to_le_bytes().to_vec())); 241 } 242 243 #[test] 244 fn master_volume_reads_signed_db() { 245 let mut m = MockTransport::new(); 246 // -6 dB as i16 LE 247 m.push_response(op::GET_DATA, &(-6i16).to_le_bytes()); 248 let mut dev = Scarlett::new(m); 249 assert_eq!(dev.get_master_volume_db().unwrap(), -6); 250 } 251 252 #[test] 253 fn read_monitor_state_collects_vol_mute_dim() { 254 let mut m = MockTransport::new(); 255 m.push_response(op::GET_DATA, &0i16.to_le_bytes()); // master 0 dB 256 m.push_response(op::GET_DATA, &[1]); // mute on 257 m.push_response(op::GET_DATA, &[0]); // dim off 258 let mut dev = Scarlett::new(m); 259 let s = dev.read_monitor_state().unwrap(); 260 assert_eq!(s.master_db, 0); 261 assert!(s.mute); 262 assert!(!s.dim); 263 } 264 265 #[test] 266 fn read_input_state_fills_all_channels() { 267 let mut m = MockTransport::new(); 268 // 8 air + 8 pad + 2 inst + 2 phantom = 20 GET_DATA responses 269 for _ in 0..8 { 270 m.push_response(op::GET_DATA, &[0]); 271 } 272 for _ in 0..8 { 273 m.push_response(op::GET_DATA, &[1]); // all pads on 274 } 275 for _ in 0..2 { 276 m.push_response(op::GET_DATA, &[0]); 277 } 278 for _ in 0..2 { 279 m.push_response(op::GET_DATA, &[1]); // both phantom groups on 280 } 281 let mut dev = Scarlett::new(m); 282 283 let s = dev.read_input_state().unwrap(); 284 assert_eq!(s.air.len(), 8); 285 assert_eq!(s.pad, vec![true; 8]); 286 assert_eq!(s.inst, vec![false, false]); 287 assert_eq!(s.phantom, vec![true, true]); 288 } 289 }