mirror of
https://github.com/dredozubov/polyrhythmix.git
synced 2024-11-22 11:57:43 +00:00
Better code
This commit is contained in:
parent
f1dfb31b70
commit
1d74b52cff
4 changed files with 295 additions and 57 deletions
14
.vscode/settings.json
vendored
Normal file
14
.vscode/settings.json
vendored
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
317
src/midi/core.rs
317
src/midi/core.rs
|
@ -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,17 +55,92 @@ 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;
|
||||||
|
@ -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]
|
||||||
|
fn test_cycle_grid() {
|
||||||
|
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 time: Tick = Tick(0);
|
||||||
let mut grid : EventGrid = Vec::new();
|
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))
|
||||||
|
@ -147,5 +362,15 @@ fn create_smf<'a>(groups: HashMap<Part, Vec<Group>>) -> Smf<'a> {
|
||||||
// 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!()
|
||||||
|
// }
|
||||||
|
|
Loading…
Reference in a new issue