Skip to main content

rill_lang/
program.rs

1//! `RillProgram<T>` — a compiled rill-lang program that implements
2//! [`rill_core::Algorithm`]. Owns its IR, schedule, and pre-allocated state;
3//! `process()` performs no heap allocation after warm-up.
4
5use rill_core::builtin::MultichannelBlockBuiltin;
6use rill_core::math::Transcendental;
7#[cfg(feature = "router")]
8use rill_core::traits::MultichannelAlgorithm;
9use rill_core::traits::{Algorithm, ParamValue, ProcessResult};
10
11use crate::builtin::{BlockBuiltin, SampleBuiltin};
12use crate::error::CompileError;
13use crate::ir::{Ir, ParamDef};
14use crate::schedule::{build_schedule, Schedule};
15
16/// A runtime built-in instance, indexed directly by IR `instance` fields.
17pub(crate) enum BuiltinInst<T: Transcendental> {
18    /// A per-sample stateful built-in.
19    Sample(Box<dyn SampleBuiltin<T>>),
20    /// An opaque whole-buffer built-in.
21    Block(Box<dyn BlockBuiltin<T>>),
22    /// A whole-buffer multi-channel built-in.
23    #[allow(dead_code)]
24    MultichannelBlock(Box<dyn MultichannelBlockBuiltin<T>>),
25}
26
27/// A compiled program ready to run inside the rill graph.
28pub struct RillProgram<T: Transcendental> {
29    pub(crate) ir: Ir,
30    pub(crate) schedule: Schedule,
31    /// Persistent feedback state (previous-sample values). Length = state_slots.
32    pub(crate) state: Vec<f64>,
33    /// Next-sample feedback writes, applied at sample end.
34    pub(crate) state_next: Vec<f64>,
35    /// Delay lines: ring buffers, one per `@` site.
36    pub(crate) delays: Vec<DelayRing>,
37    /// Whole-buffer register store for the hybrid path (grown to block length).
38    pub(crate) block_regs: Vec<Vec<T>>,
39    /// Scalar register file for the reference (per-sample) path.
40    pub(crate) regs_scalar: Vec<f64>,
41    /// Runtime built-in instances (indexed by `ir.builtins` indices).
42    pub(crate) builtins: Vec<BuiltinInst<T>>,
43    /// Current parameter values, indexed by [`Ir::params`].
44    pub(crate) params: Vec<ParamValue>,
45    /// Dirty flags: true when a param was changed since last push.
46    pub(crate) params_dirty: Vec<bool>,
47    /// Parameter metadata (name, default, range).
48    pub(crate) params_meta: Vec<ParamDef>,
49}
50
51/// A fixed-length ring buffer for one `@` delay site.
52pub(crate) struct DelayRing {
53    pub(crate) buf: Vec<f64>,
54    pub(crate) head: usize,
55}
56
57impl DelayRing {
58    pub(crate) fn new(len: usize) -> Self {
59        Self {
60            buf: vec![0.0; len.max(1)],
61            head: 0,
62        }
63    }
64    pub(crate) fn read(&self) -> f64 {
65        self.buf[self.head]
66    }
67    pub(crate) fn write(&mut self, v: f64) {
68        self.buf[self.head] = v;
69        self.head = (self.head + 1) % self.buf.len();
70    }
71}
72
73impl<T: Transcendental> RillProgram<T> {
74    /// Create a program from a compiled IR. Allocates state, delays, registers,
75    /// and builds the execution schedule. Built-ins are NOT instantiated — use
76    /// [`new_with`](Self::new_with) if the IR references built-in functions.
77    pub fn new(ir: Ir) -> Self {
78        let state = vec![0.0; ir.state.state_slots];
79        let state_next = state.clone();
80        let delays = ir
81            .state
82            .delay_lens
83            .iter()
84            .map(|&l| DelayRing::new(l))
85            .collect();
86        let block_regs = vec![Vec::new(); ir.num_regs];
87        let regs_scalar = vec![0.0; ir.num_regs];
88        let schedule = build_schedule(&ir);
89        let params_meta = ir.params.clone();
90        let params: Vec<ParamValue> = ir
91            .params
92            .iter()
93            .map(|p| ParamValue::Float(p.default as f32))
94            .collect();
95        let params_dirty = vec![false; params.len()];
96        Self {
97            ir,
98            schedule,
99            state,
100            state_next,
101            delays,
102            block_regs,
103            regs_scalar,
104            builtins: Vec::new(),
105            params,
106            params_dirty,
107            params_meta,
108        }
109    }
110
111    /// Create a program from a compiled [`Ir`], instantiating all built-ins
112    /// via the provided `Registry`. Also sets the initial `sample_rate`.
113    ///
114    /// Parses `builtins` from the IR, allocates registers, state, and delays,
115    /// and builds the execution schedule. The resulting program implements
116    /// [`Algorithm<T>`](rill_core::traits::Algorithm).
117    pub fn new_with(
118        ir: Ir,
119        registry: &crate::builtin::Registry<T>,
120        sample_rate: f32,
121    ) -> Result<Self, CompileError> {
122        let mut builtins = Vec::with_capacity(ir.builtins.len());
123        for bi in &ir.builtins {
124            let entry = registry.get(&bi.name).ok_or_else(|| {
125                CompileError::Unsupported(format!("unknown built-in '{}'", bi.name))
126            })?;
127            match bi.kind {
128                crate::builtin::BuiltinKind::Sample => {
129                    let mut b = entry
130                        .build_sample(&bi.params, sample_rate)
131                        .expect("registry build_sample failed for sample builtin");
132                    b.init(sample_rate);
133                    builtins.push(BuiltinInst::Sample(b));
134                }
135                crate::builtin::BuiltinKind::Block => {
136                    let mut b = entry
137                        .build_block(&bi.params, sample_rate)
138                        .expect("registry build_block failed for block builtin");
139                    Algorithm::init(b.as_mut(), sample_rate);
140                    builtins.push(BuiltinInst::Block(b));
141                }
142            }
143        }
144        let state = vec![0.0; ir.state.state_slots];
145        let state_next = state.clone();
146        let delays = ir
147            .state
148            .delay_lens
149            .iter()
150            .map(|&l| DelayRing::new(l))
151            .collect();
152        let block_regs = vec![Vec::new(); ir.num_regs];
153        let regs_scalar = vec![0.0; ir.num_regs];
154        let schedule = build_schedule(&ir);
155        let params_meta = ir.params.clone();
156        let params: Vec<ParamValue> = ir
157            .params
158            .iter()
159            .map(|p| ParamValue::Float(p.default as f32))
160            .collect();
161        let params_dirty = vec![false; params.len()];
162        Ok(Self {
163            ir,
164            schedule,
165            state,
166            state_next,
167            delays,
168            block_regs,
169            regs_scalar,
170            builtins,
171            params,
172            params_dirty,
173            params_meta,
174        })
175    }
176
177    /// Ensure every block register can hold `n` samples (grows + reuses).
178    pub(crate) fn ensure_block_len(&mut self, n: usize) {
179        for r in &mut self.block_regs {
180            if r.len() < n {
181                r.resize(n, T::ZERO);
182            }
183        }
184    }
185
186    /// Index of a named parameter, if present.
187    pub fn param_index(&self, name: &str) -> Option<usize> {
188        self.params_meta.iter().position(|p| p.name == name)
189    }
190
191    /// Set a parameter by index. RT-safe (plain store).
192    pub fn set_param(&mut self, idx: usize, value: ParamValue) {
193        if let Some(def) = self.params_meta.get(idx) {
194            let clamped = match &value {
195                ParamValue::Float(v) => {
196                    ParamValue::Float((*v as f64).clamp(def.min, def.max) as f32)
197                }
198                ParamValue::Int(v) if *v as f64 >= def.min && (*v as f64) <= def.max => value,
199                _ => value,
200            };
201            self.params[idx] = clamped;
202            if let Some(d) = self.params_dirty.get_mut(idx) {
203                *d = true;
204            }
205        }
206    }
207
208    /// Current value of a parameter by index.
209    pub fn param(&self, idx: usize) -> ParamValue {
210        self.params
211            .get(idx)
212            .cloned()
213            .unwrap_or(ParamValue::Float(0.0))
214    }
215
216    /// Metadata for all parameters (name, default, range).
217    pub fn params_meta(&self) -> &[ParamDef] {
218        &self.params_meta
219    }
220
221    /// Reference implementation: the MVP per-sample interpreter. Used by tests
222    /// as a numerical oracle; not the production path.
223    pub fn process_reference(
224        &mut self,
225        input: Option<&[T]>,
226        output: &mut [T],
227    ) -> ProcessResult<()> {
228        crate::backend::interp::run_block_reference(self, input, output);
229        Ok(())
230    }
231
232    /// Forward initialisation to all built-in instances.
233    pub fn init(&mut self, sample_rate: f32) {
234        for b in &mut self.builtins {
235            match b {
236                BuiltinInst::Sample(inst) => inst.init(sample_rate),
237                BuiltinInst::Block(inst) => Algorithm::init(inst.as_mut(), sample_rate),
238                BuiltinInst::MultichannelBlock(_) => {}
239            }
240        }
241    }
242}
243
244impl<T: Transcendental> Algorithm<T> for RillProgram<T> {
245    fn process(&mut self, input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
246        crate::backend::interp::run_block_hybrid(self, input, output);
247        Ok(())
248    }
249
250    fn reset(&mut self) {
251        for s in &mut self.state {
252            *s = 0.0;
253        }
254        for s in &mut self.state_next {
255            *s = 0.0;
256        }
257        for d in &mut self.delays {
258            for v in &mut d.buf {
259                *v = 0.0;
260            }
261            d.head = 0;
262        }
263        for b in &mut self.builtins {
264            match b {
265                BuiltinInst::Sample(inst) => inst.reset(),
266                BuiltinInst::Block(inst) => Algorithm::reset(inst.as_mut()),
267                BuiltinInst::MultichannelBlock(_) => {}
268            }
269        }
270    }
271}
272
273#[cfg(feature = "router")]
274impl<T: Transcendental> MultichannelAlgorithm<T> for RillProgram<T> {
275    fn num_inputs(&self) -> usize {
276        self.ir.num_inputs
277    }
278
279    fn num_outputs(&self) -> usize {
280        self.ir.num_outputs
281    }
282
283    fn process(&mut self, inputs: &[&[T]], outputs: &mut [&mut [T]]) -> ProcessResult<()> {
284        let n_in = inputs.len();
285        let n_out = outputs.len();
286        let buf_size = if n_out > 0 { outputs[0].len() } else { 0 };
287
288        if n_in <= 1 && n_out == 1 {
289            let input = if n_in == 0 { None } else { Some(inputs[0]) };
290            return Algorithm::process(self, input, outputs[0]);
291        }
292
293        crate::backend::interp::push_builtin_params(self);
294        for sample_idx in 0..buf_size {
295            let in_sample = if n_in > 0 {
296                inputs[0][sample_idx].to_f64()
297            } else {
298                0.0
299            };
300            let y = crate::backend::interp::eval_sample_scalar(self, in_sample);
301            if n_out > 0 {
302                outputs[0][sample_idx] = T::from_f64(y);
303            }
304        }
305        Ok(())
306    }
307
308    fn reset(&mut self) {
309        Algorithm::reset(self);
310    }
311}
312
313impl<T: Transcendental> BlockBuiltin<T> for RillProgram<T> {
314    fn set_param(&mut self, index: usize, value: &ParamValue) {
315        self.set_param(index, value.clone());
316    }
317}