From a67048c3d711bf8dd5b9bd68034b8c9ce37d235b Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Tue, 20 Mar 2018 16:32:43 +0100
Subject: [PATCH 1/8] Add initial support for `alsamixer`

---
 playback/Cargo.toml                |  2 +-
 playback/src/audio_backend/alsa.rs | 50 ++++++++++++++----------
 playback/src/mixer/alsamixer.rs    | 62 ++++++++++++++++++++++++++++++
 playback/src/mixer/mod.rs          | 15 ++++++--
 playback/src/mixer/softmixer.rs    |  2 +-
 src/main.rs                        |  7 ++--
 6 files changed, 108 insertions(+), 30 deletions(-)
 create mode 100644 playback/src/mixer/alsamixer.rs

diff --git a/playback/Cargo.toml b/playback/Cargo.toml
index 2efd6ca6..1d9390c3 100644
--- a/playback/Cargo.toml
+++ b/playback/Cargo.toml
@@ -15,7 +15,7 @@ futures = "0.1.8"
 log = "0.3.5"
 byteorder = "1.2.1"
 
-alsa            = { git = "https://github.com/plietar/rust-alsa", optional = true }
+alsa            = { version = "0.1.5", optional = true }
 portaudio-rs    = { version = "0.3.0", optional = true }
 libpulse-sys    = { version = "0.0.0", optional = true }
 jack            = { version = "0.5.3", optional = true }
diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs
index 982a2625..7a067697 100644
--- a/playback/src/audio_backend/alsa.rs
+++ b/playback/src/audio_backend/alsa.rs
@@ -1,11 +1,14 @@
 use super::{Open, Sink};
-use alsa::{Access, Format, Mode, Stream, PCM};
 use std::io;
 
