complete doc
This commit is contained in:
parent
4f39e98e8b
commit
b0444e13d4
3 changed files with 66 additions and 1 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bliplib"
|
name = "bliplib"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["Breval Ferrari <breval.ferrari@fish.golf>"]
|
authors = ["Breval Ferrari <breval.ferrari@fish.golf>"]
|
||||||
description = "The Bizarre Language for Intermodulation Programming (BLIP)"
|
description = "The Bizarre Language for Intermodulation Programming (BLIP)"
|
||||||
|
|
|
@ -15,12 +15,15 @@ use thiserror::Error;
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(test)] {
|
if #[cfg(test)] {
|
||||||
|
/// Static sample rate.
|
||||||
pub const SAMPLE_RATE: u16 = 10;
|
pub const SAMPLE_RATE: u16 = 10;
|
||||||
} else {
|
} else {
|
||||||
|
/// Static sample rate.
|
||||||
pub const SAMPLE_RATE: u16 = 48000;
|
pub const SAMPLE_RATE: u16 = 48000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper for a Vec of tokens.
|
||||||
#[derive(Debug, From, AsRef, Default)]
|
#[derive(Debug, From, AsRef, Default)]
|
||||||
pub struct TokenVec(pub(crate) Vec<Box<dyn Token>>);
|
pub struct TokenVec(pub(crate) Vec<Box<dyn Token>>);
|
||||||
|
|
||||||
|
@ -44,7 +47,9 @@ impl Type for Box<dyn Token> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used for getting the `type_id` of a type without it being [`std::any::Any`].
|
||||||
pub trait Type {
|
pub trait Type {
|
||||||
|
/// Get the type ID of self without self being [`std::any::Any`].
|
||||||
fn type_id(&self) -> TypeId;
|
fn type_id(&self) -> TypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +62,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// BLIP tokens implement this.
|
||||||
pub trait Token: Debug + Type {
|
pub trait Token: Debug + Type {
|
||||||
|
/// Applies itself to a context for compilation into samples.
|
||||||
fn apply(&self, context: Context) -> Result<Context, CompilerError>;
|
fn apply(&self, context: Context) -> Result<Context, CompilerError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +75,7 @@ impl PartialEq for TokenVec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Litteral silence. No sound for the duration of a note.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Silence;
|
pub struct Silence;
|
||||||
|
@ -81,6 +89,7 @@ impl Token for Silence {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to indicate where the playback or export should start from in the sheet music.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Marker;
|
pub struct Marker;
|
||||||
|
@ -93,6 +102,7 @@ impl Token for Marker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A note. The underlying `u8` is for its position in the provided list of notes to be parsed.
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Note(pub u8);
|
pub struct Note(pub u8);
|
||||||
|
@ -106,6 +116,7 @@ impl Token for Note {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes the instant mutation of a variable by the result of the provided [`Expression`] using the current values of all available variables.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct VariableChange(pub char, pub Expression);
|
pub struct VariableChange(pub char, pub Expression);
|
||||||
|
@ -124,14 +135,18 @@ impl Token for VariableChange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper for other tokens which repeat those tokens N times using the number described by [`LoopCount`].
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Loop(pub LoopCount, pub TokenVec);
|
pub struct Loop(pub LoopCount, pub TokenVec);
|
||||||
|
|
||||||
|
/// Describes the number of times a loop should repeat its captured sequence of tokens.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub enum LoopCount {
|
pub enum LoopCount {
|
||||||
|
/// Fixed number of times
|
||||||
Litteral(usize),
|
Litteral(usize),
|
||||||
|
/// Variable number of times, using the current value of the underlying variable
|
||||||
Variable(char),
|
Variable(char),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +178,7 @@ impl Token for Loop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Tuplet(pub TokenVec);
|
pub struct Tuplet(pub TokenVec);
|
||||||
|
@ -204,6 +220,7 @@ impl Token for Tuplet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A slope which describes a smooth change of a variable which will affect all captured tokens. Variable mutation occurs every frame / sample.
|
||||||
#[derive(Debug, new, Default)]
|
#[derive(Debug, new, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Slope(pub VariableChange, pub TokenVec);
|
pub struct Slope(pub VariableChange, pub TokenVec);
|
||||||
|
@ -225,10 +242,14 @@ impl Token for Slope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mathematical expression to be evaluated with a set of variables to form a single [`f64`] value.
|
||||||
#[derive(Debug, new, Default)]
|
#[derive(Debug, new, Default)]
|
||||||
pub struct Expression {
|
pub struct Expression {
|
||||||
|
/// Expression origin, used for cloning.
|
||||||
from: String,
|
from: String,
|
||||||
|
/// The wrapped fasteval struct.
|
||||||
pub(crate) instruction: Instruction,
|
pub(crate) instruction: Instruction,
|
||||||
|
/// The slab which contains all the information referenced by the [`Instruction`]. An Instruction without a Slab is usually unusable.
|
||||||
pub(crate) slab: Slab,
|
pub(crate) slab: Slab,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,39 +289,51 @@ impl FromStr for Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compiler context which persists from one token to the other.
|
||||||
#[derive(Debug, Clone, new)]
|
#[derive(Debug, Clone, new)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
note_length_variable: String,
|
note_length_variable: String,
|
||||||
note_index_variable: String,
|
note_index_variable: String,
|
||||||
|
/// Vec of samples resulting from compilation.
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
pub result: Vec<f64>,
|
pub result: Vec<f64>,
|
||||||
|
/// Set of variables used for every [`Expression`] evaluation.
|
||||||
#[new(into_iter = "(String, f64)")]
|
#[new(into_iter = "(String, f64)")]
|
||||||
pub variables: BTreeMap<String, f64>,
|
pub variables: BTreeMap<String, f64>,
|
||||||
|
/// Instrument Expression which will be used to directly generate samples from the tokens.
|
||||||
pub instrument: Expression,
|
pub instrument: Expression,
|
||||||
|
/// Set of [`Slope`]s to be applied to their corresponding variable at every frame. The string is the variable name.
|
||||||
#[new(into_iter = "(String, Expression)")]
|
#[new(into_iter = "(String, Expression)")]
|
||||||
pub slopes: Vec<(String, Expression)>,
|
pub slopes: Vec<(String, Expression)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A variable which is expected to be present in the [`Context`]'s set of variables could not be found.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[error("variable not found: {0}")]
|
#[error("variable not found: {0}")]
|
||||||
pub struct VariableNotFoundError(String);
|
pub struct VariableNotFoundError(String);
|
||||||
|
|
||||||
|
/// A slope which is expected to be present in the [`Context`]'s set of slopes could not be found.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[error("expression not found: {0}")]
|
#[error("expression not found: {0}")]
|
||||||
pub struct SlopeNotFoundError(String);
|
pub struct SlopeNotFoundError(String);
|
||||||
|
|
||||||
|
/// An error occurred during the compilation of tokens into samples.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub enum CompilerError {
|
pub enum CompilerError {
|
||||||
|
/// The error comes from a `fasteval` operation.
|
||||||
#[error("expression evaluation: {0:?}")]
|
#[error("expression evaluation: {0:?}")]
|
||||||
FastEval(#[from] fasteval::Error),
|
FastEval(#[from] fasteval::Error),
|
||||||
|
/// See [`VariableNotFoundError`].
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
VariableNotFound(#[from] VariableNotFoundError),
|
VariableNotFound(#[from] VariableNotFoundError),
|
||||||
|
/// See [`SlopeNotFoundError`].
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SlopeNotFound(#[from] SlopeNotFoundError),
|
SlopeNotFound(#[from] SlopeNotFoundError),
|
||||||
|
/// A tuplet was found which carried no underlying tokens.
|
||||||
#[error(
|
#[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."
|
"🎉 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."
|
||||||
)]
|
)]
|
||||||
|
@ -308,6 +341,7 @@ pub enum CompilerError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
/// Calculates the current note length according to the note length expression and the current values of registered variables.
|
||||||
pub fn current_length(self) -> Result<(Self, f64), CompilerError> {
|
pub fn current_length(self) -> Result<(Self, f64), CompilerError> {
|
||||||
let Context {
|
let Context {
|
||||||
note_length_variable,
|
note_length_variable,
|
||||||
|
@ -346,12 +380,14 @@ impl Context {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a variable from the set.
|
||||||
pub fn get(&self, name: impl AsRef<str> + Into<String>) -> Result<&f64, VariableNotFoundError> {
|
pub fn get(&self, name: impl AsRef<str> + Into<String>) -> Result<&f64, VariableNotFoundError> {
|
||||||
self.variables
|
self.variables
|
||||||
.get(name.as_ref())
|
.get(name.as_ref())
|
||||||
.ok_or(VariableNotFoundError(name.into()))
|
.ok_or(VariableNotFoundError(name.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a variable from the set with a mutable reference.
|
||||||
pub fn get_mut(
|
pub fn get_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl AsRef<str> + Into<String>,
|
name: impl AsRef<str> + Into<String>,
|
||||||
|
@ -361,6 +397,7 @@ impl Context {
|
||||||
.ok_or(VariableNotFoundError(name.into()))
|
.ok_or(VariableNotFoundError(name.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a slope from the set using the name of the variable it's supposed to alter the value of.
|
||||||
pub fn get_slopes_for(
|
pub fn get_slopes_for(
|
||||||
&self,
|
&self,
|
||||||
var: impl for<'a> PartialEq<&'a String> + Into<String>,
|
var: impl for<'a> PartialEq<&'a String> + Into<String>,
|
||||||
|
@ -403,10 +440,12 @@ impl Context {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate the result of an Expression using the current values of the variables registered in the set.
|
||||||
pub fn eval(&mut self, expr: &Expression) -> Result<f64, fasteval::Error> {
|
pub fn eval(&mut self, expr: &Expression) -> Result<f64, fasteval::Error> {
|
||||||
Self::eval_with(expr, &mut self.variables)
|
Self::eval_with(expr, &mut self.variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate the result of an Expression using the current values of the variables registered in the provided namespace.
|
||||||
pub fn eval_with(
|
pub fn eval_with(
|
||||||
Expression {
|
Expression {
|
||||||
from,
|
from,
|
||||||
|
@ -421,6 +460,7 @@ impl Context {
|
||||||
.inspect_err(|e| trace!("{from} = {e}"))
|
.inspect_err(|e| trace!("{from} = {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
pub fn render(mut self, n: Option<u8>) -> Result<(Self, Vec<f64>), CompilerError> {
|
pub fn render(mut self, n: Option<u8>) -> Result<(Self, Vec<f64>), CompilerError> {
|
||||||
let curr_t = *self.get("t")?;
|
let curr_t = *self.get("t")?;
|
||||||
if let Some(note) = n {
|
if let Some(note) = n {
|
||||||
|
@ -451,16 +491,19 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Explodes the context, leaving only the resulting Vec of samples.
|
||||||
pub fn finalize(self) -> Vec<f64> {
|
pub fn finalize(self) -> Vec<f64> {
|
||||||
self.result
|
self.result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience struct for the whole compilation phase.
|
||||||
#[derive(From)]
|
#[derive(From)]
|
||||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
pub struct Compiler(Context);
|
pub struct Compiler(Context);
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
|
/// Applies a single token on the underlying [`Context`].
|
||||||
pub 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)
|
||||||
}
|
}
|
||||||
|
@ -472,6 +515,7 @@ impl Compiler {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.try_fold(self, |acc, token| acc.step(token))
|
.try_fold(self, |acc, token| acc.step(token))
|
||||||
}
|
}
|
||||||
|
/// Applies every token on the underlying [`Context`] and returns the resulting samples as an iterator.
|
||||||
pub fn compile_all(
|
pub fn compile_all(
|
||||||
self,
|
self,
|
||||||
tokens: impl IntoIterator<Item = impl Token>,
|
tokens: impl IntoIterator<Item = impl Token>,
|
||||||
|
|
|
@ -29,8 +29,22 @@ use crate::compiler::{
|
||||||
VariableChange,
|
VariableChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// From `nom`'s [`IResult`](https://docs.rs/nom/8.0.0/nom/type.IResult.html) :
|
||||||
|
/// > Holds the result of parsing functions
|
||||||
|
/// >
|
||||||
|
/// > It depends on the input type `I`, the output type `O`, and the error type `E`
|
||||||
|
/// > (by default `(I, nom::ErrorKind)`)
|
||||||
|
/// >
|
||||||
|
/// > The `Ok` side is a pair containing the remainder of the input (the part of the data that
|
||||||
|
/// > was not parsed) and the produced value. The `Err` side contains an instance of `nom::Err`.
|
||||||
|
/// >
|
||||||
|
/// > Outside of the parsing code, you can use the [Finish::finish] method to convert
|
||||||
|
/// > it to a more common result type
|
||||||
|
///
|
||||||
|
/// The error type in this IResult is [`LocatedVerboseError`].
|
||||||
pub type IResult<I, O, E = LocatedVerboseError<I>> = nom::IResult<I, O, E>;
|
pub type IResult<I, O, E = LocatedVerboseError<I>> = nom::IResult<I, O, E>;
|
||||||
|
|
||||||
|
/// A parser wrapper for `nom` 8 which adds a context to the error.
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct VerboseParser<P: nom::Parser<I, Error = LocatedVerboseError<I>>, I> {
|
pub struct VerboseParser<P: nom::Parser<I, Error = LocatedVerboseError<I>>, I> {
|
||||||
parser: P,
|
parser: P,
|
||||||
|
@ -83,12 +97,16 @@ impl<I, P: nom::Parser<I, Error = LocatedVerboseError<I>>> nom::Parser<I> for Ve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error type for `nom` 8 which adds a context to parser errors using `anyhow::Error`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LocatedVerboseError<I> {
|
pub struct LocatedVerboseError<I> {
|
||||||
|
/// Error location (I is the input type)
|
||||||
pub location: I,
|
pub location: I,
|
||||||
|
/// Error description / context
|
||||||
pub error: Option<anyhow::Error>,
|
pub error: Option<anyhow::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expect the parser to succeed and if it doesn't, add a context to the error.
|
||||||
pub fn expect<P, I>(
|
pub fn expect<P, I>(
|
||||||
parser: P,
|
parser: P,
|
||||||
error_message: impl Into<Cow<'static, str>>,
|
error_message: impl Into<Cow<'static, str>>,
|
||||||
|
@ -108,6 +126,7 @@ impl<I> FromExternalError<I, anyhow::Error> for LocatedVerboseError<I> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience struct for the whole parsing phase. Very generic.
|
||||||
#[derive(new)]
|
#[derive(new)]
|
||||||
pub struct Parser<N, NS, S, SS, SV, V>
|
pub struct Parser<N, NS, S, SS, SV, V>
|
||||||
where
|
where
|
||||||
|
@ -134,6 +153,7 @@ where
|
||||||
V: AsRef<[char]>,
|
V: AsRef<[char]>,
|
||||||
'p: 'a,
|
'p: 'a,
|
||||||
{
|
{
|
||||||
|
/// Parse all the input into a Vec of tokens. Returns a descriptive error on failure.
|
||||||
pub fn parse_all(
|
pub fn parse_all(
|
||||||
&'p self,
|
&'p self,
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
|
@ -492,6 +512,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the maximum amount of input before `cond` returns [`None`], using the result of the last successful evaluation of `cond` for the output.
|
||||||
pub fn take_while_map<F, I, O>(cond: F) -> impl FnMut(I) -> IResult<I, O, LocatedVerboseError<I>>
|
pub fn take_while_map<F, I, O>(cond: F) -> impl FnMut(I) -> IResult<I, O, LocatedVerboseError<I>>
|
||||||
where
|
where
|
||||||
I: Input + Copy,
|
I: Input + Copy,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue