Skip to main content

sva_samples/machine/
ops.rs

1// Concern: the op array one node renderer lowers to, each identical op once, and the width each slot holds | Non-concern: running it (mod.rs) | IO: (&NodeRenderer, &Layout) -> ops, widths, operands
2
3use std::collections::HashMap;
4
5use crate::error::SampleError;
6use crate::machine::renderer::{
7    Between, Binary, Formula, Grid, Index, Map, NodeRenderer, Site, SiteId, Slot, Stepped, Unary,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub(crate) enum Op {
12    Const(f64),
13    Time,
14    /// `None` where its constants overflow on the program's grid.
15    Wrap(Option<Stepped>),
16    /// The draw of `seed` at the step of the rate nearest each sample; `None` where that map
17    /// overflows.
18    Noise {
19        seed: u64,
20        at: Option<Map>,
21    },
22    Read {
23        slot: Slot,
24        at: Map,
25    },
26    /// A read times a constant, the bits of `Mul` over the two.
27    ReadScaled {
28        slot: Slot,
29        at: Map,
30        by: f64,
31    },
32    /// The sample at the program's index `at`, over the `arity` step instants below it.
33    Indexed {
34        slot: Slot,
35        at: usize,
36        arity: usize,
37        reach: Option<(i64, i64)>,
38    },
39    Instant {
40        at: usize,
41        arity: usize,
42    },
43    /// The program's formula `at`, at the instant the operand below it names.
44    Formula {
45        at: usize,
46    },
47    Add(usize),
48    Mul(usize),
49    Sub,
50    Div,
51    Pow,
52    Map(Unary),
53    Zip(Binary),
54    Crop {
55        window: (i64, i64),
56        a: f64,
57        b: f64,
58        rise: f64,
59        fall: f64,
60    },
61    Join(usize),
62    Channel(usize),
63    Filter {
64        site: SiteId,
65        from: i64,
66    },
67    Physics {
68        site: SiteId,
69        from: i64,
70        arity: usize,
71    },
72}
73
74/// What the engine already knows from typing: the grid the node steps on, how wide it is,
75/// how wide each collapsed read is, and which call sites the renderer opens.
76#[derive(Clone, Debug, PartialEq)]
77pub struct Layout {
78    pub grid: Grid,
79    pub width: usize,
80    pub read_widths: Vec<usize>,
81    pub sites: Vec<Site>,
82}
83
84/// Equal widths pass, a mono side widens, anything else refuses.
85fn meet(a: usize, b: usize) -> Result<usize, SampleError> {
86    match (a, b) {
87        (a, b) if a == b => Ok(a),
88        (1, b) => Ok(b),
89        (a, 1) => Ok(a),
90        (left, right) => Err(SampleError::WidthMismatch { left, right }),
91    }
92}
93
94/// Each op's width and operands, and the formulas a `Formula` op names by position.
95#[derive(Default)]
96pub(crate) struct Lowered {
97    pub(crate) ops: Vec<Op>,
98    pub(crate) widths: Vec<usize>,
99    pub(crate) args: Vec<Vec<usize>>,
100    pub(crate) formulas: Vec<Formula>,
101    pub(crate) indices: Vec<Index<usize>>,
102    held: HashMap<String, usize>,
103}
104
105/// Operands before the ops reading them, so the runner needs no recursion and every operand's
106/// width is settled by the time the op that consumes it is reached; the root is the last op.
107pub(crate) fn lowered(
108    renderer: &NodeRenderer,
109    layout: &Layout,
110) -> Result<(Lowered, usize), SampleError> {
111    let mut out = Lowered::default();
112    let root = lower(renderer, layout, &mut out)?;
113    debug_assert_eq!(root, out.ops.len() - 1, "an op is never its own operand");
114    let width = out.widths[root];
115    Ok((out, width))
116}
117
118impl Lowered {
119    /// `op`'s slot; an identical op already pushed is that slot, evaluated once as priced once.
120    fn push(&mut self, op: Op, width: usize, args: Vec<usize>) -> usize {
121        let key = self.key(&op, &args);
122        if let Some(slot) = key.as_ref().and_then(|key| self.held.get(key)) {
123            if matches!(op, Op::Indexed { .. } | Op::Instant { .. }) {
124                self.indices.pop();
125            }
126            return *slot;
127        }
128        self.ops.push(op);
129        self.widths.push(width);
130        self.args.push(args);
131        let slot = self.ops.len() - 1;
132        if let Some(key) = key {
133            self.held.insert(key, slot);
134        }
135        slot
136    }
137
138    /// What `op` computes, numbers by their bits; `None` for a state or a formula, never shared.
139    fn key(&self, op: &Op, args: &[usize]) -> Option<String> {
140        let bits = |v: &[f64]| v.iter().map(|v| v.to_bits()).collect::<Vec<u64>>();
141        let what = match op {
142            Op::Const(v) => format!("const {:?}", bits(&[*v])),
143            Op::ReadScaled { slot, at, by } => format!("scaled {slot:?} {at:?} {:?}", bits(&[*by])),
144            Op::Crop {
145                window,
146                a,
147                b,
148                rise,
149                fall,
150            } => format!("crop {window:?} {:?}", bits(&[*a, *b, *rise, *fall])),
151            Op::Indexed {
152                slot, at, reach, ..
153            } => format!("indexed {slot:?} {reach:?} {:?}", self.indices[*at]),
154            Op::Instant { at, .. } => format!("instant {:?}", self.indices[*at]),
155            Op::Formula { .. } | Op::Filter { .. } | Op::Physics { .. } => return None,
156            other => format!("{other:?}"),
157        };
158        Some(format!("{what} {args:?}"))
159    }
160}
161
162fn indexed(
163    index: &Index,
164    layout: &Layout,
165    out: &mut Lowered,
166) -> Result<(usize, Vec<usize>), SampleError> {
167    let mut operands = Vec::new();
168    let program = index.mapped(&mut |time| {
169        operands.push(lower(time, layout, out)?);
170        Ok::<usize, SampleError>(operands.len() - 1)
171    })?;
172    out.indices.push(program);
173    Ok((out.indices.len() - 1, operands))
174}
175
176/// The slot holding `r`.
177fn lower(r: &NodeRenderer, layout: &Layout, out: &mut Lowered) -> Result<usize, SampleError> {
178    if let NodeRenderer::Mul(parts) = r
179        && let Some((slot, at, by)) = scaled_read(parts)
180    {
181        let width = slot_width(slot, layout);
182        return Ok(out.push(Op::ReadScaled { slot, at, by }, width, Vec::new()));
183    }
184    let (op, operands) = match r {
185        NodeRenderer::Formula { formula, time, .. } => {
186            let operands = vec![lower(time, layout, out)?];
187            out.formulas.push(formula.clone());
188            let at = out.formulas.len() - 1;
189            (Op::Formula { at }, operands)
190        }
191        NodeRenderer::Indexed { slot, index, reach } => {
192            let (at, operands) = indexed(index, layout, out)?;
193            let op = Op::Indexed {
194                slot: *slot,
195                at,
196                arity: operands.len(),
197                reach: *reach,
198            };
199            (op, operands)
200        }
201        NodeRenderer::Instant(index) => {
202            let (at, operands) = indexed(index, layout, out)?;
203            let arity = operands.len();
204            (Op::Instant { at, arity }, operands)
205        }
206        other => {
207            let operands = other
208                .operands()
209                .into_iter()
210                .map(|p| lower(p, layout, out))
211                .collect::<Result<Vec<_>, _>>()?;
212            (op_of(other, layout), operands)
213        }
214    };
215    let widths: Vec<usize> = operands.iter().map(|s| out.widths[*s]).collect();
216    let w = width(r, &widths, layout)?;
217    Ok(out.push(op, w, operands))
218}
219
220fn op_of(r: &NodeRenderer, layout: &Layout) -> Op {
221    match r {
222        NodeRenderer::Const(v) => Op::Const(*v),
223        NodeRenderer::Time => Op::Time,
224        NodeRenderer::Wrap(wrap) => Op::Wrap(wrap.on(layout.grid)),
225        NodeRenderer::Noise(seed) => Op::Noise {
226            seed: *seed,
227            at: Map::rounded(layout.grid.a, 0, layout.grid.d, Between::Even),
228        },
229        NodeRenderer::Read { slot, map } => Op::Read {
230            slot: *slot,
231            at: *map,
232        },
233        NodeRenderer::Add(parts) => Op::Add(parts.len()),
234        NodeRenderer::Mul(parts) => Op::Mul(parts.len()),
235        NodeRenderer::Join(parts) => Op::Join(parts.len()),
236        NodeRenderer::Sub(..) => Op::Sub,
237        NodeRenderer::Div(..) => Op::Div,
238        NodeRenderer::Pow(..) => Op::Pow,
239        NodeRenderer::Map(f, _) => Op::Map(*f),
240        NodeRenderer::Zip(f, ..) => Op::Zip(*f),
241        NodeRenderer::Crop {
242            window,
243            a,
244            b,
245            rise,
246            fall,
247            ..
248        } => Op::Crop {
249            window: *window,
250            a: *a,
251            b: *b,
252            rise: *rise,
253            fall: *fall,
254        },
255        NodeRenderer::Channel { k, .. } => Op::Channel(*k),
256        NodeRenderer::Filter { site, from, .. } => Op::Filter {
257            site: *site,
258            from: *from,
259        },
260        NodeRenderer::Physics { site, from, args } => Op::Physics {
261            site: *site,
262            from: *from,
263            arity: args.len(),
264        },
265        NodeRenderer::Formula { .. } | NodeRenderer::Indexed { .. } | NodeRenderer::Instant(_) => {
266            unreachable!("lowered with the program tables they name")
267        }
268    }
269}
270
271/// The width `r` holds over operands `operands` wide, in `NodeRenderer::operands` order.
272pub(crate) fn width(
273    r: &NodeRenderer,
274    operands: &[usize],
275    layout: &Layout,
276) -> Result<usize, SampleError> {
277    let met = |from: usize| operands.iter().try_fold(from, |w, &o| meet(w, o));
278    Ok(match r {
279        NodeRenderer::Const(_)
280        | NodeRenderer::Time
281        | NodeRenderer::Wrap(_)
282        | NodeRenderer::Noise(_) => 1,
283        NodeRenderer::Read { slot, .. } => slot_width(*slot, layout),
284        NodeRenderer::Formula { width, .. } => match operands {
285            [times] if *times != 1 && times != width => {
286                return Err(SampleError::WidthMismatch {
287                    left: *times,
288                    right: *width,
289                });
290            }
291            _ => *width,
292        },
293        NodeRenderer::Indexed { slot, .. } => slot_width(*slot, layout),
294        NodeRenderer::Instant(_) | NodeRenderer::Physics { .. } => 1,
295        NodeRenderer::Add(_) | NodeRenderer::Mul(_) => met(1)?,
296        NodeRenderer::Sub(..)
297        | NodeRenderer::Div(..)
298        | NodeRenderer::Pow(..)
299        | NodeRenderer::Zip(..)
300        | NodeRenderer::Filter { .. } => met(operands[0])?,
301        NodeRenderer::Map(..) | NodeRenderer::Crop { .. } => operands[0],
302        NodeRenderer::Join(_) => operands.iter().sum(),
303        NodeRenderer::Channel { k, .. } => match operands[0] {
304            w if *k >= w => return Err(SampleError::ChannelOutOfRange { k: *k, width: w }),
305            _ => 1,
306        },
307    })
308}
309
310fn slot_width(slot: Slot, layout: &Layout) -> usize {
311    match slot {
312        Slot::Read(id) => layout.read_widths[id.0 as usize],
313        Slot::Own => layout.width,
314    }
315}
316
317fn scaled_read(parts: &[NodeRenderer]) -> Option<(Slot, Map, f64)> {
318    match parts {
319        [NodeRenderer::Read { slot, map }, NodeRenderer::Const(by)]
320        | [NodeRenderer::Const(by), NodeRenderer::Read { slot, map }] => Some((*slot, *map, *by)),
321        _ => None,
322    }
323}