<commit_before>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 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("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 = 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; // Meh
        } else {
            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<commit_msg>Better `alsamixer` volume mapping for hardware mixers<commit_after>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 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();
        let hw_mix = selem
            .get_playback_vol_db(alsa::mixer::SelemChannelId::mono())
            .is_ok();

        info!(
            "Alsa min: {} ({:?}[dB]) -- max: {} ({:?}[dB]) HW: {:?}",
            min, min_db, max, max_db, hw_mix
        );

        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 if !config.mapped_volume {
            info!("Using Alsa linear volume");
        }

        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("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())
            .unwrap_or(alsa::mixer::MilliBel(9999));

        let new_vol: u16;
        info!("Current alsa volume: {}[i64] {:?}", cur_vol, cur_vol_db);

        if let Some(vol) = set_volume {
            if self.config.mapped_volume {
                info!("Using mapped_volume");
                //TODO Check if min is not mute!
                let vol_db = ((self.pvol(vol, 0x0000, 0xFFFF)).log10() * 6000.0).floor() as i64
                    + self.params.max;
                selem
                    .set_playback_volume_all(vol_db)
                    .expect("Couldn't set alsa dB volume");
                info!(
                    "Mapping volume [{:.3}%] {:?} [u16] ->> Alsa [{:.3}%] {:?} [dB]",
                    self.pvol(vol, 0x0000, 0xFFFF) * 100.0,
                    vol,
                    self.pvol(
                        vol_db as f64,
                        self.params.min_db.to_db() as f64,
                        self.params.max_db.to_db() as f64
                    ) * 100.0,
                    vol_db as f64 / 100.0,
                );
            } else {
                info!("Using linear vol");
                let alsa_volume =
                    ((vol as f64 / 0xFFFF as f64) * self.params.range) as i64 + self.params.min;
                selem
                    .set_playback_volume_all(alsa_volume)
                    .expect("Couldn't set alsa raw volume");
                info!(
                    "Mapping 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
                );
            };
            new_vol = vol; // Meh
        } else {
            new_vol =
                (((cur_vol - self.params.min) as f64 / self.params.range) * 0xFFFF as f64) as u16;
            info!(
                "Mapping 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
{'fraction_non_alphanumeric': 0.14210761330654165, 'fraction_numerical': 0.02589863808885912, 'mean_word_length': 1.5855699855699856, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3928536', 'n_tokens_mistral': 3133, 'n_tokens_neox': 2870, 'n_words': 607}