mirror of
https://github.com/dredozubov/polyrhythmix.git
synced 2024-11-25 21:37:43 +00:00
WIP
This commit is contained in:
parent
f210b76454
commit
c582fa08a3
2 changed files with 187 additions and 53 deletions
|
@ -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)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
193
src/midi/core.rs
193
src/midi/core.rs
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue