wayle-audio 0.1.4

PulseAudio service with reactive state
Documentation
use libpulse_binding::volume::{ChannelVolumes, Volume as PulseVolume};

use crate::volume::types::Volume;

pub(crate) fn to_pulse(volume: &Volume) -> ChannelVolumes {
    let channels = volume.channels();
    if channels == 0 {
        return ChannelVolumes::default();
    }

    let mut pulse_volume = ChannelVolumes::default();
    pulse_volume.set(channels as u8, PulseVolume::MUTED);

    for (channel_idx, &level) in volume.as_slice().iter().enumerate() {
        pulse_volume.get_mut()[channel_idx] =
            PulseVolume((level * PulseVolume::NORMAL.0 as f64) as u32);
    }

    pulse_volume
}

pub(crate) fn from_pulse(pulse_volume: &ChannelVolumes) -> Volume {
    let volumes: Vec<f64> = (0..pulse_volume.len())
        .map(|channel_idx| {
            let raw = pulse_volume.get()[channel_idx as usize].0 as f64;
            raw / PulseVolume::NORMAL.0 as f64
        })
        .collect();

    Volume::new(volumes)
}

pub(super) fn from_pulse_single(pulse_volume: PulseVolume) -> Volume {
    Volume::new(vec![pulse_volume.0 as f64 / PulseVolume::NORMAL.0 as f64])
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_channels_returns_default() {
        let volume = Volume::new(vec![]);

        let result = to_pulse(&volume);

        assert_eq!(result.len(), 0);
    }

    #[test]
    fn preserves_per_channel_values() {
        let volume = Volume::stereo(0.3, 0.8);

        let result = to_pulse(&volume);

        assert_eq!(result.len(), 2);
        let left = result.get()[0].0 as f64 / PulseVolume::NORMAL.0 as f64;
        let right = result.get()[1].0 as f64 / PulseVolume::NORMAL.0 as f64;
        assert!((left - 0.3).abs() < 0.001);
        assert!((right - 0.8).abs() < 0.001);
    }

    #[test]
    fn normal_volume_maps_to_pa_normal() {
        let volume = Volume::mono(1.0);

        let result = to_pulse(&volume);

        assert_eq!(result.len(), 1);
        assert_eq!(result.avg(), PulseVolume::NORMAL);
    }

    #[test]
    fn zero_volume_maps_to_muted() {
        let volume = Volume::mono(0.0);

        let result = to_pulse(&volume);

        assert_eq!(result.len(), 1);
        assert_eq!(result.avg(), PulseVolume::MUTED);
    }

    #[test]
    fn max_volume_maps_correctly() {
        let volume = Volume::mono(4.0);

        let result = to_pulse(&volume);

        let expected = PulseVolume((4.0 * PulseVolume::NORMAL.0 as f64) as u32);
        assert_eq!(result.avg(), expected);
    }

    #[test]
    fn from_pulse_normal_volume() {
        let mut pulse_volume = ChannelVolumes::default();
        pulse_volume.set(1, PulseVolume::NORMAL);

        let result = from_pulse(&pulse_volume);

        assert_eq!(result.channels(), 1);
        assert_eq!(result.channel(0), Some(1.0));
    }

    #[test]
    fn from_pulse_muted() {
        let mut pulse_volume = ChannelVolumes::default();
        pulse_volume.set(1, PulseVolume::MUTED);

        let result = from_pulse(&pulse_volume);

        assert_eq!(result.channels(), 1);
        assert_eq!(result.channel(0), Some(0.0));
    }

    #[test]
    fn from_pulse_max_volume() {
        let mut pulse_volume = ChannelVolumes::default();
        let max = PulseVolume((4.0 * PulseVolume::NORMAL.0 as f64) as u32);
        pulse_volume.set(1, max);

        let result = from_pulse(&pulse_volume);

        assert_eq!(result.channels(), 1);
        assert!((result.channel(0).unwrap() - 4.0).abs() < 0.01);
    }

    #[test]
    fn from_pulse_multi_channel() {
        let mut pulse_volume = ChannelVolumes::default();
        pulse_volume.set(2, PulseVolume::NORMAL);

        let result = from_pulse(&pulse_volume);

        assert_eq!(result.channels(), 2);
        assert_eq!(result.channel(0), Some(1.0));
        assert_eq!(result.channel(1), Some(1.0));
    }

    #[test]
    fn roundtrip_preserves_stereo_balance() {
        let original = Volume::stereo(0.3, 0.8);

        let pulse = to_pulse(&original);
        let roundtrip = from_pulse(&pulse);

        assert_eq!(roundtrip.channels(), 2);
        assert!((roundtrip.channel(0).unwrap() - 0.3).abs() < 0.001);
        assert!((roundtrip.channel(1).unwrap() - 0.8).abs() < 0.001);
    }
}