1use sva_ast::{Arg, BinOp, ByteSpan, Expr, Literal};
4use sva_formula::closed_form::{map_children, read_at};
5use sva_formula::{Body, C64, IndexId, Part, Series, Var};
6
7use crate::arguments::Chosen;
8use crate::error::{Diagnostic, EngineError, Located};
9use crate::instantiate::{Cx, Instances, Node};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13pub enum Delay {
14 Steps(u32),
15 Secs(f64),
16 Varying,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub(crate) enum SelfKind {
22 Series { gain: C64, delay: f64 },
23 Sampled,
24 Refuse(Box<EngineError>),
25}
26
27pub(crate) fn classify(inst: &Instances, e: &Expr, cx: Cx, at: &str) -> SelfKind {
31 match read(inst, e, cx, C64::ONE) {
32 Reading::Refused(tap) => SelfKind::Refuse(Box::new(
33 tap_refusal(tap, Located::at(at, None)).expect("a refused tap names its reason"),
34 )),
35 Reading::Free | Reading::Nonlinear => SelfKind::Sampled,
36 Reading::Linear {
37 gain,
38 delay: Delay::Steps(_),
39 } if gain.abs() > 1.0 => SelfKind::Refuse(Box::new(unbounded(gain, at))),
40 Reading::Linear {
41 delay: Delay::Steps(_) | Delay::Varying,
42 ..
43 } => SelfKind::Sampled,
44 Reading::Linear {
45 gain,
46 delay: Delay::Secs(secs),
47 } => match gain.abs() < 1.0 {
48 true => SelfKind::Series { gain, delay: secs },
49 false => SelfKind::Refuse(Box::new(unbounded(gain, at))),
50 },
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq)]
56pub(crate) enum Tap {
57 At(Delay),
58 Zero,
59 Forward,
60 Fractional,
61}
62
63pub(crate) fn tap_of(inst: &Instances, arg: &Expr, cx: Cx) -> Tap {
65 match shift_of(inst, arg, cx) {
66 None => Tap::At(Delay::Varying),
67 Some(Shift::Now) => Tap::Zero,
68 Some(Shift::Secs(secs)) if secs > 0.0 => Tap::At(Delay::Secs(secs)),
69 Some(Shift::Steps(steps)) if steps > 0.0 && steps.fract() == 0.0 => {
70 Tap::At(Delay::Steps(steps as u32))
71 }
72 Some(Shift::Steps(steps)) if steps > 0.0 => Tap::Fractional,
73 Some(_) => Tap::Forward,
74 }
75}
76
77pub(crate) fn tap_refusal(tap: Tap, at: Located) -> Option<EngineError> {
78 let (code, message, help) = match tap {
79 Tap::At(_) => return None,
80 Tap::Zero => (
81 "samples.zero_delay_loop",
82 "a loop reaches no sample it has already written.",
83 "write self(t - 1sp) for a one-step loop",
84 ),
85 Tap::Forward => (
86 "engine.forward_self_read",
87 "a loop reads its own output before it is written.",
88 "write self at an earlier time, as in self(t - 1sp)",
89 ),
90 Tap::Fractional => (
91 "ref.fractional_shift_on_samples",
92 "a read on the grid moves by whole samples.",
93 "write a whole number of sp, or the delay in seconds",
94 ),
95 };
96 Some(EngineError::refused(Diagnostic {
97 code: code.to_string(),
98 message: message.to_string(),
99 location: at,
100 help: help.to_string(),
101 }))
102}
103
104fn unbounded(gain: C64, at: &str) -> EngineError {
105 EngineError::refused(Diagnostic {
106 code: "type.self_gain_unbounded".to_string(),
107 message: format!("loop gain {} does not settle.", gain.abs()),
108 location: Located::at(at, None),
109 help: "write self(t - 1sp) for a sampled loop".to_string(),
110 })
111}
112
113enum Reading {
115 Free,
116 Linear { gain: C64, delay: Delay },
117 Refused(Tap),
118 Nonlinear,
119}
120
121fn read(inst: &Instances, e: &Expr, cx: Cx, gain: C64) -> Reading {
122 if let Some(r) = inst.follow(e, cx, |e2, cx2| read(inst, e2, cx2, gain)) {
123 return r;
124 }
125 match inst.node(e, cx) {
126 Node::Own { arg, .. } => match tap_of(inst, arg, cx) {
127 Tap::At(delay) => Reading::Linear { gain, delay },
128 refused => Reading::Refused(refused),
129 },
130 Node::Bin(op @ (BinOp::Add | BinOp::Sub), l, r) => {
131 let right = if op == BinOp::Sub { -gain } else { gain };
132 join(read(inst, l, cx, gain), read(inst, r, cx, right))
133 }
134 Node::Bin(BinOp::Mul, l, r) => match (holds(inst, l, cx), holds(inst, r, cx)) {
135 (false, true) => match constant(inst, l, cx) {
136 Some(k) => read(inst, r, cx, gain * k),
137 None => Reading::Nonlinear,
138 },
139 (true, false) => match constant(inst, r, cx) {
140 Some(k) => read(inst, l, cx, gain * k),
141 None => Reading::Nonlinear,
142 },
143 (false, false) => Reading::Free,
144 (true, true) => Reading::Nonlinear,
145 },
146 Node::Bin(BinOp::Div, l, r) => match (holds(inst, l, cx), holds(inst, r, cx)) {
147 (true, false) => match constant(inst, r, cx) {
148 Some(k) if !k.is_zero() => read(inst, l, cx, gain / k),
149 _ => Reading::Nonlinear,
150 },
151 (false, false) => Reading::Free,
152 _ => Reading::Nonlinear,
153 },
154 other => match holds_in(inst, &other, cx) {
155 true => Reading::Nonlinear,
156 false => Reading::Free,
157 },
158 }
159}
160
161fn join(a: Reading, b: Reading) -> Reading {
162 match (a, b) {
163 (Reading::Refused(tap), _) | (_, Reading::Refused(tap)) => Reading::Refused(tap),
164 (Reading::Nonlinear, _) | (_, Reading::Nonlinear) => Reading::Nonlinear,
165 (Reading::Free, other) | (other, Reading::Free) => other,
166 (
167 Reading::Linear {
168 gain: g1,
169 delay: d1,
170 },
171 Reading::Linear {
172 gain: g2,
173 delay: d2,
174 },
175 ) if d1 == d2 => Reading::Linear {
176 gain: g1 + g2,
177 delay: d1,
178 },
179 _ => Reading::Nonlinear,
180 }
181}
182
183fn holds(inst: &Instances, e: &Expr, cx: Cx) -> bool {
184 holds_in(inst, &inst.node(e, cx), cx) || inst.holds_self(e, cx)
185}
186
187fn holds_in(inst: &Instances, node: &Node, cx: Cx) -> bool {
188 match node {
189 Node::Own { .. } => true,
190 Node::Read { arg, .. } => inst.holds_self(arg, cx),
191 Node::Call { args, .. } => args.iter().any(|a| {
192 let (sva_ast::Arg::Pos(x) | sva_ast::Arg::Named(_, x)) = a;
193 inst.holds_self(x, cx)
194 }),
195 Node::Bin(_, l, r) => inst.holds_self(l, cx) || inst.holds_self(r, cx),
196 Node::Lit(_) | Node::Name(_) => false,
197 }
198}
199
200fn constant(inst: &Instances, e: &Expr, cx: Cx) -> Option<C64> {
201 plain(amount(inst, e, cx)?).map(C64::real)
202}
203
204pub(crate) fn neumann(rest: &Body, gain: C64, delay: f64, index: IndexId) -> Body {
207 let log = C64::new(gain.abs().ln(), gain.im.atan2(gain.re));
208 let power = Body::Apply(
209 sva_formula::Unary::Exp,
210 Part::bare(Body::Mul(vec![
211 Part::bare(Body::Index(index)),
212 Part::bare(Body::Const(log)),
213 ])),
214 );
215 let addends: Vec<&Body> = match rest {
216 Body::Add(parts) => parts.iter().map(|p| &*p.body).collect(),
217 other => vec![other],
218 };
219 let scaled: Vec<Part> = addends
220 .into_iter()
221 .map(|addend| {
222 Part::bare(Body::Mul(vec![
223 Part::bare(power.clone()),
224 Part::bare(moved(addend, index, delay)),
225 ]))
226 })
227 .collect();
228 let term = match scaled.as_slice() {
229 [only] => (*only.body).clone(),
230 _ => Body::Add(scaled),
231 };
232 Body::Series(Box::new(Series {
233 index,
234 lo: 0,
235 hi: sva_formula::Bound::Infinite,
236 term: Part::bare(term),
237 }))
238}
239
240fn moved(f: &Body, index: IndexId, delay: f64) -> Body {
242 let at = Body::Add(vec![
243 Part::bare(Body::Line),
244 Part::bare(Body::Mul(vec![
245 Part::bare(Body::Const(C64::real(-delay))),
246 Part::bare(Body::Index(index)),
247 ])),
248 ]);
249 read_at(f, &at)
250}
251
252pub(crate) fn expandable(
254 f: &Body,
255 var: Var,
256 of: &dyn Fn(sva_formula::NodeId) -> Option<(Body, Var)>,
257) -> Option<Body> {
258 match f {
259 Body::Node(id) => {
260 let (body, held) = of(*id)?;
261 match held == var {
262 true => expandable(&body, var, of),
263 false => None,
264 }
265 }
266 other => {
267 let mut ok = true;
268 let out = map_children(other, |p| match expandable(&p.body, var, of) {
269 Some(body) => Part::new(p.origin, body),
270 None => {
271 ok = false;
272 p.clone()
273 }
274 });
275 ok.then_some(out)
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq)]
282pub enum Shift {
283 Now,
284 Secs(f64),
285 Steps(f64),
286}
287
288pub fn shift_of(inst: &Instances, e: &Expr, cx: Cx) -> Option<Shift> {
291 let (time, secs, steps) = walk(inst, e, cx, 1.0)?;
292 if !time {
293 return None;
294 }
295 match (secs, steps) {
296 (0.0, 0.0) => Some(Shift::Now),
297 (secs, 0.0) => Some(Shift::Secs(-secs)),
298 (0.0, steps) => Some(Shift::Steps(-steps)),
299 _ => None,
300 }
301}
302
303fn walk(inst: &Instances, e: &Expr, cx: Cx, sign: f64) -> Option<(bool, f64, f64)> {
305 if let Some(r) = inst.follow(e, cx, |e2, cx2| walk(inst, e2, cx2, sign)) {
306 return r;
307 }
308 match inst.node(e, cx) {
309 Node::Name("t") => Some((true, 0.0, 0.0)),
310 Node::Bin(op @ (BinOp::Add | BinOp::Sub), l, r) => {
311 let (lt, ls, lg) = walk(inst, l, cx, sign)?;
312 let flip = if op == BinOp::Sub { -sign } else { sign };
313 let (rt, rs, rg) = walk(inst, r, cx, flip)?;
314 Some((lt || rt, ls + rs, lg + rg))
315 }
316 _ => {
317 let (secs, steps) = amount(inst, e, cx)?;
318 Some((false, sign * secs, sign * steps))
319 }
320 }
321}
322
323pub(crate) fn amount(inst: &Instances, e: &Expr, cx: Cx) -> Option<(f64, f64)> {
326 folded(inst, e, cx, &mut None)
327}
328
329pub(crate) fn amount_choosing(
331 inst: &Instances,
332 e: &Expr,
333 cx: Cx,
334 chosen: &mut Vec<Chosen>,
335) -> Option<(f64, f64)> {
336 folded(inst, e, cx, &mut Some(chosen))
337}
338
339fn folded(
341 inst: &Instances,
342 e: &Expr,
343 cx: Cx,
344 chosen: &mut Option<&mut Vec<Chosen>>,
345) -> Option<(f64, f64)> {
346 if let Some(r) = inst.follow(e, cx, |e2, cx2| folded(inst, e2, cx2, &mut None)) {
347 return r;
348 }
349 match inst.node(e, cx) {
350 Node::Lit(Literal::Num(n)) => Some((*n, 0.0)),
351 Node::Lit(Literal::Samples(n)) => Some((0.0, *n)),
352 Node::Name("pi") => Some((std::f64::consts::PI, 0.0)),
353 Node::Name(other) => sva_formula::note::frequency(other).map(|hz| (hz, 0.0)),
354 Node::Bin(op, l, r) => {
355 let (a, b) = (folded(inst, l, cx, chosen)?, folded(inst, r, cx, chosen)?);
356 Some(match op {
357 BinOp::Add => (a.0 + b.0, a.1 + b.1),
358 BinOp::Sub => (a.0 - b.0, a.1 - b.1),
359 BinOp::Mul => scaled(a, b)?,
360 BinOp::Div => {
361 let by = plain(b)?;
362 (a.0 / by, a.1 / by)
363 }
364 BinOp::Mod => (crate::lower::constant_modulo(plain(a)?, plain(b)?)?, 0.0),
365 })
366 }
367 Node::Call { name, args, span } => called(inst, (name, span), args, cx, chosen),
368 Node::Read { path, arg, .. } if inst.is_now(arg, cx) => {
370 let (body, held) = inst.at(path)?;
371 folded(inst, body, held, &mut None)
372 }
373 _ => None,
374 }
375}
376
377fn scaled(a: (f64, f64), b: (f64, f64)) -> Option<(f64, f64)> {
379 match (plain(a), plain(b)) {
380 (Some(k), _) => Some((k * b.0, k * b.1)),
381 (_, Some(k)) => Some((k * a.0, k * a.1)),
382 _ => None,
383 }
384}
385
386fn called(
387 inst: &Instances,
388 (name, at): (&str, ByteSpan),
389 args: &[Arg],
390 cx: Cx,
391 chosen: &mut Option<&mut Vec<Chosen>>,
392) -> Option<(f64, f64)> {
393 let mut positional = Vec::new();
394 let mut named = Vec::new();
395 for arg in args {
396 match arg {
397 Arg::Pos(x) => positional.push(plain(folded(inst, x, cx, chosen)?)?),
398 Arg::Named(key, x) => {
399 named.push((key.as_str(), plain(folded(inst, x, cx, chosen)?)?));
400 }
401 }
402 }
403 let n = crate::lower::constant_call(name, &positional, &named)?;
404 let won = positional.iter().position(|v| v.to_bits() == n.to_bits());
405 if let (Some(held), "min" | "max", Some(won)) = (chosen.as_mut(), name, won) {
406 held.push(Chosen {
407 name: name.to_string(),
408 at,
409 operands: positional,
410 chosen: won,
411 });
412 }
413 Some((n, 0.0))
414}
415
416pub(crate) fn plain(amount: (f64, f64)) -> Option<f64> {
417 (amount.1 == 0.0 && amount.0.is_finite()).then_some(amount.0)
418}