use crate::{
DspError,
convolution::{FirRoute, MatrixConvolver},
fir::firwin,
invalid,
};
#[derive(Debug)]
pub struct MultirateMatrixConvolver {
factor: usize,
block: usize,
prototype: Vec<f64>,
low: MatrixConvolver,
down_history: Vec<Vec<f64>>,
down_head: usize,
up_history: Vec<Vec<f64>>,
up_head: usize,
low_inputs: Vec<Vec<f64>>,
low_outputs: Vec<Vec<f64>>,
tail: usize,
}
impl MultirateMatrixConvolver {
pub fn new(
inputs: usize,
outputs: usize,
low_block_size: usize,
factor: usize,
routes: &[FirRoute],
) -> Result<Self, DspError> {
if factor == 0 || low_block_size == 0 || inputs == 0 || outputs == 0 {
return Err(invalid("multirate sizes must be positive"));
}
let length = factor
.checked_mul(64)
.and_then(|n| n.checked_add(1))
.ok_or_else(|| invalid("multirate prototype size overflow"))?;
low_block_size
.checked_mul(factor)
.ok_or_else(|| invalid("multirate block size overflow"))?;
length
.checked_mul(std::mem::size_of::<f64>())
.ok_or_else(|| invalid("multirate prototype size overflow"))?;
let mut prototype = firwin(length, 0.4 * (48000.0 / factor as f64), 48000.0)?;
for k in 0..length / 2 {
let value = (prototype[k] + prototype[length - 1 - k]) * 0.5;
prototype[k] = value;
prototype[length - 1 - k] = value;
}
Self::with_prototype(inputs, outputs, low_block_size, factor, routes, &prototype)
}
pub fn with_prototype(
inputs: usize,
outputs: usize,
low_block_size: usize,
factor: usize,
routes: &[FirRoute],
prototype: &[f64],
) -> Result<Self, DspError> {
if factor == 0 || low_block_size == 0 || inputs == 0 || outputs == 0 {
return Err(invalid("multirate sizes must be positive"));
}
if prototype.is_empty()
|| prototype.len().is_multiple_of(2)
|| prototype.iter().any(|v| !v.is_finite())
|| prototype.iter().zip(prototype.iter().rev()).any(|(a, b)| {
(a - b).abs() > 8.0 * f64::EPSILON * a.abs().max(b.abs()).max(f64::MIN_POSITIVE)
})
{
return Err(invalid(
"multirate prototype must be finite, odd and symmetric",
));
}
let block = low_block_size
.checked_mul(factor)
.ok_or_else(|| invalid("multirate block size overflow"))?;
let up_length = prototype.len().div_ceil(factor);
inputs
.checked_mul(prototype.len())
.and_then(|n| n.checked_mul(8))
.ok_or_else(|| invalid("multirate decimator size overflow"))?;
outputs
.checked_mul(up_length)
.and_then(|n| n.checked_mul(8))
.ok_or_else(|| invalid("multirate interpolator size overflow"))?;
let low = MatrixConvolver::new(inputs, outputs, low_block_size, routes)?;
let tail = low
.tail_samples()
.checked_mul(factor)
.and_then(|n| {
(prototype.len() - 1)
.checked_mul(2)
.and_then(|m| n.checked_add(m))
})
.ok_or_else(|| invalid("multirate tail size overflow"))?;
Ok(Self {
factor,
block,
prototype: prototype.to_vec(),
low,
down_history: vec![vec![0.0; prototype.len()]; inputs],
down_head: 0,
up_history: vec![vec![0.0; up_length]; outputs],
up_head: 0,
low_inputs: vec![vec![0.0; low_block_size]; inputs],
low_outputs: vec![vec![0.0; low_block_size]; outputs],
tail,
})
}
pub fn process(
&mut self,
inputs: &[&[f64]],
outputs: &mut [&mut [f64]],
) -> Result<(), DspError> {
self.process_buffers(inputs, outputs)
}
pub fn process_buffers<I: AsRef<[f64]>, O: AsMut<[f64]>>(
&mut self,
inputs: &[I],
outputs: &mut [O],
) -> Result<(), DspError> {
if inputs.len() != self.input_count()
|| outputs.len() != self.output_count()
|| inputs.iter().any(|x| {
x.as_ref().len() != self.block || x.as_ref().iter().any(|v| !v.is_finite())
})
|| outputs.iter_mut().any(|y| y.as_mut().len() != self.block)
{
return Err(invalid("invalid multirate input/output block"));
}
let length = self.prototype.len();
for t in 0..self.block {
for (channel, input) in inputs.iter().enumerate() {
self.down_history[channel][self.down_head] = input.as_ref()[t];
}
if t.is_multiple_of(self.factor) {
for (channel, history) in self.down_history.iter().enumerate() {
let mut value = 0.0;
for (k, h) in self.prototype.iter().enumerate() {
value += h * history[(self.down_head + length - k) % length];
}
self.low_inputs[channel][t / self.factor] = value;
}
}
self.down_head = (self.down_head + 1) % length;
}
self.low
.process_buffers(&self.low_inputs, &mut self.low_outputs)?;
let up_length = self.up_history[0].len();
for t in 0..self.low.block_size() {
for (channel, history) in self.up_history.iter_mut().enumerate() {
history[self.up_head] = self.low_outputs[channel][t];
}
for phase in 0..self.factor {
for (channel, output) in outputs.iter_mut().enumerate() {
let mut value = 0.0;
for k in (phase..length).step_by(self.factor) {
value += self.prototype[k]
* self.up_history[channel]
[(self.up_head + up_length - k / self.factor) % up_length];
}
output.as_mut()[t * self.factor + phase] = value * self.factor as f64;
}
}
self.up_head = (self.up_head + 1) % up_length;
}
Ok(())
}
pub fn reset(&mut self) {
self.low.reset();
self.down_head = 0;
self.up_head = 0;
for history in &mut self.down_history {
history.fill(0.0);
}
for history in &mut self.up_history {
history.fill(0.0);
}
for block in &mut self.low_inputs {
block.fill(0.0);
}
for block in &mut self.low_outputs {
block.fill(0.0);
}
}
pub fn block_size(&self) -> usize {
self.block
}
pub fn low_block_size(&self) -> usize {
self.low.block_size()
}
pub fn factor(&self) -> usize {
self.factor
}
pub fn input_count(&self) -> usize {
self.low.input_count()
}
pub fn output_count(&self) -> usize {
self.low.output_count()
}
pub fn latency_samples(&self) -> usize {
self.prototype.len() - 1
}
pub fn tail_samples(&self) -> usize {
self.tail
}
pub fn transforms_per_block(&self) -> (usize, usize) {
self.low.transforms_per_block()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn direct(x: &[f64], h: &[f64], n: usize) -> Vec<f64> {
(0..n)
.map(|t| {
h.iter()
.enumerate()
.take(t + 1)
.filter(|(k, _)| t - k < x.len())
.map(|(k, h)| x[t - k] * h)
.sum()
})
.collect()
}
#[test]
fn polyphase_stream_matches_explicit_filter_downsample_upsample_filter() {
let (factor, n) = (3, 504);
let prototype = [0.05, 0.1, 0.2, 0.3, 0.2, 0.1, 0.05];
let routes = vec![
FirRoute {
input: 0,
output: 0,
taps: vec![1.0, -0.3, 0.2],
},
FirRoute {
input: 1,
output: 0,
taps: vec![0.4; 19],
},
FirRoute {
input: 0,
output: 1,
taps: vec![0.5; 13],
},
];
let inputs: Vec<Vec<_>> = (0..2)
.map(|i| {
(0..n)
.map(|k| {
if k < n / 2 {
((k * 7 + i) as f64).sin()
} else {
0.0
}
})
.collect()
})
.collect();
let down: Vec<Vec<_>> = inputs
.iter()
.map(|x| {
direct(x, &prototype, n)
.into_iter()
.step_by(factor)
.collect()
})
.collect();
let mut low = vec![vec![0.0; n / factor]; 2];
for route in &routes {
let filtered = direct(&down[route.input], &route.taps, n / factor);
for (a, b) in low[route.output].iter_mut().zip(filtered) {
*a += b;
}
}
let expected: Vec<_> = low
.iter()
.map(|x| {
let mut up = vec![0.0; n];
for (t, x) in x.iter().enumerate() {
up[t * factor] = *x;
}
direct(&up, &prototype, n)
.into_iter()
.map(|y| y * factor as f64)
.collect::<Vec<_>>()
})
.collect();
for block in [1, 7, 28] {
let mut filter =
MultirateMatrixConvolver::with_prototype(2, 2, block, factor, &routes, &prototype)
.unwrap();
assert_eq!(filter.latency_samples(), 6);
for _ in 0..2 {
let mut got = vec![vec![0.0; n]; 2];
for offset in (0..n).step_by(filter.block_size()) {
let end = offset + filter.block_size();
let x: Vec<_> = inputs.iter().map(|x| &x[offset..end]).collect();
let mut y: Vec<_> = got.iter_mut().map(|y| &mut y[offset..end]).collect();
filter.process(&x, &mut y).unwrap();
}
for (a, b) in got.iter().flatten().zip(expected.iter().flatten()) {
assert!((a - b).abs() < 2e-12);
}
filter.reset();
}
}
}
#[test]
fn default_rate_converter_preserves_dc_rejects_aliases_and_reports_delay() {
let route = FirRoute {
input: 0,
output: 0,
taps: vec![1.0],
};
let (factor, block) = (80, 32);
let mut filter = MultirateMatrixConvolver::new(1, 1, block, factor, &[route]).unwrap();
assert_eq!(filter.latency_samples(), 5120);
let n = filter.block_size();
let mut output = vec![0.0; n];
for _ in 0..8 {
filter
.process(&[&vec![1.0; n]], &mut [&mut output])
.unwrap();
}
let dc = output.iter().sum::<f64>() / n as f64;
assert!((dc - 1.0).abs() < 2e-4, "DC gain={dc}");
assert!(output.iter().all(|y| (y - 1.0).abs() < 1e-3));
filter.reset();
for b in 0..8 {
let input: Vec<_> = (0..n)
.map(|t| (2.0 * std::f64::consts::PI * 550.0 * (b * n + t) as f64 / 48000.0).sin())
.collect();
filter.process(&[&input], &mut [&mut output]).unwrap();
}
let rms = (output.iter().map(|y| y * y).sum::<f64>() / n as f64).sqrt();
assert!(rms < 1e-3, "550 Hz alias RMS={rms}");
filter.reset();
let mut impulse_response = Vec::new();
for b in 0..5 {
let mut input = vec![0.0; n];
if b == 0 {
input[0] = 1.0;
}
filter.process(&[&input], &mut [&mut output]).unwrap();
impulse_response.extend_from_slice(&output);
}
let peak = impulse_response
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.abs().total_cmp(&b.abs()))
.unwrap()
.0;
assert_eq!(peak, filter.latency_samples());
}
#[test]
fn invalid_multirate_dimensions_and_prototypes_are_rejected() {
assert!(MultirateMatrixConvolver::new(1, 1, 8, 0, &[]).is_err());
assert!(MultirateMatrixConvolver::new(1, 1, usize::MAX, 80, &[]).is_err());
for prototype in [&[][..], &[1.0, 0.0], &[1.0, 2.0, 0.0], &[f64::NAN]] {
assert!(MultirateMatrixConvolver::with_prototype(1, 1, 8, 3, &[], prototype).is_err());
}
let mut filter = MultirateMatrixConvolver::with_prototype(1, 1, 8, 3, &[], &[1.0]).unwrap();
let mut output = [0.0; 24];
assert!(filter.process(&[&[0.0; 23]], &mut [&mut output]).is_err());
assert!(
filter
.process(&[&[f64::NAN; 24]], &mut [&mut output])
.is_err()
);
}
}