use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::data::traces::TraceRef;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BiquadParams {
pub b: [f64; 3],
pub a: [f64; 3],
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MinMaxMode {
Min,
Max,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FilterKind {
Lowpass { cutoff_hz: f64 },
Highpass { cutoff_hz: f64 },
Bandpass { low_cut_hz: f64, high_cut_hz: f64 },
BiquadLowpass { cutoff_hz: f64, q: f64 },
BiquadHighpass { cutoff_hz: f64, q: f64 },
BiquadBandpass { center_hz: f64, q: f64 },
Custom { params: BiquadParams },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MathKind {
Add { inputs: Vec<(TraceRef, f64)> },
Multiply { a: TraceRef, b: TraceRef },
Divide { a: TraceRef, b: TraceRef },
Differentiate { input: TraceRef },
Integrate { input: TraceRef, y0: f64 },
Filter { input: TraceRef, kind: FilterKind },
MinMax {
input: TraceRef,
decay_per_sec: Option<f64>,
mode: MinMaxMode,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MathTrace {
pub name: TraceRef,
pub kind: MathKind,
}
impl MathTrace {
pub fn new(name: TraceRef, kind: MathKind) -> Self {
Self {
name,
kind,
}
}
pub fn compute_math_trace(
&mut self,
sources: HashMap<TraceRef, Vec<[f64; 2]>>,
) -> Vec<[f64; 2]> {
let mut out = if let Some(points) = sources.get(&self.name) {
points.clone()
} else {
Vec::new()
};
match &self.kind {
MathKind::Add { inputs } => {
let used_sources: Vec<Vec<[f64; 2]>> = inputs
.iter()
.filter_map(|(r, _k)| sources.get(r).cloned())
.collect();
let grid: Vec<f64> = MathTrace::union_times(used_sources.clone());
let mut idx_map: std::collections::HashMap<TraceRef, usize> = HashMap::new();
for t in grid {
if out.last().map_or(false, |p| p[0] >= t) {
continue;
}
let mut sum = 0.0;
let mut all = true;
for (r, k) in inputs {
if let Some(src_data) = sources.get(r) {
let last_idx = if let Some(idx) = idx_map.get_mut(r) {
idx
} else {
idx_map.insert(r.clone(), 0);
idx_map.get_mut(r).unwrap()
};
if let Some(v) =
MathTrace::interpolate_value_at(t, src_data.clone(), last_idx)
{
sum += k * v;
} else {
all = false;
break;
}
}
}
if all {
out.push([t, sum]);
}
}
}
MathKind::Multiply { a, b } => {
if let (Some(src_a), Some(src_b)) = (sources.get(a), sources.get(b)) {
let grid: Vec<f64> = MathTrace::union_times(vec![src_a.clone(), src_b.clone()]);
let mut idx_a = 0usize;
let mut idx_b = 0usize;
for &t in &grid {
if out.last().map_or(false, |p| p[0] >= t) {
continue;
}
if let (Some(va), Some(vb)) = (
MathTrace::interpolate_value_at(t, src_a.clone(), &mut idx_a),
MathTrace::interpolate_value_at(t, src_b.clone(), &mut idx_b),
) {
out.push([t, va * vb]);
}
}
} else {
return out;
}
}
MathKind::Divide { a, b } => {
if let (Some(src_a), Some(src_b)) = (sources.get(a), sources.get(b)) {
let grid: Vec<f64> = MathTrace::union_times(vec![src_a.clone(), src_b.clone()]);
let mut idx_a = 0usize;
let mut idx_b = 0usize;
for &t in &grid {
if out.last().map_or(false, |p| p[0] >= t) {
continue;
}
if let (Some(va), Some(vb)) = (
MathTrace::interpolate_value_at(t, src_a.clone(), &mut idx_a),
MathTrace::interpolate_value_at(t, src_b.clone(), &mut idx_b),
) {
if vb.abs() > 1e-12 {
out.push([t, va / vb]);
}
}
}
} else {
return out;
}
}
MathKind::Differentiate { input } => {
if let Some(src) = sources.get(input) {
let mut prev: Option<(f64, f64)> = None;
for &p in src.iter() {
let t = p[0];
let v = p[1];
if out.last().map_or(false, |p| p[0] >= t) {
prev = Some((t, v));
continue;
}
if let Some((t0, v0)) = prev {
let dt = t - t0;
if dt > 0.0 {
out.push([t, (v - v0) / dt]);
}
}
prev = Some((t, v));
}
} else {
return out;
}
}
MathKind::Integrate { input, y0 } => {
if let Some(src) = sources.get(input) {
let mut prev: Option<(f64, f64)> = None;
let mut accum = if out.is_empty() {
*y0
} else {
out.last().unwrap()[1]
};
for &p in src.iter() {
let t = p[0];
let v = p[1];
if out.last().map_or(false, |p| p[0] >= t) {
prev = Some((t, v));
continue;
}
if let Some((t0, v0)) = prev {
let dt = t - t0;
if dt > 0.0 {
accum += 0.5 * (v + v0) * dt;
out.push([t, accum.clone()]);
}
} else if out.is_empty() {
out.push([t, accum.clone()]);
}
prev = Some((t, v));
}
} else {
return out;
}
}
MathKind::Filter { input, kind } => {
let data: &Vec<[f64; 2]> = match sources.get(input) {
Some(v) => v,
None => return out,
};
let mut x1: f64 = 0.0;
let mut x2: f64 = 0.0;
let mut y1: f64 = 0.0;
let mut y2: f64 = 0.0;
let mut x1b: f64 = 0.0;
let mut x2b: f64 = 0.0;
let mut y1b: f64 = 0.0;
let mut y2b: f64 = 0.0;
let mut last_t: Option<f64> = None;
let mut start_idx: usize = 0;
if !out.is_empty() {
let find_idx = |t: f64, d: &Vec<[f64; 2]>| -> Option<usize> {
match d.binary_search_by(|p| p[0].partial_cmp(&t).unwrap()) {
Ok(i) => Some(i),
Err(i) => {
if i > 0 && (d[i - 1][0] - t).abs() < 1e-12 {
Some(i - 1)
} else {
None
}
}
}
};
let (t1, y1v) = {
let p = out.last().copied().unwrap();
(p[0], p[1])
};
if let Some(i1) = find_idx(t1, data) {
y1 = y1v;
x1 = data[i1][1];
last_t = Some(t1);
start_idx = i1.saturating_add(1);
}
if out.len() >= 2 {
let (t2, y2v) = {
let p = out[out.len() - 2];
(p[0], p[1])
};
if let Some(i2) = find_idx(t2, data) {
y2 = y2v;
x2 = data[i2][1];
start_idx = start_idx.max(i2.saturating_add(1));
}
}
}
for p in data.iter().skip(start_idx) {
let t = p[0];
let x = p[1];
let dt = if let Some(t0) = last_t {
(t - t0).max(1e-9)
} else {
1e-3
};
let y = match kind {
FilterKind::Lowpass { cutoff_hz } => {
let p = MathTrace::first_order_lowpass(*cutoff_hz, dt);
MathTrace::biquad_step(p, x, x1, x2, y1, y2)
}
FilterKind::Highpass { cutoff_hz } => {
let p = MathTrace::first_order_highpass(*cutoff_hz, dt);
MathTrace::biquad_step(p, x, x1, x2, y1, y2)
}
FilterKind::Bandpass {
low_cut_hz,
high_cut_hz,
} => {
let p1 = MathTrace::first_order_highpass(*low_cut_hz, dt);
let z1 = MathTrace::biquad_step(p1, x, x1, x2, y1, y2);
let p2 = MathTrace::first_order_lowpass(*high_cut_hz, dt);
MathTrace::biquad_step(p2, z1, x1b, x2b, y1b, y2b)
}
FilterKind::BiquadLowpass { cutoff_hz, q } => {
let p = MathTrace::biquad_lowpass(*cutoff_hz, *q, dt);
MathTrace::biquad_step(p, x, x1, x2, y1, y2)
}
FilterKind::BiquadHighpass { cutoff_hz, q } => {
let p = MathTrace::biquad_highpass(*cutoff_hz, *q, dt);
MathTrace::biquad_step(p, x, x1, x2, y1, y2)
}
FilterKind::BiquadBandpass { center_hz, q } => {
let p = MathTrace::biquad_bandpass(*center_hz, *q, dt);
MathTrace::biquad_step(p, x, x1, x2, y1, y2)
}
FilterKind::Custom { params } => {
MathTrace::biquad_step(*params, x, x1, x2, y1, y2)
}
};
match kind {
FilterKind::Bandpass { .. } => {
let p1 = if let FilterKind::Bandpass { low_cut_hz, .. } = kind {
MathTrace::first_order_highpass(*low_cut_hz, dt)
} else {
MathTrace::first_order_highpass(1.0, dt)
};
let z1 = MathTrace::biquad_step(p1, x, x1, x2, y1, y2);
x2 = x1;
x1 = x;
y2 = y1;
y1 = z1;
x2b = x1b;
x1b = z1;
y2b = y1b;
y1b = y;
}
_ => {
x2 = x1;
x1 = x;
y2 = y1;
y1 = y;
}
}
last_t = Some(t);
out.push([t, y]);
}
}
MathKind::MinMax {
input,
decay_per_sec,
mode,
} => {
if let Some(src) = sources.get(input) {
let mut prev: Option<(f64, f64)> = None;
let mut minmax = if out.is_empty() {
src.first().map(|p| p[1]).unwrap_or(0.0)
} else {
out.last().unwrap()[1]
};
for &p in src.iter() {
let t = p[0];
let v = p[1];
if out.last().map_or(false, |p| p[0] >= t) {
prev = Some((t, v));
continue;
}
minmax = match mode {
MinMaxMode::Min => minmax.min(v),
MinMaxMode::Max => minmax.max(v),
};
if let (Some((t0, _v0)), Some(decay)) = (prev, decay_per_sec) {
let dt = t - t0;
if dt > 0.0 {
let k = (-decay * dt).exp();
minmax = match mode {
MinMaxMode::Min => minmax * k + v * (1.0 - k),
MinMaxMode::Max => minmax * k + v * (1.0 - k),
};
}
}
out.push([t, minmax.clone()]);
prev = Some((t, v));
}
} else {
return out;
}
}
}
out
}
fn union_times<'a>(sources: Vec<Vec<[f64; 2]>>) -> Vec<f64> {
let mut v = Vec::new();
for s in sources {
v.extend(s.iter().map(|p| p[0]));
}
v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
v.dedup_by(|a, b| (*a - *b).abs() < 1e-15);
v
}
fn interpolate_value_at(t: f64, data: Vec<[f64; 2]>, last_idx: &mut usize) -> Option<f64> {
if data.is_empty() {
return None;
}
let n = data.len();
if n == 0 {
return None;
}
let first_t = data[0][0];
let last_t = data[n - 1][0];
if t < first_t || t > last_t {
return None;
}
if *last_idx >= n {
*last_idx = n - 1;
}
if data[*last_idx][0] > t {
let mut lo = 0usize;
let mut hi = n; while lo < hi {
let mid = (lo + hi) / 2;
let tm = data[mid][0];
if tm < t {
lo = mid + 1;
} else {
hi = mid;
}
}
let j = lo;
if j == 0 {
return Some(data[0][1]);
}
if j == n {
return Some(data[n - 1][1]);
}
let i = j - 1;
*last_idx = i;
let t0 = data[i][0];
let v0 = data[i][1];
let t1 = data[j][0];
let v1 = data[j][1];
if t1 == t0 {
return Some(v0);
}
let alpha = (t - t0) / (t1 - t0);
return Some(v0 + alpha * (v1 - v0));
}
while *last_idx + 1 < n && data[*last_idx + 1][0] < t {
*last_idx += 1;
}
if data[*last_idx][0] == t {
return Some(data[*last_idx][1]);
}
if *last_idx + 1 < n && data[*last_idx + 1][0] == t {
*last_idx += 1;
return Some(data[*last_idx][1]);
}
if *last_idx + 1 < n {
let t0 = data[*last_idx][0];
let v0 = data[*last_idx][1];
let t1 = data[*last_idx + 1][0];
let v1 = data[*last_idx + 1][1];
if t1 == t0 {
return Some(v0);
}
let alpha = (t - t0) / (t1 - t0);
Some(v0 + alpha * (v1 - v0))
} else {
None
}
}
pub fn math_formula_string(&self) -> String {
match &self.kind {
MathKind::Add { inputs } => {
if inputs.is_empty() {
"0".to_string()
} else {
let mut s = String::new();
for (i, (r, g)) in inputs.iter().enumerate() {
if i > 0 {
s.push_str(" + ");
}
if (*g - 1.0).abs() < 1e-12 {
s.push_str(&r.0);
} else {
s.push_str(&format!("{:.3}*{}", g, r.0));
}
}
s
}
}
MathKind::Multiply { a, b } => format!("{} * {}", a.0, b.0),
MathKind::Divide { a, b } => format!("{} / {}", a.0, b.0),
MathKind::Differentiate { input } => format!("d({})/dt", input.0),
MathKind::Integrate { input, y0 } => format!("∫ {} dt (y0={:.3})", input.0, y0),
MathKind::Filter { input, kind } => {
let k = match kind {
FilterKind::Lowpass { cutoff_hz } => format!("LP fc={:.3} Hz", cutoff_hz),
FilterKind::Highpass { cutoff_hz } => format!("HP fc={:.3} Hz", cutoff_hz),
FilterKind::Bandpass {
low_cut_hz,
high_cut_hz,
} => format!("BP [{:.3},{:.3}] Hz", low_cut_hz, high_cut_hz),
FilterKind::BiquadLowpass { cutoff_hz, q } => {
format!("BQ-LP fc={:.3} Q={:.3}", cutoff_hz, q)
}
FilterKind::BiquadHighpass { cutoff_hz, q } => {
format!("BQ-HP fc={:.3} Q={:.3}", cutoff_hz, q)
}
FilterKind::BiquadBandpass { center_hz, q } => {
format!("BQ-BP f0={:.3} Q={:.3}", center_hz, q)
}
FilterKind::Custom { .. } => "Custom biquad".to_string(),
};
format!("{} -> {}", input.0, k)
}
MathKind::MinMax {
input,
decay_per_sec,
mode,
} => {
let mm = match mode {
MinMaxMode::Min => "Min",
MinMaxMode::Max => "Max",
};
match decay_per_sec {
Some(d) => format!("{}({}) with decay {:.3} 1/s", mm, input.0, d),
None => format!("{}({})", mm, input.0),
}
}
}
}
#[inline]
fn first_order_lowpass(fc: f64, dt: f64) -> BiquadParams {
let rc = 1.0 / (2.0 * std::f64::consts::PI * fc.max(1e-9));
let alpha = dt / (rc + dt);
BiquadParams {
b: [alpha, 0.0, 0.0],
a: [1.0, -(1.0 - alpha), 0.0],
}
}
#[inline]
fn first_order_highpass(fc: f64, dt: f64) -> BiquadParams {
let rc = 1.0 / (2.0 * std::f64::consts::PI * fc.max(1e-9));
let alpha = rc / (rc + dt);
BiquadParams {
b: [alpha, -alpha, 0.0],
a: [1.0, -alpha, 0.0],
}
}
#[inline]
fn biquad_step(p: BiquadParams, x0: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 {
let a0 = if p.a[0].abs() < 1e-15 { 1.0 } else { p.a[0] };
let b0 = p.b[0] / a0;
let b1 = p.b[1] / a0;
let b2 = p.b[2] / a0;
let a1 = p.a[1] / a0;
let a2 = p.a[2] / a0;
b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2
}
#[inline]
fn biquad_lowpass(fc: f64, q: f64, dt: f64) -> BiquadParams {
let fs = (1.0 / dt).max(1.0);
let w0 = 2.0 * std::f64::consts::PI * (fc.max(1e-9) / fs);
let cosw0 = w0.cos();
let sinw0 = w0.sin();
let q = q.max(1e-6);
let alpha = sinw0 / (2.0 * q);
let b0 = (1.0 - cosw0) * 0.5;
let b1 = 1.0 - cosw0;
let b2 = (1.0 - cosw0) * 0.5;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cosw0;
let a2 = 1.0 - alpha;
BiquadParams {
b: [b0, b1, b2],
a: [a0, a1, a2],
}
}
#[inline]
fn biquad_highpass(fc: f64, q: f64, dt: f64) -> BiquadParams {
let fs = (1.0 / dt).max(1.0);
let w0 = 2.0 * std::f64::consts::PI * (fc.max(1e-9) / fs);
let cosw0 = w0.cos();
let sinw0 = w0.sin();
let q = q.max(1e-6);
let alpha = sinw0 / (2.0 * q);
let b0 = (1.0 + cosw0) * 0.5;
let b1 = -(1.0 + cosw0);
let b2 = (1.0 + cosw0) * 0.5;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cosw0;
let a2 = 1.0 - alpha;
BiquadParams {
b: [b0, b1, b2],
a: [a0, a1, a2],
}
}
#[inline]
fn biquad_bandpass(fc: f64, q: f64, dt: f64) -> BiquadParams {
let fs = (1.0 / dt).max(1.0);
let w0 = 2.0 * std::f64::consts::PI * (fc.max(1e-9) / fs);
let cosw0 = w0.cos();
let sinw0 = w0.sin();
let q = q.max(1e-6);
let alpha = sinw0 / (2.0 * q);
let b0 = alpha;
let b1 = 0.0;
let b2 = -alpha;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cosw0;
let a2 = 1.0 - alpha;
BiquadParams {
b: [b0, b1, b2],
a: [a0, a1, a2],
}
}
}