replace total note length variable from l to L, swap fasteval log_2 with log(2,...), fix silence rendering, first compiler test
This commit is contained in:
parent
ce5a8760c1
commit
478ce49d8d
3 changed files with 71 additions and 52 deletions
|
@ -16,7 +16,7 @@ modes:
|
||||||
-i EXPR, --instrument EXPR
|
-i EXPR, --instrument EXPR
|
||||||
signal expression (instrument) to generate music samples (you can use the floating-point number t for the current time in seconds, n for the indice of the currently playing note in the list of available notes starting with 0 and N for the number of available notes) [default: "sin(2*PI*(442+442*((n+1)/N))*t)"]
|
signal expression (instrument) to generate music samples (you can use the floating-point number t for the current time in seconds, n for the indice of the currently playing note in the list of available notes starting with 0 and N for the number of available notes) [default: "sin(2*PI*(442+442*((n+1)/N))*t)"]
|
||||||
-l EXPR, --length EXPR
|
-l EXPR, --length EXPR
|
||||||
expression to generate note length in seconds [default: "2^(2-log_2(l))*(60/T)"]
|
expression to generate note length in seconds [default: "2^(2-log(2, l))*(60/T)"]
|
||||||
-v VARIABLE=VALUE, --variable VARIABLE=VALUE
|
-v VARIABLE=VALUE, --variable VARIABLE=VALUE
|
||||||
add a variable named VARIABLE (an single letter) for the sheet music and all expressions and set its initial value to VALUE (you can override n, t, l and T which is the only way to set the initial tempo)
|
add a variable named VARIABLE (an single letter) for the sheet music and all expressions and set its initial value to VALUE (you can override n, t, l and T which is the only way to set the initial tempo)
|
||||||
-m NAME:EXPANSION, --macro NAME:EXPANSION
|
-m NAME:EXPANSION, --macro NAME:EXPANSION
|
||||||
|
@ -34,7 +34,7 @@ modes:
|
||||||
-i EXPR, --instrument EXPR
|
-i EXPR, --instrument EXPR
|
||||||
signal expression (instrument) to generate music samples (you can use the floating-point number t for the current time in seconds, n for the indice of the currently playing note in the list of available notes starting with 0 and N for the number of available notes) [default: "sin(2*PI*(442+442*((n+1)/N))*t)"]
|
signal expression (instrument) to generate music samples (you can use the floating-point number t for the current time in seconds, n for the indice of the currently playing note in the list of available notes starting with 0 and N for the number of available notes) [default: "sin(2*PI*(442+442*((n+1)/N))*t)"]
|
||||||
-l EXPR, --length EXPR
|
-l EXPR, --length EXPR
|
||||||
expression to generate note length in seconds [default: "2^(2-log_2(l))*(60/T)"]
|
expression to generate note length in seconds [default: "2^(2-log(2, l))*(60/T)"]
|
||||||
-v VARIABLE=VALUE, --variable VARIABLE=VALUE
|
-v VARIABLE=VALUE, --variable VARIABLE=VALUE
|
||||||
add a variable named VARIABLE (an single letter) for the sheet music and all expressions and set its initial value to VALUE (you can override n, t, l and T which is the only way to set the initial tempo)
|
add a variable named VARIABLE (an single letter) for the sheet music and all expressions and set its initial value to VALUE (you can override n, t, l and T which is the only way to set the initial tempo)
|
||||||
-m NAME:EXPANSION, --macro NAME:EXPANSION
|
-m NAME:EXPANSION, --macro NAME:EXPANSION
|
||||||
|
|
|
@ -29,7 +29,7 @@ use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
const DEFAULT_INSTRUMENT: &str = "sin(2*PI*(442+442*((n+1)/N))*t)";
|
const DEFAULT_INSTRUMENT: &str = "sin(2*PI*(442+442*((n+1)/N))*t)";
|
||||||
const DEFAULT_LENGTH: &str = "2^(2-log_2(l))*(60/T)";
|
const DEFAULT_LENGTH: &str = "2^(2-log(2, l))*(60/T)";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
117
src/compiler.rs
117
src/compiler.rs
|
@ -66,7 +66,8 @@ impl_type!(Silence);
|
||||||
|
|
||||||
impl Token for Silence {
|
impl Token for Silence {
|
||||||
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
|
fn apply(&self, mut context: Context) -> Result<Context, CompilerError> {
|
||||||
context.render(None)?;
|
let mut next = context.render(None)?;
|
||||||
|
context.result.append(&mut next);
|
||||||
Ok(context)
|
Ok(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,14 +267,17 @@ pub struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[error("variable not found: {0}")]
|
#[error("variable not found: {0}")]
|
||||||
pub struct VariableNotFoundError(char);
|
pub struct VariableNotFoundError(char);
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[error("expression not found: {0}")]
|
#[error("expression not found: {0}")]
|
||||||
pub struct SlopeNotFoundError(char);
|
pub struct SlopeNotFoundError(char);
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub enum CompilerError {
|
pub enum CompilerError {
|
||||||
#[error("expression evaluation: {0:?}")]
|
#[error("expression evaluation: {0:?}")]
|
||||||
FastEval(#[from] fasteval::Error),
|
FastEval(#[from] fasteval::Error),
|
||||||
|
@ -284,19 +288,21 @@ pub enum CompilerError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn current_length(&mut self) -> Result<f64, CompilerError> {
|
pub fn current_length(&self) -> Result<f64, CompilerError> {
|
||||||
*self = self
|
self.get_slopes_for('L')
|
||||||
.get_slopes_for('L')
|
|
||||||
.map_err(CompilerError::from)?
|
.map_err(CompilerError::from)?
|
||||||
.iter()
|
.iter()
|
||||||
.try_fold(
|
.try_fold(
|
||||||
self.clone(),
|
self.clone(),
|
||||||
|mut acc, slope| -> Result<Context, CompilerError> {
|
|mut acc, slope| -> Result<Context, CompilerError> {
|
||||||
|
// just in case next slopes use L too
|
||||||
*acc.get_mut('L')? = self.eval(slope).map_err(Into::<CompilerError>::into)?;
|
*acc.get_mut('L')? = self.eval(slope).map_err(Into::<CompilerError>::into)?;
|
||||||
Ok(acc)
|
Ok(acc)
|
||||||
},
|
},
|
||||||
)?;
|
)?
|
||||||
Ok(*self.get('L')?)
|
.get('L')
|
||||||
|
.map_err(Into::into)
|
||||||
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, name: char) -> Result<&f64, VariableNotFoundError> {
|
pub fn get(&self, name: char) -> Result<&f64, VariableNotFoundError> {
|
||||||
|
@ -323,39 +329,30 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) -> Result<(), CompilerError> {
|
fn tick(&mut self) -> Result<(), CompilerError> {
|
||||||
*self.get_mut('t')? += 1f64 / SAMPLE_RATE as f64;
|
*self.get_mut('t')? += 1f64 / (SAMPLE_RATE as f64);
|
||||||
{
|
*self = self.slopes.iter().try_fold(
|
||||||
let changes = self
|
self.clone(),
|
||||||
.slopes
|
|mut acc,
|
||||||
.iter()
|
(
|
||||||
.map(
|
v,
|
||||||
|(
|
Expression {
|
||||||
v,
|
from: _,
|
||||||
Expression {
|
instruction,
|
||||||
from: _,
|
slab,
|
||||||
instruction,
|
},
|
||||||
slab,
|
)|
|
||||||
},
|
-> Result<Self, CompilerError> {
|
||||||
)|
|
*acc.get_mut(*v)? = instruction.eval(
|
||||||
-> Result<(char, f64), fasteval::Error> {
|
slab,
|
||||||
Ok((
|
&mut acc
|
||||||
*v,
|
.variables
|
||||||
instruction.eval(
|
.iter()
|
||||||
slab,
|
.map(|(c, f)| (c.to_string(), *f))
|
||||||
&mut self
|
.collect::<BTreeMap<String, f64>>(),
|
||||||
.variables
|
)?;
|
||||||
.iter()
|
Ok(acc)
|
||||||
.map(|(c, f)| (c.to_string(), *f))
|
},
|
||||||
.collect::<BTreeMap<String, f64>>(),
|
)?;
|
||||||
)?,
|
|
||||||
))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.collect::<Result<Vec<(char, f64)>, fasteval::Error>>()?;
|
|
||||||
for (variable, new_value) in changes {
|
|
||||||
*self.get_mut(variable)? = new_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,10 +395,14 @@ impl Context {
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
} else {
|
} else {
|
||||||
while (self.current_length()? * SAMPLE_RATE as f64) > *self.get('t')? - curr_t {
|
while (self.current_length()?) > *self.get('t')? - curr_t {
|
||||||
self.tick()?;
|
self.tick()?;
|
||||||
}
|
}
|
||||||
Ok(vec![0.0; (*self.get('t')? - curr_t) as usize])
|
Ok(vec![
|
||||||
|
0.0;
|
||||||
|
((*self.get('t')? - curr_t) * SAMPLE_RATE as f64)
|
||||||
|
as usize
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,29 +412,34 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(From)]
|
#[derive(From)]
|
||||||
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
struct Compiler(Context);
|
struct Compiler(Context);
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
fn step(self, token: impl Token) -> Result<Self, CompilerError> {
|
pub fn step(self, token: impl Token) -> Result<Self, CompilerError> {
|
||||||
token.apply(self.0).map(Into::into)
|
token.apply(self.0).map(Into::into)
|
||||||
}
|
}
|
||||||
fn compile_all(
|
fn apply_all(
|
||||||
self,
|
self,
|
||||||
tokens: impl IntoIterator<Item = impl Token>,
|
tokens: impl IntoIterator<Item = impl Token>,
|
||||||
) -> Result<Vec<f64>, CompilerError> {
|
) -> Result<Self, CompilerError> {
|
||||||
tokens
|
tokens
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.try_fold(self, |acc, token| acc.step(token))
|
.try_fold(self, |acc, token| acc.step(token))
|
||||||
.map(|c| c.0)
|
}
|
||||||
.map(Context::finalize)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::compiler::Expression;
|
use crate::compiler::{Compiler, Expression, SAMPLE_RATE, Silence};
|
||||||
|
|
||||||
use super::Context;
|
use super::{CompilerError, Context};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expression_is_clone() {
|
fn expression_is_clone() {
|
||||||
|
@ -453,7 +459,20 @@ mod tests {
|
||||||
('T', 60.0),
|
('T', 60.0),
|
||||||
],
|
],
|
||||||
"sin(2*PI*(442+442*((n+1)/N))*t)".parse().unwrap(),
|
"sin(2*PI*(442+442*((n+1)/N))*t)".parse().unwrap(),
|
||||||
[('L', "2^(2-log_2(l))*(60/T)")].map(|(c, e)| (c, e.parse().unwrap())),
|
[('L', "2^(2-log(2, l))*(60/T)")].map(|(c, e)| (c, e.parse().unwrap())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn silence() -> Result<(), CompilerError> {
|
||||||
|
assert_eq!(
|
||||||
|
SAMPLE_RATE as usize,
|
||||||
|
Compiler::from(context_generator())
|
||||||
|
.apply_all(vec![Silence])?
|
||||||
|
.0
|
||||||
|
.result
|
||||||
|
.len()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue