use bytemuck::NoUninit;
use ndarray::ScalarOperand;
use num_traits::{FromPrimitive, Num, NumCast, One, ToBytes, Zero};
use serde::{Deserialize, Serialize};
use crate::repr::SampleType;
use crate::{AudioSamples, I24};
use std::fmt::{Debug, Display};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
pub trait CastFrom<S>: Sized {
fn cast_from(value: S) -> Self;
}
pub trait CastInto<T>: Sized
where
Self: CastFrom<T>,
{
fn cast_into(self) -> T;
}
pub trait Castable:
CastInto<u8> + CastInto<i16> + CastInto<I24> + CastInto<i32> + CastInto<f32> + CastInto<f64>
{
}
impl<T> Castable for T where
T: CastInto<u8> + CastInto<i16> + CastInto<I24> + CastInto<i32> + CastInto<f32> + CastInto<f64>
{
}
mod sealed {
pub trait Sealed {}
}
use sealed::Sealed;
#[non_exhaustive]
pub struct SampleByteSize<const N: usize>;
pub trait SupportedByteSize: Sealed {}
impl<const N: usize> Sealed for SampleByteSize<N> {}
impl SupportedByteSize for SampleByteSize<1> {}
impl SupportedByteSize for SampleByteSize<2> {}
impl SupportedByteSize for SampleByteSize<3> {}
impl SupportedByteSize for SampleByteSize<4> {}
impl SupportedByteSize for SampleByteSize<8> {}
pub trait AudioSample:
Copy
+ Sized
+ Default
+ Display
+ Debug
+ Sync
+ Send
+ PartialEq
+ PartialOrd
+ Add<Output = Self>
+ AddAssign<Self>
+ Sub<Output = Self>
+ SubAssign<Self>
+ Mul<Output = Self>
+ MulAssign<Self>
+ Div<Output = Self>
+ DivAssign<Self>
+ Rem<Output = Self>
+ RemAssign<Self>
+ Into<Self>
+ From<Self>
+ ToString
+ NoUninit + Num + One + Zero + ToBytes + Serialize + Deserialize<'static> + FromPrimitive + NumCast + ScalarOperand
+ ConvertTo<Self> + ConvertTo<u8> + ConvertTo<i16> + ConvertTo<I24> + ConvertTo<i32> + ConvertTo<f32> + ConvertTo<f64> + CastFrom<usize> + Castable {
#[inline]
#[must_use]
fn into_inner(self) -> Self {
self
}
#[inline]
#[must_use]
fn clamp_to(self, min: Self, max: Self) -> Self {
if self < min { min } else if self > max { max } else { self }
}
#[doc(hidden)]
#[inline]
fn avx2_abs_max(_slice: &[Self]) -> Option<Self> { None }
#[inline]
#[must_use]
fn to_bytes(self) -> Vec<u8> {
self.to_ne_bytes().as_ref().to_vec()
}
#[inline]
fn as_bytes<const N: usize>(&self) -> [u8; N]
where SampleByteSize<N>: SupportedByteSize,
{
let bytes_ref = self.to_ne_bytes();
let bytes_slice: &[u8] = bytes_ref.as_ref();
let mut result = [0u8; N];
result.copy_from_slice(bytes_slice);
result
}
#[inline]
fn slice_to_bytes(samples: &[Self]) -> Vec<u8> {
Vec::from(bytemuck::cast_slice(samples))
}
#[inline]
fn as_float(self) -> f64
{
self.cast_into()
}
const MAX: Self;
const MIN: Self;
const BITS: u8;
const BYTES: u32 = Self::BITS as u32 / 8;
const LABEL: &'static str;
const SAMPLE_TYPE: SampleType;
}
pub trait StandardSample:
AudioSample
+ CastInto<f64>
+ CastFrom<f64>
+ ConvertFrom<Self>
+ ConvertFrom<u8>
+ ConvertFrom<i16>
+ ConvertFrom<I24>
+ ConvertFrom<i32>
+ ConvertFrom<f32>
+ ConvertFrom<f64>
+ Castable
{
}
impl<T> StandardSample for T where
T: AudioSample
+ CastInto<f64>
+ CastFrom<f64>
+ ConvertFrom<Self>
+ ConvertFrom<u8>
+ ConvertFrom<i16>
+ ConvertFrom<I24>
+ ConvertFrom<i32>
+ ConvertFrom<f32>
+ ConvertFrom<f64>
{
}
pub trait ConvertTo<Dst> {
fn convert_to(self) -> Dst;
}
pub trait ConvertFrom<Src> {
fn convert_from(source: Src) -> Self;
}
impl<Src, Dst> ConvertTo<Dst> for Src
where
Dst: ConvertFrom<Src>,
{
#[inline]
fn convert_to(self) -> Dst {
Dst::convert_from(self)
}
}
macro_rules! impl_identity_conversion {
($ty:ty) => {
impl ConvertFrom<$ty> for $ty {
#[inline]
fn convert_from(source: $ty) -> Self {
source
}
}
};
}
macro_rules! impl_int_to_int_conversion {
($from:ty, $to:ty) => {
impl ConvertFrom<$from> for $to {
#[inline]
fn convert_from(source: $from) -> Self {
let from_bits = <$from>::BITS as i32;
let to_bits = <$to>::BITS as i32;
let v = <i128 as From<$from>>::from(source);
let scaled = if from_bits < to_bits {
v << (to_bits - from_bits)
} else if from_bits > to_bits {
v >> (from_bits - to_bits)
} else {
v
};
let min = <i128 as From<$to>>::from(<$to>::MIN);
let max = <i128 as From<$to>>::from(<$to>::MAX);
if scaled < min {
<$to>::MIN
} else if scaled > max {
<$to>::MAX
} else {
scaled as $to
}
}
}
};
}
macro_rules! impl_i24_to_int {
($to:ty) => {
impl ConvertFrom<I24> for $to {
#[inline]
fn convert_from(source: I24) -> Self {
let to_bits = <$to>::BITS as i32;
let v = <i128 as From<i32>>::from(source.to_i32());
let shift = to_bits - 24;
let scaled = if shift >= 0 {
v << shift
} else {
v >> (-shift)
};
let min = <i128 as From<$to>>::from(<$to>::MIN);
let max = <i128 as From<$to>>::from(<$to>::MAX);
if scaled < min {
<$to>::MIN
} else if scaled > max {
<$to>::MAX
} else {
scaled as $to
}
}
}
};
}
macro_rules! impl_int_to_i24 {
($from:ty) => {
impl ConvertFrom<$from> for I24 {
#[inline]
fn convert_from(source: $from) -> Self {
let from_bits = <$from>::BITS as i32;
let v = <i128 as From<$from>>::from(source);
let shift = 24 - from_bits;
let scaled = if shift >= 0 {
v << shift
} else {
v >> (-shift)
};
let min = <i128 as From<i32>>::from(I24::MIN.to_i32());
let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
let clamped = if scaled < min {
min as i32
} else if scaled > max {
max as i32
} else {
scaled as i32
};
I24::saturating_from_i32(clamped)
}
}
};
}
macro_rules! impl_i24_to_float {
($to:ty) => {
impl ConvertFrom<I24> for $to {
#[inline]
#[allow(clippy::cast_lossless)]
fn convert_from(source: I24) -> Self {
let v = source.to_i32() as $to;
let max = I24::MAX.to_i32() as $to;
let min = I24::MIN.to_i32() as $to;
if v < 0.0 { v / -min } else { v / max }
}
}
};
}
macro_rules! impl_int_to_float {
($from:ty, $to:ty) => {
impl ConvertFrom<$from> for $to {
#[inline]
fn convert_from(source: $from) -> Self {
let v = source;
if v < 0 {
(v as $to) / (-(<$from>::MIN as $to))
} else {
(v as $to) / (<$from>::MAX as $to)
}
}
}
};
}
macro_rules! impl_float_to_int {
($from:ty, $to:ty) => {
impl ConvertFrom<$from> for $to {
#[inline]
fn convert_from(source: $from) -> Self {
let v = source.max(-1.0).min(1.0);
let scaled = v * (<$to>::MAX as $from);
let as_i32: i32 = unsafe { scaled.to_int_unchecked() };
as_i32 as $to
}
}
};
}
macro_rules! impl_float_to_large_int {
($from:ty, $to:ty) => {
impl ConvertFrom<$from> for $to {
#[inline]
fn convert_from(source: $from) -> Self {
let v = source.max(-1.0).min(1.0);
let scaled = v * (<$to>::MAX as $from);
scaled.clamp(<$to>::MIN as $from, <$to>::MAX as $from) as $to
}
}
};
}
macro_rules! impl_float_to_i24 {
($from:ty) => {
impl ConvertFrom<$from> for I24 {
#[inline]
fn convert_from(source: $from) -> Self {
let v = source.clamp(-1.0, 1.0);
let scaled = if v < 0.0 {
v * (-(I24::MIN.to_i32() as $from))
} else {
v * (I24::MAX.to_i32() as $from)
};
let rounded = scaled.round();
let clamped = if rounded < (I24::MIN.to_i32() as $from) {
I24::MIN.to_i32()
} else if rounded > (I24::MAX.to_i32() as $from) {
I24::MAX.to_i32()
} else {
rounded as i32
};
I24::saturating_from_i32(clamped)
}
}
};
}
macro_rules! impl_float_to_float {
($from:ty, $to:ty) => {
impl ConvertFrom<$from> for $to {
#[inline]
fn convert_from(source: $from) -> Self {
source as $to
}
}
};
}
#[inline]
const fn div_round_nearest_i128(num: i128, den: i128) -> i128 {
(num + (den / 2)) / den
}
macro_rules! impl_u8_to_int {
($to:ty) => {
impl ConvertFrom<u8> for $to {
#[inline]
#[allow(clippy::cast_lossless)]
fn convert_from(source: u8) -> Self {
let c: i128 = (source as i128) - 128;
let scaled: i128 = if c < 0 {
c * (-(<$to>::MIN as i128)) / 128
} else {
c * (<$to>::MAX as i128) / 127
};
let min = <$to>::MIN as i128;
let max = <$to>::MAX as i128;
if scaled < min {
<$to>::MIN
} else if scaled > max {
<$to>::MAX
} else {
scaled as $to
}
}
}
};
}
macro_rules! impl_int_to_u8 {
($from:ty) => {
impl ConvertFrom<$from> for u8 {
#[inline]
#[allow(clippy::cast_lossless)]
fn convert_from(source: $from) -> Self {
let v: i128 = source as i128;
let out_i128: i128 = if v < 0 {
let mag = (-v) as i128; let den = (-(<$from>::MIN as i128)); let scaled = div_round_nearest_i128(mag * 128, den); 128 - scaled
} else {
let den = (<$from>::MAX as i128);
let scaled = div_round_nearest_i128(v * 127, den); 128 + scaled
};
if out_i128 < 0 {
0
} else if out_i128 > 255 {
255
} else {
out_i128 as u8
}
}
}
};
}
macro_rules! impl_u8_to_i24 {
() => {
impl ConvertFrom<u8> for I24 {
#[inline]
fn convert_from(source: u8) -> Self {
let c: i128 = (<i128 as From<u8>>::from(source)) - 128; let min = <i128 as From<i32>>::from(I24::MIN.to_i32());
let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
let scaled: i128 = if c < 0 {
c * (-min) / 128
} else {
c * max / 127
};
let clamped: i32 = if scaled < min {
min as i32
} else if scaled > max {
max as i32
} else {
scaled as i32
};
I24::saturating_from_i32(clamped)
}
}
};
}
macro_rules! impl_i24_to_u8 {
() => {
impl ConvertFrom<I24> for u8 {
#[inline]
fn convert_from(source: I24) -> Self {
let v: i128 = <i128 as From<i32>>::from(source.to_i32());
let min = <i128 as From<i32>>::from(I24::MIN.to_i32()); let max = <i128 as From<i32>>::from(I24::MAX.to_i32());
let out_i128: i128 = if v < 0 {
let mag = <i128 as From<i128>>::from(-v);
let den = <i128 as From<i128>>::from(-min); let scaled = div_round_nearest_i128(mag * 128, den); 128 - scaled
} else {
let den = <i128 as From<i128>>::from(max); let scaled = div_round_nearest_i128(v * 127, den); 128 + scaled
};
if out_i128 < 0 {
0
} else if out_i128 > 255 {
255
} else {
out_i128 as u8
}
}
}
};
}
macro_rules! impl_u8_to_float {
($to:ty) => {
impl ConvertFrom<u8> for $to {
#[inline]
fn convert_from(source: u8) -> Self {
let c: i32 = (<i32 as From<u8>>::from(source)) - 128; let v = c as $to;
if c < 0 {
v / (128.0 as $to)
} else {
v / (127.0 as $to)
}
}
}
};
}
macro_rules! impl_float_to_u8 {
($from:ty) => {
impl ConvertFrom<$from> for u8 {
#[inline]
fn convert_from(source: $from) -> Self {
let v = source.clamp(-1.0, 1.0);
let c: i128 = if v < 0.0 {
(v * (128.0 as $from)).round() as i128
} else {
(v * (127.0 as $from)).round() as i128
};
let out = 128i128 + c;
if out < 0 {
0
} else if out > 255 {
255
} else {
out as u8
}
}
}
};
}
impl_identity_conversion!(u8);
impl_u8_to_int!(i16);
impl_u8_to_int!(i32);
impl_int_to_u8!(i16);
impl_int_to_u8!(i32);
impl_u8_to_i24!();
impl_i24_to_u8!();
impl_u8_to_float!(f32);
impl_u8_to_float!(f64);
impl_float_to_u8!(f32);
impl_float_to_u8!(f64);
impl_identity_conversion!(i16);
impl_identity_conversion!(I24);
impl_identity_conversion!(i32);
impl_identity_conversion!(f32);
impl_identity_conversion!(f64);
impl_int_to_int_conversion!(i16, i32);
impl_int_to_int_conversion!(i32, i16);
impl_i24_to_int!(i16);
impl_i24_to_int!(i32);
impl_int_to_i24!(i16);
impl_int_to_i24!(i32);
impl_int_to_float!(i16, f32);
impl_int_to_float!(i16, f64);
impl_int_to_float!(i32, f32);
impl_int_to_float!(i32, f64);
impl_i24_to_float!(f32);
impl_i24_to_float!(f64);
impl_float_to_int!(f32, i16);
impl_float_to_int!(f64, i16);
impl_float_to_large_int!(f32, i32);
impl_float_to_large_int!(f64, i32);
impl_float_to_i24!(f32);
impl_float_to_i24!(f64);
impl_float_to_float!(f32, f64);
impl_float_to_float!(f64, f32);
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn abs_max_f32_avx2(slice: &[f32]) -> f32 {
use std::arch::x86_64::{
_mm_cvtss_f32, _mm_max_ps, _mm_max_ss, _mm_movehl_ps, _mm_shuffle_ps, _mm256_andnot_ps,
_mm256_castps256_ps128, _mm256_extractf128_ps, _mm256_loadu_ps, _mm256_max_ps,
_mm256_set1_ps, _mm256_setzero_ps,
};
let sign_mask = _mm256_set1_ps(-0.0_f32);
let mut max_v = _mm256_setzero_ps();
let chunks = slice.len() / 8;
let ptr = slice.as_ptr();
for i in 0..chunks {
unsafe {
let v = _mm256_loadu_ps(ptr.add(i * 8));
let abs_v = _mm256_andnot_ps(sign_mask, v); max_v = _mm256_max_ps(max_v, abs_v);
}
}
let hi128 = _mm256_extractf128_ps(max_v, 1); let lo128 = _mm256_castps256_ps128(max_v); let max128 = _mm_max_ps(lo128, hi128);
let shuf = _mm_movehl_ps(max128, max128);
let max64 = _mm_max_ps(max128, shuf);
let shuf2 = _mm_shuffle_ps(max64, max64, 0x1);
let max32 = _mm_max_ss(max64, shuf2);
let mut result = _mm_cvtss_f32(max32);
for &x in &slice[chunks * 8..] {
let ax = x.abs();
if ax > result {
result = ax;
}
}
result
}
impl AudioSample for u8 {
const MAX: Self = Self::MAX;
const MIN: Self = Self::MIN;
const BITS: Self = 8;
const LABEL: &'static str = "u8";
const SAMPLE_TYPE: SampleType = SampleType::U8;
#[inline]
fn clamp_to(self, min: Self, max: Self) -> Self {
Ord::clamp(self, min, max)
}
}
impl AudioSample for i16 {
const MAX: Self = Self::MAX;
const MIN: Self = Self::MIN;
const BITS: u8 = 16;
const LABEL: &'static str = "i16";
const SAMPLE_TYPE: SampleType = SampleType::I16;
#[inline]
fn clamp_to(self, min: Self, max: Self) -> Self {
Ord::clamp(self, min, max)
}
}
impl AudioSample for I24 {
#[inline]
fn slice_to_bytes(samples: &[Self]) -> Vec<u8> {
Self::write_i24s_ne(samples)
}
const MAX: Self = Self::MAX;
const MIN: Self = Self::MIN;
const BITS: u8 = 24;
const LABEL: &'static str = "I24";
const SAMPLE_TYPE: SampleType = SampleType::I24;
#[inline]
fn clamp_to(self, min: Self, max: Self) -> Self {
Ord::clamp(self, min, max)
}
}
impl AudioSample for i32 {
const MAX: Self = Self::MAX;
const MIN: Self = Self::MIN;
const BITS: u8 = 32;
const LABEL: &'static str = "i32";
const SAMPLE_TYPE: SampleType = SampleType::I32;
#[inline]
fn clamp_to(self, min: Self, max: Self) -> Self {
Ord::clamp(self, min, max)
}
}
impl AudioSample for f32 {
const MAX: Self = 1.0;
const MIN: Self = -1.0;
const BITS: u8 = 32;
const LABEL: &'static str = "f32";
const SAMPLE_TYPE: SampleType = SampleType::F32;
#[inline]
fn clamp_to(self, min: Self, max: Self) -> Self {
self.clamp(min, max)
}
#[inline]
fn avx2_abs_max(slice: &[Self]) -> Option<Self> {
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("avx2") {
return Some(unsafe { abs_max_f32_avx2(slice) });
}
None
}
}
impl AudioSample for f64 {
const MAX: Self = 1.0;
const MIN: Self = -1.0;
const BITS: u8 = 64;
const LABEL: &'static str = "f64";
const SAMPLE_TYPE: SampleType = SampleType::F64;
#[inline]
fn clamp_to(self, min: Self, max: Self) -> Self {
self.clamp(min, max)
}
}
macro_rules! impl_cast_from {
($src:ty => [$($dst:ty),+]) => {
$(
impl CastFrom<$src> for $dst {
#[inline]
fn cast_from(value: $src) -> Self {
value as $dst
}
}
)+
};
}
impl_cast_from!(u8 => [u8, i16, i32, f32, f64]);
impl_cast_from!(i16 => [u8, i16, i32, f32, f64]);
impl_cast_from!(i32 => [u8, i16, i32, f32, f64]);
impl_cast_from!(f64 => [u8, i16, i32, f32, f64]);
impl_cast_from!(f32 => [u8, i16, i32, f32, f64]);
macro_rules! impl_cast_from_i24 {
($src:ty => $dst:ty) => {
impl CastFrom<$src> for $dst {
#[inline]
#[allow(clippy::cast_lossless)]
fn cast_from(value: $src) -> Self {
value as $dst
}
}
};
(clamp_usize $src:ty => $dst:ty, $max:expr) => {
impl CastFrom<$src> for $dst {
#[inline]
fn cast_from(value: $src) -> Self {
if value > $max as $src {
$max
} else {
value as $dst
}
}
}
};
(usize_to_i24 $src:ty => $dst:ty) => {
impl CastFrom<$src> for $dst {
#[inline]
fn cast_from(value: $src) -> Self {
if value > I24::MAX.to_i32() as $src {
I24::MAX
} else {
match I24::try_from_i32(value as i32) {
Some(x) => x,
None => I24::MIN,
}
}
}
}
};
(i24_to_primitive $src:ty => $dst:ty) => {
impl CastFrom<$src> for $dst {
#[inline]
#[allow(clippy::cast_lossless)]
fn cast_from(value: $src) -> Self {
value.to_i32() as $dst
}
}
};
(primitive_to_i24 $src:ty => $dst:ty) => {
impl CastFrom<$src> for $dst {
#[inline]
#[allow(clippy::cast_lossless)]
fn cast_from(value: $src) -> Self {
I24::try_from_i32(value as i32).unwrap_or(I24::MIN)
}
}
};
(identity $t:ty) => {
impl CastFrom<$t> for $t {
#[inline]
fn cast_from(value: $t) -> Self {
value
}
}
};
}
impl_cast_from_i24!(clamp_usize usize => u8, u8::MAX);
impl_cast_from_i24!(clamp_usize usize => i16, i16::MAX);
impl_cast_from_i24!(usize_to_i24 usize => I24);
impl_cast_from_i24!(clamp_usize usize => i32, i32::MAX);
impl_cast_from_i24!(usize => f32);
impl_cast_from_i24!(usize => f64);
impl_cast_from_i24!(i24_to_primitive I24 => u8);
impl_cast_from_i24!(i24_to_primitive I24 => i16);
impl_cast_from_i24!(identity I24);
impl_cast_from_i24!(i24_to_primitive I24 => i32);
impl_cast_from_i24!(i24_to_primitive I24 => f32);
impl_cast_from_i24!(i24_to_primitive I24 => f64);
impl_cast_from_i24!(primitive_to_i24 u8 => I24);
impl_cast_from_i24!(primitive_to_i24 i16 => I24);
impl_cast_from_i24!(primitive_to_i24 i32 => I24);
impl_cast_from_i24!(primitive_to_i24 f32 => I24);
impl_cast_from_i24!(primitive_to_i24 f64 => I24);
macro_rules! impl_cast_into {
($src:ty => [$($dst:ty),+]) => {
$(
impl CastInto<$dst> for $src {
#[inline]
fn cast_into(self) -> $dst {
<$dst>::cast_from(self)
}
}
)+
};
}
impl_cast_into!(u8 => [u8, i16, i32, f32, f64]);
impl_cast_into!(i16 => [u8, i16, i32, f32, f64]);
impl_cast_into!(i32 => [u8, i16, i32, f32, f64]);
impl_cast_into!(f64 => [u8, i16, i32, f32, f64]);
impl_cast_into!(f32 => [u8, i16, i32, f32, f64]);
macro_rules! impl_cast_into_i24 {
(i24_to_primitive $src:ty => $dst:ty) => {
impl CastInto<$dst> for $src {
#[inline]
fn cast_into(self) -> $dst {
self.to_i32() as $dst
}
}
};
(primitive_to_i24 $src:ty => $dst:ty) => {
impl CastInto<$dst> for $src {
#[inline]
fn cast_into(self) -> $dst {
<$dst as CastFrom<$src>>::cast_from(self)
}
}
};
(identity $t:ty) => {
impl CastInto<$t> for $t {
#[inline]
fn cast_into(self) -> $t {
self
}
}
};
}
impl_cast_into_i24!(i24_to_primitive I24 => u8);
impl_cast_into_i24!(i24_to_primitive I24 => i16);
impl_cast_into_i24!(identity I24);
impl_cast_into_i24!(i24_to_primitive I24 => i32);
impl_cast_into_i24!(i24_to_primitive I24 => f32);
impl_cast_into_i24!(i24_to_primitive I24 => f64);
impl_cast_into_i24!(primitive_to_i24 u8 => I24);
impl_cast_into_i24!(primitive_to_i24 i16 => I24);
impl_cast_into_i24!(primitive_to_i24 i32 => I24);
impl_cast_into_i24!(primitive_to_i24 f32 => I24);
impl_cast_into_i24!(primitive_to_i24 f64 => I24);
pub trait AudioTypeConversion: Sized {
type Sample: StandardSample;
fn to_format<O>(&self) -> AudioSamples<'static, O>
where
Self::Sample: ConvertTo<O> + ConvertFrom<O>,
O: StandardSample;
fn to_type<O>(self) -> AudioSamples<'static, O>
where
Self::Sample: ConvertTo<O> + ConvertFrom<O>,
O: StandardSample;
fn cast_as<O>(&self) -> AudioSamples<'static, O>
where
Self::Sample: CastInto<O> + ConvertTo<O>,
O: StandardSample;
fn cast_to<O>(self) -> AudioSamples<'static, O>
where
Self::Sample: CastInto<O> + ConvertTo<O>,
O: StandardSample;
#[inline]
fn cast_as_f64(&self) -> AudioSamples<'static, f64> {
self.cast_as::<f64>()
}
#[inline]
fn as_float(&self) -> AudioSamples<'static, f64> {
self.to_format::<f64>()
}
#[inline]
fn as_f64(&self) -> AudioSamples<'static, f64> {
self.to_format::<f64>()
}
#[inline]
fn as_f32(&self) -> AudioSamples<'static, f32> {
self.to_format::<f32>()
}
#[inline]
fn as_i32(&self) -> AudioSamples<'static, i32> {
self.to_format::<i32>()
}
#[inline]
fn as_i16(&self) -> AudioSamples<'static, i16> {
self.to_format::<i16>()
}
#[inline]
fn as_i24(&self) -> AudioSamples<'static, I24> {
self.to_format::<I24>()
}
#[inline]
fn as_u8(&self) -> AudioSamples<'static, u8> {
self.to_format::<u8>()
}
}
#[cfg(test)]
mod conversion_tests {
use super::*;
use i24::i24;
macro_rules! assert_approx_eq {
($left:expr, $right:expr, $tolerance:expr) => {
assert!(
($left - $right).abs() < $tolerance,
"assertion failed: `{} ≈ {}` (tolerance: {})",
$left,
$right,
$tolerance
);
};
}
#[test]
fn u8_tests() {
let zero: u8 = 0;
let mid: u8 = 128;
let max: u8 = 255;
let neg_one_f32: f32 = -1.0;
let zero_f32: f32 = 0.0;
let one_f32: f32 = 1.0;
let zero_to_neg_one: f32 = zero.convert_to();
let mid_to_zero: f32 = mid.convert_to();
let max_to_one: f32 = max.convert_to();
assert_approx_eq!(zero_to_neg_one as f64, -1.0, 1e-10);
assert_approx_eq!(mid_to_zero as f64, 0.0, 1e-10);
assert_approx_eq!(max_to_one as f64, 1.0, 1e-10);
let neg_one_to_u8: u8 = neg_one_f32.convert_to();
let zero_to_u8: u8 = zero_f32.convert_to();
let one_to_u8: u8 = one_f32.convert_to();
assert_eq!(neg_one_to_u8, 0);
assert_eq!(zero_to_u8, 128);
assert_eq!(one_to_u8, 255);
}
#[test]
fn i16_edge_cases() {
let min_i16: i16 = i16::MIN;
let min_i16_to_f32: f32 = min_i16.convert_to();
assert_approx_eq!(min_i16_to_f32 as f64, -1.0, 1e-5);
let min_i16_to_i32: i32 = min_i16.convert_to();
assert_eq!(min_i16_to_i32, i32::MIN);
let min_i16_to_i24: I24 = min_i16.convert_to();
let expected_i24_min = i24!(i32::MIN >> 8);
assert_eq!(min_i16_to_i24.to_i32(), expected_i24_min.to_i32());
let max_i16: i16 = i16::MAX;
let max_i16_to_f32: f32 = max_i16.convert_to();
assert_approx_eq!(max_i16_to_f32 as f64, 1.0, 1e-4);
let max_i16_to_i32: i32 = max_i16.convert_to();
assert_eq!(max_i16_to_i32, 0x7FFF0000);
let zero_i16: i16 = 0;
let zero_i16_to_f32: f32 = zero_i16.convert_to();
assert_approx_eq!(zero_i16_to_f32 as f64, 0.0, 1e-10);
let zero_i16_to_i32: i32 = zero_i16.convert_to();
assert_eq!(zero_i16_to_i32, 0);
let zero_i16_to_i24: I24 = zero_i16.convert_to();
assert_eq!(zero_i16_to_i24.to_i32(), 0);
let half_max_i16: i16 = i16::MAX / 2;
let half_max_i16_to_f32: f32 = half_max_i16.convert_to();
assert_approx_eq!(half_max_i16_to_f32 as f64, 0.5, 1e-4);
let half_max_i16_to_i32: i32 = half_max_i16.convert_to();
assert_eq!(half_max_i16_to_i32, 0x3FFF0000);
let half_min_i16: i16 = i16::MIN / 2;
let half_min_i16_to_f32: f32 = half_min_i16.convert_to();
assert_approx_eq!(half_min_i16_to_f32 as f64, -0.5, 1e-4);
}
#[test]
fn i32_edge_cases() {
let min_i32: i32 = i32::MIN;
let min_i32_to_f32: f32 = min_i32.convert_to();
assert_approx_eq!(min_i32_to_f32 as f64, -1.0, 1e-6);
let min_i32_to_f64: f64 = min_i32.convert_to();
assert_approx_eq!(min_i32_to_f64, -1.0, 1e-12);
let min_i32_to_i16: i16 = min_i32.convert_to();
assert_eq!(min_i32_to_i16, i16::MIN);
let max_i32: i32 = i32::MAX;
let max_i32_to_f32: f32 = max_i32.convert_to();
assert_approx_eq!(max_i32_to_f32 as f64, 1.0, 1e-6);
let max_i32_to_f64: f64 = max_i32.convert_to();
assert_approx_eq!(max_i32_to_f64, 1.0, 1e-12);
let max_i32_to_i16: i16 = max_i32.convert_to();
assert_eq!(max_i32_to_i16, i16::MAX);
let zero_i32: i32 = 0;
let zero_i32_to_f32: f32 = zero_i32.convert_to();
assert_approx_eq!(zero_i32_to_f32 as f64, 0.0, 1e-10);
let zero_i32_to_f64: f64 = zero_i32.convert_to();
assert_approx_eq!(zero_i32_to_f64, 0.0, 1e-12);
let zero_i32_to_i16: i16 = zero_i32.convert_to();
assert_eq!(zero_i32_to_i16, 0);
let quarter_max_i32: i32 = i32::MAX / 4;
let quarter_max_i32_to_f32: f32 = quarter_max_i32.convert_to();
assert_approx_eq!(quarter_max_i32_to_f32 as f64, 0.25, 1e-6);
let quarter_min_i32: i32 = i32::MIN / 4;
let quarter_min_i32_to_f32: f32 = quarter_min_i32.convert_to();
assert_approx_eq!(quarter_min_i32_to_f32 as f64, -0.25, 1e-6);
}
#[test]
fn f32_edge_cases() {
let min_f32: f32 = -1.0;
let min_f32_to_i16: i16 = min_f32.convert_to();
assert!(
min_f32_to_i16 == i16::MIN || min_f32_to_i16 == -32767,
"Expected either -32768 or -32767, got {}",
min_f32_to_i16
);
let min_f32_to_i32: i32 = min_f32.convert_to();
assert!(
min_f32_to_i32 == i32::MIN || min_f32_to_i32 == -2147483647,
"Expected either i32::MIN or -2147483647, got {}",
min_f32_to_i32
);
let min_f32_to_i24: I24 = min_f32.convert_to();
let expected_i24 = I24::MIN;
let diff = (min_f32_to_i24.to_i32() - expected_i24.to_i32()).abs();
assert!(diff <= 1, "I24 values differ by more than 1, {}", diff);
let max_f32: f32 = 1.0;
let max_f32_to_i16: i16 = max_f32.convert_to();
println!("DEBUG: f32 -> i16 conversion for 1.0");
println!(
"Input: {}, Output: {}, Expected: {}",
max_f32,
max_f32_to_i16,
i16::MAX
);
assert_eq!(max_f32_to_i16, i16::MAX);
let max_f32_to_i32: i32 = max_f32.convert_to();
println!("DEBUG: f32 -> i32 conversion for 1.0");
println!(
"Input: {}, Output: {}, Expected: {}",
max_f32,
max_f32_to_i32,
i32::MAX
);
assert_eq!(max_f32_to_i32, i32::MAX);
let zero_f32: f32 = 0.0;
let zero_f32_to_i16: i16 = zero_f32.convert_to();
println!("DEBUG: f32 -> i16 conversion for 0.0");
println!(
"Input: {}, Output: {}, Expected: 0",
zero_f32, zero_f32_to_i16
);
assert_eq!(zero_f32_to_i16, 0);
let zero_f32_to_i32: i32 = zero_f32.convert_to();
println!("DEBUG: f32 -> i32 conversion for 0.0");
println!(
"Input: {}, Output: {}, Expected: 0",
zero_f32, zero_f32_to_i32
);
assert_eq!(zero_f32_to_i32, 0);
let zero_f32_to_i24: I24 = zero_f32.convert_to();
println!("DEBUG: f32 -> I24 conversion for 0.0");
println!(
"Input: {}, Output: {} (i32 value), Expected: 0",
zero_f32,
zero_f32_to_i24.to_i32()
);
assert_eq!(zero_f32_to_i24.to_i32(), 0);
let large_f32: f32 = 2.0;
let large_f32_to_i16: i16 = large_f32.convert_to();
assert_eq!(large_f32_to_i16, i16::MAX);
let neg_large_f32: f32 = -2.0;
let neg_large_f32_to_i16: i16 = neg_large_f32.convert_to();
assert!(
neg_large_f32_to_i16 == i16::MIN || neg_large_f32_to_i16 == -32767,
"Expected either -32768 or -32767, got {}",
neg_large_f32_to_i16
);
let large_f32_to_i32: i32 = large_f32.convert_to();
assert_eq!(large_f32_to_i32, i32::MAX);
let neg_large_f32_to_i32: i32 = neg_large_f32.convert_to();
assert!(
neg_large_f32_to_i32 == i32::MIN || neg_large_f32_to_i32 == -2147483647,
"Expected either i32::MIN or -2147483647, got {}",
neg_large_f32_to_i32
);
let small_value: f32 = 1.0e-6;
let small_value_to_i16: i16 = small_value.convert_to();
assert_eq!(small_value_to_i16, 0);
let small_value_to_i32: i32 = small_value.convert_to();
assert_eq!(small_value_to_i32, 2147);
let half_f32: f32 = 0.5;
let half_f32_to_i16: i16 = half_f32.convert_to();
assert_eq!(half_f32_to_i16, 16383);
let neg_half_f32: f32 = -0.5;
let neg_half_f32_to_i16: i16 = neg_half_f32.convert_to();
assert_eq!(neg_half_f32_to_i16, -16383); }
#[test]
fn f64_edge_cases() {
let min_f64: f64 = -1.0;
let min_f64_to_i16: i16 = min_f64.convert_to();
println!("DEBUG: f64 -> i16 conversion for -1.0");
println!(
"Input: {}, Output: {}, Expected: {} or {}",
min_f64,
min_f64_to_i16,
i16::MIN,
-32767
);
assert!(
min_f64_to_i16 == i16::MIN || min_f64_to_i16 == -32767,
"Expected either -32768 or -32767, got {}",
min_f64_to_i16
);
let min_f64_to_i32: i32 = min_f64.convert_to();
println!("DEBUG: f64 -> i32 conversion for -1.0");
println!(
"Input: {}, Output: {}, Expected: {} or {}",
min_f64,
min_f64_to_i32,
i32::MIN,
-2147483647
);
assert!(
min_f64_to_i32 == i32::MIN || min_f64_to_i32 == -2147483647,
"Expected either i32::MIN or -2147483647, got {}",
min_f64_to_i32
);
let min_f64_to_f32: f32 = min_f64.convert_to();
println!("DEBUG: f64 -> f32 conversion for -1.0");
println!(
"Input: {}, Output: {}, Expected: -1.0",
min_f64, min_f64_to_f32
);
assert_approx_eq!(min_f64_to_f32 as f64, -1.0, 1e-6);
let max_f64: f64 = 1.0;
let max_f64_to_i16: i16 = max_f64.convert_to();
assert_eq!(max_f64_to_i16, i16::MAX);
let max_f64_to_i32: i32 = max_f64.convert_to();
assert_eq!(max_f64_to_i32, i32::MAX);
let max_f64_to_f32: f32 = max_f64.convert_to();
assert_approx_eq!(max_f64_to_f32 as f64, 1.0, 1e-6);
let zero_f64: f64 = 0.0;
let zero_f64_to_i16: i16 = zero_f64.convert_to();
assert_eq!(zero_f64_to_i16, 0);
let zero_f64_to_i32: i32 = zero_f64.convert_to();
assert_eq!(zero_f64_to_i32, 0);
let zero_f64_to_f32: f32 = zero_f64.convert_to();
assert_approx_eq!(zero_f64_to_f32 as f64, 0.0, 1e-10);
let large_f64: f64 = 2.0;
let large_f64_to_i16: i16 = large_f64.convert_to();
assert_eq!(large_f64_to_i16, i16::MAX);
let neg_large_f64: f64 = -2.0;
let neg_large_f64_to_i16: i16 = neg_large_f64.convert_to();
assert!(
neg_large_f64_to_i16 == i16::MIN || neg_large_f64_to_i16 == -32767,
"Expected either -32768 or -32767, got {}",
neg_large_f64_to_i16
);
let tiny_value: f64 = 1.0e-12;
let tiny_value_to_i16: i16 = tiny_value.convert_to();
assert_eq!(tiny_value_to_i16, 0);
let tiny_value_to_i32: i32 = tiny_value.convert_to();
assert_eq!(tiny_value_to_i32, 0);
let tiny_value_to_f32: f32 = tiny_value.convert_to();
assert_approx_eq!(tiny_value_to_f32 as f64, 0.0, 1e-10);
}
#[test]
fn i24_conversion_tests() {
let i24_value = i24!(4660 << 8); println!(
"DEBUG: Created I24 value from 4660 << 8 = {}",
i24_value.to_i32()
);
let i24_to_i16: i16 = i24_value.convert_to();
let expected_i16 = 0x1234_i16;
println!("DEBUG: I24 -> i16 conversion");
println!(
"I24 (as i32): {}, i16: {}, Expected: {}",
i24_value.to_i32(),
i24_to_i16,
expected_i16
);
assert_eq!(i24_to_i16, expected_i16);
let i24_to_f32: f32 = i24_value.convert_to();
let expected_f32 = (0x123456 as f32) / (I24::MAX.to_i32() as f32);
println!("DEBUG: I24 -> f32 conversion");
println!(
"I24 (as i32): {}, f32: {}, Expected: {}",
i24_value.to_i32(),
i24_to_f32,
expected_f32
);
println!("DEBUG: Difference: {}", (i24_to_f32 - expected_f32).abs());
assert_approx_eq!(i24_to_f32 as f64, expected_f32 as f64, 1e-4);
let i24_to_f64: f64 = i24_value.convert_to();
let expected_f64 = (0x123456 as f64) / (I24::MAX.to_i32() as f64);
println!("DEBUG: I24 -> f64 conversion");
println!(
"I24 (as i32): {}, f64: {}, Expected: {}",
i24_value.to_i32(),
i24_to_f64,
expected_f64
);
println!("DEBUG: Difference: {}", (i24_to_f64 - expected_f64).abs());
assert_approx_eq!(i24_to_f64, expected_f64, 1e-4);
}
#[test]
fn convert_from_tests() {
let f32_source: f32 = 0.5;
let i16_result: i16 = i16::convert_from(f32_source);
assert_eq!(i16_result, 16383);
let i32_source: i32 = 65536;
let i16_result: i16 = i16::convert_from(i32_source);
assert_eq!(i16_result, 1);
let i16_source: i16 = 16384;
let f32_result: f32 = f32::convert_from(i16_source);
assert_approx_eq!(f32_result as f64, 0.5, 1e-4);
let i32_source: i32 = i32::MAX / 2;
let f32_result: f32 = f32::convert_from(i32_source);
assert_approx_eq!(f32_result as f64, 0.5, 1e-4);
let i16_source: i16 = 4660; let i24_result: I24 = I24::convert_from(i16_source);
assert_eq!(i24_result.to_i32(), 4660 << 8);
let zero_f32: f32 = 0.0;
let zero_i16: i16 = i16::convert_from(zero_f32);
assert_eq!(zero_i16, 0);
let zero_i16_source: i16 = 0;
let zero_f32_result: f32 = f32::convert_from(zero_i16_source);
assert_approx_eq!(zero_f32_result as f64, 0.0, 1e-10);
}
#[test]
fn round_trip_conversions() {
for sample in [-32768, -16384, 0, 16384, 32767].iter() {
let original = *sample;
let intermediate: f32 = original.convert_to();
let round_tripped: i16 = intermediate.convert_to();
println!("DEBUG: i16->f32->i16 conversion");
println!(
"Original i16: {}, f32: {}, Round trip i16: {}",
original, intermediate, round_tripped
);
assert!(
(original - round_tripped).abs() <= 1,
"Expected {}, got {}",
original,
round_tripped
);
}
for &sample in &[i32::MIN, i32::MIN / 2, 0, i32::MAX / 2, i32::MAX] {
let original = sample;
let intermediate: f32 = original.convert_to();
let round_tripped: i32 = intermediate.convert_to();
if original == i32::MIN {
assert!(
round_tripped == i32::MIN || round_tripped == -2147483647,
"Expected either i32::MIN or -2147483647, got {}",
round_tripped
);
} else if original == i32::MAX || original == 0 {
assert_eq!(
original, round_tripped,
"Failed in i32->f32->i32 with extreme value {}",
original
);
} else {
let ratio = (round_tripped as f64) / (original as f64);
assert!(
ratio > 0.999 && ratio < 1.001,
"Round trip error too large: {} -> {}",
original,
round_tripped
);
}
}
for &sample in &[-1.0, -0.5, 0.0, 0.5, 1.0] {
let original: f32 = sample;
let intermediate: i16 = original.convert_to();
let round_tripped: f32 = intermediate.convert_to();
assert_approx_eq!(original as f64, round_tripped as f64, 1e-4);
}
for &sample in &[i16::MIN, -16384, 0, 16384, i16::MAX] {
let original = sample;
let intermediate: I24 = original.convert_to();
let round_tripped: i16 = intermediate.convert_to();
if original == i16::MIN {
assert!(
round_tripped == i16::MIN || round_tripped == -32767,
"Expected either -32768 or -32767, got {}",
round_tripped
);
} else {
assert_eq!(
original, round_tripped,
"Failed in i16->I24->i16 with value {}",
original
);
}
}
}
}