mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor(audio): extract audio-stage settings into mix_commands
Pull the six configuration setters out of audio/commands.rs into audio/mix_commands.rs (~170 LOC): - audio_set_volume (with replay-gain-aware ramp) - audio_update_replay_gain (resolves cache-backed loudness, computes gain, ramps sink, emits NormalizationStatePayload) - audio_set_eq (10-band gains + pre-gain) - audio_set_crossfade - audio_set_gapless - audio_set_normalization These are pure AudioEngine state mutations + (for normalization) an ipc emit. They don't drive playback; collecting them in one place makes commands.rs more focused on the playback orchestrators. lib.rs invoke_handler updated. audio/commands.rs: 1784 → 1614 LOC.
This commit is contained in:
@@ -1388,125 +1388,8 @@ pub fn audio_seek(seconds: f64, state: State<'_, AudioEngine>) -> Result<(), Str
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_volume(volume: f32, state: State<'_, AudioEngine>) {
|
||||
let mut cur = state.current.lock().unwrap();
|
||||
let prev_effective = (cur.base_volume * cur.replay_gain_linear * MASTER_HEADROOM).clamp(0.0, 1.0);
|
||||
cur.base_volume = volume.clamp(0.0, 1.0);
|
||||
if let Some(sink) = &cur.sink {
|
||||
let next_effective = (cur.base_volume * cur.replay_gain_linear * MASTER_HEADROOM).clamp(0.0, 1.0);
|
||||
ramp_sink_volume(Arc::clone(sink), prev_effective, next_effective);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_update_replay_gain(
|
||||
volume: f32,
|
||||
replay_gain_db: Option<f32>,
|
||||
replay_gain_peak: Option<f32>,
|
||||
loudness_gain_db: Option<f32>,
|
||||
pre_gain_db: f32,
|
||||
fallback_db: f32,
|
||||
app: AppHandle,
|
||||
state: State<'_, AudioEngine>,
|
||||
) {
|
||||
let norm_mode = state.normalization_engine.load(Ordering::Relaxed);
|
||||
let target_lufs = f32::from_bits(state.normalization_target_lufs.load(Ordering::Relaxed));
|
||||
let pre_analysis_db = loudness_pre_analysis_db_for_engine(&state);
|
||||
let url_for_loudness = if norm_mode == 2 {
|
||||
state.current_playback_url.lock().unwrap().clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let logical_for_loudness = state
|
||||
.current_analysis_track_id
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|g| (*g).clone())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
// If `current_playback_url` is not pinned yet, still honour JS `loudness_gain_db`
|
||||
// for the uncached path (`effective_loudness_db` / UI gain follow from `compute_gain`).
|
||||
let cache_loudness = url_for_loudness.as_deref().and_then(|u| {
|
||||
resolve_loudness_gain_from_cache_impl(
|
||||
&app,
|
||||
u,
|
||||
target_lufs,
|
||||
logical_for_loudness.as_deref(),
|
||||
ResolveLoudnessCacheOpts {
|
||||
touch_waveform: false,
|
||||
log_soft_misses: false,
|
||||
},
|
||||
)
|
||||
});
|
||||
let effective_loudness_db = if norm_mode == 2 {
|
||||
match url_for_loudness.as_deref() {
|
||||
Some(_u) => loudness_gain_db_after_resolve(
|
||||
cache_loudness,
|
||||
target_lufs,
|
||||
pre_analysis_db,
|
||||
true,
|
||||
loudness_gain_db,
|
||||
),
|
||||
None => {
|
||||
loudness_gain_db.or(Some(loudness_gain_placeholder_until_cache(
|
||||
target_lufs,
|
||||
pre_analysis_db,
|
||||
)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loudness_gain_db
|
||||
};
|
||||
let (gain_linear, effective) = compute_gain(
|
||||
norm_mode,
|
||||
replay_gain_db,
|
||||
replay_gain_peak,
|
||||
effective_loudness_db,
|
||||
pre_gain_db,
|
||||
fallback_db,
|
||||
volume,
|
||||
);
|
||||
let current_gain_db = loudness_ui_current_gain_db(gain_linear);
|
||||
crate::app_deprintln!(
|
||||
"[normalization] audio_update_replay_gain engine={} replay_gain_db={:?} replay_gain_peak={:?} loudness_gain_db={:?} gain_linear={:.4} current_gain_db={:?} target_lufs={:.2} volume={:.3} effective={:.3}",
|
||||
normalization_engine_name(norm_mode),
|
||||
replay_gain_db,
|
||||
replay_gain_peak,
|
||||
loudness_gain_db,
|
||||
gain_linear,
|
||||
current_gain_db,
|
||||
target_lufs,
|
||||
volume,
|
||||
effective
|
||||
);
|
||||
let mut cur = state.current.lock().unwrap();
|
||||
let prev_effective = (cur.base_volume * cur.replay_gain_linear * MASTER_HEADROOM).clamp(0.0, 1.0);
|
||||
cur.replay_gain_linear = gain_linear;
|
||||
cur.base_volume = volume.clamp(0.0, 1.0);
|
||||
if let Some(sink) = &cur.sink {
|
||||
ramp_sink_volume(Arc::clone(sink), prev_effective, effective);
|
||||
}
|
||||
drop(cur);
|
||||
maybe_emit_normalization_state(
|
||||
&app,
|
||||
NormalizationStatePayload {
|
||||
engine: normalization_engine_name(norm_mode).to_string(),
|
||||
current_gain_db,
|
||||
target_lufs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_eq(gains: [f32; 10], enabled: bool, pre_gain: f32, state: State<'_, AudioEngine>) {
|
||||
state.eq_enabled.store(enabled, Ordering::Relaxed);
|
||||
state.eq_pre_gain.store(pre_gain.clamp(-30.0, 6.0).to_bits(), Ordering::Relaxed);
|
||||
for (i, &gain) in gains.iter().enumerate() {
|
||||
state.eq_gains[i].store(gain.clamp(-12.0, 12.0).to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn audio_preload(
|
||||
@@ -1731,54 +1614,3 @@ pub async fn audio_play_radio(
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_crossfade(enabled: bool, secs: f32, state: State<'_, AudioEngine>) {
|
||||
state.crossfade_enabled.store(enabled, Ordering::Relaxed);
|
||||
state.crossfade_secs.store(secs.clamp(0.1, 12.0).to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_gapless(enabled: bool, state: State<'_, AudioEngine>) {
|
||||
state.gapless_enabled.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_normalization(
|
||||
engine: String,
|
||||
target_lufs: f32,
|
||||
pre_analysis_attenuation_db: f32,
|
||||
app: AppHandle,
|
||||
state: State<'_, AudioEngine>,
|
||||
) {
|
||||
let mode = match engine.as_str() {
|
||||
"replaygain" => 1,
|
||||
"loudness" => 2,
|
||||
_ => 0,
|
||||
};
|
||||
state.normalization_engine.store(mode, Ordering::Relaxed);
|
||||
let target = target_lufs.clamp(-30.0, -8.0);
|
||||
state
|
||||
.normalization_target_lufs
|
||||
.store(target.to_bits(), Ordering::Relaxed);
|
||||
let pre = pre_analysis_attenuation_db.clamp(-24.0, 0.0).min(0.0);
|
||||
state
|
||||
.loudness_pre_analysis_attenuation_db
|
||||
.store(pre.to_bits(), Ordering::Relaxed);
|
||||
crate::app_deprintln!(
|
||||
"[normalization] audio_set_normalization requested_engine={} resolved_engine={} target_lufs={:.2} pre_analysis_db={:.2}",
|
||||
engine,
|
||||
normalization_engine_name(mode),
|
||||
target,
|
||||
pre
|
||||
);
|
||||
maybe_emit_normalization_state(
|
||||
&app,
|
||||
NormalizationStatePayload {
|
||||
engine: normalization_engine_name(mode).to_string(),
|
||||
// At mode-switch time the effective track gain may not be recalculated yet.
|
||||
// Emit `None` and let audio_play/audio_update_replay_gain publish actual value.
|
||||
current_gain_db: None,
|
||||
target_lufs: target,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
//! Audio-stage settings commands: volume, replay-gain / loudness normalization,
|
||||
//! 10-band EQ, crossfade, gapless.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
use super::engine::AudioEngine;
|
||||
use super::helpers::*;
|
||||
use super::ipc::{maybe_emit_normalization_state, NormalizationStatePayload};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_volume(volume: f32, state: State<'_, AudioEngine>) {
|
||||
let mut cur = state.current.lock().unwrap();
|
||||
let prev_effective = (cur.base_volume * cur.replay_gain_linear * MASTER_HEADROOM).clamp(0.0, 1.0);
|
||||
cur.base_volume = volume.clamp(0.0, 1.0);
|
||||
if let Some(sink) = &cur.sink {
|
||||
let next_effective = (cur.base_volume * cur.replay_gain_linear * MASTER_HEADROOM).clamp(0.0, 1.0);
|
||||
ramp_sink_volume(Arc::clone(sink), prev_effective, next_effective);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_update_replay_gain(
|
||||
volume: f32,
|
||||
replay_gain_db: Option<f32>,
|
||||
replay_gain_peak: Option<f32>,
|
||||
loudness_gain_db: Option<f32>,
|
||||
pre_gain_db: f32,
|
||||
fallback_db: f32,
|
||||
app: AppHandle,
|
||||
state: State<'_, AudioEngine>,
|
||||
) {
|
||||
let norm_mode = state.normalization_engine.load(Ordering::Relaxed);
|
||||
let target_lufs = f32::from_bits(state.normalization_target_lufs.load(Ordering::Relaxed));
|
||||
let pre_analysis_db = loudness_pre_analysis_db_for_engine(&state);
|
||||
let url_for_loudness = if norm_mode == 2 {
|
||||
state.current_playback_url.lock().unwrap().clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let logical_for_loudness = state
|
||||
.current_analysis_track_id
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|g| (*g).clone())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
// If `current_playback_url` is not pinned yet, still honour JS `loudness_gain_db`
|
||||
// for the uncached path (`effective_loudness_db` / UI gain follow from `compute_gain`).
|
||||
let cache_loudness = url_for_loudness.as_deref().and_then(|u| {
|
||||
resolve_loudness_gain_from_cache_impl(
|
||||
&app,
|
||||
u,
|
||||
target_lufs,
|
||||
logical_for_loudness.as_deref(),
|
||||
ResolveLoudnessCacheOpts {
|
||||
touch_waveform: false,
|
||||
log_soft_misses: false,
|
||||
},
|
||||
)
|
||||
});
|
||||
let effective_loudness_db = if norm_mode == 2 {
|
||||
match url_for_loudness.as_deref() {
|
||||
Some(_u) => loudness_gain_db_after_resolve(
|
||||
cache_loudness,
|
||||
target_lufs,
|
||||
pre_analysis_db,
|
||||
true,
|
||||
loudness_gain_db,
|
||||
),
|
||||
None => {
|
||||
loudness_gain_db.or(Some(loudness_gain_placeholder_until_cache(
|
||||
target_lufs,
|
||||
pre_analysis_db,
|
||||
)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loudness_gain_db
|
||||
};
|
||||
let (gain_linear, effective) = compute_gain(
|
||||
norm_mode,
|
||||
replay_gain_db,
|
||||
replay_gain_peak,
|
||||
effective_loudness_db,
|
||||
pre_gain_db,
|
||||
fallback_db,
|
||||
volume,
|
||||
);
|
||||
let current_gain_db = loudness_ui_current_gain_db(gain_linear);
|
||||
crate::app_deprintln!(
|
||||
"[normalization] audio_update_replay_gain engine={} replay_gain_db={:?} replay_gain_peak={:?} loudness_gain_db={:?} gain_linear={:.4} current_gain_db={:?} target_lufs={:.2} volume={:.3} effective={:.3}",
|
||||
normalization_engine_name(norm_mode),
|
||||
replay_gain_db,
|
||||
replay_gain_peak,
|
||||
loudness_gain_db,
|
||||
gain_linear,
|
||||
current_gain_db,
|
||||
target_lufs,
|
||||
volume,
|
||||
effective
|
||||
);
|
||||
let mut cur = state.current.lock().unwrap();
|
||||
let prev_effective = (cur.base_volume * cur.replay_gain_linear * MASTER_HEADROOM).clamp(0.0, 1.0);
|
||||
cur.replay_gain_linear = gain_linear;
|
||||
cur.base_volume = volume.clamp(0.0, 1.0);
|
||||
if let Some(sink) = &cur.sink {
|
||||
ramp_sink_volume(Arc::clone(sink), prev_effective, effective);
|
||||
}
|
||||
drop(cur);
|
||||
maybe_emit_normalization_state(
|
||||
&app,
|
||||
NormalizationStatePayload {
|
||||
engine: normalization_engine_name(norm_mode).to_string(),
|
||||
current_gain_db,
|
||||
target_lufs,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_eq(gains: [f32; 10], enabled: bool, pre_gain: f32, state: State<'_, AudioEngine>) {
|
||||
state.eq_enabled.store(enabled, Ordering::Relaxed);
|
||||
state.eq_pre_gain.store(pre_gain.clamp(-30.0, 6.0).to_bits(), Ordering::Relaxed);
|
||||
for (i, &gain) in gains.iter().enumerate() {
|
||||
state.eq_gains[i].store(gain.clamp(-12.0, 12.0).to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_crossfade(enabled: bool, secs: f32, state: State<'_, AudioEngine>) {
|
||||
state.crossfade_enabled.store(enabled, Ordering::Relaxed);
|
||||
state.crossfade_secs.store(secs.clamp(0.1, 12.0).to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_gapless(enabled: bool, state: State<'_, AudioEngine>) {
|
||||
state.gapless_enabled.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn audio_set_normalization(
|
||||
engine: String,
|
||||
target_lufs: f32,
|
||||
pre_analysis_attenuation_db: f32,
|
||||
app: AppHandle,
|
||||
state: State<'_, AudioEngine>,
|
||||
) {
|
||||
let mode = match engine.as_str() {
|
||||
"replaygain" => 1,
|
||||
"loudness" => 2,
|
||||
_ => 0,
|
||||
};
|
||||
state.normalization_engine.store(mode, Ordering::Relaxed);
|
||||
let target = target_lufs.clamp(-30.0, -8.0);
|
||||
state
|
||||
.normalization_target_lufs
|
||||
.store(target.to_bits(), Ordering::Relaxed);
|
||||
let pre = pre_analysis_attenuation_db.clamp(-24.0, 0.0).min(0.0);
|
||||
state
|
||||
.loudness_pre_analysis_attenuation_db
|
||||
.store(pre.to_bits(), Ordering::Relaxed);
|
||||
crate::app_deprintln!(
|
||||
"[normalization] audio_set_normalization requested_engine={} resolved_engine={} target_lufs={:.2} pre_analysis_db={:.2}",
|
||||
engine,
|
||||
normalization_engine_name(mode),
|
||||
target,
|
||||
pre
|
||||
);
|
||||
maybe_emit_normalization_state(
|
||||
&app,
|
||||
NormalizationStatePayload {
|
||||
engine: normalization_engine_name(mode).to_string(),
|
||||
// At mode-switch time the effective track gain may not be recalculated yet.
|
||||
// Emit `None` and let audio_play/audio_update_replay_gain publish actual value.
|
||||
current_gain_db: None,
|
||||
target_lufs: target,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod commands;
|
||||
mod decode;
|
||||
mod dev_io;
|
||||
pub mod device_commands;
|
||||
pub mod mix_commands;
|
||||
mod device_watcher;
|
||||
mod engine;
|
||||
mod power_resume;
|
||||
|
||||
@@ -392,9 +392,9 @@ pub fn run() {
|
||||
audio::commands::audio_resume,
|
||||
audio::commands::audio_stop,
|
||||
audio::commands::audio_seek,
|
||||
audio::commands::audio_set_volume,
|
||||
audio::commands::audio_update_replay_gain,
|
||||
audio::commands::audio_set_eq,
|
||||
audio::mix_commands::audio_set_volume,
|
||||
audio::mix_commands::audio_update_replay_gain,
|
||||
audio::mix_commands::audio_set_eq,
|
||||
audio::autoeq_commands::autoeq_entries,
|
||||
audio::autoeq_commands::autoeq_fetch_profile,
|
||||
audio::commands::audio_preload,
|
||||
@@ -403,9 +403,9 @@ pub fn run() {
|
||||
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,
|
||||
audio::mix_commands::audio_set_crossfade,
|
||||
audio::mix_commands::audio_set_gapless,
|
||||
audio::mix_commands::audio_set_normalization,
|
||||
audio::device_commands::audio_list_devices,
|
||||
audio::device_commands::audio_canonicalize_selected_device,
|
||||
audio::device_commands::audio_default_output_device_name,
|
||||
|
||||
Reference in New Issue
Block a user