use num_complex::Complex;
use std::f64::consts::PI;
use crate::context::{AudioContextRegistration, BaseAudioContext};
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
use crate::MAX_CHANNELS;
use super::{AudioNode, ChannelConfig, ChannelConfigOptions};
const MAX_IIR_COEFFS_LEN: usize = 20;
#[track_caller]
#[inline(always)]
fn assert_valid_feedforward_coefs(coefs: &Vec<f64>) {
if coefs.is_empty() || coefs.len() > MAX_IIR_COEFFS_LEN {
panic!("NotSupportedError - IIR Filter feedforward coefficients should have length >= 0 and <= 20");
}
if coefs.iter().all(|&f| f == 0.) {
panic!("InvalidStateError - IIR Filter feedforward coefficients cannot be all zeros");
}
}
#[track_caller]
#[inline(always)]
fn assert_valid_feedback_coefs(coefs: &Vec<f64>) {
if coefs.is_empty() || coefs.len() > MAX_IIR_COEFFS_LEN {
panic!("NotSupportedError - IIR Filter feedback coefficients should have length >= 0 and <= 20");
}
if coefs[0] == 0. {
panic!("InvalidStateError - IIR Filter feedback first coefficient cannot be zero");
}
}
pub struct IIRFilterOptions {
pub channel_config: ChannelConfigOptions,
pub feedforward: Vec<f64>, pub feedback: Vec<f64>, }
pub struct IIRFilterNode {
registration: AudioContextRegistration,
channel_config: ChannelConfig,
feedforward: Vec<f64>,
feedback: Vec<f64>,
}
impl AudioNode for IIRFilterNode {
fn registration(&self) -> &AudioContextRegistration {
&self.registration
}
fn channel_config(&self) -> &ChannelConfig {
&self.channel_config
}
fn number_of_inputs(&self) -> usize {
1
}
fn number_of_outputs(&self) -> usize {
1
}
}
impl IIRFilterNode {
pub fn new<C: BaseAudioContext>(context: &C, options: IIRFilterOptions) -> Self {
context.register(move |registration| {
let IIRFilterOptions {
feedforward,
feedback,
channel_config,
} = options;
assert_valid_feedforward_coefs(&feedforward);
assert_valid_feedback_coefs(&feedback);
let render = IirFilterRenderer::new(feedforward.clone(), feedback.clone());
let node = Self {
registration,
channel_config: channel_config.into(),
feedforward,
feedback,
};
(node, Box::new(render))
})
}
pub fn get_frequency_response(
&self,
frequency_hz: &[f32],
mag_response: &mut [f32],
phase_response: &mut [f32],
) {
if frequency_hz.len() != mag_response.len() || mag_response.len() != phase_response.len() {
panic!("InvalidAccessError - Parameter lengths must match");
}
let sample_rate = self.context().sample_rate() as f64;
let nquist = sample_rate / 2.;
for (i, &f) in frequency_hz.iter().enumerate() {
let freq = f64::from(f).clamp(0., nquist);
let z = -2.0 * PI * freq / sample_rate;
let mut num: Complex<f64> = Complex::new(0., 0.);
let mut denom: Complex<f64> = Complex::new(0., 0.);
for (idx, &b) in self.feedforward.iter().enumerate() {
num += Complex::from_polar(b, idx as f64 * z);
}
for (idx, &a) in self.feedback.iter().enumerate() {
denom += Complex::from_polar(a, idx as f64 * z);
}
let response = num / denom;
let (mag, phase) = response.to_polar();
mag_response[i] = mag as f32;
phase_response[i] = phase as f32;
}
}
}
struct IirFilterRenderer {
norm_coeffs: Vec<(f64, f64)>,
states: Vec<Vec<f64>>,
}
impl IirFilterRenderer {
fn new(mut feedforward: Vec<f64>, mut feedback: Vec<f64>) -> Self {
match (feedforward.len(), feedback.len()) {
(feedforward_len, feedback_len) if feedforward_len > feedback_len => {
feedforward = feedforward
.into_iter()
.chain(std::iter::repeat(0.))
.take(feedback_len)
.collect();
}
(feedforward_len, feedback_len) if feedforward_len < feedback_len => {
feedback = feedback
.into_iter()
.chain(std::iter::repeat(0.))
.take(feedforward_len)
.collect();
}
_ => (),
};
let a0 = feedback[0];
let mut norm_coeffs: Vec<(f64, f64)> = feedforward.into_iter().zip(feedback).collect();
norm_coeffs.iter_mut().for_each(|(b, a)| {
*b /= a0;
*a /= a0;
});
let coeffs_len = norm_coeffs.len();
let states = vec![Vec::<f64>::with_capacity(MAX_CHANNELS); coeffs_len];
Self {
norm_coeffs,
states,
}
}
}
impl AudioProcessor for IirFilterRenderer {
fn process(
&mut self,
inputs: &[AudioRenderQuantum],
outputs: &mut [AudioRenderQuantum],
_params: AudioParamValues,
_scope: &RenderScope,
) -> bool {
let input = &inputs[0];
let output = &mut outputs[0];
if input.is_silent() {
let mut ended = true;
self.states.iter().all(|state| {
if state.iter().any(|&v| v.is_normal()) {
ended = false;
}
ended
});
if ended {
output.make_silent();
return false;
}
}
if !input.is_silent() {
let num_channels = input.number_of_channels();
if num_channels != self.states[0].len() {
self.states
.iter_mut()
.for_each(|state| state.resize(num_channels, 0.));
}
output.set_number_of_channels(num_channels);
} else {
let num_channels = self.states[0].len();
output.set_number_of_channels(num_channels);
}
for (channel_number, (input_channel, output_channel)) in input
.channels()
.iter()
.zip(output.channels_mut())
.enumerate()
{
for (&i, o) in input_channel.iter().zip(output_channel.iter_mut()) {
let input = f64::from(i);
let b0 = self.norm_coeffs[0].0;
let last_state = self.states[0][channel_number];
let output = b0.mul_add(input, last_state);
for (i, (b, a)) in self.norm_coeffs.iter().skip(1).enumerate() {
let state = self.states[i + 1][channel_number];
self.states[i][channel_number] = b * input - a * output + state;
}
#[cfg(debug_assertions)]
if output.is_nan() || output.is_infinite() {
log::debug!("An unstable filter is processed.");
}
*o = output as f32;
}
}
true
}
}
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
use std::fs::File;
use crate::context::{BaseAudioContext, OfflineAudioContext};
use crate::node::{AudioNode, AudioScheduledSourceNode, BiquadFilterType};
use crate::AudioBuffer;
use super::*;
const LENGTH: usize = 512;
#[test]
fn test_constructor_and_factory() {
{
let context = OfflineAudioContext::new(2, LENGTH, 44_100.);
let options = IIRFilterOptions {
feedback: vec![1.; 3],
feedforward: vec![1.; 3],
channel_config: ChannelConfigOptions::default(),
};
let _biquad = IIRFilterNode::new(&context, options);
}
{
let context = OfflineAudioContext::new(2, LENGTH, 44_100.);
let feedforward = vec![1.; 3];
let feedback = vec![1.; 3];
let _biquad = context.create_iir_filter(feedforward, feedback);
}
}
#[test]
#[should_panic]
fn test_invalid_feedforward_size() {
let feedforward = vec![1.; 21];
assert_valid_feedforward_coefs(&feedforward);
}
#[test]
#[should_panic]
fn test_invalid_feedforward_values() {
let feedforward = vec![0.; 5];
assert_valid_feedforward_coefs(&feedforward);
}
#[test]
fn test_valid_feedforward_values() {
let feedforward = vec![1.; 5];
assert_valid_feedforward_coefs(&feedforward);
}
#[test]
#[should_panic]
fn test_invalid_feedback_size() {
let feedback = vec![1.; 21];
assert_valid_feedback_coefs(&feedback);
}
#[test]
#[should_panic]
fn test_invalid_feedback_values() {
let mut feedback = vec![1.; 5];
feedback[0] = 0.;
assert_valid_feedback_coefs(&feedback);
}
#[test]
fn test_valid_feedback_values() {
let feedback = vec![1.; 5];
assert_valid_feedback_coefs(&feedback);
}
#[test]
#[should_panic]
fn test_frequency_response_arguments() {
let context = OfflineAudioContext::new(2, 555, 44_100.);
let options = IIRFilterOptions {
feedback: vec![1.; 10],
feedforward: vec![1.; 10],
channel_config: ChannelConfigOptions::default(),
};
let iir = IIRFilterNode::new(&context, options);
let frequency_hz = [0.];
let mut mag_response = [0., 1.0];
let mut phase_response = [0.];
iir.get_frequency_response(&frequency_hz, &mut mag_response, &mut phase_response);
}
#[test]
#[should_panic]
fn test_frequency_response_arguments_2() {
let context = OfflineAudioContext::new(2, 555, 44_100.);
let options = IIRFilterOptions {
feedback: vec![1.; 10],
feedforward: vec![1.; 10],
channel_config: ChannelConfigOptions::default(),
};
let iir = IIRFilterNode::new(&context, options);
let frequency_hz = [0.];
let mut mag_response = [0.];
let mut phase_response = [0., 1.0];
iir.get_frequency_response(&frequency_hz, &mut mag_response, &mut phase_response);
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_output_against_biquad() {
let context = OfflineAudioContext::new(1, 1, 44_100.);
let file = File::open("samples/white.ogg").unwrap();
let noise = context.decode_audio_data_sync(file).unwrap();
fn compare_output(
noise: AudioBuffer,
filter_type: BiquadFilterType,
feedback: Vec<f64>,
feedforward: Vec<f64>,
) {
let frequency = 2000.;
let q = 1.;
let gain = 3.;
let biquad_res = {
let context = OfflineAudioContext::new(1, 1000, 44_100.);
let biquad = context.create_biquad_filter();
biquad.connect(&context.destination());
biquad.set_type(filter_type);
biquad.frequency().set_value(frequency);
biquad.q().set_value(q);
biquad.gain().set_value(gain);
let src = context.create_buffer_source();
src.connect(&biquad);
src.set_buffer(noise.clone());
src.start();
context.start_rendering_sync()
};
let iir_res = {
let context = OfflineAudioContext::new(1, 1000, 44_100.);
let iir = context.create_iir_filter(feedforward, feedback);
iir.connect(&context.destination());
let src = context.create_buffer_source();
src.connect(&iir);
src.set_buffer(noise.clone());
src.start();
context.start_rendering_sync()
};
assert_float_eq!(
biquad_res.get_channel_data(0),
iir_res.get_channel_data(0),
abs_all <= 0.
);
}
let a0 = 1.1252702717383296;
let a1 = -1.9193504546709936;
let a2 = 0.8747297282616704;
let b0 = 0.02016238633225159;
let b1 = 0.04032477266450318;
let b2 = 0.02016238633225159;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Lowpass,
feedback,
feedforward,
);
let a0 = 1.1252702717383296;
let a1 = -1.9193504546709936;
let a2 = 0.8747297282616704;
let b0 = 0.9798376136677485;
let b1 = -1.959675227335497;
let b2 = 0.9798376136677485;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Highpass,
feedback,
feedforward,
);
let a0 = 1.1405555566658274;
let a1 = -1.9193504546709936;
let a2 = 0.8594444433341726;
let b0 = 0.14055555666582747;
let b1 = 0.0;
let b2 = -0.14055555666582747;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Bandpass,
feedback,
feedforward,
);
let a0 = 1.1405555566658274;
let a1 = -1.9193504546709936;
let a2 = 0.8594444433341726;
let b0 = 1.0;
let b1 = -1.9193504546709936;
let b2 = 1.0;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Notch,
feedback,
feedforward,
);
let a0 = 1.1405555566658274;
let a1 = -1.9193504546709936;
let a2 = 0.8594444433341726;
let b0 = 0.8594444433341726;
let b1 = -1.9193504546709936;
let b2 = 1.1405555566658274;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Allpass,
feedback,
feedforward,
);
let a0 = 1.1182627625098631;
let a1 = -1.9193504546709936;
let a2 = 0.8817372374901369;
let b0 = 1.167050592175986;
let b1 = -1.9193504546709936;
let b2 = 0.8329494078240139;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Peaking,
feedback,
feedforward,
);
let a0 = 2.8028072429836723;
let a1 = -4.577507200153761;
let a2 = 1.935999047828101;
let b0 = 2.9011403634599007;
let b1 = -4.544236234748791;
let b2 = 1.8709368927568424;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Lowshelf,
feedback,
feedforward,
);
let a0 = 2.4410054070459357;
let a1 = -3.8234982904056865;
let a2 = 1.5741972118903644;
let b0 = 3.331142651362703;
let b1 = -5.440377503491735;
let b2 = 2.300939180659645;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_output(
noise.clone(),
BiquadFilterType::Highshelf,
feedback,
feedforward,
);
}
#[test]
fn tests_get_frequency_response() {
let ref_mag = [
1e-3,
4.152_807e-4,
1.460_789_5e-3,
5.051_316e-3,
1.130_323_5e-2,
2.230_340_2e-2,
4.311_698e-2,
8.843_45e-2,
2.146_620_2e-1,
6.802_952e-1,
];
let context = OfflineAudioContext::new(2, LENGTH, 44_100.);
let feedforward = vec![
0.019_618_022_238_052_212,
-0.036_007_928_102_449_24,
0.019_618_022_238_052_21,
];
let feedback = vec![1., 1.576_436_200_538_313_7, 0.651_680_173_116_867_3];
let options = IIRFilterOptions {
feedback,
feedforward,
channel_config: ChannelConfigOptions::default(),
};
let iir = IIRFilterNode::new(&context, options);
let frequency_hz = [
0., 2205., 4410., 6615., 8820., 11025., 13230., 15435., 17640., 19845.,
];
let mut mag_response = [0.; 10];
let mut phase_response = [0.; 10];
iir.get_frequency_response(&frequency_hz, &mut mag_response, &mut phase_response);
assert_float_eq!(mag_response, ref_mag, abs_all <= 0.);
}
#[test]
fn test_frequency_responses_against_biquad() {
fn compare_frequency_response(
filter_type: BiquadFilterType,
feedback: Vec<f64>,
feedforward: Vec<f64>,
) {
let frequency = 2000.;
let q = 1.;
let gain = 3.;
let freqs = [
400., 800., 1200., 1600., 2000., 2400., 2800., 3200., 3600., 4000.,
];
let context = OfflineAudioContext::new(1, 1, 44_100.);
let biquad_response = {
let mut mags = [0.; 10];
let mut phases = [0.; 10];
let biquad = context.create_biquad_filter();
biquad.set_type(filter_type);
biquad.frequency().set_value(frequency);
biquad.q().set_value(q);
biquad.gain().set_value(gain);
biquad.get_frequency_response(&freqs, &mut mags, &mut phases);
(mags, phases)
};
let iir_response = {
let mut mags = [0.; 10];
let mut phases = [0.; 10];
let iir = context.create_iir_filter(feedforward, feedback);
iir.get_frequency_response(&freqs, &mut mags, &mut phases);
(mags, phases)
};
assert_float_eq!(biquad_response.0, iir_response.0, abs_all <= 1e-6);
assert_float_eq!(biquad_response.1, iir_response.1, abs_all <= 1e-6);
}
let a0 = 1.1252702717383296;
let a1 = -1.9193504546709936;
let a2 = 0.8747297282616704;
let b0 = 0.02016238633225159;
let b1 = 0.04032477266450318;
let b2 = 0.02016238633225159;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Lowpass, feedback, feedforward);
let a0 = 1.1252702717383296;
let a1 = -1.9193504546709936;
let a2 = 0.8747297282616704;
let b0 = 0.9798376136677485;
let b1 = -1.959675227335497;
let b2 = 0.9798376136677485;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Highpass, feedback, feedforward);
let a0 = 1.1405555566658274;
let a1 = -1.9193504546709936;
let a2 = 0.8594444433341726;
let b0 = 0.14055555666582747;
let b1 = 0.0;
let b2 = -0.14055555666582747;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Bandpass, feedback, feedforward);
let a0 = 1.1405555566658274;
let a1 = -1.9193504546709936;
let a2 = 0.8594444433341726;
let b0 = 0.8594444433341726;
let b1 = -1.9193504546709936;
let b2 = 1.1405555566658274;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Allpass, feedback, feedforward);
let a0 = 1.1182627625098631;
let a1 = -1.9193504546709936;
let a2 = 0.8817372374901369;
let b0 = 1.167050592175986;
let b1 = -1.9193504546709936;
let b2 = 0.8329494078240139;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Peaking, feedback, feedforward);
let a0 = 2.8028072429836723;
let a1 = -4.577507200153761;
let a2 = 1.935999047828101;
let b0 = 2.9011403634599007;
let b1 = -4.544236234748791;
let b2 = 1.8709368927568424;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Lowshelf, feedback, feedforward);
let a0 = 2.4410054070459357;
let a1 = -3.8234982904056865;
let a2 = 1.5741972118903644;
let b0 = 3.331142651362703;
let b1 = -5.440377503491735;
let b2 = 2.300939180659645;
let feedback = vec![a0, a1, a2];
let feedforward = vec![b0, b1, b2];
compare_frequency_response(BiquadFilterType::Highshelf, feedback, feedforward);
}
}