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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@ -34,7 +34,7 @@ modes:
|
|||
-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)"]
|
||||
-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
|
||||
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
|
||||
|
|
|
@ -29,7 +29,7 @@ use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr};
|
|||
use thiserror::Error;
|
||||
|
||||
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() {
|
||||
let cli = Cli::parse();
|
||||
|
|
117
src/compiler.rs
117
src/compiler.rs
|
@ -66,7 +66,8 @@ impl_type!(Silence);
|
|||
|
||||
impl Token for Silence {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -266,14 +267,17 @@ pub struct Context {
|
|||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[error("variable not found: {0}")]
|
||||
pub struct VariableNotFoundError(char);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[error("expression not found: {0}")]
|
||||
pub struct SlopeNotFoundError(char);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum CompilerError {
|
||||
#[error("expression evaluation: {0:?}")]
|
||||
FastEval(#[from] fasteval::Error),
|
||||
|
@ -284,19 +288,21 @@ pub enum CompilerError {
|
|||
}
|
||||
|
||||
impl Context {
|
||||
pub fn current_length(&mut self) -> Result<f64, CompilerError> {
|
||||
*self = self
|
||||
.get_slopes_for('L')
|
||||
pub fn current_length(&self) -> Result<f64, CompilerError> {
|
||||
self.get_slopes_for('L')
|
||||
.map_err(CompilerError::from)?
|
||||
.iter()
|
||||
.try_fold(
|
||||
self.clone(),
|
||||
|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)?;
|
||||
Ok(acc)
|
||||
},
|
||||
)?;
|
||||
Ok(*self.get('L')?)
|
||||
)?
|
||||
.get('L')
|
||||
.map_err(Into::into)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn get(&self, name: char) -> Result<&f64, VariableNotFoundError> {
|
||||
|
@ -323,39 +329,30 @@ impl Context {
|
|||
}
|
||||
|
||||
fn tick(&mut self) -> Result<(), CompilerError> {
|
||||
*self.get_mut('t')? += 1f64 / SAMPLE_RATE as f64;
|
||||
{
|
||||
let changes = self
|
||||
.slopes
|
||||
.iter()
|
||||
.map(
|
||||
|(
|
||||
v,
|
||||
Expression {
|
||||
from: _,
|
||||
instruction,
|
||||
slab,
|
||||
},
|
||||
)|
|
||||
-> Result<(char, f64), fasteval::Error> {
|
||||
Ok((
|
||||
*v,
|
||||
instruction.eval(
|
||||
slab,
|
||||
&mut self
|
||||
.variables
|
||||
.iter()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
*self.get_mut('t')? += 1f64 / (SAMPLE_RATE as f64);
|
||||
*self = self.slopes.iter().try_fold(
|
||||
self.clone(),
|
||||
|mut acc,
|
||||
(
|
||||
v,
|
||||
Expression {
|
||||
from: _,
|
||||
instruction,
|
||||
slab,
|
||||
},
|
||||
)|
|
||||
-> Result<Self, CompilerError> {
|
||||
*acc.get_mut(*v)? = instruction.eval(
|
||||
slab,
|
||||
&mut acc
|
||||
.variables
|
||||
.iter()
|
||||
.map(|(c, f)| (c.to_string(), *f))
|
||||
.collect::<BTreeMap<String, f64>>(),
|
||||
)?;
|
||||
Ok(acc)
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -398,10 +395,14 @@ impl Context {
|
|||
}
|
||||
Ok(result)
|
||||
} 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()?;
|
||||
}
|
||||
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)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
struct Compiler(Context);
|
||||
|
||||
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)
|
||||
}
|
||||
fn compile_all(
|
||||
fn apply_all(
|
||||
self,
|
||||
tokens: impl IntoIterator<Item = impl Token>,
|
||||
) -> Result<Vec<f64>, CompilerError> {
|
||||
) -> Result<Self, CompilerError> {
|
||||
tokens
|
||||
.into_iter()
|
||||
.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)]
|
||||
mod tests {
|
||||
use crate::compiler::Expression;
|
||||
use crate::compiler::{Compiler, Expression, SAMPLE_RATE, Silence};
|
||||
|
||||
use super::Context;
|
||||
use super::{CompilerError, Context};
|
||||
|
||||
#[test]
|
||||
fn expression_is_clone() {
|
||||
|
@ -453,7 +459,20 @@ mod tests {
|
|||
('T', 60.0),
|
||||
],
|
||||
"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