diff --git a/Cargo.lock b/Cargo.lock index a52f6cf..6c696a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,127 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "convert_case" version = "0.4.0" @@ -73,7 +182,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -82,6 +191,33 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -91,12 +227,47 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "libc" version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + [[package]] name = "memchr" version = "2.5.0" @@ -143,14 +314,21 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + [[package]] name = "poly" version = "0.1.0" dependencies = [ + "clap", "derive_more", "midly", "nom", @@ -205,6 +383,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -217,6 +409,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -228,8 +426,91 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 5074ff8..650264f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ path = "src/bin/main.rs" [dependencies] nom = "*" midly = "0.5.3" -derive_more = "*" \ No newline at end of file +derive_more = "*" +clap = { version = "4.2.7", features = ["derive"] } \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs index 5c5d93d..b9359a6 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,6 +1,83 @@ -use poly::dsl; +use std::collections::HashMap; +use std::process::exit; + +use poly::dsl::dsl; use poly::midi; +use poly::midi::core::Part; + +use clap::*; + + +#[derive(Debug, Parser)] +#[command(name = "poly")] +#[command(author = "Denis Redozubov ")] +#[command(version = "0.1")] +#[command(about = "Polyrhythmically-inclinded Midi Drum generator", long_about = None)] +struct Cli { + #[arg(short = 'k', default_value = None)] + kick: Option, + + #[arg(short = 's', default_value = None)] + snare: Option, + + #[arg(short = 'h', default_value = None)] + hihat: Option, + + #[arg(short = 'c', default_value = None)] + crash: Option, + + #[arg(short = 't', default_value = "120")] + tempo: String, + + #[arg(short = 'S', default_value = "4/4")] + time_signature: String, + + #[arg(short = 'o', default_value = None)] + output: Option +} + + + +fn part_to_instrument(part: Part) -> String { + match part { + Part::KickDrum => String::from("Kick Drum"), + Part::SnareDrum => String::from("Snare Drum"), + Part::HiHat => String::from("Hi-Hat"), + Part::CrashCymbal => String::from("Crash Cymbal"), + } +} + +fn validate_and_parse_part(cli: Option, part: Part, patterns: &mut HashMap>) -> () { + match cli { + None => {}, + Some(pattern) => { + match dsl::groups(pattern.as_str()) { + Ok((_, group)) => { patterns.insert(part, group); }, + Err(e) => { + println!("{} pattern is malformed.", part_to_instrument(part)); + exit(1) + } + } + } + } +} fn main() { - println!("Hello, world!"); + let matches = Cli::parse(); + let mut drum_patterns : HashMap> = HashMap::new(); + match matches { + Cli { kick, snare, hihat, crash, tempo, time_signature, output} => { + if kick == None && snare == None && hihat == None && crash == None { + println!("No drum pattern was supplied, exiting..."); + exit(1) + } else if output == None { + println!("No output file path was supplied, running a dry run...") + } else { + validate_and_parse_part(kick, Part::KickDrum, &mut drum_patterns); + validate_and_parse_part(snare, Part::SnareDrum, &mut drum_patterns); + validate_and_parse_part(hihat, Part::HiHat, &mut drum_patterns); + validate_and_parse_part(crash, Part::CrashCymbal, &mut drum_patterns); + } + } + } } diff --git a/src/dsl/dsl.rs b/src/dsl/dsl.rs index 24cf1e2..86e4bf7 100644 --- a/src/dsl/dsl.rs +++ b/src/dsl/dsl.rs @@ -9,7 +9,7 @@ use nom::branch::alt; use nom::combinator::{map, map_res}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum BasicLength { Whole, Half, @@ -20,26 +20,26 @@ pub enum BasicLength { SixtyFourth } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ModdedLength { Plain(BasicLength), Dotted(BasicLength) } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Length { Simple(ModdedLength), Tied(ModdedLength, ModdedLength), Triplet(ModdedLength) } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Note { Hit, Rest } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Times(pub u16); #[derive(Debug, Clone, PartialEq, Eq)] @@ -49,38 +49,76 @@ pub enum GroupOrNote { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Group { notes: Vec, length: Length, times: Times } +pub struct Group { + pub notes: Vec, + pub length: Length, + pub times: Times +} -static WHOLE : Length = Length::Simple(ModdedLength::Plain(BasicLength::Whole)); -static HALF : Length = Length::Simple(ModdedLength::Plain(BasicLength::Half)); -static FOURTH : Length = Length::Simple(ModdedLength::Plain(BasicLength::Fourth)); -static EIGHTH : Length = Length::Simple(ModdedLength::Plain(BasicLength::Eighth)); -static SIXTEENTH : Length = Length::Simple(ModdedLength::Plain(BasicLength::Sixteenth)); -static THIRTY_SECOND : Length = Length::Simple(ModdedLength::Plain(BasicLength::ThirtySecond)); -static SIXTY_FOURTH : Length = Length::Simple(ModdedLength::Plain(BasicLength::SixtyFourth)); +impl std::ops::Deref for Group { + type Target = Vec; -static WHOLE_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::Whole)); -static HALF_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::Half)); -static FOURTH_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::Fourth)); -static EIGHTH_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::Eighth)); -static SIXTEENTH_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::Sixteenth)); -static THIRTY_SECOND_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::ThirtySecond)); -static SIXTY_FOURTH_DOTTED_TRIPLET : Length = Length::Triplet(ModdedLength::Dotted(BasicLength::SixtyFourth)); + fn deref(&self) -> &Self::Target { + &self.notes + } +} -static WHOLE_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::Whole)); -static HALF_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::Half)); -static FOURTH_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::Fourth)); -static EIGHTH_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::Eighth)); -static SIXTEENTH_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::Sixteenth)); -static THIRTY_SECOND_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::ThirtySecond)); -static SIXTY_FOURTH_TRIPLET : Length = Length::Triplet(ModdedLength::Plain(BasicLength::SixtyFourth)); +#[allow(dead_code)] +static WHOLE : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::Whole)); +#[allow(dead_code)] +static HALF : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::Half)); +#[allow(dead_code)] +static FOURTH : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::Fourth)); +#[allow(dead_code)] +static EIGHTH : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::Eighth)); +#[allow(dead_code)] +static SIXTEENTH : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::Sixteenth)); +#[allow(dead_code)] +static THIRTY_SECOND : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::ThirtySecond)); +#[allow(dead_code)] +static SIXTY_FOURTH : &Length = &Length::Simple(ModdedLength::Plain(BasicLength::SixtyFourth)); +#[allow(dead_code)] +static WHOLE_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::Whole)); +#[allow(dead_code)] +static HALF_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::Half)); +#[allow(dead_code)] +static FOURTH_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::Fourth)); +#[allow(dead_code)] +static EIGHTH_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::Eighth)); +#[allow(dead_code)] +static SIXTEENTH_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::Sixteenth)); +#[allow(dead_code)] +static THIRTY_SECOND_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::ThirtySecond)); +#[allow(dead_code)] +static SIXTY_FOURTH_DOTTED_TRIPLET : &Length = &Length::Triplet(ModdedLength::Dotted(BasicLength::SixtyFourth)); + +#[allow(dead_code)] +static WHOLE_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::Whole)); +#[allow(dead_code)] +static HALF_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::Half)); +#[allow(dead_code)] +static FOURTH_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::Fourth)); +#[allow(dead_code)] +static EIGHTH_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::Eighth)); +#[allow(dead_code)] +static SIXTEENTH_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::Sixteenth)); +#[allow(dead_code)] +static THIRTY_SECOND_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::ThirtySecond)); +#[allow(dead_code)] +static SIXTY_FOURTH_TRIPLET : &Length = &Length::Triplet(ModdedLength::Plain(BasicLength::SixtyFourth)); + +#[allow(dead_code)] static HIT : GroupOrNote = GroupOrNote::SingleNote(Note::Hit); +#[allow(dead_code)] static REST : GroupOrNote = GroupOrNote::SingleNote(Note::Rest); -static ONCE : Times = Times(1); -static TWICE: Times = Times(2); -static THRICE : Times = Times(3); +#[allow(dead_code)] +static ONCE : &Times = &Times(1); +#[allow(dead_code)] +static TWICE: &Times = &Times(2); +#[allow(dead_code)] +static THRICE : &Times = &Times(3); fn hit(input: &str) -> IResult<&str, Note> { @@ -104,7 +142,7 @@ fn length_basic(input: &str) -> IResult<&str, BasicLength> { Ok((r,16)) => Ok((r, BasicLength::Sixteenth)), Ok((r,32)) => Ok((r, BasicLength::ThirtySecond)), Ok((r, 64)) => Ok((r, BasicLength::SixtyFourth)), - Ok((r, i)) => { + Ok((r, _)) => { Err(Err::Error(nom::error::make_error(r, nom::error::ErrorKind::Fail))) }, Err(e) => Err(e) @@ -150,35 +188,35 @@ fn group_or_delimited_group(input: &str) -> IResult<&str, Group> { alt((delimited_group, group))(input) } -fn groups(input: &str) -> IResult<&str, Vec> { +pub fn groups(input: &str) -> IResult<&str, Vec> { many1(group_or_delimited_group)(input) } #[test] fn parse_length() { - assert_eq!(length("16"), Ok(("", SIXTEENTH.clone()))); + assert_eq!(length("16"), Ok(("", *SIXTEENTH))); assert_eq!(length("8+16"), Ok(("", Length::Tied(ModdedLength::Plain(BasicLength::Eighth), ModdedLength::Plain(BasicLength::Sixteenth))))); - assert_eq!(length("8t"), Ok(("", EIGHTH_TRIPLET.clone()))); - assert_eq!(length("4.t"), Ok(("", FOURTH_DOTTED_TRIPLET.clone()))); + assert_eq!(length("8t"), Ok(("", *EIGHTH_TRIPLET))); + assert_eq!(length("4.t"), Ok(("", *FOURTH_DOTTED_TRIPLET))); } #[test] fn parse_group() { - assert_eq!(group("16x--x-"), Ok(("", Group { times: ONCE.clone(), notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: SIXTEENTH.clone()}))); - assert_eq!(group("8txxx"), Ok(("", Group { times: ONCE.clone(), notes: vec![HIT.clone(), HIT.clone(), HIT.clone()], length: EIGHTH_TRIPLET.clone()}))); - assert_eq!(group("16+32x-xx"), Ok(("", Group { times: ONCE.clone(), notes: vec![HIT.clone(), REST.clone(), HIT.clone(), HIT.clone()], length: Length::Tied(ModdedLength::Plain(BasicLength::Sixteenth), ModdedLength::Plain(BasicLength::ThirtySecond))}))); - assert_eq!(group("3,16xx"), Ok(("", Group { times: THRICE.clone(), length: SIXTEENTH.clone(), notes: vec![HIT.clone(), HIT.clone()] }))); + assert_eq!(group("16x--x-"), Ok(("", Group { times: *ONCE, notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: *SIXTEENTH}))); + assert_eq!(group("8txxx"), Ok(("", Group { times: *ONCE, notes: vec![HIT.clone(), HIT.clone(), HIT.clone()], length: *EIGHTH_TRIPLET}))); + assert_eq!(group("16+32x-xx"), Ok(("", Group { times: *ONCE, notes: vec![HIT.clone(), REST.clone(), HIT.clone(), HIT.clone()], length: Length::Tied(ModdedLength::Plain(BasicLength::Sixteenth), ModdedLength::Plain(BasicLength::ThirtySecond))}))); + assert_eq!(group("3,16xx"), Ok(("", Group { times: *THRICE, length: *SIXTEENTH, notes: vec![HIT.clone(), HIT.clone()] }))); } #[test] fn parse_delimited_group() { - assert_eq!(delimited_group("(3,16x--x-)"), Ok(("", Group { times: THRICE.clone(), notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: SIXTEENTH.clone()}))); + assert_eq!(delimited_group("(3,16x--x-)"), Ok(("", Group { times: *THRICE, notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: *SIXTEENTH}))); } #[test] fn parse_group_or_delimited_group() { - assert_eq!(group_or_delimited_group("(3,16x--x-)"), Ok(("", Group { times: THRICE.clone(), notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: SIXTEENTH.clone()}))); - assert_eq!(group_or_delimited_group("16x--x-"), Ok(("", Group { times: ONCE.clone(), notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: SIXTEENTH.clone()}))); + assert_eq!(group_or_delimited_group("(3,16x--x-)"), Ok(("", Group { times: *THRICE, notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: *SIXTEENTH}))); + assert_eq!(group_or_delimited_group("16x--x-"), Ok(("", Group { times: *ONCE, notes: vec![HIT.clone(), REST.clone(), REST.clone(), HIT.clone(), REST.clone()], length: *SIXTEENTH}))); } // “x” hit diff --git a/src/dsl/mod.rs b/src/dsl/mod.rs index 8d642ce..6366b56 100644 --- a/src/dsl/mod.rs +++ b/src/dsl/mod.rs @@ -1,2 +1 @@ -pub mod dsl; -pub mod process; \ No newline at end of file +pub mod dsl; \ No newline at end of file diff --git a/src/dsl/process.rs b/src/dsl/process.rs deleted file mode 100644 index 8e00641..0000000 --- a/src/dsl/process.rs +++ /dev/null @@ -1,6 +0,0 @@ -use super::dsl::{BasicLength}; - -pub struct TimeSignature { - numerator: u8, - denominator: BasicLength -} \ No newline at end of file diff --git a/src/midi/core.rs b/src/midi/core.rs index 50e9287..548e685 100644 --- a/src/midi/core.rs +++ b/src/midi/core.rs @@ -1,7 +1,8 @@ extern crate derive_more; +use std::collections::HashMap; + use derive_more::{Mul, Add}; -use midly::{Smf, Header, live::LiveEvent, MidiMessage, num::u15}; -use std::convert::{TryFrom}; +use midly::{Smf, Header, live::LiveEvent, MidiMessage, num::u15, Track}; use crate::dsl::dsl::{Group, Length, ModdedLength, BasicLength}; @@ -17,10 +18,25 @@ pub struct Tick(pub u128); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum EventType { - NoteOn, - NoteOff, - Tempo, - Signature + NoteOn(Part), + NoteOff(Part), + Tempo(u8), + Signature(TimeSignature) +} + + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct TimeSignature { + pub numerator: u8, + pub denominator: BasicLength +} + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub enum Part { + KickDrum, + SnareDrum, + HiHat, + CrashCymbal } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -32,6 +48,7 @@ pub struct Event { // Events are supposed to be sorted. pub type EventGrid = Vec; +#[allow(dead_code)] static TICKS_PER_QUARTER_NOTE : u16 = 48; fn basic_length_to_ticks(basic_length: BasicLength) -> Tick { @@ -69,42 +86,66 @@ fn length_to_ticks(length: Length) -> Tick { } } -fn flatten_group(Group { notes, length, times }: Group, start: Tick) -> EventGrid { +fn flatten_group(Group { notes, length, times }: &Group, part: Part, start: &mut Tick, grid: &mut EventGrid) -> EventGrid { let mut time = start; - let note_length = length_to_ticks(length); + let note_length = length_to_ticks(*length); let mut grid = Vec::new(); - let ticks = length_to_ticks(length); for entry in notes.iter() { |entry| match entry { - crate::dsl::dsl::GroupOrNote::SingleGroup(_) => todo!(), - crate::dsl::dsl::GroupOrNote::SingleNote(Rest) => { time = time + note_length }, + crate::dsl::dsl::GroupOrNote::SingleGroup(group) => { + flatten_group(&group, part, time , &mut grid).repeat(times.0 as usize); + }, + crate::dsl::dsl::GroupOrNote::SingleNote(Rest) => { *time = *time + note_length; }, crate::dsl::dsl::GroupOrNote::SingleNote(Hit) => { - let note_end = time + note_length; - let note_on = Event { tick: time, event_type: EventType::NoteOn }; - let note_off = Event { tick: note_end, event_type: EventType::NoteOff }; + let note_end = *time + note_length; + let note_on = Event { tick: *time, event_type: EventType::NoteOn(part) }; + let note_off = Event { tick: note_end, event_type: EventType::NoteOff(part) }; grid.push(note_on); grid.push(note_off); - time = note_end; + *time = note_end; }, }; } - grid.repeat(times.0 as usize) + grid } -fn flatten_groups(groups: Vec) -> EventGrid { - let mut out : EventGrid = Vec::new(); - groups.iter().flat_map(|Group { notes, length, times }| { - - }).collect() +fn flatten_groups(part: Part, groups: Vec) -> EventGrid { + let mut time : Tick = Tick(0); + let mut grid : EventGrid = Vec::new(); + groups.iter().for_each(|group| { + flatten_group(group, part, &mut time, &mut grid); + }); + grid } +fn combine_event_grids<'a>(a: &'a mut EventGrid, b : &'a mut EventGrid) -> &'a mut EventGrid { + a.append(b); + a.sort_by(|e1, e2| { e1.tick.cmp(&e2.tick)} ); + a +} + +fn merge_event_grids(mut eg: Vec) -> EventGrid { + let first = eg.pop().unwrap(); + eg.iter_mut().fold(first, |mut acc, next| { + combine_event_grids(&mut acc, next); + acc + }) +} + +fn flatten_and_merge(groups: HashMap>) -> EventGrid { + let mut eg = Vec::new(); + for (part, group) in groups { + eg.push(flatten_groups(part, group)) + } + merge_event_grids(eg) +} // The length of a beat is not standard, so in order to fully describe the length of a MIDI tick the MetaMessage::Tempo event should be present. -fn create_smf<'a>() -> Smf<'a> { - let tracks = vec![]; // FIXME +fn create_smf<'a>(groups: HashMap>) -> Smf<'a> { + let tracks = vec![]; //create_tracks(groups); // FIXME // https://majicdesigns.github.io/MD_MIDIFile/page_timing.html // says " If it is not specified the MIDI default is 48 ticks per quarter note." // As it's required in `Header`, let's use the same value. - let metrical = midly::Timing::Metrical(u15::new(TICKS_PER_QUARTER_NOTE.clone())); + let metrical = midly::Timing::Metrical(u15::new(TICKS_PER_QUARTER_NOTE)); Smf { header: Header { format: midly::Format::Parallel, timing: metrical }, tracks: tracks } } \ No newline at end of file