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
}
#[cfg(test)]
pub(crate) 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()
}
pub fn rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum_sq: f64 = samples.iter().map(|&s| (s as f64) * (s as f64)).sum();
(sum_sq / samples.len() as f64).sqrt() as f32
}
#[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());
}
#[test]
fn test_rms_silence_is_zero() {
assert_eq!(rms(&[0.0; 512]), 0.0);
}
#[test]
fn test_rms_empty_is_zero() {
assert_eq!(rms(&[]), 0.0);
}
#[test]
fn test_rms_constant_equals_amplitude() {
let rms_val = rms(&[0.25; 1000]);
assert!((rms_val - 0.25).abs() < 1e-6, "rms {rms_val}");
}
#[test]
fn test_rms_sine_is_amplitude_over_root_two() {
let amplitude = 0.5_f32;
let samples: Vec<f32> = (0..4000)
.map(|k| amplitude * (k as f32 * 0.05).sin())
.collect();
let rms_val = rms(&samples);
let expected = amplitude / 2.0_f32.sqrt();
assert!(
(rms_val - expected).abs() < 1e-2,
"rms {rms_val} vs {expected}"
);
}
#[test]
fn test_rms_matches_i16_normalized_convention() {
let samples: Vec<f32> = (0..2000).map(|k| 0.1 * (k as f32 * 0.05).sin()).collect();
let f32_rms = rms(&samples);
let i16_norm: Vec<f64> = samples
.iter()
.map(|&s| {
let q = (s.clamp(-1.0, 1.0) * 32768.0) as i32;
let v = q.clamp(-32768, 32767) as i16;
v as f64 / 32768.0
})
.collect();
let legacy_rms =
(i16_norm.iter().map(|x| x * x).sum::<f64>() / i16_norm.len() as f64).sqrt() as f32;
assert!(
(f32_rms - legacy_rms).abs() < 1e-3,
"f32 rms {f32_rms} vs legacy i16-normalized rms {legacy_rms}"
);
}
}