cleanup
This commit is contained in:
parent
3fdb495a3b
commit
40f3f23525
23 changed files with 0 additions and 1930 deletions
37
Cargo.toml
37
Cargo.toml
|
@ -1,37 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "blip"
|
|
||||||
version = "0.0.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
amplify = "4"
|
|
||||||
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_yml = "0.0.12"
|
|
||||||
splines = "4"
|
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
|
||||||
strum_macros = "0.26"
|
|
||||||
tinyaudio = { version = "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"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["play", "save"]
|
|
||||||
play = ["dep:tinyaudio"]
|
|
||||||
save = []
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = ["bng_macros"]
|
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
unused = "allow"
|
|
|
@ -1,13 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "bng_macros"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
inflector = "0.11.4"
|
|
||||||
proc-macro2 = "1.0"
|
|
||||||
quote = "1.0"
|
|
||||||
syn = { version = "2.0", features = ["full"] }
|
|
|
@ -1,183 +0,0 @@
|
||||||
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};
|
|
||||||
|
|
||||||
#[proc_macro_derive(SlopeModifierParser)]
|
|
||||||
pub fn slope_modifier_parser(input: TokenStream) -> TokenStream {
|
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
|
||||||
impl_slope_modifier_parser(ast)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(QuickModifierParser)]
|
|
||||||
pub fn quick_modifier_parser(input: TokenStream) -> TokenStream {
|
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
|
||||||
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();
|
|
||||||
quote! {
|
|
||||||
nom::error::context(
|
|
||||||
stringify!(#variant_name_lower),
|
|
||||||
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",
|
|
||||||
nom::branch::alt((
|
|
||||||
#(#match_arms),*
|
|
||||||
))
|
|
||||||
)(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
panic!("this macro only works on enums")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
#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",
|
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} 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()
|
|
||||||
} else {
|
|
||||||
panic!("this macro only works on enums")
|
|
||||||
}
|
|
||||||
}
|
|
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,
|
|
43
src/bng.rs
43
src/bng.rs
|
@ -1,43 +0,0 @@
|
||||||
use std::{collections::HashMap, str::FromStr};
|
|
||||||
|
|
||||||
use amplify::{From, Wrapper};
|
|
||||||
use derive_new::new;
|
|
||||||
use derived_deref::Deref;
|
|
||||||
use fasteval::{Compiler, Instruction};
|
|
||||||
pub(super) use instrument::Instrument;
|
|
||||||
pub(super) use score::Atom;
|
|
||||||
use score::Atoms;
|
|
||||||
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);
|
|
||||||
|
|
||||||
#[derive(new, Deserialize)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(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)]
|
|
||||||
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 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use super::Expression as Instruction;
|
|
||||||
use derive_new::new;
|
|
||||||
use derived_deref::Deref;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deref, new, Deserialize)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub struct Instrument {
|
|
||||||
#[target]
|
|
||||||
expr: Instruction,
|
|
||||||
vars: Option<HashMap<String, f64>>,
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
use std::{
|
|
||||||
error::Error,
|
|
||||||
num::{NonZeroU16, NonZeroU8},
|
|
||||||
};
|
|
||||||
|
|
||||||
use amplify::From;
|
|
||||||
use anyhow::Context;
|
|
||||||
use bng_macros::{ModifierParser, QuickModifierParser, SlopeModifierParser};
|
|
||||||
use derive_new::new;
|
|
||||||
use derived_deref::Deref;
|
|
||||||
use nom::{
|
|
||||||
branch::alt,
|
|
||||||
character::{complete::char, streaming::one_of},
|
|
||||||
combinator::{all_consuming, eof},
|
|
||||||
multi::many0,
|
|
||||||
sequence::{preceded, terminated},
|
|
||||||
};
|
|
||||||
use serde::{
|
|
||||||
de::{self as serde_de, Visitor},
|
|
||||||
Deserialize,
|
|
||||||
};
|
|
||||||
use strum::EnumDiscriminants;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
mod de;
|
|
||||||
mod lex;
|
|
||||||
pub use de::*;
|
|
||||||
|
|
||||||
use super::Expression as Instruction;
|
|
||||||
|
|
||||||
#[derive(Deref, From, Default, Clone)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
|
||||||
pub struct Atoms(Vec<Atom>);
|
|
||||||
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
|
||||||
pub enum Atom {
|
|
||||||
Note(u8),
|
|
||||||
Rest,
|
|
||||||
StartHere,
|
|
||||||
Modifier(Modifier),
|
|
||||||
QuickModifier(QuickModifier),
|
|
||||||
Loop(NonZeroU8, Atoms),
|
|
||||||
Tuple(Atoms),
|
|
||||||
Slope(SlopeModifier, Instruction, Atoms),
|
|
||||||
Comment,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Atom {
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, ModifierParser)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
|
||||||
pub enum Modifier {
|
|
||||||
Volume(u8),
|
|
||||||
Octave(u8),
|
|
||||||
Length(NonZeroU8),
|
|
||||||
Tempo(NonZeroU16),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Modifier {
|
|
||||||
fn default() -> Self {
|
|
||||||
Modifier::Volume(Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(QuickModifierParser, Clone)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
|
||||||
pub enum QuickModifier {
|
|
||||||
Volume(bool),
|
|
||||||
Octave(bool),
|
|
||||||
Length(bool),
|
|
||||||
Pizz(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, SlopeModifierParser, Debug)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(PartialEq))]
|
|
||||||
pub enum SlopeModifier {
|
|
||||||
Note,
|
|
||||||
Volume,
|
|
||||||
Octave,
|
|
||||||
Length,
|
|
||||||
Tempo,
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
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,
|
|
||||||
};
|
|
||||||
use serde::{
|
|
||||||
de::{self, Deserializer, Visitor},
|
|
||||||
Deserialize,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::bng::score::lex::lexer::{root, set_notes};
|
|
||||||
|
|
||||||
use super::{Atom, Atoms};
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Atoms {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(field_identifier, rename_all = "lowercase")]
|
|
||||||
enum Field {
|
|
||||||
Notes,
|
|
||||||
Sheet,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, new)]
|
|
||||||
struct NotesSheet {
|
|
||||||
notes: String,
|
|
||||||
sheet: String,
|
|
||||||
}
|
|
||||||
let NotesSheet { notes, sheet } = NotesSheet::deserialize(deserializer)?;
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pretty_verbose_err<I>(input: I, e: nom::Err<VerboseError<I>>) -> String
|
|
||||||
where
|
|
||||||
I: Deref<Target = 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),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
use super::{Atom, Modifier, QuickModifier, SlopeModifier};
|
|
||||||
|
|
||||||
pub(super) mod lexer;
|
|
||||||
|
|
||||||
pub(super) const ON: bool = true;
|
|
||||||
pub(super) const OFF: bool = false;
|
|
||||||
pub(super) const UP: bool = true;
|
|
||||||
pub(super) const DOWN: bool = false;
|
|
||||||
|
|
||||||
struct WrappingTokens;
|
|
||||||
impl WrappingTokens {
|
|
||||||
const PARENTHESES: (char, char) = ('(', ')');
|
|
||||||
const SQUARE_BRACKETS: (char, char) = ('[', ']');
|
|
||||||
const BRACKETS: (char, char) = ('{', '}');
|
|
||||||
const SEMICOLON_COMMA: (char, char) = (';', ',');
|
|
||||||
const PLUS_MINUS: (char, char) = ('+', '-');
|
|
||||||
const RIGHT_LEFT: (char, char) = ('>', '<');
|
|
||||||
const SLASH_BACKSLASH: (char, char) = ('/', '\\');
|
|
||||||
const QUOTE_DEG: (char, char) = ('\'', '°');
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuickModifier {
|
|
||||||
pub(super) const VOLUME: (char, char) = WrappingTokens::PLUS_MINUS;
|
|
||||||
pub(super) const OCTAVE: (char, char) = WrappingTokens::RIGHT_LEFT;
|
|
||||||
pub(super) const LENGTH: (char, char) = WrappingTokens::SLASH_BACKSLASH;
|
|
||||||
pub(super) const PIZZ: (char, char) = WrappingTokens::QUOTE_DEG;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Modifier {
|
|
||||||
pub(super) const VOLUME: char = 'v';
|
|
||||||
pub(super) const OCTAVE: char = 'o';
|
|
||||||
pub(super) const LENGTH: char = 'l';
|
|
||||||
pub(super) const TEMPO: char = 't';
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SlopeModifier {
|
|
||||||
pub(super) const NOTE: char = 'n';
|
|
||||||
pub(super) const VOLUME: char = 'v';
|
|
||||||
pub(super) const OCTAVE: char = 'o';
|
|
||||||
pub(super) const LENGTH: char = 'l';
|
|
||||||
pub(super) const TEMPO: char = 't';
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Atom {
|
|
||||||
pub(super) const REST: char = '.';
|
|
||||||
pub(super) const START_HERE: char = ':';
|
|
||||||
pub(super) const MODIFIER: char = '!';
|
|
||||||
pub(super) const LOOP: (char, char) = WrappingTokens::PARENTHESES;
|
|
||||||
pub(super) const TUPLE: (char, char) = WrappingTokens::SQUARE_BRACKETS;
|
|
||||||
pub(super) const SLOPE: (char, char) = WrappingTokens::BRACKETS;
|
|
||||||
pub(super) const COMMENT: (char, char) = WrappingTokens::SEMICOLON_COMMA;
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::BTreeMap,
|
|
||||||
num::{NonZeroU16, NonZeroU8, TryFromIntError},
|
|
||||||
sync::{Mutex, MutexGuard, PoisonError},
|
|
||||||
};
|
|
||||||
|
|
||||||
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},
|
|
||||||
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},
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref NOTES: Mutex<Option<String>> = Mutex::new(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
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!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
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(
|
|
||||||
separated_pair(
|
|
||||||
slope_modifier,
|
|
||||||
char(' '),
|
|
||||||
map_res(take_till1(|c| c == ','), |s: &str| {
|
|
||||||
s.parse()
|
|
||||||
.map_err(|_| E::from_error_kind(s, 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",
|
|
||||||
value(
|
|
||||||
Atom::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)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)(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,389 +0,0 @@
|
||||||
use const_format::concatcp;
|
|
||||||
use nom::{
|
|
||||||
error::{Error, ErrorKind, VerboseError},
|
|
||||||
Err,
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub(super) const SAMPLE_STR: &str = concatcp!(
|
|
||||||
Atom::TUPLE.0,
|
|
||||||
"acc",
|
|
||||||
Atom::TUPLE.1,
|
|
||||||
"ed",
|
|
||||||
Atom::COMMENT.0,
|
|
||||||
"hello",
|
|
||||||
Atom::COMMENT.1
|
|
||||||
);
|
|
||||||
|
|
||||||
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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rest() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, Atom::Rest)),
|
|
||||||
atom::<NomError>.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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn modifier() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::Modifier(Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) }))
|
|
||||||
)),
|
|
||||||
atom::<NomError>.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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn r#loop() {
|
|
||||||
set_notes();
|
|
||||||
assert_eq!(
|
|
||||||
Ok((
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::Loop(unsafe { NonZeroU8::new_unchecked(3) }, sample_str_expr())
|
|
||||||
)),
|
|
||||||
atom::<NomError>.parse(concatcp!(
|
|
||||||
Atom::LOOP.0,
|
|
||||||
3u8,
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::LOOP.1,
|
|
||||||
SAMPLE_STR
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Ok((
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::Loop(unsafe { NonZeroU8::new_unchecked(2) }, sample_str_expr())
|
|
||||||
)),
|
|
||||||
atom::<NomError>.parse(concatcp!(
|
|
||||||
Atom::LOOP.0,
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::LOOP.1,
|
|
||||||
SAMPLE_STR
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Err(nom::Err::Failure(Error::new(
|
|
||||||
concatcp!(0u8, SAMPLE_STR),
|
|
||||||
ErrorKind::MapOpt
|
|
||||||
))),
|
|
||||||
atom.parse(concatcp!(Atom::LOOP.0, 0u8, SAMPLE_STR))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tuple() {
|
|
||||||
set_notes();
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, Atom::Tuple(sample_str_expr()))),
|
|
||||||
atom::<NomError>.parse(concatcp!(
|
|
||||||
Atom::TUPLE.0,
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::TUPLE.1,
|
|
||||||
SAMPLE_STR
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn slope() {
|
|
||||||
set_notes();
|
|
||||||
assert_eq!(
|
|
||||||
Ok((
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::Slope(
|
|
||||||
SlopeModifier::Note,
|
|
||||||
FASTEVAL_INSTRUCTION.parse().unwrap(),
|
|
||||||
sample_str_expr()
|
|
||||||
)
|
|
||||||
)),
|
|
||||||
atom::<NomError>.parse(concatcp!(
|
|
||||||
Atom::SLOPE.0,
|
|
||||||
SlopeModifier::NOTE,
|
|
||||||
' ',
|
|
||||||
FASTEVAL_INSTRUCTION,
|
|
||||||
',',
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::SLOPE.1,
|
|
||||||
SAMPLE_STR
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn comment() {
|
|
||||||
set_notes();
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, Atom::Comment)),
|
|
||||||
atom::<NomError>.parse(concatcp!(
|
|
||||||
Atom::COMMENT.0,
|
|
||||||
"hi I'm a little pony",
|
|
||||||
SAMPLE_STR,
|
|
||||||
Atom::COMMENT.1,
|
|
||||||
SAMPLE_STR
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn volume() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, Modifier::Volume(2))),
|
|
||||||
modifier::<NomError>(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))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn octave() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, Modifier::Octave(2))),
|
|
||||||
modifier::<NomError>(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))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn length() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((
|
|
||||||
SAMPLE_STR,
|
|
||||||
Modifier::Length(unsafe { NonZeroU8::new_unchecked(2) })
|
|
||||||
)),
|
|
||||||
modifier::<NomError>(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))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Err(nom::Err::Error(Error::new(
|
|
||||||
concatcp!(Modifier::LENGTH, 0u8, SAMPLE_STR),
|
|
||||||
ErrorKind::Char
|
|
||||||
))),
|
|
||||||
modifier(concatcp!(Modifier::LENGTH, 0u8, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tempo() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((
|
|
||||||
SAMPLE_STR,
|
|
||||||
Modifier::Tempo(unsafe { NonZeroU16::new_unchecked(2) })
|
|
||||||
)),
|
|
||||||
modifier::<NomError>(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))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod quick_modifier {
|
|
||||||
use const_format::concatcp;
|
|
||||||
use nom::error::VerboseError;
|
|
||||||
|
|
||||||
use super::{NomError, FLATATOM_SAMPLE_STRING as SAMPLE_STR};
|
|
||||||
use crate::bng::score::{
|
|
||||||
lex::{DOWN, OFF, ON, UP},
|
|
||||||
quick_modifier, QuickModifier,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn volume() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Volume(UP))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::VOLUME.0, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Volume(DOWN))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::VOLUME.1, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn octave() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Octave(UP))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::OCTAVE.0, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Octave(DOWN))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::OCTAVE.1, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn length() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Length(UP))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::LENGTH.0, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Length(DOWN))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::LENGTH.1, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pizz() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Pizz(ON))),
|
|
||||||
quick_modifier::<NomError>(concatcp!(QuickModifier::PIZZ.0, SAMPLE_STR))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, QuickModifier::Pizz(OFF))),
|
|
||||||
quick_modifier::<NomError>(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};
|
|
||||||
|
|
||||||
const SAMPLE_STR: &str = concatcp!(' ', INSTRUCTION, Atom::SLOPE.1);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn note() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, SlopeModifier::Note)),
|
|
||||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::NOTE, SAMPLE_STR))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn volume() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, SlopeModifier::Volume)),
|
|
||||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::VOLUME, SAMPLE_STR))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn octave() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, SlopeModifier::Octave)),
|
|
||||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::OCTAVE, SAMPLE_STR))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn length() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, SlopeModifier::Length)),
|
|
||||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::LENGTH, SAMPLE_STR))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tempo() {
|
|
||||||
assert_eq!(
|
|
||||||
Ok((SAMPLE_STR, SlopeModifier::Tempo)),
|
|
||||||
slope_modifier::<NomError>(concatcp!(SlopeModifier::TEMPO, SAMPLE_STR))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
105
src/cli.rs
105
src/cli.rs
|
@ -1,105 +0,0 @@
|
||||||
use std::{convert::Infallible, 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)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub enum BngCli {
|
|
||||||
/// Play the song through default sink
|
|
||||||
Play(PlayOpts),
|
|
||||||
/// Export the song to a sound file
|
|
||||||
Export(ExportOpts),
|
|
||||||
/// List supported sound file extensions and instrument / song available expressions
|
|
||||||
#[command(subcommand)]
|
|
||||||
List(ListOpts),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`BngCli`] "play" command options
|
|
||||||
#[derive(Clone, Parser)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub struct PlayOpts {
|
|
||||||
#[arg(value_parser = FileContents::from_str)]
|
|
||||||
pub(super) input: FileContents,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`BngCli`] "export" command options
|
|
||||||
#[derive(Clone, Parser)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub struct ExportOpts {
|
|
||||||
/// Input file (written song file)
|
|
||||||
#[arg(value_parser = FileContents::from_str)]
|
|
||||||
input: FileContents,
|
|
||||||
/// Output file (sound file)
|
|
||||||
#[arg(value_parser = AudioFileName::from_str)]
|
|
||||||
output: AudioFileName,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`BngCli`] "list" command sub-commands
|
|
||||||
#[derive(Clone, Parser)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub enum ListOpts {
|
|
||||||
/// List supported sound file extensions to export songs
|
|
||||||
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 score glyphs and their meaning
|
|
||||||
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))]
|
|
||||||
pub struct FileContents(String);
|
|
||||||
|
|
||||||
impl FromStr for FileContents {
|
|
||||||
type Err = io::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
read_to_string(s).map(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Wrapper, From)]
|
|
||||||
#[wrapper(Deref)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub struct AudioFileName(String);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UnsupportedFileExtensionError;
|
|
||||||
|
|
||||||
impl std::error::Error for UnsupportedFileExtensionError {}
|
|
||||||
|
|
||||||
impl Display for UnsupportedFileExtensionError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"The extension of the selected output sound file is not supported."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for AudioFileName {
|
|
||||||
type Err = UnsupportedFileExtensionError;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(s.to_owned().into())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"#;
|
|
||||||
}
|
|
50
src/main.rs
50
src/main.rs
|
@ -1,50 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
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 fasteval::Compiler;
|
|
||||||
|
|
||||||
mod bng;
|
|
||||||
mod cli;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
|
||||||
// println!("{}", option_env!("TEST").unwrap_or("ok"));
|
|
||||||
let args = Cli::parse();
|
|
||||||
// #[cfg(debug_assertions)]
|
|
||||||
// {
|
|
||||||
// println!("{}", serde_yml::to_string(&bngfile_generator())?);
|
|
||||||
// println!("{:?}", args);
|
|
||||||
// }
|
|
||||||
match args {
|
|
||||||
Cli::Play(PlayOpts { input }) => {
|
|
||||||
let bng_file: BngFile = serde_yml::from_str(&input)?;
|
|
||||||
#[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"),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue