Skip to main content

sva_samples/machine/
mod.rs

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