mirror of
https://github.com/dredozubov/polyrhythmix.git
synced 2024-11-22 11:57:43 +00:00
Add -B option: adds bass track that follows the kick drum
This commit is contained in:
parent
d129941266
commit
1eaecd0180
3 changed files with 202 additions and 126 deletions
|
@ -1,5 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "poly"
|
name = "poly"
|
||||||
|
description = "Polyrhythmically-inclinded Midi Drum generator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,12 @@ use std::collections::BTreeMap;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use poly::dsl::dsl::{self, KnownLength, flatten_groups};
|
use poly::dsl::dsl::{self, flatten_groups, KnownLength};
|
||||||
use poly::midi::core::{create_smf, Part};
|
use poly::midi::core::{create_smf, DrumPart};
|
||||||
use poly::midi::time::TimeSignature;
|
use poly::midi::time::TimeSignature;
|
||||||
|
|
||||||
use clap::*;
|
use clap::*;
|
||||||
|
use DrumPart::*;
|
||||||
|
|
||||||
#[derive(Debug, Parser, Clone)]
|
#[derive(Debug, Parser, Clone)]
|
||||||
#[command(name = "poly")]
|
#[command(name = "poly")]
|
||||||
|
@ -34,21 +35,24 @@ struct Cli {
|
||||||
|
|
||||||
#[arg(short = 'o', default_value = None)]
|
#[arg(short = 'o', default_value = None)]
|
||||||
output: Option<String>,
|
output: Option<String>,
|
||||||
|
|
||||||
|
#[clap(short = 'B', long)]
|
||||||
|
follow_kick_drum_with_bass: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn part_to_string(part: Part) -> String {
|
fn part_to_string(part: DrumPart) -> String {
|
||||||
match part {
|
match part {
|
||||||
Part::KickDrum => String::from("Kick Drum"),
|
KickDrum => String::from("Kick Drum"),
|
||||||
Part::SnareDrum => String::from("Snare Drum"),
|
SnareDrum => String::from("Snare Drum"),
|
||||||
Part::HiHat => String::from("Hi-Hat"),
|
HiHat => String::from("Hi-Hat"),
|
||||||
Part::CrashCymbal => String::from("Crash Cymbal"),
|
CrashCymbal => String::from("Crash Cymbal"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_and_parse_part(
|
fn validate_and_parse_part(
|
||||||
cli: Option<String>,
|
cli: Option<String>,
|
||||||
part: Part,
|
part: DrumPart,
|
||||||
patterns: &mut BTreeMap<Part, dsl::Groups>,
|
patterns: &mut BTreeMap<DrumPart, dsl::Groups>,
|
||||||
) -> () {
|
) -> () {
|
||||||
match cli {
|
match cli {
|
||||||
None => {}
|
None => {}
|
||||||
|
@ -63,7 +67,12 @@ fn validate_and_parse_part(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_text_description(kick: &Option<String>, snare: &Option<String>, hihat: &Option<String>, crash: &Option<String>) -> String {
|
fn create_text_description(
|
||||||
|
kick: &Option<String>,
|
||||||
|
snare: &Option<String>,
|
||||||
|
hihat: &Option<String>,
|
||||||
|
crash: &Option<String>,
|
||||||
|
) -> String {
|
||||||
let mut parts: String = "".to_string();
|
let mut parts: String = "".to_string();
|
||||||
if kick.is_some() {
|
if kick.is_some() {
|
||||||
parts.push_str(&format!("\nKick Drum - {}", kick.clone().unwrap()));
|
parts.push_str(&format!("\nKick Drum - {}", kick.clone().unwrap()));
|
||||||
|
@ -91,6 +100,7 @@ fn main() {
|
||||||
tempo,
|
tempo,
|
||||||
time_signature,
|
time_signature,
|
||||||
output,
|
output,
|
||||||
|
follow_kick_drum_with_bass,
|
||||||
} => {
|
} => {
|
||||||
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...");
|
||||||
|
@ -103,21 +113,33 @@ fn main() {
|
||||||
let text_description = create_text_description(&kick, &snare, &hihat, &crash);
|
let text_description = create_text_description(&kick, &snare, &hihat, &crash);
|
||||||
|
|
||||||
let mut groups = BTreeMap::new();
|
let mut groups = BTreeMap::new();
|
||||||
validate_and_parse_part(kick, Part::KickDrum, &mut groups);
|
validate_and_parse_part(kick, KickDrum, &mut groups);
|
||||||
validate_and_parse_part(snare, Part::SnareDrum, &mut groups);
|
validate_and_parse_part(snare, SnareDrum, &mut groups);
|
||||||
validate_and_parse_part(hihat, Part::HiHat, &mut groups);
|
validate_and_parse_part(hihat, HiHat, &mut groups);
|
||||||
validate_and_parse_part(crash, Part::CrashCymbal, &mut groups);
|
validate_and_parse_part(crash, CrashCymbal, &mut groups);
|
||||||
|
|
||||||
let output_file = output.clone();
|
let output_file = output.clone();
|
||||||
|
|
||||||
match output_file {
|
match output_file {
|
||||||
None => {
|
None => {
|
||||||
println!("No output file path was supplied, running a dry run...");
|
println!("No output file path was supplied, running a dry run...");
|
||||||
create_smf(groups, signature, text_description.as_str(), tempo)
|
create_smf(
|
||||||
|
groups,
|
||||||
|
signature,
|
||||||
|
text_description.as_str(),
|
||||||
|
tempo,
|
||||||
|
matches.follow_kick_drum_with_bass,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
match create_smf(groups, signature, text_description.as_str(), tempo)
|
match create_smf(
|
||||||
.save(path.clone())
|
groups,
|
||||||
|
signature,
|
||||||
|
text_description.as_str(),
|
||||||
|
tempo,
|
||||||
|
follow_kick_drum_with_bass,
|
||||||
|
)
|
||||||
|
.save(path.clone())
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("{} was written successfully", path);
|
println!("{} was written successfully", path);
|
||||||
|
|
271
src/midi/core.rs
271
src/midi/core.rs
|
@ -13,11 +13,13 @@ use midly::{MetaMessage, TrackEvent};
|
||||||
|
|
||||||
use crate::dsl::dsl::{
|
use crate::dsl::dsl::{
|
||||||
flatten_group, group_or_delimited_group, groups, BasicLength, Group, GroupOrNote, Groups,
|
flatten_group, group_or_delimited_group, groups, BasicLength, Group, GroupOrNote, Groups,
|
||||||
KnownLength, Length, ModdedLength, Note, Times, SIXTEENTH,
|
KnownLength, Length, ModdedLength, Note, Times, SIXTEENTH, EIGHTH,
|
||||||
};
|
};
|
||||||
use crate::midi::time::TimeSignature;
|
use crate::midi::time::TimeSignature;
|
||||||
use GroupOrNote::*;
|
use GroupOrNote::*;
|
||||||
use Note::*;
|
use Note::*;
|
||||||
|
use Part::*;
|
||||||
|
use DrumPart::*;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
static BAR_LIMIT: u32 = 1000;
|
static BAR_LIMIT: u32 = 1000;
|
||||||
|
@ -84,16 +86,20 @@ impl Ord for EventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
pub enum Part {
|
pub enum DrumPart {
|
||||||
KickDrum,
|
KickDrum,
|
||||||
SnareDrum,
|
SnareDrum,
|
||||||
HiHat,
|
HiHat,
|
||||||
CrashCymbal,
|
CrashCymbal
|
||||||
}
|
}
|
||||||
|
|
||||||
use Part::*;
|
use DrumPart::*;
|
||||||
|
|
||||||
impl Part {
|
trait ToMidi {
|
||||||
|
fn to_midi_key(&self) -> u7;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToMidi for DrumPart {
|
||||||
// https://computermusicresource.com/GM.Percussion.KeyMap.html
|
// https://computermusicresource.com/GM.Percussion.KeyMap.html
|
||||||
fn to_midi_key(&self) -> u7 {
|
fn to_midi_key(&self) -> u7 {
|
||||||
match self {
|
match self {
|
||||||
|
@ -105,6 +111,21 @@ impl Part {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToMidi for Part {
|
||||||
|
fn to_midi_key(&self) -> u7 {
|
||||||
|
match self {
|
||||||
|
Drum(dp) => dp.to_midi_key(),
|
||||||
|
Bass => 28.into(), // low E
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
|
pub enum Part {
|
||||||
|
Drum(DrumPart),
|
||||||
|
Bass
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Event<T> {
|
pub struct Event<T> {
|
||||||
tick: T,
|
tick: T,
|
||||||
|
@ -160,15 +181,15 @@ where
|
||||||
fn test_ord_event_t() {
|
fn test_ord_event_t() {
|
||||||
let first_on = Event {
|
let first_on = Event {
|
||||||
tick: Tick(0),
|
tick: Tick(0),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
};
|
};
|
||||||
let first_off = Event {
|
let first_off = Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
};
|
};
|
||||||
let second_on = Event {
|
let second_on = Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
};
|
};
|
||||||
assert_eq!(first_on.cmp(&first_off), Less);
|
assert_eq!(first_on.cmp(&first_off), Less);
|
||||||
assert_eq!(first_off.cmp(&second_on), Less);
|
assert_eq!(first_off.cmp(&second_on), Less);
|
||||||
|
@ -251,11 +272,11 @@ fn test_concat_event_grid() {
|
||||||
let empty: EventGrid<Tick> = EventGrid::empty();
|
let empty: EventGrid<Tick> = EventGrid::empty();
|
||||||
let kick_on = Event {
|
let kick_on = Event {
|
||||||
tick: Tick(0),
|
tick: Tick(0),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
};
|
};
|
||||||
let kick_off = Event {
|
let kick_off = Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
};
|
};
|
||||||
let simple_grid = EventGrid {
|
let simple_grid = EventGrid {
|
||||||
events: vec![kick_on, kick_off],
|
events: vec![kick_on, kick_off],
|
||||||
|
@ -270,11 +291,11 @@ fn test_concat_event_grid() {
|
||||||
events: vec![
|
events: vec![
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(12),
|
tick: Tick(12),
|
||||||
event_type: NoteOn(HiHat),
|
event_type: NoteOn(Drum(HiHat)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOff(HiHat),
|
event_type: NoteOff(Drum(HiHat)),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
start: Tick(12),
|
start: Tick(12),
|
||||||
|
@ -283,7 +304,7 @@ fn test_concat_event_grid() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
input.concat(input.clone()),
|
input.concat(input.clone()),
|
||||||
EventGrid {
|
EventGrid {
|
||||||
events: vec![Event { tick: Tick(12), event_type: NoteOn(HiHat) }, Event { tick: Tick(24), event_type: NoteOff(HiHat) }, Event { tick: Tick(24), event_type: NoteOn(HiHat) }, Event { tick: Tick(36), event_type: NoteOff(HiHat) }],
|
events: vec![Event { tick: Tick(12), event_type: NoteOn(Drum(HiHat)) }, Event { tick: Tick(24), event_type: NoteOff(Drum(HiHat)) }, Event { tick: Tick(24), event_type: NoteOn(Drum(HiHat)) }, Event { tick: Tick(36), event_type: NoteOff(Drum(HiHat)) }],
|
||||||
start: Tick(12),
|
start: Tick(12),
|
||||||
end: Tick(36)
|
end: Tick(36)
|
||||||
}
|
}
|
||||||
|
@ -466,22 +487,22 @@ fn test_group_to_event_grid() {
|
||||||
};
|
};
|
||||||
let grid = EventGrid {
|
let grid = EventGrid {
|
||||||
events: vec![
|
events: vec![
|
||||||
Event { tick: Tick(12), event_type: NoteOn(HiHat) },
|
Event { tick: Tick(12), event_type: NoteOn(Drum(HiHat)) },
|
||||||
Event { tick: Tick(24), event_type: NoteOff(HiHat) },
|
Event { tick: Tick(24), event_type: NoteOff(Drum(HiHat)) },
|
||||||
Event { tick: Tick(24), event_type: NoteOn(HiHat) },
|
Event { tick: Tick(24), event_type: NoteOn(Drum(HiHat)) },
|
||||||
Event { tick: Tick(36), event_type: NoteOff(HiHat) }
|
Event { tick: Tick(36), event_type: NoteOff(Drum(HiHat)) }
|
||||||
],
|
],
|
||||||
start: start_time,
|
start: start_time,
|
||||||
end: Tick(36),
|
end: Tick(36),
|
||||||
};
|
};
|
||||||
assert_eq!(group_to_event_grid(&group, HiHat, &start_time), grid);
|
assert_eq!(group_to_event_grid(&group, Drum(HiHat), &start_time), grid);
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// group_to_event_grid(
|
// group_to_event_grid(
|
||||||
// flatten_group(group_or_delimited_group("(2,8x--)").unwrap().1).0.first().unwrap(),
|
// flatten_group(group_or_delimited_group("(2,8x--)").unwrap().1).0.first().unwrap(),
|
||||||
// KickDrum,
|
// KickDrum,
|
||||||
// &start_time
|
// &start_time
|
||||||
// ),
|
// ),
|
||||||
// EventGrid { events: vec![Event { tick: Tick(0), event_type: NoteOn(KickDrum) }, Event { tick: Tick(24), event_type: NoteOff(KickDrum) }, Event { tick: Tick(72), event_type: NoteOn(KickDrum) }, Event { tick: Tick(96), event_type: NoteOff(KickDrum) }], length: Tick(144) }
|
// EventGrid { events: vec![Event { tick: Tick(0), event_type: NoteOn(Drum(KickDrum)) }, Event { tick: Tick(24), event_type: NoteOff(Drum(KickDrum)) }, Event { tick: Tick(72), event_type: NoteOn(Drum(KickDrum)) }, Event { tick: Tick(96), event_type: NoteOff(Drum(KickDrum)) }], length: Tick(144) }
|
||||||
// );
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,11 +525,11 @@ fn test_concat_grid() {
|
||||||
events: vec![
|
events: vec![
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(12),
|
tick: Tick(12),
|
||||||
event_type: NoteOn(HiHat)
|
event_type: NoteOn(Drum(HiHat))
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOff(HiHat)
|
event_type: NoteOff(Drum(HiHat))
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
start: Tick(12),
|
start: Tick(12),
|
||||||
|
@ -516,7 +537,7 @@ fn test_concat_grid() {
|
||||||
},
|
},
|
||||||
Times(2)
|
Times(2)
|
||||||
),
|
),
|
||||||
EventGrid { events: vec![Event { tick: Tick(12), event_type: NoteOn(HiHat) }, Event { tick: Tick(24), event_type: NoteOff(HiHat) }, Event { tick: Tick(24), event_type: NoteOn(HiHat) }, Event { tick: Tick(36), event_type: NoteOff(HiHat) }], start: Tick(12), end: Tick(36) }
|
EventGrid { events: vec![Event { tick: Tick(12), event_type: NoteOn(Drum(HiHat)) }, Event { tick: Tick(24), event_type: NoteOff(Drum(HiHat)) }, Event { tick: Tick(24), event_type: NoteOn(Drum(HiHat)) }, Event { tick: Tick(36), event_type: NoteOff(Drum(HiHat)) }], start: Tick(12), end: Tick(36) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +591,7 @@ impl Iterator for EventIterator {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let candidates: BTreeMap<Part, Event<Tick>> = [
|
let candidates: BTreeMap<DrumPart, Event<Tick>> = [
|
||||||
(KickDrum, self.kick.peek()),
|
(KickDrum, self.kick.peek()),
|
||||||
(SnareDrum, self.snare.peek()),
|
(SnareDrum, self.snare.peek()),
|
||||||
(HiHat, self.hihat.peek()),
|
(HiHat, self.hihat.peek()),
|
||||||
|
@ -605,7 +626,7 @@ fn test_event_iterator_impl() {
|
||||||
.0
|
.0
|
||||||
.first()
|
.first()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
KickDrum,
|
Drum(KickDrum),
|
||||||
&mut Tick(0),
|
&mut Tick(0),
|
||||||
);
|
);
|
||||||
let snare1 = group_to_event_grid(
|
let snare1 = group_to_event_grid(
|
||||||
|
@ -613,7 +634,7 @@ fn test_event_iterator_impl() {
|
||||||
.0
|
.0
|
||||||
.first()
|
.first()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
SnareDrum,
|
Drum(SnareDrum),
|
||||||
&mut Tick(0),
|
&mut Tick(0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -630,19 +651,19 @@ fn test_event_iterator_impl() {
|
||||||
vec![
|
vec![
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(0),
|
tick: Tick(0),
|
||||||
event_type: NoteOn(KickDrum)
|
event_type: NoteOn(Drum(KickDrum))
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48),
|
tick: Tick(48),
|
||||||
event_type: NoteOff(KickDrum)
|
event_type: NoteOff(Drum(KickDrum))
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48),
|
tick: Tick(48),
|
||||||
event_type: NoteOn(SnareDrum)
|
event_type: NoteOn(Drum(SnareDrum))
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(96),
|
tick: Tick(96),
|
||||||
event_type: NoteOff(SnareDrum)
|
event_type: NoteOff(Drum(SnareDrum))
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -660,11 +681,11 @@ fn test_event_iterator_impl() {
|
||||||
[
|
[
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(0),
|
tick: Tick(0),
|
||||||
event_type: NoteOn(KickDrum)
|
event_type: NoteOn(Drum(KickDrum))
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48),
|
tick: Tick(48),
|
||||||
event_type: NoteOff(KickDrum)
|
event_type: NoteOff(Drum(KickDrum))
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -675,11 +696,11 @@ fn test_event_iterator_impl() {
|
||||||
///
|
///
|
||||||
/// Returns time as a number of ticks from beginning, has to be turned into the midi delta-time.
|
/// Returns time as a number of ticks from beginning, has to be turned into the midi delta-time.
|
||||||
fn merge_into_iterator(
|
fn merge_into_iterator(
|
||||||
groups: BTreeMap<Part, Groups>,
|
groups: &BTreeMap<DrumPart, Groups>,
|
||||||
time_signature: TimeSignature,
|
time_signature: TimeSignature,
|
||||||
) -> EventIterator {
|
) -> EventIterator {
|
||||||
// Maps a drum part to a number of 128th notes
|
// Maps a drum part to a number of 128th notes
|
||||||
let length_map: BTreeMap<Part, u32> = groups.iter().map(|(k, x)| (*k, x.to_128th())).collect();
|
let length_map: BTreeMap<DrumPart, u32> = groups.iter().map(|(k, x)| (*k, x.to_128th())).collect();
|
||||||
|
|
||||||
// We want exactly length_limit or BAR_LIMIT
|
// We want exactly length_limit or BAR_LIMIT
|
||||||
let converges_over_bars = time_signature
|
let converges_over_bars = time_signature
|
||||||
|
@ -695,12 +716,12 @@ fn merge_into_iterator(
|
||||||
// length limit in 128th notes
|
// length limit in 128th notes
|
||||||
let length_limit = converges_over_bars * time_signature.to_128th();
|
let length_limit = converges_over_bars * time_signature.to_128th();
|
||||||
|
|
||||||
let to_event_grid = |part| {
|
let to_event_grid = |part: &DrumPart| {
|
||||||
match groups.get(part) {
|
match groups.get(part) {
|
||||||
Some(groups) => {
|
Some(groups) => {
|
||||||
let length_128th = length_map.get(part).unwrap();
|
let length_128th = length_map.get(part).unwrap();
|
||||||
let times = length_limit / length_128th;
|
let times = length_limit / length_128th;
|
||||||
let event_grid = groups_to_event_grid(*part, groups);
|
let event_grid = groups_to_event_grid(Drum(*part), groups);
|
||||||
(event_grid, times)
|
(event_grid, times)
|
||||||
}
|
}
|
||||||
None => (EventGrid::empty(), 0),
|
None => (EventGrid::empty(), 0),
|
||||||
|
@ -729,154 +750,154 @@ fn test_merge_into_iterator() {
|
||||||
let kick_events = vec![
|
let kick_events = vec![
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(0),
|
tick: Tick(0),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(12),
|
tick: Tick(12),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(12),
|
tick: Tick(12),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(36),
|
tick: Tick(36),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48),
|
tick: Tick(48),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(60),
|
tick: Tick(60),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(72),
|
tick: Tick(72),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(72),
|
tick: Tick(72),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(84),
|
tick: Tick(84),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(96),
|
tick: Tick(96),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(108),
|
tick: Tick(108),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(108),
|
tick: Tick(108),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(120),
|
tick: Tick(120),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(132),
|
tick: Tick(132),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(144),
|
tick: Tick(144),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(156),
|
tick: Tick(156),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(168),
|
tick: Tick(168),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(168),
|
tick: Tick(168),
|
||||||
event_type: NoteOn(KickDrum),
|
event_type: NoteOn(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(180),
|
tick: Tick(180),
|
||||||
event_type: NoteOff(KickDrum),
|
event_type: NoteOff(Drum(KickDrum)),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let snare_events = vec![
|
let snare_events = vec![
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24),
|
tick: Tick(24),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48),
|
tick: Tick(48),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(96),
|
tick: Tick(96),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(120),
|
tick: Tick(120),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24 + 144),
|
tick: Tick(24 + 144),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48 + 144),
|
tick: Tick(48 + 144),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(96 + 144),
|
tick: Tick(96 + 144),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(120 + 144),
|
tick: Tick(120 + 144),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24 + 288),
|
tick: Tick(24 + 288),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48 + 288),
|
tick: Tick(48 + 288),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(96 + 288),
|
tick: Tick(96 + 288),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(120 + 288),
|
tick: Tick(120 + 288),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(24 + 144 * 3),
|
tick: Tick(24 + 144 * 3),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(48 + 144 * 3),
|
tick: Tick(48 + 144 * 3),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(96 + 144 * 3),
|
tick: Tick(96 + 144 * 3),
|
||||||
event_type: NoteOn(SnareDrum),
|
event_type: NoteOn(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
Event {
|
Event {
|
||||||
tick: Tick(120 + 144 * 3),
|
tick: Tick(120 + 144 * 3),
|
||||||
event_type: NoteOff(SnareDrum),
|
event_type: NoteOff(Drum(SnareDrum)),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let four_fourth = TimeSignature::from_str("4/4").unwrap();
|
let four_fourth = TimeSignature::from_str("4/4").unwrap();
|
||||||
let flattened_kick_and_snare = merge_into_iterator(
|
let flattened_kick_and_snare = merge_into_iterator(
|
||||||
BTreeMap::from_iter([
|
&BTreeMap::from_iter([
|
||||||
(KickDrum, groups("16xx-x-xx-").unwrap().1),
|
(KickDrum, groups("16xx-x-xx-").unwrap().1),
|
||||||
(SnareDrum, groups("8-x--x-").unwrap().1),
|
(SnareDrum, groups("8-x--x-").unwrap().1),
|
||||||
]),
|
]),
|
||||||
|
@ -886,7 +907,7 @@ fn test_merge_into_iterator() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
merge_into_iterator(
|
merge_into_iterator(
|
||||||
BTreeMap::from_iter([(KickDrum, groups(kick_group).unwrap().1)]),
|
&BTreeMap::from_iter([(KickDrum, groups(kick_group).unwrap().1)]),
|
||||||
four_fourth
|
four_fourth
|
||||||
)
|
)
|
||||||
.collect::<Vec<Event<Tick>>>(),
|
.collect::<Vec<Event<Tick>>>(),
|
||||||
|
@ -894,7 +915,7 @@ fn test_merge_into_iterator() {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
merge_into_iterator(
|
merge_into_iterator(
|
||||||
BTreeMap::from_iter([(SnareDrum, groups(snare_group).unwrap().1)]),
|
&BTreeMap::from_iter([(SnareDrum, groups(snare_group).unwrap().1)]),
|
||||||
four_fourth
|
four_fourth
|
||||||
)
|
)
|
||||||
.collect::<Vec<Event<Tick>>>(),
|
.collect::<Vec<Event<Tick>>>(),
|
||||||
|
@ -913,12 +934,13 @@ fn test_merge_into_iterator() {
|
||||||
|
|
||||||
// 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>(
|
pub fn create_smf<'a>(
|
||||||
groups: BTreeMap<Part, Groups>,
|
groups: BTreeMap<DrumPart, Groups>,
|
||||||
time_signature: TimeSignature,
|
time_signature: TimeSignature,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
tempo: u16,
|
tempo: u16,
|
||||||
|
add_bass: bool
|
||||||
) -> Smf<'a> {
|
) -> Smf<'a> {
|
||||||
let tracks = create_tracks(groups, time_signature, text, MidiTempo::from_tempo(tempo));
|
let tracks = create_tracks(groups, time_signature, text, MidiTempo::from_tempo(tempo), add_bass);
|
||||||
// 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.
|
||||||
|
@ -945,12 +967,13 @@ pub fn create_smf<'a>(
|
||||||
/// Multi-track vectors of MIDI events in `midly` format.
|
/// Multi-track vectors of MIDI events in `midly` format.
|
||||||
///
|
///
|
||||||
fn create_tracks<'a>(
|
fn create_tracks<'a>(
|
||||||
parts_and_groups: BTreeMap<Part, Groups>,
|
parts_and_groups: BTreeMap<DrumPart, Groups>,
|
||||||
time_signature: TimeSignature,
|
time_signature: TimeSignature,
|
||||||
text_event: &'a str,
|
text_event: &'a str,
|
||||||
midi_tempo: MidiTempo,
|
midi_tempo: MidiTempo,
|
||||||
|
add_bass: bool
|
||||||
) -> Vec<Vec<midly::TrackEvent<'a>>> {
|
) -> Vec<Vec<midly::TrackEvent<'a>>> {
|
||||||
let events_iter = merge_into_iterator(parts_and_groups, time_signature);
|
let events_iter = merge_into_iterator(&parts_and_groups, time_signature);
|
||||||
let events: Vec<Event<Tick>> = events_iter.collect();
|
let events: Vec<Event<Tick>> = events_iter.collect();
|
||||||
|
|
||||||
let track_time = match events.last() {
|
let track_time = match events.last() {
|
||||||
|
@ -961,40 +984,40 @@ fn create_tracks<'a>(
|
||||||
};
|
};
|
||||||
let event_grid_tick = EventGrid::new(events, track_time);
|
let event_grid_tick = EventGrid::new(events, track_time);
|
||||||
let event_grid = event_grid_tick.to_delta();
|
let event_grid = event_grid_tick.to_delta();
|
||||||
let mut drums = Vec::new();
|
let mut drums_track = Vec::new();
|
||||||
|
|
||||||
// This is likely to be specific to Guitar Pro. Tested with Guitar Pro 7.
|
// This is likely to be specific to Guitar Pro. Tested with Guitar Pro 7.
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Midi {
|
kind: TrackEventKind::Midi {
|
||||||
channel: 9.into(),
|
channel: 9.into(),
|
||||||
message: MidiMessage::ProgramChange { program: 0.into() },
|
message: MidiMessage::ProgramChange { program: 0.into() },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::TrackName(b"Drumkit")),
|
kind: TrackEventKind::Meta(MetaMessage::TrackName(b"Drumkit")),
|
||||||
});
|
});
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::InstrumentName(b"Drumkit")),
|
kind: TrackEventKind::Meta(MetaMessage::InstrumentName(b"Drumkit")),
|
||||||
});
|
});
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::MidiChannel(10.into())),
|
kind: TrackEventKind::Meta(MetaMessage::MidiChannel(10.into())),
|
||||||
});
|
});
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::MidiPort(10.into())),
|
kind: TrackEventKind::Meta(MetaMessage::MidiPort(10.into())),
|
||||||
});
|
});
|
||||||
|
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::Tempo(midi_tempo.0)),
|
kind: TrackEventKind::Meta(MetaMessage::Tempo(midi_tempo.0)),
|
||||||
});
|
});
|
||||||
|
|
||||||
let (midi_time_signature_numerator, midi_time_signature_denominator) = time_signature.to_midi();
|
let (midi_time_signature_numerator, midi_time_signature_denominator) = time_signature.to_midi();
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::TimeSignature(
|
kind: TrackEventKind::Meta(MetaMessage::TimeSignature(
|
||||||
midi_time_signature_numerator,
|
midi_time_signature_numerator,
|
||||||
|
@ -1004,34 +1027,64 @@ fn create_tracks<'a>(
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
|
||||||
drums.push(TrackEvent {
|
drums_track.push(TrackEvent {
|
||||||
delta: 0.into(),
|
delta: 0.into(),
|
||||||
kind: TrackEventKind::Meta(MetaMessage::Text(text_event.as_bytes())),
|
kind: TrackEventKind::Meta(MetaMessage::Text(text_event.as_bytes())),
|
||||||
});
|
});
|
||||||
|
|
||||||
for event in event_grid.events {
|
let map_notes = |grid: EventGrid<Delta>, track: &mut Vec<TrackEvent>| {
|
||||||
let midi_message = match event.event_type {
|
for event in grid.events {
|
||||||
NoteOn(part) => MidiMessage::NoteOn {
|
let midi_message = match event.event_type {
|
||||||
key: part.to_midi_key(),
|
NoteOn(part) => MidiMessage::NoteOn {
|
||||||
vel: 127.into(),
|
key: part.to_midi_key(),
|
||||||
},
|
vel: 127.into(),
|
||||||
NoteOff(part) => MidiMessage::NoteOff {
|
},
|
||||||
key: part.to_midi_key(),
|
NoteOff(part) => MidiMessage::NoteOff {
|
||||||
vel: 127.into(),
|
key: part.to_midi_key(),
|
||||||
},
|
vel: 127.into(),
|
||||||
};
|
},
|
||||||
drums.push(TrackEvent {
|
};
|
||||||
delta: u28::from(event.tick.0 as u32),
|
track.push(TrackEvent {
|
||||||
kind: TrackEventKind::Midi {
|
delta: u28::from(event.tick.0 as u32),
|
||||||
channel: u4::from(10),
|
kind: TrackEventKind::Midi {
|
||||||
message: midi_message,
|
channel: u4::from(10),
|
||||||
},
|
message: midi_message,
|
||||||
})
|
},
|
||||||
}
|
})
|
||||||
drums.push(TrackEvent {
|
}
|
||||||
delta: drums.last().unwrap().delta,
|
};
|
||||||
|
|
||||||
|
map_notes(event_grid, &mut drums_track);
|
||||||
|
|
||||||
|
drums_track.push(TrackEvent {
|
||||||
|
delta: drums_track.last().unwrap().delta,
|
||||||
kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
|
kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![drums]
|
if add_bass {
|
||||||
|
let mut bass_track = Vec::new();
|
||||||
|
let empty_groups = Groups(Vec::new());
|
||||||
|
let kick = parts_and_groups.get(&KickDrum).unwrap_or(&empty_groups);
|
||||||
|
let bass = groups_to_event_grid(Bass, kick);
|
||||||
|
// This is likely to be specific to Guitar Pro. Tested with Guitar Pro 7.
|
||||||
|
bass_track.push(TrackEvent {
|
||||||
|
delta: 0.into(),
|
||||||
|
kind: TrackEventKind::Midi {
|
||||||
|
channel: 0.into(),
|
||||||
|
message: MidiMessage::ProgramChange { program: 34.into() },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
bass_track.push(TrackEvent {
|
||||||
|
delta: 0.into(),
|
||||||
|
kind: TrackEventKind::Meta(MetaMessage::TrackName(b"Bass")),
|
||||||
|
});
|
||||||
|
bass_track.push(TrackEvent {
|
||||||
|
delta: 0.into(),
|
||||||
|
kind: TrackEventKind::Meta(MetaMessage::InstrumentName(b"Bass")),
|
||||||
|
});
|
||||||
|
map_notes(bass.to_delta(), &mut bass_track);
|
||||||
|
vec![drums_track, bass_track]
|
||||||
|
} else {
|
||||||
|
vec![drums_track]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue