Skip to main content

bliplib/
compiler.rs

1use std::{
2    any::{Any, TypeId, type_name},
3    collections::BTreeMap,
4    f64,
5    fmt::{Debug, Display},
6    str::FromStr,
7};
8
9use cfg_if::cfg_if;
10use derive_new::new;
11use derive_wrapper::{AsRef, From};
12use fasteval::{Compiler as _, EvalNamespace, Evaler, Instruction, Slab};
13use log::{debug, trace};
14use thiserror::Error;
15
16cfg_if! {
17    if #[cfg(test)] {
18        /// Static sample rate.
19        pub const SAMPLE_RATE: u16 = 10;
20    } else {
21        /// Static sample rate.
22        pub const SAMPLE_RATE: u16 = 48000;
23    }
24}
25
26/// A wrapper for a Vec of tokens.
27#[derive(Debug, From, AsRef, Default)]
28pub struct TokenVec(pub(crate) Vec<Box<dyn Token>>);
29
30impl IntoIterator for TokenVec {
31    type Item = Box<dyn Token>;
32    type IntoIter = <Vec<Box<(dyn Token + 'static)>> as IntoIterator>::IntoIter;
33    fn into_iter(self) -> Self::IntoIter {
34        self.0.into_iter()
35    }
36}
37
38impl Token for Box<dyn Token> {
39    fn apply(&self, context: Context) -> Result<Context, CompilerError> {
40        self.as_ref().apply(context)
41    }
42}
43
44impl Type for Box<dyn Token> {
45    fn type_id(&self) -> TypeId {
46        self.as_ref().type_id()
47    }
48}
49
50/// Used for getting the `type_id` of a type without it being [`std::any::Any`].
51pub trait Type {
52    /// Get the type ID of self without self being [`std::any::Any`].
53    fn type_id(&self) -> TypeId;
54}
55
56impl<T> Type for T
57where
58    T: Default + 'static,
59{
60    fn type_id(&self) -> TypeId {
61        <Self as Any>::type_id(&Default::default())
62    }
63}
64
65/// BLIP tokens implement this.
66pub trait Token: Debug + Type {
67    /// Applies itself to a context for compilation into samples.
68    fn apply(&self, context: Context) -> Result<Context, CompilerError>;
69}
70
71#[cfg(test)]
72impl PartialEq for TokenVec {
73    fn eq(&self, other: &Self) -> bool {
74        format!("{self:?}") == format!("{other:?}")
75    }
76}
77
78/// Litteral silence. No sound for the duration of a note.
79#[derive(Debug, Clone, Copy, Default)]
80#[cfg_attr(test, derive(PartialEq))]
81pub struct Silence;
82
83impl Token for Silence {
84    fn apply(&self, context: Context) -> Result<Context, CompilerError> {
85        debug!("⚡ {}", type_name::<Self>());
86        let (mut context, mut next) = context.render(None)?;
87        context.result.append(&mut next);
88        Ok(context)
89    }
90}
91
92/// Used to indicate where the playback or export should start from in the sheet music.
93#[derive(Debug, Clone, Copy, Default)]
94#[cfg_attr(test, derive(PartialEq))]
95pub struct Marker;
96
97impl Token for Marker {
98    fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
99        debug!("⚡ {}", type_name::<Self>());
100        context.result.clear();
101        Ok(context)
102    }
103}
104
105/// A note. The underlying `u8` is for its position in the provided list of notes to be parsed.
106#[derive(Debug, Clone, Copy, Default)]
107#[cfg_attr(test, derive(PartialEq))]
108pub struct Note(pub u8);
109
110impl Token for Note {
111    fn apply(&self, context: Context) -> Result<Context, CompilerError> {
112        debug!("⚡ {}", type_name::<Self>());
113        let (mut context, mut next) = context.render(Some(self.0))?;
114        context.result.append(&mut next);
115        Ok(context)
116    }
117}
118
119/// Describes the instant mutation of a variable by the result of the provided [`Expression`] using the current values of all available variables.
120#[derive(Debug, Clone, Default)]
121#[cfg_attr(test, derive(PartialEq))]
122pub struct VariableChange(pub char, pub Expression);
123
124impl AsRef<VariableChange> for VariableChange {
125    fn as_ref(&self) -> &VariableChange {
126        self
127    }
128}
129
130impl Token for VariableChange {
131    fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
132        debug!("⚡ {}", type_name::<Self>());
133        *context.get_mut(self.0.to_string())? = context.eval(self.1.as_ref())?;
134        Ok(context)
135    }
136}
137
138/// A wrapper for other tokens which repeat those tokens N times using the number described by [`LoopCount`].
139#[derive(Debug, Default)]
140#[cfg_attr(test, derive(PartialEq))]
141pub struct Loop(pub LoopCount, pub TokenVec);
142
143/// Describes the number of times a loop should repeat its captured sequence of tokens.
144#[derive(Debug)]
145#[cfg_attr(test, derive(PartialEq))]
146pub enum LoopCount {
147    /// Fixed number of times
148    Litteral(usize),
149    /// Variable number of times, using the current value of the underlying variable
150    Variable(char),
151}
152
153impl Default for LoopCount {
154    fn default() -> Self {
155        LoopCount::Litteral(2)
156    }
157}
158
159impl Token for Loop {
160    fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
161        debug!("⚡ {}", type_name::<Self>());
162        let mut old_result = context.result.clone();
163        let count = match self.0 {
164            LoopCount::Litteral(n) => n,
165            LoopCount::Variable(v) => *context.get(v.to_string())? as usize,
166        };
167        context.result.clear();
168        let new_context = self
169            .1
170            .0
171            .iter()
172            .try_fold(context, |context, t| t.apply(context))?;
173        old_result.append(&mut new_context.result.repeat(count));
174        Ok(Context {
175            result: old_result,
176            ..new_context
177        })
178    }
179}
180
181/// The duration of the underlying tokens in a tuplet will fit in the current calculated duration of a single note. The length of all these tokens will be shrinked equally.
182#[derive(Debug, Default)]
183#[cfg_attr(test, derive(PartialEq))]
184pub struct Tuplet(pub TokenVec);
185
186impl Token for Tuplet {
187    fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
188        debug!("⚡ {}", type_name::<Self>());
189        if self.0.as_ref().is_empty() {
190            return Err(CompilerError::Nilplet);
191        }
192        let mut old_result = context.result.clone();
193        context.result.clear();
194        let mut new_context = self
195            .0
196            .0
197            .iter()
198            .try_fold(context, |context, t| t.apply(context))?;
199        let len = new_context.result.len();
200        new_context.result = new_context
201            .result
202            .into_iter()
203            .step_by(
204                len / self
205                    .0
206                    .0
207                    .iter()
208                    .filter(|t| {
209                        t.as_ref().type_id() == Type::type_id(&Note(0u8))
210                            || t.as_ref().type_id() == Type::type_id(&Silence)
211                    })
212                    .count(),
213            )
214            .collect();
215        old_result.append(&mut new_context.result);
216        Ok(Context {
217            result: old_result,
218            ..new_context
219        })
220    }
221}
222
223/// A slope which describes a smooth change of a variable which will affect all captured tokens. Variable mutation occurs every frame / sample.
224#[derive(Debug, new, Default)]
225#[cfg_attr(test, derive(PartialEq))]
226pub struct Slope(pub VariableChange, pub TokenVec);
227
228impl Token for Slope {
229    fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
230        debug!("⚡ {}", type_name::<Self>());
231        context.slopes.push((
232            self.0.as_ref().0.to_string(),
233            self.0.as_ref().1.as_ref().clone(),
234        ));
235        context = self
236            .1
237            .0
238            .iter()
239            .try_fold(context, |context, t| t.apply(context))?;
240        context.slopes.pop();
241        Ok(context)
242    }
243}
244
245/// A mathematical expression to be evaluated with a set of variables to form a single [`f64`] value.
246#[derive(Debug, new, Default)]
247pub struct Expression {
248    /// Expression origin, used for cloning.
249    from: String,
250    /// The wrapped fasteval struct.
251    pub(crate) instruction: Instruction,
252    /// The slab which contains all the information referenced by the [`Instruction`]. An Instruction without a Slab is usually unusable.
253    pub(crate) slab: Slab,
254}
255
256impl AsRef<Expression> for Expression {
257    fn as_ref(&self) -> &Expression {
258        self
259    }
260}
261
262impl Clone for Expression {
263    fn clone(&self) -> Self {
264        self.from
265            .parse()
266            .unwrap_or_else(|_| panic!("expression {self:?} have an invalid from"))
267    }
268}
269
270impl PartialEq for Expression {
271    fn eq(&self, other: &Self) -> bool {
272        format!("{self:?}") == format!("{other:?}")
273    }
274}
275
276impl FromStr for Expression {
277    type Err = fasteval::Error;
278    fn from_str(s: &str) -> Result<Self, Self::Err> {
279        let mut slab = Slab::new();
280        let instruction = fasteval::Parser::new()
281            .parse(s, &mut slab.ps)?
282            .from(&slab.ps)
283            .compile(&slab.ps, &mut slab.cs);
284        Ok(Expression::new(
285            s.trim_start().trim_end().to_string(),
286            instruction,
287            slab,
288        ))
289    }
290}
291
292impl Display for Expression {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        Display::fmt(&self.from, f)
295    }
296}
297
298/// Compiler context which persists from one token to the other.
299#[derive(Debug, Clone, new)]
300#[cfg_attr(test, derive(PartialEq))]
301pub struct Context {
302    note_length_variable: String,
303    note_index_variable: String,
304    /// Vec of samples resulting from compilation.
305    #[new(default)]
306    pub result: Vec<f64>,
307    /// Set of variables used for every [`Expression`] evaluation.
308    #[new(into_iter = "(String, f64)")]
309    pub variables: BTreeMap<String, f64>,
310    /// Instrument Expression which will be used to directly generate samples from the tokens.
311    pub instrument: Expression,
312    /// Set of [`Slope`]s to be applied to their corresponding variable at every frame. The string is the variable name.
313    #[new(into_iter = "(String, Expression)")]
314    pub slopes: Vec<(String, Expression)>,
315}
316
317/// A variable which is expected to be present in the [`Context`]'s set of variables could not be found.
318#[derive(Debug, Error)]
319#[cfg_attr(test, derive(PartialEq))]
320#[error("variable not found: {0}")]
321pub struct VariableNotFoundError(String);
322
323/// A slope which is expected to be present in the [`Context`]'s set of slopes could not be found.
324#[derive(Debug, Error)]
325#[cfg_attr(test, derive(PartialEq))]
326#[error("expression not found: {0}")]
327pub struct SlopeNotFoundError(String);
328
329/// An error occurred during the compilation of tokens into samples.
330#[derive(Debug, Error)]
331#[cfg_attr(test, derive(PartialEq))]
332pub enum CompilerError {
333    /// The error comes from a `fasteval` operation.
334    #[error("expression evaluation: {0:?}")]
335    FastEval(#[from] fasteval::Error),
336    /// See [`VariableNotFoundError`].
337    #[error(transparent)]
338    VariableNotFound(#[from] VariableNotFoundError),
339    /// See [`SlopeNotFoundError`].
340    #[error(transparent)]
341    SlopeNotFound(#[from] SlopeNotFoundError),
342    /// A tuplet was found which carried no underlying tokens.
343    #[error(
344        "🎉 You successfully made a nilplet / noplet (and I don't know what to do with it)\nTo resume compilation, remove any occurrence of \"[]\" in your sheet music."
345    )]
346    Nilplet,
347}
348
349impl Context {
350    /// Calculates the current note length according to the note length expression and the current values of registered variables.
351    pub fn current_length(self) -> Result<(Self, f64), CompilerError> {
352        let Context {
353            note_length_variable,
354            note_index_variable,
355            result,
356            mut variables,
357            instrument,
358            slopes,
359        } = self;
360        let mut slopes_iter = slopes
361            .iter()
362            .filter_map(|(c, e)| (c == &note_length_variable).then_some(e))
363            .peekable();
364        if slopes_iter.peek().is_none() {
365            return Err(SlopeNotFoundError(note_length_variable.clone()).into());
366        }
367        for slope in slopes_iter {
368            *variables
369                .get_mut(&note_length_variable)
370                .ok_or(VariableNotFoundError(note_length_variable.clone()))? =
371                slope.instruction.eval(&slope.slab, &mut variables)?;
372        }
373        let note_length = *variables
374            .get(&note_length_variable)
375            .ok_or(VariableNotFoundError(note_length_variable.clone()))?;
376        Ok((
377            Context {
378                note_length_variable,
379                note_index_variable,
380                result,
381                variables,
382                instrument,
383                slopes,
384            },
385            note_length,
386        ))
387    }
388
389    /// Get a variable from the set.
390    pub fn get(&self, name: impl AsRef<str> + Into<String>) -> Result<&f64, VariableNotFoundError> {
391        self.variables
392            .get(name.as_ref())
393            .ok_or(VariableNotFoundError(name.into()))
394    }
395
396    /// Get a variable from the set with a mutable reference.
397    pub fn get_mut(
398        &mut self,
399        name: impl AsRef<str> + Into<String>,
400    ) -> Result<&mut f64, VariableNotFoundError> {
401        self.variables
402            .get_mut(name.as_ref())
403            .ok_or(VariableNotFoundError(name.into()))
404    }
405
406    /// Get a slope from the set using the name of the variable it's supposed to alter the value of.
407    pub fn get_slopes_for(
408        &self,
409        var: impl for<'a> PartialEq<&'a String> + Into<String>,
410    ) -> Result<Vec<&Expression>, SlopeNotFoundError> {
411        let result: Vec<&Expression> = self
412            .slopes
413            .iter()
414            .filter_map(|(c, e)| (var == c).then_some(e))
415            .collect();
416        if result.is_empty() {
417            Err(SlopeNotFoundError(var.into()))
418        } else {
419            Ok(result)
420        }
421    }
422
423    fn tick(mut self) -> Result<Self, CompilerError> {
424        *self.get_mut("t")? += 1f64 / (SAMPLE_RATE as f64);
425        let Context {
426            note_length_variable,
427            note_index_variable,
428            result,
429            mut variables,
430            instrument,
431            slopes,
432        } = self;
433        for (var, expr) in slopes.iter() {
434            *variables
435                .get_mut(var)
436                .ok_or(VariableNotFoundError(var.clone()))? =
437                expr.instruction.eval(&expr.slab, &mut variables)?;
438        }
439        Ok(Context {
440            note_length_variable,
441            note_index_variable,
442            result,
443            variables,
444            instrument,
445            slopes,
446        })
447    }
448
449    /// Evaluate the result of an Expression using the current values of the variables registered in the set.
450    pub fn eval(&mut self, expr: &Expression) -> Result<f64, fasteval::Error> {
451        Self::eval_with(expr, &mut self.variables)
452    }
453
454    /// Evaluate the result of an Expression using the current values of the variables registered in the provided namespace.
455    pub fn eval_with(
456        Expression {
457            from,
458            instruction,
459            slab,
460        }: &Expression,
461        ns: &mut impl EvalNamespace,
462    ) -> Result<f64, fasteval::Error> {
463        instruction
464            .eval(slab, ns)
465            .inspect(|ok| trace!("{from} = {ok}"))
466            .inspect_err(|e| trace!("{from} = {e}"))
467    }
468
469    /// Render a note (by providing the index of the note in the note list as `n`) or a silence (with None for `n`) into samples and return them in a Vec.
470    pub fn render(mut self, n: Option<u8>) -> Result<(Self, Vec<f64>), CompilerError> {
471        let curr_t = *self.get("t")?;
472        if let Some(note) = n {
473            let mut result = Vec::new();
474            self.variables
475                .insert(self.note_index_variable.clone(), note as f64);
476            while {
477                let (new_self, length) = self.current_length()?;
478                self = new_self;
479                length
480            } > *self.get("t")? - curr_t + (1f64 / SAMPLE_RATE as f64)
481            {
482                result.push(Self::eval_with(&self.instrument, &mut self.variables)?);
483                self = self.tick()?;
484            }
485            Ok((self, result))
486        } else {
487            while {
488                let (new_self, length) = self.current_length()?;
489                self = new_self;
490                length
491            } > *self.get("t")? - curr_t
492            {
493                self = self.tick()?;
494            }
495            let len = (*self.get("t")? - curr_t) * SAMPLE_RATE as f64;
496            Ok((self, vec![0.0; len as usize]))
497        }
498    }
499
500    /// Explodes the context, leaving only the resulting Vec of samples.
501    pub fn finalize(self) -> Vec<f64> {
502        self.result
503    }
504}
505
506/// Convenience struct for the whole compilation phase.
507#[derive(From)]
508#[cfg_attr(test, derive(Debug, PartialEq))]
509pub struct Compiler(Context);
510
511impl Compiler {
512    /// Applies a single token on the underlying [`Context`].
513    pub fn step(self, token: impl Token) -> Result<Self, CompilerError> {
514        token.apply(self.0).map(Into::into)
515    }
516    fn apply_all(
517        self,
518        tokens: impl IntoIterator<Item = impl Token>,
519    ) -> Result<Self, CompilerError> {
520        tokens
521            .into_iter()
522            .try_fold(self, |acc, token| acc.step(token))
523    }
524    /// Applies every token on the underlying [`Context`] and returns the resulting samples as an iterator.
525    pub fn compile_all(
526        self,
527        tokens: impl IntoIterator<Item = impl Token>,
528    ) -> Result<Vec<f64>, CompilerError> {
529        self.apply_all(tokens).map(|c| c.0).map(Context::finalize)
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use crate::compiler::{Compiler, Expression, Note, SAMPLE_RATE, Silence};
536
537    use super::{CompilerError, Context};
538
539    #[test]
540    fn expression_is_clone() {
541        let expr: Expression = "1 + 5 / x".parse().unwrap();
542        assert_eq!(expr, expr.clone());
543    }
544
545    fn context_generator() -> Context {
546        Context::new(
547            'L'.to_string(),
548            'n'.to_string(),
549            [
550                ('a', 5.0),
551                ('t', 0.0),
552                ('n', 0.0),
553                ('N', 12.0),
554                ('L', 0.0),
555                ('l', 4.0),
556                ('T', 60.0),
557            ]
558            .map(|(c, f)| (c.to_string(), f)),
559            "sin(2*pi()*(442+442*((n+1)/N))*t)".parse().unwrap(),
560            [('L', "2^(2-log(2, l))*(60/T)")].map(|(c, e)| (c.to_string(), e.parse().unwrap())),
561        )
562    }
563
564    #[test]
565    fn silence_renders_correct_amount_of_samples() -> Result<(), CompilerError> {
566        assert_eq!(
567            SAMPLE_RATE as usize,
568            Compiler::from(context_generator())
569                .apply_all(vec![Silence])?
570                .0
571                .result
572                .len()
573        );
574        Ok(())
575    }
576
577    #[test]
578    fn silence_renders_zeros() -> Result<(), CompilerError> {
579        assert!(
580            Compiler::from(context_generator())
581                .apply_all(vec![Silence])?
582                .0
583                .result
584                .into_iter()
585                .all(|s| s == 0f64)
586        );
587        Ok(())
588    }
589
590    #[test]
591    fn note_renders_correct_amount_of_samples() -> Result<(), CompilerError> {
592        assert_eq!(
593            SAMPLE_RATE as usize,
594            Compiler::from(context_generator())
595                .apply_all(vec![Note(2)])?
596                .0
597                .result
598                .len()
599        );
600        Ok(())
601    }
602
603    #[test]
604    fn reproducible_note() -> Result<(), CompilerError> {
605        let note = Note(3);
606        let mut compiler = Compiler::from(context_generator());
607
608        compiler = compiler.step(note)?;
609        let first = compiler.0.result.clone();
610
611        *compiler.0.get_mut('t'.to_string())? = 0.0;
612        compiler.0.result.clear();
613        compiler = compiler.step(note)?;
614        let second = compiler.0.result.clone();
615
616        assert_eq!(first, second);
617        Ok(())
618    }
619}