From 829cb64511c443f780341eb090ab63d82930385b Mon Sep 17 00:00:00 2001 From: p6nj Date: Thu, 31 Oct 2024 00:21:24 -0400 Subject: [PATCH] serialize everything (error on the sheet type) --- poc/poc.yml | 68 +++++++++++++------------ src/bng.rs | 12 +++-- src/bng/instrument.rs | 5 +- src/bng/score.rs | 100 +++++++++++++++++++++++++++++++++++++ src/bng/score/lex/lexer.rs | 92 ++++++++++++++++++---------------- src/bng/score/utils.rs | 6 +-- src/main.rs | 60 +++++++++++++--------- 7 files changed, 232 insertions(+), 111 deletions(-) diff --git a/poc/poc.yml b/poc/poc.yml index c6f23c4..274a713 100644 --- a/poc/poc.yml +++ b/poc/poc.yml @@ -14,36 +14,38 @@ channels: melody: instr: sine score: - aabc. - 'rt°y - +d+d+d--- - /ff/f\\ - ab>c< - # 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 + notes: cCdDefFgGaAb + sheet: + aabc. + 'rt°y + +d+d+d--- + /ff/f\\ + ab>c< + # 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 diff --git a/src/bng.rs b/src/bng.rs index b4a9cd3..d3a2a8d 100644 --- a/src/bng.rs +++ b/src/bng.rs @@ -6,6 +6,7 @@ use derived_deref::Deref; 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}; @@ -65,15 +66,16 @@ impl FromStr for Expression { } } -#[derive(new)] -#[cfg_attr(debug_assertions, derive(Serialize))] +#[derive(new, Deserialize)] +#[cfg_attr(debug_assertions, derive(Serialize, Debug))] pub(super) struct Channel { instr: String, - score: Vec, + score: Atoms, } -#[cfg_attr(debug_assertions, derive(new, Serialize))] +#[derive(Deserialize)] +#[cfg_attr(debug_assertions, derive(new, Debug, Serialize))] pub(super) struct BngFile { - instruments: Vec>, + instruments: HashMap, channels: HashMap, } diff --git a/src/bng/instrument.rs b/src/bng/instrument.rs index 382a636..af5164f 100644 --- a/src/bng/instrument.rs +++ b/src/bng/instrument.rs @@ -2,13 +2,14 @@ use std::collections::HashMap; 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)] -#[cfg_attr(debug_assertions, derive(Serialize))] +#[derive(Deref, new, Deserialize)] +#[cfg_attr(debug_assertions, derive(Serialize, Debug))] pub struct Instrument { #[target] expr: Instruction, diff --git a/src/bng/score.rs b/src/bng/score.rs index 89174e8..4202844 100644 --- a/src/bng/score.rs +++ b/src/bng/score.rs @@ -1,16 +1,116 @@ use std::num::{NonZeroU16, NonZeroU8}; +use amplify::From; use anyhow::Context; 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)] use serde::Serialize; +use serde::{ + de::{self, Visitor}, + Deserialize, +}; use strum::EnumDiscriminants; +use thiserror::Error; +use utils::{inflate, InflateError}; mod lex; mod utils; use super::Expression as Instruction; +#[derive(Deref, From)] +#[cfg_attr(debug_assertions, derive(Serialize, Debug))] +pub struct Atoms(Vec); + +#[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(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "lowercase")] + enum Field { + Notes, + Sheet, + } + #[derive(Deserialize, new)] + struct NotesSheet<'a> { + notes: &'a str, + sheet: &'a str, + } + struct NotesSheetVisitor; + impl<'de> Visitor<'de> for NotesSheetVisitor { + type Value = NotesSheet<'de>; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a \"notes\" field and a \"sheet\" field") + } + fn visit_seq(self, mut seq: A) -> Result + 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(self, mut map: A) -> Result + 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)?; + many0(flat_atom_parser(notes))(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) + } +} + #[cfg_attr(debug_assertions, derive(Debug, PartialEq))] pub enum Atom { Note(u8), diff --git a/src/bng/score/lex/lexer.rs b/src/bng/score/lex/lexer.rs index acf1372..7631144 100644 --- a/src/bng/score/lex/lexer.rs +++ b/src/bng/score/lex/lexer.rs @@ -8,7 +8,7 @@ use fasteval::Compiler; use nom::{ branch::alt, bytes::complete::{take_till, take_till1}, - character::complete::{anychar, char, one_of, u16, u8}, + character::complete::{anychar, char, one_of, space1, u16, u8}, combinator::{map_opt, map_res, opt, value, verify}, multi::many0, sequence::{delimited, pair, preceded, separated_pair, terminated}, @@ -38,50 +38,54 @@ impl Parse for Modifier { } } -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), - 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( +pub fn flat_atom_parser(notes: &str) -> impl Parser<&str, FlatAtom, nom::error::Error<&str>> { + preceded( + many0(alt((char('\t'), char(' '), char('\n'), char('\r')))), + 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::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(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( + 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), - ), - ), - )) + )), + ) } diff --git a/src/bng/score/utils.rs b/src/bng/score/utils.rs index 2177b12..e7bb56d 100644 --- a/src/bng/score/utils.rs +++ b/src/bng/score/utils.rs @@ -7,7 +7,7 @@ mod tests; #[derive(Debug, EnumDiscriminants)] #[strum_discriminants(derive(Display))] -enum Wrapper { +pub enum Wrapper { Loop(NonZeroU8), Tuple, Slope(SlopeModifier, Instruction), @@ -15,12 +15,12 @@ enum Wrapper { #[derive(Debug, Error)] #[cfg_attr(test, derive(PartialEq))] -enum InflateError { +pub enum InflateError { #[error("misplaced {0} end symbol")] MismatchedEnd(WrapperDiscriminants), } -fn inflate(mut flat_atoms: Vec) -> Result, InflateError> { +pub fn inflate(mut flat_atoms: Vec) -> Result, InflateError> { type Error = InflateError; let mut result = Vec::with_capacity(flat_atoms.len()); let mut loop_stack: Vec> = Vec::new(); diff --git a/src/main.rs b/src/main.rs index 0af91ba..f87805a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,29 +12,41 @@ mod cli; fn main() -> Result<(), serde_yml::Error> { // println!("{}", option_env!("TEST").unwrap_or("ok")); let args = Cli::parse(); - println!( - "{}", - serde_yml::to_string(&BngFile::new( - vec![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::::from([( - "melody".to_string(), - Channel::new("sine".to_string(), vec![]) - )]) - ))? - ); - #[cfg(debug_assertions)] - println!("{:?}", args); + // #[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); + } + _ => 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::::from([( + "melody".to_string(), + Channel::new("sine".to_string(), vec![].into()), + )]), + ) +}