From 1eaecd0180b0796322ce5c41863500bd2f9e1446 Mon Sep 17 00:00:00 2001 From: Denis Redozubov Date: Tue, 27 Jun 2023 17:11:03 +0400 Subject: [PATCH] Add -B option: adds bass track that follows the kick drum --- Cargo.toml | 1 + src/bin/main.rs | 56 +++++++--- src/midi/core.rs | 271 ++++++++++++++++++++++++++++------------------- 3 files changed, 202 insertions(+), 126 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46f931b..29f0bee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "poly" +description = "Polyrhythmically-inclinded Midi Drum generator" version = "0.1.0" edition = "2021" diff --git a/src/bin/main.rs b/src/bin/main.rs index f83a5dd..98e19bc 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -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, + + #[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, - part: Part, - patterns: &mut BTreeMap, + part: DrumPart, + patterns: &mut BTreeMap, ) -> () { match cli { None => {} @@ -63,7 +67,12 @@ fn validate_and_parse_part( } } -fn create_text_description(kick: &Option, snare: &Option, hihat: &Option, crash: &Option) -> String { +fn create_text_description( + kick: &Option, + snare: &Option, + hihat: &Option, + crash: &Option, +) -> 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); diff --git a/src/midi/core.rs b/src/midi/core.rs index be44671..ad16ee4 100644 --- a/src/midi/core.rs +++ b/src/midi/core.rs @@ -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 { 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 = 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 { - let candidates: BTreeMap> = [ + let candidates: BTreeMap> = [ (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, + groups: &BTreeMap, time_signature: TimeSignature, ) -> EventIterator { // Maps a drum part to a number of 128th notes - let length_map: BTreeMap = groups.iter().map(|(k, x)| (*k, x.to_128th())).collect(); + let length_map: BTreeMap = 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::>>(), @@ -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::>>(), @@ -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, + groups: BTreeMap, 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, + parts_and_groups: BTreeMap, time_signature: TimeSignature, text_event: &'a str, midi_tempo: MidiTempo, + add_bass: bool ) -> Vec>> { - 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> = 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, track: &mut Vec| { + 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] + } }