use std::{
any::{Any, TypeId, type_name},
collections::BTreeMap,
f64,
fmt::{Debug, Display},
str::FromStr,
};
use cfg_if::cfg_if;
use derive_new::new;
use derive_wrapper::{AsRef, From};
use fasteval::{Compiler as _, EvalNamespace, Evaler, Instruction, Slab};
use log::{debug, trace};
use thiserror::Error;
cfg_if! {
if #[cfg(test)] {
pub const SAMPLE_RATE: u16 = 10;
} else {
pub const SAMPLE_RATE: u16 = 48000;
}
}
#[derive(Debug, From, AsRef, Default)]
pub struct TokenVec(pub(crate) Vec<Box<dyn Token>>);
impl IntoIterator for TokenVec {
type Item = Box<dyn Token>;
type IntoIter = <Vec<Box<(dyn Token + 'static)>> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Token for Box<dyn Token> {
fn apply(&self, context: Context) -> Result<Context, CompilerError> {
self.as_ref().apply(context)
}
}
impl Type for Box<dyn Token> {
fn type_id(&self) -> TypeId {
self.as_ref().type_id()
}
}
pub trait Type {
fn type_id(&self) -> TypeId;
}
impl<T> Type for T
where
T: Default + 'static,
{
fn type_id(&self) -> TypeId {
<Self as Any>::type_id(&Default::default())
}
}
pub trait Token: Debug + Type {
fn apply(&self, context: Context) -> Result<Context, CompilerError>;
}
#[cfg(test)]
impl PartialEq for TokenVec {
fn eq(&self, other: &Self) -> bool {
format!("{self:?}") == format!("{other:?}")
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Silence;
impl Token for Silence {
fn apply(&self, context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
let (mut context, mut next) = context.render(None)?;
context.result.append(&mut next);
Ok(context)
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Marker;
impl Token for Marker {
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
context.result.clear();
Ok(context)
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Note(pub u8);
impl Token for Note {
fn apply(&self, context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
let (mut context, mut next) = context.render(Some(self.0))?;
context.result.append(&mut next);
Ok(context)
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct VariableChange(pub char, pub Expression);
impl AsRef<VariableChange> for VariableChange {
fn as_ref(&self) -> &VariableChange {
self
}
}
impl Token for VariableChange {
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
*context.get_mut(self.0.to_string())? = context.eval(self.1.as_ref())?;
Ok(context)
}
}
#[derive(Debug, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Loop(pub LoopCount, pub TokenVec);
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub enum LoopCount {
Litteral(usize),
Variable(char),
}
impl Default for LoopCount {
fn default() -> Self {
LoopCount::Litteral(2)
}
}
impl Token for Loop {
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
let mut old_result = context.result.clone();
let count = match self.0 {
LoopCount::Litteral(n) => n,
LoopCount::Variable(v) => *context.get(v.to_string())? as usize,
};
context.result.clear();
let new_context = self
.1
.0
.iter()
.try_fold(context, |context, t| t.apply(context))?;
old_result.append(&mut new_context.result.repeat(count));
Ok(Context {
result: old_result,
..new_context
})
}
}
#[derive(Debug, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Tuplet(pub TokenVec);
impl Token for Tuplet {
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
if self.0.as_ref().is_empty() {
return Err(CompilerError::Nilplet);
}
let mut old_result = context.result.clone();
context.result.clear();
let mut new_context = self
.0
.0
.iter()
.try_fold(context, |context, t| t.apply(context))?;
let len = new_context.result.len();
new_context.result = new_context
.result
.into_iter()
.step_by(
len / self
.0
.0
.iter()
.filter(|t| {
t.as_ref().type_id() == Type::type_id(&Note(0u8))
|| t.as_ref().type_id() == Type::type_id(&Silence)
})
.count(),
)
.collect();
old_result.append(&mut new_context.result);
Ok(Context {
result: old_result,
..new_context
})
}
}
#[derive(Debug, new, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Slope(pub VariableChange, pub TokenVec);
impl Token for Slope {
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
debug!("âš¡ {}", type_name::<Self>());
context.slopes.push((
self.0.as_ref().0.to_string(),
self.0.as_ref().1.as_ref().clone(),
));
context = self
.1
.0
.iter()
.try_fold(context, |context, t| t.apply(context))?;
context.slopes.pop();
Ok(context)
}
}
#[derive(Debug, new, Default)]
pub struct Expression {
from: String,
pub(crate) instruction: Instruction,
pub(crate) slab: Slab,
}
impl AsRef<Expression> for Expression {
fn as_ref(&self) -> &Expression {
self
}
}
impl Clone for Expression {
fn clone(&self) -> Self {
self.from
.parse()
.unwrap_or_else(|_| panic!("expression {self:?} have an invalid from"))
}
}
impl PartialEq for Expression {
fn eq(&self, other: &Self) -> bool {
format!("{self:?}") == format!("{other:?}")
}
}
impl FromStr for Expression {
type Err = fasteval::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut slab = Slab::new();
let instruction = fasteval::Parser::new()
.parse(s, &mut slab.ps)?
.from(&slab.ps)
.compile(&slab.ps, &mut slab.cs);
Ok(Expression::new(
s.trim_start().trim_end().to_string(),
instruction,
slab,
))
}
}
impl Display for Expression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.from, f)
}
}
#[derive(Debug, Clone, new)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Context {
note_length_variable: String,
note_index_variable: String,
#[new(default)]
pub result: Vec<f64>,
#[new(into_iter = "(String, f64)")]
pub variables: BTreeMap<String, f64>,
pub instrument: Expression,
#[new(into_iter = "(String, Expression)")]
pub slopes: Vec<(String, Expression)>,
}
#[derive(Debug, Error)]
#[cfg_attr(test, derive(PartialEq))]
#[error("variable not found: {0}")]
pub struct VariableNotFoundError(String);
#[derive(Debug, Error)]
#[cfg_attr(test, derive(PartialEq))]
#[error("expression not found: {0}")]
pub struct SlopeNotFoundError(String);
#[derive(Debug, Error)]
#[cfg_attr(test, derive(PartialEq))]
pub enum CompilerError {
#[error("expression evaluation: {0:?}")]
FastEval(#[from] fasteval::Error),
#[error(transparent)]
VariableNotFound(#[from] VariableNotFoundError),
#[error(transparent)]
SlopeNotFound(#[from] SlopeNotFoundError),
#[error(
"🎉 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."
)]
Nilplet,
}
impl Context {
pub fn current_length(self) -> Result<(Self, f64), CompilerError> {
let Context {
note_length_variable,
note_index_variable,
result,
mut variables,
instrument,
slopes,
} = self;
let mut slopes_iter = slopes
.iter()
.filter_map(|(c, e)| (c == ¬e_length_variable).then_some(e))
.peekable();
if slopes_iter.peek().is_none() {
return Err(SlopeNotFoundError(note_length_variable.clone()).into());
}
for slope in slopes_iter {
*variables
.get_mut(¬e_length_variable)
.ok_or(VariableNotFoundError(note_length_variable.clone()))? =
slope.instruction.eval(&slope.slab, &mut variables)?;
}
let note_length = *variables
.get(¬e_length_variable)
.ok_or(VariableNotFoundError(note_length_variable.clone()))?;
Ok((
Context {
note_length_variable,
note_index_variable,
result,
variables,
instrument,
slopes,
},
note_length,
))
}
pub fn get(&self, name: impl AsRef<str> + Into<String>) -> Result<&f64, VariableNotFoundError> {
self.variables
.get(name.as_ref())
.ok_or(VariableNotFoundError(name.into()))
}
pub fn get_mut(
&mut self,
name: impl AsRef<str> + Into<String>,
) -> Result<&mut f64, VariableNotFoundError> {
self.variables
.get_mut(name.as_ref())
.ok_or(VariableNotFoundError(name.into()))
}
pub fn get_slopes_for(
&self,
var: impl for<'a> PartialEq<&'a String> + Into<String>,
) -> Result<Vec<&Expression>, SlopeNotFoundError> {
let result: Vec<&Expression> = self
.slopes
.iter()
.filter_map(|(c, e)| (var == c).then_some(e))
.collect();
if result.is_empty() {
Err(SlopeNotFoundError(var.into()))
} else {
Ok(result)
}
}
fn tick(mut self) -> Result<Self, CompilerError> {
*self.get_mut("t")? += 1f64 / (SAMPLE_RATE as f64);
let Context {
note_length_variable,
note_index_variable,
result,
mut variables,
instrument,
slopes,
} = self;
for (var, expr) in slopes.iter() {
*variables
.get_mut(var)
.ok_or(VariableNotFoundError(var.clone()))? =
expr.instruction.eval(&expr.slab, &mut variables)?;
}
Ok(Context {
note_length_variable,
note_index_variable,
result,
variables,
instrument,
slopes,
})
}
pub fn eval(&mut self, expr: &Expression) -> Result<f64, fasteval::Error> {
Self::eval_with(expr, &mut self.variables)
}
pub fn eval_with(
Expression {
from,
instruction,
slab,
}: &Expression,
ns: &mut impl EvalNamespace,
) -> Result<f64, fasteval::Error> {
instruction
.eval(slab, ns)
.inspect(|ok| trace!("{from} = {ok}"))
.inspect_err(|e| trace!("{from} = {e}"))
}
pub fn render(mut self, n: Option<u8>) -> Result<(Self, Vec<f64>), CompilerError> {
let curr_t = *self.get("t")?;
if let Some(note) = n {
let mut result = Vec::new();
self.variables
.insert(self.note_index_variable.clone(), note as f64);
while {
let (new_self, length) = self.current_length()?;
self = new_self;
length
} > *self.get("t")? - curr_t + (1f64 / SAMPLE_RATE as f64)
{
result.push(Self::eval_with(&self.instrument, &mut self.variables)?);
self = self.tick()?;
}
Ok((self, result))
} else {
while {
let (new_self, length) = self.current_length()?;
self = new_self;
length
} > *self.get("t")? - curr_t
{
self = self.tick()?;
}
let len = (*self.get("t")? - curr_t) * SAMPLE_RATE as f64;
Ok((self, vec![0.0; len as usize]))
}
}
pub fn finalize(self) -> Vec<f64> {
self.result
}
}
#[derive(From)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct Compiler(Context);
impl Compiler {
pub fn step(self, token: impl Token) -> Result<Self, CompilerError> {
token.apply(self.0).map(Into::into)
}
fn apply_all(
self,
tokens: impl IntoIterator<Item = impl Token>,
) -> Result<Self, CompilerError> {
tokens
.into_iter()
.try_fold(self, |acc, token| acc.step(token))
}
pub fn compile_all(
self,
tokens: impl IntoIterator<Item = impl Token>,
) -> Result<Vec<f64>, CompilerError> {
self.apply_all(tokens).map(|c| c.0).map(Context::finalize)
}
}
#[cfg(test)]
mod tests {
use crate::compiler::{Compiler, Expression, Note, SAMPLE_RATE, Silence};
use super::{CompilerError, Context};
#[test]
fn expression_is_clone() {
let expr: Expression = "1 + 5 / x".parse().unwrap();
assert_eq!(expr, expr.clone());
}
fn context_generator() -> Context {
Context::new(
'L'.to_string(),
'n'.to_string(),
[
('a', 5.0),
('t', 0.0),
('n', 0.0),
('N', 12.0),
('L', 0.0),
('l', 4.0),
('T', 60.0),
]
.map(|(c, f)| (c.to_string(), f)),
"sin(2*pi()*(442+442*((n+1)/N))*t)".parse().unwrap(),
[('L', "2^(2-log(2, l))*(60/T)")].map(|(c, e)| (c.to_string(), e.parse().unwrap())),
)
}
#[test]
fn silence_renders_correct_amount_of_samples() -> Result<(), CompilerError> {
assert_eq!(
SAMPLE_RATE as usize,
Compiler::from(context_generator())
.apply_all(vec![Silence])?
.0
.result
.len()
);
Ok(())
}
#[test]
fn silence_renders_zeros() -> Result<(), CompilerError> {
assert!(
Compiler::from(context_generator())
.apply_all(vec![Silence])?
.0
.result
.into_iter()
.all(|s| s == 0f64)
);
Ok(())
}
#[test]
fn note_renders_correct_amount_of_samples() -> Result<(), CompilerError> {
assert_eq!(
SAMPLE_RATE as usize,
Compiler::from(context_generator())
.apply_all(vec![Note(2)])?
.0
.result
.len()
);
Ok(())
}
#[test]
fn reproducible_note() -> Result<(), CompilerError> {
let note = Note(3);
let mut compiler = Compiler::from(context_generator());
compiler = compiler.step(note)?;
let first = compiler.0.result.clone();
*compiler.0.get_mut('t'.to_string())? = 0.0;
compiler.0.result.clear();
compiler = compiler.step(note)?;
let second = compiler.0.result.clone();
assert_eq!(first, second);
Ok(())
}
}