commit d6965e725d2eba1c17f2bd45d4b747f4b520cb3a
parent 5a53c4fc3ff00d1e2c003db19882eb19fad6b08a
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date: Mon, 1 Jun 2026 14:18:08 -0500
feat(spike): adatcheck — diagnose hot ADAT output (routing + DAW levels)
Read-only: shows what feeds each ADAT out (PCM-direct like FC, vs a mix bus
that would sum the wall-of-unity mixer to a hot level) and compares DAW send
levels PCM 13-20 (ADAT) vs PCM 1-2 (main/HP). Pinpoints whether the loud ADAT
is induced routing, DAW levels, or the external converter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat:
2 files changed, 90 insertions(+), 0 deletions(-)
diff --git a/spike/Cargo.toml b/spike/Cargo.toml
@@ -37,6 +37,11 @@ path = "src/bin/muxcheck.rs"
name = "presetcheck"
path = "src/bin/presetcheck.rs"
+# Read-only ADAT-output routing + level diagnostic.
+[[bin]]
+name = "adatcheck"
+path = "src/bin/adatcheck.rs"
+
[dependencies]
rusb = { version = "0.9", features = ["vendored"] }
anyhow.workspace = true
diff --git a/spike/src/bin/adatcheck.rs b/spike/src/bin/adatcheck.rs
@@ -0,0 +1,85 @@
+//! ADAT-output diagnostic (READ-ONLY). Answers: are the ADAT outputs fed
+//! straight from the DAW (PCM, like a normal Focusrite Control setup) or through
+//! a mix bus (which, with the wall-of-unity mixer, would sum to a hot level)?
+//! Also compares the meter levels feeding ADAT (PCM 13-20) vs headphones/main
+//! (PCM 1-2) so we can see if the DAW itself is sending ADAT hotter.
+//!
+//! Run with Focusrite Control quit, ideally while playing audio:
+//! cargo run -p spike --bin adatcheck
+
+use scarlett_core::matrix::mixer_value_to_db;
+use scarlett_core::meter::pcm_capture_level;
+use scarlett_core::model::S18I20_GEN3;
+use scarlett_core::mux::{id_to_num, num_dsts, Dir, MuxState, PORT_COUNT_18I20_GEN3};
+use scarlett_core::ports::source_name;
+use scarlett_core::{Scarlett, UsbTransport};
+
+fn main() {
+ if let Err(e) = run() {
+ eprintln!("\x1b[31mADATCHECK FAILED:\x1b[0m {e}");
+ std::process::exit(1);
+ }
+}
+
+fn run() -> Result<(), Box<dyn std::error::Error>> {
+ let mut dev = Scarlett::new(UsbTransport::open_default()?);
+ dev.init()?;
+ let pc = PORT_COUNT_18I20_GEN3;
+ println!("connected: {}\n", S18I20_GEN3.name);
+
+ // 1. What feeds each ADAT output right now?
+ let entries = dev.get_mux(num_dsts(&pc))?;
+ let st = MuxState::from_entries(pc, &entries);
+ println!("=== ADAT OUTPUT ROUTING ===");
+ let mut via_mix = 0;
+ for i in 0..8u16 {
+ let out_num = id_to_num(&pc, Dir::Out, 0x200 + i).unwrap_or(0);
+ let src_num = st.get(out_num);
+ let src_id = id_to_num(&pc, Dir::In, src_num) // round-trip for label
+ .map(|_| src_num)
+ .unwrap_or(src_num);
+ let src_hw = scarlett_core::mux::num_to_id(&pc, Dir::In, src_id);
+ let name = source_name(src_hw);
+ let is_mix = (src_hw & 0xf00) == 0x300;
+ if is_mix {
+ via_mix += 1;
+ }
+ println!(" ADAT Out {} ← {name}{}", i + 1, if is_mix { " <-- via MIXER" } else { "" });
+ }
+ if via_mix > 0 {
+ println!(
+ "\n\x1b[33m{via_mix}/8 ADAT outs are fed by a MIX bus.\x1b[0m With the wall-of-unity\n\
+ mixer this sums many sources → HOT. Focusrite Control feeds ADAT from\n\
+ PCM directly, which is why FC wasn't loud. Fix: route ADAT ← PCM\n\
+ (Routing tab), or lower those mix buses."
+ );
+ } else {
+ println!("\n\x1b[32mAll ADAT outs are fed directly from PCM (like FC).\x1b[0m");
+ println!("So the level is the DAW's output or the external converter, not routing.");
+ }
+
+ // 2. Compare DAW send levels: PCM 13-20 (ADAT) vs PCM 1-2 (main/HP).
+ println!("\n=== DAW SEND LEVELS (play audio; peak over ~4s) ===");
+ let mut peak = vec![0u32; S18I20_GEN3.meter_count as usize];
+ for _ in 0..20 {
+ let raw = dev.get_meters(S18I20_GEN3.meter_count)?;
+ for (i, &v) in raw.iter().enumerate() {
+ if v > peak[i] {
+ peak[i] = v;
+ }
+ }
+ for _ in 0..4 {
+ let _ = dev.get_meters(4);
+ }
+ }
+ let db = |raw: u32| scarlett_core::meter::raw_to_dbfs(raw);
+ let pk = |n| pcm_capture_level(&peak, n);
+ println!(" PCM 1 (main/HP L): {:>6.1} dBFS", db(pk(1)));
+ println!(" PCM 2 (main/HP R): {:>6.1} dBFS", db(pk(2)));
+ println!(" PCM 13 (ADAT 1): {:>6.1} dBFS", db(pk(13)));
+ println!(" PCM 14 (ADAT 2): {:>6.1} dBFS", db(pk(14)));
+ let _ = mixer_value_to_db; // (kept for parity)
+
+ println!("\nADATCHECK DONE (read-only).");
+ Ok(())
+}