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]
|
||||
name = "bliplib"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2024"
|
||||
authors = ["Breval Ferrari <breval.ferrari@fish.golf>"]
|
||||
description = "The Bizarre Language for Intermodulation Programming (BLIP)"
|
||||
|
|
|
@ -15,12 +15,15 @@ use thiserror::Error;
|
|||
|
||||
cfg_if! {
|
||||
if #[cfg(test)] {
|
||||
/// Static sample rate.
|
||||
pub const SAMPLE_RATE: u16 = 10;
|
||||
} else {
|
||||
/// Static sample rate.
|
||||
pub const SAMPLE_RATE: u16 = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for a Vec of tokens.
|
||||
#[derive(Debug, From, AsRef, Default)]
|
||||
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 {
|
||||
/// Get the type ID of self without self being [`std::any::Any`].
|
||||
fn type_id(&self) -> TypeId;
|
||||
}
|
||||
|
||||
|
@ -57,7 +62,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// BLIP tokens implement this.
|
||||
pub trait Token: Debug + Type {
|
||||
/// Applies itself to a context for compilation into samples.
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Loop(pub LoopCount, pub TokenVec);
|
||||
|
||||
/// Describes the number of times a loop should repeat its captured sequence of tokens.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum LoopCount {
|
||||
/// Fixed number of times
|
||||
Litteral(usize),
|
||||
/// Variable number of times, using the current value of the underlying variable
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
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)]
|
||||
pub struct Expression {
|
||||
/// Expression origin, used for cloning.
|
||||
from: String,
|
||||
/// The wrapped fasteval struct.
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -268,39 +289,51 @@ impl FromStr for Expression {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compiler context which persists from one token to the other.
|
||||
#[derive(Debug, Clone, new)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Context {
|
||||
note_length_variable: String,
|
||||
note_index_variable: String,
|
||||
/// Vec of samples resulting from compilation.
|
||||
#[new(default)]
|
||||
pub result: Vec<f64>,
|
||||
/// Set of variables used for every [`Expression`] evaluation.
|
||||
#[new(into_iter = "(String, f64)")]
|
||||
pub variables: BTreeMap<String, f64>,
|
||||
/// Instrument Expression which will be used to directly generate samples from the tokens.
|
||||
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)")]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[error("variable not found: {0}")]
|
||||
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)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[error("expression not found: {0}")]
|
||||
pub struct SlopeNotFoundError(String);
|
||||
|
||||
/// An error occurred during the compilation of tokens into samples.
|
||||
#[derive(Debug, Error)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum CompilerError {
|
||||
/// The error comes from a `fasteval` operation.
|
||||
#[error("expression evaluation: {0:?}")]
|
||||
FastEval(#[from] fasteval::Error),
|
||||
/// See [`VariableNotFoundError`].
|
||||
#[error(transparent)]
|
||||
VariableNotFound(#[from] VariableNotFoundError),
|
||||
/// See [`SlopeNotFoundError`].
|
||||
#[error(transparent)]
|
||||
SlopeNotFound(#[from] SlopeNotFoundError),
|
||||
/// A tuplet was found which carried no underlying tokens.
|
||||
#[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."
|
||||
)]
|
||||
|
@ -308,6 +341,7 @@ pub enum CompilerError {
|
|||
}
|
||||
|
||||
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> {
|
||||
let Context {
|
||||
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> {
|
||||
self.variables
|
||||
.get(name.as_ref())
|
||||
.ok_or(VariableNotFoundError(name.into()))
|
||||
}
|
||||
|
||||
/// Get a variable from the set with a mutable reference.
|
||||
pub fn get_mut(
|
||||
&mut self,
|
||||
name: impl AsRef<str> + Into<String>,
|
||||
|
@ -361,6 +397,7 @@ impl Context {
|
|||
.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(
|
||||
&self,
|
||||
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> {
|
||||
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(
|
||||
Expression {
|
||||
from,
|
||||
|
@ -421,6 +460,7 @@ impl Context {
|
|||
.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> {
|
||||
let curr_t = *self.get("t")?;
|
||||
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> {
|
||||
self.result
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience struct for the whole compilation phase.
|
||||
#[derive(From)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct Compiler(Context);
|
||||
|
||||
impl Compiler {
|
||||
/// Applies a single token on the underlying [`Context`].
|
||||
pub fn step(self, token: impl Token) -> Result<Self, CompilerError> {
|
||||
token.apply(self.0).map(Into::into)
|
||||
}
|
||||
|
@ -472,6 +515,7 @@ impl Compiler {
|
|||
.into_iter()
|
||||
.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(
|
||||
self,
|
||||
tokens: impl IntoIterator<Item = impl Token>,
|
||||
|
|
|
@ -29,8 +29,22 @@ use crate::compiler::{
|
|||
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>;
|
||||
|
||||
/// A parser wrapper for `nom` 8 which adds a context to the error.
|
||||
#[derive(new)]
|
||||
pub struct VerboseParser<P: nom::Parser<I, Error = LocatedVerboseError<I>>, I> {
|
||||
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)]
|
||||
pub struct LocatedVerboseError<I> {
|
||||
/// Error location (I is the input type)
|
||||
pub location: I,
|
||||
/// Error description / context
|
||||
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>(
|
||||
parser: P,
|
||||
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)]
|
||||
pub struct Parser<N, NS, S, SS, SV, V>
|
||||
where
|
||||
|
@ -134,6 +153,7 @@ where
|
|||
V: AsRef<[char]>,
|
||||
'p: 'a,
|
||||
{
|
||||
/// Parse all the input into a Vec of tokens. Returns a descriptive error on failure.
|
||||
pub fn parse_all(
|
||||
&'p self,
|
||||
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>>
|
||||
where
|
||||
I: Input + Copy,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue