use std::num::{NonZeroU32, NonZeroUsize};
use crate::config::{BackendKind, DownloadConfig};
use mineral_audio::{AudioMode, EngineParams, EnvelopeParams, HighpassParams, ShelfParams};
use mineral_model::BitRate;
#[non_exhaustive]
#[derive(Clone, Debug, derive_getters::Getters)]
pub struct ServerConfig {
engine: EngineParams,
envelope: EnvelopeParams,
playback_quality: BitRate,
audio_cache_capacity: u64,
channel_workers_per: usize,
download: DownloadConfig,
#[getter(skip)]
timing: ServerTiming,
hook_timeout_ms: u64,
favorites_backfill_chunk_size: usize,
favorites_backfill_max_concurrent: usize,
}
impl ServerConfig {
pub fn from_config(cfg: &crate::config::DaemonConfig) -> Self {
let audio = cfg.audio();
Self {
engine: EngineParams::builder()
.initial_volume(*audio.volume())
.tick_ms(*audio.engine_tick_ms())
.prefetch_bytes(*audio.prefetch_bytes())
.tap_capacity(*audio.tap_capacity())
.build(),
envelope: envelope_params_from(audio.envelope()),
playback_quality: *audio.playback_quality(),
audio_cache_capacity: *cfg.cache().audio_capacity(),
channel_workers_per: *cfg.channel_workers_per(),
download: cfg.download().clone(),
timing: ServerTiming {
gapless_prefetch_ms: *cfg.gapless_prefetch_ms(),
prev_restart_threshold_ms: *cfg.prev_restart_threshold_ms(),
player_tick_ms: *cfg.player_tick_ms(),
session_save_secs: *cfg.session_save_secs(),
heartbeat_secs: *cfg.heartbeat_secs(),
report_interval_ms: *cfg.report_interval_ms(),
seek_threshold_ms: *cfg.seek_threshold_ms(),
download_speed_tick_ms: *cfg.download_speed_tick_ms(),
},
hook_timeout_ms: *cfg.script().hook_timeout_ms(),
favorites_backfill_chunk_size: *cfg.sources().mineral().backfill().chunk_size(),
favorites_backfill_max_concurrent: *cfg.sources().mineral().backfill().max_concurrent(),
}
}
pub(crate) fn timing(&self) -> &ServerTiming {
&self.timing
}
}
#[derive(Clone, Debug, derive_getters::Getters)]
pub(crate) struct ServerTiming {
gapless_prefetch_ms: u64,
prev_restart_threshold_ms: u64,
player_tick_ms: u64,
session_save_secs: u64,
heartbeat_secs: u64,
report_interval_ms: u64,
seek_threshold_ms: u64,
download_speed_tick_ms: u64,
}
fn envelope_params_from(cfg: &crate::config::EnvelopeConfig) -> EnvelopeParams {
EnvelopeParams::builder()
.point_count(NonZeroUsize::new(*cfg.points()).unwrap_or(NonZeroUsize::MIN))
.block_ms(NonZeroU32::new(*cfg.block_ms()).unwrap_or(NonZeroU32::MIN))
.window_ms(NonZeroU32::new(*cfg.window_ms()).unwrap_or(NonZeroU32::MIN))
.shelf(
ShelfParams::builder()
.f0_hz(*cfg.shelf().f0_hz())
.gain_db(*cfg.shelf().gain_db())
.q(*cfg.shelf().q())
.band_exponent(*cfg.shelf().band_exponent())
.build(),
)
.highpass(
HighpassParams::builder()
.f0_hz(*cfg.highpass().f0_hz())
.q(*cfg.highpass().q())
.build(),
)
.build()
}
pub fn resolve_audio_mode(env_null: bool, backend: BackendKind) -> AudioMode {
if env_null {
return AudioMode::ForceNull;
}
match backend {
BackendKind::Null => AudioMode::ForceNull,
BackendKind::Auto => AudioMode::Auto,
}
}
#[cfg(test)]
mod tests {
use crate::config::BackendKind;
use mineral_audio::AudioMode;
use super::{ServerConfig, resolve_audio_mode};
#[test]
fn server_config_defaults_snapshot() -> color_eyre::Result<()> {
let cfg = crate::config::DaemonConfig::defaults()?;
mineral_test::assert_snap_debug!(
"ServerConfig(daemon-default.lua → daemon 启动切片)",
ServerConfig::from_config(&cfg)
);
Ok(())
}
#[test]
fn resolve_audio_mode_matrix() {
assert_eq!(
resolve_audio_mode( true, BackendKind::Auto),
AudioMode::ForceNull
);
assert_eq!(
resolve_audio_mode( true, BackendKind::Null),
AudioMode::ForceNull
);
assert_eq!(
resolve_audio_mode( false, BackendKind::Null),
AudioMode::ForceNull
);
assert_eq!(
resolve_audio_mode( false, BackendKind::Auto),
AudioMode::Auto
);
}
}