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        if p.width != own.width() {
234            return Err(SampleError::OwnWidth {
235                writes: p.width,
236                holds: own.width(),
237            });
238        }
239        while own.end() < to {
240            let from = own.end();
241            let most = (to - from).min(BLOCK as i64 - from.rem_euclid(BLOCK as i64)) as usize;
242            let len = p.block(from, most, &mut self.block.recurrent);
243            let here = Here {
244                reads,
245                own: own.within(Extent::from(origin)),
246                grid: self.grid,
247            };
248            let (held, refused) =
249                block::run(p, &mut self.block, &here, &mut self.states, (from, len));
250            for i in 0..held {
251                let top = self.block.top(p, i);
252                for c in 0..p.width {
253                    own.push(c, part(top, c));
254                }
255            }
256            if let Some(e) = refused {
257                return Err(e);
258            }
259        }
260        Ok(())
261    }
262
263    /// Call site `site`'s solver as it stands now, where that site is one.
264    pub fn solver(&self, site: usize) -> Option<&dyn Solver> {
265        match self.states.get(site)? {
266            State::Physics(solver) => Some(solver.as_ref()),
267            State::Filter(_) => None,
268        }
269    }
270
271    pub fn filter(&self, site: usize) -> Option<&FilterSite> {
272        match self.states.get(site)? {
273            State::Filter(filter) => Some(filter),
274            State::Physics(_) => None,
275        }
276    }
277
278    pub fn state(&self) -> MachineState {
279        MachineState {
280            sites: self.compiled_ops.sites.clone(),
281            states: self.states.clone(),
282        }
283    }
284
285    pub fn restart(&mut self) {
286        self.states = open(&self.compiled_ops, self.grid).expect("the sites opened once already");
287    }
288
289    /// Whether `carry` takes `held`: the same sites, a varying parameter's values aside.
290    pub fn accepts(&self, held: &MachineState) -> bool {
291        held.sites == self.compiled_ops.sites
292    }
293
294    /// Takes `held`'s state whole where its sites are these; `false`, and nothing taken, else.
295    pub fn carry(&mut self, held: &MachineState) -> bool {
296        let taken = self.accepts(held);
297        if taken {
298            self.states.clone_from(&held.states);
299        }
300        taken
301    }
302}
303
304impl CompiledOps {
305    /// Up to `most` samples from `from`, and the slots run sample by sample: each reading its
306    /// own past inside the block, and what reads one.
307    fn block(&self, from: i64, most: usize, recurrent: &mut Vec<bool>) -> usize {
308        let reach: Vec<Option<usize>> = self
309            .ops
310            .iter()
311            .map(|op| own_reach(op, from, most))
312            .collect();
313        let len = reach
314            .iter()
315            .flatten()
316            .filter(|n| **n >= RUN)
317            .fold(most, |len, n| len.min(*n))
318            .max(1);
319        recurrent.clear();
320        for (slot, reach) in reach.iter().enumerate() {
321            let reads = self.args[slot].iter().any(|a| recurrent[*a]);
322            recurrent.push(reads || reach.is_some_and(|n| n < len));
323        }
324        len
325    }
326}
327
328/// How long a block from `from` may run with an own-past read reading only before it.
329fn own_reach(op: &Op, from: i64, most: usize) -> Option<usize> {
330    match op {
331        Op::Read {
332            slot: Slot::Own,
333            at,
334        }
335        | Op::ReadScaled {
336            slot: Slot::Own,
337            at,
338            ..
339        } => {
340            let last = |len: usize| from + len as i64 - 1;
341            let mut len = most;
342            while len > 1 && at.at(from).max(at.at(last(len))) >= from {
343                len /= 2;
344            }
345            Some(match at.at(from).max(at.at(last(len))) < from {
346                true => len,
347                false => 0,
348            })
349        }
350        Op::Indexed {
351            slot: Slot::Own,
352            reach,
353            ..
354        } => Some(match reach {
355            Some((_, most)) if *most < 0 => {
356                usize::try_from(most.unsigned_abs()).unwrap_or(usize::MAX)
357            }
358            _ => 0,
359        }),
360        _ => None,
361    }
362}
363
364/// The fewest samples a block an own-past read shortens runs; a nearer read runs per sample.
365const RUN: usize = 16;
366
367#[cfg(test)]
368mod tests {
369    use super::renderer::{BufId, Index, Map, NodeRenderer, Slot};
370    use super::*;
371    use crate::grid::{Extent, Round, WIDE};
372
373    const LONG: [i64; 2] = [1489, 1721];
374
375    /// A reverb's loop: two lines fed back through a mix, each damped by its last sample.
376    fn feedback() -> NodeRenderer {
377        let own = |lag: i64, k: usize| NodeRenderer::Channel {
378            x: Box::new(NodeRenderer::Read {
379                slot: Slot::Own,
380                map: Map::shift(-lag),
381            }),
382            k,
383        };
384        let input = NodeRenderer::Read {
385            slot: Slot::Read(BufId(0)),
386            map: Map::shift(0),
387        };
388        let line = |k: usize| {
389            let mix = LONG
390                .iter()
391                .enumerate()
392                .map(|(j, lag)| NodeRenderer::Mul(vec![NodeRenderer::Const(0.6), own(*lag, j)]));
393            let fed = NodeRenderer::Add(std::iter::once(input.clone()).chain(mix).collect());
394            NodeRenderer::Add(vec![
395                NodeRenderer::Mul(vec![NodeRenderer::Const(0.7), fed]),
396                NodeRenderer::Mul(vec![NodeRenderer::Const(0.2), own(1, k)]),
397            ])
398        };
399        NodeRenderer::Join(vec![line(0), line(1)])
400    }
401
402    fn ran(to: i64) -> Vec<Vec<f64>> {
403        let layout = Layout {
404            grid: Grid::of(48_000),
405            width: 2,
406            read_widths: vec![1],
407            sites: Vec::new(),
408        };
409        let spanned =
410            Spanned::new(&feedback(), &layout, (0, to), &[Extent::EVERYWHERE]).expect("a program");
411        let mut input = Buffer::empty(48_000, 1, to as usize, 0);
412        (0..to).for_each(|n| input.push(0, f64::from(u8::from(n % 4800 == 0))));
413        let mut out = Buffer::empty(48_000, 2, to as usize, 0);
414        let mut machine = Machine::over(&spanned, 0).expect("a machine");
415        machine
416            .run_to(to, &[input.within(Extent::from(0))], (&mut out, 0))
417            .expect("samples");
418        out.planes
419    }
420
421    /// Each line reads both long lines and its own last sample, and both mix the same input:
422    /// every identical read and mix is one op, writing the bits the loop's own arithmetic does.
423    #[test]
424    fn identical_reads_of_a_loops_own_past_run_once() {
425        let to = 24_000;
426        let layout = Layout {
427            grid: Grid::of(48_000),
428            width: 2,
429            read_widths: vec![1],
430            sites: Vec::new(),
431        };
432        assert_eq!(lowered(&feedback(), &layout).expect("ops").0.ops.len(), 20);
433        let planes = ran(to);
434        let mut y = vec![vec![0.0f64; to as usize]; 2];
435        let past = |y: &[Vec<f64>], k: usize, n: i64| match n {
436            n if n < 0 => 0.0,
437            n => y[k][n as usize],
438        };
439        for n in 0..to {
440            let input = f64::from(u8::from(n % 4800 == 0));
441            let mut fed = 0.0 + input;
442            for (j, lag) in LONG.iter().enumerate() {
443                fed += 1.0 * 0.6 * past(&y, j, n - lag);
444            }
445            for k in 0..2 {
446                y[k][n as usize] = 0.0 + 1.0 * 0.7 * fed + 1.0 * 0.2 * past(&y, k, n - 1);
447            }
448        }
449        let bits = |p: &[Vec<f64>]| -> Vec<Vec<u64>> {
450            p.iter()
451                .map(|c| c.iter().map(|v| v.to_bits()).collect())
452                .collect()
453        };
454        assert_eq!(bits(&planes), bits(&y));
455    }
456
457    /// A two-line loop whose last samples move a filter, a map, a pair, a crop, a draw, a
458    /// delay read at a moving length and that length's instant.
459    fn through_every_op() -> NodeRenderer {
460        use super::renderer::{Binary, Formula, Unary};
461        use NodeRenderer::{Const, Map as Of, Mul};
462        let own = |lag: i64, k: usize| NodeRenderer::Channel {
463            x: Box::new(NodeRenderer::Read {
464                slot: Slot::Own,
465                map: Map::shift(-lag),
466            }),
467            k,
468        };
469        let input = NodeRenderer::Read {
470            slot: Slot::Read(BufId(0)),
471            map: Map::shift(0),
472        };
473        let b = Box::new;
474        let filtered = NodeRenderer::Filter {
475            site: super::renderer::SiteId(0),
476            from: 0,
477            x: b(NodeRenderer::Add(vec![
478                input.clone(),
479                Mul(vec![Const(0.5), own(1, 0)]),
480            ])),
481            cutoff: b(NodeRenderer::Add(vec![
482                Const(800.0),
483                Mul(vec![Const(400.0), Of(Unary::Sin, b(own(1, 1)))]),
484            ])),
485            q: b(Const(0.7)),
486            gain: b(Const(0.0)),
487        };
488        let paired = NodeRenderer::Add(vec![
489            NodeRenderer::Zip(
490                Binary::Max,
491                b(NodeRenderer::Div(b(own(2, 0)), b(Const(3.0)))),
492                b(own(1, 1)),
493            ),
494            NodeRenderer::Pow(b(Const(0.9)), b(own(1, 0))),
495        ]);
496        let cropped = NodeRenderer::Crop {
497            x: b(NodeRenderer::Sub(b(paired), b(Const(0.5)))),
498            window: (100, 20_000),
499            a: 100.0 / 48_000.0,
500            b: 20_000.0 / 48_000.0,
501            rise: 0.01,
502            fall: 0.01,
503        };
504        let drawn = NodeRenderer::Formula {
505            formula: Formula::Drawn {
506                seed: 7,
507                rate: 1_000,
508            },
509            width: 1,
510            time: b(NodeRenderer::Add(vec![
511                NodeRenderer::Time,
512                Mul(vec![Const(0.000_1), own(1, 0)]),
513            ])),
514        };
515        let back = || {
516            let length = NodeRenderer::Add(vec![
517                Const(0.000_2),
518                Mul(vec![Const(0.000_1), Of(Unary::Sin, b(own(1, 1)))]),
519            ]);
520            Index::Add(vec![
521                Index::At(Map::whole(1, 0)),
522                Index::Neg(Box::new(Index::Step(b(length), Round::Floor))),
523            ])
524        };
525        let delayed = NodeRenderer::Indexed {
526            slot: Slot::Own,
527            index: back(),
528            reach: Some((-14, -4)),
529        };
530        let lag = NodeRenderer::Sub(b(NodeRenderer::Time), b(NodeRenderer::Instant(back())));
531        NodeRenderer::Join(vec![
532            NodeRenderer::Add(vec![
533                Mul(vec![Const(0.3), filtered]),
534                Mul(vec![Const(0.2), cropped]),
535                Mul(vec![Const(0.1), drawn]),
536                Mul(vec![
537                    Const(0.3),
538                    NodeRenderer::Channel {
539                        x: b(delayed),
540                        k: 1,
541                    },
542                ]),
543            ]),
544            NodeRenderer::Add(vec![
545                Mul(vec![Const(0.5), input]),
546                Mul(vec![Const(0.4), own(1, 1)]),
547                Mul(vec![Const(10.0), lag]),
548            ]),
549        ])
550    }
551
552    /// A machine's spans continue exactly where the last ended, whatever they cut a loop
553    /// through every op into: the same bits run whole, a sample at a time, or 37 at a time.
554    #[test]
555    fn a_loop_through_every_op_writes_what_a_sample_at_a_time_run_does() {
556        let to = 12_000;
557        let layout = Layout {
558            grid: Grid::of(48_000),
559            width: 2,
560            read_widths: vec![1],
561            sites: vec![Site::Filter(sva_formula::Shape::Lowpass)],
562        };
563        let spanned = Spanned::new(&through_every_op(), &layout, (0, to), &[Extent::EVERYWHERE])
564            .expect("a program");
565        let mut input = Buffer::empty(48_000, 1, to as usize, 0);
566        (0..to).for_each(|n| input.push(0, f64::from(u8::from(n % 2400 == 0))));
567        let run = |step: i64| {
568            let mut out = Buffer::empty(48_000, 2, to as usize, 0);
569            let mut machine = Machine::over(&spanned, 0).expect("a machine");
570            let mut at = 0;
571            while at < to {
572                at = (at + step).min(to);
573                machine
574                    .run_to(at, &[input.within(Extent::from(0))], (&mut out, 0))
575                    .expect("samples");
576            }
577            let bits = out.planes.iter().flatten().map(|v| v.to_bits());
578            bits.collect::<Vec<u64>>()
579        };
580        let whole = run(to);
581        assert!(whole.iter().all(|v| f64::from_bits(*v).is_finite()));
582        assert_eq!(whole, run(1));
583        assert_eq!(whole, run(37));
584    }
585
586    /// A delay read at a moving length and a loop reading itself far back, by index.
587    fn delayed() -> NodeRenderer {
588        let now = Index::At(Map::whole(1, 0));
589        let length = NodeRenderer::Add(vec![
590            NodeRenderer::Const(0.002),
591            NodeRenderer::Mul(vec![
592                NodeRenderer::Const(0.000_2),
593                NodeRenderer::Map(
594                    super::renderer::Unary::Sin,
595                    Box::new(NodeRenderer::Mul(vec![
596                        NodeRenderer::Const(18.85),
597                        NodeRenderer::Time,
598                    ])),
599                ),
600            ]),
601        ]);
602        let back = Index::Neg(Box::new(Index::Step(Box::new(length), Round::Floor)));
603        let echo = Index::Add(vec![now.clone(), Index::At(Map::whole(0, -1489))]);
604        NodeRenderer::Add(vec![
605            NodeRenderer::Indexed {
606                slot: Slot::Read(BufId(0)),
607                index: Index::Add(vec![now, back]),
608                reach: None,
609            },
610            NodeRenderer::Mul(vec![
611                NodeRenderer::Const(0.5),
612                NodeRenderer::Indexed {
613                    slot: Slot::Own,
614                    index: echo,
615                    reach: Some((-1489, -1489)),
616                },
617            ]),
618        ])
619    }
620
621    /// Every index a sample reads, its delay's step included, is rounded in doubles or `i64`:
622    /// none in 128-bit integers.
623    #[test]
624    fn a_delay_and_an_echo_round_no_index_in_wide_integers() {
625        let to = 9_600;
626        let layout = Layout {
627            grid: Grid::of(48_000),
628            width: 1,
629            read_widths: vec![1],
630            sites: Vec::new(),
631        };
632        let spanned =
633            Spanned::new(&delayed(), &layout, (0, to), &[Extent::EVERYWHERE]).expect("a program");
634        let mut input = Buffer::empty(48_000, 1, to as usize, 0);
635        (0..to).for_each(|n| input.push(0, (n as f64 * 0.01).sin()));
636        let mut out = Buffer::empty(48_000, 1, to as usize, 0);
637        let mut machine = Machine::over(&spanned, 0).expect("a machine");
638        let before = WIDE.with(std::cell::Cell::get);
639        machine
640            .run_to(to, &[input.within(Extent::from(0))], (&mut out, 0))
641            .expect("samples");
642        assert_eq!(WIDE.with(std::cell::Cell::get), before);
643    }
644}