librespot_playback/
config.rs

1use std::{mem, path::PathBuf, str::FromStr, time::Duration};
2
3pub use crate::dither::{DithererBuilder, TriangularDitherer, mk_ditherer};
4use crate::{convert::i24, player::duration_to_coefficient};
5
6#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Default)]
7pub enum Bitrate {
8    Bitrate96,
9    #[default]
10    Bitrate160,
11    Bitrate320,
12}
13
14impl FromStr for Bitrate {
15    type Err = ();
16    fn from_str(s: &str) -> Result<Self, Self::Err> {
17        match s {
18            "96" => Ok(Self::Bitrate96),
19            "160" => Ok(Self::Bitrate160),
20            "320" => Ok(Self::Bitrate320),
21            _ => Err(()),
22        }
23    }
24}
25
26#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Default)]
27pub enum AudioFormat {
28    F64,
29    F32,
30    S32,
31    S24,
32    S24_3,
33    #[default]
34    S16,
35}
36
37impl FromStr for AudioFormat {
38    type Err = ();
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        match s.to_uppercase().as_ref() {
41            "F64" => Ok(Self::F64),
42            "F32" => Ok(Self::F32),
43            "S32" => Ok(Self::S32),
44            "S24" => Ok(Self::S24),
45            "S24_3" => Ok(Self::S24_3),
46            "S16" => Ok(Self::S16),
47            _ => Err(()),
48        }
49    }
50}
51
52impl AudioFormat {
53    // not used by all backends
54    #[allow(dead_code)]
55    pub fn size(&self) -> usize {
56        match self {
57            Self::F64 => mem::size_of::<f64>(),
58            Self::F32 => mem::size_of::<f32>(),
59            Self::S24_3 => mem::size_of::<i24>(),
60            Self::S16 => mem::size_of::<i16>(),
61            _ => mem::size_of::<i32>(), // S32 and S24 are both stored in i32
62        }
63    }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
67pub enum NormalisationType {
68    Album,
69    Track,
70    #[default]
71    Auto,
72}
73
74impl FromStr for NormalisationType {
75    type Err = ();
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        match s.to_lowercase().as_ref() {
78            "album" => Ok(Self::Album),
79            "track" => Ok(Self::Track),
80            "auto" => Ok(Self::Auto),
81            _ => Err(()),
82        }
83    }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
87pub enum NormalisationMethod {
88    Basic,
89    #[default]
90    Dynamic,
91}
92
93impl FromStr for NormalisationMethod {
94    type Err = ();
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        match s.to_lowercase().as_ref() {
97            "basic" => Ok(Self::Basic),
98            "dynamic" => Ok(Self::Dynamic),
99            _ => Err(()),
100        }
101    }
102}
103
104#[derive(Clone)]
105pub struct PlayerConfig {
106    pub bitrate: Bitrate,
107    pub gapless: bool,
108    pub passthrough: bool,
109
110    pub normalisation: bool,
111    pub normalisation_type: NormalisationType,
112    pub normalisation_method: NormalisationMethod,
113    pub normalisation_pregain_db: f64,
114    pub normalisation_threshold_dbfs: f64,
115    pub normalisation_attack_cf: f64,
116    pub normalisation_release_cf: f64,
117    pub normalisation_knee_db: f64,
118
119    pub local_file_directories: Vec<PathBuf>,
120
121    // pass function pointers so they can be lazily instantiated *after* spawning a thread
122    // (thereby circumventing Send bounds that they might not satisfy)
123    pub ditherer: Option<DithererBuilder>,
124    /// Setting this will enable periodically sending events during playback informing about the playback position
125    /// To consume the PlayerEvent::PositionChanged event, listen to events via `Player::get_player_event_channel()``
126    pub position_update_interval: Option<Duration>,
127}
128
129impl Default for PlayerConfig {
130    fn default() -> Self {
131        Self {
132            bitrate: Bitrate::default(),
133            gapless: true,
134            normalisation: false,
135            normalisation_type: NormalisationType::default(),
136            normalisation_method: NormalisationMethod::default(),
137            normalisation_pregain_db: 0.0,
138            normalisation_threshold_dbfs: -2.0,
139            normalisation_attack_cf: duration_to_coefficient(Duration::from_millis(5)),
140            normalisation_release_cf: duration_to_coefficient(Duration::from_millis(100)),
141            normalisation_knee_db: 5.0,
142            passthrough: false,
143            ditherer: Some(mk_ditherer::<TriangularDitherer>),
144            position_update_interval: None,
145            local_file_directories: Vec::new(),
146        }
147    }
148}
149
150// fields are intended for volume control range in dB
151#[derive(Clone, Copy, Debug)]
152pub enum VolumeCtrl {
153    Cubic(f64),
154    Fixed,
155    Linear,
156    Log(f64),
157}
158
159impl FromStr for VolumeCtrl {
160    type Err = ();
161    fn from_str(s: &str) -> Result<Self, Self::Err> {
162        Self::from_str_with_range(s, Self::DEFAULT_DB_RANGE)
163    }
164}
165
166impl Default for VolumeCtrl {
167    fn default() -> VolumeCtrl {
168        VolumeCtrl::Log(Self::DEFAULT_DB_RANGE)
169    }
170}
171
172impl VolumeCtrl {
173    pub const MAX_VOLUME: u16 = u16::MAX;
174
175    // Taken from: https://www.dr-lex.be/info-stuff/volumecontrols.html
176    pub const DEFAULT_DB_RANGE: f64 = 60.0;
177
178    pub fn from_str_with_range(s: &str, db_range: f64) -> Result<Self, <Self as FromStr>::Err> {
179        use self::VolumeCtrl::*;
180        match s.to_lowercase().as_ref() {
181            "cubic" => Ok(Cubic(db_range)),
182            "fixed" => Ok(Fixed),
183            "linear" => Ok(Linear),
184            "log" => Ok(Log(db_range)),
185            _ => Err(()),
186        }
187    }
188}