From 6f7ff9956cefeaeb3e888722510c2148fd44d0ce Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:27:07 -0600 Subject: [PATCH] feat(audio): surface "JACK is not bit-perfect" in Audio settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JACK routes through the JACK graph (resamples to the graph rate), so it can't be bit-perfect — but nothing told the user. Add a `backend-is-jack` flag (settings.rs computes it beside alsa/pipewire at both set-sites; state.slint property) and a WarningBanner in AudioSettings, gated on it, pointing at ALSA-direct / PipeWire-passthrough for bit-perfect. UI-only; backend behavior unchanged. Blind change (no build this session) — validates on the next qbz_ui build. cc --- crates/qbz-ui/ui/settings/AudioSettings.slint | 8 ++++++++ crates/qbz-ui/ui/state.slint | 1 + crates/qbz/src/settings.rs | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/crates/qbz-ui/ui/settings/AudioSettings.slint b/crates/qbz-ui/ui/settings/AudioSettings.slint index b819a4c70..0bdabcc50 100644 --- a/crates/qbz-ui/ui/settings/AudioSettings.slint +++ b/crates/qbz-ui/ui/settings/AudioSettings.slint @@ -10,6 +10,7 @@ import { SettingsState, DacWizardActions } from "../state.slint"; import { QbzToggle } from "../primitives/QbzToggle.slint"; import { QbzSelect } from "../primitives/QbzSelect.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint"; +import { WarningBanner } from "../primitives/WarningBanner.slint"; import { SettingRow } from "SettingRow.slint"; component GroupHeader inherits Text { @@ -95,6 +96,13 @@ export component AudioSettings inherits VerticalLayout { } } } + // JACK routes through the JACK graph (resamples to the graph rate), so it + // can't be bit-perfect — surface that so the user isn't misled. + if SettingsState.backend-is-jack: WarningBanner { + variant: "warning"; + title: @tr("JACK is not bit-perfect"); + body: @tr("The JACK backend routes audio through the JACK graph, which resamples to the graph's sample rate. For bit-perfect playback use ALSA (direct) or PipeWire with passthrough."); + } SettingRow { label: @tr("Output device"); description: @tr("The DAC or sound device that receives audio."); diff --git a/crates/qbz-ui/ui/state.slint b/crates/qbz-ui/ui/state.slint index 5c8302fb3..093d76a5e 100644 --- a/crates/qbz-ui/ui/state.slint +++ b/crates/qbz-ui/ui/state.slint @@ -2804,6 +2804,7 @@ export global SettingsState { // Audio — Rust-computed conditional flags driving `if`-gated rows. in-out property backend-is-alsa: false; in-out property backend-is-pipewire: false; + in-out property backend-is-jack: false; in-out property alsa-plugin-is-hw: false; // --- PLAYBACK ---------------------------------------------------------- diff --git a/crates/qbz/src/settings.rs b/crates/qbz/src/settings.rs index 370ac6d8c..e0598df06 100644 --- a/crates/qbz/src/settings.rs +++ b/crates/qbz/src/settings.rs @@ -147,6 +147,7 @@ pub struct SettingsSnapshot { // Audio — conditional flags. backend_is_alsa: bool, backend_is_pipewire: bool, + backend_is_jack: bool, alsa_plugin_is_hw: bool, // Playback. continue_playback: bool, @@ -481,6 +482,7 @@ fn build_snapshot( let backend_is_alsa = active_backend == AudioBackendType::Alsa; let backend_is_pipewire = active_backend == AudioBackendType::PipeWire; + let backend_is_jack = active_backend == AudioBackendType::Jack; let alsa_plugin_is_hw = alsa_plugin == AlsaPlugin::Hw; let (out_backend_label, out_mode_label, out_backend_active, out_mode_active) = output_labels(&audio); @@ -525,6 +527,7 @@ fn build_snapshot( skip_sink_switch: audio.skip_sink_switch, backend_is_alsa, backend_is_pipewire, + backend_is_jack, alsa_plugin_is_hw, continue_playback, show_context_icon: prefs.show_context_icon, @@ -596,6 +599,7 @@ pub fn apply_snapshot(window: &AppWindow, snap: SettingsSnapshot) { // Audio — conditional flags. st.set_backend_is_alsa(snap.backend_is_alsa); st.set_backend_is_pipewire(snap.backend_is_pipewire); + st.set_backend_is_jack(snap.backend_is_jack); st.set_alsa_plugin_is_hw(snap.alsa_plugin_is_hw); // Playback. st.set_continue_playback(snap.continue_playback); @@ -721,6 +725,7 @@ fn push_conditional_flags(ctx: &SettingsCtx, weak: &slint::Weak) { let plugin = audio.alsa_plugin.unwrap_or(AlsaPlugin::Hw); let is_alsa = backend == AudioBackendType::Alsa; let is_pipewire = backend == AudioBackendType::PipeWire; + let is_jack = backend == AudioBackendType::Jack; let plugin_is_hw = plugin == AlsaPlugin::Hw; let plugin_index = ALSA_PLUGINS .iter() @@ -730,6 +735,7 @@ fn push_conditional_flags(ctx: &SettingsCtx, weak: &slint::Weak) { let st = w.global::(); st.set_backend_is_alsa(is_alsa); st.set_backend_is_pipewire(is_pipewire); + st.set_backend_is_jack(is_jack); st.set_alsa_plugin_is_hw(plugin_is_hw); st.set_alsa_plugin_index(plugin_index); });