From 6f62bb8f402c8fcafc4472473f2c5119b4eb337e Mon Sep 17 00:00:00 2001 From: p6nj Date: Wed, 2 Oct 2024 11:18:26 -0400 Subject: [PATCH 1/3] add deps --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 535c67b..10353de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] +amplify = "4.7.0" anyhow = "1.0" cfg-if = "1.0.0" clap = { version = "4.5", features = ["derive"] } derived-deref = "2.1.0" fasteval = "0.2.4" +nom = "7.1.3" +serde = "1.0.209" +serde_yml = "0.0.12" splines = "4.3.1" tinyaudio = { version = "0.1", optional = true } From 903e3ebc64956ab408714fe7f1758e89e2076af8 Mon Sep 17 00:00:00 2001 From: p6nj Date: Wed, 2 Oct 2024 11:21:58 -0400 Subject: [PATCH 2/3] adapt cli to new yml system --- src/cli.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 5057a49..1bba81e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,6 @@ -use std::path::PathBuf; +use std::{fmt::Display, fs::read_to_string, io, str::FromStr}; +use amplify::{From, Wrapper}; use clap::Parser; /// Cli entry point @@ -8,9 +9,9 @@ use clap::Parser; pub enum BngCli { /// Play the song through default sink Play(PlayOpts), - /// Export the song to a sound file + /// Export the song to a sound FileContents Export(ExportOpts), - /// List supported sound file extensions and instrument / song available expressions + /// List supported sound FileContents extensions and instrument / song available expressions #[command(subcommand)] List(ListOpts), } @@ -19,24 +20,27 @@ pub enum BngCli { #[derive(Clone, Parser)] #[cfg_attr(debug_assertions, derive(Debug))] pub struct PlayOpts { - input: PathBuf, + #[arg(value_parser = FileContents::from_str)] + input: FileContents, } /// [`BngCli`] "export" command options #[derive(Clone, Parser)] #[cfg_attr(debug_assertions, derive(Debug))] pub struct ExportOpts { - /// Input file (written song file) - input: PathBuf, - /// Output file (sound file) - output: PathBuf, + /// Input FileContents (written song FileContents) + #[arg(value_parser = FileContents::from_str)] + input: FileContents, + /// Output FileContents (sound FileContents) + #[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 + /// List supported sound FileContents extensions to export songs #[command(subcommand)] Extensions, /// List available math expressions for instrument definition @@ -46,3 +50,41 @@ pub enum ListOpts { #[command(subcommand)] Glyphs, } + +#[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 { + 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 { + Ok(s.to_owned().into()) + } +} From 9ed583e4e4b66ad674553e17ef3dcbb64eacc927 Mon Sep 17 00:00:00 2001 From: p6nj Date: Wed, 2 Oct 2024 11:22:47 -0400 Subject: [PATCH 3/3] bng syntax rules & poc --- poc/poc.yml | 44 ++++++++++++++++++++++++++++++++++++++ src/bng/score.rs | 42 +++++++++++++++++++++++++++++++++++++ src/bng/score/lex.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 ++ 4 files changed, 138 insertions(+) create mode 100644 poc/poc.yml create mode 100644 src/bng/score/lex.rs diff --git a/poc/poc.yml b/poc/poc.yml new file mode 100644 index 0000000..83e7cf7 --- /dev/null +++ b/poc/poc.yml @@ -0,0 +1,44 @@ +# fixed +instruments: + # instrument name + sine: + # fixed + expr: sin(2*PI*f*t) # instrument formula (f is the frequency in Hertz, t is the time in seconds) + square: + expr: v*abs(sin(2*PI*f*t)) + # fixed + vars: + # name of the variable + v: 1 # initial value of the variable +channels: + melody: + instr: sine + score: + aabc. + 'rt°y + +d+d+d--- + /ff/f\\ + ab>c< + # comment?: ;, + # start here: ':' + # glissando: {EXPR, score} + # 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/score.rs b/src/bng/score.rs index e69de29..26d8e95 100644 --- a/src/bng/score.rs +++ b/src/bng/score.rs @@ -0,0 +1,42 @@ +use fasteval::Instruction; + +mod lex; + +pub(super) enum Atom { + Note(u8), + Rest, + StartHere, + Modifier(Modifier), + QuickModifier(QuickModifier), + Wrapper(WrapperKind, Vec), + EmptyWrapper(WrapperKind), +} + +pub(super) enum Modifier { + Volume(u8), + Octave(u8), + Length(u8), + Tempo(u16), +} + +pub(super) enum QuickModifier { + Volume(bool), + Octave(bool), + Length(bool), + Pizz(bool), +} + +pub(super) enum WrapperKind { + Loop(u8), + Tuple, + Slope(SlopeModifier, Instruction), + Comment, +} + +pub(super) enum SlopeModifier { + Note, + Volume, + Octave, + Length, + Tempo, +} diff --git a/src/bng/score/lex.rs b/src/bng/score/lex.rs new file mode 100644 index 0000000..75ca318 --- /dev/null +++ b/src/bng/score/lex.rs @@ -0,0 +1,50 @@ +use super::{Atom, Modifier, WrapperKind}; +const MORE: bool = true; +const LESS: bool = false; +const ON: bool = true; +const OFF: bool = false; + +struct WrappingTokens; +impl WrappingTokens { + const PARENTHESES: (char, char) = ('(', ')'); + const SQUARE_BRACKETS: (char, char) = ('[', ']'); + const BRACKETS: (char, char) = ('{', '}'); + const SEMICOLON_COMMA: (char, char) = (';', ','); +} + +trait Token { + fn token(self) -> T; +} + +impl Token<(char, char)> for WrapperKind { + fn token(self) -> (char, char) { + match self { + Self::Loop(_) => WrappingTokens::PARENTHESES, + Self::Tuple => WrappingTokens::SQUARE_BRACKETS, + Self::Slope(_, _) => WrappingTokens::BRACKETS, + WrapperKind::Comment => WrappingTokens::SEMICOLON_COMMA, + } + } +} + +impl Token for Modifier { + fn token(self) -> char { + match self { + Self::Volume(_) => 'v', + Self::Octave(_) => 'o', + Self::Length(_) => 'l', + Self::Tempo(_) => 't', + } + } +} + +impl Token for Atom { + fn token(self) -> char { + match self { + Atom::Rest => '.', + Atom::StartHere => ':', + Atom::Modifier(_) => '!', + _ => unimplemented!("not a singleton"), + } + } +} diff --git a/src/main.rs b/src/main.rs index 615cdae..e06d996 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ +#![feature(unsize)] use clap::Parser; mod bng; mod cli; fn main() { + // println!("{}", option_env!("TEST").unwrap_or("ok")); let args = cli::BngCli::parse(); #[cfg(debug_assertions)] println!("{:?}", args);