Compare commits
No commits in common. "main" and "standardised-nom-parsing" have entirely different histories.
main
...
standardis
26 changed files with 871 additions and 1212 deletions
15
Cargo.toml
15
Cargo.toml
|
@ -4,26 +4,23 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
amplify = "4"
|
||||
amplify = "4.7.0"
|
||||
anyhow = "1.0"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
derived-deref = "2.1.0"
|
||||
fasteval = "0.2.4"
|
||||
nom = "7.1.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_yml = "0.0.12"
|
||||
splines = "4"
|
||||
splines = "4.3.1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
strum_macros = "0.26"
|
||||
tinyaudio = { version = "1", optional = true }
|
||||
tinyaudio = { version = "0.1", optional = true }
|
||||
bng_macros = { path = "bng_macros" }
|
||||
const_format = "0.2.33"
|
||||
thiserror = "2"
|
||||
derive-new = "0.7"
|
||||
naan = "0.1"
|
||||
lazy_static = "1.5"
|
||||
tune = "0.35"
|
||||
thiserror = "1.0.64"
|
||||
derive-new = "0.7.0"
|
||||
|
||||
[features]
|
||||
default = ["play", "save"]
|
||||
|
|
|
@ -7,7 +7,6 @@ edition = "2021"
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
Inflector = "0.11.4"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
extern crate proc_macro;
|
||||
use inflector::Inflector;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, FieldsUnnamed, Ident};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Ident};
|
||||
|
||||
#[proc_macro_derive(SlopeModifierParser)]
|
||||
pub fn slope_modifier_parser(input: TokenStream) -> TokenStream {
|
||||
|
@ -17,49 +16,24 @@ pub fn quick_modifier_parser(input: TokenStream) -> TokenStream {
|
|||
impl_quick_modifier_parser(ast)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ModifierParser)]
|
||||
pub fn modifier_parser(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
impl_modifier_parser(ast)
|
||||
}
|
||||
|
||||
trait ToIdent: ToString {
|
||||
fn to_ident(&self) -> Ident {
|
||||
Ident::new(&self.to_string(), Span::call_site())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ToString> ToIdent for S {}
|
||||
|
||||
fn impl_slope_modifier_parser(ast: DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
let fn_name = name.to_string().to_snake_case().to_ident();
|
||||
if let Data::Enum(DataEnum { variants, .. }) = ast.data {
|
||||
let match_arms = variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let variant_name_lower = variant_name.to_string().to_lowercase().to_ident();
|
||||
let const_name = &variant.ident.to_string().to_uppercase().to_ident();
|
||||
let const_name = Ident::new(&variant.ident.to_string().to_uppercase(),Span::call_site());
|
||||
quote! {
|
||||
nom::error::context(
|
||||
stringify!(#variant_name_lower),
|
||||
nom::combinator::value(
|
||||
#name::#variant_name,
|
||||
nom::character::complete::char(SlopeModifier::#const_name)
|
||||
)
|
||||
)
|
||||
nom::combinator::value(#name::#variant_name, nom::character::complete::char(SlopeModifier::#const_name))
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn #fn_name<'a, E: nom::error::ParseError<&'a str> + nom::error::ContextError<&'a str>>(
|
||||
i: &'a str,
|
||||
) -> nom::IResult<&'a str, #name, E> {
|
||||
nom::error::context(
|
||||
"slope modifier",
|
||||
impl lex::lexer::Parse for #name {
|
||||
fn parse(input: &str) -> nom::IResult<&str, #name> {
|
||||
nom::branch::alt((
|
||||
#(#match_arms),*
|
||||
))
|
||||
)(i)
|
||||
))(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
|
@ -70,110 +44,37 @@ fn impl_slope_modifier_parser(ast: DeriveInput) -> TokenStream {
|
|||
|
||||
fn impl_quick_modifier_parser(ast: DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
let fn_name = name.to_string().to_snake_case().to_ident();
|
||||
if let Data::Enum(DataEnum { variants, .. }) = ast.data {
|
||||
let match_arms = variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let variant_name_lower = variant_name.to_string().to_lowercase().to_ident();
|
||||
let const_name = &variant.ident.to_string().to_uppercase().to_ident();
|
||||
let const_name =
|
||||
Ident::new(&variant.ident.to_string().to_uppercase(), Span::call_site());
|
||||
quote! {
|
||||
nom::error::context(
|
||||
stringify!(#variant_name_lower),
|
||||
nom::combinator::map(
|
||||
nom::branch::alt(
|
||||
(
|
||||
nom::combinator::value(
|
||||
lex::UP,
|
||||
nom::character::complete::char(#name::#const_name.0)
|
||||
),
|
||||
nom::combinator::value(
|
||||
lex::DOWN,
|
||||
nom::character::complete::char(#name::#const_name.1)
|
||||
)
|
||||
nom::combinator::map(
|
||||
nom::branch::alt(
|
||||
(
|
||||
nom::combinator::value(
|
||||
lex::UP,
|
||||
nom::character::complete::char(#name::#const_name.0)
|
||||
),
|
||||
nom::combinator::value(
|
||||
lex::DOWN,
|
||||
nom::character::complete::char(#name::#const_name.1)
|
||||
)
|
||||
),
|
||||
#name::#variant_name
|
||||
)
|
||||
)
|
||||
),
|
||||
#name::#variant_name
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn #fn_name<'a, E: nom::error::ParseError<&'a str> + nom::error::ContextError<&'a str>>(
|
||||
i: &'a str,
|
||||
) -> nom::IResult<&'a str, #name, E> {
|
||||
nom::error::context(
|
||||
"quick modifier",
|
||||
impl lex::lexer::Parse for #name {
|
||||
fn parse(input: &str) -> nom::IResult<&str, #name> {
|
||||
nom::branch::alt((
|
||||
#(#match_arms),*
|
||||
))
|
||||
)(i)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
panic!("this macro only works on enums")
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_modifier_parser(ast: DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
let fn_name = name.to_string().to_snake_case().to_ident();
|
||||
if let Data::Enum(DataEnum { variants, .. }) = ast.data {
|
||||
let match_arms = variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let variant_name_lower = variant_name.to_string().to_lowercase().to_ident();
|
||||
let const_name = &variant.ident.to_string().to_uppercase().to_ident();
|
||||
if let Fields::Unnamed(FieldsUnnamed {
|
||||
paren_token: _,
|
||||
unnamed,
|
||||
}) = &variant.fields
|
||||
{
|
||||
let type_name = {
|
||||
let type_name = &unnamed[0].ty.to_token_stream().to_string();
|
||||
match type_name.strip_prefix("NonZero") {
|
||||
Some(s) => {
|
||||
let lower = s.to_lowercase().to_ident();
|
||||
let type_name = &unnamed[0].ty;
|
||||
quote! {
|
||||
nom::combinator::map_opt(
|
||||
nom::character::complete::#lower,
|
||||
std::num::#type_name::new
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let type_name = &unnamed[0].ty;
|
||||
quote! {
|
||||
nom::character::complete::#type_name
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
nom::error::context(
|
||||
stringify!(#variant_name_lower),
|
||||
nom::combinator::map(
|
||||
nom::sequence::preceded(nom::character::complete::char(#name::#const_name), #type_name),
|
||||
#name::#variant_name
|
||||
)
|
||||
)
|
||||
))(input)
|
||||
}
|
||||
} else {
|
||||
panic!("this macro only works on unnamed fields")
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn #fn_name<'a, E: nom::error::ParseError<&'a str> + nom::error::ContextError<&'a str>>(
|
||||
i: &'a str,
|
||||
) -> nom::IResult<&'a str, #name, E> {
|
||||
nom::error::context(
|
||||
"modifier",
|
||||
nom::branch::alt((
|
||||
#(#match_arms),*
|
||||
))
|
||||
)(i)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
|
|
122
check.txt
122
check.txt
|
@ -1,122 +0,0 @@
|
|||
Checking bng v0.1.0 (/home/p6nj/bng)
|
||||
error[E0720]: cannot resolve opaque type
|
||||
--> src/bng/score/lex/lexer.rs:35:39
|
||||
|
|
||||
35 | pub fn root<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atoms, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
|
||||
...
|
||||
42 | / terminated(
|
||||
43 | | many1(preceded(maybe_yml_str_space(), atom(notes))),
|
||||
44 | | maybe_yml_str_space(),
|
||||
45 | | )
|
||||
46 | | .map(Atoms)
|
||||
| |_______________- returning here with type `nom::Map<impl FnMut(&str) -> std::result::Result<(&str, std::vec::Vec<Atom>), nom::Err<E>>, fn(std::vec::Vec<Atom>) -> Atoms {Atoms}, std::vec::Vec<Atom>>`
|
||||
|
|
||||
::: /home/p6nj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/nom-7.1.3/src/sequence/mod.rs:100:6
|
||||
|
|
||||
100 | ) -> impl FnMut(I) -> IResult<I, O1, E>
|
||||
| ---------------------------------- returning this type `nom::Map<impl FnMut(&str) -> std::result::Result<(&str, std::vec::Vec<Atom>), nom::Err<E>>, fn(std::vec::Vec<Atom>) -> Atoms {Atoms}, std::vec::Vec<Atom>>`
|
||||
|
||||
error[E0720]: cannot resolve opaque type
|
||||
--> src/bng/score/lex/lexer.rs:98:37
|
||||
|
|
||||
98 | fn r#loop<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atom, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
|
||||
...
|
||||
105 | / context(
|
||||
106 | | "loop",
|
||||
107 | | delimited(
|
||||
108 | | char(Atom::LOOP.0),
|
||||
... |
|
||||
121 | | )
|
||||
122 | | .map(|(n, v)| Atom::Loop(n, v))
|
||||
| |___________________________________- returning here with type `nom::Map<impl FnMut(&str) -> std::result::Result<(&str, (NonZero<u8>, Atoms)), nom::Err<E>>, {closure@src/bng/score/lex/lexer.rs:122:10: 122:18}, (NonZero<u8>, Atoms)>`
|
||||
|
|
||||
::: /home/p6nj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/nom-7.1.3/src/error.rs:236:6
|
||||
|
|
||||
236 | ) -> impl FnMut(I) -> IResult<I, O, E>
|
||||
| --------------------------------- returning this type `nom::Map<impl FnMut(&str) -> std::result::Result<(&str, (NonZero<u8>, Atoms)), nom::Err<E>>, {closure@src/bng/score/lex/lexer.rs:122:10: 122:18}, (NonZero<u8>, Atoms)>`
|
||||
|
||||
error[E0720]: cannot resolve opaque type
|
||||
--> src/bng/score/lex/lexer.rs:185:35
|
||||
|
|
||||
185 | fn atom<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atom, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
|
||||
...
|
||||
192 | / context(
|
||||
193 | | "atom",
|
||||
194 | | alt((
|
||||
195 | | note(notes),
|
||||
... |
|
||||
204 | | )),
|
||||
205 | | )
|
||||
| |_____- returning here with type `impl FnMut(&str) -> std::result::Result<(&str, Atom), nom::Err<E>>`
|
||||
|
|
||||
::: /home/p6nj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/nom-7.1.3/src/error.rs:236:6
|
||||
|
|
||||
236 | ) -> impl FnMut(I) -> IResult<I, O, E>
|
||||
| --------------------------------- returning this opaque type `impl FnMut(&str) -> std::result::Result<(&str, Atom), nom::Err<E>>`
|
||||
|
||||
error[E0391]: cycle detected when computing type of opaque `bng::score::lex::lexer::root::{opaque#0}`
|
||||
--> src/bng/score/lex/lexer.rs:35:39
|
||||
|
|
||||
35 | pub fn root<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atoms, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: ...which requires type-checking `bng::score::lex::lexer::root`...
|
||||
--> src/bng/score/lex/lexer.rs:46:6
|
||||
|
|
||||
46 | .map(Atoms)
|
||||
| ^^^
|
||||
= note: ...which requires evaluating trait selection obligation `nom::sequence::terminated::{opaque#0}: core::marker::Sync`...
|
||||
= note: ...which again requires computing type of opaque `bng::score::lex::lexer::root::{opaque#0}`, completing the cycle
|
||||
note: cycle used when computing type of `bng::score::lex::lexer::root::{opaque#0}`
|
||||
--> src/bng/score/lex/lexer.rs:35:39
|
||||
|
|
||||
35 | pub fn root<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atoms, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
|
||||
|
||||
error[E0391]: cycle detected when computing type of opaque `bng::score::lex::lexer::tuple::{opaque#0}`
|
||||
--> src/bng/score/lex/lexer.rs:125:36
|
||||
|
|
||||
125 | fn tuple<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atom, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: ...which requires type-checking `bng::score::lex::lexer::tuple`...
|
||||
--> src/bng/score/lex/lexer.rs:136:6
|
||||
|
|
||||
136 | .map(Atom::Tuple)
|
||||
| ^^^
|
||||
= note: ...which requires evaluating trait selection obligation `nom::error::context::{opaque#0}: core::marker::Sync`...
|
||||
= note: ...which again requires computing type of opaque `bng::score::lex::lexer::tuple::{opaque#0}`, completing the cycle
|
||||
note: cycle used when computing type of `bng::score::lex::lexer::tuple::{opaque#0}`
|
||||
--> src/bng/score/lex/lexer.rs:125:36
|
||||
|
|
||||
125 | fn tuple<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atom, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
|
||||
|
||||
error[E0391]: cycle detected when computing type of opaque `bng::score::lex::lexer::slope::{opaque#0}`
|
||||
--> src/bng/score/lex/lexer.rs:139:36
|
||||
|
|
||||
139 | fn slope<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atom, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: ...which requires type-checking `bng::score::lex::lexer::slope`...
|
||||
--> src/bng/score/lex/lexer.rs:165:6
|
||||
|
|
||||
165 | .map(|((sm, i), v)| Atom::Slope(sm, i, v))
|
||||
| ^^^
|
||||
= note: ...which requires evaluating trait selection obligation `nom::error::context::{opaque#0}: core::marker::Sync`...
|
||||
= note: ...which again requires computing type of opaque `bng::score::lex::lexer::slope::{opaque#0}`, completing the cycle
|
||||
note: cycle used when computing type of `bng::score::lex::lexer::slope::{opaque#0}`
|
||||
--> src/bng/score/lex/lexer.rs:139:36
|
||||
|
|
||||
139 | fn slope<'a, E>(notes: &'a str) -> impl Parser<&'a str, Atom, E>
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
|
||||
|
||||
Some errors have detailed explanations: E0391, E0720.
|
||||
For more information about an error, try `rustc --explain E0391`.
|
||||
error: could not compile `bng` (bin "bng") due to 6 previous errors
|
61
class.puml
61
class.puml
|
@ -1,61 +0,0 @@
|
|||
@startuml Class Diagram
|
||||
|
||||
package core {
|
||||
package str {
|
||||
package traits {
|
||||
interface FromStr<type Error> {
|
||||
from_str(s: &str): Result<Self, Self::Error>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package cli {
|
||||
package docstr {}
|
||||
package schema {
|
||||
+enum Cli {
|
||||
+Play { PlayOpts }
|
||||
+Export { ExportOptions }
|
||||
+List { ListOptions }
|
||||
}
|
||||
+struct PlayOptions {
|
||||
#input: FileContents
|
||||
}
|
||||
+struct ExportOptions {
|
||||
~input: FileContents
|
||||
~output: AudioFileName
|
||||
}
|
||||
+enum ListOptions {
|
||||
+Extensions
|
||||
+struct Math { -kind: Option<MathDocKind> }
|
||||
+Glyphs
|
||||
}
|
||||
+enum MathDocKind {
|
||||
+Functions
|
||||
+Operators
|
||||
+Literals
|
||||
}
|
||||
+struct FileContents {
|
||||
0: String
|
||||
}
|
||||
FileContents --|> FromStr: type Error = io::Error
|
||||
+struct AudioFileName {
|
||||
0: String
|
||||
}
|
||||
+struct UnsupportedFileExtensionError {
|
||||
}
|
||||
AudioFileName -|> FromStr: type Error = UnsupportedFileExtensionError
|
||||
Cli --> PlayOptions
|
||||
Cli --> ExportOptions
|
||||
Cli --> ListOptions
|
||||
PlayOptions --> FileContents
|
||||
ExportOptions -> FileContents
|
||||
ExportOptions -> AudioFileName
|
||||
ListOptions --> MathDocKind
|
||||
AudioFileName --> UnsupportedFileExtensionError
|
||||
}
|
||||
}
|
||||
package lang {}
|
||||
package dasp {}
|
||||
|
||||
@enduml
|
|
@ -1,12 +0,0 @@
|
|||
! C:\Audio\Tunings\MTS Packs\World Scales Pack\scl\Ancient Greek Didymus Chromatic.scl
|
||||
!
|
||||
Didymus' Chromatic
|
||||
7
|
||||
!
|
||||
16/15
|
||||
10/9
|
||||
4/3
|
||||
3/2
|
||||
8/5
|
||||
5/3
|
||||
2/1
|
|
@ -1,28 +0,0 @@
|
|||
! Ancient Greek scales on E (white notes)
|
||||
! Size of map:
|
||||
12
|
||||
! First MIDI note number to retune:
|
||||
0
|
||||
! Last MIDI note number to retune:
|
||||
127
|
||||
! Middle note where the first entry in the mapping is mapped to:
|
||||
64
|
||||
! Reference note for which frequency is given:
|
||||
64
|
||||
! Frequency to tune the above note to (floating point e.g. 440.0):
|
||||
329.627557
|
||||
! Scale degree to consider as formal octave:
|
||||
7
|
||||
! Mapping.
|
||||
0
|
||||
1
|
||||
1
|
||||
2
|
||||
2
|
||||
3
|
||||
3
|
||||
4
|
||||
5
|
||||
5
|
||||
6
|
||||
6
|
|
@ -1,57 +0,0 @@
|
|||
# fixed
|
||||
instruments:
|
||||
# instrument name
|
||||
sine:
|
||||
# fixed
|
||||
expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds)
|
||||
square:
|
||||
expr: v*abs(sin(2*PI*f*t))
|
||||
# fixed
|
||||
vars:
|
||||
# name of the variable
|
||||
v: 1 # initial value of the variable
|
||||
scl:
|
||||
Didymus: poc/Ancient Greek Didymus Chromatic.scl
|
||||
kbm:
|
||||
Greek: poc/Ancient Greek.kbm
|
||||
channels:
|
||||
melody:
|
||||
instr: sine
|
||||
score:
|
||||
notes: cCdDefFgGaAb
|
||||
sheet: |
|
||||
aabc.
|
||||
'ab°A
|
||||
+d+d+d---
|
||||
/ff/f\\
|
||||
ab>c<ba
|
||||
;this is a comment (or lyrics whatever),
|
||||
C:CC
|
||||
(3deff)
|
||||
(deff)
|
||||
[ffe]
|
||||
{l 1-cos((PI*x)/2),acced}
|
||||
abbc!o5cc!v15feed!l4fedd!t60Gdd
|
||||
|
||||
; syntax :
|
||||
rest: .
|
||||
pizz.: '°
|
||||
volume: +-
|
||||
length: /\
|
||||
octave: ><
|
||||
comment?: ;,
|
||||
start here: ':'
|
||||
slope: {MODIFIER EXPR score}
|
||||
note modifier prefix: n
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t
|
||||
loop: ()
|
||||
loop with count: (COUNT score)
|
||||
tuple: []
|
||||
modifier: !
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t,
|
|
@ -1,55 +0,0 @@
|
|||
# fixed
|
||||
instruments:
|
||||
# instrument name
|
||||
sine:
|
||||
# fixed
|
||||
expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds)
|
||||
square:
|
||||
expr: v*abs(sin(2*PI*f*t))
|
||||
# fixed
|
||||
vars:
|
||||
# name of the variable
|
||||
v: 1 # initial value of the variable
|
||||
scl:
|
||||
Greek: poc/Ancient Greek Didymus Chromatic.scl
|
||||
channels:
|
||||
melody:
|
||||
instr: sine
|
||||
score:
|
||||
notes: cCdDefFgGaAb
|
||||
sheet: |
|
||||
aabc.
|
||||
'ab°A
|
||||
+d+d+d---
|
||||
/ff/f\\
|
||||
ab>c<ba
|
||||
;this is a comment (or lyrics whatever),
|
||||
C:CC
|
||||
(3deff)
|
||||
(deff)
|
||||
[ffe]
|
||||
{l 1-cos((PI*x)/2),acced}
|
||||
abbc!o5cc!v15feed!l4fedd!t60Gdd
|
||||
|
||||
; syntax :
|
||||
rest: .
|
||||
pizz.: '°
|
||||
volume: +-
|
||||
length: /\
|
||||
octave: ><
|
||||
comment?: ;,
|
||||
start here: ':'
|
||||
slope: {MODIFIER EXPR score}
|
||||
note modifier prefix: n
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t
|
||||
loop: ()
|
||||
loop with count: (COUNT score)
|
||||
tuple: []
|
||||
modifier: !
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t,
|
|
@ -1,55 +0,0 @@
|
|||
# fixed
|
||||
instruments:
|
||||
# instrument name
|
||||
sine:
|
||||
# fixed
|
||||
expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds)
|
||||
square:
|
||||
expr: v*abs(sin(2*PI*f*t))
|
||||
# fixed
|
||||
vars:
|
||||
# name of the variable
|
||||
v: 1 # initial value of the variable
|
||||
kbm:
|
||||
Greek: poc/Ancient Greek.kbm
|
||||
channels:
|
||||
melody:
|
||||
instr: sine
|
||||
score:
|
||||
notes: cCdDefFgGaAb
|
||||
sheet: |
|
||||
aabc.
|
||||
'ab°A
|
||||
+d+d+d---
|
||||
/ff/f\\
|
||||
ab>c<ba
|
||||
;this is a comment (or lyrics whatever),
|
||||
C:CC
|
||||
(3deff)
|
||||
(deff)
|
||||
[ffe]
|
||||
{l 1-cos((PI*x)/2),acced}
|
||||
abbc!o5cc!v15feed!l4fedd!t60Gdd
|
||||
|
||||
; syntax :
|
||||
rest: .
|
||||
pizz.: '°
|
||||
volume: +-
|
||||
length: /\
|
||||
octave: ><
|
||||
comment?: ;,
|
||||
start here: ':'
|
||||
slope: {MODIFIER EXPR score}
|
||||
note modifier prefix: n
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t
|
||||
loop: ()
|
||||
loop with count: (COUNT score)
|
||||
tuple: []
|
||||
modifier: !
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t,
|
|
@ -1,49 +0,0 @@
|
|||
# fixed
|
||||
instruments:
|
||||
# instrument name
|
||||
sine:
|
||||
# fixed
|
||||
expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds)
|
||||
square:
|
||||
expr: v*abs(sin(2*PI*f*t))
|
||||
channels:
|
||||
melody:
|
||||
instr: sine
|
||||
score:
|
||||
notes: cCdDefFgGaAb
|
||||
sheet: |
|
||||
aabc.
|
||||
'ab°A
|
||||
+d+d+d---
|
||||
/ff/f\\
|
||||
ab>c<ba
|
||||
;this is a comment (or lyrics whatever),
|
||||
C:CC
|
||||
(3deff)
|
||||
(deff)
|
||||
[ffe]
|
||||
{l 1-cos((PI*x)/2),acced}
|
||||
abbc!o5cc!v15feed!l4fedd!t60Gdd
|
||||
|
||||
; syntax :
|
||||
rest: .
|
||||
pizz.: '°
|
||||
volume: +-
|
||||
length: /\
|
||||
octave: ><
|
||||
comment?: ;,
|
||||
start here: ':'
|
||||
slope: {MODIFIER EXPR score}
|
||||
note modifier prefix: n
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t
|
||||
loop: ()
|
||||
loop with count: (COUNT score)
|
||||
tuple: []
|
||||
modifier: !
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t,
|
51
poc/poc.yml
Normal file
51
poc/poc.yml
Normal file
|
@ -0,0 +1,51 @@
|
|||
# fixed
|
||||
instruments:
|
||||
# instrument name
|
||||
sine:
|
||||
# fixed
|
||||
expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds)
|
||||
square:
|
||||
expr: v*abs(sin(2*PI*f*t))
|
||||
# fixed
|
||||
vars:
|
||||
# name of the variable
|
||||
v: 1 # initial value of the variable
|
||||
channels:
|
||||
melody:
|
||||
instr: sine
|
||||
score:
|
||||
notes: cCdDefFgGaAb
|
||||
sheet:
|
||||
aabc.
|
||||
'ab°A
|
||||
+d+d+d---
|
||||
/ff/f\\
|
||||
ab>c<ba
|
||||
;this is a comment (or lyrics whatever),
|
||||
C:CC
|
||||
(3deff)
|
||||
(deff)
|
||||
[ffe]
|
||||
{l 1-cos((PI*x)/2),acced}
|
||||
abbc!o5cc!v15feed!l4fedd!t60Gdd
|
||||
# rest: .
|
||||
# pizz.: '°
|
||||
# volume: +-
|
||||
# length: /\
|
||||
# octave: ><
|
||||
# comment?: ;,
|
||||
# start here: ':'
|
||||
# slope: {MODIFIER EXPR, score}
|
||||
# note modifier prefix: n
|
||||
# volume modifier prefix: v
|
||||
# octave modifier prefix: o
|
||||
# length modifier prefix: l
|
||||
# tempo modifier prefix: t
|
||||
# loop: ()
|
||||
# loop with count: (COUNT, score)
|
||||
# tuple: []
|
||||
# modifier: !
|
||||
# volume modifier prefix: v
|
||||
# octave modifier prefix: o
|
||||
# length modifier prefix: l
|
||||
# tempo modifier prefix: t
|
70
src/bng.rs
70
src/bng.rs
|
@ -7,37 +7,75 @@ use fasteval::{Compiler, Instruction};
|
|||
pub(super) use instrument::Instrument;
|
||||
pub(super) use score::Atom;
|
||||
use score::Atoms;
|
||||
#[cfg(debug_assertions)]
|
||||
use serde::Serialize;
|
||||
use serde::{de::Visitor, Deserialize};
|
||||
use tune::scala::{Kbm, Scl};
|
||||
|
||||
mod de;
|
||||
mod instrument;
|
||||
mod score;
|
||||
|
||||
#[derive(Debug, PartialEq, Wrapper, From)]
|
||||
pub struct Expression(Instruction);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl Serialize for Expression {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let expr_str = String::new();
|
||||
serializer.serialize_str(&format!("{:#?}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Expression {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(ExpressionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpressionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ExpressionVisitor {
|
||||
type Value = Expression;
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter
|
||||
.write_str("a math expression following fasteval syntax (https://docs.rs/fasteval)")
|
||||
}
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
v.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Expression {
|
||||
type Err = fasteval::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parser = fasteval::Parser::new();
|
||||
let mut slab = fasteval::Slab::new();
|
||||
Ok(parser
|
||||
.parse(s, &mut slab.ps)?
|
||||
.from(&slab.ps)
|
||||
.compile(&slab.ps, &mut slab.cs)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(new, Deserialize)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
||||
pub(super) struct Channel {
|
||||
instr: String,
|
||||
score: Atoms,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
#[cfg_attr(debug_assertions, derive(new, Debug))]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize)]
|
||||
#[cfg_attr(debug_assertions, derive(new, Debug, Serialize))]
|
||||
pub(super) struct BngFile {
|
||||
instruments: HashMap<String, Instrument>,
|
||||
channels: HashMap<String, Channel>,
|
||||
#[serde(rename = "scl")]
|
||||
scales: HashMap<String, Scale>,
|
||||
#[serde(rename = "kbm")]
|
||||
keyboard_mappings: HashMap<String, KeyboardMapping>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Wrapper, From)]
|
||||
struct Scale(Scl);
|
||||
#[derive(Debug, Wrapper, From)]
|
||||
struct KeyboardMapping(Kbm);
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
use std::{fmt::Display, marker::PhantomData, str::FromStr};
|
||||
|
||||
use fasteval::Compiler;
|
||||
use serde::{de::Visitor, Deserialize};
|
||||
|
||||
use super::{Expression, KeyboardMapping, Scale};
|
||||
|
||||
mod from_str;
|
||||
|
||||
struct FromStrVisitor<'a, V> {
|
||||
expecting: &'a str,
|
||||
phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<'a, V> FromStrVisitor<'a, V> {
|
||||
fn new(expecting: &'a str) -> Self {
|
||||
FromStrVisitor {
|
||||
expecting,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, 'a, V> Visitor<'de> for FromStrVisitor<'a, V>
|
||||
where
|
||||
V: FromStr,
|
||||
<V as FromStr>::Err: Display,
|
||||
{
|
||||
type Value = V;
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str(self.expecting)
|
||||
}
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
v.parse::<V>().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! deserialize_fromstr_expect {
|
||||
($type:ty, $expect:literal) => {
|
||||
impl<'de> serde::de::Deserialize<'de> for $type {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(FromStrVisitor::new($expect))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
deserialize_fromstr_expect!(
|
||||
Expression,
|
||||
"a math expression following fasteval syntax (https://docs.rs/fasteval)"
|
||||
);
|
||||
|
||||
deserialize_fromstr_expect!(Scale, "a .scl file containing a Scala scale");
|
||||
deserialize_fromstr_expect!(
|
||||
KeyboardMapping,
|
||||
"a .kbm file containing a Scala keyboard mapping"
|
||||
);
|
|
@ -1,53 +0,0 @@
|
|||
use std::{fs::File, io, str::FromStr};
|
||||
|
||||
use amplify::{From, Wrapper};
|
||||
use fasteval::Compiler;
|
||||
use thiserror::Error;
|
||||
use tune::scala::{Kbm, KbmImportError, Scl, SclImportError};
|
||||
|
||||
use crate::bng::{KeyboardMapping, Scale};
|
||||
|
||||
use super::super::Expression;
|
||||
|
||||
impl FromStr for Expression {
|
||||
type Err = fasteval::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parser = fasteval::Parser::new();
|
||||
let mut slab = fasteval::Slab::new();
|
||||
Ok(parser
|
||||
.parse(s, &mut slab.ps)?
|
||||
.from(&slab.ps)
|
||||
.compile(&slab.ps, &mut slab.cs)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("{0:?}")]
|
||||
pub struct ScalaImportError<E>(E);
|
||||
|
||||
impl FromStr for Scale {
|
||||
type Err = ScalaImportError<SclImportError>;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Scl::import(
|
||||
File::open(s)
|
||||
.map_err(Into::into)
|
||||
.map_err(ScalaImportError)?,
|
||||
)
|
||||
.map(Scale::from)
|
||||
.map_err(ScalaImportError)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for KeyboardMapping {
|
||||
type Err = ScalaImportError<KbmImportError>;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Kbm::import(
|
||||
File::open(s)
|
||||
.map_err(Into::into)
|
||||
.map_err(ScalaImportError)?,
|
||||
)
|
||||
.map(KeyboardMapping::from)
|
||||
.map_err(ScalaImportError)
|
||||
}
|
||||
}
|
|
@ -1,15 +1,17 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use super::Expression as Instruction;
|
||||
use derive_new::new;
|
||||
use derived_deref::Deref;
|
||||
use serde::Deserialize;
|
||||
#[cfg(debug_assertions)]
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Expression as Instruction;
|
||||
|
||||
#[derive(Deref, new, Deserialize)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
||||
pub struct Instrument {
|
||||
#[target]
|
||||
expr: Instruction,
|
||||
vars: Option<HashMap<String, f64>>,
|
||||
vars: Option<HashMap<String, f32>>,
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@ use std::{
|
|||
|
||||
use amplify::From;
|
||||
use anyhow::Context;
|
||||
use bng_macros::{ModifierParser, QuickModifierParser, SlopeModifierParser};
|
||||
use bng_macros::{QuickModifierParser, SlopeModifierParser};
|
||||
use derive_new::new;
|
||||
use derived_deref::Deref;
|
||||
use lex::lexer::flat_atom_parser;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
character::{complete::char, streaming::one_of},
|
||||
|
@ -15,21 +16,25 @@ use nom::{
|
|||
multi::many0,
|
||||
sequence::{preceded, terminated},
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
use serde::Serialize;
|
||||
use serde::{
|
||||
de::{self as serde_de, Visitor},
|
||||
Deserialize,
|
||||
};
|
||||
use strum::EnumDiscriminants;
|
||||
use thiserror::Error;
|
||||
use utils::{inflate, InflateError};
|
||||
|
||||
mod de;
|
||||
mod lex;
|
||||
mod utils;
|
||||
pub use de::*;
|
||||
|
||||
use super::Expression as Instruction;
|
||||
|
||||
#[derive(Deref, From, Default, Clone)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
||||
#[derive(Deref, From, Default)]
|
||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
||||
pub struct Atoms(Vec<Atom>);
|
||||
|
||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
||||
|
@ -39,27 +44,54 @@ pub enum Atom {
|
|||
StartHere,
|
||||
Modifier(Modifier),
|
||||
QuickModifier(QuickModifier),
|
||||
Loop(NonZeroU8, Atoms),
|
||||
Tuple(Atoms),
|
||||
Slope(SlopeModifier, Instruction, Atoms),
|
||||
Loop(NonZeroU8, Vec<Atom>),
|
||||
Tuple(Vec<Atom>),
|
||||
Slope(SlopeModifier, Instruction, Vec<Atom>),
|
||||
Comment,
|
||||
}
|
||||
|
||||
impl Clone for Atom {
|
||||
#[cfg(debug_assertions)]
|
||||
impl Serialize for Atom {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str("atom")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
||||
pub(super) enum FlatAtom {
|
||||
Note(u8),
|
||||
Rest,
|
||||
StartHere,
|
||||
Modifier(Modifier),
|
||||
QuickModifier(QuickModifier),
|
||||
LoopStarts(NonZeroU8),
|
||||
LoopEnds,
|
||||
TupleStarts,
|
||||
TupleEnds,
|
||||
SlopeStarts(SlopeModifier, Instruction),
|
||||
SlopeEnds,
|
||||
Comment,
|
||||
}
|
||||
|
||||
impl Clone for FlatAtom {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Note(n) => Self::Note(*n),
|
||||
Self::Rest => Self::Rest,
|
||||
Self::StartHere => Self::StartHere,
|
||||
Self::Modifier(m) => Self::Modifier(m.clone()),
|
||||
Self::QuickModifier(q) => Self::QuickModifier(q.clone()),
|
||||
Self::Comment => Self::Comment,
|
||||
_ => unimplemented!("variant can't be cloned right now"),
|
||||
Self::LoopEnds => Self::LoopEnds,
|
||||
Self::SlopeEnds => Self::SlopeEnds,
|
||||
Self::StartHere => Self::StartHere,
|
||||
Self::TupleEnds => Self::TupleEnds,
|
||||
Self::TupleStarts => Self::TupleStarts,
|
||||
_ => unimplemented!("variant can't be cloned"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, ModifierParser)]
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
||||
pub enum Modifier {
|
||||
Volume(u8),
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
num::TryFromIntError,
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use derive_new::new;
|
||||
use naan::fun::{F1Once, F2Once};
|
||||
use nom::{
|
||||
character::complete::one_of,
|
||||
combinator::all_consuming,
|
||||
error::{context, convert_error, ContextError, FromExternalError, ParseError, VerboseError},
|
||||
multi::{many0, many1},
|
||||
sequence::{preceded, terminated},
|
||||
IResult, Needed, Parser,
|
||||
Parser,
|
||||
};
|
||||
use serde::{
|
||||
de::{self, Deserializer, Visitor},
|
||||
|
@ -20,9 +12,37 @@ use serde::{
|
|||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bng::score::lex::lexer::{root, set_notes};
|
||||
use crate::bng::score::lex::lexer::flat_atom_parser;
|
||||
|
||||
use super::{Atom, Atoms};
|
||||
use super::{
|
||||
utils::{inflate, InflateError},
|
||||
Atoms, FlatAtom,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum AtomsSerializeError {
|
||||
#[error("sheet parsing error: {0}")]
|
||||
Parsing(String),
|
||||
#[error("sheet semantics: {0}")]
|
||||
Inflation(#[from] InflateError),
|
||||
}
|
||||
|
||||
fn nom_err_message(e: nom::Err<nom::error::Error<&str>>) -> String {
|
||||
match e {
|
||||
nom::Err::Incomplete(needed) => format!(
|
||||
"input is incomplete, needed {} byte(s) more",
|
||||
match needed {
|
||||
nom::Needed::Unknown => "?".to_string(),
|
||||
nom::Needed::Size(s) => s.to_string(),
|
||||
}
|
||||
),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => format!(
|
||||
"got error code {code:#?} at \"{input}\"",
|
||||
code = e.code,
|
||||
input = e.input
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Atoms {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
|
@ -44,23 +64,37 @@ impl<'de> Deserialize<'de> for Atoms {
|
|||
if sheet.is_empty() {
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
set_notes(notes).unwrap();
|
||||
all_consuming(root)
|
||||
.parse(&sheet)
|
||||
.map_err(|e| pretty_verbose_err.curry().call1(sheet.as_str()).call1(e))
|
||||
.map_err(de::Error::custom)
|
||||
.map(|(i, r)| r)
|
||||
flat_atom_parser_mapper::<D, _>(&sheet, flat_atom_parser(¬es))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_verbose_err<I>(input: I, e: nom::Err<VerboseError<I>>) -> String
|
||||
fn maybe_yml_str_space<'a, E>() -> impl Parser<&'a str, Vec<char>, E>
|
||||
where
|
||||
I: Deref<Target = str>,
|
||||
E: nom::error::ParseError<&'a str>,
|
||||
{
|
||||
match e {
|
||||
nom::Err::Incomplete(Needed::Unknown) => "needed some more bytes".to_string(),
|
||||
nom::Err::Incomplete(Needed::Size(n)) => format!("needed {} more bytes", n),
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => convert_error(input, e),
|
||||
}
|
||||
many0(one_of(" \t\r"))
|
||||
}
|
||||
|
||||
fn flat_atom_parser_mapper<'a, 'de, D, P>(
|
||||
input: &'a str,
|
||||
parser: P,
|
||||
) -> Result<Atoms, <D as Deserializer<'de>>::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
P: Parser<&'a str, FlatAtom, nom::error::Error<&'a str>>,
|
||||
{
|
||||
all_consuming(terminated(
|
||||
many1(preceded(maybe_yml_str_space(), parser)),
|
||||
maybe_yml_str_space(),
|
||||
))(input)
|
||||
.map_err(nom_err_message)
|
||||
.map_err(AtomsSerializeError::Parsing)
|
||||
.map_err(de::Error::custom)
|
||||
.and_then(|(_, v)| {
|
||||
inflate(v)
|
||||
.map_err(AtomsSerializeError::from)
|
||||
.map_err(de::Error::custom)
|
||||
})
|
||||
.map(Atoms)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{Atom, Modifier, QuickModifier, SlopeModifier};
|
||||
use super::{Atom, FlatAtom, Modifier, QuickModifier, SlopeModifier};
|
||||
|
||||
pub(super) mod lexer;
|
||||
|
||||
|
|
|
@ -1,230 +1,87 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
num::{NonZeroU16, NonZeroU8, TryFromIntError},
|
||||
sync::{Mutex, MutexGuard, PoisonError},
|
||||
num::{NonZeroU16, NonZeroU8},
|
||||
};
|
||||
|
||||
use clap::builder::TypedValueParser;
|
||||
use const_format::concatcp;
|
||||
use fasteval::Compiler;
|
||||
use lazy_static::lazy_static;
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{is_not, tag, take_till, take_till1},
|
||||
bytes::complete::{take_till, take_till1},
|
||||
character::complete::{anychar, char, one_of, space1, u16, u8},
|
||||
combinator::{all_consuming, cut, eof, map_opt, map_res, not, opt, value, verify},
|
||||
error::{context, ContextError, ErrorKind, FromExternalError, ParseError},
|
||||
multi::{many0, many1, many_till},
|
||||
combinator::{map_opt, map_res, opt, value, verify},
|
||||
multi::many0,
|
||||
sequence::{delimited, pair, preceded, separated_pair, terminated},
|
||||
Err, IResult, Parser,
|
||||
};
|
||||
|
||||
use crate::bng::score::Atoms;
|
||||
|
||||
use super::{
|
||||
super::super::Expression as Instruction, Atom, Modifier, QuickModifier, SlopeModifier,
|
||||
super::super::Expression as Instruction,
|
||||
{Atom, FlatAtom, Modifier, QuickModifier, SlopeModifier},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
lazy_static! {
|
||||
static ref NOTES: Mutex<Option<String>> = Mutex::new(None);
|
||||
pub(crate) trait Parse: Sized {
|
||||
fn parse(input: &str) -> IResult<&str, Self>;
|
||||
}
|
||||
|
||||
pub fn set_notes(
|
||||
notes: impl ToString,
|
||||
) -> Result<Option<String>, PoisonError<MutexGuard<'static, Option<String>>>> {
|
||||
Ok(NOTES.lock()?.replace(notes.to_string()))
|
||||
}
|
||||
|
||||
fn maybe_yml_str_space<'a, E>(i: &'a str) -> IResult<&'a str, Vec<char>, E>
|
||||
where
|
||||
E: nom::error::ParseError<&'a str> + ContextError<&'a str>,
|
||||
{
|
||||
context("yml white space", many0(one_of(" \t\r\n"))).parse(i)
|
||||
}
|
||||
|
||||
pub fn root<'a, E>(i: &'a str) -> IResult<&'a str, Atoms, E>
|
||||
where
|
||||
E: ParseError<&'a str>
|
||||
+ ContextError<&'a str>
|
||||
+ FromExternalError<&'a str, TryFromIntError>
|
||||
+ FromExternalError<&'a str, E>,
|
||||
{
|
||||
many0(delimited(maybe_yml_str_space, atom, maybe_yml_str_space))
|
||||
.map(Atoms)
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn note<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str> + ContextError<&'a str> + FromExternalError<&'a str, TryFromIntError>,
|
||||
{
|
||||
if let Some(notes) = NOTES.lock().unwrap().as_deref() {
|
||||
context(
|
||||
"note",
|
||||
map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from),
|
||||
)
|
||||
.map(Atom::Note)
|
||||
.parse(i)
|
||||
} else {
|
||||
panic!("trouble locking the note set!")
|
||||
impl Parse for Modifier {
|
||||
fn parse(input: &str) -> IResult<&str, Self> {
|
||||
alt((
|
||||
preceded(char(Modifier::VOLUME), u8).map(Modifier::Volume),
|
||||
preceded(char(Modifier::OCTAVE), u8).map(Modifier::Octave),
|
||||
preceded(char(Modifier::LENGTH), map_opt(u8, NonZeroU8::new)).map(Modifier::Length),
|
||||
preceded(char(Modifier::TEMPO), map_opt(u16, NonZeroU16::new)).map(Modifier::Tempo),
|
||||
))(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn rest<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str> + ContextError<&'a str>,
|
||||
{
|
||||
value(Atom::Rest, context("rest", char(Atom::REST))).parse(i)
|
||||
}
|
||||
|
||||
fn start_here<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str> + ContextError<&'a str>,
|
||||
{
|
||||
value(
|
||||
Atom::StartHere,
|
||||
context("start_here", char(Atom::START_HERE)),
|
||||
)
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn modifier<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str> + ContextError<&'a str>,
|
||||
{
|
||||
use super::super::modifier;
|
||||
context("modifier", preceded(char(Atom::MODIFIER), cut(modifier)))
|
||||
.map(Atom::Modifier)
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn quick_modifier<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str> + ContextError<&'a str>,
|
||||
{
|
||||
use super::super::quick_modifier;
|
||||
context("quick_modifier", quick_modifier)
|
||||
.map(Atom::QuickModifier)
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn r#loop<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str>
|
||||
+ ContextError<&'a str>
|
||||
+ FromExternalError<&'a str, TryFromIntError>
|
||||
+ FromExternalError<&'a str, E>,
|
||||
{
|
||||
context(
|
||||
"loop",
|
||||
delimited(
|
||||
pub fn flat_atom_parser(notes: &str) -> impl Parser<&str, FlatAtom, nom::error::Error<&str>> {
|
||||
alt((
|
||||
map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note),
|
||||
value(FlatAtom::Rest, char(Atom::REST)),
|
||||
value(FlatAtom::StartHere, char(Atom::START_HERE)),
|
||||
preceded(char(Atom::MODIFIER), Modifier::parse).map(FlatAtom::Modifier),
|
||||
QuickModifier::parse.map(FlatAtom::QuickModifier),
|
||||
preceded(
|
||||
char(Atom::LOOP.0),
|
||||
cut(pair(
|
||||
map_opt(opt(u8), |n| {
|
||||
if let Some(n) = n {
|
||||
NonZeroU8::new(n)
|
||||
} else {
|
||||
unsafe { Some(NonZeroU8::new_unchecked(2)) }
|
||||
}
|
||||
}),
|
||||
root,
|
||||
)),
|
||||
cut(char(Atom::LOOP.1)),
|
||||
),
|
||||
)
|
||||
.map(|(n, v)| Atom::Loop(n, v))
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn tuple<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str>
|
||||
+ ContextError<&'a str>
|
||||
+ FromExternalError<&'a str, TryFromIntError>
|
||||
+ FromExternalError<&'a str, E>,
|
||||
{
|
||||
context(
|
||||
"tuple",
|
||||
delimited(char(Atom::TUPLE.0), cut(root), cut(char(Atom::TUPLE.1))),
|
||||
)
|
||||
.map(Atom::Tuple)
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn slope<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str>
|
||||
+ ContextError<&'a str>
|
||||
+ FromExternalError<&'a str, TryFromIntError>
|
||||
+ FromExternalError<&'a str, E>,
|
||||
{
|
||||
use super::super::slope_modifier;
|
||||
context(
|
||||
"slope_starts",
|
||||
delimited(
|
||||
char(Atom::SLOPE.0),
|
||||
cut(separated_pair(
|
||||
map_opt(opt(u8), |n| {
|
||||
if let Some(n) = n {
|
||||
NonZeroU8::new(n)
|
||||
} else {
|
||||
unsafe { Some(NonZeroU8::new_unchecked(2)) }
|
||||
}
|
||||
}),
|
||||
)
|
||||
.map(FlatAtom::LoopStarts),
|
||||
value(FlatAtom::LoopEnds, char(Atom::LOOP.1)),
|
||||
value(FlatAtom::TupleStarts, char(Atom::TUPLE.0)),
|
||||
value(FlatAtom::TupleEnds, char(Atom::TUPLE.1)),
|
||||
terminated(
|
||||
preceded(
|
||||
char(Atom::SLOPE.0),
|
||||
separated_pair(
|
||||
slope_modifier,
|
||||
SlopeModifier::parse,
|
||||
char(' '),
|
||||
map_res(take_till1(|c| c == ','), |s: &str| {
|
||||
s.parse()
|
||||
.map_err(|_| E::from_error_kind(s, ErrorKind::Verify))
|
||||
.map_err(|_| nom::error::Error::new(s, nom::error::ErrorKind::Verify))
|
||||
}),
|
||||
),
|
||||
char(','),
|
||||
root,
|
||||
)),
|
||||
cut(char(Atom::SLOPE.1)),
|
||||
),
|
||||
)
|
||||
.map(|((sm, i), v)| Atom::Slope(sm, i, v))
|
||||
.parse(i)
|
||||
}
|
||||
|
||||
fn comment<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str> + ContextError<&'a str>,
|
||||
{
|
||||
context(
|
||||
"comment",
|
||||
),
|
||||
char(','),
|
||||
)
|
||||
.map(|(sm, i)| FlatAtom::SlopeStarts(sm, i)),
|
||||
value(FlatAtom::SlopeEnds, char(Atom::SLOPE.1)),
|
||||
value(
|
||||
Atom::Comment,
|
||||
FlatAtom::Comment,
|
||||
delimited(
|
||||
char(Atom::COMMENT.0),
|
||||
many0(alt((
|
||||
value((), comment),
|
||||
value((), is_not(concatcp!(Atom::COMMENT.0, Atom::COMMENT.1))),
|
||||
))),
|
||||
cut(char(Atom::COMMENT.1)),
|
||||
take_till(|c| c == Atom::COMMENT.1),
|
||||
char(Atom::COMMENT.1),
|
||||
),
|
||||
),
|
||||
)(i)
|
||||
}
|
||||
|
||||
fn atom<'a, E>(i: &'a str) -> IResult<&'a str, Atom, E>
|
||||
where
|
||||
E: ParseError<&'a str>
|
||||
+ ContextError<&'a str>
|
||||
+ FromExternalError<&'a str, TryFromIntError>
|
||||
+ FromExternalError<&'a str, E>,
|
||||
{
|
||||
context(
|
||||
"atom",
|
||||
alt((
|
||||
comment,
|
||||
start_here,
|
||||
modifier,
|
||||
quick_modifier,
|
||||
r#loop,
|
||||
tuple,
|
||||
slope,
|
||||
rest,
|
||||
note,
|
||||
)),
|
||||
)
|
||||
.parse(i)
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use const_format::concatcp;
|
||||
use nom::{
|
||||
error::{Error, ErrorKind, VerboseError},
|
||||
error::{Error, ErrorKind},
|
||||
Err,
|
||||
};
|
||||
|
||||
|
@ -8,24 +8,15 @@ use flat_atom::{
|
|||
FASTEVAL_INSTRUCTION as FLATATOM_FASTEVAL_INSTRUCTION, SAMPLE_STR as FLATATOM_SAMPLE_STRING,
|
||||
};
|
||||
|
||||
fn set_notes() {
|
||||
super::set_notes("abcdefg").unwrap();
|
||||
}
|
||||
|
||||
type NomError<'a> = VerboseError<&'a str>;
|
||||
|
||||
mod flat_atom {
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use fasteval::Compiler;
|
||||
use lazy_static::lazy_static;
|
||||
use nom::Parser;
|
||||
|
||||
use crate::bng::score::Atoms;
|
||||
|
||||
use super::super::{
|
||||
super::super::super::Expression as Instruction, super::UP, atom, Atom, Modifier,
|
||||
QuickModifier, SlopeModifier,
|
||||
super::super::super::Expression as Instruction, super::UP, flat_atom_parser, Atom,
|
||||
FlatAtom, Modifier, QuickModifier, SlopeModifier,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
|
@ -35,43 +26,32 @@ mod flat_atom {
|
|||
Atom::TUPLE.1,
|
||||
"ed",
|
||||
Atom::COMMENT.0,
|
||||
"hello",
|
||||
Atom::COMMENT.1
|
||||
"hello"
|
||||
);
|
||||
|
||||
pub(super) fn sample_str_expr() -> Atoms {
|
||||
Atoms(vec![
|
||||
Atom::Tuple(Atoms(vec![Atom::Note(0), Atom::Note(2), Atom::Note(2)])),
|
||||
Atom::Note(4),
|
||||
Atom::Note(3),
|
||||
Atom::Comment,
|
||||
])
|
||||
}
|
||||
|
||||
pub(super) const FASTEVAL_INSTRUCTION: &str = "1-cos((PI*x)/2)";
|
||||
|
||||
#[test]
|
||||
fn note() {
|
||||
set_notes();
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Atom::Note(2))),
|
||||
atom::<NomError>.parse(concatcp!('c', SAMPLE_STR))
|
||||
Ok((SAMPLE_STR, FlatAtom::Note(2))),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!('c', SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Atom::Rest)),
|
||||
atom::<NomError>.parse(concatcp!(Atom::REST, SAMPLE_STR))
|
||||
Ok((SAMPLE_STR, FlatAtom::Rest)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::REST, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_here() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Atom::StartHere)),
|
||||
atom::<NomError>.parse(concatcp!(Atom::START_HERE, SAMPLE_STR))
|
||||
Ok((SAMPLE_STR, FlatAtom::StartHere)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::START_HERE, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -80,102 +60,108 @@ mod flat_atom {
|
|||
assert_eq!(
|
||||
Ok((
|
||||
SAMPLE_STR,
|
||||
Atom::Modifier(Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) }))
|
||||
FlatAtom::Modifier(Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) }))
|
||||
)),
|
||||
atom::<NomError>.parse(concatcp!(Atom::MODIFIER, Modifier::LENGTH, 2u8, SAMPLE_STR))
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(
|
||||
Atom::MODIFIER,
|
||||
Modifier::LENGTH,
|
||||
2u8,
|
||||
SAMPLE_STR
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_modifier() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Atom::QuickModifier(QuickModifier::Length(UP)))),
|
||||
atom::<NomError>.parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR))
|
||||
Ok((
|
||||
SAMPLE_STR,
|
||||
FlatAtom::QuickModifier(QuickModifier::Length(UP))
|
||||
)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r#loop() {
|
||||
set_notes();
|
||||
fn loop_starts() {
|
||||
assert_eq!(
|
||||
Ok((
|
||||
SAMPLE_STR,
|
||||
Atom::Loop(unsafe { NonZeroU8::new_unchecked(3) }, sample_str_expr())
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) })
|
||||
)),
|
||||
atom::<NomError>.parse(concatcp!(
|
||||
Atom::LOOP.0,
|
||||
3u8,
|
||||
SAMPLE_STR,
|
||||
Atom::LOOP.1,
|
||||
SAMPLE_STR
|
||||
))
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::LOOP.0, 3u8, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Ok((
|
||||
SAMPLE_STR,
|
||||
Atom::Loop(unsafe { NonZeroU8::new_unchecked(2) }, sample_str_expr())
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(2) })
|
||||
)),
|
||||
atom::<NomError>.parse(concatcp!(
|
||||
Atom::LOOP.0,
|
||||
SAMPLE_STR,
|
||||
Atom::LOOP.1,
|
||||
SAMPLE_STR
|
||||
))
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::LOOP.0, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Err(nom::Err::Failure(Error::new(
|
||||
concatcp!(0u8, SAMPLE_STR),
|
||||
ErrorKind::MapOpt
|
||||
Err(nom::Err::Error(Error::new(
|
||||
concatcp!(Atom::LOOP.0, 0u8, SAMPLE_STR),
|
||||
ErrorKind::Char
|
||||
))),
|
||||
atom.parse(concatcp!(Atom::LOOP.0, 0u8, SAMPLE_STR))
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::LOOP.0, 0u8, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple() {
|
||||
set_notes();
|
||||
fn loop_ends() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Atom::Tuple(sample_str_expr()))),
|
||||
atom::<NomError>.parse(concatcp!(
|
||||
Atom::TUPLE.0,
|
||||
SAMPLE_STR,
|
||||
Atom::TUPLE.1,
|
||||
SAMPLE_STR
|
||||
))
|
||||
Ok((SAMPLE_STR, FlatAtom::LoopEnds)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::LOOP.1, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slope() {
|
||||
set_notes();
|
||||
fn tuple_starts() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, FlatAtom::TupleStarts)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::TUPLE.0, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_ends() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, FlatAtom::TupleEnds)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::TUPLE.1, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slope_starts() {
|
||||
assert_eq!(
|
||||
Ok((
|
||||
SAMPLE_STR,
|
||||
Atom::Slope(
|
||||
SlopeModifier::Note,
|
||||
FASTEVAL_INSTRUCTION.parse().unwrap(),
|
||||
sample_str_expr()
|
||||
)
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Note, FASTEVAL_INSTRUCTION.parse().unwrap())
|
||||
)),
|
||||
atom::<NomError>.parse(concatcp!(
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(
|
||||
Atom::SLOPE.0,
|
||||
SlopeModifier::NOTE,
|
||||
' ',
|
||||
FASTEVAL_INSTRUCTION,
|
||||
',',
|
||||
SAMPLE_STR,
|
||||
Atom::SLOPE.1,
|
||||
SAMPLE_STR
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment() {
|
||||
set_notes();
|
||||
fn slope_ends() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Atom::Comment)),
|
||||
atom::<NomError>.parse(concatcp!(
|
||||
Ok((SAMPLE_STR, FlatAtom::SlopeEnds)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(Atom::SLOPE.1, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, FlatAtom::Comment)),
|
||||
flat_atom_parser("abcdefg").parse(concatcp!(
|
||||
Atom::COMMENT.0,
|
||||
"hi I'm a little pony",
|
||||
SAMPLE_STR,
|
||||
|
@ -184,38 +170,27 @@ mod flat_atom {
|
|||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_comments() {
|
||||
set_notes();
|
||||
assert_eq!(
|
||||
Ok(("", Atom::Comment)),
|
||||
atom::<NomError>.parse(";d;;dd,ef,,")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod modifier {
|
||||
use std::num::{NonZeroU16, NonZeroU8};
|
||||
|
||||
use const_format::concatcp;
|
||||
use nom::error::{Error, ErrorKind, VerboseError};
|
||||
|
||||
use super::{NomError, FLATATOM_SAMPLE_STRING as SAMPLE_STR};
|
||||
use crate::bng::score::{modifier, Modifier};
|
||||
use super::FLATATOM_SAMPLE_STRING as SAMPLE_STR;
|
||||
use super::*;
|
||||
use crate::bng::score::{lex::lexer::Parse, Modifier};
|
||||
|
||||
#[test]
|
||||
fn volume() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Modifier::Volume(2))),
|
||||
modifier::<NomError>(concatcp!(Modifier::VOLUME, 2u8, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::VOLUME, 2u8, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Err(nom::Err::Error(Error::new(
|
||||
concatcp!(Modifier::VOLUME, 2556u16, SAMPLE_STR),
|
||||
ErrorKind::Char
|
||||
))),
|
||||
modifier(concatcp!(Modifier::VOLUME, 2556u16, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::VOLUME, 2556u16, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -223,14 +198,14 @@ mod modifier {
|
|||
fn octave() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, Modifier::Octave(2))),
|
||||
modifier::<NomError>(concatcp!(Modifier::OCTAVE, 2u8, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::OCTAVE, 2u8, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Err(nom::Err::Error(Error::new(
|
||||
concatcp!(Modifier::OCTAVE, 2556u16, SAMPLE_STR),
|
||||
ErrorKind::Char
|
||||
))),
|
||||
modifier(concatcp!(Modifier::OCTAVE, 2556u16, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::OCTAVE, 2556u16, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -241,21 +216,21 @@ mod modifier {
|
|||
SAMPLE_STR,
|
||||
Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) })
|
||||
)),
|
||||
modifier::<NomError>(concatcp!(Modifier::LENGTH, 2u8, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::LENGTH, 2u8, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Err(nom::Err::Error(Error::new(
|
||||
concatcp!(Modifier::LENGTH, 2556u16, SAMPLE_STR),
|
||||
ErrorKind::Char
|
||||
))),
|
||||
modifier(concatcp!(Modifier::LENGTH, 2556u16, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::LENGTH, 2556u16, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Err(nom::Err::Error(Error::new(
|
||||
concatcp!(Modifier::LENGTH, 0u8, SAMPLE_STR),
|
||||
ErrorKind::Char
|
||||
))),
|
||||
modifier(concatcp!(Modifier::LENGTH, 0u8, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::LENGTH, 0u8, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -266,37 +241,35 @@ mod modifier {
|
|||
SAMPLE_STR,
|
||||
Modifier::Tempo(unsafe { NonZeroU16::new_unchecked(2) })
|
||||
)),
|
||||
modifier::<NomError>(concatcp!(Modifier::TEMPO, 2u8, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::TEMPO, 2u8, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Err(nom::Err::Error(Error::new(
|
||||
concatcp!(655353u32, SAMPLE_STR),
|
||||
ErrorKind::Digit
|
||||
))),
|
||||
modifier(concatcp!(Modifier::TEMPO, 655353u32, SAMPLE_STR))
|
||||
Modifier::parse(concatcp!(Modifier::TEMPO, 655353u32, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod quick_modifier {
|
||||
use const_format::concatcp;
|
||||
use nom::error::VerboseError;
|
||||
|
||||
use super::{NomError, FLATATOM_SAMPLE_STRING as SAMPLE_STR};
|
||||
use super::FLATATOM_SAMPLE_STRING as SAMPLE_STR;
|
||||
use super::*;
|
||||
use crate::bng::score::{
|
||||
lex::{DOWN, OFF, ON, UP},
|
||||
quick_modifier, QuickModifier,
|
||||
lex::{lexer::Parse, DOWN, OFF, ON, UP},
|
||||
QuickModifier,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn volume() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Volume(UP))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::VOLUME.0, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::VOLUME.0, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Volume(DOWN))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::VOLUME.1, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::VOLUME.1, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -304,11 +277,11 @@ mod quick_modifier {
|
|||
fn octave() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Octave(UP))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::OCTAVE.0, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::OCTAVE.0, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Octave(DOWN))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::OCTAVE.1, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::OCTAVE.1, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -316,11 +289,11 @@ mod quick_modifier {
|
|||
fn length() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Length(UP))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Length(DOWN))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::LENGTH.1, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::LENGTH.1, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -328,22 +301,20 @@ mod quick_modifier {
|
|||
fn pizz() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Pizz(ON))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::PIZZ.0, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::PIZZ.0, SAMPLE_STR))
|
||||
);
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, QuickModifier::Pizz(OFF))),
|
||||
quick_modifier::<NomError>(concatcp!(QuickModifier::PIZZ.1, SAMPLE_STR))
|
||||
QuickModifier::parse(concatcp!(QuickModifier::PIZZ.1, SAMPLE_STR))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod slope_modifier {
|
||||
use const_format::concatcp;
|
||||
use nom::error::VerboseError;
|
||||
|
||||
use super::{NomError, FLATATOM_FASTEVAL_INSTRUCTION as INSTRUCTION};
|
||||
use crate::bng::score::{slope_modifier, Atom, SlopeModifier};
|
||||
use super::FLATATOM_FASTEVAL_INSTRUCTION as INSTRUCTION;
|
||||
use super::*;
|
||||
use crate::bng::score::{lex::lexer::Parse, Atom, SlopeModifier};
|
||||
|
||||
const SAMPLE_STR: &str = concatcp!(' ', INSTRUCTION, Atom::SLOPE.1);
|
||||
|
||||
|
@ -351,7 +322,7 @@ mod slope_modifier {
|
|||
fn note() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, SlopeModifier::Note)),
|
||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::NOTE, SAMPLE_STR))
|
||||
SlopeModifier::parse(concatcp!(SlopeModifier::NOTE, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -359,7 +330,7 @@ mod slope_modifier {
|
|||
fn volume() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, SlopeModifier::Volume)),
|
||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::VOLUME, SAMPLE_STR))
|
||||
SlopeModifier::parse(concatcp!(SlopeModifier::VOLUME, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -367,7 +338,7 @@ mod slope_modifier {
|
|||
fn octave() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, SlopeModifier::Octave)),
|
||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::OCTAVE, SAMPLE_STR))
|
||||
SlopeModifier::parse(concatcp!(SlopeModifier::OCTAVE, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -375,7 +346,7 @@ mod slope_modifier {
|
|||
fn length() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, SlopeModifier::Length)),
|
||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::LENGTH, SAMPLE_STR))
|
||||
SlopeModifier::parse(concatcp!(SlopeModifier::LENGTH, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -383,7 +354,7 @@ mod slope_modifier {
|
|||
fn tempo() {
|
||||
assert_eq!(
|
||||
Ok((SAMPLE_STR, SlopeModifier::Tempo)),
|
||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::TEMPO, SAMPLE_STR))
|
||||
SlopeModifier::parse(concatcp!(SlopeModifier::TEMPO, SAMPLE_STR))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
236
src/bng/score/utils.rs
Normal file
236
src/bng/score/utils.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
use super::*;
|
||||
use strum::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, EnumDiscriminants)]
|
||||
#[strum_discriminants(derive(Display))]
|
||||
pub enum Wrapper {
|
||||
Loop(NonZeroU8),
|
||||
Tuple,
|
||||
Slope(SlopeModifier, Instruction),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum InflateError {
|
||||
#[error("misplaced {0} end symbol")]
|
||||
MismatchedEnd(WrapperDiscriminants),
|
||||
}
|
||||
|
||||
pub fn inflate(mut flat_atoms: Vec<FlatAtom>) -> Result<Vec<Atom>, InflateError> {
|
||||
type Error = InflateError;
|
||||
let mut result = Vec::with_capacity(flat_atoms.len());
|
||||
let mut loop_stack: Vec<Vec<Atom>> = Vec::new();
|
||||
let mut tuple_stack: Vec<Vec<Atom>> = Vec::new();
|
||||
let mut slope_stack: Vec<Vec<Atom>> = Vec::new();
|
||||
let mut stack_history: Vec<Wrapper> = Vec::new();
|
||||
for mut atom in flat_atoms.into_iter() {
|
||||
#[cfg(test)]
|
||||
{
|
||||
dbg!(&atom);
|
||||
dbg!(&loop_stack);
|
||||
dbg!(&tuple_stack);
|
||||
dbg!(&slope_stack);
|
||||
dbg!(&stack_history);
|
||||
}
|
||||
match stack_history.last().map(WrapperDiscriminants::from) {
|
||||
Some(WrapperDiscriminants::Loop) => match atom {
|
||||
FlatAtom::Note(n) => {
|
||||
unsafe { loop_stack.last_mut().unwrap_unchecked() }.push(Atom::Note(n))
|
||||
}
|
||||
FlatAtom::Rest => {
|
||||
unsafe { loop_stack.last_mut().unwrap_unchecked() }.push(Atom::Rest)
|
||||
}
|
||||
FlatAtom::StartHere => {
|
||||
unsafe { loop_stack.last_mut().unwrap_unchecked() }.push(Atom::StartHere)
|
||||
}
|
||||
FlatAtom::Modifier(m) => {
|
||||
unsafe { loop_stack.last_mut().unwrap_unchecked() }.push(Atom::Modifier(m))
|
||||
}
|
||||
FlatAtom::QuickModifier(q) => {
|
||||
unsafe { loop_stack.last_mut().unwrap_unchecked() }.push(Atom::QuickModifier(q))
|
||||
}
|
||||
FlatAtom::LoopStarts(n) => {
|
||||
loop_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Loop(n));
|
||||
}
|
||||
FlatAtom::LoopEnds => {
|
||||
let popped = unsafe { loop_stack.pop().unwrap_unchecked() };
|
||||
if stack_history.len() > 1 {
|
||||
match WrapperDiscriminants::from(
|
||||
stack_history.get(stack_history.len() - 2).unwrap(),
|
||||
) {
|
||||
WrapperDiscriminants::Loop => &mut loop_stack,
|
||||
WrapperDiscriminants::Tuple => &mut tuple_stack,
|
||||
WrapperDiscriminants::Slope => &mut slope_stack,
|
||||
}
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.push(Atom::Loop(
|
||||
match stack_history.pop().unwrap() {
|
||||
Wrapper::Loop(n) => n,
|
||||
_ => unreachable!("this one is proven to be a loop"),
|
||||
},
|
||||
popped,
|
||||
))
|
||||
} else {
|
||||
result.push(Atom::Loop(
|
||||
match stack_history.pop().unwrap() {
|
||||
Wrapper::Loop(n) => n,
|
||||
_ => unreachable!("this one is proven to be a loop"),
|
||||
},
|
||||
popped,
|
||||
))
|
||||
}
|
||||
}
|
||||
FlatAtom::TupleStarts => {
|
||||
tuple_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Tuple);
|
||||
}
|
||||
FlatAtom::TupleEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Tuple));
|
||||
}
|
||||
FlatAtom::SlopeStarts(s, i) => {
|
||||
slope_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Slope(s, i));
|
||||
}
|
||||
FlatAtom::SlopeEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Slope));
|
||||
}
|
||||
FlatAtom::Comment => loop_stack.last_mut().unwrap().push(Atom::Comment),
|
||||
},
|
||||
Some(WrapperDiscriminants::Tuple) => match atom {
|
||||
FlatAtom::Note(n) => tuple_stack.last_mut().unwrap().push(Atom::Note(n)),
|
||||
FlatAtom::Rest => tuple_stack.last_mut().unwrap().push(Atom::Rest),
|
||||
FlatAtom::StartHere => tuple_stack.last_mut().unwrap().push(Atom::StartHere),
|
||||
FlatAtom::Modifier(m) => tuple_stack.last_mut().unwrap().push(Atom::Modifier(m)),
|
||||
FlatAtom::QuickModifier(q) => {
|
||||
tuple_stack.last_mut().unwrap().push(Atom::QuickModifier(q))
|
||||
}
|
||||
FlatAtom::LoopStarts(n) => {
|
||||
loop_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Loop(n));
|
||||
}
|
||||
FlatAtom::LoopEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Loop));
|
||||
}
|
||||
FlatAtom::TupleStarts => {
|
||||
tuple_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Tuple);
|
||||
}
|
||||
FlatAtom::TupleEnds => {
|
||||
let popped = tuple_stack.pop().unwrap();
|
||||
if stack_history.len() > 1 {
|
||||
match WrapperDiscriminants::from(
|
||||
stack_history.get(stack_history.len() - 2).unwrap(),
|
||||
) {
|
||||
WrapperDiscriminants::Loop => &mut loop_stack,
|
||||
WrapperDiscriminants::Tuple => &mut tuple_stack,
|
||||
WrapperDiscriminants::Slope => &mut slope_stack,
|
||||
}
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.push({
|
||||
stack_history.pop();
|
||||
Atom::Tuple(popped)
|
||||
})
|
||||
} else {
|
||||
result.push(Atom::Tuple(popped))
|
||||
}
|
||||
}
|
||||
FlatAtom::SlopeStarts(s, i) => {
|
||||
slope_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Slope(s, i));
|
||||
}
|
||||
FlatAtom::SlopeEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Slope));
|
||||
}
|
||||
FlatAtom::Comment => tuple_stack.last_mut().unwrap().push(Atom::Comment),
|
||||
},
|
||||
Some(WrapperDiscriminants::Slope) => match atom {
|
||||
FlatAtom::Note(n) => slope_stack.last_mut().unwrap().push(Atom::Note(n)),
|
||||
FlatAtom::Rest => slope_stack.last_mut().unwrap().push(Atom::Rest),
|
||||
FlatAtom::StartHere => slope_stack.last_mut().unwrap().push(Atom::StartHere),
|
||||
FlatAtom::Modifier(m) => slope_stack.last_mut().unwrap().push(Atom::Modifier(m)),
|
||||
FlatAtom::QuickModifier(q) => {
|
||||
slope_stack.last_mut().unwrap().push(Atom::QuickModifier(q))
|
||||
}
|
||||
FlatAtom::LoopStarts(n) => {
|
||||
loop_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Loop(n));
|
||||
}
|
||||
FlatAtom::LoopEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Loop));
|
||||
}
|
||||
FlatAtom::TupleStarts => {
|
||||
tuple_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Tuple);
|
||||
}
|
||||
FlatAtom::TupleEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Tuple));
|
||||
}
|
||||
FlatAtom::SlopeStarts(s, i) => {
|
||||
slope_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Slope(s, i));
|
||||
}
|
||||
FlatAtom::SlopeEnds => {
|
||||
let popped = slope_stack.pop().unwrap();
|
||||
if stack_history.len() > 1 {
|
||||
match WrapperDiscriminants::from(
|
||||
stack_history.get(stack_history.len() - 2).unwrap(),
|
||||
) {
|
||||
WrapperDiscriminants::Loop => &mut loop_stack,
|
||||
WrapperDiscriminants::Tuple => &mut tuple_stack,
|
||||
WrapperDiscriminants::Slope => &mut slope_stack,
|
||||
}
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.push(match stack_history.pop().unwrap() {
|
||||
Wrapper::Slope(m, i) => Atom::Slope(m, i, popped),
|
||||
_ => unreachable!("this one is proven to be a slope"),
|
||||
})
|
||||
} else {
|
||||
result.push(match stack_history.pop().unwrap() {
|
||||
Wrapper::Slope(m, i) => Atom::Slope(m, i, popped),
|
||||
_ => unreachable!("this one is proven to be a slope"),
|
||||
})
|
||||
}
|
||||
}
|
||||
FlatAtom::Comment => slope_stack.last_mut().unwrap().push(Atom::Comment),
|
||||
},
|
||||
None => match atom {
|
||||
FlatAtom::Note(n) => result.push(Atom::Note(n)),
|
||||
FlatAtom::Rest => result.push(Atom::Rest),
|
||||
FlatAtom::StartHere => result.push(Atom::StartHere),
|
||||
FlatAtom::Modifier(m) => result.push(Atom::Modifier(m)),
|
||||
FlatAtom::QuickModifier(q) => result.push(Atom::QuickModifier(q)),
|
||||
FlatAtom::LoopStarts(n) => {
|
||||
loop_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Loop(n));
|
||||
}
|
||||
FlatAtom::LoopEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Loop));
|
||||
}
|
||||
FlatAtom::TupleStarts => {
|
||||
tuple_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Tuple);
|
||||
}
|
||||
FlatAtom::TupleEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Tuple));
|
||||
}
|
||||
FlatAtom::SlopeStarts(s, i) => {
|
||||
slope_stack.push(Vec::new());
|
||||
stack_history.push(Wrapper::Slope(s, i));
|
||||
}
|
||||
FlatAtom::SlopeEnds => {
|
||||
return Err(Error::MismatchedEnd(WrapperDiscriminants::Slope));
|
||||
}
|
||||
FlatAtom::Comment => result.push(Atom::Comment),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
206
src/bng/score/utils/tests.rs
Normal file
206
src/bng/score/utils/tests.rs
Normal file
|
@ -0,0 +1,206 @@
|
|||
#[cfg(test)]
|
||||
mod inflate {
|
||||
use fasteval::Compiler;
|
||||
use lex::{ON, UP};
|
||||
|
||||
use super::{super::*, inflate};
|
||||
|
||||
const FASTEVAL_INSTRUCTION: &str = "1-cos((PI*x)/2)";
|
||||
|
||||
fn instruction() -> Instruction {
|
||||
FASTEVAL_INSTRUCTION.parse().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_flat() {
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
Atom::Note(2),
|
||||
Atom::Rest,
|
||||
Atom::StartHere,
|
||||
Atom::Modifier(Modifier::Volume(2)),
|
||||
Atom::QuickModifier(QuickModifier::Volume(UP)),
|
||||
Atom::Comment
|
||||
]),
|
||||
inflate(vec![
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Rest,
|
||||
FlatAtom::StartHere,
|
||||
FlatAtom::Modifier(Modifier::Volume(2)),
|
||||
FlatAtom::QuickModifier(QuickModifier::Volume(UP)),
|
||||
FlatAtom::Comment
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_loop_l1() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Loop(
|
||||
unsafe { NonZeroU8::new_unchecked(3) },
|
||||
vec![Atom::Note(2), Atom::Note(3)]
|
||||
)]),
|
||||
inflate(vec![
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) }),
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::LoopEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_tuple_l1() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Tuple(vec![Atom::Note(2), Atom::Note(3)])]),
|
||||
inflate(vec![
|
||||
FlatAtom::TupleStarts,
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::TupleEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_slope_l1() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Slope(
|
||||
SlopeModifier::Note,
|
||||
instruction(),
|
||||
vec![Atom::Note(2), Atom::Note(3)]
|
||||
)]),
|
||||
inflate(vec![
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Note, instruction()),
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::SlopeEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_loop_l2() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Loop(
|
||||
unsafe { NonZeroU8::new_unchecked(2) },
|
||||
vec![Atom::Loop(
|
||||
unsafe { NonZeroU8::new_unchecked(3) },
|
||||
vec![Atom::Note(2), Atom::Note(3)]
|
||||
)]
|
||||
)]),
|
||||
inflate(vec![
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(2) }),
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(3) }),
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::LoopEnds,
|
||||
FlatAtom::LoopEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_tuple_l2() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Tuple(vec![Atom::Tuple(vec![
|
||||
Atom::Note(2),
|
||||
Atom::Note(3)
|
||||
])])]),
|
||||
inflate(vec![
|
||||
FlatAtom::TupleStarts,
|
||||
FlatAtom::TupleStarts,
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::TupleEnds,
|
||||
FlatAtom::TupleEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflate_slope_l2() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Slope(
|
||||
SlopeModifier::Note,
|
||||
instruction(),
|
||||
vec![Atom::Slope(
|
||||
SlopeModifier::Length,
|
||||
instruction(),
|
||||
vec![Atom::Note(2), Atom::Note(3)]
|
||||
)]
|
||||
)]),
|
||||
inflate(vec![
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Note, instruction()),
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Length, instruction()),
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::SlopeEnds,
|
||||
FlatAtom::SlopeEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed() {
|
||||
assert_eq!(
|
||||
Ok(vec![Atom::Slope(
|
||||
SlopeModifier::Note,
|
||||
instruction(),
|
||||
vec![Atom::Slope(
|
||||
SlopeModifier::Length,
|
||||
instruction(),
|
||||
vec![
|
||||
Atom::Note(2),
|
||||
Atom::Tuple(vec![Atom::Rest, Atom::Note(6)]),
|
||||
Atom::Note(3),
|
||||
Atom::Loop(
|
||||
unsafe { NonZeroU8::new_unchecked(9) },
|
||||
vec![Atom::QuickModifier(QuickModifier::Pizz(ON)), Atom::Note(0)]
|
||||
)
|
||||
]
|
||||
)]
|
||||
)]),
|
||||
inflate(vec![
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Note, instruction()),
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Length, instruction()),
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::TupleStarts,
|
||||
FlatAtom::Rest,
|
||||
FlatAtom::Note(6),
|
||||
FlatAtom::TupleEnds,
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(9) }),
|
||||
FlatAtom::QuickModifier(QuickModifier::Pizz(ON)),
|
||||
FlatAtom::Note(0),
|
||||
FlatAtom::LoopEnds,
|
||||
FlatAtom::SlopeEnds,
|
||||
FlatAtom::SlopeEnds
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_mismatched_end() {
|
||||
assert_eq!(
|
||||
Err(InflateError::MismatchedEnd(WrapperDiscriminants::Slope)),
|
||||
inflate(vec![
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Note, instruction()),
|
||||
FlatAtom::SlopeStarts(SlopeModifier::Length, instruction()),
|
||||
FlatAtom::Note(2),
|
||||
FlatAtom::TupleStarts,
|
||||
FlatAtom::Rest,
|
||||
FlatAtom::SlopeEnds, // mismatched slope end while in a tuple
|
||||
FlatAtom::Note(6),
|
||||
FlatAtom::TupleEnds,
|
||||
FlatAtom::Note(3),
|
||||
FlatAtom::LoopStarts(unsafe { NonZeroU8::new_unchecked(9) }),
|
||||
FlatAtom::QuickModifier(QuickModifier::Pizz(ON)),
|
||||
FlatAtom::Note(0),
|
||||
FlatAtom::LoopEnds,
|
||||
FlatAtom::SlopeEnds,
|
||||
FlatAtom::SlopeEnds,
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
37
src/cli.rs
37
src/cli.rs
|
@ -1,10 +1,7 @@
|
|||
use std::{convert::Infallible, fmt::Display, fs::read_to_string, io, str::FromStr};
|
||||
use std::{fmt::Display, fs::read_to_string, io, str::FromStr};
|
||||
|
||||
use amplify::{From, Wrapper};
|
||||
use clap::Parser;
|
||||
use strum::EnumString;
|
||||
|
||||
pub mod doc;
|
||||
|
||||
/// Cli entry point
|
||||
#[derive(Clone, Parser)]
|
||||
|
@ -12,9 +9,9 @@ pub mod doc;
|
|||
pub enum BngCli {
|
||||
/// Play the song through default sink
|
||||
Play(PlayOpts),
|
||||
/// Export the song to a sound file
|
||||
/// Export the song to a sound FileContents
|
||||
Export(ExportOpts),
|
||||
/// List supported sound file extensions and instrument / song available expressions
|
||||
/// List supported sound FileContents extensions and instrument / song available expressions
|
||||
#[command(subcommand)]
|
||||
List(ListOpts),
|
||||
}
|
||||
|
@ -31,10 +28,10 @@ pub struct PlayOpts {
|
|||
#[derive(Clone, Parser)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||
pub struct ExportOpts {
|
||||
/// Input file (written song file)
|
||||
/// Input FileContents (written song FileContents)
|
||||
#[arg(value_parser = FileContents::from_str)]
|
||||
input: FileContents,
|
||||
/// Output file (sound file)
|
||||
/// Output FileContents (sound FileContents)
|
||||
#[arg(value_parser = AudioFileName::from_str)]
|
||||
output: AudioFileName,
|
||||
}
|
||||
|
@ -43,29 +40,17 @@ pub struct ExportOpts {
|
|||
#[derive(Clone, Parser)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||
pub enum ListOpts {
|
||||
/// List supported sound file extensions to export songs
|
||||
/// List supported sound FileContents extensions to export songs
|
||||
#[command(subcommand)]
|
||||
Extensions,
|
||||
/// Show the math syntax used for instrument definition and slopes
|
||||
Math {
|
||||
/// Kind of syntax you want to list (functions, operators or literals)
|
||||
#[arg(value_parser = MathDocKind::from_str)]
|
||||
kind: Option<MathDocKind>,
|
||||
},
|
||||
/// List available math expressions for instrument definition
|
||||
#[command(subcommand)]
|
||||
Math,
|
||||
/// List available score glyphs and their meaning
|
||||
#[command(subcommand)]
|
||||
Glyphs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, EnumString)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||
pub enum MathDocKind {
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Functions,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Operators,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Literals,
|
||||
}
|
||||
|
||||
#[derive(Clone, Wrapper, From)]
|
||||
#[wrapper(Deref)]
|
||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
pub mod math {
|
||||
pub const FUNCTIONS: &str = r#"* print(...strings and values...) -- Prints to stderr. Very useful to 'probe' an expression.
|
||||
Evaluates to the last value.
|
||||
Example: `print("x is", x, "and y is", y)`
|
||||
Example: `x + print("y:", y) + z == x+y+z`
|
||||
|
||||
* log(base=10, val) -- Logarithm with optional 'base' as first argument.
|
||||
If not provided, 'base' defaults to '10'.
|
||||
Example: `log(100) + log(e(), 100)`
|
||||
|
||||
* e() -- Euler's number (2.718281828459045)
|
||||
* pi() -- π (3.141592653589793)
|
||||
|
||||
* int(val)
|
||||
* ceil(val)
|
||||
* floor(val)
|
||||
* round(modulus=1, val) -- Round with optional 'modulus' as first argument.
|
||||
Example: `round(1.23456) == 1 && round(0.001, 1.23456) == 1.235`
|
||||
|
||||
* abs(val)
|
||||
* sign(val)
|
||||
|
||||
* min(val, ...) -- Example: `min(1, -2, 3, -4) == -4`
|
||||
* max(val, ...) -- Example: `max(1, -2, 3, -4) == 3`
|
||||
|
||||
* sin(radians) * asin(val)
|
||||
* cos(radians) * acos(val)
|
||||
* tan(radians) * atan(val)
|
||||
* sinh(val) * asinh(val)
|
||||
* cosh(val) * acosh(val)
|
||||
* tanh(val) * atanh(val)"#;
|
||||
pub const OPERATORS: &str = r#"Listed in order of precedence:
|
||||
|
||||
(Highest Precedence) ^ Exponentiation
|
||||
% Modulo
|
||||
/ Division
|
||||
* Multiplication
|
||||
- Subtraction
|
||||
+ Addition
|
||||
== != < <= >= > Comparisons (all have equal precedence)
|
||||
&& and Logical AND with short-circuit
|
||||
(Lowest Precedence) || or Logical OR with short-circuit"#;
|
||||
pub const LITERALS: &str = r#"Several numeric formats are supported:
|
||||
|
||||
Integers: 1, 2, 10, 100, 1001
|
||||
|
||||
Decimals: 1.0, 1.23456, 0.000001
|
||||
|
||||
Exponents: 1e3, 1E3, 1e-3, 1E-3, 1.2345e100
|
||||
|
||||
Suffix:
|
||||
1.23p = 0.00000000000123
|
||||
1.23n = 0.00000000123
|
||||
1.23µ, 1.23u = 0.00000123
|
||||
1.23m = 0.00123
|
||||
1.23K, 1.23k = 1230
|
||||
1.23M = 1230000
|
||||
1.23G = 1230000000
|
||||
1.23T = 1230000000000"#;
|
||||
}
|
||||
|
||||
pub mod glyphs {
|
||||
pub const ALL: &str = r#"rest: .
|
||||
pizz.: '°
|
||||
volume: +-
|
||||
length: /\
|
||||
octave: ><
|
||||
comment?: ;,
|
||||
start here: ':'
|
||||
slope: {{MODIFIER EXPR score}}
|
||||
note modifier prefix: n
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t
|
||||
loop: ()
|
||||
loop with count: (COUNT score)
|
||||
tuple: []
|
||||
modifier: !
|
||||
volume modifier prefix: v
|
||||
octave modifier prefix: o
|
||||
length modifier prefix: l
|
||||
tempo modifier prefix: t
|
||||
|
||||
EXAMPLE
|
||||
aabc.
|
||||
'ab°A
|
||||
+d+d+d---
|
||||
/ff/f\\
|
||||
ab>c<ba
|
||||
;this is a comment (or lyrics whatever),
|
||||
C:CC
|
||||
(3deff)
|
||||
(deff)
|
||||
[ffe]
|
||||
{{l 1-cos((PI*x)/2),acced}}
|
||||
abbc!o5cc!v15feed!l4fedd!t60Gdd"#;
|
||||
}
|
47
src/main.rs
47
src/main.rs
|
@ -4,7 +4,7 @@ use anyhow::Error;
|
|||
use bng::{BngFile, Channel, Expression, Instrument};
|
||||
/// TODO: remove clap, use only a file or standard in
|
||||
use clap::Parser;
|
||||
use cli::{doc, BngCli as Cli, ListOpts, MathDocKind, PlayOpts};
|
||||
use cli::{BngCli as Cli, PlayOpts};
|
||||
use fasteval::Compiler;
|
||||
|
||||
mod bng;
|
||||
|
@ -24,27 +24,30 @@ fn main() -> Result<(), Error> {
|
|||
#[cfg(debug_assertions)]
|
||||
println!("{:#?}", bng_file);
|
||||
}
|
||||
Cli::List(l) => match l {
|
||||
ListOpts::Extensions => println!("you can't export songs yet"),
|
||||
ListOpts::Math { kind } => match kind {
|
||||
None => println!(
|
||||
"Functions :\n{}\n\nOperators :\n{}\n\nLiterals :\n{}",
|
||||
doc::math::FUNCTIONS,
|
||||
doc::math::OPERATORS,
|
||||
doc::math::LITERALS
|
||||
),
|
||||
Some(m) => println!(
|
||||
"{}",
|
||||
match m {
|
||||
MathDocKind::Functions => doc::math::FUNCTIONS,
|
||||
MathDocKind::Operators => doc::math::OPERATORS,
|
||||
MathDocKind::Literals => doc::math::LITERALS,
|
||||
}
|
||||
),
|
||||
},
|
||||
ListOpts::Glyphs => println!("{}", doc::glyphs::ALL),
|
||||
},
|
||||
Cli::Export(_) => unimplemented!("can't do that yet"),
|
||||
_ => unimplemented!("can't do that yet"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn bngfile_generator() -> BngFile {
|
||||
BngFile::new(
|
||||
HashMap::from([
|
||||
(
|
||||
"sine".to_string(),
|
||||
Instrument::new("sin(2*PI*f*t)".parse().unwrap(), None),
|
||||
),
|
||||
(
|
||||
"square".to_string(),
|
||||
Instrument::new(
|
||||
"v*abs(sin(2*PI*f*t))".parse().unwrap(),
|
||||
Some(HashMap::from([("v".to_string(), 1f32)])),
|
||||
),
|
||||
),
|
||||
]),
|
||||
HashMap::<String, Channel>::from([(
|
||||
"melody".to_string(),
|
||||
Channel::new("sine".to_string(), vec![].into()),
|
||||
)]),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue