#![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
#[must_use]
pub fn f32_to_s16(sample: f32) -> i16 {
let scaled = sample * f32::from(i16::MAX);
if scaled.is_nan() {
return 0;
}
if scaled >= f32::from(i16::MAX) {
i16::MAX
} else if scaled <= f32::from(i16::MIN) {
i16::MIN
} else {
scaled as i16
}
}
#[must_use]
pub fn s16_to_f32(sample: i16) -> f32 {
f32::from(sample) / f32::from(i16::MAX)
}
#[must_use]
pub fn s16_to_le_bytes(sample: i16) -> [u8; 2] {
sample.to_le_bytes()
}
#[must_use]
pub fn f32_to_s32(sample: f32) -> i32 {
let scaled = sample * (i32::MAX as f32);
if scaled.is_nan() {
return 0;
}
if scaled >= i32::MAX as f32 {
i32::MAX
} else if scaled <= i32::MIN as f32 {
i32::MIN
} else {
scaled as i32
}
}
#[must_use]
pub fn s32_to_f32(sample: i32) -> f32 {
(sample as f32) / (i32::MAX as f32)
}
#[must_use]
pub fn s32_to_le_bytes(sample: i32) -> [u8; 4] {
sample.to_le_bytes()
}
pub fn f32_interleaved_to_s16(input: &[f32], out: &mut [i16]) {
let n = input.len().min(out.len());
for (i, &s) in input.iter().take(n).enumerate() {
out[i] = f32_to_s16(s);
}
}
pub fn s16_interleaved_to_f32(input: &[i16], out: &mut [f32]) {
let n = input.len().min(out.len());
for (i, &s) in input.iter().take(n).enumerate() {
out[i] = s16_to_f32(s);
}
}
pub fn f32_interleaved_to_s32(input: &[f32], out: &mut [i32]) {
let n = input.len().min(out.len());
for (i, &s) in input.iter().take(n).enumerate() {
out[i] = f32_to_s32(s);
}
}
pub fn s32_interleaved_to_f32(input: &[i32], out: &mut [f32]) {
let n = input.len().min(out.len());
for (i, &s) in input.iter().take(n).enumerate() {
out[i] = s32_to_f32(s);
}
}
pub fn s16_interleaved_to_le_bytes(input: &[i16], out: &mut [u8]) {
let needed = input.len() * 2;
debug_assert!(
out.len() >= needed,
"out too small: need {needed}, have {}",
out.len()
);
for (i, &s) in input.iter().enumerate() {
let b = s16_to_le_bytes(s);
out[i * 2] = b[0];
out[i * 2 + 1] = b[1];
}
}
pub fn s32_interleaved_to_le_bytes(input: &[i32], out: &mut [u8]) {
let needed = input.len() * 4;
debug_assert!(
out.len() >= needed,
"out too small: need {needed}, have {}",
out.len()
);
for (i, &s) in input.iter().enumerate() {
let b = s32_to_le_bytes(s);
out[i * 4] = b[0];
out[i * 4 + 1] = b[1];
out[i * 4 + 2] = b[2];
out[i * 4 + 3] = b[3];
}
}
#[allow(clippy::float_cmp)]
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn f32_to_s16_clips_above_one() {
assert_eq!(f32_to_s16(1.0), i16::MAX);
assert_eq!(f32_to_s16(2.0), i16::MAX);
}
#[test]
fn f32_to_s16_minus_one_is_negative_max() {
assert_eq!(f32_to_s16(-1.0), -i16::MAX);
}
#[test]
fn f32_to_s16_clips_below_minus_one_to_min() {
assert_eq!(f32_to_s16(-2.0), i16::MIN);
assert_eq!(f32_to_s16(-100.0), i16::MIN);
}
#[test]
fn f32_to_s16_nan_is_zero() {
assert_eq!(f32_to_s16(f32::NAN), 0);
}
#[test]
fn s16_to_f32_reconstructs_known_values() {
assert_eq!(s16_to_f32(0), 0.0);
assert!((s16_to_f32(i16::MAX) - 1.0).abs() < 1e-6);
}
#[test]
fn s16_round_trip_is_lossless() {
for s in [0_i16, 1, -1, 100, -100, i16::MAX, i16::MIN, 16_384, -16_384] {
let back = f32_to_s16(s16_to_f32(s));
assert_eq!(back, s, "s16 {s} did not round-trip");
}
}
#[test]
fn f32_to_s32_clips() {
assert_eq!(f32_to_s32(1.0), i32::MAX);
assert_eq!(f32_to_s32(-1.0), i32::MIN);
assert_eq!(f32_to_s32(5.0), i32::MAX);
}
#[test]
fn f32_s32_round_trip_is_high_fidelity() {
for s in [0.0_f32, 0.5, -0.5, 1.0, -1.0, 0.123_456, -0.987_654] {
let back = s32_to_f32(f32_to_s32(s));
assert!((back - s).abs() < 1e-6, "f32 {s} -> s32 -> {back} drifted");
}
}
#[test]
fn le_byte_encoders_match_to_le_bytes() {
assert_eq!(s16_to_le_bytes(0x0102), [0x02, 0x01]);
assert_eq!(s32_to_le_bytes(0x0102_0304), [0x04, 0x03, 0x02, 0x01]);
}
#[test]
fn interleaved_s16_slice_round_trip() {
let f = vec![0.0_f32, 0.5, -0.5, 1.0, -1.0];
let mut i = vec![0_i16; f.len()];
f32_interleaved_to_s16(&f, &mut i);
let mut back = vec![0.0_f32; f.len()];
s16_interleaved_to_f32(&i, &mut back);
assert_eq!(back[0], 0.0);
assert!((back[3] - 1.0).abs() < 1e-3);
assert!((back[4] + 1.0).abs() < 1e-3);
}
#[test]
fn s16_interleaved_to_le_bytes_writes_correctly() {
let i = [0x0102_i16, 0x0304];
let mut bytes = [0_u8; 4];
s16_interleaved_to_le_bytes(&i, &mut bytes);
assert_eq!(bytes, [0x02, 0x01, 0x04, 0x03]);
}
#[test]
fn s32_interleaved_to_le_bytes_writes_correctly() {
let i = [0x0102_0304_i32];
let mut bytes = [0_u8; 4];
s32_interleaved_to_le_bytes(&i, &mut bytes);
assert_eq!(bytes, [0x04, 0x03, 0x02, 0x01]);
}
proptest! {
#[test]
fn prop_s16_round_trip_exact(s in any::<i16>()) {
prop_assert_eq!(f32_to_s16(s16_to_f32(s)), s);
}
#[test]
fn prop_f32_to_s16_never_panics_in_range(s in -1.0f32..=1.0) {
let v = f32_to_s16(s);
prop_assert!((i16::MIN..=i16::MAX).contains(&v));
}
#[test]
fn prop_f32_s16_quantisation_bound(s in -1.0f32..=1.0) {
let back = s16_to_f32(f32_to_s16(s));
let step = 1.0 / f32::from(i16::MAX);
prop_assert!((back - s).abs() <= step + 1e-6);
}
}
}