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 std::sync::Arc;
4
5use sva_formula::{Body, Shape};
6
7use crate::collapse::Rows;
8use crate::grid::{Extent, Grid, Round};
9use crate::physics::Params;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
12pub struct BufId(pub u32);
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
15pub struct SiteId(pub u32);
16
17/// Reader sample `n` reads source sample `(a*n + b)/d`, `d > 0`: exactly, or rounded down or
18/// to the nearest, ties to even. An exact map is whole, `d = 1`; nothing reads between samples.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct Map {
21    pub a: i128,
22    pub b: i128,
23    pub d: i128,
24    pub between: Between,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub enum Between {
29    Exact,
30    Floor,
31    Even,
32}
33
34impl Map {
35    pub const fn shift(b: i64) -> Map {
36        Map::whole(1, b)
37    }
38
39    /// Reader sample `n` reads source sample `a*n + b`.
40    pub const fn whole(a: i128, b: i64) -> Map {
41        Map {
42            a,
43            b: b as i128,
44            d: 1,
45            between: Between::Exact,
46        }
47    }
48
49    /// Lowest terms; `None` where a sample lands between two, or past what a map holds.
50    pub fn new(a: i128, b: i128, d: i128) -> Option<Map> {
51        Map::rounded(a, b, d, Between::Exact)
52    }
53
54    /// A rounding every sample takes alike is folded into an exact map.
55    pub fn rounded(a: i128, b: i128, d: i128, between: Between) -> Option<Map> {
56        let g = gcd(gcd(a.abs(), b.abs()), d.abs()).max(1);
57        let sign = d.signum();
58        let limit = 1i128 << 100;
59        let map = Map {
60            a: sign * a / g,
61            b: sign * b / g,
62            d: d.abs() / g,
63            between,
64        };
65        let held = map.d > 0 && map.a.abs() < limit && map.b.abs() < limit && map.d < limit;
66        held.then(|| map.settled())
67            .filter(|m| m.d == 1 || m.between != Between::Exact)
68    }
69
70    fn settled(self) -> Map {
71        let exact = match self.between {
72            _ if self.d == 1 => true,
73            Between::Exact => false,
74            Between::Floor => self.a % self.d == 0,
75            Between::Even => {
76                let tie = 2 * self.b.rem_euclid(self.d) == self.d;
77                self.a % self.d == 0 && (!tie || (self.a / self.d) % 2 == 0)
78            }
79        };
80        match exact {
81            true => Map {
82                a: self.a / self.d,
83                b: self.index_at(0),
84                d: 1,
85                between: Between::Exact,
86            },
87            false => self,
88        }
89    }
90
91    pub fn moved(self) -> Option<i64> {
92        (self.a == 1 && self.d == 1).then(|| i64::try_from(self.b).ok())?
93    }
94
95    pub fn at(self, n: i64) -> i64 {
96        let clamp = |k: i128| k.clamp(i128::from(i64::MIN), i128::from(i64::MAX)) as i64;
97        self.narrow_at(n)
98            .unwrap_or_else(|| clamp(self.index_at(i128::from(n))))
99    }
100
101    /// `index_at` in `i64`, where every step of it fits one.
102    fn narrow_at(self, n: i64) -> Option<i64> {
103        if let Some(k) = self.whole_at(n) {
104            return Some(k);
105        }
106        let [a, b, d] = [self.a, self.b, self.d].map(|v| i64::try_from(v).ok());
107        let num = a?.checked_mul(n)?.checked_add(b?)?;
108        let d = d?;
109        let (floor, rem) = (num.div_euclid(d), num.rem_euclid(d));
110        Some(match self.between {
111            Between::Exact | Between::Floor => floor,
112            Between::Even => match rem.cmp(&(d - rem)) {
113                std::cmp::Ordering::Less => floor,
114                std::cmp::Ordering::Greater => floor + 1,
115                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
116            },
117        })
118    }
119
120    fn whole_at(self, n: i64) -> Option<i64> {
121        if self.d != 1 {
122            return None;
123        }
124        let (a, b) = (i64::try_from(self.a).ok()?, i64::try_from(self.b).ok()?);
125        a.checked_mul(n)?.checked_add(b)
126    }
127
128    pub fn ahead(self) -> bool {
129        self.a != self.d || self.lead() > 0
130    }
131
132    pub fn lead(self) -> i64 {
133        let most = match self.between {
134            Between::Exact | Between::Floor => self.b.div_euclid(self.d),
135            Between::Even => (2 * self.b + self.d).div_euclid(2 * self.d),
136        };
137        most.clamp(i128::from(i64::MIN / 2), i128::from(i64::MAX / 2)) as i64
138    }
139
140    pub fn least(self) -> i64 {
141        let least = match self.between {
142            Between::Exact | Between::Floor => self.b.div_euclid(self.d),
143            Between::Even => (2 * self.b + self.d - 1).div_euclid(2 * self.d),
144        };
145        least.clamp(i128::from(i64::MIN / 2), i128::from(i64::MAX / 2)) as i64
146    }
147
148    pub fn image(self, over: Extent) -> Extent {
149        if over.is_empty() {
150            return over;
151        }
152        if self.a == 0 {
153            let at = self.at(0);
154            return Extent::new(at, at.saturating_add(1));
155        }
156        let ends = (first(over), last(over));
157        let at = |n: Option<i128>| n.map(|n| self.index_at(n));
158        let (lo, hi) = match self.a > 0 {
159            true => (at(ends.0), at(ends.1)),
160            false => (at(ends.1), at(ends.0)),
161        };
162        extent(lo, hi.map(|h| h + 1))
163    }
164
165    /// The reader samples whose reading lands in `into`. Rounding to even reads at most one
166    /// below rounding half up.
167    pub fn preimage(self, into: Extent) -> Extent {
168        if into.is_empty() {
169            return into;
170        }
171        if self.between == Between::Even {
172            let up = Map {
173                a: 2 * self.a,
174                b: 2 * self.b + self.d,
175                d: 2 * self.d,
176                between: Between::Floor,
177            };
178            let end = match into.end {
179                i64::MAX => i64::MAX,
180                e => e.saturating_add(1),
181            };
182            return up.preimage(Extent::new(into.start, end));
183        }
184        if self.a == 0 {
185            return match into.contains(self.at(0)) {
186                true => Extent::EVERYWHERE,
187                false => Extent::NOWHERE,
188            };
189        }
190        let (a, b, d) = (self.a, self.b, self.d);
191        let (first, last) = (first(into), last(into));
192        let lowest = |m: i128| ceil_div(m * d - b, a);
193        let highest = |m: i128| floor_div((m + 1) * d - 1 - b, a);
194        let (lo, hi) = match self.a > 0 {
195            true => (first.map(lowest), last.map(highest)),
196            false => (
197                last.map(|m| ceil_div((m + 1) * d - 1 - b, a)),
198                first.map(|m| floor_div(m * d - b, a)),
199            ),
200        };
201        extent(lo, hi.map(|h| h + 1))
202    }
203
204    fn index_at(self, n: i128) -> i128 {
205        #[cfg(test)]
206        crate::grid::WIDE.with(|w| w.set(w.get() + 1));
207        let num = self.a.saturating_mul(n).saturating_add(self.b);
208        let (floor, rem) = (num.div_euclid(self.d), num.rem_euclid(self.d));
209        match self.between {
210            Between::Exact | Between::Floor => floor,
211            Between::Even => match (2 * rem).cmp(&self.d) {
212                std::cmp::Ordering::Less => floor,
213                std::cmp::Ordering::Greater => floor + 1,
214                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
215            },
216        }
217    }
218}
219
220fn gcd(a: i128, b: i128) -> i128 {
221    match b {
222        0 => a,
223        b => gcd(b, a % b),
224    }
225}
226
227fn first(e: Extent) -> Option<i128> {
228    (e.start != i64::MIN).then(|| i128::from(e.start))
229}
230
231fn last(e: Extent) -> Option<i128> {
232    (e.end != i64::MAX).then(|| i128::from(e.end) - 1)
233}
234
235fn floor_div(num: i128, den: i128) -> i128 {
236    match den < 0 {
237        true => (-num).div_euclid(-den),
238        false => num.div_euclid(den),
239    }
240}
241
242fn ceil_div(num: i128, den: i128) -> i128 {
243    -floor_div(-num, den)
244}
245
246fn extent(lo: Option<i128>, hi: Option<i128>) -> Extent {
247    let clamp = |n: i128| n.clamp(i128::from(i64::MIN + 1), i128::from(i64::MAX - 1)) as i64;
248    let (start, end) = (lo.map_or(i64::MIN, clamp), hi.map_or(i64::MAX, clamp));
249    match start < end {
250        true => Extent::new(start, end),
251        false => Extent::NOWHERE,
252    }
253}
254
255/// `scale*t + shift + gain*((inner_scale*t + inner_shift) mod period)` at sample `n`'s
256/// instant, each a rational `(num, den)` with `den > 0` and `period > 0`. The remainder and
257/// the sum are integers over one denominator, so which side of a jump an instant falls on is
258/// decided exactly and only the quotient that states the sum rounds.
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
260pub struct Wrap {
261    pub scale: (i128, i128),
262    pub shift: (i128, i128),
263    pub gain: (i128, i128),
264    pub inner: [(i128, i128); 2],
265    pub period: (i128, i128),
266}
267
268impl Wrap {
269    /// `None` where its constants overflow.
270    pub fn on(self, grid: Grid) -> Option<Stepped> {
271        let (_, rate) = grid.exact(0)?;
272        let [(s, sd), (o, od)] = self.inner;
273        let over = sd.checked_mul(rate)?;
274        let den = lcm(lcm(over, od)?, self.period.1)?;
275        let line = self.scale.1.checked_mul(rate)?;
276        let wrapped = self.gain.1.checked_mul(den)?;
277        let whole = lcm(lcm(line, self.shift.1)?, wrapped)?;
278        Some(Stepped {
279            step: grid.a,
280            inner: s,
281            over: den / over,
282            offset: o.checked_mul(den / od)?,
283            period: self.period.0.checked_mul(den / self.period.1)?,
284            scale: self.scale.0,
285            line: whole / line,
286            shift: self.shift.0.checked_mul(whole / self.shift.1)?,
287            gain: self.gain.0,
288            wrapped: whole / wrapped,
289            whole,
290        })
291    }
292
293    /// The largest magnitude it takes over instants no later than `t`.
294    pub fn most(self, t: f64) -> f64 {
295        let q = |(num, den): (i128, i128)| num as f64 / den as f64;
296        q(self.scale).abs() * t.abs() + q(self.shift).abs() + q(self.gain).abs() * q(self.period)
297    }
298}
299
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
301pub struct Stepped {
302    step: i128,
303    inner: i128,
304    over: i128,
305    offset: i128,
306    period: i128,
307    scale: i128,
308    line: i128,
309    shift: i128,
310    gain: i128,
311    wrapped: i128,
312    whole: i128,
313}
314
315impl Stepped {
316    /// `None` where the integers it is computed in would overflow.
317    pub fn at(self, n: i64) -> Option<f64> {
318        let n = self.step.checked_mul(i128::from(n))?;
319        let x = self
320            .inner
321            .checked_mul(n)?
322            .checked_mul(self.over)?
323            .checked_add(self.offset)?;
324        let rem = x.rem_euclid(self.period);
325        let sum = self
326            .scale
327            .checked_mul(n)?
328            .checked_mul(self.line)?
329            .checked_add(self.shift)?
330            .checked_add(self.gain.checked_mul(rem)?.checked_mul(self.wrapped)?)?;
331        Some(sum as f64 / self.whole as f64)
332    }
333}
334
335fn lcm(a: i128, b: i128) -> Option<i128> {
336    (a / gcd(a, b)).checked_mul(b)
337}
338
339/// An integer every sample evaluates exactly, `None` past `i64`.
340#[derive(Clone, Debug, PartialEq)]
341pub enum Index<T = Box<NodeRenderer>> {
342    At(Map),
343    Step(T, Round),
344    Add(Vec<Index<T>>),
345    Neg(Box<Index<T>>),
346    Mul(Vec<Index<T>>),
347}
348
349impl<T> Index<T> {
350    pub fn times(&self) -> Vec<&T> {
351        let mut out = Vec::new();
352        self.each(&mut |t| out.push(t));
353        out
354    }
355
356    fn each<'a>(&'a self, f: &mut impl FnMut(&'a T)) {
357        match self {
358            Index::At(_) => {}
359            Index::Step(t, _) => f(t),
360            Index::Add(parts) | Index::Mul(parts) => parts.iter().for_each(|p| p.each(f)),
361            Index::Neg(p) => p.each(f),
362        }
363    }
364
365    pub fn mapped<U, E>(&self, f: &mut impl FnMut(&T) -> Result<U, E>) -> Result<Index<U>, E> {
366        let mut each = |parts: &[Index<T>]| -> Result<Vec<Index<U>>, E> {
367            parts.iter().map(|p| p.mapped(f)).collect()
368        };
369        Ok(match self {
370            Index::At(map) => Index::At(*map),
371            Index::Step(t, round) => Index::Step(f(t)?, *round),
372            Index::Add(parts) => Index::Add(each(parts)?),
373            Index::Mul(parts) => Index::Mul(each(parts)?),
374            Index::Neg(p) => Index::Neg(Box::new(p.mapped(f)?)),
375        })
376    }
377
378    pub fn at(&self, n: i64, grid: Grid, time: &impl Fn(&T) -> f64) -> Option<i64> {
379        match self {
380            Index::At(map) => map
381                .narrow_at(n)
382                .or_else(|| i64::try_from(map.index_at(i128::from(n))).ok()),
383            Index::Step(t, round) => grid.step_at(time(t), *round),
384            Index::Add(parts) => parts
385                .iter()
386                .try_fold(0i64, |held, p| held.checked_add(p.at(n, grid, time)?)),
387            Index::Mul(parts) => parts
388                .iter()
389                .try_fold(1i64, |held, p| held.checked_mul(p.at(n, grid, time)?)),
390            Index::Neg(p) => p.at(n, grid, time)?.checked_neg(),
391        }
392    }
393}
394
395/// Another node's samples, or this node's own past.
396#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
397pub enum Slot {
398    Read(BufId),
399    Own,
400}
401
402#[derive(Clone, Copy, Debug, PartialEq, Eq)]
403pub enum Unary {
404    Sin,
405    Cos,
406    Exp,
407    Sqrt,
408    Abs,
409    Tanh,
410    Log,
411    Sat,
412    Step,
413}
414
415#[derive(Clone, Copy, Debug, PartialEq, Eq)]
416pub enum Binary {
417    Max,
418    Min,
419    Mod,
420}
421
422/// What the machine evaluates at an instant it computes: a closed form's own rows, a written
423/// form reading other forms, or the noise.
424#[derive(Clone, Debug)]
425pub enum Formula {
426    Rows(Arc<Rows>),
427    Written(Box<Written>),
428    Drawn { seed: u64, rate: u32 },
429}
430
431impl PartialEq for Formula {
432    fn eq(&self, other: &Formula) -> bool {
433        match (self, other) {
434            (Formula::Rows(a), Formula::Rows(b)) => Arc::ptr_eq(a, b),
435            (Formula::Written(a), Formula::Written(b)) => a == b,
436            (Formula::Drawn { seed, rate }, Formula::Drawn { seed: s, rate: r }) => {
437                (seed, rate) == (s, r)
438            }
439            _ => false,
440        }
441    }
442}
443
444/// A written form and each form it reads, `Body::Node(k)` in either naming `refs[k]`.
445#[derive(Clone, Debug, PartialEq)]
446pub struct Written {
447    pub body: Body,
448    pub refs: Vec<Body>,
449}
450
451/// Every closed form-typed subterm was collapsed to a buffer, inlined or held as a formula
452/// before this tree was built, so there is no oscillator, no series and no delta here.
453#[derive(Clone, Debug, PartialEq)]
454pub enum NodeRenderer {
455    Const(f64),
456    Time,
457    Wrap(Wrap),
458    Read {
459        slot: Slot,
460        map: Map,
461    },
462    /// A closed form at the instant `time` names, `width` components wide.
463    Formula {
464        formula: Formula,
465        width: usize,
466        time: Box<NodeRenderer>,
467    },
468    Noise(u64),
469    /// The stored sample at the integer `index` names, within `reach` of the sample being
470    /// written where that is known.
471    Indexed {
472        slot: Slot,
473        index: Index,
474        reach: Option<(i64, i64)>,
475    },
476    /// The instant of the sample `index` names.
477    Instant(Index),
478    Add(Vec<NodeRenderer>),
479    Mul(Vec<NodeRenderer>),
480    Sub(Box<NodeRenderer>, Box<NodeRenderer>),
481    Div(Box<NodeRenderer>, Box<NodeRenderer>),
482    Pow(Box<NodeRenderer>, Box<NodeRenderer>),
483    Map(Unary, Box<NodeRenderer>),
484    Zip(Binary, Box<NodeRenderer>, Box<NodeRenderer>),
485    /// `x` over the samples `window` holds, the instants `[a, b)` it was written as, with a
486    /// raised-cosine `rise` and `fall` inside it; zero at every sample outside.
487    Crop {
488        x: Box<NodeRenderer>,
489        window: (i64, i64),
490        a: f64,
491        b: f64,
492        rise: f64,
493        fall: f64,
494    },
495    Join(Vec<NodeRenderer>),
496    Channel {
497        x: Box<NodeRenderer>,
498        k: usize,
499    },
500    /// Zero, stepping nothing, before `from`, where its state starts.
501    Filter {
502        site: SiteId,
503        from: i64,
504        x: Box<NodeRenderer>,
505        cutoff: Box<NodeRenderer>,
506        q: Box<NodeRenderer>,
507        gain: Box<NodeRenderer>,
508    },
509    Physics {
510        site: SiteId,
511        from: i64,
512        args: Vec<NodeRenderer>,
513    },
514}
515
516impl NodeRenderer {
517    /// Holds no call site and reads none of its own past, so skipping a sample of it changes
518    /// no later one.
519    pub fn stateless(&self) -> bool {
520        !self.holds_state() && self.operands().into_iter().all(NodeRenderer::stateless)
521    }
522
523    pub(crate) fn holds_state(&self) -> bool {
524        matches!(
525            self,
526            NodeRenderer::Filter { .. }
527                | NodeRenderer::Physics { .. }
528                | NodeRenderer::Read {
529                    slot: Slot::Own,
530                    ..
531                }
532                | NodeRenderer::Indexed {
533                    slot: Slot::Own,
534                    ..
535                }
536        )
537    }
538
539    /// Every operand in the order the op array lowers them.
540    pub(crate) fn operands(&self) -> Vec<&NodeRenderer> {
541        match self {
542            NodeRenderer::Add(set) | NodeRenderer::Mul(set) | NodeRenderer::Join(set) => {
543                set.iter().collect()
544            }
545            NodeRenderer::Sub(a, b)
546            | NodeRenderer::Div(a, b)
547            | NodeRenderer::Pow(a, b)
548            | NodeRenderer::Zip(_, a, b) => vec![a, b],
549            NodeRenderer::Map(_, x)
550            | NodeRenderer::Crop { x, .. }
551            | NodeRenderer::Channel { x, .. } => vec![x],
552            NodeRenderer::Filter {
553                x, cutoff, q, gain, ..
554            } => vec![x, cutoff, q, gain],
555            NodeRenderer::Physics { args, .. } => args.iter().collect(),
556            NodeRenderer::Formula { time, .. } => vec![time],
557            NodeRenderer::Indexed { index, .. } | NodeRenderer::Instant(index) => {
558                index.times().into_iter().map(|t| &**t).collect()
559            }
560            NodeRenderer::Const(_)
561            | NodeRenderer::Time
562            | NodeRenderer::Wrap(_)
563            | NodeRenderer::Noise(_)
564            | NodeRenderer::Read { .. } => Vec::new(),
565        }
566    }
567}
568
569#[derive(Clone, Debug, PartialEq)]
570pub enum Site {
571    Filter(Shape),
572    Physics(Box<Params>),
573}
574
575impl From<sva_formula::Unary> for Unary {
576    fn from(written: sva_formula::Unary) -> Unary {
577        match written {
578            sva_formula::Unary::Sin => Unary::Sin,
579            sva_formula::Unary::Cos => Unary::Cos,
580            sva_formula::Unary::Exp => Unary::Exp,
581            sva_formula::Unary::Sqrt => Unary::Sqrt,
582            sva_formula::Unary::Abs => Unary::Abs,
583            sva_formula::Unary::Tanh => Unary::Tanh,
584            sva_formula::Unary::Log => Unary::Log,
585            sva_formula::Unary::Sat => Unary::Sat,
586            sva_formula::Unary::Step => Unary::Step,
587        }
588    }
589}
590
591impl Unary {
592    pub fn apply(self, x: f64) -> f64 {
593        match self {
594            Unary::Sin => x.sin(),
595            Unary::Cos => x.cos(),
596            Unary::Exp => x.exp(),
597            Unary::Sqrt => x.sqrt(),
598            Unary::Abs => x.abs(),
599            Unary::Tanh => x.tanh(),
600            Unary::Log => x.ln(),
601            Unary::Sat => x.clamp(-1.0, 1.0),
602            Unary::Step => sva_formula::affine::step(x),
603        }
604    }
605}
606
607impl Binary {
608    pub fn apply(self, a: f64, b: f64) -> f64 {
609        match self {
610            Binary::Max => a.max(b),
611            Binary::Min => a.min(b),
612            Binary::Mod => a.rem_euclid(b),
613        }
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::Map;
620
621    #[test]
622    fn a_rounded_map_reads_alike_narrow_and_wide() {
623        for (a, b, d) in [(1, 0, 2), (3, -7, 4), (-5, 11, 6), (2, 1, 4), (7, 3, 2)] {
624            for between in [super::Between::Floor, super::Between::Even] {
625                let Some(map) = Map::rounded(a, b, d, between) else {
626                    continue;
627                };
628                for n in -50..50 {
629                    let wide = i64::try_from(map.index_at(i128::from(n))).ok();
630                    assert_eq!(map.narrow_at(n), wide, "{map:?} at {n}");
631                }
632            }
633        }
634    }
635}