concord 2.4.8

A terminal user interface client for Discord
Documentation
use cpal::traits::DeviceTrait;

pub(crate) trait F32OutputSource {
    fn fill<T>(&mut self, output: &mut [T], channels: usize, convert: fn(f32) -> T)
    where
        T: Default + Copy;
}

pub(crate) fn build_f32_output_stream<S, E>(
    device: &cpal::Device,
    config: &cpal::StreamConfig,
    sample_format: cpal::SampleFormat,
    source: S,
    error_callback: E,
    unsupported_context: &str,
    build_context: &str,
) -> Result<cpal::Stream, String>
where
    S: F32OutputSource + Send + 'static,
    E: FnMut(cpal::Error) + Send + 'static,
{
    match sample_format {
        cpal::SampleFormat::F32 => build_f32_output_stream_as::<f32, _, _>(
            device,
            config,
            source,
            error_callback,
            clamp_f32_sample,
            build_context,
        ),
        cpal::SampleFormat::F64 => build_f32_output_stream_as::<f64, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_f64,
            build_context,
        ),
        cpal::SampleFormat::U8 => build_f32_output_stream_as::<u8, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_u8,
            build_context,
        ),
        cpal::SampleFormat::I8 => build_f32_output_stream_as::<i8, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_i8,
            build_context,
        ),
        cpal::SampleFormat::I16 => build_f32_output_stream_as::<i16, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_i16,
            build_context,
        ),
        cpal::SampleFormat::I24 => build_f32_output_stream_as::<cpal::I24, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_i24,
            build_context,
        ),
        cpal::SampleFormat::I32 => build_f32_output_stream_as::<i32, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_i32,
            build_context,
        ),
        cpal::SampleFormat::I64 => build_f32_output_stream_as::<i64, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_i64,
            build_context,
        ),
        cpal::SampleFormat::U16 => build_f32_output_stream_as::<u16, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_u16,
            build_context,
        ),
        cpal::SampleFormat::U24 => build_f32_output_stream_as::<cpal::U24, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_u24,
            build_context,
        ),
        cpal::SampleFormat::U32 => build_f32_output_stream_as::<u32, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_u32,
            build_context,
        ),
        cpal::SampleFormat::U64 => build_f32_output_stream_as::<u64, _, _>(
            device,
            config,
            source,
            error_callback,
            f32_sample_to_u64,
            build_context,
        ),
        other => Err(format!(
            "unsupported {unsupported_context} sample format: {other:?}"
        )),
    }
}

fn build_f32_output_stream_as<T, S, E>(
    device: &cpal::Device,
    config: &cpal::StreamConfig,
    mut source: S,
    error_callback: E,
    convert: fn(f32) -> T,
    build_context: &str,
) -> Result<cpal::Stream, String>
where
    T: cpal::SizedSample + Default + Copy + 'static,
    S: F32OutputSource + Send + 'static,
    E: FnMut(cpal::Error) + Send + 'static,
{
    let channels = usize::from(config.channels.max(1));
    device
        .build_output_stream(
            *config,
            move |output: &mut [T], _| source.fill(output, channels, convert),
            error_callback,
            None,
        )
        .map_err(|error| format!("{build_context} output stream build failed: {error}"))
}

pub(crate) fn clamp_f32_sample(sample: f32) -> f32 {
    sample.clamp(-1.0, 1.0)
}

pub(crate) fn f32_sample_to_f64(sample: f32) -> f64 {
    f64::from(clamp_f32_sample(sample))
}

pub(crate) fn f32_sample_to_u8(sample: f32) -> u8 {
    ((clamp_f32_sample(sample) + 1.0) * 0.5 * f32::from(u8::MAX)).round() as u8
}

pub(crate) fn f32_sample_to_i8(sample: f32) -> i8 {
    (clamp_f32_sample(sample) * f32::from(i8::MAX)).round() as i8
}

pub(crate) fn f32_sample_to_i16(sample: f32) -> i16 {
    (clamp_f32_sample(sample) * f32::from(i16::MAX)).round() as i16
}

pub(crate) fn f32_sample_to_i24(sample: f32) -> cpal::I24 {
    let value = (clamp_f32_sample(sample) * 8_388_607.0).round() as i32;
    cpal::I24::new(value).expect("clamped sample fits I24")
}

pub(crate) fn f32_sample_to_i32(sample: f32) -> i32 {
    (f64::from(clamp_f32_sample(sample)) * f64::from(i32::MAX)).round() as i32
}

pub(crate) fn f32_sample_to_i64(sample: f32) -> i64 {
    let sample = clamp_f32_sample(sample);
    if sample == 1.0 {
        i64::MAX
    } else if sample == -1.0 {
        -i64::MAX
    } else {
        (f64::from(sample) * i64::MAX as f64).round() as i64
    }
}

pub(crate) fn f32_sample_to_u16(sample: f32) -> u16 {
    ((clamp_f32_sample(sample) + 1.0) * 0.5 * f32::from(u16::MAX)).round() as u16
}

pub(crate) fn f32_sample_to_u24(sample: f32) -> cpal::U24 {
    let value = ((clamp_f32_sample(sample) + 1.0) * 0.5 * 16_777_215.0).round() as i32;
    cpal::U24::new(value).expect("clamped sample fits U24")
}

pub(crate) fn f32_sample_to_u32(sample: f32) -> u32 {
    ((f64::from(clamp_f32_sample(sample)) + 1.0) * 0.5 * f64::from(u32::MAX)).round() as u32
}

pub(crate) fn f32_sample_to_u64(sample: f32) -> u64 {
    let sample = clamp_f32_sample(sample);
    if sample == 1.0 {
        u64::MAX
    } else if sample == -1.0 {
        u64::MIN
    } else {
        ((f64::from(sample) + 1.0) * 0.5 * u64::MAX as f64).round() as u64
    }
}

#[cfg(test)]
mod tests {
    use super::{
        f32_sample_to_f64, f32_sample_to_i8, f32_sample_to_i16, f32_sample_to_i24,
        f32_sample_to_i32, f32_sample_to_i64, f32_sample_to_u8, f32_sample_to_u16,
        f32_sample_to_u24, f32_sample_to_u32, f32_sample_to_u64,
    };

    #[test]
    fn converts_f32_samples_to_output_ranges() {
        assert_eq!(f32_sample_to_f64(1.0), 1.0);
        assert_eq!(f32_sample_to_f64(-1.0), -1.0);
        assert_eq!(f32_sample_to_f64(0.0), 0.0);
        assert_eq!(f32_sample_to_u8(1.0), u8::MAX);
        assert_eq!(f32_sample_to_u8(-1.0), 0);
        assert_eq!(f32_sample_to_i8(1.0), i8::MAX);
        assert_eq!(f32_sample_to_i8(-1.0), -i8::MAX);
        assert_eq!(f32_sample_to_i8(0.0), 0);
        assert_eq!(f32_sample_to_i16(1.0), i16::MAX);
        assert_eq!(f32_sample_to_i16(-1.0), -i16::MAX);
        assert_eq!(f32_sample_to_i24(1.0).inner(), 8_388_607);
        assert_eq!(f32_sample_to_i24(-1.0).inner(), -8_388_607);
        assert_eq!(f32_sample_to_i24(0.0).inner(), 0);
        assert_eq!(f32_sample_to_i32(1.0), i32::MAX);
        assert_eq!(f32_sample_to_i32(-1.0), -i32::MAX);
        assert_eq!(f32_sample_to_i32(0.0), 0);
        assert_eq!(f32_sample_to_i64(1.0), i64::MAX);
        assert_eq!(f32_sample_to_i64(-1.0), -i64::MAX);
        assert_eq!(f32_sample_to_i64(0.0), 0);
        assert_eq!(f32_sample_to_u16(1.0), u16::MAX);
        assert_eq!(f32_sample_to_u16(-1.0), 0);
        assert_eq!(f32_sample_to_u24(1.0).inner(), 16_777_215);
        assert_eq!(f32_sample_to_u24(-1.0).inner(), 0);
        assert_eq!(f32_sample_to_u24(0.0).inner(), 8_388_608);
        assert_eq!(f32_sample_to_u32(1.0), u32::MAX);
        assert_eq!(f32_sample_to_u32(-1.0), u32::MIN);
        assert_eq!(f32_sample_to_u32(0.0), 1 << 31);
        assert_eq!(f32_sample_to_u64(1.0), u64::MAX);
        assert_eq!(f32_sample_to_u64(-1.0), u64::MIN);
        assert_eq!(f32_sample_to_u64(0.0), 1 << 63);
    }
}