valentine

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

commit 8291d0f2b2aa4f48658acc4421a3b08a4065f72f
parent 9d02e362e6366d46b63e5282b34f919a9c37a2ce
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date:   Mon,  1 Jun 2026 15:12:46 -0500

feat(spike): adatfader — trace the ADAT via-mixer fader chain on hardware

Runs route_group_via_mixer + set_group_level for the ADAT group and reads the
device back at each step (ADAT out routing, mixer-input feeds, per-bus gains)
to pinpoint why the fader has no audible effect. Restores ADAT to direct after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Diffstat:
Mspike/Cargo.toml | 5+++++
Aspike/src/bin/adatfader.rs | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+), 0 deletions(-)

diff --git a/spike/Cargo.toml b/spike/Cargo.toml @@ -42,6 +42,11 @@ path = "src/bin/presetcheck.rs" name = "adatcheck" path = "src/bin/adatcheck.rs" +# ADAT-fader chain diagnostic (routes via mixer, reads back, restores). +[[bin]] +name = "adatfader" +path = "src/bin/adatfader.rs" + [dependencies] rusb = { version = "0.9", features = ["vendored"] } anyhow.workspace = true diff --git a/spike/src/bin/adatfader.rs b/spike/src/bin/adatfader.rs @@ -0,0 +1,91 @@ +//! ADAT-fader chain diagnostic. Runs the exact via-mixer + level sequence the +//! TUI does, reading device state back at each step so we can see precisely +//! where the fader chain breaks. Restores ADAT to direct-from-DAW at the end. +//! +//! Run with Focusrite Control quit: cargo run -p spike --bin adatfader + +use scarlett_core::matrix::mixer_value_to_db; +use scarlett_core::model::S18I20_GEN3; +use scarlett_core::mux::{id_to_num, num_dsts, num_to_id, 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[31mADATFADER FAILED:\x1b[0m {e}"); + std::process::exit(1); + } +} + +fn adat_group() -> &'static scarlett_core::matrix::MonitorGroup { + scarlett_core::matrix::MONITOR_GROUPS + .iter() + .find(|g| g.name.starts_with("ADAT")) + .expect("ADAT group exists") +} + +fn show_adat_routing(dev: &mut Scarlett<UsbTransport>, pc: [(u16, u16); 6], tag: &str) { + let entries = dev.get_mux(num_dsts(&pc)).unwrap(); + let st = MuxState::from_entries(pc, &entries); + println!("--- ADAT Out routing [{tag}] ---"); + for i in 0..8u16 { + let out = id_to_num(&pc, Dir::Out, 0x200 + i).unwrap_or(0); + let src = num_to_id(&pc, Dir::In, st.get(out)); + println!(" ADAT Out {} ← {}", i + 1, source_name(src)); + } +} + +fn run() -> Result<(), Box<dyn std::error::Error>> { + let mut dev = Scarlett::new(UsbTransport::open_default()?); + dev.init()?; + let pc = PORT_COUNT_18I20_GEN3; + let g = adat_group(); + let inputs = S18I20_GEN3.mixer_inputs() as usize; + println!("connected: {}\n", S18I20_GEN3.name); + + show_adat_routing(&mut dev, pc, "before"); + + // Step 1: route ADAT via mixer. + println!("\n>>> route_group_via_mixer(ADAT)"); + dev.route_group_via_mixer(pc, g)?; + show_adat_routing(&mut dev, pc, "after via-mixer"); + + // What feeds the ADAT mixer-inputs now? + let entries = dev.get_mux(num_dsts(&pc))?; + let st = MuxState::from_entries(pc, &entries); + println!("--- ADAT mixer-input feeds ---"); + for i in 0..g.count { + let mi = id_to_num(&pc, Dir::Out, 0x300 + g.mix_in_base + i).unwrap_or(0); + let src = num_to_id(&pc, Dir::In, st.get(mi)); + println!(" MixerIn {} ← {}", g.mix_in_base + i + 1, source_name(src)); + } + + // Step 2: set group level to -40 dB and read the buses back. + println!("\n>>> set_group_level(ADAT, -40 dB)"); + dev.set_group_level(g, -40.0, inputs)?; + for i in 0..g.count { + let bus = g.bus_base + i; + let raw = dev.get_mix(bus, inputs)?; + let in_idx = (g.mix_in_base + i) as usize; + let this = raw.get(in_idx).map(|&v| mixer_value_to_db(v)).unwrap_or(-99.0); + let nonzero = raw.iter().filter(|&&v| v > 0).count(); + println!( + " Mix bus {bus:>2}: input {} = {this:>6.1} dB ({nonzero} non-silent inputs total)", + in_idx + 1 + ); + } + + println!( + "\nEXPECTED: ADAT Out ← Mix C..J; MixerIn ← PCM 13..20; each bus shows its\n\ + one input at -40 dB. If routing still shows ← PCM, the route write failed.\n\ + If the bus input isn't -40, the level write/index is wrong." + ); + + // Restore: ADAT back to direct from DAW. + println!("\n>>> restoring ADAT to direct-from-DAW"); + dev.route_group_direct(pc, g)?; + show_adat_routing(&mut dev, pc, "restored"); + + println!("\nADATFADER DONE."); + Ok(()) +}