1use std::f64::consts::TAU;
4
5use sva_formula::affine::exact_constant;
6use sva_formula::closed_form::{Bound, Series, children, map_children};
7use sva_formula::series::{mentions_line, substitute};
8use sva_formula::spectral_sum::atom::{Exp, Factors, Singular, SpectralAtom};
9use sva_formula::table::series::{Shape, read};
10use sva_formula::{Body, C64, Lane, Part, SpectralSum, Var, lines};
11
12use crate::error::CollapseError;
13use crate::profile::Profile;
14
15#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct Audible {
18 ceiling: f64,
19 floor_db: f64,
20}
21
22impl Audible {
23 pub fn of(profile: &Profile, rate: u32) -> Audible {
24 let ceiling = profile.ceiling(rate);
25 Audible {
26 ceiling,
27 floor_db: profile.floor(ceiling),
28 }
29 }
30}
31
32const MAX_EXPANDED_TERMS: usize = 1 << 13;
35
36pub fn spectral_sum(n: &SpectralSum, band: Audible) -> Result<SpectralSum, CollapseError> {
39 if n.lanes.iter().all(|l| l.series.is_empty()) {
40 return Ok(n.clone());
41 }
42 let mut lanes = Vec::with_capacity(n.lanes.len());
43 for lane in &n.lanes {
44 let mut held = Lane {
45 series: Vec::new(),
46 ..lane.clone()
47 };
48 for s in &lane.series {
49 held.atoms.extend(atoms(s, band)?);
50 }
51 lanes.push(held);
52 }
53 Ok(SpectralSum::of(n.var, lanes))
54}
55
56pub fn written(f: &Body, band: Audible) -> Result<Body, CollapseError> {
58 if let Body::Series(s) = f {
59 return expanded(s, band);
60 }
61 let mut found = None;
62 let out = map_children(f, |p| match written(&p.body, band) {
63 Ok(body) => Part::new(p.origin, body),
64 Err(e) => {
65 found = Some(e);
66 p.clone()
67 }
68 });
69 match found {
70 Some(e) => Err(e),
71 None => Ok(out),
72 }
73}
74
75fn atoms(s: &Series, band: Audible) -> Result<Vec<SpectralAtom>, CollapseError> {
76 let (body, window) = sva_formula::crop_peeled(&s.term.body);
77 let bare = Series {
78 term: Part::new(s.term.origin, body),
79 ..s.clone()
80 };
81 let taken = enumerated(&bare, band);
82 if !taken.is_empty() {
83 return Ok(taken
84 .into_iter()
85 .map(|l| {
86 SpectralAtom::new(
87 l.amp,
88 Factors {
89 exp: Some(Exp::at(0.0, TAU * l.hz)),
90 ind: window,
91 ..Factors::NONE
92 },
93 Singular::Regular,
94 s.term.origin,
95 )
96 })
97 .collect());
98 }
99 let body = expanded(s, band)?;
100 let held = sva_formula::normalize(&body, Var::T)
101 .map_err(|_| CollapseError::NotEvaluable("a series term"))?;
102 Ok(held.lanes.into_iter().flat_map(|l| l.atoms).collect())
103}
104
105fn expanded(s: &Series, band: Audible) -> Result<Body, CollapseError> {
107 let taken = enumerated(s, band);
108 if !taken.is_empty() {
109 return Ok(sum(taken.iter().map(wave).collect()));
110 }
111 let count = terms(s, band)?;
112 let mut parts = Vec::with_capacity(count);
113 for i in 0..count {
114 let form = substitute(&s.term.body, s.index, (s.lo + i as i64) as f64);
115 parts.push(written(&form, band)?);
116 }
117 Ok(sum(parts))
118}
119
120fn enumerated(s: &Series, band: Audible) -> Vec<sva_formula::Line> {
121 match read(&s.term.body) {
122 Some(Shape::Lines(_)) => lines(s, band.ceiling, band.floor_db).taken,
123 _ => Vec::new(),
124 }
125}
126
127fn wave(l: &sva_formula::Line) -> Body {
128 Body::Mul(vec![
129 Part::bare(Body::Const(l.amp)),
130 Part::bare(Body::Apply(
131 sva_formula::Unary::Exp,
132 Part::bare(Body::Mul(vec![
133 Part::bare(Body::Const(C64::new(0.0, TAU * l.hz))),
134 Part::bare(Body::Line),
135 ])),
136 )),
137 ])
138}
139
140fn sum(parts: Vec<Body>) -> Body {
141 match parts.len() {
142 0 => Body::Const(C64::ZERO),
143 1 => parts.into_iter().next().expect("one part"),
144 _ => Body::Add(parts.into_iter().map(Part::bare).collect()),
145 }
146}
147
148fn terms(s: &Series, band: Audible) -> Result<usize, CollapseError> {
150 let cost = cost(s, band).ok_or(CollapseError::NotEvaluable(
151 "a series whose term count no coefficient bounds",
152 ))?;
153 if cost.depth == 1 && cost.own > MAX_EXPANDED_TERMS {
154 return Err(CollapseError::NotEvaluable(
155 "a series of more terms than one instant expands",
156 ));
157 }
158 match cost.terms > MAX_EXPANDED_TERMS {
159 true => Err(CollapseError::NestedSeries {
160 depth: cost.depth,
161 terms: cost.terms,
162 bound: MAX_EXPANDED_TERMS,
163 }),
164 false => Ok(cost.own),
165 }
166}
167
168struct Cost {
170 depth: usize,
171 terms: usize,
172 own: usize,
173}
174
175fn cost(s: &Series, band: Audible) -> Option<Cost> {
176 let own = counted(s, band)?;
177 let inside = substitute(&s.term.body, s.index, s.lo as f64);
178 let (depth, inner) = price(&inside, band)?;
179 Some(Cost {
180 depth: depth + 1,
181 terms: own.saturating_mul(inner),
182 own,
183 })
184}
185
186fn price(f: &Body, band: Audible) -> Option<(usize, usize)> {
188 if let Body::Series(s) = f {
189 let cost = cost(s, band)?;
190 return Some((cost.depth, cost.terms));
191 }
192 let summed = matches!(f, Body::Add(_));
193 let mut depth = 0;
194 let mut terms: usize = if summed { 0 } else { 1 };
195 for p in children(f) {
196 let (d, n) = price(&p.body, band)?;
197 depth = depth.max(d);
198 terms = match summed {
199 true => terms.saturating_add(n),
200 false => terms.saturating_mul(n),
201 };
202 }
203 Some((depth, terms.max(1)))
204}
205
206fn counted(s: &Series, band: Audible) -> Option<usize> {
207 let hi = match s.hi {
208 Bound::Finite(n) => return usize::try_from((n - s.lo + 1).max(0)).ok(),
209 Bound::Infinite => MAX_EXPANDED_TERMS,
210 };
211 let coefficient = coefficient(&s.term.body)?;
212 let floor = 10f64.powf(band.floor_db / 20.0);
213 let mut peak = 0.0f64;
214 for i in 0..hi {
215 let at = substitute(&coefficient, s.index, (s.lo + i as i64) as f64);
216 let held = exact_constant(&at)?.abs();
217 peak = peak.max(held);
218 if peak > 0.0 && held < peak * floor {
219 return Some(i.max(1));
220 }
221 }
222 None
223}
224
225fn coefficient(f: &Body) -> Option<Body> {
228 let one = || Some(Body::Const(C64::ONE));
229 match f {
230 Body::Line | Body::Node(_) | Body::Series(_) => one(),
231 Body::Mul(parts) | Body::Add(parts) => {
232 let mut kept = Vec::with_capacity(parts.len());
233 for p in parts {
234 kept.push(Part::new(p.origin, coefficient(&p.body)?));
235 }
236 Some(match f {
237 Body::Mul(_) => Body::Mul(kept),
238 _ => Body::Add(kept),
239 })
240 }
241 Body::Div(a, b) => Some(Body::Div(
242 Part::new(a.origin, coefficient(&a.body)?),
243 Part::new(b.origin, coefficient(&b.body)?),
244 )),
245 Body::Apply(
246 sva_formula::Unary::Sin
247 | sva_formula::Unary::Cos
248 | sva_formula::Unary::Tanh
249 | sva_formula::Unary::Sat,
250 _,
251 ) => one(),
252 Body::Crop { of, .. } | Body::Shift { of, .. } => coefficient(&of.body),
253 Body::Pow(base, n) => Some(Body::Pow(
254 Part::new(base.origin, coefficient(&base.body)?),
255 *n,
256 )),
257 other if !mentions_line(other) => Some(other.clone()),
258 _ => None,
259 }
260}