#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::arithmetic_side_effects
)]
pub fn biquad_alt_stride1(
input: &[i16],
b_q28: &[i32; 3],
a_q28: &[i32; 2],
state: &mut [i32; 2],
output: &mut [i16],
) {
assert_eq!(input.len(), output.len());
let mut s0 = state[0];
let mut s1 = state[1];
let a0_l_q28 = (-a_q28[0]) & 0x0000_3fff;
let a0_u_q28 = (-a_q28[0]) >> 14;
let a1_l_q28 = (-a_q28[1]) & 0x0000_3fff;
let a1_u_q28 = (-a_q28[1]) >> 14;
for (in_sample, out_sample) in input.iter().zip(output.iter_mut()) {
let inval = i32::from(*in_sample);
let mut out32_q14 = smlawb(s0, b_q28[0], inval);
out32_q14 <<= 2;
s0 = s1.wrapping_add(rshift_round(smulwb(out32_q14, a0_l_q28), 14));
s0 = smlawb(s0, out32_q14, a0_u_q28);
s0 = smlawb(s0, b_q28[1], inval);
s1 = rshift_round(smulwb(out32_q14, a1_l_q28), 14);
s1 = smlawb(s1, out32_q14, a1_u_q28);
s1 = smlawb(s1, b_q28[2], inval);
let rounded = out32_q14.wrapping_add((1 << 14) - 1) >> 14;
*out_sample = sat16(rounded);
}
state[0] = s0;
state[1] = s1;
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::arithmetic_side_effects
)]
pub fn biquad_alt_stride1_inplace(
signal: &mut [i16],
b_q28: &[i32; 3],
a_q28: &[i32; 2],
state: &mut [i32; 2],
) {
let mut s0 = state[0];
let mut s1 = state[1];
let a0_l_q28 = (-a_q28[0]) & 0x0000_3fff;
let a0_u_q28 = (-a_q28[0]) >> 14;
let a1_l_q28 = (-a_q28[1]) & 0x0000_3fff;
let a1_u_q28 = (-a_q28[1]) >> 14;
for sample in signal.iter_mut() {
let inval = i32::from(*sample);
let mut out32_q14 = smlawb(s0, b_q28[0], inval);
out32_q14 <<= 2;
s0 = s1.wrapping_add(rshift_round(smulwb(out32_q14, a0_l_q28), 14));
s0 = smlawb(s0, out32_q14, a0_u_q28);
s0 = smlawb(s0, b_q28[1], inval);
s1 = rshift_round(smulwb(out32_q14, a1_l_q28), 14);
s1 = smlawb(s1, out32_q14, a1_u_q28);
s1 = smlawb(s1, b_q28[2], inval);
let rounded = out32_q14.wrapping_add((1 << 14) - 1) >> 14;
*sample = sat16(rounded);
}
state[0] = s0;
state[1] = s1;
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::arithmetic_side_effects
)]
pub fn biquad_alt_stride2(
input: &[i16],
b_q28: &[i32; 3],
a_q28: &[i32; 2],
state: &mut [i32; 4],
output: &mut [i16],
) {
assert_eq!(input.len(), output.len());
assert!(input.len().is_multiple_of(2), "signal length must be even");
let mut s0 = state[0];
let mut s1 = state[1];
let mut s2 = state[2];
let mut s3 = state[3];
let a0_l_q28 = (-a_q28[0]) & 0x0000_3fff;
let a0_u_q28 = (-a_q28[0]) >> 14;
let a1_l_q28 = (-a_q28[1]) & 0x0000_3fff;
let a1_u_q28 = (-a_q28[1]) >> 14;
for (chunk_in, chunk_out) in input.chunks_exact(2).zip(output.chunks_exact_mut(2)) {
let inval0 = i32::from(chunk_in[0]);
let inval1 = i32::from(chunk_in[1]);
let out0_q14 = smlawb(s0, b_q28[0], inval0) << 2;
let out1_q14 = smlawb(s2, b_q28[0], inval1) << 2;
s0 = s1.wrapping_add(rshift_round(smulwb(out0_q14, a0_l_q28), 14));
s2 = s3.wrapping_add(rshift_round(smulwb(out1_q14, a0_l_q28), 14));
s0 = smlawb(s0, out0_q14, a0_u_q28);
s2 = smlawb(s2, out1_q14, a0_u_q28);
s0 = smlawb(s0, b_q28[1], inval0);
s2 = smlawb(s2, b_q28[1], inval1);
s1 = rshift_round(smulwb(out0_q14, a1_l_q28), 14);
s3 = rshift_round(smulwb(out1_q14, a1_l_q28), 14);
s1 = smlawb(s1, out0_q14, a1_u_q28);
s3 = smlawb(s3, out1_q14, a1_u_q28);
s1 = smlawb(s1, b_q28[2], inval0);
s3 = smlawb(s3, b_q28[2], inval1);
let rounded0 = out0_q14.wrapping_add((1 << 14) - 1) >> 14;
let rounded1 = out1_q14.wrapping_add((1 << 14) - 1) >> 14;
chunk_out[0] = sat16(rounded0);
chunk_out[1] = sat16(rounded1);
}
state[0] = s0;
state[1] = s1;
state[2] = s2;
state[3] = s3;
}
#[inline]
fn smlawb(a: i32, b: i32, c: i32) -> i32 {
let product = i64::from(b) * i64::from(c as i16);
a.wrapping_add((product >> 16) as i32)
}
#[inline]
fn smulwb(a: i32, b: i32) -> i32 {
let product = i64::from(a) * i64::from(b as i16);
(product >> 16) as i32
}
#[inline]
fn rshift_round(value: i32, shift: i32) -> i32 {
debug_assert!(shift > 0);
if shift == 1 {
(value >> 1) + (value & 1)
} else {
((value >> (shift - 1)) + 1) >> 1
}
}
#[inline]
fn sat16(value: i32) -> i16 {
if value > i32::from(i16::MAX) {
i16::MAX
} else if value < i32::from(i16::MIN) {
i16::MIN
} else {
value as i16
}
}
#[cfg(test)]
mod tests {
use super::{biquad_alt_stride1, biquad_alt_stride1_inplace, biquad_alt_stride2};
#[test]
fn stride1_matches_reference_sequence() {
let mut state = [123_456, -654_321];
let b = [1_145_324_612, -229_064_922, 1_145_324_612];
let a = [-1_010_580_540, 505_290_270];
let input = [1234, -2345, 3456, -4567, 5678, -6789];
let mut output = [0i16; 6];
biquad_alt_stride1(&input, &b, &a, &mut state, &mut output);
assert_eq!(output, [5296, -12_464, 14_977, -12_502, 17_619, -32_768]);
assert_eq!(state, [19_797_224, 142_793_818]);
}
#[test]
fn stride2_matches_reference_sequence() {
let mut state = [2_345_678, -3_456_789, 4_567_890, -5_678_901];
let b = [1_145_324_612, -229_064_922, 1_145_324_612];
let a = [-1_010_580_540, 505_290_270];
let input = [1357, -2468, 3579, -4680, 5791, -6802, 7913, -8024];
let mut output = [0i16; 8];
biquad_alt_stride2(&input, &b, &a, &mut state, &mut output);
assert_eq!(
output,
[
6363, -9414, 11_772, -17_033, 12_698, -13_828, 18_946, -13_083
]
);
assert_eq!(state, [-42_612_823, -7_780_131, 28_399_995, -39_356_343]);
}
#[test]
fn identity_coefficients_copy_input() {
let mut state = [0; 2];
let b = [1 << 28, 0, 0];
let a = [0, 0];
let input = [100, -200, 300, -400];
let mut output = [0i16; 4];
biquad_alt_stride1(&input, &b, &a, &mut state, &mut output);
assert_eq!(output, input);
assert_eq!(state, [0, 0]);
}
#[test]
fn inplace_matches_out_of_place_processing() {
let b = [1_145_324_612, -229_064_922, 1_145_324_612];
let a = [-1_010_580_540, 505_290_270];
let input = [1234, -2345, 3456, -4567, 5678, -6789];
let mut state_out = [123_456, -654_321];
let mut state_inplace = state_out;
let mut output = [0i16; 6];
let mut inplace_buffer = input;
biquad_alt_stride1(&input, &b, &a, &mut state_out, &mut output);
biquad_alt_stride1_inplace(&mut inplace_buffer, &b, &a, &mut state_inplace);
assert_eq!(inplace_buffer, output);
assert_eq!(state_inplace, state_out);
}
}