Skip to main content

sva_samples/machine/
renderer.rs

1// Concern: the node renderer sva-engine hands this crate | Non-concern: building it (sva-engine lower.rs), running it (mod.rs, ops.rs) | IO: none
2
3use sva_formula::{Body, C64, Shape, SpectralSum};
4
5use crate::collapse::Extent;
6use crate::error::CollapseError;
7use crate::physics::Params;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
10pub struct BufId(pub u32);
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub struct SiteId(pub u32);
14
15/// Reader sample `n` reads source sample `(a*n + b)/d`, `d > 0`: exactly, or rounded down or
16/// to the nearest, ties to even. An exact map is whole, `d = 1`; nothing reads between samples.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct Map {
19    pub a: i128,
20    pub b: i128,
21    pub d: i128,
22    pub between: Between,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub enum Between {
27    Exact,
28    Floor,
29    Even,
30}
31
32impl Map {
33    pub const fn shift(b: i64) -> Map {
34        Map::whole(1, b)
35    }
36
37    /// Reader sample `n` reads source sample `a*n + b`.
38    pub const fn whole(a: i128, b: i64) -> Map {
39        Map {
40            a,
41            b: b as i128,
42            d: 1,
43            between: Between::Exact,
44        }
45    }
46
47    /// Lowest terms; `None` where a sample lands between two, or past what a map holds.
48    pub fn new(a: i128, b: i128, d: i128) -> Option<Map> {
49        Map::rounded(a, b, d, Between::Exact)
50    }
51
52    /// A rounding every sample takes alike is folded into an exact map.
53    pub fn rounded(a: i128, b: i128, d: i128, between: Between) -> Option<Map> {
54        let g = gcd(gcd(a.abs(), b.abs()), d.abs()).max(1);
55        let sign = d.signum();
56        let limit = 1i128 << 100;
57        let map = Map {
58            a: sign * a / g,
59            b: sign * b / g,
60            d: d.abs() / g,
61            between,
62        };
63        let held = map.d > 0 && map.a.abs() < limit && map.b.abs() < limit && map.d < limit;
64        held.then(|| map.settled())
65            .filter(|m| m.d == 1 || m.between != Between::Exact)
66    }
67
68    fn settled(self) -> Map {
69        let exact = match self.between {
70            _ if self.d == 1 => true,
71            Between::Exact => false,
72            Between::Floor => self.a % self.d == 0,
73            Between::Even => {
74                let tie = 2 * self.b.rem_euclid(self.d) == self.d;
75                self.a % self.d == 0 && (!tie || (self.a / self.d) % 2 == 0)
76            }
77        };
78        match exact {
79            true => Map {
80                a: self.a / self.d,
81                b: self.index_at(0),
82                d: 1,
83                between: Between::Exact,
84            },
85            false => self,
86        }
87    }
88
89    pub fn moved(self) -> Option<i64> {
90        (self.a == 1 && self.d == 1).then(|| i64::try_from(self.b).ok())?
91    }
92
93    pub fn at(self, n: i64) -> i64 {
94        let clamp = |k: i128| k.clamp(i128::from(i64::MIN), i128::from(i64::MAX)) as i64;
95        self.narrow_at(n)
96            .unwrap_or_else(|| clamp(self.index_at(i128::from(n))))
97    }
98
99    /// `index_at` in `i64`, where every step of it fits one.
100    fn narrow_at(self, n: i64) -> Option<i64> {
101        if let Some(k) = self.whole_at(n) {
102            return Some(k);
103        }
104        let [a, b, d] = [self.a, self.b, self.d].map(|v| i64::try_from(v).ok());
105        let num = a?.checked_mul(n)?.checked_add(b?)?;
106        let d = d?;
107        let (floor, rem) = (num.div_euclid(d), num.rem_euclid(d));
108        Some(match self.between {
109            Between::Exact | Between::Floor => floor,
110            Between::Even => match rem.cmp(&(d - rem)) {
111                std::cmp::Ordering::Less => floor,
112                std::cmp::Ordering::Greater => floor + 1,
113                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
114            },
115        })
116    }
117
118    fn whole_at(self, n: i64) -> Option<i64> {
119        if self.d != 1 {
120            return None;
121        }
122        let (a, b) = (i64::try_from(self.a).ok()?, i64::try_from(self.b).ok()?);
123        a.checked_mul(n)?.checked_add(b)
124    }
125
126    pub fn ahead(self) -> bool {
127        self.a != self.d || self.lead() > 0
128    }
129
130    pub fn lead(self) -> i64 {
131        let most = match self.between {
132            Between::Exact | Between::Floor => self.b.div_euclid(self.d),
133            Between::Even => (2 * self.b + self.d).div_euclid(2 * self.d),
134        };
135        most.clamp(i128::from(i64::MIN / 2), i128::from(i64::MAX / 2)) as i64
136    }
137
138    pub fn least(self) -> i64 {
139        let least = match self.between {
140            Between::Exact | Between::Floor => self.b.div_euclid(self.d),
141            Between::Even => (2 * self.b + self.d - 1).div_euclid(2 * self.d),
142        };
143        least.clamp(i128::from(i64::MIN / 2), i128::from(i64::MAX / 2)) as i64
144    }
145
146    pub fn image(self, over: Extent) -> Extent {
147        if over.is_empty() {
148            return over;
149        }
150        if self.a == 0 {
151            let at = self.at(0);
152            return Extent::new(at, at.saturating_add(1));
153        }
154        let ends = (first(over), last(over));
155        let at = |n: Option<i128>| n.map(|n| self.index_at(n));
156        let (lo, hi) = match self.a > 0 {
157            true => (at(ends.0), at(ends.1)),
158            false => (at(ends.1), at(ends.0)),
159        };
160        extent(lo, hi.map(|h| h + 1))
161    }
162
163    /// The reader samples whose reading lands in `into`. Rounding to even reads at most one
164    /// below rounding half up.
165    pub fn preimage(self, into: Extent) -> Extent {
166        if into.is_empty() {
167            return into;
168        }
169        if self.between == Between::Even {
170            let up = Map {
171                a: 2 * self.a,
172                b: 2 * self.b + self.d,
173                d: 2 * self.d,
174                between: Between::Floor,
175            };
176            let end = match into.end {
177                i64::MAX => i64::MAX,
178                e => e.saturating_add(1),
179            };
180            return up.preimage(Extent::new(into.start, end));
181        }
182        if self.a == 0 {
183            return match into.contains(self.at(0)) {
184                true => Extent::EVERYWHERE,
185                false => Extent::NOWHERE,
186            };
187        }
188        let (a, b, d) = (self.a, self.b, self.d);
189        let (first, last) = (first(into), last(into));
190        let lowest = |m: i128| ceil_div(m * d - b, a);
191        let highest = |m: i128| floor_div((m + 1) * d - 1 - b, a);
192        let (lo, hi) = match self.a > 0 {
193            true => (first.map(lowest), last.map(highest)),
194            false => (
195                last.map(|m| ceil_div((m + 1) * d - 1 - b, a)),
196                first.map(|m| floor_div(m * d - b, a)),
197            ),
198        };
199        extent(lo, hi.map(|h| h + 1))
200    }
201
202    fn index_at(self, n: i128) -> i128 {
203        #[cfg(test)]
204        WIDE.with(|w| w.set(w.get() + 1));
205        let num = self.a.saturating_mul(n).saturating_add(self.b);
206        let (floor, rem) = (num.div_euclid(self.d), num.rem_euclid(self.d));
207        match self.between {
208            Between::Exact | Between::Floor => floor,
209            Between::Even => match (2 * rem).cmp(&self.d) {
210                std::cmp::Ordering::Less => floor,
211                std::cmp::Ordering::Greater => floor + 1,
212                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
213            },
214        }
215    }
216}
217
218fn gcd(a: i128, b: i128) -> i128 {
219    match b {
220        0 => a,
221        b => gcd(b, a % b),
222    }
223}
224
225fn first(e: Extent) -> Option<i128> {
226    (e.start != i64::MIN).then(|| i128::from(e.start))
227}
228
229fn last(e: Extent) -> Option<i128> {
230    (e.end != i64::MAX).then(|| i128::from(e.end) - 1)
231}
232
233fn floor_div(num: i128, den: i128) -> i128 {
234    match den < 0 {
235        true => (-num).div_euclid(-den),
236        false => num.div_euclid(den),
237    }
238}
239
240fn ceil_div(num: i128, den: i128) -> i128 {
241    -floor_div(-num, den)
242}
243
244fn extent(lo: Option<i128>, hi: Option<i128>) -> Extent {
245    let clamp = |n: i128| n.clamp(i128::from(i64::MIN + 1), i128::from(i64::MAX - 1)) as i64;
246    let (start, end) = (lo.map_or(i64::MIN, clamp), hi.map_or(i64::MAX, clamp));
247    match start < end {
248        true => Extent::new(start, end),
249        false => Extent::NOWHERE,
250    }
251}
252
253/// Sample `n` stands at `a*n/d` samples of `rate`, in lowest terms, `a, d > 0`: every grid
254/// starts at t = 0, so a sample index is the one clock every read and key counts in.
255#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
256pub struct Grid {
257    pub rate: u32,
258    pub a: i128,
259    pub d: i128,
260}
261
262impl Grid {
263    pub const fn of(rate: u32) -> Grid {
264        Grid { rate, a: 1, d: 1 }
265    }
266
267    /// `scale` samples to each of `rate`'s, as an alias reference reads it.
268    pub fn finer(rate: u32, scale: usize) -> Grid {
269        Grid {
270            rate,
271            a: 1,
272            d: scale as i128,
273        }
274    }
275
276    pub fn is_rate(&self) -> bool {
277        (self.a, self.d) == (1, 1)
278    }
279
280    fn exact(&self, n: i64) -> Option<(i128, i128)> {
281        let num = self.a.checked_mul(i128::from(n))?;
282        Some((num, self.d.checked_mul(i128::from(self.rate))?))
283    }
284
285    /// One quotient: correctly rounded while both integers are under 2^53; past that each
286    /// integer rounds once converting and the quotient once more.
287    pub fn instant(&self, n: i64) -> f64 {
288        if self.is_rate() {
289            return n as f64 / f64::from(self.rate);
290        }
291        let num = self.a.saturating_mul(i128::from(n));
292        num as f64 / self.d.saturating_mul(i128::from(self.rate)) as f64
293    }
294
295    pub fn position(&self, n: i64) -> f64 {
296        self.a.saturating_mul(i128::from(n)) as f64 / self.d as f64
297    }
298
299    pub fn sr(&self) -> f64 {
300        f64::from(self.rate) * self.d as f64 / self.a as f64
301    }
302
303    pub fn count(&self, t: f64) -> f64 {
304        t * f64::from(self.rate) * self.d as f64 / self.a as f64
305    }
306
307    /// The step `t` falls nearest, rounded exactly from `t`'s own binary value; `None` where
308    /// that is past what the integers hold.
309    pub fn step_at(&self, t: f64, round: Round) -> Option<i64> {
310        match self.is_rate() {
311            true => rated(t, self.rate, round).or_else(|| self.wide(t, round)),
312            false => self.wide(t, round),
313        }
314    }
315
316    fn wide(&self, t: f64, round: Round) -> Option<i64> {
317        #[cfg(test)]
318        WIDE.with(|w| w.set(w.get() + 1));
319        if !t.is_finite() {
320            return None;
321        }
322        let bits = t.abs().to_bits();
323        let (exp, frac) = ((bits >> 52) as i32, (bits & ((1 << 52) - 1)) as i128);
324        let (mantissa, shift) = match exp {
325            0 => (frac, 1074),
326            e => (frac | (1 << 52), 1075 - e),
327        };
328        let zeros = mantissa.trailing_zeros().min(127) as i32;
329        let (mantissa, shift) = match mantissa {
330            0 => (0, 0),
331            m => (m >> zeros, shift - zeros),
332        };
333        let mantissa = if t < 0.0 { -mantissa } else { mantissa };
334        let (num, den) = match shift {
335            s if s <= 0 => (mantissa.checked_mul(1i128.checked_shl((-s) as u32)?)?, 1),
336            s if s < 127 => (mantissa, 1i128 << s),
337            _ => return None,
338        };
339        let top = num.checked_mul(self.d.checked_mul(i128::from(self.rate))?)?;
340        let bottom = self.a.checked_mul(den)?;
341        let (floor, rem) = (top.div_euclid(bottom), top.rem_euclid(bottom));
342        let k = match round {
343            Round::Floor => floor,
344            Round::Ceil => floor + i128::from(rem != 0),
345            Round::Even => match (2 * rem).cmp(&bottom) {
346                std::cmp::Ordering::Less => floor,
347                std::cmp::Ordering::Greater => floor + 1,
348                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
349            },
350        };
351        i64::try_from(k).ok()
352    }
353}
354
355/// `t*rate` rounded from the double nearest it and its exact error: halves of `t` times `rate`
356/// exactly, summed exactly; the error decides only a sum on a whole or half step. `None` where
357/// `wide` may refuse or a product could round.
358fn rated(t: f64, rate: u32, round: Round) -> Option<i64> {
359    let held = t == 0.0 || (2f64.powi(-74)..2f64.powi(51)).contains(&t.abs());
360    if !held || rate >= 1 << 26 {
361        return None;
362    }
363    let r = f64::from(rate);
364    let split = 134_217_729.0 * t;
365    let hi = split - (split - t);
366    let (x, y) = (hi * r, (t - hi) * r);
367    let p = x + y;
368    let back = p - x;
369    let e = (x - (p - back)) + (y - back);
370    if p.abs() >= 2f64.powi(51) {
371        return None;
372    }
373    let f = p.floor();
374    let k = match round {
375        Round::Floor if p == f && e < 0.0 => f - 1.0,
376        Round::Floor => f,
377        Round::Ceil if p == p.ceil() && e > 0.0 => p + 1.0,
378        Round::Ceil => p.ceil(),
379        Round::Even => match (p - f).total_cmp(&0.5) {
380            std::cmp::Ordering::Less => f,
381            std::cmp::Ordering::Greater => f + 1.0,
382            std::cmp::Ordering::Equal if e > 0.0 => f + 1.0,
383            std::cmp::Ordering::Equal if e < 0.0 => f,
384            std::cmp::Ordering::Equal => f + f.rem_euclid(2.0),
385        },
386    };
387    Some(k as i64)
388}
389
390#[cfg(test)]
391thread_local! {
392    pub(super) static WIDE: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
393}
394
395/// `scale*t + shift + gain*((inner_scale*t + inner_shift) mod period)` at sample `n`'s
396/// instant, each a rational `(num, den)` with `den > 0` and `period > 0`. The remainder and
397/// the sum are integers over one denominator, so which side of a jump an instant falls on is
398/// decided exactly and only the quotient that states the sum rounds.
399#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
400pub struct Wrap {
401    pub scale: (i128, i128),
402    pub shift: (i128, i128),
403    pub gain: (i128, i128),
404    pub inner: [(i128, i128); 2],
405    pub period: (i128, i128),
406}
407
408impl Wrap {
409    /// `None` where its constants overflow.
410    pub fn on(self, grid: Grid) -> Option<Stepped> {
411        let (_, rate) = grid.exact(0)?;
412        let [(s, sd), (o, od)] = self.inner;
413        let over = sd.checked_mul(rate)?;
414        let den = lcm(lcm(over, od)?, self.period.1)?;
415        let line = self.scale.1.checked_mul(rate)?;
416        let wrapped = self.gain.1.checked_mul(den)?;
417        let whole = lcm(lcm(line, self.shift.1)?, wrapped)?;
418        Some(Stepped {
419            step: grid.a,
420            inner: s,
421            over: den / over,
422            offset: o.checked_mul(den / od)?,
423            period: self.period.0.checked_mul(den / self.period.1)?,
424            scale: self.scale.0,
425            line: whole / line,
426            shift: self.shift.0.checked_mul(whole / self.shift.1)?,
427            gain: self.gain.0,
428            wrapped: whole / wrapped,
429            whole,
430        })
431    }
432
433    /// The largest magnitude it takes over instants no later than `t`.
434    pub fn most(self, t: f64) -> f64 {
435        let q = |(num, den): (i128, i128)| num as f64 / den as f64;
436        q(self.scale).abs() * t.abs() + q(self.shift).abs() + q(self.gain).abs() * q(self.period)
437    }
438}
439
440#[derive(Clone, Copy, Debug, PartialEq, Eq)]
441pub struct Stepped {
442    step: i128,
443    inner: i128,
444    over: i128,
445    offset: i128,
446    period: i128,
447    scale: i128,
448    line: i128,
449    shift: i128,
450    gain: i128,
451    wrapped: i128,
452    whole: i128,
453}
454
455impl Stepped {
456    /// `None` where the integers it is computed in would overflow.
457    pub fn at(self, n: i64) -> Option<f64> {
458        let n = self.step.checked_mul(i128::from(n))?;
459        let x = self
460            .inner
461            .checked_mul(n)?
462            .checked_mul(self.over)?
463            .checked_add(self.offset)?;
464        let rem = x.rem_euclid(self.period);
465        let sum = self
466            .scale
467            .checked_mul(n)?
468            .checked_mul(self.line)?
469            .checked_add(self.shift)?
470            .checked_add(self.gain.checked_mul(rem)?.checked_mul(self.wrapped)?)?;
471        Some(sum as f64 / self.whole as f64)
472    }
473}
474
475fn lcm(a: i128, b: i128) -> Option<i128> {
476    (a / gcd(a, b)).checked_mul(b)
477}
478
479#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
480pub enum Round {
481    Even,
482    Floor,
483    Ceil,
484}
485
486/// An integer every sample evaluates exactly, `None` past `i64`.
487#[derive(Clone, Debug, PartialEq)]
488pub enum Index<T = Box<NodeRenderer>> {
489    At(Map),
490    Step(T, Round),
491    Add(Vec<Index<T>>),
492    Neg(Box<Index<T>>),
493    Mul(Vec<Index<T>>),
494}
495
496impl<T> Index<T> {
497    pub fn times(&self) -> Vec<&T> {
498        let mut out = Vec::new();
499        self.each(&mut |t| out.push(t));
500        out
501    }
502
503    fn each<'a>(&'a self, f: &mut impl FnMut(&'a T)) {
504        match self {
505            Index::At(_) => {}
506            Index::Step(t, _) => f(t),
507            Index::Add(parts) | Index::Mul(parts) => parts.iter().for_each(|p| p.each(f)),
508            Index::Neg(p) => p.each(f),
509        }
510    }
511
512    pub fn mapped<U, E>(&self, f: &mut impl FnMut(&T) -> Result<U, E>) -> Result<Index<U>, E> {
513        let mut each = |parts: &[Index<T>]| -> Result<Vec<Index<U>>, E> {
514            parts.iter().map(|p| p.mapped(f)).collect()
515        };
516        Ok(match self {
517            Index::At(map) => Index::At(*map),
518            Index::Step(t, round) => Index::Step(f(t)?, *round),
519            Index::Add(parts) => Index::Add(each(parts)?),
520            Index::Mul(parts) => Index::Mul(each(parts)?),
521            Index::Neg(p) => Index::Neg(Box::new(p.mapped(f)?)),
522        })
523    }
524
525    pub fn at(&self, n: i64, grid: Grid, time: &impl Fn(&T) -> f64) -> Option<i64> {
526        match self {
527            Index::At(map) => map
528                .narrow_at(n)
529                .or_else(|| i64::try_from(map.index_at(i128::from(n))).ok()),
530            Index::Step(t, round) => grid.step_at(time(t), *round),
531            Index::Add(parts) => parts
532                .iter()
533                .try_fold(0i64, |held, p| held.checked_add(p.at(n, grid, time)?)),
534            Index::Mul(parts) => parts
535                .iter()
536                .try_fold(1i64, |held, p| held.checked_mul(p.at(n, grid, time)?)),
537            Index::Neg(p) => p.at(n, grid, time)?.checked_neg(),
538        }
539    }
540}
541
542/// Another node's samples, or this node's own past.
543#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
544pub enum Slot {
545    Read(BufId),
546    Own,
547}
548
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
550pub enum Unary {
551    Sin,
552    Cos,
553    Exp,
554    Sqrt,
555    Abs,
556    Tanh,
557    Log,
558    Sat,
559    Step,
560}
561
562#[derive(Clone, Copy, Debug, PartialEq, Eq)]
563pub enum Binary {
564    Max,
565    Min,
566    Mod,
567}
568
569/// What the machine evaluates at an instant it computes, a closed form truncated to the band
570/// or the noise.
571#[derive(Clone, Debug, PartialEq)]
572pub enum Formula {
573    Sum(Box<SpectralSum>),
574    Written(Box<Written>),
575    Drawn { seed: u64, rate: u32 },
576}
577
578/// A written form and each form it reads, `Body::Node(k)` in either naming `refs[k]`.
579#[derive(Clone, Debug, PartialEq)]
580pub struct Written {
581    pub body: Body,
582    pub refs: Vec<Body>,
583}
584
585impl Formula {
586    pub fn at(&self, component: usize, t: f64) -> Result<f64, CollapseError> {
587        let value: C64 =
588            match self {
589                Formula::Drawn { seed, rate } => {
590                    let step = Grid::of(*rate).step_at(t, Round::Even).ok_or(
591                        CollapseError::NotEvaluable("a draw at an instant past any step"),
592                    )?;
593                    return Ok(sva_formula::draw(*seed, step));
594                }
595                Formula::Sum(sum) => crate::collapse::eval_spectral_sum_at(sum, component, t)?,
596                Formula::Written(written) => crate::collapse::eval_written_at(
597                    &written.body,
598                    component,
599                    t,
600                    &crate::collapse::Shared::new(&written.refs),
601                )?,
602            };
603        Ok(value.re)
604    }
605
606    /// One operation per atom or written subterm, each read of a ref counting its form's.
607    pub fn ops(&self) -> usize {
608        fn terms(body: &Body, refs: &[usize]) -> usize {
609            match body {
610                Body::Node(id) => refs[id.0 as usize],
611                Body::Banded(b) => (b.widest.max(0) as usize)
612                    .saturating_mul(terms(&b.series.term.body, refs))
613                    .saturating_add(2),
614                _ => sva_formula::closed_form::children(body)
615                    .iter()
616                    .fold(1usize, |held, p| held.saturating_add(terms(&p.body, refs))),
617            }
618        }
619        match self {
620            Formula::Sum(sum) => sum.atoms().count().max(1),
621            Formula::Written(written) => {
622                let mut counted = Vec::with_capacity(written.refs.len());
623                for body in &written.refs {
624                    counted.push(terms(body, &counted));
625                }
626                terms(&written.body, &counted)
627            }
628            Formula::Drawn { .. } => 1,
629        }
630    }
631}
632
633/// Every closed form-typed subterm was collapsed to a buffer, inlined or held as a formula
634/// before this tree was built, so there is no oscillator, no series and no delta here.
635#[derive(Clone, Debug, PartialEq)]
636pub enum NodeRenderer {
637    Const(f64),
638    Time,
639    Wrap(Wrap),
640    Read {
641        slot: Slot,
642        map: Map,
643    },
644    /// A closed form at the instant `time` names, `width` components wide.
645    Formula {
646        formula: Formula,
647        width: usize,
648        time: Box<NodeRenderer>,
649    },
650    Noise(u64),
651    /// The stored sample at the integer `index` names, within `reach` of the sample being
652    /// written where that is known.
653    Indexed {
654        slot: Slot,
655        index: Index,
656        reach: Option<(i64, i64)>,
657    },
658    /// The instant of the sample `index` names.
659    Instant(Index),
660    Add(Vec<NodeRenderer>),
661    Mul(Vec<NodeRenderer>),
662    Sub(Box<NodeRenderer>, Box<NodeRenderer>),
663    Div(Box<NodeRenderer>, Box<NodeRenderer>),
664    Pow(Box<NodeRenderer>, Box<NodeRenderer>),
665    Map(Unary, Box<NodeRenderer>),
666    Zip(Binary, Box<NodeRenderer>, Box<NodeRenderer>),
667    /// `x` over the samples `window` holds, the instants `[a, b)` it was written as, with a
668    /// raised-cosine `rise` and `fall` inside it; zero at every sample outside.
669    Crop {
670        x: Box<NodeRenderer>,
671        window: (i64, i64),
672        a: f64,
673        b: f64,
674        rise: f64,
675        fall: f64,
676    },
677    Join(Vec<NodeRenderer>),
678    Channel {
679        x: Box<NodeRenderer>,
680        k: usize,
681    },
682    /// Zero, stepping nothing, before `from`, where its state starts.
683    Filter {
684        site: SiteId,
685        from: i64,
686        x: Box<NodeRenderer>,
687        cutoff: Box<NodeRenderer>,
688        q: Box<NodeRenderer>,
689        gain: Box<NodeRenderer>,
690    },
691    Physics {
692        site: SiteId,
693        from: i64,
694        args: Vec<NodeRenderer>,
695    },
696}
697
698impl NodeRenderer {
699    /// Holds no call site and reads none of its own past, so skipping a sample of it changes
700    /// no later one.
701    pub fn stateless(&self) -> bool {
702        !self.holds_state() && self.operands().into_iter().all(NodeRenderer::stateless)
703    }
704
705    pub(crate) fn holds_state(&self) -> bool {
706        matches!(
707            self,
708            NodeRenderer::Filter { .. }
709                | NodeRenderer::Physics { .. }
710                | NodeRenderer::Read {
711                    slot: Slot::Own,
712                    ..
713                }
714                | NodeRenderer::Indexed {
715                    slot: Slot::Own,
716                    ..
717                }
718        )
719    }
720
721    /// Every operand in the order the op array lowers them.
722    pub(crate) fn operands(&self) -> Vec<&NodeRenderer> {
723        match self {
724            NodeRenderer::Add(set) | NodeRenderer::Mul(set) | NodeRenderer::Join(set) => {
725                set.iter().collect()
726            }
727            NodeRenderer::Sub(a, b)
728            | NodeRenderer::Div(a, b)
729            | NodeRenderer::Pow(a, b)
730            | NodeRenderer::Zip(_, a, b) => vec![a, b],
731            NodeRenderer::Map(_, x)
732            | NodeRenderer::Crop { x, .. }
733            | NodeRenderer::Channel { x, .. } => vec![x],
734            NodeRenderer::Filter {
735                x, cutoff, q, gain, ..
736            } => vec![x, cutoff, q, gain],
737            NodeRenderer::Physics { args, .. } => args.iter().collect(),
738            NodeRenderer::Formula { time, .. } => vec![time],
739            NodeRenderer::Indexed { index, .. } | NodeRenderer::Instant(index) => {
740                index.times().into_iter().map(|t| &**t).collect()
741            }
742            NodeRenderer::Const(_)
743            | NodeRenderer::Time
744            | NodeRenderer::Wrap(_)
745            | NodeRenderer::Noise(_)
746            | NodeRenderer::Read { .. } => Vec::new(),
747        }
748    }
749}
750
751#[derive(Clone, Debug, PartialEq)]
752pub enum Site {
753    Filter(Shape),
754    Physics(Box<Params>),
755}
756
757impl From<sva_formula::Unary> for Unary {
758    fn from(written: sva_formula::Unary) -> Unary {
759        match written {
760            sva_formula::Unary::Sin => Unary::Sin,
761            sva_formula::Unary::Cos => Unary::Cos,
762            sva_formula::Unary::Exp => Unary::Exp,
763            sva_formula::Unary::Sqrt => Unary::Sqrt,
764            sva_formula::Unary::Abs => Unary::Abs,
765            sva_formula::Unary::Tanh => Unary::Tanh,
766            sva_formula::Unary::Log => Unary::Log,
767            sva_formula::Unary::Sat => Unary::Sat,
768            sva_formula::Unary::Step => Unary::Step,
769        }
770    }
771}
772
773impl Unary {
774    pub fn apply(self, x: f64) -> f64 {
775        match self {
776            Unary::Sin => x.sin(),
777            Unary::Cos => x.cos(),
778            Unary::Exp => x.exp(),
779            Unary::Sqrt => x.sqrt(),
780            Unary::Abs => x.abs(),
781            Unary::Tanh => x.tanh(),
782            Unary::Log => x.ln(),
783            Unary::Sat => x.clamp(-1.0, 1.0),
784            Unary::Step => sva_formula::affine::step(x),
785        }
786    }
787}
788
789impl Binary {
790    pub fn apply(self, a: f64, b: f64) -> f64 {
791        match self {
792            Binary::Max => a.max(b),
793            Binary::Min => a.min(b),
794            Binary::Mod => a.rem_euclid(b),
795        }
796    }
797}
798
799#[cfg(test)]
800mod tests {
801    use super::{Grid, Map, Round, rated};
802
803    /// Doubles at, near and far from whole and half steps.
804    fn instants(rate: u32) -> Vec<f64> {
805        let r = f64::from(rate);
806        let mut out = vec![0.0, -0.0, 1e-300, -1e-300, 1e300, f64::MIN_POSITIVE];
807        let mut seed = 0x9e37_79b9_7f4a_7c15u64;
808        for k in -2_000i64..2_000 {
809            for half in [0.0, 0.5] {
810                let at = (k as f64 + half) / r;
811                let mut near = at;
812                for _ in 0..3 {
813                    near = near.next_up();
814                    out.push(near);
815                }
816                near = at;
817                for _ in 0..3 {
818                    near = near.next_down();
819                    out.push(near);
820                }
821                out.push(at);
822                out.push(at * 1e9);
823            }
824            seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
825            out.push(f64::from_bits(seed >> 2) * if seed & 1 == 0 { 1.0 } else { -1.0 });
826        }
827        out
828    }
829
830    #[test]
831    fn a_step_rounds_alike_in_doubles_and_in_wide_integers() {
832        for rate in [1, 8_000, 44_100, 48_000, 88_200, 96_000, 192_000] {
833            let grid = Grid::of(rate);
834            for t in instants(rate) {
835                for round in [Round::Floor, Round::Ceil, Round::Even] {
836                    if let Some(k) = rated(t, rate, round) {
837                        assert_eq!(Some(k), grid.wide(t, round), "{t:e} at {rate} {round:?}");
838                    }
839                }
840            }
841        }
842    }
843
844    #[test]
845    fn a_rounded_map_reads_alike_narrow_and_wide() {
846        for (a, b, d) in [(1, 0, 2), (3, -7, 4), (-5, 11, 6), (2, 1, 4), (7, 3, 2)] {
847            for between in [super::Between::Floor, super::Between::Even] {
848                let Some(map) = Map::rounded(a, b, d, between) else {
849                    continue;
850                };
851                for n in -50..50 {
852                    let wide = i64::try_from(map.index_at(i128::from(n))).ok();
853                    assert_eq!(map.narrow_at(n), wide, "{map:?} at {n}");
854                }
855            }
856        }
857    }
858}