Better code

This commit is contained in:
Denis Redozubov 2023-05-09 18:25:05 +04:00
parent f1dfb31b70
commit 1d74b52cff
4 changed files with 295 additions and 57 deletions

14
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml"
]
}

View file

@ -36,9 +36,7 @@ struct Cli {
output: Option<String> output: Option<String>
} }
fn part_to_string(part: Part) -> String {
fn part_to_instrument(part: Part) -> String {
match part { match part {
Part::KickDrum => String::from("Kick Drum"), Part::KickDrum => String::from("Kick Drum"),
Part::SnareDrum => String::from("Snare Drum"), Part::SnareDrum => String::from("Snare Drum"),
@ -54,7 +52,7 @@ fn validate_and_parse_part(cli: Option<String>, part: Part, patterns: &mut HashM
match dsl::groups(pattern.as_str()) { match dsl::groups(pattern.as_str()) {
Ok((_, group)) => { patterns.insert(part, group); }, Ok((_, group)) => { patterns.insert(part, group); },
Err(e) => { Err(e) => {
println!("{} pattern is malformed.", part_to_instrument(part)); println!("{} pattern is malformed.", part_to_string(part));
exit(1) exit(1)
} }
} }
@ -77,6 +75,7 @@ fn main() {
validate_and_parse_part(snare, Part::SnareDrum, &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(hihat, Part::HiHat, &mut drum_patterns);
validate_and_parse_part(crash, Part::CrashCymbal, &mut drum_patterns); validate_and_parse_part(crash, Part::CrashCymbal, &mut drum_patterns);
} }
} }
} }

View file

@ -184,7 +184,7 @@ fn delimited_group(input: &str) -> IResult<&str, Group> {
delimited(char('('), group, char(')'))(input) delimited(char('('), group, char(')'))(input)
} }
fn group_or_delimited_group(input: &str) -> IResult<&str, Group> { pub fn group_or_delimited_group(input: &str) -> IResult<&str, Group> {
alt((delimited_group, group))(input) alt((delimited_group, group))(input)
} }

View file

