From 74b2fea33814b8ea190343d8ab6a7cd1f5c6f9c2 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Sun, 21 Mar 2021 22:16:47 +0100 Subject: [PATCH] Refactor sample conversion into separate struct --- audio/src/lib.rs | 67 +++++++++++++------------ playback/src/audio_backend/mod.rs | 9 ++-- playback/src/audio_backend/portaudio.rs | 15 +++--- playback/src/audio_backend/rodio.rs | 9 ++-- playback/src/audio_backend/sdl.rs | 14 +++--- 5 files changed, 61 insertions(+), 53 deletions(-) diff --git a/audio/src/lib.rs b/audio/src/lib.rs index 86c5b4ae..fe3b5c96 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -38,33 +38,6 @@ pub enum AudioPacket { OggData(Vec), } -#[derive(AsBytes, Copy, Clone, Debug)] -#[allow(non_camel_case_types)] -#[repr(transparent)] -pub struct i24([u8; 3]); -impl i24 { - fn pcm_from_i32(sample: i32) -> Self { - // drop the least significant byte - let [a, b, c, _d] = (sample >> 8).to_le_bytes(); - i24([a, b, c]) - } -} - -// Losslessly represent [-1.0, 1.0] to [$type::MIN, $type::MAX] while maintaining DC linearity. -macro_rules! convert_samples_to { - ($type: ident, $samples: expr) => { - convert_samples_to!($type, $samples, 0) - }; - ($type: ident, $samples: expr, $shift: expr) => { - $samples - .iter() - .map(|sample| { - (*sample as f64 * (std::$type::MAX as f64 + 0.5) - 0.5) as $type >> $shift - }) - .collect() - }; -} - impl AudioPacket { pub fn samples(&self) -> &[f32] { match self { @@ -86,23 +59,53 @@ impl AudioPacket { AudioPacket::OggData(d) => d.is_empty(), } } +} - pub fn f32_to_s32(samples: &[f32]) -> Vec { +#[derive(AsBytes, Copy, Clone, Debug)] +#[allow(non_camel_case_types)] +#[repr(transparent)] +pub struct i24([u8; 3]); +impl i24 { + fn pcm_from_i32(sample: i32) -> Self { + // drop the least significant byte + let [a, b, c, _d] = (sample >> 8).to_le_bytes(); + i24([a, b, c]) + } +} + +// Losslessly represent [-1.0, 1.0] to [$type::MIN, $type::MAX] while maintaining DC linearity. +macro_rules! convert_samples_to { + ($type: ident, $samples: expr) => { + convert_samples_to!($type, $samples, 0) + }; + ($type: ident, $samples: expr, $drop_bits: expr) => { + $samples + .iter() + .map(|sample| { + (*sample as f64 * (std::$type::MAX as f64 + 0.5) - 0.5) as $type >> $drop_bits + }) + .collect() + }; +} + +pub struct SamplesConverter {} +impl SamplesConverter { + pub fn to_s32(samples: &[f32]) -> Vec { convert_samples_to!(i32, samples) } - pub fn f32_to_s24(samples: &[f32]) -> Vec { + pub fn to_s24(samples: &[f32]) -> Vec { convert_samples_to!(i32, samples, 8) } - pub fn f32_to_s24_3(samples: &[f32]) -> Vec { - Self::f32_to_s32(samples) + pub fn to_s24_3(samples: &[f32]) -> Vec { + Self::to_s32(samples) .iter() .map(|sample| i24::pcm_from_i32(*sample)) .collect() } - pub fn f32_to_s16(samples: &[f32]) -> Vec { + pub fn to_s16(samples: &[f32]) -> Vec { convert_samples_to!(i16, samples) } } diff --git a/playback/src/audio_backend/mod.rs b/playback/src/audio_backend/mod.rs index 9c46dbe4..94b6a529 100644 --- a/playback/src/audio_backend/mod.rs +++ b/playback/src/audio_backend/mod.rs @@ -24,24 +24,25 @@ fn mk_sink(device: Option, format: AudioFormat macro_rules! sink_as_bytes { () => { fn write(&mut self, packet: &AudioPacket) -> io::Result<()> { + use crate::audio::{i24, SamplesConverter}; use zerocopy::AsBytes; match packet { AudioPacket::Samples(samples) => match self.format { AudioFormat::F32 => self.write_bytes(samples.as_bytes()), AudioFormat::S32 => { - let samples_s32 = AudioPacket::f32_to_s32(samples); + let samples_s32: &[i32] = &SamplesConverter::to_s32(samples); self.write_bytes(samples_s32.as_bytes()) } AudioFormat::S24 => { - let samples_s24 = AudioPacket::f32_to_s24(samples); + let samples_s24: &[i32] = &SamplesConverter::to_s24(samples); self.write_bytes(samples_s24.as_bytes()) } AudioFormat::S24_3 => { - let samples_s24_3 = AudioPacket::f32_to_s24_3(samples); + let samples_s24_3: &[i24] = &SamplesConverter::to_s24_3(samples); self.write_bytes(samples_s24_3.as_bytes()) } AudioFormat::S16 => { - let samples_s16 = AudioPacket::f32_to_s16(samples); + let samples_s16: &[i16] = &SamplesConverter::to_s16(samples); self.write_bytes(samples_s16.as_bytes()) } }, diff --git a/playback/src/audio_backend/portaudio.rs b/playback/src/audio_backend/portaudio.rs index fca305e0..f29bac2d 100644 --- a/playback/src/audio_backend/portaudio.rs +++ b/playback/src/audio_backend/portaudio.rs @@ -1,5 +1,5 @@ use super::{Open, Sink}; -use crate::audio::AudioPacket; +use crate::audio::{AudioPacket, SamplesConverter}; use crate::config::AudioFormat; use crate::player::{NUM_CHANNELS, SAMPLE_RATE}; use portaudio_rs; @@ -146,18 +146,19 @@ impl<'a> Sink for PortAudioSink<'a> { $stream.as_mut().unwrap().write($samples) }; } + + let samples = packet.samples(); let result = match self { Self::F32(stream, _parameters) => { - let samples = packet.samples(); - write_sink!(stream, &samples) + write_sink!(stream, samples) } Self::S32(stream, _parameters) => { - let samples_s32: Vec = AudioPacket::f32_to_s32(packet.samples()); - write_sink!(stream, &samples_s32) + let samples_s32: &[i32] = &SamplesConverter::to_s32(samples); + write_sink!(stream, samples_s32) } Self::S16(stream, _parameters) => { - let samples_s16: Vec = AudioPacket::f32_to_s16(packet.samples()); - write_sink!(stream, &samples_s16) + let samples_s16: &[i16] = &SamplesConverter::to_s16(samples); + write_sink!(stream, samples_s16) } }; match result { diff --git a/playback/src/audio_backend/rodio.rs b/playback/src/audio_backend/rodio.rs index 5262a9cc..2fc4fbde 100644 --- a/playback/src/audio_backend/rodio.rs +++ b/playback/src/audio_backend/rodio.rs @@ -1,8 +1,9 @@ use super::{Open, Sink}; extern crate cpal; extern crate rodio; -use crate::audio::AudioPacket; +use crate::audio::{AudioPacket, SamplesConverter}; use crate::config::AudioFormat; +use crate::player::{NUM_CHANNELS, SAMPLE_RATE}; use cpal::traits::{DeviceTrait, HostTrait}; use std::process::exit; use std::{io, thread, time}; @@ -25,12 +26,12 @@ macro_rules! rodio_sink { let samples = packet.samples(); match self.format { AudioFormat::F32 => { - let source = rodio::buffer::SamplesBuffer::new(2, 44100, samples); + let source = rodio::buffer::SamplesBuffer::new(NUM_CHANNELS as u16, SAMPLE_RATE, samples); self.rodio_sink.append(source) }, AudioFormat::S16 => { - let samples_s16: Vec = AudioPacket::f32_to_s16(samples); - let source = rodio::buffer::SamplesBuffer::new(2, 44100, samples_s16); + let samples_s16: &[i16] = &SamplesConverter::to_s16(samples); + let source = rodio::buffer::SamplesBuffer::new(NUM_CHANNELS as u16, SAMPLE_RATE, samples_s16); self.rodio_sink.append(source) }, _ => unimplemented!(), diff --git a/playback/src/audio_backend/sdl.rs b/playback/src/audio_backend/sdl.rs index 64523732..b1b4c2e1 100644 --- a/playback/src/audio_backend/sdl.rs +++ b/playback/src/audio_backend/sdl.rs @@ -1,5 +1,5 @@ use super::{Open, Sink}; -use crate::audio::AudioPacket; +use crate::audio::{AudioPacket, SamplesConverter}; use crate::config::AudioFormat; use crate::player::{NUM_CHANNELS, SAMPLE_RATE}; use sdl2::audio::{AudioQueue, AudioSpecDesired}; @@ -89,20 +89,22 @@ impl Sink for SdlSink { } }}; } + + let samples = packet.samples(); match self { Self::F32(queue) => { drain_sink!(queue, mem::size_of::()); - queue.queue(packet.samples()) + queue.queue(samples) } Self::S32(queue) => { + let samples_s32: &[i32] = &SamplesConverter::to_s32(samples); drain_sink!(queue, mem::size_of::()); - let samples_s32: Vec = AudioPacket::f32_to_s32(packet.samples()); - queue.queue(&samples_s32) + queue.queue(samples_s32) } Self::S16(queue) => { + let samples_s16: &[i16] = &SamplesConverter::to_s16(samples); drain_sink!(queue, mem::size_of::()); - let samples_s16: Vec = AudioPacket::f32_to_s16(packet.samples()); - queue.queue(&samples_s16) + queue.queue(samples_s16) } }; Ok(())