Compare commits
No commits in common. "a82579dfa525ec3f94d0a49ddd7ea74cae4bcade" and "9c6f76fd6e498a2a2a10293ea6c639db1137fa8a" have entirely different histories.
a82579dfa5
...
9c6f76fd6e
7 changed files with 111 additions and 233 deletions
68
poc/poc.yml
68
poc/poc.yml
|
@ -14,38 +14,36 @@ channels:
|
||||||
melody:
|
melody:
|
||||||
instr: sine
|
instr: sine
|
||||||
score:
|
score:
|
||||||
notes: cCdDefFgGaAb
|
aabc.
|
||||||
sheet:
|
'rt°y
|
||||||
aabc.
|
+d+d+d---
|
||||||
'rt°y
|
/ff/f\\
|
||||||
+d+d+d---
|
ab>c<ba
|
||||||
/ff/f\\
|
;this is a comment (or lyrics whatever),
|
||||||
ab>c<ba
|
s:df
|
||||||
;this is a comment (or lyrics whatever),
|
(3deff)
|
||||||
s:df
|
(deff)
|
||||||
(3deff)
|
[ffe]
|
||||||
(deff)
|
{l 1-cos((PI*x)/2),acced}
|
||||||
[ffe]
|
abbc!o5cc!v15feed!l4fedd!t60hdd
|
||||||
{l 1-cos((PI*x)/2),acced}
|
# rest: .
|
||||||
abbc!o5cc!v15feed!l4fedd!t60hdd
|
# pizz.: '°
|
||||||
# rest: .
|
# volume: +-
|
||||||
# pizz.: '°
|
# length: /\
|
||||||
# volume: +-
|
# octave: ><
|
||||||
# length: /\
|
# comment?: ;,
|
||||||
# octave: ><
|
# start here: ':'
|
||||||
# comment?: ;,
|
# slope: {MODIFIER EXPR, score}
|
||||||
# start here: ':'
|
# note modifier prefix: n
|
||||||
# slope: {MODIFIER EXPR, score}
|
# volume modifier prefix: v
|
||||||
# note modifier prefix: n
|
# octave modifier prefix: o
|
||||||
# volume modifier prefix: v
|
# length modifier prefix: l
|
||||||
# octave modifier prefix: o
|
# tempo modifier prefix: t
|
||||||
# length modifier prefix: l
|
# loop: ()
|
||||||
# tempo modifier prefix: t
|
# loop with count: (COUNT, score)
|
||||||
# loop: ()
|
# tuple: []
|
||||||
# loop with count: (COUNT, score)
|
# modifier: !
|
||||||
# tuple: []
|
# volume modifier prefix: v
|
||||||
# modifier: !
|
# octave modifier prefix: o
|
||||||
# volume modifier prefix: v
|
# length modifier prefix: l
|
||||||
# octave modifier prefix: o
|
# tempo modifier prefix: t
|
||||||
# length modifier prefix: l
|
|
||||||
# tempo modifier prefix: t
|
|
||||||
|
|
12
src/bng.rs
12
src/bng.rs
|
@ -6,7 +6,6 @@ use derived_deref::Deref;
|
||||||
use fasteval::{Compiler, Instruction};
|
use fasteval::{Compiler, Instruction};
|
||||||
pub(super) use instrument::Instrument;
|
pub(super) use instrument::Instrument;
|
||||||
pub(super) use score::Atom;
|
pub(super) use score::Atom;
|
||||||
use score::Atoms;
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::{de::Visitor, Deserialize};
|
use serde::{de::Visitor, Deserialize};
|
||||||
|
@ -66,16 +65,15 @@ impl FromStr for Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(new, Deserialize)]
|
#[derive(new)]
|
||||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
#[cfg_attr(debug_assertions, derive(Serialize))]
|
||||||
pub(super) struct Channel {
|
pub(super) struct Channel {
|
||||||
instr: String,
|
instr: String,
|
||||||
score: Atoms,
|
score: Vec<Atom>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[cfg_attr(debug_assertions, derive(new, Serialize))]
|
||||||
#[cfg_attr(debug_assertions, derive(new, Debug, Serialize))]
|
|
||||||
pub(super) struct BngFile {
|
pub(super) struct BngFile {
|
||||||
instruments: HashMap<String, Instrument>,
|
instruments: Vec<HashMap<String, Instrument>>,
|
||||||
channels: HashMap<String, Channel>,
|
channels: HashMap<String, Channel>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,13 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use derived_deref::Deref;
|
use derived_deref::Deref;
|
||||||
use serde::Deserialize;
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::Expression as Instruction;
|
use super::Expression as Instruction;
|
||||||
|
|
||||||
#[derive(Deref, new, Deserialize)]
|
#[derive(Deref, new)]
|
||||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
#[cfg_attr(debug_assertions, derive(Serialize))]
|
||||||
pub struct Instrument {
|
pub struct Instrument {
|
||||||
#[target]
|
#[target]
|
||||||
expr: Instruction,
|
expr: Instruction,
|
||||||
|
|
101
src/bng/score.rs
101
src/bng/score.rs
|
@ -1,117 +1,16 @@
|
||||||
use std::num::{NonZeroU16, NonZeroU8};
|
use std::num::{NonZeroU16, NonZeroU8};
|
||||||
|
|
||||||
use amplify::From;
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bng_macros::{QuickModifierParser, SlopeModifierParser};
|
use bng_macros::{QuickModifierParser, SlopeModifierParser};
|
||||||
use derive_new::new;
|
|
||||||
use derived_deref::Deref;
|
|
||||||
use lex::lexer::flat_atom_parser;
|
|
||||||
use nom::multi::many0;
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::{
|
|
||||||
de::{self, Visitor},
|
|
||||||
Deserialize,
|
|
||||||
};
|
|
||||||
use strum::EnumDiscriminants;
|
use strum::EnumDiscriminants;
|
||||||
use thiserror::Error;
|
|
||||||
use utils::{inflate, InflateError};
|
|
||||||
|
|
||||||
mod lex;
|
mod lex;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use super::Expression as Instruction;
|
use super::Expression as Instruction;
|
||||||
|
|
||||||
#[derive(Deref, From)]
|
|
||||||
#[cfg_attr(debug_assertions, derive(Serialize, Debug))]
|
|
||||||
pub struct Atoms(Vec<Atom>);
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
enum AtomsSerializeError {
|
|
||||||
#[error("error while parsing with nom")]
|
|
||||||
Parsing(String),
|
|
||||||
#[error("error while inflating flat atoms")]
|
|
||||||
Inflation(#[from] InflateError),
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
struct NotesSheetVisitor;
|
|
||||||
impl<'de> Visitor<'de> for NotesSheetVisitor {
|
|
||||||
type Value = NotesSheet;
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a \"notes\" field and a \"sheet\" field")
|
|
||||||
}
|
|
||||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: serde::de::SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let notes = seq
|
|
||||||
.next_element()?
|
|
||||||
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
|
||||||
let sheet = seq
|
|
||||||
.next_element()?
|
|
||||||
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
|
||||||
Ok(NotesSheet::new(notes, sheet))
|
|
||||||
}
|
|
||||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: de::MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut notes = None;
|
|
||||||
let mut sheet = None;
|
|
||||||
while let Some(key) = map.next_key()? {
|
|
||||||
match key {
|
|
||||||
Field::Notes => {
|
|
||||||
if notes.is_some() {
|
|
||||||
return Err(de::Error::duplicate_field("notes"));
|
|
||||||
}
|
|
||||||
notes = Some(map.next_value()?);
|
|
||||||
}
|
|
||||||
Field::Sheet => {
|
|
||||||
if sheet.is_some() {
|
|
||||||
return Err(de::Error::duplicate_field("sheet"));
|
|
||||||
}
|
|
||||||
sheet = Some(map.next_value()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let notes = notes.ok_or_else(|| de::Error::missing_field("notes"))?;
|
|
||||||
let sheet = sheet.ok_or_else(|| de::Error::missing_field("sheet"))?;
|
|
||||||
Ok(NotesSheet::new(notes, sheet))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const FIELDS: &[&str] = &["notes", "sheet"];
|
|
||||||
let NotesSheet { notes, sheet } =
|
|
||||||
deserializer.deserialize_struct("NotesSheet", FIELDS, NotesSheetVisitor)?;
|
|
||||||
let x = many0(flat_atom_parser(¬es))(&sheet)
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
.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);
|
|
||||||
x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
#[cfg_attr(debug_assertions, derive(Debug, PartialEq))]
|
||||||
pub enum Atom {
|
pub enum Atom {
|
||||||
Note(u8),
|
Note(u8),
|
||||||
|
|
|
@ -8,7 +8,7 @@ use fasteval::Compiler;
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{take_till, take_till1},
|
bytes::complete::{take_till, take_till1},
|
||||||
character::complete::{anychar, char, one_of, space1, u16, u8},
|
character::complete::{anychar, char, one_of, u16, u8},
|
||||||
combinator::{map_opt, map_res, opt, value, verify},
|
combinator::{map_opt, map_res, opt, value, verify},
|
||||||
multi::many0,
|
multi::many0,
|
||||||
sequence::{delimited, pair, preceded, separated_pair, terminated},
|
sequence::{delimited, pair, preceded, separated_pair, terminated},
|
||||||
|
@ -38,54 +38,50 @@ impl Parse for Modifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flat_atom_parser(notes: &str) -> impl Parser<&str, FlatAtom, nom::error::Error<&str>> {
|
fn flat_atom_parser(notes: &str) -> impl Parser<&str, FlatAtom, nom::error::Error<&str>> {
|
||||||
preceded(
|
alt((
|
||||||
many0(alt((char('\t'), char(' '), char('\n'), char('\r')))),
|
map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note),
|
||||||
alt((
|
value(FlatAtom::Rest, char(Atom::REST)),
|
||||||
map_res(map_opt(one_of(notes), |c| notes.find(c)), u8::try_from).map(FlatAtom::Note),
|
value(FlatAtom::StartHere, char(Atom::START_HERE)),
|
||||||
value(FlatAtom::Rest, char(Atom::REST)),
|
preceded(char(Atom::MODIFIER), Modifier::parse).map(FlatAtom::Modifier),
|
||||||
value(FlatAtom::StartHere, char(Atom::START_HERE)),
|
QuickModifier::parse.map(FlatAtom::QuickModifier),
|
||||||
preceded(char(Atom::MODIFIER), Modifier::parse).map(FlatAtom::Modifier),
|
preceded(
|
||||||
QuickModifier::parse.map(FlatAtom::QuickModifier),
|
char(Atom::LOOP.0),
|
||||||
|
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(
|
preceded(
|
||||||
char(Atom::LOOP.0),
|
char(Atom::SLOPE.0),
|
||||||
map_opt(opt(u8), |n| {
|
separated_pair(
|
||||||
if let Some(n) = n {
|
SlopeModifier::parse,
|
||||||
NonZeroU8::new(n)
|
char(' '),
|
||||||
} else {
|
map_res(take_till1(|c| c == ','), |s: &str| {
|
||||||
unsafe { Some(NonZeroU8::new_unchecked(2)) }
|
s.parse()
|
||||||
}
|
.map_err(|_| nom::error::Error::new(s, nom::error::ErrorKind::Verify))
|
||||||
}),
|
}),
|
||||||
)
|
|
||||||
.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(
|
|
||||||
SlopeModifier::parse,
|
|
||||||
char(' '),
|
|
||||||
map_res(take_till1(|c| c == ','), |s: &str| {
|
|
||||||
s.parse().map_err(|_| {
|
|
||||||
nom::error::Error::new(s, nom::error::ErrorKind::Verify)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
char(','),
|
|
||||||
)
|
|
||||||
.map(|(sm, i)| FlatAtom::SlopeStarts(sm, i)),
|
|
||||||
value(FlatAtom::SlopeEnds, char(Atom::SLOPE.1)),
|
|
||||||
value(
|
|
||||||
FlatAtom::Comment,
|
|
||||||
delimited(
|
|
||||||
char(Atom::COMMENT.0),
|
|
||||||
take_till(|c| c == Atom::COMMENT.1),
|
|
||||||
char(Atom::COMMENT.1),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
char(','),
|
||||||
)
|
)
|
||||||
|
.map(|(sm, i)| FlatAtom::SlopeStarts(sm, i)),
|
||||||
|
value(FlatAtom::SlopeEnds, char(Atom::SLOPE.1)),
|
||||||
|
value(
|
||||||
|
FlatAtom::Comment,
|
||||||
|
delimited(
|
||||||
|
char(Atom::COMMENT.0),
|
||||||
|
take_till(|c| c == Atom::COMMENT.1),
|
||||||
|
char(Atom::COMMENT.1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod tests;
|
||||||
|
|
||||||
#[derive(Debug, EnumDiscriminants)]
|
#[derive(Debug, EnumDiscriminants)]
|
||||||
#[strum_discriminants(derive(Display))]
|
#[strum_discriminants(derive(Display))]
|
||||||
pub enum Wrapper {
|
enum Wrapper {
|
||||||
Loop(NonZeroU8),
|
Loop(NonZeroU8),
|
||||||
Tuple,
|
Tuple,
|
||||||
Slope(SlopeModifier, Instruction),
|
Slope(SlopeModifier, Instruction),
|
||||||
|
@ -15,12 +15,12 @@ pub enum Wrapper {
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub enum InflateError {
|
enum InflateError {
|
||||||
#[error("misplaced {0} end symbol")]
|
#[error("misplaced {0} end symbol")]
|
||||||
MismatchedEnd(WrapperDiscriminants),
|
MismatchedEnd(WrapperDiscriminants),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inflate(mut flat_atoms: Vec<FlatAtom>) -> Result<Vec<Atom>, InflateError> {
|
fn inflate(mut flat_atoms: Vec<FlatAtom>) -> Result<Vec<Atom>, InflateError> {
|
||||||
type Error = InflateError;
|
type Error = InflateError;
|
||||||
let mut result = Vec::with_capacity(flat_atoms.len());
|
let mut result = Vec::with_capacity(flat_atoms.len());
|
||||||
let mut loop_stack: Vec<Vec<Atom>> = Vec::new();
|
let mut loop_stack: Vec<Vec<Atom>> = Vec::new();
|
||||||
|
|
60
src/main.rs
60
src/main.rs
|
@ -12,41 +12,29 @@ mod cli;
|
||||||
fn main() -> Result<(), serde_yml::Error> {
|
fn main() -> Result<(), serde_yml::Error> {
|
||||||
// println!("{}", option_env!("TEST").unwrap_or("ok"));
|
// println!("{}", option_env!("TEST").unwrap_or("ok"));
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
// #[cfg(debug_assertions)]
|
println!(
|
||||||
// {
|
"{}",
|
||||||
// println!("{}", serde_yml::to_string(&bngfile_generator())?);
|
serde_yml::to_string(&BngFile::new(
|
||||||
// println!("{:?}", args);
|
vec![HashMap::from([
|
||||||
// }
|
(
|
||||||
match args {
|
"sine".to_string(),
|
||||||
Cli::Play(PlayOpts { input }) => {
|
Instrument::new("sin(2*PI*f*t)".parse().unwrap(), None)
|
||||||
let bng_file: BngFile = serde_yml::from_str(&input)?;
|
),
|
||||||
#[cfg(debug_assertions)]
|
(
|
||||||
println!("{:?}", bng_file);
|
"square".to_string(),
|
||||||
}
|
Instrument::new(
|
||||||
_ => unimplemented!("can't do that yet"),
|
"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![])
|
||||||
|
)])
|
||||||
|
))?
|
||||||
|
);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
println!("{:?}", args);
|
||||||
Ok(())
|
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…
Add table
Add a link
Reference in a new issue