matrix.rs (23136B)
1 //! The mixer matrix, routing matrix (mux), and metering — the three parts of the 2 //! device that aren't simple config offsets. Request/response framing transcribed 3 //! from the kernel driver and confirmed against the gen3 device. 4 //! 5 //! - **Mixer** (`GET_MIX`/`SET_MIX`): per mix bus, a `u16` gain for each input. 6 //! - **Routing** (`GET_MUX`/`SET_MUX`): a flat list of `u32` entries, each packing 7 //! `destination | (source << 12)` — destination in the low 12 bits, source in 8 //! the high 12 (confirmed against the kernel's `populate_mux`/`set_mux`). 9 //! - **Meters** (`GET_METER`): a snapshot of `u32` levels, one per metering point. 10 11 use crate::protocol::{op, Scarlett}; 12 use crate::transport::{Transport, TransportError}; 13 14 /// Mixer crosspoint gain range, in dB (−80 … +12, matching the device). 15 pub const MIXER_MIN_DB: f32 = -80.0; 16 pub const MIXER_MAX_DB: f32 = 12.0; 17 /// Raw mixer value that means 0 dB (unity). The device's curve is 18 /// `value = 8192 · 10^(dB/20)` (confirmed from the kernel's generating formula). 19 const MIXER_UNITY: f32 = 8192.0; 20 21 /// Convert a raw mixer value (as returned by [`Scarlett::get_mix`]) to dB, 22 /// clamped to the device's range. 0 → silence floor (`MIXER_MIN_DB`). 23 pub fn mixer_value_to_db(value: u16) -> f32 { 24 if value == 0 { 25 return MIXER_MIN_DB; 26 } 27 (20.0 * (value as f32 / MIXER_UNITY).log10()).clamp(MIXER_MIN_DB, MIXER_MAX_DB) 28 } 29 30 /// Convert a dB gain to the raw mixer value for [`Scarlett::set_mix`]. 31 pub fn db_to_mixer_value(db: f32) -> u16 { 32 let db = db.clamp(MIXER_MIN_DB, MIXER_MAX_DB); 33 (MIXER_UNITY * 10f32.powf(db / 20.0)) as u16 34 } 35 36 /// Bit shift separating source (high bits) from destination (low bits). 37 const MUX_SRC_SHIFT: u32 = 12; 38 /// Mask for the destination field (low 12 bits). 39 const MUX_DST_MASK: u32 = (1 << MUX_SRC_SHIFT) - 1; 40 41 /// One routing assignment: which `source` feeds which `dest` (sink). Both are 42 /// device-internal hardware port IDs. 43 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 44 pub struct MuxEntry { 45 pub source: u16, 46 pub dest: u16, 47 } 48 49 impl MuxEntry { 50 /// Pack into the device's `dest | (source << 12)` u32. 51 pub fn pack(self) -> u32 { 52 (self.dest as u32 & MUX_DST_MASK) | ((self.source as u32) << MUX_SRC_SHIFT) 53 } 54 55 /// Unpack a device u32 into source/dest. 56 pub fn unpack(raw: u32) -> Self { 57 MuxEntry { 58 dest: (raw & MUX_DST_MASK) as u16, 59 source: (raw >> MUX_SRC_SHIFT) as u16, 60 } 61 } 62 } 63 64 /// A set of output channels that can be monitored "via the mixer" so a software 65 /// fader controls their level. Each channel `i` (0..count) uses output hardware 66 /// id `out_id_base+i`, mix bus `bus_base+i`, mixer-input `mix_in_base+i`, fed by 67 /// PCM `pcm_base+i`. Bases are chosen so groups don't collide on buses/inputs. 68 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 69 pub struct MonitorGroup { 70 pub name: &'static str, 71 pub out_id_base: u16, // hardware id of first output (e.g. 0x080 analogue, 0x200 ADAT) 72 pub bus_base: u16, // first mix bus index (0-based) 73 pub mix_in_base: u16, // first mixer-input index (0-based) 74 pub pcm_base: u16, // first PCM source index (0-based; matches the DAW channel) 75 pub count: u16, // number of channels 76 } 77 78 /// The 18i20 g3 monitor groups. Analogue 1-2 on buses 0-1 / mixer-in 0-1 / PCM 79 /// 1-2; ADAT 1-8 on buses 2-9 / mixer-in 2-9 / PCM 13-20 (the user's ADAT 80 /// monitoring path). Non-overlapping so both can be active at once (10 of 12 81 /// buses, 10 of 25 mixer-inputs). 82 pub const MONITOR_GROUPS: &[MonitorGroup] = &[ 83 MonitorGroup { 84 name: "Analogue 1-2", 85 out_id_base: 0x080, 86 bus_base: 0, 87 mix_in_base: 0, 88 pcm_base: 0, // PCM 1-2 89 count: 2, 90 }, 91 MonitorGroup { 92 name: "ADAT 1-8", 93 out_id_base: 0x200, 94 bus_base: 2, 95 mix_in_base: 2, 96 pcm_base: 12, // PCM 13-20 97 count: 8, 98 }, 99 ]; 100 101 /// Convenience operations layered on a connected [`Scarlett`]. 102 impl<T: Transport> Scarlett<T> { 103 /// Read the input gains feeding mix bus `mix_num`. Returns one raw `u16` 104 /// mixer value per input (`num_inputs`); convert to dB with the device's 105 /// mixer curve at the UI layer. 106 pub fn get_mix(&mut self, mix_num: u16, num_inputs: usize) -> Result<Vec<u16>, TransportError> { 107 // Request is { mix_num: u16, count: u16 } (the kernel sends both; sending 108 // only mix_num gets device error 0x9). 109 let mut req = Vec::with_capacity(4); 110 req.extend_from_slice(&mix_num.to_le_bytes()); 111 req.extend_from_slice(&(num_inputs as u16).to_le_bytes()); 112 let resp = self.command(op::GET_MIX, &req, num_inputs * 2)?; 113 Ok(le_u16s(&resp.payload, num_inputs)) 114 } 115 116 /// Set every input gain for mix bus `mix_num` at once (raw mixer `u16`s). 117 pub fn set_mix(&mut self, mix_num: u16, levels: &[u16]) -> Result<(), TransportError> { 118 let mut payload = Vec::with_capacity(2 + levels.len() * 2); 119 payload.extend_from_slice(&mix_num.to_le_bytes()); 120 for &l in levels { 121 payload.extend_from_slice(&l.to_le_bytes()); 122 } 123 self.command(op::SET_MIX, &payload, 0)?; 124 Ok(()) 125 } 126 127 /// Route a monitor group's outputs THROUGH the mixer so their level is 128 /// controllable by a software fader: for each channel `i`, 129 /// `MixerIn(mix_in_base+i) ← PCM(pcm_base+i)` and `Out(out_hw+i) ← Mix(bus_base+i)`. 130 /// With [`Self::set_group_level`], the buses' gains are the group's volume. 131 /// One atomic routing write. See [`MonitorGroup`]. 132 pub fn route_group_via_mixer( 133 &mut self, 134 pc: [(u16, u16); 6], 135 g: &MonitorGroup, 136 ) -> Result<crate::mux::MuxState, TransportError> { 137 use crate::mux::{id_to_num, Dir}; 138 let n = |dir, id| id_to_num(&pc, dir, id).unwrap_or(0); 139 let mut changes = Vec::with_capacity(g.count as usize * 2); 140 for i in 0..g.count { 141 // MixerIn(base+i) ← PCM(pcm_base+i) 142 changes.push(( 143 n(Dir::Out, 0x300 + g.mix_in_base + i), 144 n(Dir::In, 0x600 + g.pcm_base + i), 145 )); 146 // Out(base+i) ← Mix(bus_base+i) 147 changes.push(( 148 n(Dir::Out, g.out_id_base + i), 149 n(Dir::In, 0x300 + g.bus_base + i), 150 )); 151 } 152 self.set_routes(pc, &changes) 153 } 154 155 /// Route a group's outputs straight from the DAW again 156 /// (`Out(out_hw+i) ← PCM(pcm_base+i)`), undoing [`Self::route_group_via_mixer`]. 157 pub fn route_group_direct( 158 &mut self, 159 pc: [(u16, u16); 6], 160 g: &MonitorGroup, 161 ) -> Result<crate::mux::MuxState, TransportError> { 162 use crate::mux::{id_to_num, Dir}; 163 let n = |dir, id| id_to_num(&pc, dir, id).unwrap_or(0); 164 let changes: Vec<(u16, u16)> = (0..g.count) 165 .map(|i| (n(Dir::Out, g.out_id_base + i), n(Dir::In, 0x600 + g.pcm_base + i))) 166 .collect(); 167 self.set_routes(pc, &changes) 168 } 169 170 /// Set a group's level (dB): each of the group's buses passes only its own 171 /// mixer-input at `db`; all other inputs on those buses are silenced for a 172 /// clean monitor path. `inputs` = mixer input count. 173 pub fn set_group_level( 174 &mut self, 175 g: &MonitorGroup, 176 db: f32, 177 inputs: usize, 178 ) -> Result<(), TransportError> { 179 let v = db_to_mixer_value(db); 180 for i in 0..g.count { 181 let mut bus = vec![0u16; inputs]; 182 let in_idx = (g.mix_in_base + i) as usize; 183 if in_idx < inputs { 184 bus[in_idx] = v; 185 } 186 self.set_mix(g.bus_base + i, &bus)?; 187 } 188 Ok(()) 189 } 190 191 /// Read a group's current level (dB) — the gain of its first bus on its 192 /// first mixer-input. Meaningful only when the group is routed via the mixer. 193 pub fn get_group_level( 194 &mut self, 195 g: &MonitorGroup, 196 inputs: usize, 197 ) -> Result<f32, TransportError> { 198 let raw = self.get_mix(g.bus_base, inputs)?; 199 let idx = g.mix_in_base as usize; 200 Ok(raw.get(idx).map(|&v| mixer_value_to_db(v)).unwrap_or(MIXER_MIN_DB)) 201 } 202 203 /// True if a group is currently routed via the mixer (its first output is fed 204 /// by its first mix bus). 205 pub fn group_is_via_mixer( 206 &mut self, 207 pc: [(u16, u16); 6], 208 g: &MonitorGroup, 209 ) -> Result<bool, TransportError> { 210 use crate::mux::{id_to_num, Dir, MuxState}; 211 let entries = self.get_mux(crate::mux::num_dsts(&pc))?; 212 let st = MuxState::from_entries(pc, &entries); 213 let out0 = id_to_num(&pc, Dir::Out, g.out_id_base).unwrap_or(0); 214 let bus0 = id_to_num(&pc, Dir::In, 0x300 + g.bus_base).unwrap_or(0); 215 Ok(st.get(out0) == bus0) 216 } 217 218 /// Read the full routing table: `count` destination assignments. 219 pub fn get_mux(&mut self, count: usize) -> Result<Vec<MuxEntry>, TransportError> { 220 let mut payload = Vec::with_capacity(4); 221 payload.extend_from_slice(&0u16.to_le_bytes()); // num (start index) 222 payload.extend_from_slice(&(count as u16).to_le_bytes()); 223 let resp = self.command(op::GET_MUX, &payload, count * 4)?; 224 Ok(le_u32s(&resp.payload, count) 225 .into_iter() 226 .map(MuxEntry::unpack) 227 .collect()) 228 } 229 230 /// Read the routing table and decode each entry to `(sink_name, source_name)` 231 /// using [`crate::ports`]. `count` is the number of destinations to read. 232 /// 233 /// NOTE: read-only/display use. Editing routing needs the kernel's 3-table 234 /// (per sample-rate-band) write semantics, which [`Self::set_mux`] does not 235 /// yet replicate — verify on hardware before exposing edits. 236 pub fn read_routing(&mut self, count: usize) -> Result<Vec<(String, String)>, TransportError> { 237 Ok(self 238 .get_mux(count)? 239 .into_iter() 240 .map(|e| { 241 ( 242 crate::ports::sink_name(e.dest), 243 crate::ports::source_name(e.source), 244 ) 245 }) 246 .collect()) 247 } 248 249 /// Change one routing assignment: route `src` (source port number, 0 = Off) 250 /// to `dst` (destination port number), then write the full mux back. Reads 251 /// current routing first so only the one destination changes. Returns the 252 /// updated [`crate::mux::MuxState`]. Uses the hardware-verified write path. 253 pub fn set_route( 254 &mut self, 255 pc: [(u16, u16); 6], 256 dst: u16, 257 src: u16, 258 ) -> Result<crate::mux::MuxState, TransportError> { 259 use crate::mux::{mux_assignment_18i20_gen3, num_dsts, MuxState}; 260 let count = num_dsts(&pc); 261 let entries = self.get_mux(count)?; 262 let mut state = MuxState::from_entries(pc, &entries); 263 state.set(dst, src); 264 let tables = state.encode_all(&mux_assignment_18i20_gen3()); 265 self.write_routing_tables(&tables)?; 266 Ok(state) 267 } 268 269 /// Apply several routing changes in ONE atomic write. `changes` is a list of 270 /// `(dest_port, source_port)`. All are set in the model, then the full mux is 271 /// written once — so a stereo pair never passes through a half-routed state. 272 pub fn set_routes( 273 &mut self, 274 pc: [(u16, u16); 6], 275 changes: &[(u16, u16)], 276 ) -> Result<crate::mux::MuxState, TransportError> { 277 use crate::mux::{mux_assignment_18i20_gen3, num_dsts, MuxState}; 278 let count = num_dsts(&pc); 279 let entries = self.get_mux(count)?; 280 let mut state = MuxState::from_entries(pc, &entries); 281 for &(dst, src) in changes { 282 state.set(dst, src); 283 } 284 let tables = state.encode_all(&mux_assignment_18i20_gen3()); 285 self.write_routing_tables(&tables)?; 286 Ok(state) 287 } 288 289 /// Read routing and build a map `source_hw_id -> PCM-capture channel (1-based)` 290 /// for every source routed to a PCM capture destination. This is how we meter 291 /// physical inputs: each input is normally routed to a DAW (PCM) capture, and 292 /// the PCM-capture meters carry the real signal (the mixer-input meters only 293 /// cover sources routed into the mixer). `count` = total routing destinations. 294 pub fn source_to_pcm_capture( 295 &mut self, 296 count: usize, 297 ) -> Result<std::collections::HashMap<u16, u16>, TransportError> { 298 // PCM hardware destination IDs are 0x600.. ; capture channel = id & 0xff + 1. 299 const PCM_BASE: u16 = 0x600; 300 let mut map = std::collections::HashMap::new(); 301 for e in self.get_mux(count)? { 302 if e.source != 0 && (e.dest & 0xf00) == PCM_BASE { 303 let pcm_ch = (e.dest - PCM_BASE) + 1; // 1-based PCM capture channel 304 map.entry(e.source).or_insert(pcm_ch); 305 } 306 } 307 Ok(map) 308 } 309 310 /// Write a full routing state to the device, exactly as the kernel does: 311 /// one SET_MUX message per sample-rate-band table, each carrying 312 /// `{pad:u16, table:u16, data:[u32]}`. This OVERWRITES all routing. 313 /// 314 /// SAFETY: this is the most destructive write in the protocol — a wrong 315 /// table scrambles routing until restored. Prefer [`Self::dry_run_routing`] 316 /// first to inspect the exact payloads. Pass the per-table encodings from 317 /// [`crate::mux::MuxState::encode_table`] over `mux::mux_assignment_*`. 318 pub fn write_routing_tables(&mut self, tables: &[Vec<u32>]) -> Result<(), TransportError> { 319 for (table_num, data) in tables.iter().enumerate() { 320 let mut payload = Vec::with_capacity(4 + data.len() * 4); 321 payload.extend_from_slice(&0u16.to_le_bytes()); // pad 322 payload.extend_from_slice(&(table_num as u16).to_le_bytes()); // table index 323 for v in data { 324 payload.extend_from_slice(&v.to_le_bytes()); 325 } 326 self.command(op::SET_MUX, &payload, 0)?; 327 } 328 Ok(()) 329 } 330 331 /// Write a single routing table by index — diagnostics: pinpoint which of the 332 /// 3 sample-rate-band tables the device rejects. 333 pub fn write_routing_table( 334 &mut self, 335 table_num: u16, 336 data: &[u32], 337 ) -> Result<(), TransportError> { 338 let mut payload = Vec::with_capacity(4 + data.len() * 4); 339 payload.extend_from_slice(&0u16.to_le_bytes()); 340 payload.extend_from_slice(&table_num.to_le_bytes()); 341 for v in data { 342 payload.extend_from_slice(&v.to_le_bytes()); 343 } 344 self.command(op::SET_MUX, &payload, 0)?; 345 Ok(()) 346 } 347 348 /// Build (but do NOT send) the per-table SET_MUX payload byte-vectors for a 349 /// routing state — for inspection / dry-run verification before writing. 350 pub fn dry_run_routing(tables: &[Vec<u32>]) -> Vec<Vec<u8>> { 351 tables 352 .iter() 353 .enumerate() 354 .map(|(table_num, data)| { 355 let mut payload = Vec::with_capacity(4 + data.len() * 4); 356 payload.extend_from_slice(&0u16.to_le_bytes()); 357 payload.extend_from_slice(&(table_num as u16).to_le_bytes()); 358 for v in data { 359 payload.extend_from_slice(&v.to_le_bytes()); 360 } 361 payload 362 }) 363 .collect() 364 } 365 366 /// Legacy single-table writer (kept for the unit tests / simple cases). 367 pub fn set_mux(&mut self, entries: &[MuxEntry]) -> Result<(), TransportError> { 368 let mut payload = Vec::with_capacity(4 + entries.len() * 4); 369 payload.extend_from_slice(&0u16.to_le_bytes()); // pad 370 payload.extend_from_slice(&0u16.to_le_bytes()); // num (start index) 371 for e in entries { 372 payload.extend_from_slice(&e.pack().to_le_bytes()); 373 } 374 self.command(op::SET_MUX, &payload, 0)?; 375 Ok(()) 376 } 377 378 /// Set every crosspoint of every mix bus to silence (all inputs off). This 379 /// is the bulk "clear mixer" — useful to tame a factory wall-of-unity matrix. 380 /// `buses`/`inputs` are the device's mix dimensions. 381 pub fn clear_mixer(&mut self, buses: u16, inputs: usize) -> Result<(), TransportError> { 382 let silence = vec![0u16; inputs]; 383 for bus in 0..buses { 384 self.set_mix(bus, &silence)?; 385 } 386 Ok(()) 387 } 388 389 /// Read every mix bus into a `buses × inputs` grid of dB gains (for a UI 390 /// refresh of the whole mixer matrix). 391 pub fn read_mixer_db( 392 &mut self, 393 buses: u16, 394 inputs: usize, 395 ) -> Result<Vec<Vec<f32>>, TransportError> { 396 let mut grid = Vec::with_capacity(buses as usize); 397 for bus in 0..buses { 398 let raw = self.get_mix(bus, inputs)?; 399 grid.push(raw.into_iter().map(mixer_value_to_db).collect()); 400 } 401 Ok(grid) 402 } 403 404 /// Set one crosspoint: input `input` → mix bus `bus` at `db`. Reads the bus's 405 /// current levels, changes the one input, and writes the bus back. 406 pub fn set_mix_point_db( 407 &mut self, 408 bus: u16, 409 input: usize, 410 db: f32, 411 num_inputs: usize, 412 ) -> Result<(), TransportError> { 413 let mut levels = self.get_mix(bus, num_inputs)?; 414 if let Some(slot) = levels.get_mut(input) { 415 *slot = db_to_mixer_value(db); 416 } 417 self.set_mix(bus, &levels) 418 } 419 420 /// Snapshot `num_meters` metering points. Each level is a raw `u32`. 421 pub fn get_meters(&mut self, num_meters: u16) -> Result<Vec<u32>, TransportError> { 422 const METER_MAGIC: u32 = 1; 423 let mut payload = Vec::with_capacity(8); 424 payload.extend_from_slice(&0u16.to_le_bytes()); // pad 425 payload.extend_from_slice(&num_meters.to_le_bytes()); 426 payload.extend_from_slice(&METER_MAGIC.to_le_bytes()); 427 let resp = self.command(op::GET_METER, &payload, num_meters as usize * 4)?; 428 Ok(le_u32s(&resp.payload, num_meters as usize)) 429 } 430 } 431 432 fn le_u16s(bytes: &[u8], count: usize) -> Vec<u16> { 433 bytes 434 .chunks_exact(2) 435 .take(count) 436 .map(|c| u16::from_le_bytes([c[0], c[1]])) 437 .collect() 438 } 439 440 fn le_u32s(bytes: &[u8], count: usize) -> Vec<u32> { 441 bytes 442 .chunks_exact(4) 443 .take(count) 444 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]])) 445 .collect() 446 } 447 448 #[cfg(test)] 449 mod tests { 450 use super::*; 451 use crate::transport::mock::MockTransport; 452 453 #[test] 454 fn mixer_db_unity_and_extremes() { 455 // 8192 is unity = 0 dB. 456 assert!((mixer_value_to_db(8192) - 0.0).abs() < 0.05); 457 assert_eq!(mixer_value_to_db(0), MIXER_MIN_DB); 458 // round-trip 0 dB 459 assert_eq!(db_to_mixer_value(0.0), 8192); 460 // +12 dB is the ceiling 461 assert!(db_to_mixer_value(20.0) <= db_to_mixer_value(MIXER_MAX_DB) + 1); 462 } 463 464 #[test] 465 fn mixer_db_round_trips_within_quantization() { 466 for &db in &[-60.0_f32, -20.0, -6.0, 0.0, 6.0, 12.0] { 467 let v = db_to_mixer_value(db); 468 let back = mixer_value_to_db(v); 469 assert!((back - db).abs() < 0.5, "db {db} -> {v} -> {back}"); 470 } 471 } 472 473 #[test] 474 fn read_mixer_db_builds_grid() { 475 let mut m = MockTransport::new(); 476 // 2 buses × 2 inputs, all unity 477 for _ in 0..2 { 478 let mut p = Vec::new(); 479 p.extend_from_slice(&8192u16.to_le_bytes()); 480 p.extend_from_slice(&8192u16.to_le_bytes()); 481 m.push_response(op::GET_MIX, &p); 482 } 483 let mut dev = Scarlett::new(m); 484 let grid = dev.read_mixer_db(2, 2).unwrap(); 485 assert_eq!(grid.len(), 2); 486 assert!((grid[0][0]).abs() < 0.05); // ~0 dB 487 } 488 489 #[test] 490 fn mux_entry_round_trips_through_packing() { 491 // dest in low 12 bits, source in high 12 (kernel order) 492 let e = MuxEntry { source: 5, dest: 9 }; 493 let packed = e.pack(); 494 assert_eq!(packed, 9 | (5 << 12)); 495 assert_eq!(MuxEntry::unpack(packed), e); 496 } 497 498 #[test] 499 fn get_mix_requests_bus_and_parses_levels() { 500 let mut m = MockTransport::new(); 501 // two inputs: levels 0x1000, 0x2000 502 m.push_response(op::GET_MIX, &[0x00, 0x10, 0x00, 0x20]); 503 let mut dev = Scarlett::new(m); 504 505 let levels = dev.get_mix(3, 2).unwrap(); 506 assert_eq!(levels, vec![0x1000, 0x2000]); 507 508 let m = dev.into_transport(); 509 assert_eq!(m.sent[0].0, op::GET_MIX); 510 // request = mix_num(3) + count(2), both u16 LE 511 assert_eq!(m.sent[0].1, vec![0x03, 0x00, 0x02, 0x00]); 512 } 513 514 #[test] 515 fn set_mix_packs_bus_then_levels() { 516 let mut m = MockTransport::new(); 517 m.push_response(op::SET_MIX, &[]); 518 let mut dev = Scarlett::new(m); 519 520 dev.set_mix(1, &[0xaabb, 0xccdd]).unwrap(); 521 522 let m = dev.into_transport(); 523 let (cmd, payload) = &m.sent[0]; 524 assert_eq!(*cmd, op::SET_MIX); 525 assert_eq!(payload, &vec![0x01, 0x00, 0xbb, 0xaa, 0xdd, 0xcc]); 526 } 527 528 #[test] 529 fn get_mux_parses_packed_entries() { 530 let mut m = MockTransport::new(); 531 // dest|src<<12 : (dest 0, src 2) and (dest 3, src 7) 532 let raw0 = (0u32 | (2 << 12)).to_le_bytes(); 533 let raw1 = (3u32 | (7 << 12)).to_le_bytes(); 534 let mut payload = Vec::new(); 535 payload.extend_from_slice(&raw0); 536 payload.extend_from_slice(&raw1); 537 m.push_response(op::GET_MUX, &payload); 538 let mut dev = Scarlett::new(m); 539 540 let entries = dev.get_mux(2).unwrap(); 541 assert_eq!(entries[0], MuxEntry { source: 2, dest: 0 }); 542 assert_eq!(entries[1], MuxEntry { source: 7, dest: 3 }); 543 544 let m = dev.into_transport(); 545 // request = num(0) + count(2) 546 assert_eq!(m.sent[0].1, vec![0x00, 0x00, 0x02, 0x00]); 547 } 548 549 #[test] 550 fn set_mux_writes_pad_num_then_packed_entries() { 551 let mut m = MockTransport::new(); 552 m.push_response(op::SET_MUX, &[]); 553 let mut dev = Scarlett::new(m); 554 555 dev.set_mux(&[MuxEntry { source: 1, dest: 4 }]).unwrap(); 556 557 let m = dev.into_transport(); 558 let (cmd, payload) = &m.sent[0]; 559 assert_eq!(*cmd, op::SET_MUX); 560 let entry = (4u32 | (1 << 12)).to_le_bytes(); // dest 4, source 1 561 let mut expect = vec![0x00, 0x00, 0x00, 0x00]; // pad, num 562 expect.extend_from_slice(&entry); 563 assert_eq!(payload, &expect); 564 } 565 566 #[test] 567 fn get_meters_sends_magic_and_parses_u32_levels() { 568 let mut m = MockTransport::new(); 569 let mut payload = Vec::new(); 570 payload.extend_from_slice(&0x0000_1234u32.to_le_bytes()); 571 payload.extend_from_slice(&0x0000_5678u32.to_le_bytes()); 572 m.push_response(op::GET_METER, &payload); 573 let mut dev = Scarlett::new(m); 574 575 let meters = dev.get_meters(2).unwrap(); 576 assert_eq!(meters, vec![0x1234, 0x5678]); 577 578 let m = dev.into_transport(); 579 let (cmd, sent) = &m.sent[0]; 580 assert_eq!(*cmd, op::GET_METER); 581 // pad(0,u16) + num_meters(2,u16) + magic(1,u32) 582 assert_eq!(sent, &vec![0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00]); 583 } 584 }