commit 50199c69e1068614cec5a8dbfa5ed2e158a79f82
parent d6d4dfeaf98ef73310e931708f610c309db77dc2
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date: Mon, 1 Jun 2026 17:54:53 -0500
revert: remove monitor-via-mixer fader from the UI (device-safe)
The mix-insert output fader was the only thing that could corrupt routing and
wedge the device (SET_MUX 0x3). Removed it from the Monitor tab + app state:
monitor_key now only does mute/dim; panel shows the read-only hardware knob +
mute/dim. Confirmed with the user this device has no writable output volume and
the ADAT loudness is downstream (external converter / monitor knobs), not
fixable in software. Core group/route methods kept (used by spike diagnostics).
Everything else intact: routing editor, presets, per-input meters, themes,
input toggles, mixer + clear. 67 tests, 0 warnings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat:
2 files changed, 7 insertions(+), 209 deletions(-)
diff --git a/valentine/src/main.rs b/valentine/src/main.rs
@@ -162,16 +162,6 @@ struct App {
modal: Modal,
/// Inputs panel: stereo-pair view (default) vs. one row per channel.
stereo_inputs: bool,
- /// Which monitor group is selected on the Monitor tab (index into
- /// `scarlett_core::matrix::MONITOR_GROUPS`: 0 = Analogue 1-2, 1 = ADAT 1-8).
- monitor_group: usize,
- /// Per-group software level in dB (the group's mix-bus gain when via mixer).
- group_level_db: Vec<f32>,
- /// Per-group: is it currently routed through the mixer (fader active)?
- group_via_mixer: Vec<bool>,
- /// Group index whose level changed in-memory and needs a device write on the
- /// next frame (coalesces rapid fader keypresses into one write).
- pending_group_write: Option<usize>,
status: Option<String>,
show_help: bool,
last_poll: Instant,
@@ -203,10 +193,6 @@ impl App {
routing_pending: None,
modal: Modal::None,
stereo_inputs: true,
- monitor_group: 0,
- group_level_db: vec![0.0; scarlett_core::matrix::MONITOR_GROUPS.len()],
- group_via_mixer: vec![false; scarlett_core::matrix::MONITOR_GROUPS.len()],
- pending_group_write: None,
status: None,
show_help: false,
last_poll: Instant::now(),
@@ -312,24 +298,6 @@ impl App {
/// Lazy-load data a freshly-opened tab needs.
fn on_tab_enter(&mut self) {
- if self.tab == 1 {
- // Detect which monitor groups are already routed via the mixer, and
- // seed each fader from the device's actual current level so it's live
- // and accurate without any toggle.
- let pc = scarlett_core::mux::PORT_COUNT_18I20_GEN3;
- let inputs = S18I20_GEN3.mixer_inputs() as usize;
- if let Ok(dev) = &mut self.device {
- for (i, g) in scarlett_core::matrix::MONITOR_GROUPS.iter().enumerate() {
- let via = dev.scarlett.group_is_via_mixer(pc, g).unwrap_or(false);
- self.group_via_mixer[i] = via;
- if via {
- if let Ok(db) = dev.scarlett.get_group_level(g, inputs) {
- self.group_level_db[i] = db;
- }
- }
- }
- }
- }
if self.tab == 2 {
if let Ok(dev) = &mut self.device {
dev.load_mixer();
@@ -645,138 +613,14 @@ impl App {
}
fn monitor_key(&mut self, code: KeyCode) {
- let ngroups = scarlett_core::matrix::MONITOR_GROUPS.len();
match code {
KeyCode::Up | KeyCode::Char('k') => self.monitor_cursor.up(),
KeyCode::Down | KeyCode::Char('j') => self.monitor_cursor.down(),
KeyCode::Char(' ') | KeyCode::Enter => self.toggle_monitor_button(),
- // Tab/[ ] selects which monitor group the fader controls.
- KeyCode::Char('[') => {
- self.monitor_group = self.monitor_group.saturating_sub(1);
- }
- KeyCode::Char(']') => {
- if self.monitor_group + 1 < ngroups {
- self.monitor_group += 1;
- }
- }
- // Software fader for the selected group (works when via mixer).
- KeyCode::Left | KeyCode::Char('-') => self.nudge_group(-2.0),
- KeyCode::Right | KeyCode::Char('=') | KeyCode::Char('+') => self.nudge_group(2.0),
- // Toggle routing the selected group through the mixer.
- KeyCode::Char('v') => self.toggle_group_via_mixer(),
_ => {}
}
}
- /// Adjust the selected group's software level by `delta` dB.
- ///
- /// The fader OWNS the routing: if the group isn't yet routed through the
- /// mixer, the first nudge routes it (so the fader always works without the
- /// user having to toggle `v` first). The level change itself only updates the
- /// in-memory value + flags a pending write, coalesced once per frame in
- /// [`Self::flush_pending_writes`].
- fn nudge_group(&mut self, delta: f32) {
- let gi = self.monitor_group;
- // Ensure the group is routed via the mixer so the fader has an effect.
- if !self.group_via_mixer.get(gi).copied().unwrap_or(false) {
- self.enable_group_via_mixer(gi);
- if !self.group_via_mixer.get(gi).copied().unwrap_or(false) {
- return; // routing failed; status already set
- }
- }
- let g = &scarlett_core::matrix::MONITOR_GROUPS[gi];
- let new_db =
- (self.group_level_db[gi] + delta).clamp(scarlett_core::matrix::MIXER_MIN_DB, 0.0);
- self.group_level_db[gi] = new_db;
- self.pending_group_write = Some(gi);
- self.status = Some(format!("{} level {new_db:.0} dB", g.name));
- }
-
- /// Route a group through the mixer and seed its bus gains at the current
- /// in-memory level. Idempotent-ish; sets `group_via_mixer[gi]` on success.
- fn enable_group_via_mixer(&mut self, gi: usize) {
- let g = &scarlett_core::matrix::MONITOR_GROUPS[gi];
- let pc = scarlett_core::mux::PORT_COUNT_18I20_GEN3;
- let inputs = S18I20_GEN3.mixer_inputs() as usize;
- let level = self.group_level_db[gi];
- let dev = match &mut self.device {
- Ok(d) => d,
- Err(_) => return,
- };
- match dev
- .scarlett
- .route_group_via_mixer(pc, g)
- .and_then(|st| {
- dev.routing = Some(st);
- dev.scarlett.set_group_level(g, level, inputs)
- }) {
- Ok(()) => {
- self.group_via_mixer[gi] = true;
- dev.mixer.clear();
- dev.refresh_src_meter();
- }
- Err(e) => self.status = Some(format!("routing failed: {e}")),
- }
- }
-
- /// Write any pending group-level change to the device (once per frame).
- fn flush_pending_writes(&mut self) {
- let gi = match self.pending_group_write.take() {
- Some(g) => g,
- None => return,
- };
- let g = &scarlett_core::matrix::MONITOR_GROUPS[gi];
- let inputs = S18I20_GEN3.mixer_inputs() as usize;
- let db = self.group_level_db[gi];
- let dev = match &mut self.device {
- Ok(d) => d,
- Err(_) => return,
- };
- if let Err(e) = dev.scarlett.set_group_level(g, db, inputs) {
- self.status = Some(format!("level set failed: {e}"));
- } else {
- dev.mixer.clear();
- }
- }
-
- /// Toggle whether the selected group runs through the mixer (fader on) or
- /// straight from the DAW.
- fn toggle_group_via_mixer(&mut self) {
- let gi = self.monitor_group;
- let g = &scarlett_core::matrix::MONITOR_GROUPS[gi];
- let pc = scarlett_core::mux::PORT_COUNT_18I20_GEN3;
- let inputs = S18I20_GEN3.mixer_inputs() as usize;
- let enable = !self.group_via_mixer.get(gi).copied().unwrap_or(false);
- let level = self.group_level_db[gi];
- let dev = match &mut self.device {
- Ok(d) => d,
- Err(_) => return,
- };
- let res = if enable {
- dev.scarlett.route_group_via_mixer(pc, g).and_then(|st| {
- dev.routing = Some(st);
- dev.scarlett.set_group_level(g, level, inputs)
- })
- } else {
- dev.scarlett.route_group_direct(pc, g).map(|st| {
- dev.routing = Some(st);
- })
- };
- match res {
- Ok(()) => {
- self.group_via_mixer[gi] = enable;
- dev.mixer.clear();
- dev.refresh_src_meter();
- self.status = Some(if enable {
- format!("{} via mixer ON — ←→ adjusts level", g.name)
- } else {
- format!("{} direct from DAW (fader off)", g.name)
- });
- }
- Err(e) => self.status = Some(format!("routing failed: {e}")),
- }
- }
-
fn toggle_monitor_button(&mut self) {
let row = self.monitor_cursor.current();
let dev = match &mut self.device {
@@ -1194,7 +1038,6 @@ fn run<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {
}
}
}
- app.flush_pending_writes();
}
app.tick();
}
@@ -1260,18 +1103,7 @@ fn ui(f: &mut Frame, app: &App) {
);
}
(Ok(dev), 1) => {
- let gi = app.monitor_group;
- monitor::render(
- f,
- chunks[2],
- t,
- &dev.monitor,
- app.monitor_cursor,
- scarlett_core::matrix::MONITOR_GROUPS[gi].name,
- app.group_via_mixer[gi],
- app.group_level_db[gi],
- true,
- );
+ monitor::render(f, chunks[2], t, &dev.monitor, app.monitor_cursor, true);
}
(Ok(dev), 2) => {
mixer::render(f, chunks[2], t, &dev.mixer, app.mixer_cursor, true);
diff --git a/valentine/src/panels/monitor.rs b/valentine/src/panels/monitor.rs
@@ -63,24 +63,17 @@ fn row_on(state: &MonitorState, row: Row) -> bool {
}
}
-#[allow(clippy::too_many_arguments)]
pub fn render(
f: &mut Frame,
area: Rect,
theme: &Theme,
state: &MonitorState,
cursor: Cursor,
- group_name: &str,
- via_mixer: bool,
- soft_db: f32,
focused: bool,
) {
let border = if focused { theme.border_focus } else { theme.border };
let block = Block::default()
- .title(Span::styled(
- format!(" monitor · {group_name} "),
- Style::default().fg(theme.accent),
- ))
+ .title(Span::styled(" monitor ", Style::default().fg(theme.accent)))
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(border));
@@ -91,16 +84,14 @@ pub fn render(
Constraint::Length(1), // hardware-knob label
Constraint::Length(1), // hardware-knob gauge
Constraint::Length(1), // spacer
- Constraint::Length(1), // software fader label
- Constraint::Length(1), // software fader gauge
- Constraint::Length(1), // spacer
Constraint::Length(1), // mute
Constraint::Length(1), // dim
Constraint::Min(0), // help
])
.split(inner);
- // Hardware monitor knob (read-only).
+ // Hardware monitor knob (read-only — this device exposes no writable output
+ // volume; the physical knob and your monitors/converter set the level).
let db = state.master_db;
let ratio = ((db + VOLUME_BIAS) as f64 / VOLUME_BIAS as f64).clamp(0.0, 1.0);
f.render_widget(
@@ -119,31 +110,6 @@ pub fn render(
rows[1],
);
- // Software monitor fader — ←→ just works (auto-routes via mixer on use).
- let fader_label = if via_mixer {
- "monitor fader ←→ adjust:"
- } else {
- "monitor fader ←→ adjust (routes via mixer on first use):"
- };
- f.render_widget(
- Paragraph::new(Span::styled(
- fader_label,
- Style::default().fg(theme.fg),
- )),
- rows[3],
- );
- // Always show the fader bar; dimmed until it's driving the mixer path.
- let min = scarlett_core::matrix::MIXER_MIN_DB;
- let r = ((soft_db - min) / -min).clamp(0.0, 1.0) as f64;
- let bar_fg = if via_mixer { theme.armed } else { theme.fg_dim };
- f.render_widget(
- Gauge::default()
- .gauge_style(Style::default().fg(bar_fg).bg(theme.bg_elevated))
- .ratio(r)
- .label(format!("{soft_db:.0} dB")),
- rows[4],
- );
-
// Toggle rows (mute / dim).
for (i, row) in Row::ALL.iter().enumerate() {
let row = *row;
@@ -169,14 +135,14 @@ pub fn render(
},
),
]);
- f.render_widget(Paragraph::new(line), rows[6 + i]);
+ f.render_widget(Paragraph::new(line), rows[3 + i]);
}
f.render_widget(
Paragraph::new(Span::styled(
- "↑↓ select space toggle ←→ fader v via-mixer [ ] group",
+ "↑↓ select space/enter toggle",
Style::default().fg(theme.fg_dim),
)),
- rows[8],
+ rows[5],
);
}