use crate::error::{OpusError, Result};
pub fn planar_to_interleaved(channels: u16, planar: &[f32]) -> Result<Vec<f32>> {
let mut out = vec![0.0_f32; planar.len()];
planar_to_interleaved_into(channels, planar, &mut out)?;
Ok(out)
}
pub fn interleaved_to_planar(channels: u16, interleaved: &[f32]) -> Result<Vec<f32>> {
let mut out = vec![0.0_f32; interleaved.len()];
interleaved_to_planar_into(channels, interleaved, &mut out)?;
Ok(out)
}
pub fn planar_to_interleaved_into(channels: u16, planar: &[f32], out: &mut [f32]) -> Result<()> {
validate_layout(channels, planar.len())?;
if out.len() != planar.len() {
return Err(OpusError::BufferTooSmall {
needed: planar.len(),
have: out.len(),
});
}
let chans = usize::from(channels);
if chans == 0 {
return Ok(());
}
let frames = planar.len() / chans;
for (frame_idx, chunk) in out.chunks_mut(chans).enumerate() {
for (chan_idx, slot) in chunk.iter_mut().enumerate() {
*slot = planar[chan_idx * frames + frame_idx];
}
}
Ok(())
}
pub fn interleaved_to_planar_into(
channels: u16,
interleaved: &[f32],
out: &mut [f32],
) -> Result<()> {
validate_layout(channels, interleaved.len())?;
if out.len() != interleaved.len() {
return Err(OpusError::BufferTooSmall {
needed: interleaved.len(),
have: out.len(),
});
}
let chans = usize::from(channels);
if chans == 0 {
return Ok(());
}
let frames = interleaved.len() / chans;
for (frame_idx, chunk) in interleaved.chunks(chans).enumerate() {
for (chan_idx, &sample) in chunk.iter().enumerate() {
out[chan_idx * frames + frame_idx] = sample;
}
}
Ok(())
}
fn validate_layout(channels: u16, len: usize) -> Result<()> {
let chans = usize::from(channels);
if chans == 0 {
if len != 0 {
return Err(OpusError::LayoutMismatch(format!(
"{channels} channels cannot own {len} non-empty samples"
)));
}
return Ok(());
}
if len % chans != 0 {
return Err(OpusError::LayoutMismatch(format!(
"length {len} is not a multiple of {channels} channels"
)));
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::cast_precision_loss)] mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn stereo_planar_to_interleaved_is_exact() {
let planar: &[f32] = &[10.0, 20.0, 30.0, 40.0];
let inter = planar_to_interleaved(2, planar).expect("valid stereo planar");
assert_eq!(inter, vec![10.0, 30.0, 20.0, 40.0]);
}
#[test]
fn stereo_interleaved_to_planar_is_exact() {
let inter: &[f32] = &[10.0, 30.0, 20.0, 40.0];
let planar = interleaved_to_planar(2, inter).expect("valid stereo interleaved");
assert_eq!(planar, vec![10.0, 20.0, 30.0, 40.0]);
}
#[test]
fn round_trip_planar_interleaved_planar_is_identity_mono() {
let original: Vec<f32> = (0..960).map(|i| i as f32 * 0.001).collect();
let inter = planar_to_interleaved(1, &original).expect("mono planar");
let back = interleaved_to_planar(1, &inter).expect("mono inter");
assert_eq!(back, original);
}
#[test]
fn round_trip_planar_interleaved_planar_is_identity_stereo() {
let original: Vec<f32> = (0..1920).map(|i| i as f32 * 0.001).collect();
let inter = planar_to_interleaved(2, &original).expect("stereo planar");
assert_eq!(inter.len(), original.len());
let back = interleaved_to_planar(2, &inter).expect("stereo inter");
assert_eq!(back, original);
}
#[test]
fn round_trip_interleaved_planar_interleaved_is_identity() {
let original: Vec<f32> = (0..960).map(|i| i as f32 * 0.001).collect();
let planar = interleaved_to_planar(2, &original).expect("stereo inter");
let back = planar_to_interleaved(2, &planar).expect("stereo planar");
assert_eq!(back, original);
}
#[test]
fn uneven_length_returns_layout_mismatch() {
let bad: &[f32] = &[1.0, 2.0, 3.0, 4.0, 5.0];
let err = planar_to_interleaved(2, bad).unwrap_err();
assert!(matches!(err, OpusError::LayoutMismatch(_)), "got {err:?}");
let err = interleaved_to_planar(2, bad).unwrap_err();
assert!(matches!(err, OpusError::LayoutMismatch(_)), "got {err:?}");
}
#[test]
fn into_variants_validate_buffer_size() {
let planar: &[f32] = &[1.0, 2.0, 3.0, 4.0];
let mut out = vec![0.0_f32; 2];
let err = planar_to_interleaved_into(2, planar, &mut out).unwrap_err();
assert!(
matches!(err, OpusError::BufferTooSmall { .. }),
"expected BufferTooSmall, got {err:?}"
);
let mut out2 = vec![0.0_f32; 2];
let err = interleaved_to_planar_into(2, planar, &mut out2).unwrap_err();
assert!(
matches!(err, OpusError::BufferTooSmall { .. }),
"expected BufferTooSmall, got {err:?}"
);
}
#[test]
fn into_variants_reject_uneven_length_before_buffer_check() {
let uneven: &[f32] = &[1.0, 2.0, 3.0, 4.0, 5.0];
let mut out = vec![0.0_f32; 5];
let err = planar_to_interleaved_into(2, uneven, &mut out).unwrap_err();
assert!(
matches!(err, OpusError::LayoutMismatch(_)),
"expected LayoutMismatch, got {err:?}"
);
}
#[test]
fn zero_channels_empty_buffer_round_trips() {
assert!(planar_to_interleaved(0, &[]).unwrap().is_empty());
assert!(interleaved_to_planar(0, &[]).unwrap().is_empty());
}
#[test]
fn zero_channels_non_empty_is_layout_mismatch() {
let err = planar_to_interleaved(0, &[1.0]).unwrap_err();
assert!(matches!(err, OpusError::LayoutMismatch(_)));
}
#[test]
fn empty_buffer_round_trips_for_any_channel_count() {
let inter = planar_to_interleaved(2, &[]).unwrap();
assert!(inter.is_empty());
let back = interleaved_to_planar(2, &inter).unwrap();
assert!(back.is_empty());
}
#[test]
fn into_variant_matches_allocating_variant() {
let planar: Vec<f32> = (0..8).map(|i| i as f32).collect();
let alloc = planar_to_interleaved(2, &planar).unwrap();
let mut into = vec![0.0_f32; planar.len()];
planar_to_interleaved_into(2, &planar, &mut into).unwrap();
assert_eq!(alloc, into);
}
proptest! {
#[test]
fn prop_planar_inter_planar_identity(
channels in 1u16..=2u16,
frames in 0usize..256,
) {
let chans = usize::from(channels);
let planar: Vec<f32> = (0..chans * frames).map(|i| i as f32).collect();
let inter = planar_to_interleaved(channels, &planar).unwrap();
let back = interleaved_to_planar(channels, &inter).unwrap();
prop_assert_eq!(back, planar);
}
#[test]
fn prop_inter_planar_inter_identity(
channels in 1u16..=2u16,
frames in 0usize..256,
) {
let chans = usize::from(channels);
let inter: Vec<f32> = (0..chans * frames).map(|i| i as f32).collect();
let planar = interleaved_to_planar(channels, &inter).unwrap();
let back = planar_to_interleaved(channels, &planar).unwrap();
prop_assert_eq!(back, inter);
}
}
}