pub fn f32_to_i16_le_bytes(samples: &[f32]) -> Vec<u8> {
let mut out = Vec::with_capacity(samples.len() * 2);
for &s in samples {
let clamped = s.clamp(-1.0, 1.0);
let scaled = (clamped * 32768.0) as i32;
let val = scaled.clamp(-32768, 32767) as i16;
out.extend_from_slice(&val.to_le_bytes());
}
out
}
pub fn f32_to_f32_le_bytes(samples: &[f32]) -> Vec<u8> {
let mut out = Vec::with_capacity(samples.len() * 4);
for &s in samples {
out.extend_from_slice(&s.to_le_bytes());
}
out
}
pub fn i16_to_f32(samples: &[i16]) -> Vec<f32> {
samples.iter().map(|&s| s as f32 / 32768.0).collect()
}
pub fn i16_le_bytes_to_f32(bytes: &[u8]) -> Vec<f32> {
debug_assert!(
bytes.len().is_multiple_of(2),
"i16_le_bytes_to_f32 expects an even-length buffer; a trailing odd byte is dropped"
);
bytes
.chunks_exact(2)
.map(|c| {
let val = i16::from_le_bytes([c[0], c[1]]);
val as f32 / 32768.0
})
.collect()
}
pub fn f32_le_bytes_to_f32(bytes: &[u8]) -> Vec<f32> {
bytes
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
}
pub fn downmix_to_mono(samples: &[f32], channels: u16) -> Vec<f32> {
if channels <= 1 {
return samples.to_vec();
}
let channels = channels as usize;
samples
.chunks_exact(channels)
.map(|frame| frame.iter().sum::<f32>() / channels as f32)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_silence() {
let bytes = f32_to_i16_le_bytes(&[0.0]);
assert_eq!(bytes, [0x00, 0x00]);
}
#[test]
fn test_max_positive() {
let bytes = f32_to_i16_le_bytes(&[1.0]);
let val = i16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(val, 32767);
}
#[test]
fn test_max_negative() {
let bytes = f32_to_i16_le_bytes(&[-1.0]);
let val = i16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(val, -32768);
}
#[test]
fn test_half_amplitude() {
let bytes = f32_to_i16_le_bytes(&[0.5]);
let val = i16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(val, 16384);
}
#[test]
fn test_clamp_above_one() {
let bytes = f32_to_i16_le_bytes(&[1.5]);
let val = i16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(val, 32767);
}
#[test]
fn test_clamp_below_negative_one() {
let bytes = f32_to_i16_le_bytes(&[-1.5]);
let val = i16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(val, -32768);
}
#[test]
fn test_roundtrip_i16() {
let original: Vec<f32> = vec![0.0, 0.25, -0.25, 0.5, -0.5, 0.99, -0.99];
let bytes = f32_to_i16_le_bytes(&original);
let i16_samples: Vec<i16> = bytes
.chunks_exact(2)
.map(|c| i16::from_le_bytes([c[0], c[1]]))
.collect();
let reconstructed = i16_to_f32(&i16_samples);
for (orig, recon) in original.iter().zip(reconstructed.iter()) {
let diff = (orig - recon).abs();
assert!(
diff < 1.0 / 32768.0 + f32::EPSILON,
"Roundtrip error too large: {orig} -> {recon} (diff: {diff})"
);
}
}
#[test]
fn test_f32_le_roundtrip() {
let original: Vec<f32> = vec![0.0, 0.25, -0.25, 1.0, -1.0, 0.001, -0.999];
let bytes = f32_to_f32_le_bytes(&original);
let reconstructed: Vec<f32> = bytes
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
assert_eq!(original, reconstructed);
}
#[test]
fn test_multiple_samples() {
let samples = vec![0.0, 0.5, -0.5];
let bytes = f32_to_i16_le_bytes(&samples);
assert_eq!(bytes.len(), 6);
let f32_bytes = f32_to_f32_le_bytes(&samples);
assert_eq!(f32_bytes.len(), 12); }
#[test]
fn test_i16_le_bytes_to_f32_silence() {
let bytes = vec![0x00, 0x00];
let samples = i16_le_bytes_to_f32(&bytes);
assert_eq!(samples, vec![0.0]);
}
#[test]
fn test_i16_le_bytes_to_f32_max() {
let bytes = vec![0xFF, 0x7F];
let samples = i16_le_bytes_to_f32(&bytes);
assert!((samples[0] - 32767.0 / 32768.0).abs() < f32::EPSILON);
}
#[test]
fn test_i16_le_bytes_to_f32_roundtrip() {
let original: Vec<f32> = vec![0.0, 0.25, -0.25, 0.5, -0.5, 0.99, -0.99];
let bytes = f32_to_i16_le_bytes(&original);
let reconstructed = i16_le_bytes_to_f32(&bytes);
for (orig, recon) in original.iter().zip(reconstructed.iter()) {
let diff = (orig - recon).abs();
assert!(
diff < 1.0 / 32768.0 + f32::EPSILON,
"i16 LE roundtrip error: {orig} -> {recon} (diff: {diff})"
);
}
}
#[test]
fn test_f32_le_bytes_to_f32_roundtrip() {
let original: Vec<f32> = vec![0.0, 0.25, -0.25, 1.0, -1.0, 0.001, -0.999];
let bytes = f32_to_f32_le_bytes(&original);
let reconstructed = f32_le_bytes_to_f32(&bytes);
assert_eq!(original, reconstructed);
}
#[test]
fn test_i16_le_bytes_to_f32_empty() {
let bytes: Vec<u8> = vec![];
let samples = i16_le_bytes_to_f32(&bytes);
assert!(samples.is_empty());
}
#[test]
fn test_f32_le_bytes_to_f32_empty() {
let bytes: Vec<u8> = vec![];
let samples = f32_le_bytes_to_f32(&bytes);
assert!(samples.is_empty());
}
#[test]
fn test_downmix_stereo_averages_and_halves() {
let stereo = vec![0.5, 0.3, 0.4, 0.6];
let mono = downmix_to_mono(&stereo, 2);
assert_eq!(mono.len(), stereo.len() / 2);
assert!((mono[0] - 0.4).abs() < 1e-6); assert!((mono[1] - 0.5).abs() < 1e-6); }
#[test]
fn test_downmix_mono_passthrough() {
let samples = vec![0.1, -0.2, 0.3];
assert_eq!(downmix_to_mono(&samples, 1), samples);
assert_eq!(downmix_to_mono(&samples, 0), samples);
}
#[test]
fn test_downmix_six_channel() {
let frame = vec![0.0, 0.6, 0.3, -0.3, 1.2, -1.8];
let mono = downmix_to_mono(&frame, 6);
assert_eq!(mono.len(), 1);
assert!((mono[0] - 0.0).abs() < 1e-6); }
#[test]
fn test_downmix_preserves_sign_and_magnitude() {
assert_eq!(downmix_to_mono(&[1.0, -1.0], 2), vec![0.0]);
assert_eq!(downmix_to_mono(&[-0.5, -0.5], 2), vec![-0.5]);
}
#[test]
fn test_downmix_drops_trailing_partial_frame() {
let stereo = vec![0.2, 0.4, 0.6, 0.8, 0.9];
let mono = downmix_to_mono(&stereo, 2);
assert_eq!(mono.len(), 2);
assert!((mono[0] - 0.3).abs() < 1e-6);
assert!((mono[1] - 0.7).abs() < 1e-6);
}
#[test]
fn test_downmix_empty() {
let empty: Vec<f32> = vec![];
assert!(downmix_to_mono(&empty, 2).is_empty());
}
}