iir_int: move lowpass coefficient calculation to iirstate
This commit is contained in:
parent
9b3a47e08b
commit
7b9fc3b2b3
@ -1,8 +1,39 @@
|
|||||||
|
use super::cossin;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Generic vector for integer IIR filter.
|
||||||
|
/// This struct is used to hold the x/y input/output data vector or the b/a coefficient
|
||||||
|
/// vector.
|
||||||
#[derive(Copy, Clone, Default, Deserialize, Serialize)]
|
#[derive(Copy, Clone, Default, Deserialize, Serialize)]
|
||||||
pub struct IIRState(pub [i32; 5]);
|
pub struct IIRState(pub [i32; 5]);
|
||||||
|
|
||||||
|
impl IIRState {
|
||||||
|
/// Lowpass biquad filter using cutoff and sampling frequencies. Taken from:
|
||||||
|
/// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `f` - Corner frequency, or 3dB cutoff frequency (in units of sample rate).
|
||||||
|
/// * `q` - Quality factor (1/sqrt(2) for critical).
|
||||||
|
/// * `k` - DC gain.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1.
|
||||||
|
pub fn lowpass(f: f64, q: f64, k: f64) -> IIRState {
|
||||||
|
let scale = (1i64 << 32) as f64;
|
||||||
|
let fcossin = cossin((f * scale) as u32 as i32);
|
||||||
|
let fcos = fcossin.0 as f64 / scale;
|
||||||
|
let fsin = fcossin.1 as f64 / scale;
|
||||||
|
let alpha = fsin / (2. * q);
|
||||||
|
// IIR uses Q2.30 fixed point
|
||||||
|
let a0 = (1. + alpha) / (1 << IIR::SHIFT) as f64;
|
||||||
|
let b0 = (k / 2. * (1. - fcos) / a0) as _;
|
||||||
|
let a1 = (2. * fcos / a0) as _;
|
||||||
|
let a2 = ((alpha - 1.) / a0) as _;
|
||||||
|
|
||||||
|
IIRState([b0, 2 * b0, b0, a1, a2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn macc(y0: i32, x: &[i32], a: &[i32], shift: u32) -> i32 {
|
fn macc(y0: i32, x: &[i32], a: &[i32], shift: u32) -> i32 {
|
||||||
// Rounding bias, half up
|
// Rounding bias, half up
|
||||||
let y0 = ((y0 as i64) << shift) + (1 << (shift - 1));
|
let y0 = ((y0 as i64) << shift) + (1 << (shift - 1));
|
||||||
|
@ -41,7 +41,7 @@ impl Lockin {
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
atan2,
|
atan2,
|
||||||
iir_int::{IIRState, IIR},
|
iir_int::IIRState,
|
||||||
lockin::Lockin,
|
lockin::Lockin,
|
||||||
rpll::RPLL,
|
rpll::RPLL,
|
||||||
testing::{isclose, max_error},
|
testing::{isclose, max_error},
|
||||||
@ -189,28 +189,6 @@ mod test {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lowpass biquad filter using cutoff and sampling frequencies. Taken from:
|
|
||||||
/// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
|
|
||||||
///
|
|
||||||
/// # Args
|
|
||||||
/// * `fc` - Corner frequency, or 3dB cutoff frequency (in units of sample rate).
|
|
||||||
/// * `q` - Quality factor (1/sqrt(2) for critical).
|
|
||||||
/// * `k` - DC gain.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 is set to -1.
|
|
||||||
fn lowpass_iir_coefficients(fc: f64, q: f64, k: f64) -> IIRState {
|
|
||||||
let f = 2. * PI * fc;
|
|
||||||
let a = f.sin() / (2. * q);
|
|
||||||
// IIR uses Q2.30 fixed point
|
|
||||||
let a0 = (1. + a) / (1 << IIR::SHIFT) as f64;
|
|
||||||
let b0 = (k / 2. * (1. - f.cos()) / a0).round() as _;
|
|
||||||
let a1 = (2. * f.cos() / a0).round() as _;
|
|
||||||
let a2 = ((a - 1.) / a0).round() as _;
|
|
||||||
|
|
||||||
IIRState([b0, 2 * b0, b0, a1, a2])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the maximum effect of input noise on the lock-in magnitude computation.
|
/// Compute the maximum effect of input noise on the lock-in magnitude computation.
|
||||||
///
|
///
|
||||||
/// The maximum effect of noise on the magnitude computation is given by:
|
/// The maximum effect of noise on the magnitude computation is given by:
|
||||||
@ -415,7 +393,7 @@ mod test {
|
|||||||
harmonic,
|
harmonic,
|
||||||
(demodulation_phase_offset / (2. * PI) * (1i64 << 32) as f64)
|
(demodulation_phase_offset / (2. * PI) * (1i64 << 32) as f64)
|
||||||
.round() as i32,
|
.round() as i32,
|
||||||
&lowpass_iir_coefficients(
|
&IIRState::lowpass(
|
||||||
corner_frequency,
|
corner_frequency,
|
||||||
1. / 2f64.sqrt(), // critical q
|
1. / 2f64.sqrt(), // critical q
|
||||||
2.,
|
2.,
|
||||||
|
@ -56,7 +56,7 @@ const APP: () = {
|
|||||||
let pll = RPLL::new(ADC_SAMPLE_TICKS_LOG2 + SAMPLE_BUFFER_SIZE_LOG2, 0);
|
let pll = RPLL::new(ADC_SAMPLE_TICKS_LOG2 + SAMPLE_BUFFER_SIZE_LOG2, 0);
|
||||||
|
|
||||||
let lockin = Lockin::new(
|
let lockin = Lockin::new(
|
||||||
&iir_int::IIRState::default(), // TODO: lowpass, expose
|
&iir_int::IIRState::lowpass(1e-3, 0.707, 2.), // TODO: expose
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enable ADC/DAC events
|
// Enable ADC/DAC events
|
||||||
@ -119,8 +119,8 @@ const APP: () = {
|
|||||||
|
|
||||||
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
let (pll_phase, pll_frequency) = c.resources.pll.update(
|
||||||
c.resources.timestamper.latest_timestamp().map(|t| t as i32),
|
c.resources.timestamper.latest_timestamp().map(|t| t as i32),
|
||||||
21, // relative PLL frequency bandwidth: 2**-21, TODO: expose
|
22, // relative PLL frequency bandwidth: 2**-22, TODO: expose
|
||||||
21, // relative PLL phase bandwidth: 2**-21, TODO: expose
|
22, // relative PLL phase bandwidth: 2**-22, TODO: expose
|
||||||
);
|
);
|
||||||
|
|
||||||
// Harmonic index of the LO: -1 to _de_modulate the fundamental
|
// Harmonic index of the LO: -1 to _de_modulate the fundamental
|
||||||
|
Loading…
Reference in New Issue
Block a user