Skip to content
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [UNRELEASED](https://github.com/quartiq/idsp/compare/v0.20.0..HEAD) - DATE

### Changed

* `Pid`: use `Units` to scale and `Build` trait to convert to `BiquadClamp`

## [0.20.0](https://github.com/quartiq/idsp/compare/v0.19.0..v0.20.0) - 2026-01-13

### Changed
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ One comprehensive user for these algorithms is [Stabilizer](https://github.com/q

[`atan2()`] returns a phase given a complex signal (a pair of in-phase/`x`/cosine and quadrature/`y`/sine). The RMS phase error is less than 5e-6 rad, max error is less than 1.2e-5 rad, i.e. 20.5 bit RMS, 19.1 bit max accuracy. The bias is minimal.

### CORDIC

[`sqrt_atan2`] etc: complete family of CORDIC mode reference implementations.

## PLL, RPLL

[`PLL`], [`RPLL`]: High accuracy, zero-assumption, fully robust, forward and reciprocal PLLs with dynamically adjustable time constant and arbitrary (in the Nyquist sampling sense) capture range, and noise shaping.
Expand Down Expand Up @@ -77,15 +81,20 @@ The benchmarks and results comparing `idsp` and `biquad-rs` are in `tests/embedd
bandpass, and notch filtering of a signal.
[`iir::normal`] is a Normal Form IIR filter for narrowband applications.
[`iir::wdf`] has wave digital allpass filters that can be combined in a coupled [`dsp_process::Pair`].
[`Cic`] generic cascaded integrator comb lowpass reference implementation.

## `Lowpass`, `Lockin`

[`Lowpass`], [`Lockin`] are fast, infinitely cascadable, first- and second-order lowpass and the corresponding integration into a lockin amplifier algorithm.

## FIR filters

Type 1-4 linear phase FIR filters, [`hbf::HbfDec`], [`hbf::HbfInt`], [`hbf::HbfDec32`], [`hbf::HbfInt32`] etc:
Fast `f32` symmetric FIR filters, also optimized half-band filters, half-band filter decimators and integators and cascades.
[`hbf::EvenSymmetric`], [`hbf::OddAntiSymmetric`], [`hbf::EvenAntiSymmetric`], [`hbf::OddSymmetric`]: Type I-IV linear phase FIR filters.

[`hbf::HbfDec`], [`hbf::HbfInt`]:
Fast symmetric FIR filters, optimized half-band filters, half-band filter decimators and integators and cascades.

[`hbf::HbfDec32`], [`hbf::HbfInt32`] etc: HBF cascades with known-good coefficients for rate changes 2, 4, 8, 16, and 32.
These are used in [`stabilizer-stream`](https://github.com/quartiq/stabilizer-stream) for online PSD calculation for
arbitrarily low offset frequencies.

Expand Down
14 changes: 14 additions & 0 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Build a value using a context
///
/// This is similar to the `Into` trait but allows lossy
/// (rounding, clamping, quantization) and non-value-preserving conversions.
/// The only semantic constraint is that the conversion is obvious and unambiguous.
pub trait Build<C> {
/// The context of the convesion.
///
/// E.g. units.
type Context;

/// Perform the conversion
fn build(&self, ctx: &Self::Context) -> C;
}
4 changes: 2 additions & 2 deletions src/cossin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include!(concat!(env!("OUT_DIR"), "/cossin_table.rs"));
/// <https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py>
///
/// # Arguments
/// * `phase` - 32-bit phase where `i32::MIN` is -π and `i32::MAX` is π
/// * `phase` - 32-bit phase where `i32::MIN` is -π and `i32::MAX` is (near) π
///
/// # Returns
/// The cos and sin values of the provided phase as a `(i32, i32)`
Expand Down Expand Up @@ -42,7 +42,7 @@ pub fn cossin(mut phase: i32) -> (i32, i32) {

// 1/2 < cos(0 <= x <= pi/4) <= 1: Shift the cos
// values and scale the sine values as encoded in the LUT.
let mut cos = (lookup & 0xffff) as i32 + (1 << 16);
let mut cos = lookup as u16 as i32 + (1 << 16);
let mut sin = (lookup >> 16) as i32;

let dcos = (sin * dphi) >> COSSIN_DEPTH;
Expand Down
63 changes: 37 additions & 26 deletions src/hbf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ where
.zip(c.iter())
.map(|((old, new), tap)| (if SYM { *new + *old } else { *new - *old }) * *tap)
.sum();
if ODD { xc + x[M] } else { xc }
if ODD && SYM { xc + x[M] } else { xc }
})
}

Expand All @@ -76,6 +76,11 @@ macro_rules! type_fir {
impl<T, const M: usize> $name<[T; M]> {
/// Response length/number of taps minus one
pub const LEN: usize = 2 * M - 1 + $odd as usize;

#[allow(unused)]
const fn len(&self) -> usize {
Self::LEN
}
}

impl<
Expand Down Expand Up @@ -355,26 +360,25 @@ pub const HBF_CASCADE_BLOCK: usize = 1 << 5;
///
/// See [HBF_TAPS] and [HBF_DEC_CASCADE].
/// Supports rate changes are power of two up to 32.
pub type HbfDec2 =
HbfDec<[f32; EvenSymmetric::<[f32; HBF_TAPS.0.0.len()]>::LEN + HBF_CASCADE_BLOCK]>;
pub type HbfDec2 = HbfDec<[f32; HBF_TAPS.0.len() + HBF_CASCADE_BLOCK]>;
/// HBF Decimate-by-4 cascade state
pub type HbfDec4 = (
HbfDec<[f32; EvenSymmetric::<[f32; HBF_TAPS.1.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 1)]>,
HbfDec<[f32; HBF_TAPS.1.len() + (HBF_CASCADE_BLOCK << 1)]>,
HbfDec2,
);
/// HBF Decimate-by-8 cascade state
pub type HbfDec8 = (
HbfDec<[f32; EvenSymmetric::<[f32; HBF_TAPS.2.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 2)]>,
HbfDec<[f32; HBF_TAPS.2.len() + (HBF_CASCADE_BLOCK << 2)]>,
HbfDec4,
);
/// HBF Decimate-by-16 cascade state
pub type HbfDec16 = (
HbfDec<[f32; EvenSymmetric::<[f32; HBF_TAPS.3.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 3)]>,
HbfDec<[f32; HBF_TAPS.3.len() + (HBF_CASCADE_BLOCK << 3)]>,
HbfDec8,
);
/// HBF Decimate-by-32 cascade state
pub type HbfDec32 = (
HbfDec<[f32; EvenSymmetric::<[f32; HBF_TAPS.4.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 4)]>,
HbfDec<[f32; HBF_TAPS.4.len() + (HBF_CASCADE_BLOCK << 4)]>,
HbfDec16,
);

Expand Down Expand Up @@ -418,23 +422,27 @@ pub const HBF_DEC_CASCADE: HbfDecCascade = Major::new((

/// Response length, effective number of taps
pub const fn hbf_dec_response_length(depth: usize) -> usize {
assert!(depth < 5);
assert!(depth <= 5);
let mut n = 0;
if depth > 0 {
if depth > 4 {
n /= 2;
n += EvenSymmetric::<[f32; HBF_TAPS.3.0.len()]>::LEN;
n += HBF_TAPS.4.len();
}
if depth > 1 {
if depth > 3 {
n /= 2;
n += EvenSymmetric::<[f32; HBF_TAPS.2.0.len()]>::LEN;
n += HBF_TAPS.3.len();
}
if depth > 2 {
n /= 2;
n += EvenSymmetric::<[f32; HBF_TAPS.1.0.len()]>::LEN;
n += HBF_TAPS.2.len();
}
if depth > 3 {
if depth > 1 {
n /= 2;
n += HBF_TAPS.1.len();
}
if depth > 0 {
n /= 2;
n += EvenSymmetric::<[f32; HBF_TAPS.0.0.len()]>::LEN;
n += HBF_TAPS.0.len();
}
n
}
Expand All @@ -443,27 +451,26 @@ pub const fn hbf_dec_response_length(depth: usize) -> usize {
///
/// See [HBF_TAPS] and [HBF_INT_CASCADE].
/// Supports rate changes are power of two up to 32.
pub type HbfInt2 =
HbfInt<[f32; EvenSymmetric::<[f32; HBF_TAPS.0.0.len()]>::LEN + HBF_CASCADE_BLOCK]>;
pub type HbfInt2 = HbfInt<[f32; HBF_TAPS.0.len() + HBF_CASCADE_BLOCK]>;
/// HBF interpolate-by-4 cascade state
pub type HbfInt4 = (
HbfInt2,
HbfInt<[f32; EvenSymmetric::<[f32; HBF_TAPS.1.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 1)]>,
HbfInt<[f32; HBF_TAPS.1.len() + (HBF_CASCADE_BLOCK << 1)]>,
);
/// HBF interpolate-by-8 cascade state
pub type HbfInt8 = (
HbfInt4,
HbfInt<[f32; EvenSymmetric::<[f32; HBF_TAPS.2.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 2)]>,
HbfInt<[f32; HBF_TAPS.2.len() + (HBF_CASCADE_BLOCK << 2)]>,
);
/// HBF interpolate-by-16 cascade state
pub type HbfInt16 = (
HbfInt8,
HbfInt<[f32; EvenSymmetric::<[f32; HBF_TAPS.3.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 3)]>,
HbfInt<[f32; HBF_TAPS.3.len() + (HBF_CASCADE_BLOCK << 3)]>,
);
/// HBF interpolate-by-32 cascade state
pub type HbfInt32 = (
HbfInt16,
HbfInt<[f32; EvenSymmetric::<[f32; HBF_TAPS.4.0.len()]>::LEN + (HBF_CASCADE_BLOCK << 4)]>,
HbfInt<[f32; HBF_TAPS.4.len() + (HBF_CASCADE_BLOCK << 4)]>,
);

type HbfIntCascade<const B: usize = HBF_CASCADE_BLOCK> = Major<
Expand Down Expand Up @@ -506,22 +513,26 @@ pub const HBF_INT_CASCADE: HbfIntCascade = Major::new((

/// Response length, effective number of taps
pub const fn hbf_int_response_length(depth: usize) -> usize {
assert!(depth < 5);
assert!(depth <= 5);
let mut n = 0;
if depth > 0 {
n += EvenSymmetric::<[f32; HBF_TAPS.0.0.len()]>::LEN;
n += HBF_TAPS.0.len();
n *= 2;
}
if depth > 1 {
n += EvenSymmetric::<[f32; HBF_TAPS.1.0.len()]>::LEN;
n += HBF_TAPS.1.len();
n *= 2;
}
if depth > 2 {
n += EvenSymmetric::<[f32; HBF_TAPS.2.0.len()]>::LEN;
n += HBF_TAPS.2.len();
n *= 2;
}
if depth > 3 {
n += EvenSymmetric::<[f32; HBF_TAPS.3.0.len()]>::LEN;
n += HBF_TAPS.3.len();
n *= 2;
}
if depth > 4 {
n += HBF_TAPS.4.len();
n *= 2;
}
n
Expand Down
Loading