use crate::error::{Error, Result};
use crate::time::Micros;
#[allow(unsafe_code)]
#[must_use]
pub fn as_i16(bytes: &[u8]) -> Option<&[i16]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to::<i16>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_i32(bytes: &[u8]) -> Option<&[i32]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to::<i32>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_i32_mut(bytes: &mut [u8]) -> Option<&mut [i32]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to_mut::<i32>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_i16_mut(bytes: &mut [u8]) -> Option<&mut [i16]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to_mut::<i16>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn round_sat_i16(v: f32) -> i16 {
let t = v + libm_copysignf(0.5 - 0.25 * f32::EPSILON, v);
if t >= 32767.0 {
return i16::MAX;
}
if t <= -32768.0 {
return i16::MIN;
}
if t.is_nan() {
return 0;
}
unsafe { t.to_int_unchecked::<i16>() }
}
#[allow(unsafe_code)]
#[must_use]
pub fn rint_sat_i16(v: f32) -> i16 {
const ROUND_F32: f32 = 12_582_912.0;
if v > -32768.0 && v < 32767.0 {
let r = (v + ROUND_F32) - ROUND_F32;
return unsafe { r.to_int_unchecked::<i16>() };
}
if v.is_nan() {
0
} else if v >= 32767.0 {
i16::MAX
} else {
i16::MIN
}
}
#[inline]
fn libm_copysignf(x: f32, y: f32) -> f32 {
f32::from_bits((x.to_bits() & 0x7fff_ffff) | (y.to_bits() & 0x8000_0000))
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_u16(bytes: &[u8]) -> Option<&[u16]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to::<u16>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_u16_mut(bytes: &mut [u8]) -> Option<&mut [u16]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to_mut::<u16>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_f32(bytes: &[u8]) -> Option<&[f32]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to::<f32>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[allow(unsafe_code)]
#[must_use]
pub fn as_f32_mut(bytes: &mut [u8]) -> Option<&mut [f32]> {
if cfg!(target_endian = "big") {
return None;
}
let (prefix, mid, suffix) = unsafe { bytes.align_to_mut::<f32>() };
if prefix.is_empty() && suffix.is_empty() {
Some(mid)
} else {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SampleFormat {
I16,
I24In32,
I32,
F32,
}
impl SampleFormat {
#[must_use]
pub const fn bytes(self) -> usize {
match self {
SampleFormat::I16 => 2,
SampleFormat::I24In32 | SampleFormat::I32 | SampleFormat::F32 => 4,
}
}
#[must_use]
pub const fn tag(self) -> &'static str {
match self {
SampleFormat::I16 => "s16le",
SampleFormat::I24In32 => "s24le32",
SampleFormat::I32 => "s32le",
SampleFormat::F32 => "f32le",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PcmFormat {
pub sample_rate_hz: u32,
pub channels: u8,
pub sample: SampleFormat,
}
impl PcmFormat {
pub const fn new(sample_rate_hz: u32, channels: u8, sample: SampleFormat) -> Result<Self> {
if sample_rate_hz == 0 || channels == 0 {
return Err(Error::InvalidGeometry);
}
Ok(PcmFormat {
sample_rate_hz,
channels,
sample,
})
}
pub const PCM16_16K_MONO: PcmFormat = PcmFormat {
sample_rate_hz: 16_000,
channels: 1,
sample: SampleFormat::I16,
};
pub const PCM16_48K_STEREO: PcmFormat = PcmFormat {
sample_rate_hz: 48_000,
channels: 2,
sample: SampleFormat::I16,
};
#[must_use]
pub const fn frame_bytes(&self) -> usize {
self.sample.bytes() * self.channels as usize
}
#[must_use]
pub const fn bytes_for_micros(&self, micros: u64) -> usize {
let frames = (self.sample_rate_hz as u64) * micros / 1_000_000;
(frames as usize) * self.frame_bytes()
}
#[must_use]
pub const fn micros_for_frames(&self, frames: usize) -> u64 {
(frames as u64) * 1_000_000 / (self.sample_rate_hz as u64)
}
}
#[derive(Debug, Clone, Copy)]
pub struct PcmBlock<'a> {
pub format: PcmFormat,
pub timestamp: Micros,
pub data: &'a [u8],
}
impl<'a> PcmBlock<'a> {
pub fn new(format: PcmFormat, timestamp: Micros, data: &'a [u8]) -> Result<Self> {
let fb = format.frame_bytes();
if data.is_empty() || data.len() % fb != 0 {
return Err(Error::InvalidGeometry);
}
Ok(PcmBlock {
format,
timestamp,
data,
})
}
#[must_use]
pub fn frames(&self) -> usize {
self.data.len() / self.format.frame_bytes()
}
#[must_use]
pub fn duration_micros(&self) -> u64 {
self.format.micros_for_frames(self.frames())
}
#[must_use]
pub fn end(&self) -> Micros {
self.timestamp.add_micros(self.duration_micros())
}
#[must_use]
pub fn samples_i16(&self) -> Option<impl Iterator<Item = i16> + 'a> {
if self.format.sample != SampleFormat::I16 {
return None;
}
Some(
self.data
.chunks_exact(2)
.map(|b| i16::from_le_bytes([b[0], b[1]])),
)
}
}
#[cfg(test)]
mod i16_view {
use super::{as_i16, as_i16_mut};
#[test]
#[allow(clippy::useless_vec)]
fn agrees_with_from_le_bytes_and_refuses_the_rest() {
let mut raw = vec![0u8; 1 + 64];
for (i, b) in raw.iter_mut().enumerate() {
*b = (i.wrapping_mul(97) ^ (i >> 3)) as u8;
}
let buf = &raw[1..];
if let Some(v) = as_i16(buf) {
assert_eq!(v.len(), buf.len() / 2);
for (k, &s) in v.iter().enumerate() {
assert_eq!(
s,
i16::from_le_bytes([buf[k * 2], buf[k * 2 + 1]]),
"sample {k} disagrees with the byte path"
);
}
}
assert!(as_i16(&raw[1..64]).is_none(), "odd length must refuse");
assert!(as_i16(&raw[..1]).is_none(), "one byte must refuse");
assert!(as_i16(&[]).is_some_and(<[i16]>::is_empty) || as_i16(&[]).is_none());
let words: Vec<u16> = (0..32).map(|i| (i * 2477) as u16).collect();
let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
let aligned = as_i16(&bytes).expect("a fresh Vec<u8> is 2-byte aligned here");
for (k, &s) in aligned.iter().enumerate() {
assert_eq!(s as u16, words[k], "word {k}");
}
}
#[test]
fn writes_land_where_the_byte_path_would_put_them() {
let vals: [i16; 8] = [0, -1, 1, i16::MIN, i16::MAX, -12345, 30000, -30000];
let mut via_view = vec![0u8; 16];
let mut ok = false;
if let Some(v) = as_i16_mut(&mut via_view) {
v.copy_from_slice(&vals);
ok = true;
}
let mut via_bytes = vec![0u8; 16];
for (k, &x) in vals.iter().enumerate() {
via_bytes[k * 2..k * 2 + 2].copy_from_slice(&x.to_le_bytes());
}
if ok {
assert_eq!(via_view, via_bytes, "the two write paths disagree");
}
assert!(as_i16_mut(&mut via_view[..15]).is_none(), "odd length");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_arithmetic() {
let f = PcmFormat::PCM16_16K_MONO;
assert_eq!(f.frame_bytes(), 2);
assert_eq!(f.bytes_for_micros(20_000), 640);
assert_eq!(f.micros_for_frames(320), 20_000);
let s = PcmFormat::PCM16_48K_STEREO;
assert_eq!(s.frame_bytes(), 4);
}
#[test]
fn block_must_be_frame_aligned() {
let f = PcmFormat::PCM16_48K_STEREO;
assert_eq!(
PcmBlock::new(f, Micros::ZERO, &[0; 6]).err(),
Some(Error::InvalidGeometry)
);
assert_eq!(
PcmBlock::new(f, Micros::ZERO, &[]).err(),
Some(Error::InvalidGeometry)
);
let b = PcmBlock::new(f, Micros::from_millis(1), &[0; 8]).unwrap();
assert_eq!(b.frames(), 2);
assert_eq!(b.end().0, 1_000 + 41);
}
#[test]
fn i16_iteration() {
let f = PcmFormat::PCM16_16K_MONO;
let bytes = [0x01, 0x00, 0xFF, 0xFF];
let b = PcmBlock::new(f, Micros::ZERO, &bytes).unwrap();
let v: [i16; 2] = {
let mut it = b.samples_i16().unwrap();
[it.next().unwrap(), it.next().unwrap()]
};
assert_eq!(v, [1, -1]);
let f32 = PcmFormat::new(16_000, 1, SampleFormat::F32).unwrap();
assert!(
PcmBlock::new(f32, Micros::ZERO, &[0; 4])
.unwrap()
.samples_i16()
.is_none()
);
}
}