1use crate::C;
8use crate::input::Allpass;
9use crate::measurement::ramp_phase;
10use secs_dsp::response::allpass2;
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct BaRow {
15 pub gain_db: f64,
17 pub delay_ms: f64,
19 pub polarity: i8,
21 pub allpass: Vec<Allpass>,
23}
24
25pub fn term(h: &[C], f: &[f64], row: &BaRow) -> Vec<C> {
30 PreparedBaTerm::new(f, row).apply(h)
31}
32
33pub(crate) struct PreparedBaTerm {
36 gain: C,
37 polarity: C,
38 delay: Vec<C>,
39 allpass: Vec<Vec<C>>,
40}
41
42impl PreparedBaTerm {
43 pub(crate) fn new(f: &[f64], row: &BaRow) -> Self {
44 Self {
45 gain: C::new(10f64.powf(row.gain_db / 20.0), 0.0),
46 polarity: C::new(f64::from(row.polarity), 0.0),
47 delay: f
48 .iter()
49 .map(|&f| C::from_polar(1.0, ramp_phase(f, row.delay_ms) * 0.001))
50 .collect(),
51 allpass: row
52 .allpass
53 .iter()
54 .map(|&(f0, q)| allpass2(f, f0, q))
55 .collect(),
56 }
57 }
58
59 fn apply(&self, h: &[C]) -> Vec<C> {
60 let mut out: Vec<_> = h
61 .iter()
62 .zip(&self.delay)
63 .map(|(&h, &delay)| h * self.gain * self.polarity * delay)
64 .collect();
65 for factor in &self.allpass {
66 for (v, &a) in out.iter_mut().zip(factor) {
67 *v *= a;
68 }
69 }
70 out
71 }
72}
73
74pub(crate) fn sub_sum_prepared(hs: &[Vec<C>], terms: &[PreparedBaTerm], bins: usize) -> Vec<C> {
75 let mut sum = vec![C::new(0.0, 0.0); bins];
76 for (h, term) in hs.iter().zip(terms) {
77 for (s, t) in sum.iter_mut().zip(term.apply(h)) {
78 *s += t;
79 }
80 }
81 sum
82}
83
84pub fn sub_sum(terms: &[(&[C], &BaRow)], f: &[f64]) -> Vec<C> {
87 let mut sum = vec![C::new(0.0, 0.0); f.len()];
88 for (h, row) in terms {
89 for (s, t) in sum.iter_mut().zip(term(h, f, row)) {
90 *s += t;
91 }
92 }
93 sum
94}
95
96#[derive(Debug)]
97pub struct StageResponses {
98 pub top_xo: Vec<C>,
99 pub sub_xo: Option<Vec<C>>,
100 pub combined: Vec<C>,
101 pub output: Vec<C>,
102}
103
104pub fn stage_responses(
108 design: &crate::design::Design,
109 role: &str,
110 spectra: &crate::measurement::Spectra,
111 position: u32,
112) -> Option<StageResponses> {
113 use crate::{
114 grid::{BINS, design_freqs},
115 measurement::delay_response,
116 };
117 use secs_dsp::response::lr4;
118 let main = design.mains.iter().find(|m| m.role == role)?;
119 let h = spectra.get(position, role)?;
120 if h.len() != BINS {
121 return None;
122 }
123 let gain = C::new(10f64.powf(main.gain_db / 20.0), 0.0);
124 let mut top: Vec<_> = h
125 .iter()
126 .zip(delay_response(main.align_delay_ms))
127 .map(|(h, d)| h * d * gain)
128 .collect();
129 if let Some(p) = &main.top_response {
130 for (h, p) in top.iter_mut().zip(p.iter()) {
131 *h *= p;
132 }
133 }
134 let sub_xo = if let (Some(xo), Some(sub)) = (main.xo_hz.filter(|x| *x != 0.0), &design.subs) {
135 let terms: Option<Vec<_>> = sub
136 .rows
137 .iter()
138 .map(|(r, row)| {
139 let h = spectra.get(position, r)?;
140 (h.len() == BINS).then_some((h.as_ref(), row))
141 })
142 .collect();
143 let mut sum = sub_sum(&terms?, design_freqs());
144 if let Some(p) = &sub.bus_response {
145 for (h, p) in sum.iter_mut().zip(p.iter()) {
146 *h *= p;
147 }
148 }
149 for (h, p) in top.iter_mut().zip(lr4(design_freqs(), xo, "HP")) {
150 *h *= p;
151 }
152 for (h, p) in sum.iter_mut().zip(lr4(design_freqs(), xo, "LP")) {
153 *h *= p;
154 }
155 Some(sum)
156 } else {
157 None
158 };
159 let combined: Vec<_> = match &sub_xo {
160 Some(sub) => top.iter().zip(sub).map(|(m, s)| m + s).collect(),
161 None => top.clone(),
162 };
163 let output = combined
164 .iter()
165 .zip(main.response.iter())
166 .map(|(h, f)| h * f)
167 .collect();
168 Some(StageResponses {
169 top_xo: top,
170 sub_xo,
171 combined,
172 output,
173 })
174}
175
176pub fn input_response(
177 design: &crate::design::Design,
178 role: &str,
179 spectra: &crate::measurement::Spectra,
180 position: u32,
181) -> Option<Vec<C>> {
182 stage_responses(design, role, spectra, position).map(|s| s.output)
183}
184
185pub fn gather<T: Copy>(h: &[T], idx: &[usize]) -> Vec<T> {
187 idx.iter().map(|&i| h[i]).collect()
188}