use core::any::Any;
use miniconf::{Tree, leaf};
use num_traits::{AsPrimitive, Float, FloatConst};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
use crate::{
Build,
iir::{
BiquadClamp, Error,
coefficients::{Filter, Shape, Type},
pid::{Gains, Order, Pid, Units},
},
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ClampConfig<T>([T; 2]);
impl<T> Serialize for ClampConfig<T>
where
T: Serialize + Float,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
[
(self.0[0] != T::neg_infinity()).then_some(self.0[0]),
(self.0[1] != T::infinity()).then_some(self.0[1]),
]
.serialize(serializer)
}
}
impl<T> ClampConfig<T> {
pub const fn new(min: T, max: T) -> Self {
Self([min, max])
}
}
impl<T: Copy> ClampConfig<T> {
pub const fn bounds(&self) -> [T; 2] {
self.0
}
}
impl<T: Float> Default for ClampConfig<T> {
fn default() -> Self {
Self([T::neg_infinity(), T::infinity()])
}
}
impl<T: Float> ClampConfig<T> {
fn validate(&self) -> Result<(), Error> {
let [min, max] = self.0;
if !(min.is_finite() || min == T::neg_infinity())
|| !(max.is_finite() || max == T::infinity())
{
return Err(Error::NonFinite("output_clamp"));
}
if min > max {
return Err(Error::InvertedRange("output_clamp"));
}
Ok(())
}
}
impl<'de, T> Deserialize<'de> for ClampConfig<T>
where
T: Deserialize<'de> + Float,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bounds = <[Option<T>; 2]>::deserialize(deserializer)?;
let clamp = Self([
bounds[0].unwrap_or_else(T::neg_infinity),
bounds[1].unwrap_or_else(T::infinity),
]);
clamp.validate().map_err(serde::de::Error::custom)?;
Ok(clamp)
}
}
#[derive(Debug, Clone, Tree)]
#[tree(meta(doc, typename))]
pub struct BaConfig<T> {
#[tree(with=leaf, bounds(serialize="T: Serialize", deserialize="T: DeserializeOwned", any="T: Any"))]
pub ba: [[T; 3]; 2],
pub offset: T,
#[tree(with=leaf, bounds(serialize="T: Serialize + Float", deserialize="T: DeserializeOwned + Float", any="T: Any"))]
pub clamp: ClampConfig<T>,
}
impl<T: Float> Default for BaConfig<T> {
fn default() -> Self {
Self {
ba: [[T::zero(); 3], [T::one(), T::zero(), T::zero()]],
offset: T::zero(),
clamp: ClampConfig::default(),
}
}
}
#[derive(Clone, Debug, Tree)]
#[tree(meta(doc, typename))]
pub struct FilterConfig<T> {
#[tree(with=leaf)]
pub typ: Type,
pub frequency: T,
pub gain_db: T,
pub shelf_db: T,
#[tree(with=leaf, bounds(serialize="T: Serialize", deserialize="T: DeserializeOwned", any="T: Any"))]
pub shape: Shape<T>,
pub offset: T,
#[tree(with=leaf, bounds(serialize="T: Serialize + Float", deserialize="T: DeserializeOwned + Float", any="T: Any"))]
pub clamp: ClampConfig<T>,
}
impl<T: Float + FloatConst> Default for FilterConfig<T> {
fn default() -> Self {
Self {
typ: Type::default(),
frequency: T::zero(),
gain_db: T::zero(),
shelf_db: T::zero(),
shape: Shape::default(),
offset: T::zero(),
clamp: ClampConfig::default(),
}
}
}
#[derive(Clone, Debug, Tree, Default)]
pub struct GainsConfig<T> {
pub i2: T,
pub i: T,
pub p: T,
pub d: T,
pub d2: T,
}
impl<T> GainsConfig<T> {
pub const fn new(i2: T, i: T, p: T, d: T, d2: T) -> Self {
Self { i2, i, p, d, d2 }
}
}
impl<T: Copy> GainsConfig<T> {
pub const fn splat(value: T) -> Self {
Self::new(value, value, value, value, value)
}
}
impl<T: Copy> From<&GainsConfig<T>> for Gains<T> {
fn from(config: &GainsConfig<T>) -> Self {
Self::new([config.i2, config.i, config.p, config.d, config.d2])
}
}
#[derive(Clone, Debug, Tree)]
#[tree(meta(doc, typename))]
pub struct PidConfig<T> {
#[tree(with=leaf)]
pub order: Order,
pub gain: GainsConfig<T>,
pub limit: GainsConfig<T>,
pub setpoint: T,
#[tree(with=leaf, bounds(serialize="T: Serialize + Float", deserialize="T: DeserializeOwned + Float", any="T: Any"))]
pub clamp: ClampConfig<T>,
}
impl<T: Float + Default> Default for PidConfig<T> {
fn default() -> Self {
Self {
order: Order::default(),
gain: GainsConfig::default(),
limit: GainsConfig::splat(T::infinity()),
setpoint: T::zero(),
clamp: ClampConfig::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use miniconf::json_core;
#[test]
fn biquad_config_tag_conversion() {
let config = BiquadConfig::<f32>::try_from("Filter").unwrap();
assert_eq!(config.as_ref(), "Filter");
assert!(BiquadConfig::<f32>::try_from("Unknown").is_err());
}
#[test]
fn biquad_config_try_build_rejects_invalid_ba_config() {
let ba = BaConfig {
clamp: ClampConfig::new(1.0, 0.0),
..Default::default()
};
let config = BiquadConfig::<f32>::Ba(ba);
assert_eq!(
config.try_build(&Units::default()),
Err(Error::InvertedRange("output_clamp"))
);
}
#[test]
fn clamp_miniconf_leaf_requires_a_complete_valid_range() {
let mut config = BaConfig::<f32> {
clamp: ClampConfig::new(-1.0, 1.0),
..Default::default()
};
json_core::set(&mut config, "/clamp", br#"[-2.0,2.0]"#).unwrap();
assert_eq!(config.clamp, ClampConfig::new(-2.0, 2.0));
for value in [
br#"[-3.0]"#.as_slice(),
br#"[-3.0,3.0,4.0]"#,
br#"[3.0,-3.0]"#,
br#"{"min":-3.0,"max":3.0}"#,
] {
assert!(json_core::set(&mut config, "/clamp", value).is_err());
assert_eq!(config.clamp, ClampConfig::new(-2.0, 2.0));
}
json_core::set(&mut config, "/clamp", br#"[null,2.0]"#).unwrap();
assert_eq!(config.clamp, ClampConfig::new(f32::NEG_INFINITY, 2.0));
let mut data = [0; 64];
let len = json_core::get(&config, "/clamp", &mut data).unwrap();
assert_eq!(&data[..len], br#"[null,2.0]"#);
let mut copy = BaConfig::<f32>::default();
json_core::set(&mut copy, "/clamp", &data[..len]).unwrap();
assert_eq!(copy.clamp, config.clamp);
assert!(json_core::set(&mut copy, "/clamp/0", b"-1.0").is_err());
}
#[test]
fn raw_biquad_config_try_build_does_not_validate_units() {
let config = BiquadConfig::<f32>::Raw(Default::default());
let units = Units {
t: 0.0,
x: 0.0,
y: 0.0,
};
assert!(config.try_build(&units).is_ok());
}
#[test]
fn pid_null_gain_limit_is_unbounded() {
let mut pid = PidConfig::<f32>::default();
json_core::set(&mut pid, "/limit/i", b"null").unwrap();
let runtime = Pid::from(&pid);
let mut data = [0; 8];
let len = json_core::get(&pid, "/limit/i", &mut data).unwrap();
assert_eq!(&data[..len], b"null");
assert_eq!(
runtime.limit.value[crate::iir::pid::Action::I as usize],
f32::INFINITY
);
let config: BiquadConfig<f32> = BiquadConfig::Pid(pid);
assert!(config.try_build(&Units::default()).is_ok());
}
}
impl<T: Float> From<&PidConfig<T>> for Pid<T> {
fn from(config: &PidConfig<T>) -> Self {
let [min, max] = config.clamp.bounds();
let mut limit = Gains::from(&config.limit);
limit.value = limit
.value
.map(|value| if value.is_nan() { T::infinity() } else { value });
Self {
order: config.order,
gain: Gains::from(&config.gain),
limit,
setpoint: config.setpoint,
min,
max,
}
}
}
#[derive(Debug, Clone, Tree)]
#[tree(meta(doc = "Configurable representation of a biquad", typename))]
pub enum BiquadConfig<T, C = T, Y = T>
where
BaConfig<T>: Default,
PidConfig<T>: Default,
BiquadClamp<C, Y>: Default,
FilterConfig<T>: Default,
{
Ba(BaConfig<T>),
Raw(
#[tree(with=leaf, bounds(
serialize="C: Serialize, Y: Serialize",
deserialize="C: DeserializeOwned, Y: DeserializeOwned",
any="C: Any, Y: Any"))]
BiquadClamp<C, Y>,
),
Pid(PidConfig<T>),
Filter(FilterConfig<T>),
}
impl<T, C, Y> AsRef<str> for BiquadConfig<T, C, Y>
where
BaConfig<T>: Default,
PidConfig<T>: Default,
BiquadClamp<C, Y>: Default,
FilterConfig<T>: Default,
{
fn as_ref(&self) -> &str {
match self {
Self::Ba(_) => "Ba",
Self::Raw(_) => "Raw",
Self::Pid(_) => "Pid",
Self::Filter(_) => "Filter",
}
}
}
impl<'a, T, C, Y> TryFrom<&'a str> for BiquadConfig<T, C, Y>
where
BaConfig<T>: Default,
PidConfig<T>: Default,
BiquadClamp<C, Y>: Default,
FilterConfig<T>: Default,
{
type Error = ();
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
match value {
"Ba" => Ok(Self::Ba(Default::default())),
"Raw" => Ok(Self::Raw(Default::default())),
"Pid" => Ok(Self::Pid(Default::default())),
"Filter" => Ok(Self::Filter(Default::default())),
_ => Err(()),
}
}
}
impl<T, C, Y> Default for BiquadConfig<T, C, Y>
where
BaConfig<T>: Default,
PidConfig<T>: Default,
BiquadClamp<C, Y>: Default,
FilterConfig<T>: Default,
{
fn default() -> Self {
Self::Ba(Default::default())
}
}
fn check_offset<T: Float>(offset: T) -> Result<(), Error> {
if !offset.is_finite() {
return Err(Error::NonFinite("offset"));
}
Ok(())
}
fn check_units<T: Float>(units: &Units<T>, check_t: bool) -> Result<(), Error> {
for (name, value) in [("x", units.x), ("y", units.y)] {
if !value.is_finite() {
return Err(Error::NonFinite(name));
}
if value <= T::zero() {
return Err(Error::NonPositive(name));
}
}
if check_t {
if !units.t.is_finite() {
return Err(Error::NonFinite("t"));
}
if units.t <= T::zero() {
return Err(Error::NonPositive("t"));
}
}
Ok(())
}
impl<T, C, Y> BiquadConfig<T, C, Y>
where
BiquadClamp<C, Y>: Default + Clone,
Y: 'static + Copy,
T: 'static + Float + FloatConst + Default + AsPrimitive<Y>,
f32: AsPrimitive<T>,
Pid<T>: Build<BiquadClamp<C, Y>, Context = Units<T>>,
[[T; 3]; 2]: Into<BiquadClamp<C, Y>>,
{
pub fn build(&self, units: &Units<T>) -> BiquadClamp<C, Y> {
let yu = units.y.recip();
let yx = units.x * yu;
match self {
Self::Ba(ba) => {
let [min, max] = ba.clamp.bounds();
let mut bba = ba.ba;
bba[0] = bba[0].map(|b| b * yx);
let mut b: BiquadClamp<C, Y> = bba.into();
b.u = (ba.offset * yu).as_();
b.min = (min * yu).as_();
b.max = (max * yu).as_();
b
}
Self::Raw(raw) => raw.clone(),
Self::Pid(pid) => Pid::from(pid).build(units),
Self::Filter(filter) => {
let [min, max] = filter.clamp.bounds();
let mut f = Filter::default();
f.gain_db(filter.gain_db);
f.critical_frequency(filter.frequency * units.t);
f.shelf_db(filter.shelf_db);
f.set_shape(filter.shape);
let mut ba = f.build(filter.typ);
ba[0] = ba[0].map(|b| b * yx);
let mut b: BiquadClamp<C, Y> = ba.into();
b.u = (filter.offset * yu).as_();
b.min = (min * yu).as_();
b.max = (max * yu).as_();
b
}
}
}
pub fn try_build(&self, units: &Units<T>) -> Result<BiquadClamp<C, Y>, Error> {
match self {
Self::Ba(ba) => {
check_units(units, false)?;
check_offset(ba.offset)?;
ba.clamp.validate()?;
let [min, max] = ba.clamp.bounds();
let yu = units.y.recip();
let yx = units.x * yu;
for row in ba.ba {
for coefficient in row {
if !coefficient.is_finite() {
return Err(Error::NonFinite("ba"));
}
}
}
let mut bba = ba.ba;
bba[0] = bba[0].map(|b| b * yx);
let mut b: BiquadClamp<C, Y> = bba.into();
b.u = (ba.offset * yu).as_();
b.min = (min * yu).as_();
b.max = (max * yu).as_();
Ok(b)
}
Self::Raw(raw) => Ok(raw.clone()),
Self::Pid(pid) => {
pid.clamp.validate()?;
Pid::from(pid).try_build(units)
}
Self::Filter(filter) => {
check_units(units, true)?;
check_offset(filter.offset)?;
filter.clamp.validate()?;
let [min, max] = filter.clamp.bounds();
let yu = units.y.recip();
let yx = units.x * yu;
let mut f = Filter::default();
f.gain_db(filter.gain_db);
f.critical_frequency(filter.frequency * units.t);
f.shelf_db(filter.shelf_db);
f.set_shape(filter.shape);
let mut ba = f.try_build(filter.typ)?;
ba[0] = ba[0].map(|b| b * yx);
let mut b: BiquadClamp<C, Y> = ba.into();
b.u = (filter.offset * yu).as_();
b.min = (min * yu).as_();
b.max = (max * yu).as_();
Ok(b)
}
}
}
}