Browse Source

Implement mapped volume for alsa mixer

ashthespy 5 years ago
parent
commit
3dfad7f788
3 changed files with 102 additions and 19 deletions
  1. 94 19
      playback/src/mixer/alsamixer.rs
  2. 2 0
      playback/src/mixer/mod.rs
  3. 6 0
      src/main.rs

+ 94 - 19
playback/src/mixer/alsamixer.rs

@@ -1,44 +1,119 @@
 use super::AudioFilter;
 use super::{Mixer, MixerConfig};
+use std;
 use std::error::Error;
 
 use alsa;
 
+#[derive(Clone)]
+struct AlsaMixerVolumeParams {
+    min: i64,
+    max: i64,
+    range: f64,
+    min_db: alsa::mixer::MilliBel,
+    max_db: alsa::mixer::MilliBel,
+}
+
 #[derive(Clone)]
 pub struct AlsaMixer {
     config: MixerConfig,
+    params: AlsaMixerVolumeParams,
 }
 
 impl AlsaMixer {
-    fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<dyn Error>> {
+    fn pvol<T>(&self, vol: T, min: T, max: T) -> f64
+    where
+        T: std::ops::Sub + Copy,
+        f64: std::convert::From<<T as std::ops::Sub>::Output>,
+    {
+        f64::from(vol - min) / f64::from(max - min)
+    }
+
+    fn init_mixer(mut config: MixerConfig) -> Result<AlsaMixer, Box<Error>> {
+        let mixer = alsa::mixer::Mixer::new(&config.card, false)?;
+        let sid = alsa::mixer::SelemId::new(&config.mixer, config.index);
+
+        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
+        let (min, max) = selem.get_playback_volume_range();
+        let (min_db, max_db) = selem.get_playback_db_range();
+
+        info!(
+            "Alsa min: {} ({:?}[dB]) -- max: {} ({:?}[dB])",
+            min, min_db, max, max_db
+        );
+
+        if config.mapped_volume && (max_db - min_db <= alsa::mixer::MilliBel(24)) {
+            warn!(
+                "Switching to linear volume mapping, control range: {:?}",
+                max_db - min_db
+            );
+            config.mapped_volume = false;
+        } else {
+            info!("Using Alsa mapped volume: dB range: {:?}", max_db - min_db);
+        }
+
+        Ok(AlsaMixer {
+            config: config,
+            params: AlsaMixerVolumeParams {
+                min: min,
+                max: max,
+                range: (max - min) as f64,
+                min_db: min_db,
+                max_db: max_db,
+            },
+        })
+    }
+
+    fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
         let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
         let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);
 
-        let selem = mixer.find_selem(&sid).expect(
-            format!(
-                "Couldn't find simple mixer control for {}",
-                self.config.mixer
-            )
-            .as_str(),
-        );
-        let (min, max) = selem.get_playback_volume_range();
-        let range = (max - min) as f64;
+        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
+        let cur_vol = selem
+            .get_playback_volume(alsa::mixer::SelemChannelId::mono())
+            .expect("Couldn't get current volume");
+        let cur_vol_db = selem
+            .get_playback_vol_db(alsa::mixer::SelemChannelId::mono())
+            .expect("Couldn't get current volume");
 
         let new_vol: u16;
+        debug!("Current alsa volume: {}[i64] {:?}", cur_vol, cur_vol_db);
 
         if let Some(vol) = set_volume {
-            let alsa_volume: i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min;
-            debug!("Mapping volume {:?} ->> alsa {:?}", vol, alsa_volume);
+            let alsa_volume = if self.config.mapped_volume {
+                ((self.pvol(vol, 0x0000, 0xFFFF)).log10() * 6000.0).floor() as i64 + self.params.max
+            } else {
+                ((vol as f64 / 0xFFFF as f64) * self.params.range) as i64 + self.params.min
+            };
+            debug!(
+                "Maping volume [{:.3}%] {:?} [u16] ->> Alsa [{:.3}%] {:?} [i64]",
+                self.pvol(vol, 0x0000, 0xFFFF) * 100.0,
+                vol,
+                self.pvol(
+                    alsa_volume as f64,
+                    self.params.min as f64,
+                    self.params.max as f64
+                ) * 100.0,
+                alsa_volume
+            );
             selem
                 .set_playback_volume_all(alsa_volume)
                 .expect("Couldn't set alsa volume");
-            new_vol = vol;
+            new_vol = vol; // Meh
         } else {
-            let cur_vol = selem
-                .get_playback_volume(alsa::mixer::SelemChannelId::mono())
-                .expect("Couldn't get current volume");
-            new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16;
-            debug!("Mapping volume {:?} <<- alsa {:?}", new_vol, cur_vol);
+            new_vol =
+                (((cur_vol - self.params.min) as f64 / self.params.range) * 0xFFFF as f64) as u16;
+            debug!(
+                "Maping volume [{:.3}%] {:?} [u16] <<- Alsa [{:.3}%] {:?} [i64]",
+                self.pvol(new_vol, 0x0000, 0xFFFF),
+                new_vol,
+                self.pvol(
+                    cur_vol as f64,
+                    self.params.min as f64,
+                    self.params.max as f64
+                ),
+                cur_vol
+            );
         }
 
         Ok(new_vol)
@@ -52,7 +127,7 @@ impl Mixer for AlsaMixer {
             "Setting up new mixer: card:{} mixer:{} index:{}",
             config.card, config.mixer, config.index
         );
-        AlsaMixer { config: config }
+        AlsaMixer::init_mixer(config).expect("Error setting up mixer!")
     }
 
     fn start(&self) {}

+ 2 - 0
playback/src/mixer/mod.rs

@@ -25,6 +25,7 @@ pub struct MixerConfig {
     pub card: String,
     pub mixer: String,
     pub index: u32,
+    pub mapped_volume: bool,
 }
 
 impl Default for MixerConfig {
@@ -33,6 +34,7 @@ impl Default for MixerConfig {
             card: String::from("default"),
             mixer: String::from("PCM"),
             index: 0,
+            mapped_volume: true,
         }
     }
 }

+ 6 - 0
src/main.rs

@@ -150,6 +150,11 @@ fn setup(args: &[String]) -> Setup {
             "Alsa mixer index, Index of the cards mixer. Defaults to 0",
             "MIXER_INDEX",
         )
+        .optflag(
+            "",
+            "mixer-linear-volume",
+            "Disable alsa's mapped volume scale (cubic). Default false",
+        )
         .optopt(
             "",
             "initial-volume",
@@ -241,6 +246,7 @@ fn setup(args: &[String]) -> Setup {
             .opt_str("mixer-index")
             .map(|index| index.parse::<u32>().unwrap())
             .unwrap_or(0),
+        mapped_volume: !matches.opt_present("mixer-linear-volume"),
     };
 
     let use_audio_cache = !matches.opt_present("disable-audio-cache");