valentine

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

metermap.rs (4846B)


      1 //! Meter-map probe v2 (read-only). Two parts:
      2 //!   1. Prints your full decoded routing (sink ← source) so we can see where
      3 //!      each physical input (esp. ADAT) is actually routed.
      4 //!   2. Peak-holds all 65 raw GET_METER indices over ~12s and prints each with
      5 //!      its destination + the source currently routed there — so when you feed a
      6 //!      specific input we can see EXACTLY which raw index moves and how high
      7 //!      (for both channel mapping AND full-scale calibration vs your DAW).
      8 //!
      9 //! Run with Focusrite Control quit:  cargo run -p spike --bin metermap
     10 //! Feed ONE input you can name (e.g. ADAT 1) the whole time. Reads only.
     11 
     12 use scarlett_core::model::{PortType, S18I20_GEN3};
     13 use scarlett_core::ports::{sink_name, source_name};
     14 use scarlett_core::{Scarlett, UsbTransport};
     15 
     16 fn main() {
     17     if let Err(e) = run() {
     18         eprintln!("\x1b[31mMETERMAP FAILED:\x1b[0m {e}");
     19         eprintln!("(If access/busy: quit Focusrite Control first.)");
     20         std::process::exit(1);
     21     }
     22 }
     23 
     24 /// Destination index → human label, in the gen3c destination numbering
     25 /// (Analogue Out 1-10, S/PDIF Out 1-2, ADAT Out 1-8, Mixer In 1-25, PCM cap 1-20).
     26 fn dest_label(d: usize) -> String {
     27     let an = S18I20_GEN3.port_count(PortType::Analogue).1 as usize; // 10
     28     let sp = S18I20_GEN3.port_count(PortType::Spdif).1 as usize; // 2
     29     let ad = S18I20_GEN3.port_count(PortType::Adat).1 as usize; // 8
     30     let mx = S18I20_GEN3.port_count(PortType::Mix).1 as usize; // 25
     31     let mut base = 0;
     32     if d < base + an {
     33         return format!("AnalogueOut {}", d - base + 1);
     34     }
     35     base += an;
     36     if d < base + sp {
     37         return format!("S/PDIF Out {}", d - base + 1);
     38     }
     39     base += sp;
     40     if d < base + ad {
     41         return format!("ADAT Out {}", d - base + 1);
     42     }
     43     base += ad;
     44     if d < base + mx {
     45         return format!("MixerIn {}", d - base + 1);
     46     }
     47     base += mx;
     48     format!("PCM cap {}", d - base + 1)
     49 }
     50 
     51 fn run() -> Result<(), Box<dyn std::error::Error>> {
     52     let mut dev = Scarlett::new(UsbTransport::open_default()?);
     53     dev.init()?;
     54     println!("connected: {}\n", S18I20_GEN3.name);
     55 
     56     // --- Part 1: full routing, decoded ---
     57     let num_dsts = S18I20_GEN3.mux_dst_count();
     58     let mux = dev.get_mux(num_dsts)?;
     59     println!("=== ROUTING (sink ← source), non-Off only ===");
     60     for e in &mux {
     61         if e.source != 0 {
     62             println!(
     63                 "  {:<16} ← {:<16}  (dest hw {:#05x} / src hw {:#05x})",
     64                 sink_name(e.dest),
     65                 source_name(e.source),
     66                 e.dest,
     67                 e.source
     68             );
     69         }
     70     }
     71 
     72     // --- Part 2: peak-hold every raw index over ~12s while you feed an input ---
     73     let n = S18I20_GEN3.meter_count as usize;
     74     let mut peak = vec![0u32; n];
     75     println!("\n=== Feed ONE input now. Holding peaks for ~12s… ===");
     76     // ~12s: 60 reads with 4 throwaway reads between each as a crude delay.
     77     for _ in 0..60 {
     78         let raw = dev.get_meters(S18I20_GEN3.meter_count)?;
     79         for (i, &v) in raw.iter().enumerate() {
     80             if v > peak[i] {
     81                 peak[i] = v;
     82             }
     83         }
     84         for _ in 0..6 {
     85             let _ = dev.get_meters(4);
     86         }
     87     }
     88 
     89     // Print every raw index that saw signal, with which destination it is and
     90     // what source is routed there — sorted by peak descending.
     91     println!("\n=== RAW INDICES THAT MOVED (peak desc) ===");
     92     // raw index i corresponds to the i-th destination in meter_map order; we
     93     // reconstruct that destination list to label each raw index.
     94     let meter_map: [(u16, u16); 5] = [(45, 8), (55, 10), (0, 20), (53, 2), (20, 25)];
     95     let mut raw_to_dest = Vec::with_capacity(n);
     96     for (start, count) in meter_map {
     97         for j in 0..count {
     98             raw_to_dest.push((start + j) as usize);
     99         }
    100     }
    101     // source routed to a dest (for labeling), from the mux table
    102     let mut src_at = vec![0u16; num_dsts];
    103     for e in &mux {
    104         if (e.dest as usize) < src_at.len() {
    105             src_at[e.dest as usize] = e.source;
    106         }
    107     }
    108 
    109     let mut rows: Vec<(u32, usize, usize)> = peak
    110         .iter()
    111         .enumerate()
    112         .filter(|(_, &p)| p > 20)
    113         .map(|(i, &p)| (p, i, raw_to_dest.get(i).copied().unwrap_or(usize::MAX)))
    114         .collect();
    115     rows.sort_by(|a, b| b.0.cmp(&a.0));
    116 
    117     for (p, raw_i, dest) in rows {
    118         let src = src_at.get(dest).copied().unwrap_or(0);
    119         let src_txt = if src == 0 { "(off/none)".to_string() } else { source_name(src) };
    120         let dlabel = if dest == usize::MAX { "?".to_string() } else { dest_label(dest) };
    121         println!(
    122             "  raw{raw_i:>2}  peak {p:>5}  dest{dest:>2} {dlabel:<16}  source: {src_txt}"
    123         );
    124     }
    125 
    126     println!("\nMETERMAP DONE (read-only). Tell me which input you fed.");
    127     Ok(())
    128 }