diff --git a/Cargo.lock b/Cargo.lock index da42fa6..a52f6cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,125 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "midly" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a" +dependencies = [ + "rayon", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -24,9 +137,99 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "poly" version = "0.1.0" dependencies = [ + "derive_more", + "midly", "nom", ] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +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" diff --git a/Cargo.toml b/Cargo.toml index ea9babf..5074ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,5 @@ path = "src/bin/main.rs" [dependencies] nom = "*" +midly = "0.5.3" +derive_more = "*" \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs index 7465690..5c5d93d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,4 +1,5 @@ use poly::dsl; +use poly::midi; fn main() { println!("Hello, world!"); diff --git a/src/dsl/dsl.rs b/src/dsl/dsl.rs index 8ba5eb0..24cf1e2 100644 --- a/src/dsl/dsl.rs +++ b/src/dsl/dsl.rs @@ -6,7 +6,7 @@ use nom::multi::many1; use nom::sequence::{separated_pair, tuple, delimited}; use nom::{Err, IResult}; use nom::branch::alt; -use nom::bytes::complete::tag; + use nom::combinator::{map, map_res}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -40,7 +40,7 @@ pub enum Note { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Times(u16); +pub struct Times(pub u16); #[derive(Debug, Clone, PartialEq, Eq)] pub enum GroupOrNote { @@ -82,6 +82,7 @@ static ONCE : Times = Times(1); static TWICE: Times = Times(2); static THRICE : Times = Times(3); + fn hit(input: &str) -> IResult<&str, Note> { map(char('x'), |_| { Note::Hit })(input) } diff --git a/src/dsl/mod.rs b/src/dsl/mod.rs index 6366b56..8d642ce 100644 --- a/src/dsl/mod.rs +++ b/src/dsl/mod.rs @@ -1 +1,2 @@ -pub mod dsl; \ No newline at end of file +pub mod dsl; +pub mod process; \ No newline at end of file diff --git a/src/dsl/process.rs b/src/dsl/process.rs new file mode 100644 index 0000000..8e00641 --- /dev/null +++ b/src/dsl/process.rs @@ -0,0 +1,6 @@ +use super::dsl::{BasicLength}; + +pub struct TimeSignature { + numerator: u8, + denominator: BasicLength +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6366b56..785913c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ -pub mod dsl; \ No newline at end of file +pub mod dsl; +pub mod midi; \ No newline at end of file diff --git a/src/midi/core.rs b/src/midi/core.rs new file mode 100644 index 0000000..50e9287 --- /dev/null +++ b/src/midi/core.rs @@ -0,0 +1,110 @@ +extern crate derive_more; +use derive_more::{Mul, Add}; +use midly::{Smf, Header, live::LiveEvent, MidiMessage, num::u15}; +use std::convert::{TryFrom}; + +use crate::dsl::dsl::{Group, Length, ModdedLength, BasicLength}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct FlatNote { + // measured in ticks (128th notes), so it's easy to align to a midi grid + length: u8 +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Add, Mul)] +#[repr(transparent)] +pub struct Tick(pub u128); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum EventType { + NoteOn, + NoteOff, + Tempo, + Signature +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Event { + tick: Tick, + event_type: EventType +} + +// Events are supposed to be sorted. +pub type EventGrid = Vec; + +static TICKS_PER_QUARTER_NOTE : u16 = 48; + +fn basic_length_to_ticks(basic_length: BasicLength) -> Tick { + match basic_length { + BasicLength::Whole => Tick((TICKS_PER_QUARTER_NOTE * 4) as u128), + BasicLength::Half => Tick((TICKS_PER_QUARTER_NOTE * 2) as u128), + BasicLength::Fourth => Tick(TICKS_PER_QUARTER_NOTE as u128), + BasicLength::Eighth => Tick((TICKS_PER_QUARTER_NOTE / 2) as u128), + BasicLength::Sixteenth => Tick((TICKS_PER_QUARTER_NOTE / 4) as u128), + BasicLength::ThirtySecond => Tick((TICKS_PER_QUARTER_NOTE / 8) as u128), + BasicLength::SixtyFourth => Tick((TICKS_PER_QUARTER_NOTE / 16) as u128), + } +} + +fn modded_length_to_ticks(modded_length: ModdedLength) -> Tick { + match modded_length { + ModdedLength::Plain(blen) => basic_length_to_ticks(blen), + ModdedLength::Dotted(blen) => { + let Tick(whole) = basic_length_to_ticks(blen); + let half = whole / 2; + Tick(whole + half) + } + } +} + +fn length_to_ticks(length: Length) -> Tick { + match length { + Length::Simple(mlen) => modded_length_to_ticks(mlen), + Length::Tied(first, second) => modded_length_to_ticks(first) + modded_length_to_ticks(second), + Length::Triplet(mlen) => { + let Tick(straight) = modded_length_to_ticks(mlen); + let triplet = straight * 2 / 3; + Tick(triplet) + } + } +} + +fn flatten_group(Group { notes, length, times }: Group, start: Tick) -> EventGrid { + let mut time = start; + 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::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 }; + grid.push(note_on); + grid.push(note_off); + time = note_end; + }, + }; + } + grid.repeat(times.0 as usize) +} + +fn flatten_groups(groups: Vec) -> EventGrid { + let mut out : EventGrid = Vec::new(); + groups.iter().flat_map(|Group { notes, length, times }| { + + }).collect() +} + + +// 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 + // 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())); + Smf { header: Header { format: midly::Format::Parallel, timing: metrical }, tracks: tracks } +} \ No newline at end of file diff --git a/src/midi/mod.rs b/src/midi/mod.rs new file mode 100644 index 0000000..689cad3 --- /dev/null +++ b/src/midi/mod.rs @@ -0,0 +1 @@ +pub mod core; \ No newline at end of file