Skip to main content

sva_samples/machine/
mod.rs

1// Concern: runs one node renderer a block at a time, span after span, onto its own samples | Non-concern: the op array's shape (ops.rs), cutting spans (live.rs) | IO: (Spanned, reads, own) -> samples
2
3mod block;
4mod kernels;
5mod live;
6pub mod ops;
7mod read;
8pub mod renderer;
9mod tape;
10
11use std::sync::Arc;
12
13use crate::buffer::{Buffer, SampleView};
14use crate::collapse::Program;
15use crate::error::SampleError;
16use crate::filters::FilterSite;
17use crate::grid::Extent;
18use crate::grid::Grid;
19use crate::physics::{Solver, site};
20use block::{BLOCK, BlockScratch};
21use ops::{Layout, Op, lowered};
22use read::Here;
23use renderer::{Formula, Index, NodeRenderer, Site, Slot};
24
25pub use live::{Span, Spanned};
26
27pub use ops::Layout as MachineLayout;
28
29/// The op array, each op's width and operand slots, the formulas its ops name and the call
30/// sites the run opens state for.
31#[derive(Clone)]
32pub(super) struct CompiledOps {
33    ops: Vec<Op>,
34    widths: Vec<usize>,
35    args: Vec<Vec<usize>>,
36    /// Each formula, a written one with its program per component.
37    formulas: Vec<(Formula, Arc<Vec<Program>>)>,
38    indices: Vec<Index<usize>>,
39    sites: Vec<Site>,
40    pub width: usize,
41}
42
43impl NodeRenderer {
44    pub(super) fn compile(&self, layout: &Layout) -> Result<CompiledOps, SampleError> {
45        let (lowered, width) = lowered(self, layout)?;
46        let mut formulas: Vec<(Formula, Arc<Vec<Program>>)> = lowered
47            .formulas
48            .into_iter()
49            .map(|f| (f, Arc::new(Vec::new())))
50            .collect();
51        for (slot, op) in lowered.ops.iter().enumerate() {
52            if let Op::Formula { at } = op
53                && let (Formula::Written(w), programs) = &mut formulas[*at]
54            {
55                let each = (0..lowered.widths[slot]).map(|c| Program::of(&[&w.body], &w.refs, c));
56                *programs = Arc::new(each.collect());
57            }
58        }
59        Ok(CompiledOps {
60            ops: lowered.ops,
61            widths: lowered.widths,
62            args: lowered.args,
63            formulas,
64            indices: lowered.indices,
65            sites: layout.sites.clone(),
66            width,
67        })
68    }
69}
70
71#[derive(Clone)]
72enum State {
73    Filter(FilterSite),
74    Physics(Box<dyn Solver>),
75}
76
77impl Clone for Box<dyn Solver> {
78    fn clone(&self) -> Box<dyn Solver> {
79        self.boxed()
80    }
81}
82
83/// A filter site carries one lane per component of its widest argument, which the compiled
84/// slot width already states.
85fn open(p: &CompiledOps, grid: Grid) -> Result<Vec<State>, SampleError> {
86    let mut lanes = vec![1usize; p.sites.len()];
87    for (slot, op) in p.ops.iter().enumerate() {
88        if let Op::Filter { site, .. } = op {
89            lanes[site.0 as usize] = p.widths[slot];
90        }
91    }
92    p.sites
93        .iter()
94        .zip(lanes)
95        .map(|(s, width)| {
96            Ok(match s {
97                Site::Filter(shape) => State::Filter(FilterSite::new(
98                    *shape,
99                    width,
100                    &[0.0],
101                    &[0.0],
102                    &[0.0],
103                    grid.sr(),
104                )),
105                Site::Physics(params) => State::Physics(site(params, grid.sr())?),
106            })
107        })
108        .collect()
109}
110
111/// Component `c` of an operand that may be mono where its neighbour is wide.
112fn part(v: &[f64], c: usize) -> f64 {
113    v[c.min(v.len() - 1)]
114}
115
116/// A node's own samples, and the sample before which its past is silent.
117pub type Own<'a> = (&'a mut Buffer, i64);
118
119/// One compiled node and every call site's state, run over any span of the grid in order.
120/// A span continues exactly where the last ended, so blocks write the samples one run would.
121pub struct Machine {
122    compiled_ops: CompiledOps,
123    states: Vec<State>,
124    block: BlockScratch,
125    grid: Grid,
126    /// Each later span's first sample and the program it runs, the next one last.
127    ahead: Vec<(i64, CompiledOps)>,
128}
129
130#[derive(Clone)]
131pub struct MachineState {
132    sites: Vec<Site>,
133    states: Vec<State>,
134}
135
136impl State {
137    fn bytes(&self) -> usize {
138        match self {
139            State::Filter(filter) => filter.bytes(),
140            State::Physics(solver) => solver.bytes(),
141        }
142    }
143}
144
145impl MachineState {
146    pub fn bytes(&self) -> usize {
147        let states: usize = self.states.iter().map(State::bytes).sum();
148        size_of::<Self>() + std::mem::size_of_val(self.sites.as_slice()) + states
149    }
150}
151
152impl Machine {
153    /// Stepping from `at`, each span's own program from where it starts.
154    pub fn over(spanned: &Spanned, at: i64) -> Result<Machine, SampleError> {
155        let grid = spanned.grid();
156        let mut ahead: Vec<(i64, CompiledOps)> = spanned
157            .compiled()
158            .iter()
159            .filter(|(span, _)| span.to > at)
160            .map(|(span, compiled_ops)| (span.from, compiled_ops.clone()))
161            .collect();
162        ahead.reverse();
163        let first = match ahead.pop() {
164            Some((_, compiled_ops)) => compiled_ops,
165            None => spanned.silent()?,
166        };
167        let states = open(&first, grid)?;
168        let block = BlockScratch::of(&first.widths);
169        Ok(Machine {
170            compiled_ops: first,
171            states,
172            block,
173            grid,
174            ahead,
175        })
176    }
177
178    pub fn width(&self) -> usize {
179        self.compiled_ops.width
180    }
181
182    pub fn stateful(&self) -> bool {
183        !self.compiled_ops.sites.is_empty()
184    }
185
186    pub fn bytes(&self) -> usize {
187        self.states.iter().map(State::bytes).sum()
188    }
189
190    pub fn run_to(&mut self, to: i64, reads: &[SampleView], own: Own) -> Result<(), SampleError> {
191        for state in &mut self.states {
192            if let State::Filter(filter) = state {
193                filter.forget_frames();
194            }
195        }
196        self.steps(to, reads, own)
197    }
198
199    /// `run_to`, the filters' frames kept.
200    pub fn run_on(&mut self, to: i64, reads: &[SampleView], own: Own) -> Result<(), SampleError> {
201        self.steps(to, reads, own)
202    }
203
204    fn steps(
205        &mut self,
206        to: i64,
207        reads: &[SampleView],
208        (own, origin): Own,
209    ) -> Result<(), SampleError> {
210        loop {
211            while let Some((from, _)) = self.ahead.last()
212                && *from <= own.end()
213            {
214                let (_, compiled_ops) = self.ahead.pop().expect("a span ahead");
215                self.block = BlockScratch::of(&compiled_ops.widths);
216                self.compiled_ops = compiled_ops;
217            }
218            let until = self.ahead.last().map_or(to, |(from, _)| (*from).min(to));
219            self.stepped(until, reads, (own, origin))?;
220            if own.end() >= to {
221                return Ok(());
222            }
223        }
224    }
225
226    fn stepped(
227        &mut self,
228        to: i64,
229        reads: &[SampleView],
230        (own, origin): Own,
231    ) -> Result<(), SampleError> {
232        let p = &self.compiled_ops;
233        while own.end() < to {
234            let from = own.end();
235            let most = (to - from).min(BLOCK as i64 - from.rem_euclid(BLOCK as i64)) as usize;
236            let len = p.block(from, most, &mut self.block.recurrent);
237            let here = Here {
238                reads,
239                own: own.within(Extent::from(origin)),
240                grid: self.grid,
241            };
242            let (held, refused) =
243                block::run(p, &mut self.block, &here, &mut self.states, (from, len));
244            for i in 0..held {
245                let top = self.block.top(p, i);
246                for c in 0..p.width {
247                    own.push(c, part(top, c));
248                }
249            }
250            if let Some(e) = refused {
251                return Err(e);
252            }
253        }
254        Ok(())
255    }
256
257    /// Call site `site`'s solver as it stands now, where that site is one.
258    pub fn solver(&self, site: usize) -> Option<&dyn Solver> {
259        match self.states.get(site)? {
260            State::Physics(solver) => Some(solver.as_ref()),
261            State::Filter(_) => None,
262        }
263    }
264
265    pub fn filter(&self, site: usize) -> Option<&FilterSite> {
266        match self.states.get(site)? {
267            State::Filter(filter) => Some(filter),
268            State::Physics(_) => None,
269        }
270    }
271
272    pub fn state(&self) -> MachineState {
273        MachineState {
274            sites: self.compiled_ops.sites.clone(),
275            states: self.states.clone(),
276        }
277    }
278
279    pub fn restart(&mut self) {
280        self.states = open(&self.compiled_ops, self.grid).expect("the sites opened once already");
281    }
282
283    /// Whether `carry` takes `held`: the same sites, a varying parameter's values aside.
284    pub fn accepts(&self, held: &MachineState) -> bool {
285        held.sites == self.compiled_ops.sites
286    }
287
288    /// Takes `held`'s state whole where its sites are these; `false`, and nothing taken, else.
289    pub fn carry(&mut self, held: &MachineState) -> bool {
290        let taken = self.accepts(held);
291        if taken {
292            self.states.clone_from(&held.states);
293        }
294        taken
295    }
296}
297
298impl CompiledOps {
299    /// Up to `most` samples from `from`, and the slots run sample by sample: each reading its
300    /// own past inside the block, and what reads one.
301    fn block(&self, from: i64, most: usize, recurrent: &mut Vec<bool>) -> usize {
302        let reach: Vec<Option<usize>> = self
303            .ops
304            .iter()
305            .map(|op| own_reach(op, from, most))
306            .collect();
307        let len = reach
308            .iter()
309            .flatten()
310            .filter(|n| **n >= RUN)
311            .fold(most, |len, n| len.min(*n))
312            .max(1);
313        recurrent.clear();
314        for (slot, reach) in reach.iter().enumerate() {
315            let reads = self.args[slot].iter().any(|a| recurrent[*a]);
316            recurrent.push(reads || reach.is_some_and(|n| n < len));
317        }
318        len
319    }
320}
321
322/// How long a block from `from` may run with an own-past read reading only before it.
323fn own_reach(op: &Op, from: i64, most: usize) -> Option<usize> {
324    match op {
325        Op::Read {
326            slot: Slot::Own,
327            at,
328        }
329        | Op::ReadScaled {
330            slot: Slot::Own,
331            at,
332            ..
333        } => {
334            let last = |len: usize| from + len as i64 - 1;
335            let mut len = most;
336            while len > 1 && at.at(from).max(at.at(last(len))) >= from {
337                len /= 2;
338            }
339            Some(match at.at(from).max(at.at(last(len))) < from {
340                true => len,
341                false => 0,
342            })
343        }
344        Op::Indexed {
345            slot: Slot::Own,
346            reach,
347            ..
348        } => Some(match reach {
349            Some((_, most)) if *most < 0 => {
350                usize::try_from(most.unsigned_abs()).unwrap_or(usize::MAX)
351            }
352            _ => 0,
353        }),
354        _ => None,
355    }
356}
357
358/// The fewest samples a block an own-past read shortens runs; a nearer read runs per sample.
359const RUN: usize = 16;
360
361#[cfg(test)]
362mod tests {
363    use super::renderer::{BufId, Index, Map, NodeRenderer, Slot};
364    use super::*;
365    use crate::grid::{Extent, Round, WIDE};
366
367    const LONG: [i64; 2] = [1489, 1721];
368
369    /// A reverb's loop: two lines fed back through a mix, each damped by its last sample.
370    fn feedback() -> NodeRenderer {
371        let own = |lag: i64, k: usize| NodeRenderer::Channel {
372            x: Box::new(NodeRenderer::Read {
373                slot: Slot::Own,
374                map: Map::shift(-lag),
375            }),
376            k,
377        };
378        let input = NodeRenderer::Read {
379            slot: Slot::Read(BufId(0)),
380            map: Map::shift(0),
381        };
382        let line = |k: usize| {
383            let mix = LONG
384                .iter()
385                .enumerate()
386                .map(|(j, lag)| NodeRenderer::Mul(vec![NodeRenderer::Const(0.6), own(*lag, j)]));
387            let fed = NodeRenderer::Add(std::iter::once(input.clone()).chain(mix).collect());
388            NodeRenderer::Add(vec![
389                NodeRenderer::Mul(vec![NodeRenderer::Const(0.7), fed]),
390                NodeRenderer::Mul(vec![NodeRenderer::Const(0.2), own(1, k)]),
391            ])
392        };
393        NodeRenderer::Join(vec![line(0), line(1)])
394    }
395
396    fn ran(to: i64) -> Vec<Vec<f64>> {
397        let layout = Layout {
398            grid: Grid::of(48_000),
399            width: 2,
400            read_widths: vec![1],
401            sites: Vec::new(),
402        };
403        let spanned =
404            Spanned::new(&feedback(), &layout, (0, to), &[Extent::EVERYWHERE]).expect("a program");
405        let mut input = Buffer::empty(48_000, 1, to as usize, 0);
406        (0..to).for_each(|n| input.push(0, f64::from(u8::from(n % 4800 == 0))));
407        let mut out = Buffer::empty(48_000, 2, to as usize, 0);
408        let mut machine = Machine::over(&spanned, 0).expect("a machine");
409        machine
410            .run_to(to, &[input.within(Extent::from(0))], (&mut out, 0))
411            .expect("samples");
412        out.planes
413    }
414
415    /// Each line reads both long lines and its own last sample, and both mix the same input:
416    /// every identical read and mix is one op, writing the bits the loop's own arithmetic does.
417    #[test]
418    fn identical_reads_of_a_loops_own_past_run_once() {
419        let to = 24_000;
420        let layout = Layout {
421            grid: Grid::of(48_000),
422            width: 2,
423            read_widths: vec![1],
424            sites: Vec::new(),
425        };
426        assert_eq!(lowered(&feedback(), &layout).expect("ops").0.ops.len(), 20);
427        let planes = ran(to);
428        let mut y = vec![vec![0.0f64; to as usize]; 2];
429        let past = |y: &[Vec<f64>], k: usize, n: i64| match n {
430            n if n < 0 => 0.0,
431            n => y[k][n as usize],
432        };
433        for n in 0..to {
434            let input = f64::from(u8::from(n % 4800 == 0));
435            let mut fed = 0.0 + input;
436            for (j, lag) in LONG.iter().enumerate() {
437                fed += 1.0 * 0.6 * past(&y, j, n - lag);
438            }
439            for k in 0..2 {
440                y[k][n as usize] = 0.0 + 1.0 * 0.7 * fed + 1.0 * 0.2 * past(&y, k, n - 1);
441            }
442        }
443        let bits = |p: &[Vec<f64>]| -> Vec<Vec<u64>> {
444            p.iter()
445                .map(|c| c.iter().map(|v| v.to_bits()).collect())
446                .collect()
447        };
448        assert_eq!(bits(&planes), bits(&y));
449    }
450
451    /// A two-line loop whose last samples move a filter, a map, a pair, a crop, a draw, a
452    /// delay read at a moving length and that length's instant.
453    fn through_every_op() -> NodeRenderer {
454        use super::renderer::{Binary, Formula, Unary};
455        use NodeRenderer::{Const, Map as Of, Mul};
456        let own = |lag: i64, k: usize| NodeRenderer::Channel {
457            x: Box::new(NodeRenderer::Read {
458                slot: Slot::Own,
459                map: Map::shift(-lag),
460            }),
461            k,
462        };
463        let input = NodeRenderer::Read {
464            slot: Slot::Read(BufId(0)),
465            map: Map::shift(0),
466        };
467        let b = Box::new;
468        let filtered = NodeRenderer::Filter {
469            site: super::renderer::SiteId(0),
470            from: 0,
471            x: b(NodeRenderer::Add(vec![
472                input.clone(),
473                Mul(vec![Const(0.5), own(1, 0)]),
474            ])),
475            cutoff: b(NodeRenderer::Add(vec![
476                Const(800.0),
477                Mul(vec![Const(400.0), Of(Unary::Sin, b(own(1, 1)))]),
478            ])),
479            q: b(Const(0.7)),
480            gain: b(Const(0.0)),
481        };
482        let paired = NodeRenderer::Add(vec![
483            NodeRenderer::Zip(
484                Binary::Max,
485                b(NodeRenderer::Div(b(own(2, 0)), b(Const(3.0)))),
486                b(own(1, 1)),
487            ),
488            NodeRenderer::Pow(b(Const(0.9)), b(own(1, 0))),
489        ]);
490        let cropped = NodeRenderer::Crop {
491            x: b(NodeRenderer::Sub(b(paired), b(Const(0.5)))),
492            window: (100, 20_000),
493            a: 100.0 / 48_000.0,
494            b: 20_000.0 / 48_000.0,
495            rise: 0.01,
496            fall: 0.01,
497        };
498        let drawn = NodeRenderer::Formula {
499            formula: Formula::Drawn {
500                seed: 7,
501                rate: 1_000,
502            },
503            width: 1,
504            time: b(NodeRenderer::Add(vec![
505                NodeRenderer::Time,
506                Mul(vec![Const(0.000_1), own(1, 0)]),
507            ])),
508        };
509        let back = || {
510            let length = NodeRenderer::Add(vec![
511                Const(0.000_2),
512                Mul(vec![Const(0.000_1), Of(Unary::Sin, b(own(1, 1)))]),
513            ]);
514            Index::Add(vec![
515                Index::At(Map::whole(1, 0)),
516                Index::Neg(Box::new(Index::Step(b(length), Round::Floor))),
517            ])
518        };
519        let delayed = NodeRenderer::Indexed {
520            slot: Slot::Own,
521            index: back(),
522            reach: Some((-14, -4)),
523        };
524        let lag = NodeRenderer::Sub(b(NodeRenderer::Time), b(NodeRenderer::Instant(back())));
525        NodeRenderer::Join(vec![
526            NodeRenderer::Add(vec![
527                Mul(vec![Const(0.3), filtered]),
528                Mul(vec![Const(0.2), cropped]),
529                Mul(vec![Const(0.1), drawn]),
530                Mul(vec![
531                    Const(0.3),
532                    NodeRenderer::Channel {
533                        x: b(delayed),
534                        k: 1,
535                    },
536                ]),
537            ]),
538            NodeRenderer::Add(vec![
539                Mul(vec![Const(0.5), input]),
540                Mul(vec![Const(0.4), own(1, 1)]),
541                Mul(vec![Const(10.0), lag]),
542            ]),
543        ])
544    }
545
546    /// A machine's spans continue exactly where the last ended, whatever they cut a loop
547    /// through every op into: the same bits run whole, a sample at a time, or 37 at a time.
548    #[test]
549    fn a_loop_through_every_op_writes_what_a_sample_at_a_time_run_does() {
550        let to = 12_000;
551        let layout = Layout {
552            grid: Grid::of(48_000),
553            width: 2,
554            read_widths: vec![1],
555            sites: vec![Site::Filter(sva_formula::Shape::Lowpass)],
556        };
557        let spanned = Spanned::new(&through_every_op(), &layout, (0, to), &[Extent::EVERYWHERE])
558            .expect("a program");
559        let mut input = Buffer::empty(48_000, 1, to as usize, 0);
560        (0..to).for_each(|n| input.push(0, f64::from(u8::from(n % 2400 == 0))));
561        let run = |step: i64| {
562            let mut out = Buffer::empty(48_000, 2, to as usize, 0);
563            let mut machine = Machine::over(&spanned, 0).expect("a machine");
564            let mut at = 0;
565            while at < to {
566                at = (at + step).min(to);
567                machine
568                    .run_to(at, &[input.within(Extent::from(0))], (&mut out, 0))
569                    .expect("samples");
570            }
571            let bits = out.planes.iter().flatten().map(|v| v.to_bits());
572            bits.collect::<Vec<u64>>()
573        };
574        let whole = run(to);
575        assert!(whole.iter().all(|v| f64::from_bits(*v).is_finite()));
576        assert_eq!(whole, run(1));
577        assert_eq!(whole, run(37));
578    }
579
580    /// A delay read at a moving length and a loop reading itself far back, by index.
581    fn delayed() -> NodeRenderer {
582        let now = Index::At(Map::whole(1, 0));
583        let length = NodeRenderer::Add(vec![
584            NodeRenderer::Const(0.002),
585            NodeRenderer::Mul(vec![
586                NodeRenderer::Const(0.000_2),
587                NodeRenderer::Map(
588                    super::renderer::Unary::Sin,
589                    Box::new(NodeRenderer::Mul(vec![
590                        NodeRenderer::Const(18.85),
591                        NodeRenderer::Time,
592                    ])),
593                ),
594            ]),
595        ]);
596        let back = Index::Neg(Box::new(Index::Step(Box::new(length), Round::Floor)));
597        let echo = Index::Add(vec![now.clone(), Index::At(Map::whole(0, -1489))]);
598        NodeRenderer::Add(vec![
599            NodeRenderer::Indexed {
600                slot: Slot::Read(BufId(0)),
601                index: Index::Add(vec![now, back]),
602                reach: None,
603            },
604            NodeRenderer::Mul(vec![
605                NodeRenderer::Const(0.5),
606                NodeRenderer::Indexed {
607                    slot: Slot::Own,
608                    index: echo,
609                    reach: Some((-1489, -1489)),
610                },
611            ]),
612        ])
613    }
614
615    /// Every index a sample reads, its delay's step included, is rounded in doubles or `i64`:
616    /// none in 128-bit integers.
617    #[test]
618    fn a_delay_and_an_echo_round_no_index_in_wide_integers() {
619        let to = 9_600;
620        let layout = Layout {
621            grid: Grid::of(48_000),
622            width: 1,
623            read_widths: vec![1],
624            sites: Vec::new(),
625        };
626        let spanned =
627            Spanned::new(&delayed(), &layout, (0, to), &[Extent::EVERYWHERE]).expect("a program");
628        let mut input = Buffer::empty(48_000, 1, to as usize, 0);
629        (0..to).for_each(|n| input.push(0, (n as f64 * 0.01).sin()));
630        let mut out = Buffer::empty(48_000, 1, to as usize, 0);
631        let mut machine = Machine::over(&spanned, 0).expect("a machine");
632        let before = WIDE.with(std::cell::Cell::get);
633        machine
634            .run_to(to, &[input.within(Extent::from(0))], (&mut out, 0))
635            .expect("samples");
636        assert_eq!(WIDE.with(std::cell::Cell::get), before);
637    }
638}