valentine

Terminal control panel for the Focusrite Scarlett 18i20 — a from-scratch replacement for Focusrite Control.
Log | Files | Refs | README | LICENSE

muxcheck.rs (4673B)


      1 //! Routing-write SAFETY probe (READ-ONLY — never writes to the device).
      2 //!
      3 //! Proves the mux encoder is trustworthy before we ever send a SET_MUX:
      4 //!   1. Read the device's current routing (GET_MUX).
      5 //!   2. Decode it into our MuxState (port-number model).
      6 //!   3. Re-encode all 3 sample-rate tables.
      7 //!   4. Read the routing AGAIN and confirm our re-encode, when decoded, matches
      8 //!      the device byte-for-byte at the entry level — i.e. writing our encoding
      9 //!      would be a no-op. If this passes, an actual edit-then-write is safe.
     10 //!
     11 //! Run with Focusrite Control quit:  cargo run -p spike --bin muxcheck
     12 
     13 use std::collections::HashMap;
     14 
     15 use scarlett_core::matrix::MuxEntry;
     16 use scarlett_core::model::S18I20_GEN3;
     17 use scarlett_core::mux::{mux_assignment_18i20_gen3, MuxState, PORT_COUNT_18I20_GEN3};
     18 use scarlett_core::{Scarlett, UsbTransport};
     19 
     20 fn main() {
     21     if let Err(e) = run() {
     22         eprintln!("\x1b[31mMUXCHECK FAILED:\x1b[0m {e}");
     23         std::process::exit(1);
     24     }
     25 }
     26 
     27 fn run() -> Result<(), Box<dyn std::error::Error>> {
     28     let mut dev = Scarlett::new(UsbTransport::open_default()?);
     29     dev.init()?;
     30     println!("connected: {}", S18I20_GEN3.name);
     31 
     32     let count = S18I20_GEN3.mux_dst_count();
     33     let before = dev.get_mux(count)?;
     34 
     35     // Decode → model → re-encode all tables.
     36     let state = MuxState::from_entries(PORT_COUNT_18I20_GEN3, &before);
     37     let assign = mux_assignment_18i20_gen3();
     38     let tables: Vec<Vec<u32>> = assign.iter().map(|a| state.encode_table(a)).collect();
     39 
     40     // The device reports routing as a flat dst→src map; build it from `before`.
     41     let mut device_map: HashMap<u16, u16> = HashMap::new();
     42     for e in &before {
     43         device_map.insert(e.dest, e.source);
     44     }
     45 
     46     // Decode our re-encoded TABLE 0 (the band that covers all destinations) and
     47     // compare every non-empty entry to what the device currently reports.
     48     let mut mism = 0usize;
     49     let mut checked = 0usize;
     50     for v in &tables[0] {
     51         let dst_id = (v & 0xfff) as u16;
     52         let src_id = ((v >> 12) & 0xfff) as u16;
     53         if dst_id == 0 {
     54             continue; // empty slot
     55         }
     56         checked += 1;
     57         let dev_src = device_map.get(&dst_id).copied().unwrap_or(0);
     58         if dev_src != src_id {
     59             if mism < 12 {
     60                 println!(
     61                     "  MISMATCH dst {dst_id:#05x}: device src {dev_src:#05x} != ours {src_id:#05x}"
     62                 );
     63             }
     64             mism += 1;
     65         }
     66     }
     67 
     68     println!("\nchecked {checked} routed destinations in table 0");
     69     if mism == 0 {
     70         println!("\x1b[32mMUXCHECK PASSED\x1b[0m — our re-encode reproduces the device routing exactly.");
     71         println!("→ writing our encoding would be a no-op; routing edit is safe to enable.");
     72     } else {
     73         println!("\x1b[31mMUXCHECK: {mism} mismatches\x1b[0m — DO NOT enable routing write yet.");
     74     }
     75 
     76     // Show a small sample so the human can eyeball it too.
     77     println!("\nsample of current routing (first 6 routed):");
     78     let mut shown = 0;
     79     for e in &before {
     80         if e.source != 0 && shown < 6 {
     81             println!("  dst {:#05x} ← src {:#05x}", e.dest, e.source);
     82             shown += 1;
     83         }
     84     }
     85     let _ = MuxEntry { dest: 0, source: 0 }; // keep import meaningful
     86 
     87     // OPTIONAL no-op write test: only with `--write-noop`, and only if the
     88     // re-encode matched (mism==0). Writes the CURRENT routing back unchanged,
     89     // then re-reads to confirm nothing changed. This exercises the real write
     90     // path on hardware with zero risk of altering the setup.
     91     let do_write = std::env::args().any(|a| a == "--write-noop");
     92     if do_write && mism == 0 {
     93         println!("\n--write-noop: writing current routing back UNCHANGED…");
     94         dev.write_routing_tables(&tables)?;
     95         let after = dev.get_mux(count)?;
     96         let mut after_map: HashMap<u16, u16> = HashMap::new();
     97         for e in &after {
     98             after_map.insert(e.dest, e.source);
     99         }
    100         let changed = device_map
    101             .iter()
    102             .filter(|(d, s)| after_map.get(d).copied().unwrap_or(0) != **s)
    103             .count();
    104         if changed == 0 {
    105             println!("\x1b[32mNO-OP WRITE OK\x1b[0m — routing identical after write. Write path verified.");
    106         } else {
    107             println!("\x1b[31mNO-OP WRITE CHANGED {changed} routes!\x1b[0m — investigate before edits.");
    108         }
    109     } else if do_write {
    110         println!("\n--write-noop skipped: re-encode had mismatches; not writing.");
    111     } else {
    112         println!("\n(READ-ONLY — nothing was written. Add --write-noop to test the write path.)");
    113     }
    114     Ok(())
    115 }