Skip to main content

sva_samples/machine/
ops.rs

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