use core::fmt;
use core::iter::FusedIterator;
use crate::{Bs2b, Sample};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StreamingError {
InvalidChannelCount(usize),
InvalidChannelPair {
channels: usize,
left: usize,
right: usize,
},
MisalignedCallbackBuffer {
sample_count: usize,
channels: usize,
},
}
impl fmt::Display for StreamingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidChannelCount(channels) => write!(
f,
"channel count must be at least 2 for stereo crossfeed, got {channels}"
),
Self::InvalidChannelPair {
channels,
left,
right,
} => write!(
f,
"channel pair ({left}, {right}) is invalid for buffer with {channels} channels"
),
Self::MisalignedCallbackBuffer {
sample_count,
channels,
} => write!(
f,
"callback buffer has {sample_count} samples, which is not divisible by channel count {channels}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for StreamingError {}
#[derive(Debug, Clone)]
pub struct CallbackAdapter {
processor: Bs2b,
channels: usize,
left_channel: usize,
right_channel: usize,
}
impl CallbackAdapter {
pub fn new(processor: Bs2b, channels: usize) -> Result<Self, StreamingError> {
Self::with_channel_pair(processor, channels, 0, 1)
}
pub fn with_channel_pair(
processor: Bs2b,
channels: usize,
left_channel: usize,
right_channel: usize,
) -> Result<Self, StreamingError> {
validate_channel_pair(channels, left_channel, right_channel)?;
Ok(Self {
processor,
channels,
left_channel,
right_channel,
})
}
pub const fn processor(&self) -> &Bs2b {
&self.processor
}
pub fn processor_mut(&mut self) -> &mut Bs2b {
&mut self.processor
}
pub fn into_processor(self) -> Bs2b {
self.processor
}
pub const fn channels(&self) -> usize {
self.channels
}
pub const fn left_channel(&self) -> usize {
self.left_channel
}
pub const fn right_channel(&self) -> usize {
self.right_channel
}
pub fn process<T: Sample>(&mut self, data: &mut [T]) -> Result<(), StreamingError> {
if !data.len().is_multiple_of(self.channels) {
return Err(StreamingError::MisalignedCallbackBuffer {
sample_count: data.len(),
channels: self.channels,
});
}
for frame in data.chunks_exact_mut(self.channels) {
let (left, right) = self
.processor
.process_frame(frame[self.left_channel], frame[self.right_channel]);
frame[self.left_channel] = left;
frame[self.right_channel] = right;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct StereoSourceAdapter<I>
where
I: Iterator,
{
inner: I,
processor: Bs2b,
pending: Option<I::Item>,
}
impl<I> StereoSourceAdapter<I>
where
I: Iterator,
I::Item: Sample,
{
pub fn new(inner: I, processor: Bs2b) -> Self {
Self {
inner,
processor,
pending: None,
}
}
pub const fn processor(&self) -> &Bs2b {
&self.processor
}
pub fn processor_mut(&mut self) -> &mut Bs2b {
&mut self.processor
}
pub fn into_inner(self) -> I {
self.inner
}
}
impl<I> Iterator for StereoSourceAdapter<I>
where
I: Iterator,
I::Item: Sample,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
if let Some(sample) = self.pending.take() {
return Some(sample);
}
let left = self.inner.next()?;
let Some(right) = self.inner.next() else {
return Some(left);
};
let (left, right) = self.processor.process_frame(left, right);
self.pending = Some(right);
Some(left)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (low, high) = self.inner.size_hint();
let extra = usize::from(self.pending.is_some());
(
low.saturating_add(extra),
high.and_then(|value| value.checked_add(extra)),
)
}
}
impl<I> FusedIterator for StereoSourceAdapter<I>
where
I: FusedIterator,
I::Item: Sample,
{
}
fn validate_channel_pair(
channels: usize,
left_channel: usize,
right_channel: usize,
) -> Result<(), StreamingError> {
if channels < 2 {
return Err(StreamingError::InvalidChannelCount(channels));
}
if left_channel >= channels || right_channel >= channels || left_channel == right_channel {
return Err(StreamingError::InvalidChannelPair {
channels,
left: left_channel,
right: right_channel,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::vec;
use super::*;
use crate::Level;
#[test]
fn callback_adapter_matches_stereo_interleaved_processing() {
let mut expected = vec![0.2_f32, -0.2, 0.35, -0.15, -0.4, 0.25];
let mut actual = expected.clone();
let mut direct = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
direct
.process_interleaved(&mut expected)
.expect("buffer is valid stereo");
let processor = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
let mut adapter = CallbackAdapter::new(processor, 2).expect("valid adapter config");
adapter
.process(&mut actual)
.expect("buffer alignment is valid");
assert_eq!(actual, expected);
}
#[test]
fn callback_adapter_processes_only_selected_pair() {
let mut data = vec![
0.7_f32, -0.7, 0.2, -0.2, 0.6, -0.6, 0.4, -0.1, 0.5, -0.5, -0.3, 0.3,
];
let mut expected_pair = vec![0.2_f32, -0.2, 0.4, -0.1, -0.3, 0.3];
let mut direct = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
direct
.process_interleaved(&mut expected_pair)
.expect("buffer is valid stereo");
let processor = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
let mut adapter =
CallbackAdapter::with_channel_pair(processor, 4, 2, 3).expect("valid pair");
adapter
.process(&mut data)
.expect("buffer alignment is valid");
assert_eq!(data[0], 0.7_f32);
assert_eq!(data[1], -0.7_f32);
assert_eq!(data[4], 0.6_f32);
assert_eq!(data[5], -0.6_f32);
assert_eq!(data[8], 0.5_f32);
assert_eq!(data[9], -0.5_f32);
assert_eq!(data[2], expected_pair[0]);
assert_eq!(data[3], expected_pair[1]);
assert_eq!(data[6], expected_pair[2]);
assert_eq!(data[7], expected_pair[3]);
assert_eq!(data[10], expected_pair[4]);
assert_eq!(data[11], expected_pair[5]);
}
#[test]
fn callback_adapter_validates_channels_and_buffer_shape() {
let processor = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
assert!(matches!(
CallbackAdapter::new(processor.clone(), 1),
Err(StreamingError::InvalidChannelCount(1))
));
assert!(matches!(
CallbackAdapter::with_channel_pair(processor.clone(), 2, 0, 0),
Err(StreamingError::InvalidChannelPair {
channels: 2,
left: 0,
right: 0
})
));
let mut adapter = CallbackAdapter::new(processor, 2).expect("valid adapter config");
let mut misaligned = vec![0.1_f32, -0.2, 0.3];
assert!(matches!(
adapter.process(&mut misaligned),
Err(StreamingError::MisalignedCallbackBuffer {
sample_count: 3,
channels: 2
})
));
}
#[test]
fn stereo_source_adapter_matches_interleaved_processing() {
let input = vec![0.1_f32, -0.1, 0.2, -0.3, 0.4, 0.05];
let mut expected = input.clone();
let mut direct = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
direct
.process_interleaved(&mut expected)
.expect("buffer is valid stereo");
let processor = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
let actual: vec::Vec<f32> =
StereoSourceAdapter::new(input.into_iter(), processor).collect();
assert_eq!(actual, expected);
}
#[test]
fn stereo_source_adapter_passes_through_odd_tail_sample() {
let input = vec![0.3_f32, -0.25, 0.5];
let mut expected = vec![input[0], input[1]];
let mut direct = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
direct
.process_interleaved(&mut expected)
.expect("buffer is valid stereo");
expected.push(input[2]);
let processor = Bs2b::new(48_000, Level::DEFAULT).expect("valid config");
let actual: vec::Vec<f32> =
StereoSourceAdapter::new(input.into_iter(), processor).collect();
assert_eq!(actual, expected);
}
}