commit 0bcf3fd3f6d2d95b646e6d4025567e301c3eb53a
parent 872781d3d87cfc3d871c0c628a5ce49c9c96eb49
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date: Mon, 1 Jun 2026 10:25:48 -0500
feat(spike): metermap v2 — dump routing + peak-hold every raw index
Prints full decoded routing (sink<-source) and peak-holds all 65 GET_METER
indices for ~12s, labeling each by destination + routed source. Lets us see
exactly where ADAT is routed and which raw index moves (channel mapping) plus
real signal peaks (full-scale calibration vs DAW). Read-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat:
1 file changed, 87 insertions(+), 78 deletions(-)
diff --git a/spike/src/bin/metermap.rs b/spike/src/bin/metermap.rs
@@ -1,17 +1,16 @@
-//! Meter-map probe (read-only). Samples GET_METER repeatedly and prints the raw
-//! levels grouped by the device's meter_map spans, so you can SEE which raw
-//! index moves when you feed a physical input — empirically verifying the
-//! source→meter mapping before we wire meters into the inputs UI.
+//! Meter-map probe v2 (read-only). Two parts:
+//! 1. Prints your full decoded routing (sink ← source) so we can see where
+//! each physical input (esp. ADAT) is actually routed.
+//! 2. Peak-holds all 65 raw GET_METER indices over ~12s and prints each with
+//! its destination + the source currently routed there — so when you feed a
+//! specific input we can see EXACTLY which raw index moves and how high
+//! (for both channel mapping AND full-scale calibration vs your DAW).
//!
//! Run with Focusrite Control quit: cargo run -p spike --bin metermap
-//! Then make noise on, e.g., an ADAT input and watch which numbers jump.
-//!
-//! It only READS (GET_METER + GET_MUX). It changes nothing on the device.
-
-use std::io::Write;
+//! Feed ONE input you can name (e.g. ADAT 1) the whole time. Reads only.
-use scarlett_core::meter::{build_level_map, level_at, METER_MAP_18I20_GEN3};
-use scarlett_core::model::S18I20_GEN3;
+use scarlett_core::model::{PortType, S18I20_GEN3};
+use scarlett_core::ports::{sink_name, source_name};
use scarlett_core::{Scarlett, UsbTransport};
fn main() {
@@ -22,98 +21,108 @@ fn main() {
}
}
+/// Destination index → human label, in the gen3c destination numbering
+/// (Analogue Out 1-10, S/PDIF Out 1-2, ADAT Out 1-8, Mixer In 1-25, PCM cap 1-20).
+fn dest_label(d: usize) -> String {
+ let an = S18I20_GEN3.port_count(PortType::Analogue).1 as usize; // 10
+ let sp = S18I20_GEN3.port_count(PortType::Spdif).1 as usize; // 2
+ let ad = S18I20_GEN3.port_count(PortType::Adat).1 as usize; // 8
+ let mx = S18I20_GEN3.port_count(PortType::Mix).1 as usize; // 25
+ let mut base = 0;
+ if d < base + an {
+ return format!("AnalogueOut {}", d - base + 1);
+ }
+ base += an;
+ if d < base + sp {
+ return format!("S/PDIF Out {}", d - base + 1);
+ }
+ base += sp;
+ if d < base + ad {
+ return format!("ADAT Out {}", d - base + 1);
+ }
+ base += ad;
+ if d < base + mx {
+ return format!("MixerIn {}", d - base + 1);
+ }
+ base += mx;
+ format!("PCM cap {}", d - base + 1)
+}
+
fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut dev = Scarlett::new(UsbTransport::open_default()?);
dev.init()?;
- println!("connected: {}", S18I20_GEN3.name);
+ println!("connected: {}\n", S18I20_GEN3.name);
- // Span labels in meter_map order (start, count) → a human hint of what that
- // span of destinations is, based on the gen3c destination layout.
- // CORRECTED destination layout (verified 2026-06-01): destinations number in
- // port order — Analogue Out 1-10 (0..9), S/PDIF Out (10..11), ADAT Out 1-8
- // (12..19), Mixer Inputs 1-25 (20..44), PCM capture 1-20 (45..64).
- let span_hint = [
- "span0 (dst 45..52) — PCM capture 1-8 (DAW)",
- "span1 (dst 55..64) — PCM capture 11-20 (DAW)",
- "span2 (dst 0..19) — Analogue Out 1-10, S/PDIF Out, ADAT Out 1-8",
- "span3 (dst 53..54) — PCM capture 9-10",
- "span4 (dst 20..44) — Mixer Inputs 1-25 ← per-input meters (raw 40..64)",
- ];
-
- // Read routing once so we can also print the kernel-style level map.
- let mux_entries = dev.get_mux(S18I20_GEN3.mux_dst_count())?;
- // Build a dest->source array (mux[dest] = source hardware id, low value 0=off).
+ // --- Part 1: full routing, decoded ---
let num_dsts = S18I20_GEN3.mux_dst_count();
- let mut mux = vec![0u16; num_dsts];
- for e in &mux_entries {
- if (e.dest as usize) < mux.len() {
- mux[e.dest as usize] = e.source;
+ let mux = dev.get_mux(num_dsts)?;
+ println!("=== ROUTING (sink ← source), non-Off only ===");
+ for e in &mux {
+ if e.source != 0 {
+ println!(
+ " {:<16} ← {:<16} (dest hw {:#05x} / src hw {:#05x})",
+ sink_name(e.dest),
+ source_name(e.source),
+ e.dest,
+ e.source
+ );
}
}
- let level_map = build_level_map(METER_MAP_18I20_GEN3, &mux, num_dsts);
-
- println!("\nSampling meters 20× (~4s). Feed an input and watch the numbers.\n");
- println!("meter_map spans:");
- for h in span_hint {
- println!(" {h}");
- }
- println!();
- // Track peak per raw index across samples so brief signals are visible.
+ // --- Part 2: peak-hold every raw index over ~12s while you feed an input ---
let n = S18I20_GEN3.meter_count as usize;
let mut peak = vec![0u32; n];
-
- for s in 0..20 {
+ println!("\n=== Feed ONE input now. Holding peaks for ~12s… ===");
+ // ~12s: 60 reads with 4 throwaway reads between each as a crude delay.
+ for _ in 0..60 {
let raw = dev.get_meters(S18I20_GEN3.meter_count)?;
for (i, &v) in raw.iter().enumerate() {
if v > peak[i] {
peak[i] = v;
}
}
- // live one-liner of the mixer-input span (span4 = raw indices 40..64)
- let live: Vec<String> = raw
- .iter()
- .skip(40)
- .take(25)
- .map(|v| format!("{:>4}", v / 256)) // scale down for readability
- .collect();
- print!("\rsample {s:>2}: mix-in[1..25] {}", live.join(" "));
- std::io::stdout().flush().ok();
- // ~200ms between samples without using a sleep helper
- for _ in 0..4 {
+ for _ in 0..6 {
let _ = dev.get_meters(4);
}
}
- println!("\n");
- // Peak summary, grouped by span, with raw index so you can map exactly.
- let mut i = 0usize;
- for (si, span) in METER_MAP_18I20_GEN3.iter().enumerate() {
- println!("── {} ──", span_hint.get(si).copied().unwrap_or("span"));
- let mut line = String::new();
- for j in 0..span.count {
- let raw_idx = i + j as usize;
- let dest = span.start + j;
- line += &format!("[raw{raw_idx:>2} dst{dest:>2}={:>5}] ", peak.get(raw_idx).copied().unwrap_or(0));
- if (j + 1) % 4 == 0 {
- println!(" {line}");
- line.clear();
- }
+ // Print every raw index that saw signal, with which destination it is and
+ // what source is routed there — sorted by peak descending.
+ println!("\n=== RAW INDICES THAT MOVED (peak desc) ===");
+ // raw index i corresponds to the i-th destination in meter_map order; we
+ // reconstruct that destination list to label each raw index.
+ let meter_map: [(u16, u16); 5] = [(45, 8), (55, 10), (0, 20), (53, 2), (20, 25)];
+ let mut raw_to_dest = Vec::with_capacity(n);
+ for (start, count) in meter_map {
+ for j in 0..count {
+ raw_to_dest.push((start + j) as usize);
}
- if !line.is_empty() {
- println!(" {line}");
+ }
+ // source routed to a dest (for labeling), from the mux table
+ let mut src_at = vec![0u16; num_dsts];
+ for e in &mux {
+ if (e.dest as usize) < src_at.len() {
+ src_at[e.dest as usize] = e.source;
}
- i += span.count as usize;
}
- // Sanity: show a couple of destinations resolved through the level map.
- println!("\nlevel_map sanity (dest → raw idx, 255=off):");
- for dest in [20usize, 21, 22, 45, 46] {
- let idx = level_map.get(dest).copied().unwrap_or(u16::MAX);
- let lv = level_at(&level_map, &peak, dest);
- println!(" dest {dest:>2} → raw {idx} → peak {lv}");
+ let mut rows: Vec<(u32, usize, usize)> = peak
+ .iter()
+ .enumerate()
+ .filter(|(_, &p)| p > 20)
+ .map(|(i, &p)| (p, i, raw_to_dest.get(i).copied().unwrap_or(usize::MAX)))
+ .collect();
+ rows.sort_by(|a, b| b.0.cmp(&a.0));
+
+ for (p, raw_i, dest) in rows {
+ let src = src_at.get(dest).copied().unwrap_or(0);
+ let src_txt = if src == 0 { "(off/none)".to_string() } else { source_name(src) };
+ let dlabel = if dest == usize::MAX { "?".to_string() } else { dest_label(dest) };
+ println!(
+ " raw{raw_i:>2} peak {p:>5} dest{dest:>2} {dlabel:<16} source: {src_txt}"
+ );
}
- println!("\nMETERMAP DONE (read-only; nothing changed).");
+ println!("\nMETERMAP DONE (read-only). Tell me which input you fed.");
Ok(())
}