#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SampleFormat {
U8,
S16Le,
S16Be,
S24Le,
S32Le,
F32Le,
F64Le,
}
impl SampleFormat {
#[must_use]
pub fn bytes_per_sample(self) -> usize {
match self {
Self::U8 => 1,
Self::S16Le | Self::S16Be => 2,
Self::S24Le => 3,
Self::S32Le | Self::F32Le => 4,
Self::F64Le => 8,
}
}
#[must_use]
pub fn bit_depth(self) -> u32 {
match self {
Self::U8 => 8,
Self::S16Le | Self::S16Be => 16,
Self::S24Le => 24,
Self::S32Le => 32,
Self::F32Le => 32,
Self::F64Le => 64,
}
}
#[must_use]
pub fn is_float(self) -> bool {
matches!(self, Self::F32Le | Self::F64Le)
}
#[must_use]
pub fn is_signed_int(self) -> bool {
matches!(self, Self::S16Le | Self::S16Be | Self::S24Le | Self::S32Le)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::U8 => "U8",
Self::S16Le => "S16LE",
Self::S16Be => "S16BE",
Self::S24Le => "S24LE",
Self::S32Le => "S32LE",
Self::F32Le => "F32LE",
Self::F64Le => "F64LE",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AudioFormat {
pub sample_format: SampleFormat,
pub sample_rate: u32,
pub channels: u16,
}
impl AudioFormat {
#[must_use]
pub fn new(sample_format: SampleFormat, sample_rate: u32, channels: u16) -> Self {
Self {
sample_format,
sample_rate,
channels,
}
}
#[must_use]
pub fn byte_rate(&self) -> u64 {
#[allow(clippy::cast_precision_loss)]
{
(self.sample_rate as u64)
* (self.channels as u64)
* (self.sample_format.bytes_per_sample() as u64)
}
}
#[must_use]
pub fn frame_size_bytes(&self) -> usize {
self.sample_format.bytes_per_sample() * (self.channels as usize)
}
#[must_use]
pub fn is_compatible_with(&self, other: &Self) -> bool {
self == other
}
}
impl Default for AudioFormat {
fn default() -> Self {
Self::new(SampleFormat::F32Le, 48_000, 2)
}
}
pub struct FormatConverter {
pub src: AudioFormat,
pub dst: AudioFormat,
}
impl FormatConverter {
#[must_use]
pub fn new(src: AudioFormat, dst: AudioFormat) -> Self {
Self { src, dst }
}
#[must_use]
pub fn f32_to_s16(&self, input: &[f32]) -> Vec<i16> {
input
.iter()
.map(|&s| {
let clamped = s.clamp(-1.0, 1.0);
#[allow(clippy::cast_possible_truncation)]
let out = (clamped * 32767.0) as i16;
out
})
.collect()
}
#[must_use]
pub fn s16_to_f32(&self, input: &[i16]) -> Vec<f32> {
input
.iter()
.map(|&s| {
#[allow(clippy::cast_precision_loss)]
let out = (s as f32) / 32768.0;
out
})
.collect()
}
#[must_use]
pub fn f32_to_s32(&self, input: &[f32]) -> Vec<i32> {
input
.iter()
.map(|&s| {
let clamped = s.clamp(-1.0, 1.0);
#[allow(clippy::cast_possible_truncation)]
let out = (clamped * 2_147_483_647.0) as i32;
out
})
.collect()
}
#[must_use]
pub fn s32_to_f32(&self, input: &[i32]) -> Vec<f32> {
input
.iter()
.map(|&s| {
#[allow(clippy::cast_precision_loss)]
let out = (s as f32) / 2_147_483_648.0;
out
})
.collect()
}
pub fn normalize_f32(buffer: &mut [f32], target_level: f32) {
let peak = buffer.iter().map(|&s| s.abs()).fold(0.0_f32, f32::max);
if peak > 1e-9 {
let gain = target_level / peak;
for s in buffer.iter_mut() {
*s *= gain;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sample_format_bytes_u8() {
assert_eq!(SampleFormat::U8.bytes_per_sample(), 1);
}
#[test]
fn test_sample_format_bytes_s16() {
assert_eq!(SampleFormat::S16Le.bytes_per_sample(), 2);
}
#[test]
fn test_sample_format_bytes_s24() {
assert_eq!(SampleFormat::S24Le.bytes_per_sample(), 3);
}
#[test]
fn test_sample_format_bytes_f32() {
assert_eq!(SampleFormat::F32Le.bytes_per_sample(), 4);
}
#[test]
fn test_sample_format_bytes_f64() {
assert_eq!(SampleFormat::F64Le.bytes_per_sample(), 8);
}
#[test]
fn test_sample_format_is_float() {
assert!(SampleFormat::F32Le.is_float());
assert!(SampleFormat::F64Le.is_float());
assert!(!SampleFormat::S16Le.is_float());
}
#[test]
fn test_sample_format_is_signed_int() {
assert!(SampleFormat::S16Le.is_signed_int());
assert!(SampleFormat::S32Le.is_signed_int());
assert!(!SampleFormat::F32Le.is_signed_int());
assert!(!SampleFormat::U8.is_signed_int());
}
#[test]
fn test_sample_format_bit_depth() {
assert_eq!(SampleFormat::S16Le.bit_depth(), 16);
assert_eq!(SampleFormat::S24Le.bit_depth(), 24);
assert_eq!(SampleFormat::F32Le.bit_depth(), 32);
}
#[test]
fn test_sample_format_label() {
assert_eq!(SampleFormat::S16Le.label(), "S16LE");
assert_eq!(SampleFormat::F32Le.label(), "F32LE");
}
#[test]
fn test_audio_format_byte_rate() {
let fmt = AudioFormat::new(SampleFormat::S16Le, 48_000, 2);
assert_eq!(fmt.byte_rate(), 48_000 * 2 * 2);
}
#[test]
fn test_audio_format_frame_size() {
let fmt = AudioFormat::new(SampleFormat::F32Le, 48_000, 2);
assert_eq!(fmt.frame_size_bytes(), 8);
}
#[test]
fn test_audio_format_compatibility() {
let a = AudioFormat::new(SampleFormat::F32Le, 48_000, 2);
let b = AudioFormat::new(SampleFormat::F32Le, 48_000, 2);
let c = AudioFormat::new(SampleFormat::S16Le, 48_000, 2);
assert!(a.is_compatible_with(&b));
assert!(!a.is_compatible_with(&c));
}
#[test]
fn test_f32_to_s16_zero() {
let conv = FormatConverter::new(AudioFormat::default(), AudioFormat::default());
let out = conv.f32_to_s16(&[0.0]);
assert_eq!(out[0], 0);
}
#[test]
fn test_f32_to_s16_positive_full_scale() {
let conv = FormatConverter::new(AudioFormat::default(), AudioFormat::default());
let out = conv.f32_to_s16(&[1.0]);
assert_eq!(out[0], 32767);
}
#[test]
fn test_f32_to_s16_negative_full_scale() {
let conv = FormatConverter::new(AudioFormat::default(), AudioFormat::default());
let out = conv.f32_to_s16(&[-1.0]);
assert_eq!(out[0], -32767);
}
#[test]
fn test_s16_to_f32_zero() {
let conv = FormatConverter::new(AudioFormat::default(), AudioFormat::default());
let out = conv.s16_to_f32(&[0]);
assert!(out[0].abs() < 1e-6);
}
#[test]
fn test_s16_to_f32_range() {
let conv = FormatConverter::new(AudioFormat::default(), AudioFormat::default());
let out = conv.s16_to_f32(&[32767, -32768]);
assert!(out[0] > 0.99 && out[0] <= 1.0);
assert!(out[1] >= -1.0 && out[1] < -0.99);
}
#[test]
fn test_normalize_f32() {
let mut buf = vec![0.1_f32, 0.2, 0.05, -0.15];
FormatConverter::normalize_f32(&mut buf, 1.0);
let peak = buf.iter().map(|&s| s.abs()).fold(0.0_f32, f32::max);
assert!((peak - 1.0).abs() < 1e-5);
}
#[test]
fn test_normalize_f32_silence_noop() {
let mut buf = vec![0.0_f32; 8];
FormatConverter::normalize_f32(&mut buf, 1.0);
assert!(buf.iter().all(|&v| v == 0.0));
}
}