diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-02 13:28:23 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-02 13:28:23 +0100 |
| commit | 6e62ee4057b8ea3144f2f4df85e5d56c37c8fd72 (patch) | |
| tree | 5cc559e2eaad47f632294914a9d57133e863b1b8 /src/config.rs | |
| parent | config: parse the config once and reuse it until the file changes (diff) | |
| download | rsplayer-6e62ee4057b8ea3144f2f4df85e5d56c37c8fd72.tar.gz rsplayer-6e62ee4057b8ea3144f2f4df85e5d56c37c8fd72.zip | |
audio: open the output at the track's rate instead of pw-alsa's 44100
cpal asks the ALSA "default" PCM for its default config and pipewire-alsa
answers 44.1 kHz no matter what the graph runs at, so 48 kHz material was
resampled down to 44.1 in our linear resampler and then back up to 48 by
PipeWire. The stream now opens at the track's own rate (when the device
supports it), which keeps our resampler out of the path and leaves the
conversion to PipeWire's polyphase one.
The new `sample_rate` setting forces a fixed rate instead ("auto" by
default), for anyone who would rather match the graph exactly.
Diffstat (limited to 'src/config.rs')
| -rw-r--r-- | src/config.rs | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs index a08f01f..52afd92 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -25,6 +25,8 @@ pub struct Config { | |||
| 25 | pub volume: Option<u8>, | 25 | pub volume: Option<u8>, |
| 26 | pub shuffle: Option<String>, | 26 | pub shuffle: Option<String>, |
| 27 | pub display_format: Option<String>, | 27 | pub display_format: Option<String>, |
| 28 | /// Output sample rate: `None` follows the track, `Some` forces a rate. | ||
| 29 | pub sample_rate: Option<String>, | ||
| 28 | } | 30 | } |
| 29 | 31 | ||
| 30 | /// Identity of the config file on disk, used to tell whether a cached copy is | 32 | /// Identity of the config file on disk, used to tell whether a cached copy is |
| @@ -64,6 +66,7 @@ impl Config { | |||
| 64 | volume: Some(50), | 66 | volume: Some(50), |
| 65 | shuffle: None, | 67 | shuffle: None, |
| 66 | display_format: Some(String::from("$ARTIST - $TRACK")), | 68 | display_format: Some(String::from("$ARTIST - $TRACK")), |
| 69 | sample_rate: None, | ||
| 67 | }; | 70 | }; |
| 68 | default.save()?; | 71 | default.save()?; |
| 69 | return Ok(default); | 72 | return Ok(default); |
| @@ -192,6 +195,8 @@ impl Config { | |||
| 192 | output.push_str(&format!("shuffle = \"{}\"\n", shuffle)); | 195 | output.push_str(&format!("shuffle = \"{}\"\n", shuffle)); |
| 193 | let fmt = self.display_format.clone().unwrap_or_default(); | 196 | let fmt = self.display_format.clone().unwrap_or_default(); |
| 194 | output.push_str(&format!("display_format = \"{}\"\n", fmt)); | 197 | output.push_str(&format!("display_format = \"{}\"\n", fmt)); |
| 198 | let sample_rate = self.sample_rate.as_deref().unwrap_or("auto"); | ||
| 199 | output.push_str(&format!("sample_rate = \"{}\"\n", sample_rate)); | ||
| 195 | if let Some(parent) = self.config_path.parent() { | 200 | if let Some(parent) = self.config_path.parent() { |
| 196 | fs::create_dir_all(parent)?; | 201 | fs::create_dir_all(parent)?; |
| 197 | } | 202 | } |
| @@ -207,6 +212,11 @@ impl Config { | |||
| 207 | Ok(()) | 212 | Ok(()) |
| 208 | } | 213 | } |
| 209 | 214 | ||
| 215 | /// Forced output rate, or `None` to follow each track. | ||
| 216 | pub fn sample_rate_hz(&self) -> Option<u32> { | ||
| 217 | self.sample_rate.as_deref().and_then(|s| s.parse().ok()) | ||
| 218 | } | ||
| 219 | |||
| 210 | pub fn get(&self, key: &str) -> Result<String, Box<dyn std::error::Error>> { | 220 | pub fn get(&self, key: &str) -> Result<String, Box<dyn std::error::Error>> { |
| 211 | match key { | 221 | match key { |
| 212 | "music_paths" => Ok(self | 222 | "music_paths" => Ok(self |
| @@ -252,6 +262,10 @@ impl Config { | |||
| 252 | .display_format | 262 | .display_format |
| 253 | .clone() | 263 | .clone() |
| 254 | .unwrap_or_else(|| "not set".to_string())), | 264 | .unwrap_or_else(|| "not set".to_string())), |
| 265 | "sample_rate" => Ok(self | ||
| 266 | .sample_rate | ||
| 267 | .clone() | ||
| 268 | .unwrap_or_else(|| "auto".to_string())), | ||
| 255 | _ => Err(format!("Unknown setting: {}", key).into()), | 269 | _ => Err(format!("Unknown setting: {}", key).into()), |
| 256 | } | 270 | } |
| 257 | } | 271 | } |
| @@ -291,6 +305,20 @@ impl Config { | |||
| 291 | "display_format" => { | 305 | "display_format" => { |
| 292 | self.display_format = Some(value.to_string()); | 306 | self.display_format = Some(value.to_string()); |
| 293 | } | 307 | } |
| 308 | "sample_rate" => { | ||
| 309 | let value = value.trim(); | ||
| 310 | if value.eq_ignore_ascii_case("auto") || value == "off" { | ||
| 311 | self.sample_rate = None; | ||
| 312 | } else { | ||
| 313 | let rate: u32 = value | ||
| 314 | .parse() | ||
| 315 | .map_err(|_| format!("Invalid sample rate: {}", value))?; | ||
| 316 | if !(8_000..=384_000).contains(&rate) { | ||
| 317 | return Err(format!("Sample rate must be 8000-384000 Hz: {}", value).into()); | ||
| 318 | } | ||
| 319 | self.sample_rate = Some(rate.to_string()); | ||
| 320 | } | ||
| 321 | } | ||
| 294 | _ => return Err(format!("Unknown setting: {}", key).into()), | 322 | _ => return Err(format!("Unknown setting: {}", key).into()), |
| 295 | } | 323 | } |
| 296 | Ok(()) | 324 | Ok(()) |
| @@ -357,6 +385,12 @@ impl Config { | |||
| 357 | .clone() | 385 | .clone() |
| 358 | .unwrap_or_else(|| "not set".to_string()) | 386 | .unwrap_or_else(|| "not set".to_string()) |
| 359 | ); | 387 | ); |
| 388 | println!( | ||
| 389 | "sample_rate = {}", | ||
| 390 | self.sample_rate | ||
| 391 | .clone() | ||
| 392 | .unwrap_or_else(|| "auto".to_string()) | ||
| 393 | ); | ||
| 360 | } | 394 | } |
| 361 | 395 | ||
| 362 | pub fn unset(&mut self, key: &str) -> Result<(), Box<dyn std::error::Error>> { | 396 | pub fn unset(&mut self, key: &str) -> Result<(), Box<dyn std::error::Error>> { |
| @@ -370,6 +404,7 @@ impl Config { | |||
| 370 | "volume" => self.volume = None, | 404 | "volume" => self.volume = None, |
| 371 | "shuffle" => self.shuffle = None, | 405 | "shuffle" => self.shuffle = None, |
| 372 | "display_format" => self.display_format = None, | 406 | "display_format" => self.display_format = None, |
| 407 | "sample_rate" => self.sample_rate = None, | ||
| 373 | _ => return Err(format!("Unknown setting: {}", key).into()), | 408 | _ => return Err(format!("Unknown setting: {}", key).into()), |
| 374 | } | 409 | } |
| 375 | Ok(()) | 410 | Ok(()) |
