aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-02 13:28:23 +0100
committersalmonik <salmonik@tuta.io>2026-11-02 13:28:23 +0100
commit6e62ee4057b8ea3144f2f4df85e5d56c37c8fd72 (patch)
tree5cc559e2eaad47f632294914a9d57133e863b1b8 /src/config.rs
parentconfig: parse the config once and reuse it until the file changes (diff)
downloadrsplayer-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.rs35
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(())