use std::io::{Cursor, Read}; use byteorder::{ByteOrder, ReadBytesExt}; #[cfg(test)] use nearly_eq::NearlyEq; #[derive(Eq, PartialEq, Copy, Clone, Default, Debug)] #[repr(transparent)] pub struct Sample(Raw); impl From for Sample { #[inline] fn from(raw: i16) -> Self { Self(raw) } } impl From for Sample { #[inline] fn from(raw: f32) -> Self { Self(raw.clamp(-1.0, 1.0)) } } macro_rules! impl_from_sample_for_raw { ($raw:ty) => { impl From> for $raw { #[inline] fn from(sample: Sample<$raw>) -> $raw { sample.0 } } }; } impl_from_sample_for_raw!(i16); impl_from_sample_for_raw!(f32); // impl From> for Sample { // #[inline] // fn from(sample: Sample) -> Self { // // Fast but imprecise approach: // // Perform crude but fast upsample by bit-shifting the raw value: // Self::from((sample.0 as i64) << 16) // // Slow but precise approach: // // Perform a proper but expensive lerp from // // i16::MIN..i16::MAX to i32::MIN..i32::MAX: // // let value = sample.0 as i64; // // let from = if value <= 0 { i16::MIN } else { i16::MAX } as i64; // // let to = if value <= 0 { i32::MIN } else { i32::MAX } as i64; // // Self::from((value * to + from / 2) / from) // } // } impl From> for Sample { #[inline] fn from(sample: Sample) -> Self { let divisor = if sample.0 < 0 { i16::MIN as f32 } else { i16::MAX as f32 } .abs(); Self::from((sample.0 as f32) / divisor) } } impl From> for Sample { #[inline] fn from(sample: Sample) -> Self { let multiplier = if sample.0 < 0.0 { i16::MIN as f32 } else { i16::MAX as f32 } .abs(); Self::from((sample.0 * multiplier) as i16) } } trait FromBytes: Sized { fn from_reader(reader: &mut R) -> Result; fn from_bytes(bytes: &[u8]) -> Result { let mut cursor = Cursor::new(bytes); Self::from_reader::(&mut cursor) } } impl FromBytes for Sample { fn from_reader(reader: &mut R) -> Result { reader.read_i16::().map(Self::from) } } impl FromBytes for Sample { fn from_reader(reader: &mut R) -> Result { reader.read_f32::().map(Self::from) } } #[cfg(test)] impl NearlyEq for Sample where Raw: NearlyEq, { fn eps() -> Raw { Raw::eps() } fn eq(&self, other: &Self, eps: &Raw) -> bool { NearlyEq::eq(&self.0, &other.0, eps) } } #[cfg(test)] mod tests { use super::*; use nearly_eq::assert_nearly_eq; #[test] fn sample_i16_from_i16() { // i16: assert_eq!(Sample::::from(i16::MIN).0, i16::MIN); assert_eq!(Sample::::from(i16::MIN / 2).0, i16::MIN / 2); assert_eq!(Sample::::from(0).0, 0); assert_eq!(Sample::::from(i16::MAX / 2).0, i16::MAX / 2); assert_eq!(Sample::::from(i16::MAX).0, i16::MAX); } #[test] fn sample_f32_from_f32() { assert_eq!(Sample::::from(-1.0).0, -1.0); assert_eq!(Sample::::from(-0.5).0, -0.5); assert_eq!(Sample::::from(0.0).0, 0.0); assert_eq!(Sample::::from(0.5).0, 0.5); assert_eq!(Sample::::from(1.0).0, 1.0); // For any values outside of -1.0..=1.0 we expect clamping: assert_eq!(Sample::::from(f32::MIN).0, -1.0); assert_eq!(Sample::::from(f32::MAX).0, 1.0); } #[test] fn sample_i16_from_sample_f32() { assert_nearly_eq!( Sample::::from(Sample::::from(-1.0)), Sample::from(i16::MIN) ); assert_nearly_eq!( Sample::::from(Sample::::from(-0.5)), Sample::from(i16::MIN / 2) ); assert_nearly_eq!( Sample::::from(Sample::::from(0.0)), Sample::from(0) ); assert_nearly_eq!( Sample::::from(Sample::::from(0.5)), Sample::from(i16::MAX / 2) ); assert_nearly_eq!( Sample::::from(Sample::::from(1.0)), Sample::from(i16::MAX) ); } #[test] fn sample_f32_from_sample_i16() { assert_nearly_eq!( Sample::::from(Sample::::from(i16::MIN)), Sample::from(-1.0) ); assert_nearly_eq!( Sample::::from(Sample::::from(i16::MIN / 2)), Sample::from(-0.5) ); assert_nearly_eq!( Sample::::from(Sample::::from(0)), Sample::from(0.0) ); assert_nearly_eq!( Sample::::from(Sample::::from(i16::MAX / 2)), Sample::from(0.5), 0.0001 // rounding error due to i16::MAX being odd ); assert_nearly_eq!( Sample::::from(Sample::::from(i16::MAX)), Sample::from(1.0) ); } }