fix(preview): keep preview sink volume in sync with player slider (#498) (#502)

* fix(preview): keep preview sink volume in sync with player slider (#498)

The Rust preview sink had its volume set once at audio_preview_play and
then never updated. audio_set_volume only ramps the main sink, so slider
movements during a preview had zero effect on the preview level. With
the default loudness normalization (-4.5 dB pre-analysis attenuation)
applied at start, even a 100% slider gives 1.0 × 0.596 × MASTER_HEADROOM
≈ 53% — matching the user-visible "fixed at around 50%" symptom.

- Add audio_preview_set_volume Rust command that updates the preview
  sink if one is active (clamp + master headroom mirror the path used
  in audio_preview_play).
- Extract the preview-volume calculation in previewStore into
  computePreviewVolume() so startPreview and the new sync path share
  one formula (slider value, plus the LUFS pre-analysis attenuation
  the engine already applies to the main sink).
- Subscribe to playerStore at module level: when volume changes and a
  preview is active, push the recomputed value to Rust. Auth /
  normalization tweaks during preview are intentionally not synced —
  preview is short and that case is rare.

Reported by netherguy4.

* docs: changelog entry for PR #502

Logs the preview-volume-slider sync fix in v1.46.0 "## Fixed".
This commit is contained in:
Frank Stellmacher
2026-05-07 19:10:18 +02:00
committed by GitHub
parent 3cabb64dbc
commit d7ff1d3113
4 changed files with 52 additions and 13 deletions
+12
View File
@@ -286,6 +286,18 @@ pub fn audio_preview_stop_silent(app: AppHandle, state: State<'_, AudioEngine>)
preview_stop_inner(&app, &state, false);
}
/// Update the preview sink volume while a preview is in flight. Mirrors
/// `audio_set_volume` for the main sink. The frontend already folds in any
/// LUFS pre-analysis attenuation before calling, just like it does at preview
/// start, so the engine just clamps and applies the master headroom. No-op
/// when no preview is active.
#[tauri::command]
pub fn audio_preview_set_volume(volume: f32, state: State<'_, AudioEngine>) {
if let Some(sink) = state.preview_sink.lock().unwrap().as_ref() {
sink.set_volume((volume.clamp(0.0, 1.0) * MASTER_HEADROOM).clamp(0.0, 1.0));
}
}
pub(crate) fn preview_stop_inner(app: &AppHandle, state: &AudioEngine, resume_main: bool) {
state.preview_gen.fetch_add(1, Ordering::SeqCst);
let sink = state.preview_sink.lock().unwrap().take();
+1
View File
@@ -915,6 +915,7 @@ pub fn run() {
audio::preview::audio_preview_play,
audio::preview::audio_preview_stop,
audio::preview::audio_preview_stop_silent,
audio::preview::audio_preview_set_volume,
audio::commands::audio_set_crossfade,
audio::commands::audio_set_gapless,
audio::commands::audio_set_normalization,