+use std::ffi::CString;
+use alsa::{Direction, ValueOr};
+use alsa::pcm::{PCM, HwParams, Format, Access};
+
 pub struct AlsaSink(Option<PCM>, String);
 
 impl Open for AlsaSink {
-    fn open(device: Option<String>) -> AlsaSink {
+   fn open(device: Option<String>) -> AlsaSink {
         info!("Using alsa sink");
 
         let name = device.unwrap_or("default".to_string());
@@ -16,26 +19,24 @@ impl Open for AlsaSink {
 
 impl Sink for AlsaSink {
     fn start(&mut self) -> io::Result<()> {
-        if self.0.is_none() {
-            match PCM::open(
-                &*self.1,
-                Stream::Playback,
-                Mode::Blocking,
-                Format::Signed16,
-                Access::Interleaved,
-                2,
-                44100,
-            ) {
-                Ok(f) => self.0 = Some(f),
-                Err(e) => {
-                    error!("Alsa error PCM open {}", e);
-                    return Err(io::Error::new(
-                        io::ErrorKind::Other,
-                        "Alsa error: PCM open failed",
-                    ));
-                }
+        if self.0.is_some() {
+        } else {
+            let pcm = PCM::open(&*CString::new(self.1.to_owned().into_bytes()).unwrap(),
+                                Direction::Playback,
+                                false).unwrap();
+            {
+                // Set hardware parameters: 44100 Hz / Stereo / 16 bit
+                let hwp = HwParams::any(&pcm).unwrap();
+                hwp.set_channels(2).unwrap();
+                hwp.set_rate(44100, ValueOr::Nearest).unwrap();
+                hwp.set_format(Format::s16()).unwrap();
+                hwp.set_access(Access::RWInterleaved).unwrap();
+                pcm.hw_params(&hwp).unwrap();
             }
+
+            self.0 = Some(pcm);
         }
+
         Ok(())
     }
 
@@ -45,7 +46,14 @@ impl Sink for AlsaSink {
     }
 
     fn write(&mut self, data: &[i16]) -> io::Result<()> {
-        self.0.as_mut().unwrap().write_interleaved(&data).unwrap();
+        let pcm = self.0.as_mut().unwrap();
+        let io = pcm.io_i16().unwrap();
+
+        match io.writei(&data) {
+            Ok(_) => (),
+            Err(err) => pcm.recover(err.code(), false).unwrap(),
+        }
+
         Ok(())
     }
 }
diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs
new file mode 100644
index 00000000..1f7e1092
--- /dev/null
+++ b/playback/src/mixer/alsamixer.rs
@@ -0,0 +1,62 @@
+use super::Mixer;
+use super::AudioFilter;
+
+use alsa;
+
+#[derive(Clone)]
+pub struct AlsaMixer {
+    name: String
+}
+
+impl Mixer for AlsaMixer {
+    fn open(device: Option<String>) -> AlsaMixer {
+        let name = device.unwrap_or("default".to_string());
+        AlsaMixer {
+            name: name
+        }
+    }
+
+    fn start(&self) {
+    }
+
+    fn stop(&self) {
+    }
+
+    fn volume(&self) -> u16 {
+        let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap();
+        let selem_id = alsa::mixer::SelemId::new("Master", 0);
+        let selem = mixer.find_selem(&selem_id).unwrap();
+        let (min, max) = selem.get_playback_volume_range();
+        let volume: i64 = selem.get_playback_volume(alsa::mixer::SelemChannelId::FrontLeft).unwrap();
+
+        // Spotify uses a volume range from 0 to 65535, but the ALSA mixers resolution might
+        // differ, e.g. most ALSA mixers uses a resolution of 256. Therefore, we have to calculate
+        // the multiplier to use, to get the corresponding Spotify volume value from the ALSA
+        // mixers volume.
+        let resolution = max - min + 1;
+        let multiplier: u16 = (((0xFFFF + 1) / resolution) - 1) as u16;
+
+        volume as u16 * multiplier
+    }
+
+    fn set_volume(&self, volume: u16) {
+        let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap();
+        let selem_id = alsa::mixer::SelemId::new("Master", 0);
+        let selem = mixer.find_selem(&selem_id).unwrap();
+        let (min, max) = selem.get_playback_volume_range();
+
+        // Spotify uses a volume range from 0 to 65535, but the ALSA mixers resolution might
+        // differ, e.g. most ALSA mixers uses a resolution of 256. Therefore, we have to calculate
+        // the factor to use, to get the corresponding ALSA mixers volume value from the Spotify
+        // volume.
+        let resolution = max - min + 1;
+        let factor: u16 = (((0xFFFF + 1) / resolution) - 1) as u16;
+        let volume: i64 = (volume / factor) as i64;
+
+        selem.set_playback_volume_all(volume).unwrap();
+    }
+
+    fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
+        None
+    }
+}
diff --git a/playback/src/mixer/mod.rs b/playback/src/mixer/mod.rs
index a6ba34aa..34e4dd3e 100644
--- a/playback/src/mixer/mod.rs
+++ b/playback/src/mixer/mod.rs
@@ -1,5 +1,5 @@
 pub trait Mixer: Send {
-    fn open() -> Self
+    fn open(Option<String>) -> Self
     where
         Self: Sized;
     fn start(&self);
@@ -15,16 +15,23 @@ pub trait AudioFilter {
     fn modify_stream(&self, data: &mut [i16]);
 }
 
+#[cfg(feature = "alsa-backend")]
+pub mod alsamixer;
+#[cfg(feature = "alsa-backend")]
+use self::alsamixer::AlsaMixer;
+
 pub mod softmixer;
 use self::softmixer::SoftMixer;
 
-fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> {
-    Box::new(M::open())
+fn mk_sink<M: Mixer + 'static>(device: Option<String>) -> Box<Mixer> {
+    Box::new(M::open(device))
 }
 
-pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn() -> Box<Mixer>> {
+pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<String>) -> Box<Mixer>> {
     match name.as_ref().map(AsRef::as_ref) {
         None | Some("softvol") => Some(mk_sink::<SoftMixer>),
+        #[cfg(feature = "alsa-backend")]
+        Some("alsa") => Some(mk_sink::<AlsaMixer>),
         _ => None,
     }
 }
diff --git a/playback/src/mixer/softmixer.rs b/playback/src/mixer/softmixer.rs
index b197f09c..36e4c6f8 100644
--- a/playback/src/mixer/softmixer.rs
+++ b/playback/src/mixer/softmixer.rs
@@ -10,7 +10,7 @@ pub struct SoftMixer {
 }
 
 impl Mixer for SoftMixer {
-    fn open() -> SoftMixer {
+    fn open(_: Option<String>) -> SoftMixer {
         SoftMixer {
             volume: Arc::new(AtomicUsize::new(0xFFFF)),
         }
diff --git a/src/main.rs b/src/main.rs
index 61290fb1..333095d0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -90,7 +90,7 @@ struct Setup {
     backend: fn(Option<String>) -> Box<Sink>,
     device: Option<String>,
 
-    mixer: fn() -> Box<Mixer>,
+    mixer: fn(Option<String>) -> Box<Mixer>,
 
     cache: Option<Cache>,
     player_config: PlayerConfig,
@@ -335,7 +335,7 @@ struct Main {
     connect_config: ConnectConfig,
     backend: fn(Option<String>) -> Box<Sink>,
     device: Option<String>,
-    mixer: fn() -> Box<Mixer>,
+    mixer: fn(Option<String>) -> Box<Mixer>,
     handle: Handle,
 
     discovery: Option<DiscoveryStream>,
@@ -423,12 +423,13 @@ impl Future for Main {
             if let Async::Ready(session) = self.connect.poll().unwrap() {
                 self.connect = Box::new(futures::future::empty());
                 let device = self.device.clone();
-                let mixer = (self.mixer)();
+                let mixer = (self.mixer)(device);
                 let player_config = self.player_config.clone();
                 let connect_config = self.connect_config.clone();
 
                 let audio_filter = mixer.get_audio_filter();
                 let backend = self.backend;
+                let device = self.device.clone();
                 let (player, event_channel) =
                     Player::new(player_config, session.clone(), audio_filter, move || {
                         (backend)(device)

From 08cfb1516ddb0ad45bdff6c5f5d1924b362a1f33 Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Wed, 21 Mar 2018 22:18:37 +0100
Subject: [PATCH 2/8] Switch to latest `alsa-rs` crate

---
 playback/Cargo.toml                |  2 +-
 playback/src/audio_backend/alsa.rs | 43 ++++++++++++++++++++++--------
 playback/src/mixer/alsamixer.rs    | 31 +++++++++++++++------
 3 files changed, 56 insertions(+), 20 deletions(-)

diff --git a/playback/Cargo.toml b/playback/Cargo.toml
index 1d9390c3..ec5c03b3 100644
--- a/playback/Cargo.toml
+++ b/playback/Cargo.toml
@@ -15,7 +15,7 @@ futures = "0.1.8"
 log = "0.3.5"
 byteorder = "1.2.1"
 
-alsa            = { version = "0.1.5", optional = true }
+alsa            = { git = "https://github.com/diwic/alsa-rs.git", optional = true }
 portaudio-rs    = { version = "0.3.0", optional = true }
 libpulse-sys    = { version = "0.0.0", optional = true }
 jack            = { version = "0.5.3", optional = true }
diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs
index 7a067697..f28ddea1 100644
--- a/playback/src/audio_backend/alsa.rs
+++ b/playback/src/audio_backend/alsa.rs
@@ -1,17 +1,34 @@
 use super::{Open, Sink};
+use alsa::{Direction, Error, ValueOr};
+use alsa::device_name::HintIter;
+use std::ffi::{CStr, CString};
+use alsa::pcm::{Access, Format, HwParams, PCM};
 use std::io;
+use std::process::exit;
 
-use std::ffi::CString;
-use alsa::{Direction, ValueOr};
-use alsa::pcm::{PCM, HwParams, Format, Access};
 
 pub struct AlsaSink(Option<PCM>, String);
 
+fn list_outputs() {
+    for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
+     println!("{} devices:", t);
+     let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
+     for a in i { println!("  {:?}", a) }
+ }
+}
+
 impl Open for AlsaSink {
    fn open(device: Option<String>) -> AlsaSink {
         info!("Using alsa sink");
 
-        let name = device.unwrap_or("default".to_string());
+        let name = match device.as_ref().map(AsRef::as_ref) {
+            Some("?") => {
+                list_outputs();
+                exit(0)
+            }
+            Some(device) => device,
+            None => "default",
+        }.to_string();
 
         AlsaSink(None, name)
     }
@@ -19,11 +36,8 @@ impl Open for AlsaSink {
 
 impl Sink for AlsaSink {
     fn start(&mut self) -> io::Result<()> {
-        if self.0.is_some() {
-        } else {
-            let pcm = PCM::open(&*CString::new(self.1.to_owned().into_bytes()).unwrap(),
-                                Direction::Playback,
-                                false).unwrap();
+        if self.0.is_none() {
+            let pcm = PCM::new(&*self.1, Direction::Playback, false).unwrap();
             {
                 // Set hardware parameters: 44100 Hz / Stereo / 16 bit
                 let hwp = HwParams::any(&pcm).unwrap();
@@ -32,7 +46,9 @@ impl Sink for AlsaSink {
                 hwp.set_format(Format::s16()).unwrap();
                 hwp.set_access(Access::RWInterleaved).unwrap();
                 pcm.hw_params(&hwp).unwrap();
-            }
+                println!("PCM status: {:?}, {:?}", pcm.state(), pcm.hw_params_current().unwrap())
+                }
+            PCM::prepare(&pcm).unwrap();
 
             self.0 = Some(pcm);
         }
@@ -41,6 +57,10 @@ impl Sink for AlsaSink {
     }
 
     fn stop(&mut self) -> io::Result<()> {
+        {
+            let pcm = self.0.as_mut().unwrap();
+            pcm.drain().unwrap();
+        }
         self.0 = None;
         Ok(())
     }
@@ -51,7 +71,8 @@ impl Sink for AlsaSink {
 
         match io.writei(&data) {
             Ok(_) => (),
-            Err(err) => pcm.recover(err.code(), false).unwrap(),
+            Err(err) => pcm.try_recover(err, false).unwrap(),
+            // Err(err) => println!("{:?}",err),
         }
 
         Ok(())
diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs
index 1f7e1092..196a1e6b 100644
--- a/playback/src/mixer/alsamixer.rs
+++ b/playback/src/mixer/alsamixer.rs
@@ -5,14 +5,29 @@ use alsa;
 
 #[derive(Clone)]
 pub struct AlsaMixer {
-    name: String
+    card: String,
+    mixer: String,
 }
 
+// Doesn't work - Selem is borrowed from Mixer
+// impl AlsaMixer {
+//     fn get_selem(&self ) -> Result<(alsa::mixer::Selem), Box<Error>> {
+//
+//         let selem_id = alsa::mixer::SelemId::new(self.mixer, 0);
+//         let mixer = alsa::mixer::Mixer::new(self.card, false)?;
+//         let selem = mixer.find_selem(&selem_id).unwrap();
+//
+//         Ok((selem))
+//     }
+// }
+
 impl Mixer for AlsaMixer {
     fn open(device: Option<String>) -> AlsaMixer {
-        let name = device.unwrap_or("default".to_string());
+        let card = device.unwrap_or(String::from("default"));
+        let mixer = String::from("PCM");
         AlsaMixer {
-            name: name
+            card: card,
+            mixer: mixer,
         }
     }
 
@@ -23,8 +38,8 @@ impl Mixer for AlsaMixer {
     }
 
     fn volume(&self) -> u16 {
-        let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap();
-        let selem_id = alsa::mixer::SelemId::new("Master", 0);
+        let mixer = alsa::mixer::Mixer::new(&self.card, false).unwrap();
+        let selem_id = alsa::mixer::SelemId::new(&self.mixer, 0);
         let selem = mixer.find_selem(&selem_id).unwrap();
         let (min, max) = selem.get_playback_volume_range();
         let volume: i64 = selem.get_playback_volume(alsa::mixer::SelemChannelId::FrontLeft).unwrap();
@@ -40,8 +55,8 @@ impl Mixer for AlsaMixer {
     }
 
     fn set_volume(&self, volume: u16) {
-        let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap();
-        let selem_id = alsa::mixer::SelemId::new("Master", 0);
+        let mixer = alsa::mixer::Mixer::new(&self.card, false).unwrap();
+        let selem_id = alsa::mixer::SelemId::new(&self.mixer, 0);
         let selem = mixer.find_selem(&selem_id).unwrap();
         let (min, max) = selem.get_playback_volume_range();
 
@@ -52,7 +67,7 @@ impl Mixer for AlsaMixer {
         let resolution = max - min + 1;
         let factor: u16 = (((0xFFFF + 1) / resolution) - 1) as u16;
         let volume: i64 = (volume / factor) as i64;
-
+        info!("Setting volume: {:?}", volume);
         selem.set_playback_volume_all(volume).unwrap();
     }
 

From 99106c5ae335377d6510ad649fa7cdae54aa364a Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Sat, 1 Sep 2018 02:42:50 +0200
Subject: [PATCH 3/8] Rework `alsa` hw and mixer parameters

---
 playback/src/audio_backend/alsa.rs | 86 +++++++++++++++++++++-------
 playback/src/mixer/alsamixer.rs    | 90 ++++++++++++++++--------------
 2 files changed, 114 insertions(+), 62 deletions(-)

diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs
index f28ddea1..6db35310 100644
--- a/playback/src/audio_backend/alsa.rs
+++ b/playback/src/audio_backend/alsa.rs
@@ -1,20 +1,66 @@
 use super::{Open, Sink};
-use alsa::{Direction, Error, ValueOr};
 use alsa::device_name::HintIter;
-use std::ffi::{CStr, CString};
 use alsa::pcm::{Access, Format, HwParams, PCM};
+use alsa::{Direction, Error, ValueOr};
+use std::env;
+use std::ffi::CString;
 use std::io;
 use std::process::exit;
 
-
 pub struct AlsaSink(Option<PCM>, String);
 
 fn list_outputs() {
     for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
-     println!("{} devices:", t);
-     let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
-     for a in i { println!("  {:?}", a) }
- }
+        println!("{} devices:", t);
+        let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
+        for a in i {
+            println!("  {:?}", a)
+        }
+    }
+}
+
+fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> {
+    let pcm = PCM::new(dev_name, Direction::Playback, false)?;
+    // http://www.linuxjournal.com/article/6735?page=0,1#N0x19ab2890.0x19ba78d8
+    // latency = period_size * periods / (rate * bytes_per_frame)
+    // For 16 Bit stereo data, one frame has a length of four bytes.
+    // 500ms  = buffer_size / (44100 * 4)
+    // buffer_size = 0.5 * 44100 = 22050 frames
+    {
+        // Set hardware parameters: 44100 Hz / Stereo / 16 bit
+        let hwp = HwParams::any(&pcm)?;
+
+        hwp.set_access(Access::RWInterleaved)?;
+        hwp.set_format(Format::s16())?;
+        hwp.set_rate(44100, ValueOr::Nearest)?;
+        hwp.set_channels(2)?;
+        // hwp.set_period_size_near(256, ValueOr::Nearest)?;
+        hwp.set_buffer_size_near(11025 * 2)?; // ~ 0.25 x 2 s latency
+
+        pcm.hw_params(&hwp)?;
+    }
+
+    // Additional software paramters + check
+    if env::var("LIBRESPOT_DEBUG").is_ok() {
+        let hwp = pcm.hw_params_current()?;
+        let swp = pcm.sw_params_current()?;
+        let (bufsize, periodsize) = (hwp.get_buffer_size()?, hwp.get_period_size()?);
+        let periods = hwp.get_periods()?;
+        info!(
+            "periods: {:?} buffer_size: {:?} period_size {:?}",
+            periods, bufsize, periodsize
+        );
+        // Not required now that buffer size is set properly
+        // swp.set_start_threshold(bufsize - periodsize)?;
+        // swp.set_avail_min(periodsize)?;
+        // pcm.sw_params(&swp).unwrap();
+        info!(
+            "Opened audio output {:?} with parameters: {:?}, {:?}",
+            dev_name, hwp, swp
+        );
+    }
+
+    Ok(pcm)
 }
 
 impl Open for AlsaSink {
@@ -23,6 +69,7 @@ impl Open for AlsaSink {
 
         let name = match device.as_ref().map(AsRef::as_ref) {
             Some("?") => {
+                println!("Listing available alsa outputs");
                 list_outputs();
                 exit(0)
             }
@@ -37,20 +84,17 @@ impl Open for AlsaSink {
 impl Sink for AlsaSink {
     fn start(&mut self) -> io::Result<()> {
         if self.0.is_none() {
-            let pcm = PCM::new(&*self.1, Direction::Playback, false).unwrap();
-            {
-                // Set hardware parameters: 44100 Hz / Stereo / 16 bit
-                let hwp = HwParams::any(&pcm).unwrap();
-                hwp.set_channels(2).unwrap();
-                hwp.set_rate(44100, ValueOr::Nearest).unwrap();
-                hwp.set_format(Format::s16()).unwrap();
-                hwp.set_access(Access::RWInterleaved).unwrap();
-                pcm.hw_params(&hwp).unwrap();
-                println!("PCM status: {:?}, {:?}", pcm.state(), pcm.hw_params_current().unwrap())
+            let pcm = open_device(&self.1);
+            match pcm {
+                Ok(p) => self.0 = Some(p),
+                Err(e) => {
+                    error!("Alsa error PCM open {}", e);
+                    return Err(io::Error::new(
+                        io::ErrorKind::Other,
+                        "Alsa error: PCM open failed",
+                    ));
                 }
-            PCM::prepare(&pcm).unwrap();
-
-            self.0 = Some(pcm);
+            }
         }
 
         Ok(())
@@ -58,7 +102,7 @@ impl Sink for AlsaSink {
 
     fn stop(&mut self) -> io::Result<()> {
         {
-            let pcm = self.0.as_mut().unwrap();
+            let pcm = self.0.as_ref().unwrap();
             pcm.drain().unwrap();
         }
         self.0 = None;
diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs
index 196a1e6b..b4a0ac1a 100644
--- a/playback/src/mixer/alsamixer.rs
+++ b/playback/src/mixer/alsamixer.rs
@@ -1,5 +1,7 @@
-use super::Mixer;
 use super::AudioFilter;
+use super::Mixer;
+use std::env;
+use std::error::Error;
 
 use alsa;
 
@@ -7,27 +9,50 @@ use alsa;
 pub struct AlsaMixer {
     card: String,
     mixer: String,
+    index: u32,
 }
 
-// Doesn't work - Selem is borrowed from Mixer
-// impl AlsaMixer {
-//     fn get_selem(&self ) -> Result<(alsa::mixer::Selem), Box<Error>> {
-//
-//         let selem_id = alsa::mixer::SelemId::new(self.mixer, 0);
-//         let mixer = alsa::mixer::Mixer::new(self.card, false)?;
-//         let selem = mixer.find_selem(&selem_id).unwrap();
-//
-//         Ok((selem))
-//     }
-// }
+impl AlsaMixer {
+
+    fn map_volume(&self, set_volume:Option<u16>) -> Result<(u16),Box<Error>> {
+        let mixer  = alsa::mixer::Mixer::new(&self.card, false)?;
+        let sid    = alsa::mixer::SelemId::new(&*self.mixer, self.index);
+
+        let selem = mixer.find_selem(&sid).expect("Coundn't find SelemId");
+        let (min, max) = selem.get_playback_volume_range();
+        let cur_vol = selem.get_playback_volume(alsa::mixer::SelemChannelId::mono()).expect("Couldn't get current volume");
+        let range = (max - min) as f64;
+
+        let new_vol:u16;
+
+        if let Some(vol) = set_volume {
+            let alsa_volume:i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min;
+            debug!("Maping volume {:?} [u16] ->> Alsa {:?} [i64]",vol,alsa_volume);
+            selem.set_playback_volume_all(alsa_volume).expect("Couldn't set alsa volume");
+            new_vol = vol; // Meh
+        } else {
+            new_vol =  (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16;
+            debug!("Maping volume {:?} [u16] <<- Alsa {:?} [i64]",new_vol, cur_vol);
+        }
+
+
+        Ok(new_vol)
+    }
+}
 
 impl Mixer for AlsaMixer {
     fn open(device: Option<String>) -> AlsaMixer {
-        let card = device.unwrap_or(String::from("default"));
-        let mixer = String::from("PCM");
+        let card = env::var("LIBRESPOT_CARD").unwrap_or(device.unwrap_or(String::from("default")));
+        let mixer = env::var("LIBRESPOT_MIXER").unwrap_or(String::from("PCM"));
+        let index: u32 = 0;
+        info!(
+            "Setting up new mixer: card:{} mixer:{} index:{}",
+            card, mixer, index
+        );
         AlsaMixer {
             card: card,
             mixer: mixer,
+            index: index,
         }
     }
 
@@ -38,37 +63,20 @@ impl Mixer for AlsaMixer {
     }
 
     fn volume(&self) -> u16 {
-        let mixer = alsa::mixer::Mixer::new(&self.card, false).unwrap();
-        let selem_id = alsa::mixer::SelemId::new(&self.mixer, 0);
-        let selem = mixer.find_selem(&selem_id).unwrap();
-        let (min, max) = selem.get_playback_volume_range();
-        let volume: i64 = selem.get_playback_volume(alsa::mixer::SelemChannelId::FrontLeft).unwrap();
 
-        // Spotify uses a volume range from 0 to 65535, but the ALSA mixers resolution might
-        // differ, e.g. most ALSA mixers uses a resolution of 256. Therefore, we have to calculate
-        // the multiplier to use, to get the corresponding Spotify volume value from the ALSA
-        // mixers volume.
-        let resolution = max - min + 1;
-        let multiplier: u16 = (((0xFFFF + 1) / resolution) - 1) as u16;
-
-        volume as u16 * multiplier
+        match self.map_volume(None){
+                Ok(vol) => vol,
+                Err(e)  => {
+                        error!("Error getting volume for <{}>, {:?}",self.card, e);
+                        0 }
+        }
     }
 
     fn set_volume(&self, volume: u16) {
-        let mixer = alsa::mixer::Mixer::new(&self.card, false).unwrap();
-        let selem_id = alsa::mixer::SelemId::new(&self.mixer, 0);
-        let selem = mixer.find_selem(&selem_id).unwrap();
-        let (min, max) = selem.get_playback_volume_range();
-
-        // Spotify uses a volume range from 0 to 65535, but the ALSA mixers resolution might
-        // differ, e.g. most ALSA mixers uses a resolution of 256. Therefore, we have to calculate
-        // the factor to use, to get the corresponding ALSA mixers volume value from the Spotify
-        // volume.
-        let resolution = max - min + 1;
-        let factor: u16 = (((0xFFFF + 1) / resolution) - 1) as u16;
-        let volume: i64 = (volume / factor) as i64;
-        info!("Setting volume: {:?}", volume);
-        selem.set_playback_volume_all(volume).unwrap();
+        match self.map_volume(Some(volume)){
+                Ok(_) => (),
+                Err(e)  => error!("Error setting volume for <{}>, {:?}",self.card, e),
+        }
     }
 
     fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {

From 0e1147077c590d26d04cb469c386f0a00f69c2f3 Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Tue, 11 Sep 2018 18:53:18 +0200
Subject: [PATCH 4/8] Add run time option flags for `AlsaMixer`

Add `Cargo.lock` for Travis
---
 Cargo.lock                      | 29 ++++++++++++++++++++----
 playback/src/mixer/alsamixer.rs | 29 +++++++++---------------
 playback/src/mixer/mod.rs       | 22 +++++++++++++++---
 playback/src/mixer/softmixer.rs |  4 ++--
 src/main.rs                     | 40 ++++++++++++++++++++++++++++-----
 5 files changed, 90 insertions(+), 34 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b07bc265..2eccd947 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -49,10 +49,13 @@ dependencies = [
 
 [[package]]
 name = "alsa"
-version = "0.0.1"
-source = "git+https://github.com/plietar/rust-alsa#8c63543fa0ccd971cf15f5675293d19febd6f79e"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
+ "alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -164,6 +167,11 @@ name = "bitflags"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "bitflags"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "bitflags"
 version = "1.0.4"
@@ -824,7 +832,7 @@ dependencies = [
 name = "librespot-playback"
 version = "0.1.0"
 dependencies = [
- "alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)",
+ "alsa 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cpal 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1006,6 +1014,17 @@ dependencies = [
  "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "nix"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "nix"
 version = "0.11.0"
@@ -2181,7 +2200,7 @@ dependencies = [
 "checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d"
 "checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100"
 "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
-"checksum alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)" = "<none>"
+"checksum alsa 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fd5a75e70d45a943d2a0a818277e71d6ff777e97358529d6b460d3d4c4d0745"
 "checksum alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58"
 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94"
@@ -2196,6 +2215,7 @@ dependencies = [
 "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
 "checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c"
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
+"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
 "checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d"
 "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
@@ -2279,6 +2299,7 @@ dependencies = [
 "checksum multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb04b9f127583ed176e163fb9ec6f3e793b87e21deedd5734a69386a18a0151"
 "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
 "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
+"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
 "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
 "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b"
 "checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs
index b4a0ac1a..c34d839c 100644
--- a/playback/src/mixer/alsamixer.rs
+++ b/playback/src/mixer/alsamixer.rs
@@ -1,22 +1,19 @@
 use super::AudioFilter;
-use super::Mixer;
-use std::env;
+use super::{Mixer, MixerConfig};
 use std::error::Error;
 
 use alsa;
 
 #[derive(Clone)]
 pub struct AlsaMixer {
-    card: String,
-    mixer: String,
-    index: u32,
+    config: MixerConfig,
 }
 
 impl AlsaMixer {
 
     fn map_volume(&self, set_volume:Option<u16>) -> Result<(u16),Box<Error>> {
-        let mixer  = alsa::mixer::Mixer::new(&self.card, false)?;
-        let sid    = alsa::mixer::SelemId::new(&*self.mixer, self.index);
+        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("Coundn't find SelemId");
         let (min, max) = selem.get_playback_volume_range();
@@ -41,19 +38,13 @@ impl AlsaMixer {
 }
 
 impl Mixer for AlsaMixer {
-    fn open(device: Option<String>) -> AlsaMixer {
-        let card = env::var("LIBRESPOT_CARD").unwrap_or(device.unwrap_or(String::from("default")));
-        let mixer = env::var("LIBRESPOT_MIXER").unwrap_or(String::from("PCM"));
-        let index: u32 = 0;
+    fn open(config: Option<MixerConfig>) -> AlsaMixer {
+        let config = config.unwrap_or_default();
         info!(
             "Setting up new mixer: card:{} mixer:{} index:{}",
-            card, mixer, index
+            config.card, config.mixer, config.index
         );
-        AlsaMixer {
-            card: card,
-            mixer: mixer,
-            index: index,
-        }
+        AlsaMixer { config: config }
     }
 
     fn start(&self) {
@@ -67,7 +58,7 @@ impl Mixer for AlsaMixer {
         match self.map_volume(None){
                 Ok(vol) => vol,
                 Err(e)  => {
-                        error!("Error getting volume for <{}>, {:?}",self.card, e);
+                        error!("Error getting volume for <{}>, {:?}",self.config.card, e);
                         0 }
         }
     }
@@ -75,7 +66,7 @@ impl Mixer for AlsaMixer {
     fn set_volume(&self, volume: u16) {
         match self.map_volume(Some(volume)){
                 Ok(_) => (),
-                Err(e)  => error!("Error setting volume for <{}>, {:?}",self.card, e),
+                Err(e)  => error!("Error setting volume for <{}>, {:?}",self.config.card, e),
         }
     }
 
diff --git a/playback/src/mixer/mod.rs b/playback/src/mixer/mod.rs
index 34e4dd3e..f19a8661 100644
--- a/playback/src/mixer/mod.rs
+++ b/playback/src/mixer/mod.rs
@@ -1,5 +1,5 @@
 pub trait Mixer: Send {
-    fn open(Option<String>) -> Self
+    fn open(Option<MixerConfig>) -> Self
     where
         Self: Sized;
     fn start(&self);
@@ -20,14 +20,30 @@ pub mod alsamixer;
 #[cfg(feature = "alsa-backend")]
 use self::alsamixer::AlsaMixer;
 
+#[derive(Debug, Clone)]
+pub struct MixerConfig {
+    pub card: String,
+    pub mixer: String,
+    pub index: u32,
+}
+
+impl Default for MixerConfig {
+    fn default() -> MixerConfig { MixerConfig {
+        card: String::from("default"),
+        mixer: String::from("PCM"),
+        index: 0,
+        }
+    }
+}
+
 pub mod softmixer;
 use self::softmixer::SoftMixer;
 
-fn mk_sink<M: Mixer + 'static>(device: Option<String>) -> Box<Mixer> {
+fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<Mixer> {
     Box::new(M::open(device))
 }
 
-pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<String>) -> Box<Mixer>> {
+pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<Mixer>> {
     match name.as_ref().map(AsRef::as_ref) {
         None | Some("softvol") => Some(mk_sink::<SoftMixer>),
         #[cfg(feature = "alsa-backend")]
diff --git a/playback/src/mixer/softmixer.rs b/playback/src/mixer/softmixer.rs
index 36e4c6f8..4b969785 100644
--- a/playback/src/mixer/softmixer.rs
+++ b/playback/src/mixer/softmixer.rs
@@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::Arc;
 
 use super::AudioFilter;
-use super::Mixer;
+use super::{Mixer, MixerConfig};
 
 #[derive(Clone)]
 pub struct SoftMixer {
@@ -10,7 +10,7 @@ pub struct SoftMixer {
 }
 
 impl Mixer for SoftMixer {
-    fn open(_: Option<String>) -> SoftMixer {
+    fn open(_: Option<MixerConfig>) -> SoftMixer {
         SoftMixer {
             volume: Arc::new(AtomicUsize::new(0xFFFF)),
         }
diff --git a/src/main.rs b/src/main.rs
index 333095d0..cfc752ff 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,7 +37,7 @@ use librespot::connect::discovery::{discovery, DiscoveryStream};
 use librespot::connect::spirc::{Spirc, SpircTask};
 use librespot::playback::audio_backend::{self, Sink, BACKENDS};
 use librespot::playback::config::{Bitrate, PlayerConfig};
-use librespot::playback::mixer::{self, Mixer};
+use librespot::playback::mixer::{self, Mixer, MixerConfig};
 use librespot::playback::player::{Player, PlayerEvent};
 
 mod player_event_handler;
@@ -90,12 +90,13 @@ struct Setup {
     backend: fn(Option<String>) -> Box<Sink>,
     device: Option<String>,
 
-    mixer: fn(Option<String>) -> Box<Mixer>,
+    mixer: fn(Option<MixerConfig>) -> Box<Mixer>,
 
     cache: Option<Cache>,
     player_config: PlayerConfig,
     session_config: SessionConfig,
     connect_config: ConnectConfig,
+    mixer_config: MixerConfig,
     credentials: Option<Credentials>,
     enable_discovery: bool,
     zeroconf_port: u16,
@@ -142,7 +143,25 @@ fn setup(args: &[String]) -> Setup {
             "Audio device to use. Use '?' to list options if using portaudio",
             "DEVICE",
         )
-        .optopt("", "mixer", "Mixer to use", "MIXER")
+        .optopt("", "mixer", "Mixer to use (Alsa or softmixer)", "MIXER")
+        .optopt(
+            "m",
+            "mixer-name",
+            "Alsa mixer name, e.g \"PCM\" or \"Master\". Defaults to 'PCM'",
+            "MIXER_NAME",
+        )
+        .optopt(
+            "",
+            "mixer-card",
+            "Alsa mixer card, e.g \"hw:0\" or similar from `aplay -l`. Defaults to 'default' ",
+            "MIXER_CARD",
+        )
+        .optopt(
+            "",
+            "mixer-index",
+            "Alsa mixer index, Index of the cards mixer. Defaults to 0",
+            "MIXER_INDEX",
+        )
         .optopt(
             "",
             "initial-volume",
@@ -208,6 +227,12 @@ fn setup(args: &[String]) -> Setup {
     let mixer_name = matches.opt_str("mixer");
     let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
 
+    let mixer_config = MixerConfig {
+            card:  matches.opt_str("mixer-card").unwrap_or(String::from("default")),
+            mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
+            index: matches.opt_str("mixer-index").map(|index| index.parse::<u32>().unwrap()).unwrap_or(0),
+    };
+
     let use_audio_cache = !matches.opt_present("disable-audio-cache");
 
     let cache = matches
@@ -324,6 +349,7 @@ fn setup(args: &[String]) -> Setup {
         enable_discovery: enable_discovery,
         zeroconf_port: zeroconf_port,
         mixer: mixer,
+        mixer_config: mixer_config,
         player_event_program: matches.opt_str("onevent"),
     }
 }
@@ -335,7 +361,8 @@ struct Main {
     connect_config: ConnectConfig,
     backend: fn(Option<String>) -> Box<Sink>,
     device: Option<String>,
-    mixer: fn(Option<String>) -> Box<Mixer>,
+    mixer: fn(Option<MixerConfig>) -> Box<Mixer>,
+    mixer_config: MixerConfig,
     handle: Handle,
 
     discovery: Option<DiscoveryStream>,
@@ -362,6 +389,7 @@ impl Main {
             backend: setup.backend,
             device: setup.device,
             mixer: setup.mixer,
+            mixer_config: setup.mixer_config,
 
             connect: Box::new(futures::future::empty()),
             discovery: None,
@@ -422,8 +450,8 @@ impl Future for Main {
 
             if let Async::Ready(session) = self.connect.poll().unwrap() {
                 self.connect = Box::new(futures::future::empty());
-                let device = self.device.clone();
-                let mixer = (self.mixer)(device);
+                let mixer_config = self.mixer_config.clone();
+                let mixer = (self.mixer)(Some(mixer_config));
                 let player_config = self.player_config.clone();
                 let connect_config = self.connect_config.clone();
 

From a80bf86a2b71ab0721fbb68ae9cedbd2b30c3e7e Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Tue, 16 Oct 2018 16:46:26 +0200
Subject: [PATCH 5/8] Clean up alsa stragglers and typos

---
 playback/src/audio_backend/alsa.rs | 35 +++++--------------
 playback/src/mixer/alsamixer.rs    | 54 +++++++++++++++---------------
 2 files changed, 35 insertions(+), 54 deletions(-)

diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs
index 6db35310..927c0921 100644
--- a/playback/src/audio_backend/alsa.rs
+++ b/playback/src/audio_backend/alsa.rs
@@ -10,11 +10,13 @@ use std::process::exit;
 pub struct AlsaSink(Option<PCM>, String);
 
 fn list_outputs() {
-    for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
+    for t in &["pcm", "ctl", "hwdep"] {
         println!("{} devices:", t);
         let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
         for a in i {
-            println!("  {:?}", a)
+            if let Some(Direction::Playback) = a.direction {
+                println!("{:#?}", a)
+            }
         }
     }
 }
@@ -25,7 +27,8 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> {
     // latency = period_size * periods / (rate * bytes_per_frame)
     // For 16 Bit stereo data, one frame has a length of four bytes.
     // 500ms  = buffer_size / (44100 * 4)
-    // buffer_size = 0.5 * 44100 = 22050 frames
+    // buffer_size_bytes = 0.5 * 44100 / 4
+    // buffer_size_frames = 0.5 * 44100 = 22050
     {
         // Set hardware parameters: 44100 Hz / Stereo / 16 bit
         let hwp = HwParams::any(&pcm)?;
@@ -34,37 +37,16 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> {
         hwp.set_format(Format::s16())?;
         hwp.set_rate(44100, ValueOr::Nearest)?;
         hwp.set_channels(2)?;
-        // hwp.set_period_size_near(256, ValueOr::Nearest)?;
-        hwp.set_buffer_size_near(11025 * 2)?; // ~ 0.25 x 2 s latency
+        hwp.set_buffer_size_near(22050)?; // ~ 0.5s latency
 
         pcm.hw_params(&hwp)?;
     }
 
-    // Additional software paramters + check
-    if env::var("LIBRESPOT_DEBUG").is_ok() {
-        let hwp = pcm.hw_params_current()?;
-        let swp = pcm.sw_params_current()?;
-        let (bufsize, periodsize) = (hwp.get_buffer_size()?, hwp.get_period_size()?);
-        let periods = hwp.get_periods()?;
-        info!(
-            "periods: {:?} buffer_size: {:?} period_size {:?}",
-            periods, bufsize, periodsize
-        );
-        // Not required now that buffer size is set properly
-        // swp.set_start_threshold(bufsize - periodsize)?;
-        // swp.set_avail_min(periodsize)?;
-        // pcm.sw_params(&swp).unwrap();
-        info!(
-            "Opened audio output {:?} with parameters: {:?}, {:?}",
-            dev_name, hwp, swp
-        );
-    }
-
     Ok(pcm)
 }
 
 impl Open for AlsaSink {
-   fn open(device: Option<String>) -> AlsaSink {
+    fn open(device: Option<String>) -> AlsaSink {
         info!("Using alsa sink");
 
         let name = match device.as_ref().map(AsRef::as_ref) {
@@ -116,7 +98,6 @@ impl Sink for AlsaSink {
         match io.writei(&data) {
             Ok(_) => (),
             Err(err) => pcm.try_recover(err, false).unwrap(),
-            // Err(err) => println!("{:?}",err),
         }
 
         Ok(())
diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs
index c34d839c..36256268 100644
--- a/playback/src/mixer/alsamixer.rs
+++ b/playback/src/mixer/alsamixer.rs
@@ -10,29 +10,31 @@ pub struct AlsaMixer {
 }
 
 impl AlsaMixer {
+    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);
 
-    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("Coundn't find SelemId");
+        let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
         let (min, max) = selem.get_playback_volume_range();
-        let cur_vol = selem.get_playback_volume(alsa::mixer::SelemChannelId::mono()).expect("Couldn't get current volume");
+        let cur_vol = selem
+            .get_playback_volume(alsa::mixer::SelemChannelId::mono())
+            .expect("Couldn't get current volume");
         let range = (max - min) as f64;
 
-        let new_vol:u16;
+        let new_vol: u16;
 
         if let Some(vol) = set_volume {
-            let alsa_volume:i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min;
-            debug!("Maping volume {:?} [u16] ->> Alsa {:?} [i64]",vol,alsa_volume);
-            selem.set_playback_volume_all(alsa_volume).expect("Couldn't set alsa volume");
-            new_vol = vol; // Meh
+            let alsa_volume: i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min;
+            debug!("Mapping volume {:?} [u16] ->> alsa {:?} [i64]", vol, alsa_volume);
+            selem
+                .set_playback_volume_all(alsa_volume)
+                .expect("Couldn't set alsa volume");
+            new_vol = vol;
         } else {
-            new_vol =  (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16;
-            debug!("Maping volume {:?} [u16] <<- Alsa {:?} [i64]",new_vol, cur_vol);
+            new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16;
+            debug!("Mapping volume {:?} [u16] <<- alsa {:?} [i64]", new_vol, cur_vol);
         }
 
-
         Ok(new_vol)
     }
 }
@@ -47,26 +49,24 @@ impl Mixer for AlsaMixer {
         AlsaMixer { config: config }
     }
 
-    fn start(&self) {
-    }
+    fn start(&self) {}
 
-    fn stop(&self) {
-    }
+    fn stop(&self) {}
 
     fn volume(&self) -> u16 {
-
-        match self.map_volume(None){
-                Ok(vol) => vol,
-                Err(e)  => {
-                        error!("Error getting volume for <{}>, {:?}",self.config.card, e);
-                        0 }
+        match self.map_volume(None) {
+            Ok(vol) => vol,
+            Err(e) => {
+                error!("Error getting volume for <{}>, {:?}", self.config.card, e);
+                0
+            }
         }
     }
 
     fn set_volume(&self, volume: u16) {
-        match self.map_volume(Some(volume)){
-                Ok(_) => (),
-                Err(e)  => error!("Error setting volume for <{}>, {:?}",self.config.card, e),
+        match self.map_volume(Some(volume)) {
+            Ok(_) => (),
+            Err(e) => error!("Error setting volume for <{}>, {:?}", self.config.card, e),
         }
     }
 

From cc6c9b2dc4fae61ad79a96384357fd9d4e3b3dc1 Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Thu, 1 Nov 2018 17:40:42 +0100
Subject: [PATCH 6/8] More `alsa` stragglers

---
 playback/src/audio_backend/alsa.rs |  3 +--
 playback/src/mixer/alsamixer.rs    | 14 ++++++++------
 src/main.rs                        | 16 +++++++++-------
 3 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs
index 927c0921..0e18708b 100644
--- a/playback/src/audio_backend/alsa.rs
+++ b/playback/src/audio_backend/alsa.rs
@@ -2,7 +2,6 @@ use super::{Open, Sink};
 use alsa::device_name::HintIter;
 use alsa::pcm::{Access, Format, HwParams, PCM};
 use alsa::{Direction, Error, ValueOr};
-use std::env;
 use std::ffi::CString;
 use std::io;
 use std::process::exit;
@@ -15,7 +14,7 @@ fn list_outputs() {
         let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
         for a in i {
             if let Some(Direction::Playback) = a.direction {
-                println!("{:#?}", a)
+                println!("{}\n\t{}", a.name.unwrap(), a.desc.unwrap());
             }
         }
     }
diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs
index 36256268..5c77d470 100644
--- a/playback/src/mixer/alsamixer.rs
+++ b/playback/src/mixer/alsamixer.rs
@@ -14,25 +14,27 @@ impl AlsaMixer {
         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("Couldn't find SelemId");
+        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 cur_vol = selem
-            .get_playback_volume(alsa::mixer::SelemChannelId::mono())
-            .expect("Couldn't get current volume");
         let range = (max - min) as f64;
 
         let new_vol: u16;
 
         if let Some(vol) = set_volume {
             let alsa_volume: i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min;
-            debug!("Mapping volume {:?} [u16] ->> alsa {:?} [i64]", vol, alsa_volume);
+            debug!("Mapping volume {:?} ->> alsa {:?}", vol, alsa_volume);
             selem
                 .set_playback_volume_all(alsa_volume)
                 .expect("Couldn't set alsa volume");
             new_vol = vol;
         } 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 {:?} [u16] <<- alsa {:?} [i64]", new_vol, cur_vol);
+            debug!("Mapping volume {:?} <<- alsa {:?}", new_vol, cur_vol);
         }
 
         Ok(new_vol)
diff --git a/src/main.rs b/src/main.rs
index cfc752ff..4fec379a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -140,10 +140,10 @@ fn setup(args: &[String]) -> Setup {
         .optopt(
             "",
             "device",
-            "Audio device to use. Use '?' to list options if using portaudio",
+            "Audio device to use. Use '?' to list options if using portaudio or alsa",
             "DEVICE",
         )
-        .optopt("", "mixer", "Mixer to use (Alsa or softmixer)", "MIXER")
+        .optopt("", "mixer", "Mixer to use (alsa or softmixer)", "MIXER")
         .optopt(
             "m",
             "mixer-name",
@@ -228,9 +228,12 @@ fn setup(args: &[String]) -> Setup {
     let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
 
     let mixer_config = MixerConfig {
-            card:  matches.opt_str("mixer-card").unwrap_or(String::from("default")),
-            mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
-            index: matches.opt_str("mixer-index").map(|index| index.parse::<u32>().unwrap()).unwrap_or(0),
+        card: matches.opt_str("mixer-card").unwrap_or(String::from("default")),
+        mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
+        index: matches
+            .opt_str("mixer-index")
+            .map(|index| index.parse::<u32>().unwrap())
+            .unwrap_or(0),
     };
 
     let use_audio_cache = !matches.opt_present("disable-audio-cache");
@@ -247,8 +250,7 @@ fn setup(args: &[String]) -> Setup {
                 panic!("Initial volume must be in the range 0-100");
             }
             (volume as i32 * 0xFFFF / 100) as u16
-        })
-        .or_else(|| cache.as_ref().and_then(Cache::volume))
+        }).or_else(|| cache.as_ref().and_then(Cache::volume))
         .unwrap_or(0x8000);
 
     let zeroconf_port = matches

From 8fd0caf583e7def555cfbb595d88d709980ae0a8 Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Fri, 2 Nov 2018 15:24:43 +0100
Subject: [PATCH 7/8] Explicitly set `start_threshold` and pretty print devices

---
 playback/src/audio_backend/alsa.rs | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs
index 0e18708b..98e7c8f7 100644
--- a/playback/src/audio_backend/alsa.rs
+++ b/playback/src/audio_backend/alsa.rs
@@ -14,7 +14,12 @@ fn list_outputs() {
         let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
         for a in i {
             if let Some(Direction::Playback) = a.direction {
-                println!("{}\n\t{}", a.name.unwrap(), a.desc.unwrap());
+                // mimic aplay -L
+                println!(
+                    "{}\n\t{}\n",
+                    a.name.unwrap(),
+                    a.desc.unwrap().replace("\n", "\n\t")
+                );
             }
         }
     }
@@ -37,8 +42,11 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> {
         hwp.set_rate(44100, ValueOr::Nearest)?;
         hwp.set_channels(2)?;
         hwp.set_buffer_size_near(22050)?; // ~ 0.5s latency
-
         pcm.hw_params(&hwp)?;
+
+        let swp = pcm.sw_params_current()?;
+        swp.set_start_threshold(hwp.get_buffer_size()? - hwp.get_period_size()?)?;
+        pcm.sw_params(&swp)?;
     }
 
     Ok(pcm)

From 9cb2f49d529052f9f0d1f1a4999e7d1396afed51 Mon Sep 17 00:00:00 2001
From: ashthespy <ashthespy@gmail.com>
Date: Thu, 21 Mar 2019 23:32:18 +0100
Subject: [PATCH 8/8] Switch `alsa` to crates.io

---
 playback/Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/playback/Cargo.toml b/playback/Cargo.toml
index ec5c03b3..02b750d9 100644
--- a/playback/Cargo.toml
+++ b/playback/Cargo.toml
@@ -15,7 +15,7 @@ futures = "0.1.8"
 log = "0.3.5"
 byteorder = "1.2.1"
 
-alsa            = { git = "https://github.com/diwic/alsa-rs.git", optional = true }
+alsa            = { version = "0.2.1", optional = true }
 portaudio-rs    = { version = "0.3.0", optional = true }
 libpulse-sys    = { version = "0.0.0", optional = true }
 jack            = { version = "0.5.3", optional = true }