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