Compare commits

..

3 commits

Author SHA1 Message Date
9ed583e4e4
bng syntax rules & poc 2024-10-02 11:22:47 -04:00
903e3ebc64
adapt cli to new yml system 2024-10-02 11:21:58 -04:00
6f62bb8f40
add deps 2024-10-02 11:18:26 -04:00
6 changed files with 193 additions and 9 deletions

View file

@ -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 }

44
poc/poc.yml Normal file
View file

@ -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<ba
;this is a comment (or lyrics whatever),
s:df
(3deff)
(deff)
[ffe]
{1-cos((PI*x)/2),acced}
abbc!o5cc!v15feed!l4fedd!t60hdd
# rest: .
# pizz.: '°
# volume: +-
# length: /\
# octave: ><
# 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

View file

@ -0,0 +1,42 @@
use fasteval::Instruction;
mod lex;
pub(super) enum Atom {
Note(u8),
Rest,
StartHere,
Modifier(Modifier),
QuickModifier(QuickModifier),
Wrapper(WrapperKind, Vec<Atom>),
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,
}

50
src/bng/score/lex.rs Normal file
View file

@ -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<T> {
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<char> for Modifier {
fn token(self) -> char {
match self {
Self::Volume(_) => 'v',
Self::Octave(_) => 'o',
Self::Length(_) => 'l',
Self::Tempo(_) => 't',
}
}
}
impl Token<char> for Atom {
fn token(self) -> char {
match self {
Atom::Rest => '.',
Atom::StartHere => ':',
Atom::Modifier(_) => '!',
_ => unimplemented!("not a singleton"),
}
}
}

View file

@ -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<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())
}
}

View file

@ -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);