This commit is contained in:
Denis Redozubov 2023-05-12 17:47:24 +04:00
parent f210b76454
commit c582fa08a3
2 changed files with 187 additions and 53 deletions

View file

@ -3,7 +3,7 @@ use std::process::exit;
use poly::dsl::dsl; use poly::dsl::dsl;
use poly::midi; use poly::midi;
use poly::midi::core::Part; use poly::midi::core::{Part, create_smf, TimeSignature};
use clap::*; use clap::*;
@ -14,22 +14,22 @@ use clap::*;
#[command(version = "0.1")] #[command(version = "0.1")]
#[command(about = "Polyrhythmically-inclinded Midi Drum generator", long_about = None)] #[command(about = "Polyrhythmically-inclinded Midi Drum generator", long_about = None)]
struct Cli { struct Cli {
#[arg(short = 'k', default_value = None)] #[arg(short = 'K', default_value = None)]
kick: Option<String>, kick: Option<String>,
#[arg(short = 's', default_value = None)] #[arg(short = 'S', default_value = None)]
snare: Option<String>, snare: Option<String>,
#[arg(short = 'h', default_value = None)] #[arg(short = 'H', default_value = None)]
hihat: Option<String>, hihat: Option<String>,
#[arg(short = 'c', default_value = None)] #[arg(short = 'C', default_value = None)]
crash: Option<String>, crash: Option<String>,
#[arg(short = 't', default_value = "120")] #[arg(short = 't', default_value = "120")]
tempo: String, tempo: String,
#[arg(short = 'S', default_value = "4/4")] #[arg(short = 's', default_value = "4/4")]
time_signature: String, time_signature: String,
#[arg(short = 'o', default_value = None)] #[arg(short = 'o', default_value = None)]
@ -61,20 +61,39 @@ fn validate_and_parse_part(cli: Option<String>, part: Part, patterns: &mut HashM
fn main() { fn main() {
let matches = Cli::parse(); let matches = Cli::parse();
let mut drum_patterns : HashMap<Part, Vec<dsl::Group>> = HashMap::new();
match matches { match matches {
Cli { kick, snare, hihat, crash, tempo, time_signature, output} => { Cli { kick, snare, hihat, crash, tempo, time_signature, output} => {
if kick == None && snare == None && hihat == None && crash == None { if kick == None && snare == None && hihat == None && crash == None {
println!("No drum pattern was supplied, exiting..."); println!("No drum pattern was supplied, exiting...");
exit(1) exit(1)
} else if output == None {
println!("No output file path was supplied, running a dry run...")
} else { } else {
validate_and_parse_part(kick, Part::KickDrum, &mut drum_patterns); let mut groups = HashMap::new();
validate_and_parse_part(snare, Part::SnareDrum, &mut drum_patterns); validate_and_parse_part(kick, Part::KickDrum, &mut groups);
validate_and_parse_part(hihat, Part::HiHat, &mut drum_patterns); validate_and_parse_part(snare, Part::SnareDrum, &mut groups);
validate_and_parse_part(crash, Part::CrashCymbal, &mut drum_patterns); validate_and_parse_part(hihat, Part::HiHat, &mut groups);
validate_and_parse_part(crash, Part::CrashCymbal, &mut groups);
// TODO
let time_signature = TimeSignature { numerator: 4, denominator: dsl::BasicLength::Fourth };
match output {
None => {
println!("No output file path was supplied, running a dry run...");
create_smf(groups, time_signature)
},
Some(path) => {
match create_smf(groups, time_signature).save(path.clone()) {
Ok(_) => {
println!("{} was written successfully", path);
exit(0)
},
Err(e) => {
println!("Failed to write {}: {}", path, e);
exit(1)
},
};
}
};
} }
} }
} }

View file

@ -3,10 +3,14 @@ use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
use midly::{live::LiveEvent, num::u15, Header, MidiMessage, Smf, Track}; use midly::{
num::u15, num::u24, num::u28, num::u4, num::u7, Header, MidiMessage, Smf, Track, TrackEventKind,
};
use midly::{MetaMessage, TrackEvent};
use crate::dsl::dsl::{ use crate::dsl::dsl::{
group_or_delimited_group, groups, BasicLength, Group, Length, ModdedLength, Note, Times, group_or_delimited_group, groups, BasicLength, Group, GroupOrNote, Length, ModdedLength, Note,
Times,
}; };
// Typically used as number of ticks since the beginning of the track. // Typically used as number of ticks since the beginning of the track.
@ -42,8 +46,6 @@ pub struct Delta(pub u128);
pub enum EventType { pub enum EventType {
NoteOn(Part), NoteOn(Part),
NoteOff(Part), NoteOff(Part),
Tempo(u8),
Signature(TimeSignature),
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@ -54,14 +56,20 @@ pub struct TimeSignature {
impl TimeSignature { impl TimeSignature {
pub fn new(numerator: u8, denominator: BasicLength) -> Self { pub fn new(numerator: u8, denominator: BasicLength) -> Self {
Self { numerator, denominator } Self {
numerator,
denominator,
}
} }
} }
impl std::ops::Mul<u8> for TimeSignature { impl std::ops::Mul<u8> for TimeSignature {
type Output = TimeSignature; type Output = TimeSignature;
fn mul(self, rhs: u8) -> TimeSignature { fn mul(self, rhs: u8) -> TimeSignature {
TimeSignature { numerator: self.numerator * rhs as u8, denominator: self.denominator } TimeSignature {
numerator: self.numerator * rhs as u8,
denominator: self.denominator,
}
} }
} }
@ -86,8 +94,6 @@ fn test_cmp_time_signature() {
} }
impl TimeSignature { impl TimeSignature {
/// Checks if these two signatures converges for the next 200 bars. /// Checks if these two signatures converges for the next 200 bars.
fn converges_with(&self, other: TimeSignature) -> Result<(u32, TimeSignature), String> { fn converges_with(&self, other: TimeSignature) -> Result<(u32, TimeSignature), String> {
let d: u32 = std::cmp::max(self.denominator, other.denominator) let d: u32 = std::cmp::max(self.denominator, other.denominator)
@ -172,9 +178,12 @@ fn test_converges() {
}; };
let three_fourth = TimeSignature { let three_fourth = TimeSignature {
numerator: 3, numerator: 3,
denominator: BasicLength::Fourth denominator: BasicLength::Fourth,
}; };
assert_eq!(three_sixteenth.converges(vec![four_fourth, three_fourth]), Ok((3, four_fourth))); assert_eq!(
three_sixteenth.converges(vec![four_fourth, three_fourth]),
Ok((3, four_fourth))
);
} }
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
@ -187,12 +196,12 @@ pub enum Part {
impl Part { impl Part {
// https://computermusicresource.com/GM.Percussion.KeyMap.html // https://computermusicresource.com/GM.Percussion.KeyMap.html
fn to_midi_key(&self) -> u8 { fn to_midi_key(&self) -> u7 {
match self { match self {
Part::KickDrum => 36, Part::KickDrum => u7::from(36),
Part::SnareDrum => 38, Part::SnareDrum => u7::from(38),
Part::HiHat => 46, Part::HiHat => u7::from(46),
Part::CrashCymbal => 49, Part::CrashCymbal => u7::from(49),
} }
} }
} }
@ -210,7 +219,7 @@ pub struct EventGrid<T> {
length: Tick, length: Tick,
} }
impl<T: Add<Tick, Output = T> + Clone> Add for EventGrid<T> { impl<T: Add<Tick, Output = T> + Clone + Ord> Add for EventGrid<T> {
type Output = EventGrid<T>; type Output = EventGrid<T>;
fn add(mut self, other: EventGrid<T>) -> EventGrid<T> { fn add(mut self, other: EventGrid<T>) -> EventGrid<T> {
@ -223,11 +232,78 @@ impl<T: Add<Tick, Output = T> + Clone> Add for EventGrid<T> {
}) })
.collect(); .collect();
self.events.extend(other_events); self.events.extend(other_events);
self.events.sort();
self.length = self.length + other.length; self.length = self.length + other.length;
self self
} }
} }
impl<T: Clone + Ord> Mul for EventGrid<T> {
type Output = EventGrid<T>;
fn mul(mut self, other: EventGrid<T>) -> EventGrid<T> {
let other_events: Vec<Event<T>> = other.events;
self.events.extend(other_events);
self.events.sort();
self.length = self.length + other.length;
self
}
}
#[test]
fn test_arith_event_grids() {
let eg1 = EventGrid {
events: vec![
Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum),
},
Event {
tick: Tick(TICKS_PER_QUARTER_NOTE as u128),
event_type: EventType::NoteOff(Part::KickDrum),
},
],
length: Tick(TICKS_PER_QUARTER_NOTE as u128),
};
let eg2 = EventGrid {
events: vec![
Event {
tick: Tick(24),
event_type: EventType::NoteOn(Part::HiHat),
},
Event {
tick: Tick(TICKS_PER_QUARTER_NOTE as u128),
event_type: EventType::NoteOff(Part::HiHat),
},
],
length: Tick(TICKS_PER_QUARTER_NOTE as u128),
};
let mul_res = EventGrid {
events: vec![
Event {
tick: Tick(0),
event_type: EventType::NoteOn(Part::KickDrum),
},
Event {
tick: Tick(24),
event_type: EventType::NoteOn(Part::HiHat),
},
Event {
tick: Tick(48),
event_type: EventType::NoteOff(Part::KickDrum),
},
Event {
tick: Tick(48),
event_type: EventType::NoteOff(Part::HiHat),
},
],
length: Tick(96),
};
assert_eq!(eg1.clone() * eg2.clone(), mul_res);
}
#[test] #[test]
fn test_add_event_grid() { fn test_add_event_grid() {
let mut empty: EventGrid<Tick> = EventGrid::new(); let mut empty: EventGrid<Tick> = EventGrid::new();
@ -371,6 +447,35 @@ impl Length {
} }
} }
#[allow(dead_code)]
static MICROSECONDS_PER_BPM: u128 = 500000 as u128 / TICKS_PER_QUARTER_NOTE as u128;
#[allow(dead_code)]
static MIDI_CLOCKS_PER_CLICK: u8 = 24;
/// Microseconds per quarter note. Default is 500,000 for 120bpm.
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
derive_more::Add,
derive_more::Sub,
derive_more::Mul,
derive_more::Display,
)]
pub struct MidiTempo(u24);
// impl MidiTempo {
// fn from_tempo(Tempo(t): Tempo) -> Self {
// let mt = t as u32 * MICROSECONDS_PER_BPM as u32;
// Self(u24::from(mt))
// }
// }
/// Returns an EventGrid and a total length. Length is needed as a group can end with rests that are not in the grid, /// Returns an EventGrid and a total length. Length is needed as a group can end with rests that are not in the grid,
/// and we need it to cycle the group. /// and we need it to cycle the group.
fn flatten_group( fn flatten_group(
@ -510,26 +615,12 @@ fn flatten_groups(part: Part, groups: Vec<Group>) -> EventGrid<Tick> {
grid grid
} }
// Combines multiple sorted EventGrid<Tick> // Combines a vector of sorted EventGrid<Tick> into a single `EventGrid<Tick>`
fn combine_event_grids<'a, T>(a: EventGrid<T>, b: EventGrid<T>) -> EventGrid<T> fn merge_event_grids(mut eg: Vec<EventGrid<Tick>>) -> EventGrid<Tick>
where
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
}
// 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().fold(first, |mut acc, next| { eg.iter().fold(first, |mut acc, next| {
acc = combine_event_grids(acc, (*next).clone()); acc = acc * (*next).clone();
acc acc
}) })
} }
@ -544,11 +635,11 @@ fn flatten_and_merge(groups: HashMap<Part, Vec<Group>>) -> EventGrid<Tick> {
} }
// 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.
pub fn create_smf<'a>(groups: HashMap<Part, Vec<Group>>) -> Smf<'a> { pub fn create_smf<'a>(groups: HashMap<Part, Vec<Group>>, time_signature: TimeSignature) -> Smf<'a> {
let tracks = vec![]; // create_tracks(groups); // FIXME let tracks = create_tracks(groups, time_signature); // 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 { Smf {
header: Header { header: Header {
@ -562,8 +653,32 @@ pub fn create_smf<'a>(groups: HashMap<Part, Vec<Group>>) -> Smf<'a> {
/// Translates drum parts to a single MIDI track. /// Translates drum parts to a single MIDI track.
fn create_tracks<'a>( fn create_tracks<'a>(
parts_and_groups: HashMap<Part, Vec<Group>>, parts_and_groups: HashMap<Part, Vec<Group>>,
time_signature: TimeSignature, // tempo: u32
) -> Vec<Vec<midly::TrackEvent<'a>>> { ) -> Vec<Vec<midly::TrackEvent<'a>>> {
let event_grid = flatten_and_merge(parts_and_groups).to_delta(); let event_grid = flatten_and_merge(parts_and_groups).to_delta();
let drums = Vec::new(); let mut drums = Vec::new();
// let midi_tempo = MidiTempo::from_tempo(Tempo(130)).0;
// drums.push(TrackEvent { delta: u28::from(0), kind: TrackEventKind::Meta(MetaMessage::Tempo(midi_tempo)) });
// drums.push(TrackEvent { delta: u28::from(0), kind: TrackEventKind::Meta(MetaMessage::TimeSignature(4, 4, MIDI_CLOCKS_PER_CLICK.clone(), 8))});
for event in event_grid.events {
let midi_message = match event.event_type {
EventType::NoteOn(part) => MidiMessage::NoteOn {
key: part.to_midi_key(),
vel: u7::from(120),
},
EventType::NoteOff(part) => MidiMessage::NoteOff {
key: part.to_midi_key(),
vel: u7::from(0),
},
};
drums.push(TrackEvent {
delta: u28::from(event.tick.0 as u32),
kind: TrackEventKind::Midi {
channel: u4::from(10),
message: midi_message,
},
})
}
vec![drums] vec![drums]
} }