WIP flattening to midi events

This commit is contained in:
Denis Redozubov 2023-05-05 18:44:28 +04:00
parent c642c72061
commit 8f84491801
9 changed files with 330 additions and 4 deletions

203
Cargo.lock generated
View File

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

View File

@ -11,3 +11,5 @@ path = "src/bin/main.rs"
[dependencies]
nom = "*"
midly = "0.5.3"
derive_more = "*"

View File

@ -1,4 +1,5 @@
use poly::dsl;
use poly::midi;
fn main() {
println!("Hello, world!");

View File

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

View File

@ -1 +1,2 @@
pub mod dsl;
pub mod dsl;
pub mod process;

6
src/dsl/process.rs Normal file
View File

@ -0,0 +1,6 @@
use super::dsl::{BasicLength};
pub struct TimeSignature {
numerator: u8,
denominator: BasicLength
}

View File

@ -1 +1,2 @@
pub mod dsl;
pub mod dsl;
pub mod midi;

110
src/midi/core.rs Normal file
View File

@ -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<Event>;
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<Group>) -> 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 }
}

1
src/midi/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod core;