Skip to main content

sva_samples/machine/
mod.rs

1// Concern: runs one node renderer a sample at a time into a buffer | Non-concern: the op array's own shape (ops.rs), the tree the engine hands over (renderer.rs) | IO: (NodeRenderer, Ctx) -> Buffer
2
3pub mod ops;
4pub mod renderer;
5
6use crate::buffer::Buffer;
7use crate::error::SampleError;
8use crate::filters::FilterSite;
9use crate::physics::{Solver, site};
10use ops::{Layout, Op, lower};
11use renderer::{NodeRenderer, Site};
12
13pub use ops::Layout as MachineLayout;
14
15/// The op array, one width per slot, and the call sites the run opens state for.
16struct Program {
17    ops: Vec<Op>,
18    widths: Vec<usize>,
19    sites: Vec<Site>,
20    pub width: usize,
21}
22
23/// `self_planes` is this node's own output so far, planar: component `c` owns
24/// `[c*len, (c+1)*len)`, and only the first `written` samples are founded. `run` refounds
25/// that mark every sample, so no block sizes the loop.
26pub struct Ctx<'a> {
27    pub rate: u32,
28    pub origin_secs: f64,
29    pub len: usize,
30    pub reads: &'a [&'a Buffer],
31    pub self_planes: &'a [f64],
32    pub written: usize,
33}
34
35impl NodeRenderer {
36    fn compile(&self, layout: &Layout) -> Result<Program, SampleError> {
37        let (mut ops, mut widths) = (Vec::new(), Vec::new());
38        let width = lower(self, layout, &mut ops, &mut widths)?;
39        Ok(Program {
40            ops,
41            widths,
42            sites: layout.sites.clone(),
43            width,
44        })
45    }
46
47    /// A loop reads what this run wrote; a caller's own `self_planes` replaces it.
48    pub fn run(&self, layout: &Layout, ctx: &Ctx) -> Result<Buffer, SampleError> {
49        run(&self.compile(layout)?, ctx)
50    }
51}
52
53enum State {
54    Filter(FilterSite),
55    Physics(Box<dyn Solver>),
56}
57
58/// A filter site carries one lane per component of its widest argument, which the compiled
59/// slot width already states.
60fn open(p: &Program, rate: u32) -> Result<Vec<State>, SampleError> {
61    let mut lanes = vec![1usize; p.sites.len()];
62    for (slot, op) in p.ops.iter().enumerate() {
63        if let Op::Filter(id) = op {
64            lanes[id.0 as usize] = p.widths[slot];
65        }
66    }
67    p.sites
68        .iter()
69        .zip(lanes)
70        .map(|(s, width)| {
71            Ok(match s {
72                Site::Filter(shape) => State::Filter(FilterSite::new(
73                    *shape,
74                    width,
75                    &[0.0],
76                    &[0.0],
77                    &[0.0],
78                    f64::from(rate),
79                )),
80                Site::Physics(params) => State::Physics(site(params, rate)?),
81            })
82        })
83        .collect()
84}
85
86/// One value slot per op and a stack of the indices waiting. Nothing allocates in the loop.
87struct Stack {
88    values: Vec<Vec<f64>>,
89    pending: Vec<usize>,
90}
91
92impl Stack {
93    fn of(widths: &[usize]) -> Stack {
94        Stack {
95            values: widths.iter().map(|&w| vec![0.0; w]).collect(),
96            pending: Vec::with_capacity(widths.len()),
97        }
98    }
99}
100
101/// Component `c` of an operand that may be mono where its neighbour is wide.
102fn part(v: &[f64], c: usize) -> f64 {
103    v[c % v.len()]
104}
105
106fn run(p: &Program, ctx: &Ctx) -> Result<Buffer, SampleError> {
107    let mut states = open(p, ctx.rate)?;
108    let mut out = Buffer::silence(ctx.rate, p.width, ctx.len);
109    out.origin_secs = ctx.origin_secs;
110    let mut stack = Stack::of(&p.widths);
111    let sr = f64::from(ctx.rate);
112    let mut own = vec![0.0; p.width * ctx.len];
113    for i in 0..ctx.len {
114        {
115            let held = Ctx {
116                rate: ctx.rate,
117                origin_secs: ctx.origin_secs,
118                len: ctx.len,
119                reads: ctx.reads,
120                self_planes: match ctx.self_planes.is_empty() {
121                    true => &own,
122                    false => ctx.self_planes,
123                },
124                written: match ctx.self_planes.is_empty() {
125                    true => i,
126                    false => ctx.written,
127                },
128            };
129            step(p, &held, &mut states, &mut stack, i, sr);
130        }
131        let top = &stack.values[*stack.pending.last().expect("a renderer leaves one value")];
132        for c in 0..p.width {
133            let value = part(top, c);
134            out.planes[c][i] = value;
135            own[c * ctx.len + i] = value;
136        }
137    }
138    Ok(out)
139}
140
141/// One sample of the whole renderer. Postfix order puts every operand's slot before the slot
142/// that consumes it, so `split_at_mut` hands out the reads and the one write at once.
143fn step(p: &Program, ctx: &Ctx, states: &mut [State], stack: &mut Stack, i: usize, sr: f64) {
144    let t = ctx.origin_secs + i as f64 / sr;
145    stack.pending.clear();
146    for (slot, op) in p.ops.iter().enumerate() {
147        let at = stack.pending.len() - arity_of(op);
148        let (done, rest) = stack.values.split_at_mut(slot);
149        fill(
150            op,
151            done,
152            &stack.pending[at..],
153            &mut rest[0],
154            ctx,
155            states,
156            i,
157            t,
158            sr,
159        );
160        stack.pending.truncate(at);
161        stack.pending.push(slot);
162    }
163}
164
165fn arity_of(op: &Op) -> usize {
166    match op {
167        Op::Const(_) | Op::Time | Op::Read { .. } | Op::SelfAt { .. } | Op::Physics(_) => 0,
168        Op::Map(_) | Op::Crop { .. } | Op::Channel(_) => 1,
169        Op::Sub | Op::Div | Op::Pow | Op::Zip(_) => 2,
170        Op::Add(n) | Op::Mul(n) | Op::Join(n) => *n,
171        Op::Filter(_) => 4,
172    }
173}
174
175#[allow(clippy::too_many_arguments)]
176fn fill(
177    op: &Op,
178    done: &[Vec<f64>],
179    srcs: &[usize],
180    result: &mut [f64],
181    ctx: &Ctx,
182    states: &mut [State],
183    i: usize,
184    t: f64,
185    sr: f64,
186) {
187    let arg = |k: usize| done[srcs[k]].as_slice();
188    match op {
189        Op::Const(v) => result[0] = *v,
190        Op::Time => result[0] = t,
191        Op::Read { id, shift } => {
192            let buffer = ctx.reads[id.0 as usize];
193            let at = i as i64 + shift;
194            for (c, slot) in result.iter_mut().enumerate() {
195                *slot = usize::try_from(at)
196                    .ok()
197                    .and_then(|k| buffer.plane(c).get(k).copied())
198                    .unwrap_or(0.0);
199            }
200        }
201        Op::SelfAt { steps } => {
202            let at = i as i64 - i64::from(*steps);
203            for (c, slot) in result.iter_mut().enumerate() {
204                *slot = usize::try_from(at)
205                    .ok()
206                    .filter(|k| *k < ctx.written)
207                    .and_then(|k| ctx.self_planes.get(c * ctx.len + k).copied())
208                    .unwrap_or(0.0);
209            }
210        }
211        Op::Add(_) | Op::Mul(_) => {
212            let product = matches!(op, Op::Mul(_));
213            for (c, slot) in result.iter_mut().enumerate() {
214                *slot =
215                    srcs.iter()
216                        .enumerate()
217                        .fold(f64::from(u8::from(product)), |acc, (k, _)| {
218                            if product {
219                                acc * part(arg(k), c)
220                            } else {
221                                acc + part(arg(k), c)
222                            }
223                        });
224            }
225        }
226        Op::Sub | Op::Div | Op::Pow | Op::Zip(_) => {
227            for (c, slot) in result.iter_mut().enumerate() {
228                let (a, b) = (part(arg(0), c), part(arg(1), c));
229                *slot = match op {
230                    Op::Sub => a - b,
231                    Op::Div => a / b,
232                    Op::Pow => a.powf(b),
233                    Op::Zip(f) => f.apply(a, b),
234                    _ => unreachable!("the arm's own guard"),
235                };
236            }
237        }
238        Op::Map(f) => {
239            for (c, slot) in result.iter_mut().enumerate() {
240                *slot = f.apply(part(arg(0), c));
241            }
242        }
243        Op::Crop { a, b } => {
244            let inside = t >= *a && t < *b;
245            for (c, slot) in result.iter_mut().enumerate() {
246                *slot = if inside { part(arg(0), c) } else { 0.0 };
247            }
248        }
249        Op::Join(_) => {
250            let mut c = 0;
251            for k in 0..srcs.len() {
252                for &v in arg(k) {
253                    result[c] = v;
254                    c += 1;
255                }
256            }
257        }
258        Op::Channel(k) => result[0] = arg(0)[*k],
259        Op::Filter(id) => {
260            let State::Filter(filter) = &mut states[id.0 as usize] else {
261                unreachable!("a filter op names a filter site")
262            };
263            filter.process(arg(0), arg(1), arg(2), arg(3), result, sr, i);
264        }
265        Op::Physics(id) => {
266            let State::Physics(solver) = &mut states[id.0 as usize] else {
267                unreachable!("a physics op names a physics site")
268            };
269            result[0] = solver.step();
270        }
271    }
272}