From ea6da728d23997a08bb9d4bfea9313c793f45e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 10 Sep 2025 10:36:12 +0200 Subject: [PATCH 1/6] bump miniconf: lower Leaf<> usage --- Cargo.toml | 6 +++ src/iir/pid.rs | 47 ++++++++++--------- src/iir/repr.rs | 122 ++++++++++++++++++------------------------------ 3 files changed, 76 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5c9341d..9522e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,9 @@ std = [] [profile.release] debug = 1 + +[patch.crates-io] +# miniconf = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } +# miniconf_derive = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } +miniconf = {path="../miniconf/miniconf"} +miniconf_derive = {path="../miniconf/miniconf_derive"} diff --git a/src/iir/pid.rs b/src/iir/pid.rs index 06a090a..094ba3f 100644 --- a/src/iir/pid.rs +++ b/src/iir/pid.rs @@ -1,4 +1,4 @@ -use miniconf::{Leaf, Tree}; +use miniconf::Tree; use num_traits::{AsPrimitive, Float}; use serde::{Deserialize, Serialize}; @@ -94,7 +94,7 @@ impl PidBuilder { /// * `input` are input (`x`) units /// * `time` are sample period units, e.g. SI seconds /// * `order` is the action order: the frequency exponent - /// (`-1` for integrating, `0` for proportional, etc.) + /// (`-1` for integrating, `0` for proportional, etc.) /// /// Gains are accurate in the low frequency limit. Towards Nyquist, the /// frequency response is warped. @@ -238,23 +238,23 @@ pub struct Gain { /// /// See [`Action`] for indices. #[tree(skip)] - pub value: [Leaf; 5], - #[tree(defer = "self.value[Action::I2 as usize]", typ = "Leaf")] + pub value: [T; 5], + #[tree(defer = "self.value[Action::I2 as usize]", typ = "T")] i2: (), - #[tree(defer = "self.value[Action::I as usize]", typ = "Leaf")] + #[tree(defer = "self.value[Action::I as usize]", typ = "T")] i: (), - #[tree(defer = "self.value[Action::P as usize]", typ = "Leaf")] + #[tree(defer = "self.value[Action::P as usize]", typ = "T")] p: (), - #[tree(defer = "self.value[Action::D as usize]", typ = "Leaf")] + #[tree(defer = "self.value[Action::D as usize]", typ = "T")] d: (), - #[tree(defer = "self.value[Action::D2 as usize]", typ = "Leaf")] + #[tree(defer = "self.value[Action::D2 as usize]", typ = "T")] d2: (), } impl Gain { fn new(value: T) -> Self { Self { - value: [Leaf(value); 5], + value: [value; 5], i2: (), i: (), p: (), @@ -268,7 +268,8 @@ impl Gain { #[derive(Clone, Debug, Tree)] pub struct Pid { /// Feedback term order - pub order: Leaf, + #[tree(with=miniconf::leaf)] + pub order: Order, /// Gain /// /// * Sequence: [I², I, P, D, D²] @@ -287,26 +288,26 @@ pub struct Pid { /// Setpoint /// /// Units: input - pub setpoint: Leaf, + pub setpoint: T, /// Output lower limit /// /// Units: output - pub min: Leaf, + pub min: T, /// Output upper limit /// /// Units: output - pub max: Leaf, + pub max: T, } impl Default for Pid { fn default() -> Self { Self { - order: Leaf(Order::default()), + order: Order::default(), gain: Gain::new(T::zero()), limit: Gain::new(T::infinity()), - setpoint: Leaf(T::zero()), - min: Leaf(T::neg_infinity()), - max: Leaf(T::infinity()), + setpoint: T::zero(), + min: T::neg_infinity(), + max: T::infinity(), } } } @@ -321,22 +322,22 @@ impl Pid { T: AsPrimitive + AsPrimitive, I: Float + 'static + AsPrimitive, { - let p = *self.gain.value[Action::P as usize]; + let p = self.gain.value[Action::P as usize]; let mut biquad: Biquad = PidBuilder:: { gain: self.gain.value.map(|g| (b_scale * g.copysign(p)).as_()), limit: self.limit.value.map(|l| { // infinite gain limit is meaningful but json can only do null/nan - let l = if l.is_nan() { T::infinity() } else { *l }; + let l = if l.is_nan() { T::infinity() } else { l }; (b_scale * l.copysign(p)).as_() }), period: period.as_(), - order: *self.order, + order: self.order, } .build() .into(); - biquad.set_input_offset((-*self.setpoint * y_scale).as_()); - biquad.set_min((*self.min * y_scale).as_()); - biquad.set_max((*self.max * y_scale).as_()); + biquad.set_input_offset((-self.setpoint * y_scale).as_()); + biquad.set_min((self.min * y_scale).as_()); + biquad.set_max((self.max * y_scale).as_()); biquad } } diff --git a/src/iir/repr.rs b/src/iir/repr.rs index 67e1f3e..57ec701 100644 --- a/src/iir/repr.rs +++ b/src/iir/repr.rs @@ -10,26 +10,23 @@ use crate::{ /// Floating point BA coefficients before quantization #[derive(Debug, Clone, Tree)] pub struct Ba { - /// Coefficient array: [[b0, b1, b2], [a0, a1, a2]] + /// Coefficient array: [[b0, b1, b2q], [a0, a1, a2]] pub ba: Leaf<[[T; 3]; 2]>, /// Summing junction offset - pub u: Leaf, + pub u: T, /// Output lower limit - pub min: Leaf, + pub min: T, /// Output upper limit - pub max: Leaf, + pub max: T, } -impl Default for Ba -where - T: Float, -{ +impl Default for Ba { fn default() -> Self { Self { ba: Leaf([[T::zero(); 3], [T::one(), T::zero(), T::zero()]]), - u: Leaf(T::zero()), - min: Leaf(T::neg_infinity()), - max: Leaf(T::infinity()), + u: T::zero(), + min: T::neg_infinity(), + max: T::infinity(), } } } @@ -62,41 +59,54 @@ pub enum Typ { #[derive(Clone, Debug, Tree)] pub struct FilterRepr { /// Filter style - typ: Leaf, + #[tree(with=miniconf::leaf)] + typ: Typ, /// Angular critical frequency (in units of sampling frequency) /// Corner frequency, or 3dB cutoff frequency, - frequency: Leaf, + frequency: T, /// Passband gain - gain: Leaf, + gain: T, /// Shelf gain (only for peaking, lowshelf, highshelf) /// Relative to passband gain - shelf: Leaf, + shelf: T, /// Q/Bandwidth/Slope shape: Leaf>, /// Summing junction offset - offset: Leaf, + offset: T, /// Lower output limit - min: Leaf, + min: T, /// Upper output limit - max: Leaf, + max: T, } impl Default for FilterRepr { fn default() -> Self { Self { - typ: Leaf(Typ::default()), - frequency: Leaf(T::zero()), - gain: Leaf(T::one()), - shelf: Leaf(T::one()), + typ: Typ::default(), + frequency: T::zero(), + gain: T::one(), + shelf: T::one(), shape: Leaf(Shape::default()), - offset: Leaf(T::zero()), - min: Leaf(T::neg_infinity()), - max: Leaf(T::infinity()), + offset: T::zero(), + min: T::neg_infinity(), + max: T::infinity(), } } } /// Representation of Biquad +/// +/// `miniconf::Tree` can be used like this: +/// +/// ``` +/// use miniconf::{Tree, str_leaf}; +/// #[derive(Tree)] +/// struct Foo { +/// #[tree(typ="&str", with=str_leaf, defer=self.repr)] +/// typ: (), +/// repr: idsp::iir::BiquadRepr, +/// } +/// ``` #[derive( Debug, Clone, @@ -105,6 +115,7 @@ impl Default for FilterRepr { strum::AsRefStr, strum::FromRepr, strum::EnumDiscriminants, + strum::IntoStaticStr, )] #[strum_discriminants(derive(serde::Serialize, serde::Deserialize))] pub enum BiquadRepr @@ -122,47 +133,6 @@ where Filter(FilterRepr), } -impl BiquadRepr -where - C: Coefficient, - T: Float + FloatConst, -{ - /// `TreeSerialize` for the discriminant - /// - /// Use this through a leaf node: - /// - /// ```ignore - /// #[tree(typ="Leaf", rename="typ", - /// with(serialize=self.repr.tag_serialize, deserialize=self.repr.tag_deserialize), - /// deny(ref_any="deny", mut_any="deny"))] - /// _tag: (), - /// repr: iir::BiquadRepr, - /// ``` - pub fn tag_serialize( - &self, - keys: K, - ser: S, - ) -> Result> { - miniconf::TreeSerialize::serialize_by_key( - &Leaf(BiquadReprDiscriminants::from(self)), - keys, - ser, - ) - } - - /// `TreeDeserialize` for the discriminant - pub fn tag_deserialize<'de, K: miniconf::Keys, D: serde::Deserializer<'de>>( - &mut self, - keys: K, - de: D, - ) -> Result<(), miniconf::Error> { - let mut v = Leaf(BiquadReprDiscriminants::from(&*self)); - miniconf::TreeDeserialize::deserialize_by_key(&mut v, keys, de)?; - *self = BiquadRepr::from_repr(*v as _).unwrap(); - Ok(()) - } -} - impl Default for BiquadRepr where C: Coefficient, @@ -199,20 +169,20 @@ where match self { Self::Ba(ba) => { let mut b = Biquad::from(&[ba.ba[0].map(|b| b * b_scale), ba.ba[1]]); - b.set_u((*ba.u * y_scale).as_()); - b.set_min((*ba.min * y_scale).as_()); - b.set_max((*ba.max * y_scale).as_()); + b.set_u((ba.u * y_scale).as_()); + b.set_min((ba.min * y_scale).as_()); + b.set_max((ba.max * y_scale).as_()); b } Self::Raw(Leaf(raw)) => raw.clone(), Self::Pid(pid) => pid.build::<_, I>(period, b_scale, y_scale), Self::Filter(filter) => { let mut f = crate::iir::Filter::default(); - f.gain_db(*filter.gain); - f.critical_frequency(*filter.frequency * period); - f.shelf_db(*filter.shelf); + f.gain_db(filter.gain); + f.critical_frequency(filter.frequency * period); + f.shelf_db(filter.shelf); f.set_shape(*filter.shape); - let mut ba = match *filter.typ { + let mut ba = match filter.typ { Typ::Lowpass => f.lowpass(), Typ::Highpass => f.highpass(), Typ::Allpass => f.allpass(), @@ -225,9 +195,9 @@ where }; ba[0] = ba[0].map(|b| b * b_scale); let mut b = Biquad::from(&ba); - b.set_u((*filter.offset * y_scale).as_()); - b.set_min((*filter.min * y_scale).as_()); - b.set_max((*filter.max * y_scale).as_()); + b.set_u((filter.offset * y_scale).as_()); + b.set_min((filter.min * y_scale).as_()); + b.set_max((filter.max * y_scale).as_()); b } } From 45341e1c7c998eb18591014bdb847dee77e1434a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 17 Sep 2025 21:54:36 +0200 Subject: [PATCH 2/6] meta doc --- src/iir/pid.rs | 6 ++++++ src/iir/repr.rs | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/iir/pid.rs b/src/iir/pid.rs index 094ba3f..0664a08 100644 --- a/src/iir/pid.rs +++ b/src/iir/pid.rs @@ -239,14 +239,19 @@ pub struct Gain { /// See [`Action`] for indices. #[tree(skip)] pub value: [T; 5], + /// Double integral #[tree(defer = "self.value[Action::I2 as usize]", typ = "T")] i2: (), + /// Integral #[tree(defer = "self.value[Action::I as usize]", typ = "T")] i: (), + /// Proportional #[tree(defer = "self.value[Action::P as usize]", typ = "T")] p: (), + /// Derivative #[tree(defer = "self.value[Action::D as usize]", typ = "T")] d: (), + /// Double derivative #[tree(defer = "self.value[Action::D2 as usize]", typ = "T")] d2: (), } @@ -266,6 +271,7 @@ impl Gain { /// PID Controller parameters #[derive(Clone, Debug, Tree)] +#[tree(meta(doc, typename))] pub struct Pid { /// Feedback term order #[tree(with=miniconf::leaf)] diff --git a/src/iir/repr.rs b/src/iir/repr.rs index 57ec701..132b24c 100644 --- a/src/iir/repr.rs +++ b/src/iir/repr.rs @@ -9,6 +9,7 @@ use crate::{ /// Floating point BA coefficients before quantization #[derive(Debug, Clone, Tree)] +#[tree(meta(doc, typename))] pub struct Ba { /// Coefficient array: [[b0, b1, b2q], [a0, a1, a2]] pub ba: Leaf<[[T; 3]; 2]>, @@ -57,6 +58,7 @@ pub enum Typ { /// Standard biquad parametrizations #[derive(Clone, Debug, Tree)] +#[tree(meta(doc, typename))] pub struct FilterRepr { /// Filter style #[tree(with=miniconf::leaf)] @@ -117,7 +119,8 @@ impl Default for FilterRepr { strum::EnumDiscriminants, strum::IntoStaticStr, )] -#[strum_discriminants(derive(serde::Serialize, serde::Deserialize))] +#[strum_discriminants(derive(serde::Serialize, serde::Deserialize), allow(missing_docs))] +#[tree(meta(doc, typename))] pub enum BiquadRepr where C: Coefficient, @@ -162,7 +165,7 @@ where pub fn build(&self, period: T, b_scale: T, y_scale: T) -> Biquad where T: AsPrimitive, - I: Float + 'static + AsPrimitive, + I: Float + AsPrimitive, C: AsPrimitive, f32: AsPrimitive, { From 312ef6f470771c0b9a137ebdddbf3c3a31d959cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 18 Sep 2025 10:11:04 +0200 Subject: [PATCH 3/6] miniconf: reduce Leaf --- src/iir/repr.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/iir/repr.rs b/src/iir/repr.rs index 132b24c..07a3e06 100644 --- a/src/iir/repr.rs +++ b/src/iir/repr.rs @@ -1,4 +1,5 @@ -use miniconf::{Leaf, Tree}; +use core::any::Any; +use miniconf::Tree; use num_traits::{AsPrimitive, Float, FloatConst}; use serde::{Deserialize, Serialize}; @@ -12,7 +13,8 @@ use crate::{ #[tree(meta(doc, typename))] pub struct Ba { /// Coefficient array: [[b0, b1, b2q], [a0, a1, a2]] - pub ba: Leaf<[[T; 3]; 2]>, + #[tree(with=miniconf::leaf, bounds(serialize="T: Serialize", deserialize="T: Deserialize<'de>", any="T: Any"))] + pub ba: [[T; 3]; 2], /// Summing junction offset pub u: T, /// Output lower limit @@ -24,7 +26,7 @@ pub struct Ba { impl Default for Ba { fn default() -> Self { Self { - ba: Leaf([[T::zero(); 3], [T::one(), T::zero(), T::zero()]]), + ba: [[T::zero(); 3], [T::one(), T::zero(), T::zero()]], u: T::zero(), min: T::neg_infinity(), max: T::infinity(), @@ -72,7 +74,8 @@ pub struct FilterRepr { /// Relative to passband gain shelf: T, /// Q/Bandwidth/Slope - shape: Leaf>, + #[tree(with=miniconf::leaf, bounds(serialize="T: Serialize", deserialize="T: Deserialize<'de>", any="T: Any"))] + shape: Shape, /// Summing junction offset offset: T, /// Lower output limit @@ -88,7 +91,7 @@ impl Default for FilterRepr { frequency: T::zero(), gain: T::one(), shelf: T::one(), - shape: Leaf(Shape::default()), + shape: Shape::default(), offset: T::zero(), min: T::neg_infinity(), max: T::infinity(), @@ -120,7 +123,7 @@ impl Default for FilterRepr { strum::IntoStaticStr, )] #[strum_discriminants(derive(serde::Serialize, serde::Deserialize), allow(missing_docs))] -#[tree(meta(doc, typename))] +#[tree(meta(doc = "Representation of Biquad", typename))] pub enum BiquadRepr where C: Coefficient, @@ -129,7 +132,10 @@ where /// Normalized SI unit coefficients Ba(Ba), /// Raw, unscaled, possibly fixed point machine unit coefficients - Raw(Leaf>), + Raw( + #[tree(with=miniconf::leaf, bounds(serialize="C: Serialize", deserialize="C: Deserialize<'de>", any="C: Any"))] + Biquad, + ), /// A PID Pid(Pid), /// Standard biquad filters: Notch, Lowpass, Highpass, Shelf etc @@ -177,14 +183,14 @@ where b.set_max((ba.max * y_scale).as_()); b } - Self::Raw(Leaf(raw)) => raw.clone(), + Self::Raw(raw) => raw.clone(), Self::Pid(pid) => pid.build::<_, I>(period, b_scale, y_scale), Self::Filter(filter) => { let mut f = crate::iir::Filter::default(); f.gain_db(filter.gain); f.critical_frequency(filter.frequency * period); f.shelf_db(filter.shelf); - f.set_shape(*filter.shape); + f.set_shape(filter.shape); let mut ba = match filter.typ { Typ::Lowpass => f.lowpass(), Typ::Highpass => f.highpass(), From 24ff0c350a6b0c6bbed67be820c4dd4d24d24ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 18 Sep 2025 18:19:27 +0200 Subject: [PATCH 4/6] miniconf: docs, tree --- src/iir/biquad.rs | 10 +++++++++- src/iir/pid.rs | 2 +- src/iir/repr.rs | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/iir/biquad.rs b/src/iir/biquad.rs index b087be6..66b8ad2 100644 --- a/src/iir/biquad.rs +++ b/src/iir/biquad.rs @@ -1,3 +1,4 @@ +use miniconf::Tree; use num_traits::{AsPrimitive, Float}; use serde::{Deserialize, Serialize}; @@ -100,11 +101,18 @@ use crate::Coefficient; /// coefficients/offset sets. /// * Cascading multiple IIR filters allows stable and robust /// implementation of transfer functions beyond biquadratic terms. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Tree)] pub struct Biquad { + /// Flattened coefficients `[b0, b1, b2, a1, a2]` + /// + /// The normalization `a0` is determined by the `Coefficient` implementation of the + /// underlying type. ba: [T; 5], + /// Summing junction offset u: T, + /// Summing junction lower limit min: T, + /// Summing junction upper limit max: T, } diff --git a/src/iir/pid.rs b/src/iir/pid.rs index 0664a08..86075fe 100644 --- a/src/iir/pid.rs +++ b/src/iir/pid.rs @@ -197,7 +197,7 @@ impl PidBuilder { .rev() { gl[0] = *gain * z; - gl[1] = if i == Action::P as _ { + gl[1] = if i == Action::P as usize { T::one() } else { gl[0] / *limit diff --git a/src/iir/repr.rs b/src/iir/repr.rs index 07a3e06..70128f1 100644 --- a/src/iir/repr.rs +++ b/src/iir/repr.rs @@ -12,7 +12,7 @@ use crate::{ #[derive(Debug, Clone, Tree)] #[tree(meta(doc, typename))] pub struct Ba { - /// Coefficient array: [[b0, b1, b2q], [a0, a1, a2]] + /// Coefficient array: [[b0, b1, b2], [a0, a1, a2]] #[tree(with=miniconf::leaf, bounds(serialize="T: Serialize", deserialize="T: Deserialize<'de>", any="T: Any"))] pub ba: [[T; 3]; 2], /// Summing junction offset From bf1119a7cc40e9d96e55982e529aee1685751ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 23 Sep 2025 12:50:03 +0200 Subject: [PATCH 5/6] miniconf: use git --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9522e37..b134824 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ std = [] debug = 1 [patch.crates-io] -# miniconf = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } -# miniconf_derive = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } -miniconf = {path="../miniconf/miniconf"} -miniconf_derive = {path="../miniconf/miniconf_derive"} +miniconf = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } +miniconf_derive = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } +#miniconf = { path = "../miniconf/miniconf" } +#miniconf_derive = { path = "../miniconf/miniconf_derive" } From e5169750790784df6af7b8de9547be825b221557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 23 Sep 2025 18:09:40 +0200 Subject: [PATCH 6/6] deps: miniconf master --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b134824..08c4afc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ std = [] debug = 1 [patch.crates-io] -miniconf = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } -miniconf_derive = { git = "https://github.com/quartiq/miniconf.git", branch = "tree-schema" } +miniconf = { git = "https://github.com/quartiq/miniconf.git" } +miniconf_derive = { git = "https://github.com/quartiq/miniconf.git" } #miniconf = { path = "../miniconf/miniconf" } #miniconf_derive = { path = "../miniconf/miniconf_derive" }