Add -B option: adds bass track that follows the kick drum

This commit is contained in:
Denis Redozubov 2023-06-27 17:11:03 +04:00
parent d129941266
commit 1eaecd0180
3 changed files with 202 additions and 126 deletions

View file

@ -1,5 +1,6 @@
[package]
name = "poly"
description = "Polyrhythmically-inclinded Midi Drum generator"
version = "0.1.0"
edition = "2021"

View file

@ -2,11 +2,12 @@ use std::collections::BTreeMap;
use std::process::exit;
use std::str::FromStr;
use poly::dsl::dsl::{self, KnownLength, flatten_groups};
use poly::midi::core::{create_smf, Part};
use poly::dsl::dsl::{self, flatten_groups, KnownLength};
use poly::midi::core::{create_smf, DrumPart};
use poly::midi::time::TimeSignature;
use clap::*;
use DrumPart::*;
#[derive(Debug, Parser, Clone)]
#[command(name = "poly")]
@ -34,21 +35,24 @@ struct Cli {
#[arg(short = 'o', default_value = None)]
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 {
Part::KickDrum => String::from("Kick Drum"),
Part::SnareDrum => String::from("Snare Drum"),
Part::HiHat => String::from("Hi-Hat"),
Part::CrashCymbal => String::from("Crash Cymbal"),
KickDrum => String::from("Kick Drum"),
SnareDrum => String::from("Snare Drum"),
HiHat => String::from("Hi-Hat"),
CrashCymbal => String::from("Crash Cymbal"),
}
}
fn validate_and_parse_part(
cli: Option<String>,
part: Part,
patterns: &mut BTreeMap<Part, dsl::Groups>,
part: DrumPart,
patterns: &mut BTreeMap<DrumPart, dsl::Groups>,
) -> () {
match cli {
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();
if kick.is_some() {
parts.push_str(&format!("\nKick Drum - {}", kick.clone().unwrap()));
@ -91,6 +100,7 @@ fn main() {
tempo,
time_signature,
output,
follow_kick_drum_with_bass,
} => {
if kick == None && snare == None && hihat == None && crash == None {
println!("No drum pattern was supplied, exiting...");
@ -103,21 +113,33 @@ fn main() {
let text_description = create_text_description(&kick, &snare, &hihat, &crash);
let mut groups = BTreeMap::new();
validate_and_parse_part(kick, Part::KickDrum, &mut groups);
validate_and_parse_part(snare, Part::SnareDrum, &mut groups);
validate_and_parse_part(hihat, Part::HiHat, &mut groups);
validate_and_parse_part(crash, Part::CrashCymbal, &mut groups);
validate_and_parse_part(kick, KickDrum, &mut groups);
validate_and_parse_part(snare, SnareDrum, &mut groups);
validate_and_parse_part(hihat, HiHat, &mut groups);
validate_and_parse_part(crash, CrashCymbal, &mut groups);
let output_file = output.clone();
match output_file {
None => {
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) => {
match create_smf(groups, signature, text_description.as_str(), tempo)
.save(path.clone())
match create_smf(
groups,
signature,
text_description.as_str(),
tempo,
follow_kick_drum_with_bass,
)
.save(path.clone())
{
Ok(_) => {
println!("{} was written successfully", path);

View file

@ -13,11 +13,13 @@ use midly::{MetaMessage, TrackEvent};
use crate::dsl::dsl::{
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 GroupOrNote::*;
use Note::*;
use Part::*;
use DrumPart::*;
#[allow(dead_code)]
static BAR_LIMIT: u32 = 1000;
@ -84,16 +86,20 @@ impl Ord for EventType {
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum Part {
pub enum DrumPart {
KickDrum,
SnareDrum,
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
fn to_midi_key(&self) -> u7 {
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)]
pub struct Event<T> {
tick: T,
@ -160,15 +181,15 @@ where
fn test_ord_event_t() {
let first_on = Event {
tick: Tick(0),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
};
let first_off = Event {
tick: Tick(24),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
};
let second_on = Event {
tick: Tick(24),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
};
assert_eq!(first_on.cmp(&first_off), 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 kick_on = Event {
tick: Tick(0),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
};
let kick_off = Event {
tick: Tick(24),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
};
let simple_grid = EventGrid {
events: vec![kick_on, kick_off],
@ -270,11 +291,11 @@ fn test_concat_event_grid() {
events: vec![
Event {
tick: Tick(12),
event_type: NoteOn(HiHat),
event_type: NoteOn(Drum(HiHat)),
},
Event {
tick: Tick(24),
event_type: NoteOff(HiHat),
event_type: NoteOff(Drum(HiHat)),
},
],
start: Tick(12),
@ -283,7 +304,7 @@ fn test_concat_event_grid() {
assert_eq!(
input.concat(input.clone()),
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),
end: Tick(36)
}
@ -466,22 +487,22 @@ fn test_group_to_event_grid() {
};
let grid = 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) }
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: start_time,
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!(
// group_to_event_grid(
// flatten_group(group_or_delimited_group("(2,8x--)").unwrap().1).0.first().unwrap(),
// KickDrum,
// &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![
Event {
tick: Tick(12),
event_type: NoteOn(HiHat)
event_type: NoteOn(Drum(HiHat))
},
Event {
tick: Tick(24),
event_type: NoteOff(HiHat)
event_type: NoteOff(Drum(HiHat))
}
],
start: Tick(12),
@ -516,7 +537,7 @@ fn test_concat_grid() {
},
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]
fn next(&mut self) -> Option<Self::Item> {
let candidates: BTreeMap<Part, Event<Tick>> = [
let candidates: BTreeMap<DrumPart, Event<Tick>> = [
(KickDrum, self.kick.peek()),
(SnareDrum, self.snare.peek()),
(HiHat, self.hihat.peek()),
@ -605,7 +626,7 @@ fn test_event_iterator_impl() {
.0
.first()
.unwrap(),
KickDrum,
Drum(KickDrum),
&mut Tick(0),
);
let snare1 = group_to_event_grid(
@ -613,7 +634,7 @@ fn test_event_iterator_impl() {
.0
.first()
.unwrap(),
SnareDrum,
Drum(SnareDrum),
&mut Tick(0),
);
@ -630,19 +651,19 @@ fn test_event_iterator_impl() {
vec![
Event {
tick: Tick(0),
event_type: NoteOn(KickDrum)
event_type: NoteOn(Drum(KickDrum))
},
Event {
tick: Tick(48),
event_type: NoteOff(KickDrum)
event_type: NoteOff(Drum(KickDrum))
},
Event {
tick: Tick(48),
event_type: NoteOn(SnareDrum)
event_type: NoteOn(Drum(SnareDrum))
},
Event {
tick: Tick(96),
event_type: NoteOff(SnareDrum)
event_type: NoteOff(Drum(SnareDrum))
}
]
);
@ -660,11 +681,11 @@ fn test_event_iterator_impl() {
[
Event {
tick: Tick(0),
event_type: NoteOn(KickDrum)
event_type: NoteOn(Drum(KickDrum))
},
Event {
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.
fn merge_into_iterator(
groups: BTreeMap<Part, Groups>,
groups: &BTreeMap<DrumPart, Groups>,
time_signature: TimeSignature,
) -> EventIterator {
// 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
let converges_over_bars = time_signature
@ -695,12 +716,12 @@ fn merge_into_iterator(
// length limit in 128th notes
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) {
Some(groups) => {
let length_128th = length_map.get(part).unwrap();
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)
}
None => (EventGrid::empty(), 0),
@ -729,154 +750,154 @@ fn test_merge_into_iterator() {
let kick_events = vec![
Event {
tick: Tick(0),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(12),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(12),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(24),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(36),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(48),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(60),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(72),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(72),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(84),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(96),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(108),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(108),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(120),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(132),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(144),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(156),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(168),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
Event {
tick: Tick(168),
event_type: NoteOn(KickDrum),
event_type: NoteOn(Drum(KickDrum)),
},
Event {
tick: Tick(180),
event_type: NoteOff(KickDrum),
event_type: NoteOff(Drum(KickDrum)),
},
];
let snare_events = vec![
Event {
tick: Tick(24),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(48),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(96),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(120),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(24 + 144),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(48 + 144),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(96 + 144),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(120 + 144),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(24 + 288),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(48 + 288),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(96 + 288),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(120 + 288),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(24 + 144 * 3),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(48 + 144 * 3),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
Event {
tick: Tick(96 + 144 * 3),
event_type: NoteOn(SnareDrum),
event_type: NoteOn(Drum(SnareDrum)),
},
Event {
tick: Tick(120 + 144 * 3),
event_type: NoteOff(SnareDrum),
event_type: NoteOff(Drum(SnareDrum)),
},
];
let four_fourth = TimeSignature::from_str("4/4").unwrap();
let flattened_kick_and_snare = merge_into_iterator(
BTreeMap::from_iter([
&BTreeMap::from_iter([
(KickDrum, groups("16xx-x-xx-").unwrap().1),
(SnareDrum, groups("8-x--x-").unwrap().1),
]),
@ -886,7 +907,7 @@ fn test_merge_into_iterator() {
assert_eq!(
merge_into_iterator(
BTreeMap::from_iter([(KickDrum, groups(kick_group).unwrap().1)]),
&BTreeMap::from_iter([(KickDrum, groups(kick_group).unwrap().1)]),
four_fourth
)
.collect::<Vec<Event<Tick>>>(),
@ -894,7 +915,7 @@ fn test_merge_into_iterator() {
);
assert_eq!(
merge_into_iterator(
BTreeMap::from_iter([(SnareDrum, groups(snare_group).unwrap().1)]),
&BTreeMap::from_iter([(SnareDrum, groups(snare_group).unwrap().1)]),
four_fourth
)
.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.
pub fn create_smf<'a>(
groups: BTreeMap<Part, Groups>,
groups: BTreeMap<DrumPart, Groups>,
time_signature: TimeSignature,
text: &'a str,
tempo: u16,
add_bass: bool
) -> 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
// 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.
@ -945,12 +967,13 @@ pub fn create_smf<'a>(
/// Multi-track vectors of MIDI events in `midly` format.
///
fn create_tracks<'a>(
parts_and_groups: BTreeMap<Part, Groups>,
parts_and_groups: BTreeMap<DrumPart, Groups>,
time_signature: TimeSignature,
text_event: &'a str,
midi_tempo: MidiTempo,
add_bass: bool
) -> 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 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 = 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.
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Midi {
channel: 9.into(),
message: MidiMessage::ProgramChange { program: 0.into() },
},
});
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::TrackName(b"Drumkit")),
});
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::InstrumentName(b"Drumkit")),
});
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::MidiChannel(10.into())),
});
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::MidiPort(10.into())),
});
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::Tempo(midi_tempo.0)),
});
let (midi_time_signature_numerator, midi_time_signature_denominator) = time_signature.to_midi();
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::TimeSignature(
midi_time_signature_numerator,
@ -1004,34 +1027,64 @@ fn create_tracks<'a>(
)),
});
drums.push(TrackEvent {
drums_track.push(TrackEvent {
delta: 0.into(),
kind: TrackEventKind::Meta(MetaMessage::Text(text_event.as_bytes())),
});
for event in event_grid.events {
let midi_message = match event.event_type {
NoteOn(part) => MidiMessage::NoteOn {
key: part.to_midi_key(),
vel: 127.into(),
},
NoteOff(part) => MidiMessage::NoteOff {
key: part.to_midi_key(),
vel: 127.into(),
},
};
drums.push(TrackEvent {
delta: u28::from(event.tick.0 as u32),
kind: TrackEventKind::Midi {
channel: u4::from(10),
message: midi_message,
},
})
}
drums.push(TrackEvent {
delta: drums.last().unwrap().delta,
let map_notes = |grid: EventGrid<Delta>, track: &mut Vec<TrackEvent>| {
for event in grid.events {
let midi_message = match event.event_type {
NoteOn(part) => MidiMessage::NoteOn {
key: part.to_midi_key(),
vel: 127.into(),
},
NoteOff(part) => MidiMessage::NoteOff {
key: part.to_midi_key(),
vel: 127.into(),
},
};
track.push(TrackEvent {
delta: u28::from(event.tick.0 as u32),
kind: TrackEventKind::Midi {
channel: u4::from(10),
message: midi_message,
},
})
}
};
map_notes(event_grid, &mut drums_track);
drums_track.push(TrackEvent {
delta: drums_track.last().unwrap().delta,
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]
}
}