use serde::{Deserialize, Serialize};
use ts_rs::TS;
pub type Peq = (f64, f64, f64);
pub type Allpass = (f64, f64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export)]
pub enum BandType {
#[serde(rename = "LR12")]
Lr12,
#[serde(rename = "LR24")]
Lr24,
#[serde(rename = "LR48")]
Lr48,
#[serde(rename = "BW12")]
Bw12,
#[serde(rename = "BW24")]
Bw24,
#[serde(rename = "BW48")]
Bw48,
}
impl BandType {
pub fn name(self) -> &'static str {
match self {
Self::Lr12 => "LR12",
Self::Lr24 => "LR24",
Self::Lr48 => "LR48",
Self::Bw12 => "BW12",
Self::Bw24 => "BW24",
Self::Bw48 => "BW48",
}
}
pub fn from_name(name: &str) -> Self {
match name {
"LR12" => Self::Lr12,
"LR48" => Self::Lr48,
"BW12" => Self::Bw12,
"BW24" => Self::Bw24,
_ => Self::Lr24,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MainChannel {
pub role: String,
pub input_peq: Vec<Peq>,
pub fir: Vec<f64>,
pub gain_db: f64,
pub delay_ms: f64,
pub xo_hz: Option<f64>,
pub top_peq: Vec<Peq>,
pub align_delay_ms: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct SubChannel {
pub role: String,
pub gain_db: f64,
pub delay_ms: f64,
pub polarity: i8,
pub allpass: Vec<Allpass>,
pub hp_hz: f64,
pub hp_type: BandType,
pub lp_hz: f64,
pub lp_type: BandType,
pub peq: Vec<Peq>,
pub mode: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct SignalChain {
pub sample_rate: u32,
pub mains: Vec<MainChannel>,
pub subs: Vec<SubChannel>,
pub sub_bus_peq: Vec<Peq>,
pub main_extra_ms: f64,
pub lift_ms: f64,
pub latency_ms: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub input_hp_hz: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub enum MimoFilter {
Iir {
gain_db: f64,
delay_ms: f64,
polarity: i8,
peq: Vec<Peq>,
allpass: Vec<Allpass>,
},
Fir {
taps_48k: Vec<f64>,
},
FirLowRate {
taps_low: Vec<f64>,
fs_low: f64,
},
}
impl MimoFilter {
pub fn is_fir(&self) -> bool {
matches!(self, Self::Fir { .. } | Self::FirLowRate { .. })
}
pub fn fir_len_48k(&self) -> usize {
match self {
Self::Iir { .. } => 0,
Self::Fir { taps_48k } => taps_48k.len(),
Self::FirLowRate { taps_low, fs_low } => {
if !fs_low.is_finite() || *fs_low <= 0.0 || taps_low.is_empty() {
0
} else {
(taps_low.len() as f64 * (48_000.0 / fs_low)).ceil() as usize
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MimoPath {
pub input: String,
pub support: String,
pub filter: MimoFilter,
pub low_hz: f64,
pub delay_ms: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct MimoPaths {
pub fsiso: f64,
pub paths: Vec<MimoPath>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn input_hp_is_absent_from_an_off_chain_and_round_trips_when_set() {
let mut chain = SignalChain {
sample_rate: 48_000,
mains: Vec::new(),
subs: Vec::new(),
sub_bus_peq: Vec::new(),
main_extra_ms: 0.0,
lift_ms: 0.0,
latency_ms: 0.0,
input_hp_hz: None,
};
let off = serde_json::to_value(&chain).unwrap();
assert!(off.get("input_hp_hz").is_none());
assert_eq!(serde_json::from_value::<SignalChain>(off).unwrap(), chain);
chain.input_hp_hz = Some(27.0);
let on = serde_json::to_value(&chain).unwrap();
assert_eq!(on["input_hp_hz"], 27.0);
assert_eq!(serde_json::from_value::<SignalChain>(on).unwrap(), chain);
}
}