@ -1,34 +1,53 @@
extern crate derive_more; extern crate derive_more;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Add;
use derive_more::{Mul, Add}; use midly::{live::LiveEvent, num::u15, Header, MidiMessage, Smf, Track};
use midly::{Smf, Header, live::LiveEvent, MidiMessage, num::u15, Track};
use crate::dsl::dsl::{Group, Length, ModdedLength, BasicLength}; use crate::dsl::dsl::{
group_or_delimited_group, groups, BasicLength, Group, Length, ModdedLength, Note, Times,
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] // Typically used as number of ticks since the beginning of the track.
pub struct FlatNote { #[derive(
// measured in ticks (128th notes), so it's easy to align to a midi grid Debug,
length: u8 Clone,
} Copy,
PartialEq,
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Add, Mul)] Eq,
PartialOrd,
Ord,
derive_more::Add,
derive_more::Mul,
derive_more::Display,
)]
#[repr(transparent)] #[repr(transparent)]
pub struct Tick(pub u128); pub struct Tick(pub u128);
#[test]
fn test_add_tick() {
assert_eq!(Tick(2) + Tick(2), Tick(4));
}
// Delta in time since the last MIDI event, measured in Ticks.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, derive_more::Add, derive_more::Mul,
)]
#[repr(transparent)]
pub struct Delta(pub u128);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum EventType { pub enum EventType {
NoteOn(Part), NoteOn(Part),
NoteOff(Part), NoteOff(Part),
Tempo(u8), Tempo(u8),
Signature(TimeSignature) Signature(TimeSignature),
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct TimeSignature { pub struct TimeSignature {
pub numerator: u8, pub numerator: u8,
pub denominator: BasicLength pub denominator: BasicLength,
} }
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
@ -36,20 +55,95 @@ pub enum Part {
KickDrum, KickDrum,
SnareDrum, SnareDrum,
HiHat, HiHat,
CrashCymbal CrashCymbal,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Event { pub struct Event<T> {
tick: Tick, tick: T,
event_type: EventType event_type: EventType,
} }
// Events are supposed to be sorted. // Events are supposed to be sorted by T at all times.
pub type EventGrid = Vec<Event>; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct EventGrid<T> {
events: Vec<Event<T>>,
length: Tick,
}
impl<T: Add<Tick, Output = T> + Clone> Add for EventGrid<T> {
type Output = EventGrid<T>;
fn add(mut self, other: EventGrid<T>) -> EventGrid<T> {
let other_events: Vec<Event<T>> = other
.events
.into_iter()
.map(|mut e| {
e.tick = e.tick.clone() + self.length;
e
})
.collect();
self.events.extend(other_events);
self.length = self.length + other.length;
self
}
}
#[test]
fn test_add_event_grid() {
let mut empty: EventGrid<Tick> = EventGrid::new();
let kick_on = Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum),
};
let kick_off = Event {
tick: Tick(24),
event_type: EventType::NoteOff(Part::KickDrum),
};
let simple_grid = EventGrid {
events: vec![kick_on, kick_off],
length: Tick(48),
};
assert_eq!(empty.clone() + empty.clone(), empty);
assert_eq!(simple_grid.clone() + empty.clone(), simple_grid);
assert_eq!(empty.clone() + simple_grid.clone(), simple_grid);
assert_eq!(
simple_grid.clone() + simple_grid.clone(),
EventGrid {
events: vec![
Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum)
},
Event {
tick: Tick(24),
event_type: EventType::NoteOff(Part::KickDrum)
},
Event {
tick: Tick(48),
event_type: EventType::NoteOn(Part::KickDrum)
},
Event {
tick: Tick(72),
event_type: EventType::NoteOff(Part::KickDrum)
}
],
length: Tick(96)
}
);
}
impl<T> EventGrid<T> {
fn new() -> Self {
EventGrid {
events: Vec::new(),
length: Tick(0),
}
}
}
#[allow(dead_code)] #[allow(dead_code)]
static TICKS_PER_QUARTER_NOTE : u16 = 48; static TICKS_PER_QUARTER_NOTE: u16 = 48;
fn basic_length_to_ticks(basic_length: BasicLength) -> Tick { fn basic_length_to_ticks(basic_length: BasicLength) -> Tick {
match basic_length { match basic_length {
@ -77,7 +171,9 @@ fn modded_length_to_ticks(modded_length: ModdedLength) -> Tick {
fn length_to_ticks(length: Length) -> Tick { fn length_to_ticks(length: Length) -> Tick {
match length { match length {
Length::Simple(mlen) => modded_length_to_ticks(mlen), Length::Simple(mlen) => modded_length_to_ticks(mlen),
Length::Tied(first, second) => modded_length_to_ticks(first) + modded_length_to_ticks(second), Length::Tied(first, second) => {
modded_length_to_ticks(first) + modded_length_to_ticks(second)
}
Length::Triplet(mlen) => { Length::Triplet(mlen) => {
let Tick(straight) = modded_length_to_ticks(mlen); let Tick(straight) = modded_length_to_ticks(mlen);
let triplet = straight * 2 / 3; let triplet = straight * 2 / 3;
@ -86,53 +182,172 @@ fn length_to_ticks(length: Length) -> Tick {
} }
} }
fn flatten_group(Group { notes, length, times }: &Group, part: Part, start: &mut Tick, grid: &mut EventGrid) -> EventGrid { // Returns an EventGrid and a total length. Length is needed as a group can end with rests that are not in the grid,
let mut time = start; // and we need it to cycle the group.
fn flatten_group(
Group {
notes,
length,
times,
}: &Group,
part: Part,
start: &mut Tick,
) -> EventGrid<Tick> {
let time = start;
let note_length = length_to_ticks(*length); let note_length = length_to_ticks(*length);
let mut grid = Vec::new(); let mut grid = EventGrid::new();
for entry in notes.iter() { |entry| notes.iter().for_each(|entry| {
match entry { match entry {
crate::dsl::dsl::GroupOrNote::SingleGroup(group) => { crate::dsl::dsl::GroupOrNote::SingleGroup(group) => {
flatten_group(&group, part, time , &mut grid).repeat(times.0 as usize); let mut eg = flatten_group(&group, part, time);
}, grid.events.append(&mut eg.events);
crate::dsl::dsl::GroupOrNote::SingleNote(Rest) => { *time = *time + note_length; }, grid.length = grid.length + eg.length;
crate::dsl::dsl::GroupOrNote::SingleNote(Hit) => { }
crate::dsl::dsl::GroupOrNote::SingleNote(Note::Rest) => {
let rest_end = *time + note_length;
*time = rest_end;
grid.length = rest_end;
}
crate::dsl::dsl::GroupOrNote::SingleNote(Note::Hit) => {
let note_end = *time + note_length; let note_end = *time + note_length;
let note_on = Event { tick: *time, event_type: EventType::NoteOn(part) }; let note_on = Event {
let note_off = Event { tick: note_end, event_type: EventType::NoteOff(part) }; tick: *time,
grid.push(note_on); event_type: EventType::NoteOn(part),
grid.push(note_off);
*time = note_end;
},
}; };
let note_off = Event {
tick: note_end,
event_type: EventType::NoteOff(part),
};
grid.events.push(note_on);
grid.events.push(note_off);
grid.length = note_end;
*time = note_end;
}
};
});
cycle_grid(grid, *times)
}
#[test]
fn test_flatten_group() {
let empty: EventGrid<Tick> = EventGrid::new();
assert_eq!(
flatten_group(
&group_or_delimited_group("(2,8x--)").unwrap().1,
Part::KickDrum,
&mut Tick(0)
),
EventGrid {
events: vec![
Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum)
},
Event {
tick: Tick(24),
event_type: EventType::NoteOff(Part::KickDrum)
},
Event {
tick: Tick(72),
event_type: EventType::NoteOn(Part::KickDrum)
},
Event {
tick: Tick(96),
event_type: EventType::NoteOff(Part::KickDrum)
}
],
length: Tick(144)
}
);
}
fn cycle_grid(event_grid: EventGrid<Tick>, times: Times) -> EventGrid<Tick> {
let mut grid = EventGrid::new();
for i in 1..(times.0 + 1) {
grid = grid + event_grid.clone();
} }
grid grid
} }
fn flatten_groups(part: Part, groups: Vec<Group>) -> EventGrid { #[test]
let mut time : Tick = Tick(0); fn test_cycle_grid() {
let mut grid : EventGrid = Vec::new(); let empty: EventGrid<Tick> = EventGrid::new();
assert_eq!(cycle_grid(EventGrid::new(), Times(2)), empty);
let kick_on = Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum),
};
let kick_off = Event {
tick: Tick(24),
event_type: EventType::NoteOff(Part::KickDrum),
};
let simple_grid = EventGrid {
events: vec![kick_on, kick_off],
length: Tick(48),
};
assert_eq!(cycle_grid(simple_grid.clone(), Times(0)), empty);
assert_eq!(cycle_grid(simple_grid.clone(), Times(1)), simple_grid);
assert_eq!(
cycle_grid(simple_grid.clone(), Times(2)),
EventGrid {
events: vec![
Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum)
},
Event {
tick: Tick(24),
event_type: EventType::NoteOff(Part::KickDrum)
},
Event {
tick: Tick(48),
event_type: EventType::NoteOn(Part::KickDrum)
},
Event {
tick: Tick(72),
event_type: EventType::NoteOff(Part::KickDrum)
}
],
length: Tick(96)
}
);
}
fn flatten_groups(part: Part, groups: Vec<Group>) -> EventGrid<Tick> {
let mut time: Tick = Tick(0);
let mut grid: EventGrid<Tick> = EventGrid::new();
groups.iter().for_each(|group| { groups.iter().for_each(|group| {
flatten_group(group, part, &mut time, &mut grid); grid = grid.clone() + flatten_group(group, part, &mut time);
}); });
grid grid
} }
fn combine_event_grids<'a>(a: &'a mut EventGrid, b : &'a mut EventGrid) -> &'a mut EventGrid { // Combines multiple sorted EventGrid<Tick>
a.append(b); fn combine_event_grids<'a, T>(a: EventGrid<T>, b : EventGrid<T>) -> EventGrid<T>
a.sort_by(|e1, e2| { e1.tick.cmp(&e2.tick)} ); where
a T: Ord,
EventGrid<T>: Add<EventGrid<T>, Output = EventGrid<T>>
{
let mut all_events = a + b;
all_events.events.sort_by(|e1, e2| { e1.tick.cmp(&e2.tick)} );
all_events
} }
fn merge_event_grids(mut eg: Vec<EventGrid>) -> EventGrid { // Combines a vector of sorted EventGrid<Tick>
fn merge_event_grids<T>(mut eg: Vec<EventGrid<T>>) -> EventGrid<T>
where
T: Ord,
EventGrid<T>: Add<EventGrid<T>, Output = EventGrid<T>> + Clone
{
let first = eg.pop().unwrap(); let first = eg.pop().unwrap();
eg.iter_mut().fold(first, |mut acc, next| { eg.iter().fold(first, |mut acc, next| {
combine_event_grids(&mut acc, next); acc = combine_event_grids(acc, (*next).clone());
acc acc
}) })
} }
fn flatten_and_merge(groups: HashMap<Part, Vec<Group>>) -> EventGrid { // Returns time as a number of ticks from beginning, has to be turned into the midi delta-time.
fn flatten_and_merge(groups: HashMap<Part, Vec<Group>>) -> EventGrid<Tick> {
let mut eg = Vec::new(); let mut eg = Vec::new();
for (part, group) in groups { for (part, group) in groups {
eg.push(flatten_groups(part, group)) eg.push(flatten_groups(part, group))
@ -142,10 +357,20 @@ fn flatten_and_merge(groups: HashMap<Part, Vec<Group>>) -> EventGrid {
// 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. // 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>(groups: HashMap<Part, Vec<Group>>) -> Smf<'a> { fn create_smf<'a>(groups: HashMap<Part, Vec<Group>>) -> Smf<'a> {
let tracks = vec![]; //create_tracks(groups); // FIXME let tracks = vec![]; // create_tracks(groups); // FIXME
// https://majicdesigns.github.io/MD_MIDIFile/page_timing.html // https://majicdesigns.github.io/MD_MIDIFile/page_timing.html
// says " If it is not specified the MIDI default is 48 ticks per quarter note." // 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. // As it's required in `Header`, let's use the same value.
let metrical = midly::Timing::Metrical(u15::new(TICKS_PER_QUARTER_NOTE)); let metrical = midly::Timing::Metrical(u15::new(TICKS_PER_QUARTER_NOTE));
Smf { header: Header { format: midly::Format::Parallel, timing: metrical }, tracks: tracks } Smf {
header: Header {
format: midly::Format::Parallel,
timing: metrical,
},
tracks: tracks,
}
} }
// fn create_tracks(groups: HashMap<Part, Vec<Group>>) -> Vec<Vec<midly::TrackEvent>> {
// todo!()
// }