use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PcmFormat {
I8,
I16,
I24,
I32,
F32,
F64,
}
impl PcmFormat {
#[must_use]
#[inline]
pub const fn bytes_per_sample(self) -> u16 {
match self {
Self::I8 => 1,
Self::I16 => 2,
Self::I24 => 3,
Self::I32 | Self::F32 => 4,
Self::F64 => 8,
}
}
#[must_use]
#[inline]
pub const fn bit_depth(self) -> u16 {
match self {
Self::I8 => 8,
Self::I16 => 16,
Self::I24 => 24,
Self::I32 | Self::F32 => 32,
Self::F64 => 64,
}
}
}
#[must_use]
#[inline]
pub fn i16_to_f32(samples: &[i16]) -> Vec<f32> {
samples.iter().map(|&s| s as f32 / 32768.0).collect()
}
#[must_use]
#[inline]
pub fn f32_to_i16(samples: &[f32]) -> Vec<i16> {
samples
.iter()
.map(|&s| {
let clamped = s.clamp(-1.0, 1.0);
(clamped * 32767.0) as i16
})
.collect()
}
#[must_use]
#[inline]
pub fn i32_to_f32(samples: &[i32]) -> Vec<f32> {
samples
.iter()
.map(|&s| s as f32 / 2_147_483_648.0)
.collect()
}
#[must_use]
#[inline]
pub fn f32_to_i32(samples: &[f32]) -> Vec<i32> {
samples
.iter()
.map(|&s| {
let clamped = s.clamp(-1.0, 1.0);
(clamped as f64 * 2_147_483_647.0) as i32
})
.collect()
}
#[must_use]
#[inline]
pub fn i24_to_f32(samples: &[i32]) -> Vec<f32> {
samples
.iter()
.map(|&s| {
let extended = (s << 8) >> 8;
extended as f32 / 8_388_608.0
})
.collect()
}
#[must_use]
#[inline]
pub fn f32_to_i24(samples: &[f32]) -> Vec<i32> {
samples
.iter()
.map(|&s| {
let clamped = s.clamp(-1.0, 1.0);
(clamped * 8_388_607.0) as i32
})
.collect()
}
#[must_use]
#[inline]
pub fn i24_packed_to_f32(bytes: &[u8]) -> Vec<f32> {
bytes
.chunks_exact(3)
.map(|chunk| {
let raw =
i32::from(chunk[0]) | (i32::from(chunk[1]) << 8) | (i32::from(chunk[2]) << 16);
let extended = (raw << 8) >> 8;
extended as f32 / 8_388_608.0
})
.collect()
}
#[must_use]
#[inline]
pub fn f32_to_i24_packed(samples: &[f32]) -> Vec<u8> {
let mut out = Vec::with_capacity(samples.len() * 3);
for &s in samples {
let clamped = s.clamp(-1.0, 1.0);
let val = (clamped * 8_388_607.0) as i32;
out.push(val as u8);
out.push((val >> 8) as u8);
out.push((val >> 16) as u8);
}
out
}
#[must_use]
#[inline]
pub fn f64_to_f32(samples: &[f64]) -> Vec<f32> {
samples.iter().map(|&s| s as f32).collect()
}
#[must_use]
#[inline]
pub fn f32_to_f64(samples: &[f32]) -> Vec<f64> {
samples.iter().map(|&s| f64::from(s)).collect()
}
#[must_use]
#[inline]
pub fn u8_to_f32(samples: &[u8]) -> Vec<f32> {
samples
.iter()
.map(|&s| (f32::from(s) - 128.0) / 128.0)
.collect()
}
#[must_use]
#[inline]
pub fn f32_to_u8(samples: &[f32]) -> Vec<u8> {
samples
.iter()
.map(|&s| {
let clamped = s.clamp(-1.0, 1.0);
((clamped * 128.0) + 128.0).clamp(0.0, 255.0) as u8
})
.collect()
}
#[must_use]
pub fn interleave(channels: &[&[f32]]) -> Vec<f32> {
if channels.is_empty() {
return Vec::new();
}
let frames = channels[0].len();
let ch_count = channels.len();
let mut out = Vec::with_capacity(frames * ch_count);
for frame in 0..frames {
for ch in channels {
if frame < ch.len() {
out.push(ch[frame]);
}
}
}
out
}
#[must_use]
pub fn deinterleave(samples: &[f32], channels: u16) -> Vec<Vec<f32>> {
let ch = channels as usize;
if ch == 0 {
return Vec::new();
}
let frames = samples.len() / ch;
let mut out: Vec<Vec<f32>> = (0..ch).map(|_| Vec::with_capacity(frames)).collect();
for frame in 0..frames {
for (c, plane) in out.iter_mut().enumerate() {
plane.push(samples[frame * ch + c]);
}
}
out
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn i16_f32_roundtrip() {
let original: Vec<i16> = vec![0, 16384, -16384, 32767, -32768];
let f32s = i16_to_f32(&original);
let back = f32_to_i16(&f32s);
for (a, b) in original.iter().zip(back.iter()) {
assert!((*a as i32 - *b as i32).abs() <= 1, "{a} != {b}");
}
}
#[test]
fn i32_f32_roundtrip() {
let original: Vec<i32> = vec![0, 1_073_741_824, -1_073_741_824];
let f32s = i32_to_f32(&original);
let back = f32_to_i32(&f32s);
for (a, b) in original.iter().zip(back.iter()) {
let tolerance = 256;
assert!((*a as i64 - *b as i64).abs() <= tolerance, "{a} != {b}");
}
}
#[test]
fn f32_to_i16_clamps() {
let samples = vec![2.0, -2.0, 0.5];
let result = f32_to_i16(&samples);
assert_eq!(result[0], 32767);
assert_eq!(result[1], -32767);
}
#[test]
fn i24_f32_roundtrip() {
let original: Vec<i32> = vec![0, 4_194_304, -4_194_304, 8_388_607, -8_388_608];
let f32s = i24_to_f32(&original);
let back = f32_to_i24(&f32s);
for (a, b) in original.iter().zip(back.iter()) {
assert!((*a - *b).abs() <= 1, "{a} != {b}");
}
}
#[test]
fn i24_packed_roundtrip() {
let samples = vec![0.0f32, 0.5, -0.5, 1.0, -1.0];
let packed = f32_to_i24_packed(&samples);
assert_eq!(packed.len(), samples.len() * 3);
let back = i24_packed_to_f32(&packed);
for (a, b) in samples.iter().zip(back.iter()) {
assert!((a - b).abs() < 0.001, "{a} != {b}");
}
}
#[test]
fn interleave_deinterleave_roundtrip() {
let left = vec![1.0f32, 3.0, 5.0];
let right = vec![2.0f32, 4.0, 6.0];
let interleaved = interleave(&[&left, &right]);
assert_eq!(interleaved, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let planes = deinterleave(&interleaved, 2);
assert_eq!(planes.len(), 2);
assert_eq!(planes[0], left);
assert_eq!(planes[1], right);
}
#[test]
fn deinterleave_empty() {
let planes = deinterleave(&[], 0);
assert!(planes.is_empty());
}
#[test]
fn pcm_format_bytes() {
assert_eq!(PcmFormat::I8.bytes_per_sample(), 1);
assert_eq!(PcmFormat::I16.bytes_per_sample(), 2);
assert_eq!(PcmFormat::I24.bytes_per_sample(), 3);
assert_eq!(PcmFormat::I32.bytes_per_sample(), 4);
assert_eq!(PcmFormat::F32.bytes_per_sample(), 4);
assert_eq!(PcmFormat::F64.bytes_per_sample(), 8);
}
#[test]
fn pcm_format_bit_depth() {
assert_eq!(PcmFormat::I16.bit_depth(), 16);
assert_eq!(PcmFormat::I24.bit_depth(), 24);
assert_eq!(PcmFormat::F32.bit_depth(), 32);
}
#[test]
fn f64_f32_roundtrip() {
let original = vec![0.0f64, 0.5, -0.5, 1.0, -1.0];
let f32s = f64_to_f32(&original);
let back = f32_to_f64(&f32s);
for (a, b) in original.iter().zip(back.iter()) {
assert!((*a - *b).abs() < 1e-6, "{a} != {b}");
}
}
#[test]
fn u8_f32_roundtrip() {
let original: Vec<u8> = vec![0, 64, 128, 192, 255];
let f32s = u8_to_f32(&original);
let back = f32_to_u8(&f32s);
for (a, b) in original.iter().zip(back.iter()) {
assert!((*a as i16 - *b as i16).abs() <= 1, "{a} != {b}");
}
}
#[test]
fn u8_center_is_zero() {
let f32s = u8_to_f32(&[128]);
assert!((f32s[0]).abs() < f32::EPSILON);
}
}