beatblox_midi/parsing/
mod.rs

1pub mod duration;
2pub mod symbols;
3
4use duration::NoteDuration;
5use crate::Midi;
6use crate::parsing::duration::DurationType;
7use crate::parsing::duration::POSSIBLE_NOTE_LENGTHS;
8use crate::parsing::symbols::NoteModifier;
9use crate::parsing::symbols::NoteWrapper;
10use crate::parsing::symbols::TimeSignature;
11use std::collections::VecDeque;
12
13/// Represents the content of a midi track.
14#[derive(Clone)]
15pub struct Track {
16    /// The name of the track.
17    pub name: String,
18    /// A vector of all the notes played in the track.
19    pub notes: Vec<NoteWrapper>
20}
21
22/// Represents a raw note data taken from the midi file.
23#[derive(Clone, Copy)]
24struct RawNoteData {
25    key: u8,
26    onset: u32,
27    vel: u8,
28}
29
30/// Gets the number of ticks in each beat.
31pub fn get_ticks_per_beat(header: &midly::Header) -> f32 {
32    let midly::Header { format: _, timing } = header;
33    if let midly::Timing::Metrical(x) = timing {
34        let ticks_per_beat: u16 = (*x).into();
35        return ticks_per_beat as f32;
36    }
37    panic!("Timing format not supported");
38}
39
40/// Gets the tempo of a midi file.
41pub fn get_bpm(track: &Vec<midly::TrackEvent>) -> u32 {
42    for event in track {
43        if let midly::TrackEventKind::Meta(midly::MetaMessage::Tempo(tempo)) = event.kind {
44            let microseconds_per_beat: u32 = tempo.into();
45            return microseconds_per_beat / 1000000 * 60;
46        }
47    }
48    return 0;
49}
50
51/// Returns all time signatures in the midi file.
52pub fn get_time_signature(track: &Vec<midly::TrackEvent>) -> Vec<TimeSignature> {
53    let mut time_signatures: Vec<TimeSignature> = Vec::new();
54    let mut cur_time: u32 = 0;
55    for event in track {
56        let delta_t: u32 = event.delta.into();
57        cur_time += delta_t;
58        if let midly::TrackEventKind::Meta(message) = event.kind {
59            if let midly::MetaMessage::TimeSignature(numerator, denominator, _, _) = message {
60                time_signatures.push(TimeSignature {
61                    beat_count: numerator,
62                    beat_type: denominator.into(),
63                    time_of_occurance: cur_time,
64                });
65            }
66        }
67    }
68    return time_signatures;
69}
70
71/// Loads all the tracks in a midi file.
72/// 
73/// `midi` holds the newly created `Midi` object.
74/// 
75/// `smf` holds the `midly::Smf` object being used to parse through the midi file.
76/// 
77/// The `precision` parameter allows the user to set the degree of precision they would like
78/// when parsing. Any notes shorter than the value specified in the `precision` parameter
79/// will be grouped as a chord.
80/// 
81/// The `triplet` parameter indicated if the user wants to scan for triplets. Scanning for
82/// triplets requires extra resources.
83pub fn load_tracks(midi: &mut Midi, smf: &midly::Smf, precision: &DurationType, triplet: bool) {
84    let tmp = midi.clone();
85    for track in &smf.tracks {
86        midi.tracks.push(parse_track(&tmp, track, precision, triplet));
87    }
88}
89
90/// A helper function to build the `Track Object`.
91fn parse_track(
92    midi: &Midi, 
93    track: &Vec<midly::TrackEvent>, 
94    precision: &DurationType,
95    triplet: bool
96) -> Track {
97    Track { 
98        name: get_name(track), 
99        notes: get_notes(midi, track, precision, triplet),
100    }
101}
102
103/// Gets the name of a midi track.
104fn get_name(track: &Vec<midly::TrackEvent>) -> String {
105    for event in track {
106        if let midly::TrackEventKind::Meta(midly::MetaMessage::InstrumentName(s)) = event.kind {
107            let raw_string: Vec<u8> = s.to_vec();
108            return String::from_utf8(raw_string).unwrap();
109        }
110    }
111    return String::from("");
112}
113
114/// Gets all the notes in a midi track. 
115/// 
116/// Does this by formatting the raw midi data.
117fn get_notes(
118    midi: &Midi, 
119    track: &Vec<midly::TrackEvent>, 
120    precision: &DurationType,
121    triplet: bool
122) -> Vec<NoteWrapper> {
123    let beat_type = midi.time_signatures[0].beat_type;
124    let precision_beat = precision.get_beat_count(beat_type);
125    let divisions = if triplet { 
126        4.0 / precision_beat / 2.0 * 1.5 
127    } else { 
128        1.0 / precision_beat
129    };
130    let quantized_note_data = quantize(midi, track, divisions);
131
132    let mut possible_triplets = VecDeque::new();
133    if triplet {
134        possible_triplets = get_triplets(&quantized_note_data);
135    }
136
137    let mut complete_beat_grid = Vec::new();
138    for (mut beat_grid, _) in quantized_note_data {
139        complete_beat_grid.append(&mut beat_grid);
140    }
141
142    let mut notes = Vec::new();
143    let mut beat_count = 0;
144    let mut i = 0;
145    let mut length = 0;
146    let mut cur_note: &Vec<(u8, u8)> = &Vec::new();
147    while i < complete_beat_grid.len() {
148        if i % divisions as usize == 0 {
149            beat_count += 1;
150            if possible_triplets.len() != 0 && possible_triplets[0] == beat_count {
151                let x = i + divisions as usize;
152                let beat_data = &Vec::from(&complete_beat_grid[i..x]);
153                notes.push(gen_triplet(beat_data, beat_type));
154                possible_triplets.pop_front();
155                i += divisions as usize;
156                length = 0;
157                continue;
158            }
159        }
160        if complete_beat_grid[i].len() != 0 {
161            if length != 0 {
162                let beat_length = length as f32 / divisions;
163                println!("{} / {} = {}", length, divisions, beat_length);
164                notes.push(gen_wrapper(cur_note, beat_length, beat_type));
165            }
166            length = 0;
167            cur_note = &complete_beat_grid[i];
168        }
169        length += 1;
170        i += 1;
171    }
172
173    return notes;
174}
175
176/// This function finds all the triplets in a piece of music and returns a vector containing what
177/// beats they are on.
178/// 
179/// Precondition: the note data must have already been quantized.
180fn get_triplets(quantized_note_data: &Vec<(Vec<Vec<(u8, u8)>>, u8)>) -> VecDeque<u32> {
181    let mut triplets = VecDeque::new();
182    for i in 0..quantized_note_data.len() {
183        if is_possible_triplet(&quantized_note_data[i]) {
184            triplets.push_back(i as u32 + 1);
185        }
186    }
187    return triplets;
188}
189
190/// Determines if a group of notes can be a triplet.
191/// 
192/// `beat_data` is a vector of all the subdivisions of the current beat. Each element in the vector
193/// is another vector containing the key and velocity of the notes that start on that subdivision.
194fn is_possible_triplet(beat_data: &(Vec<Vec<(u8, u8)>>, u8)) -> bool {
195    let (beat_grid, note_count) = beat_data;
196    if *note_count != 3 {
197        return false;
198    }
199
200    let mut beat_length: [u8; 3]= [0, 0, 0];
201    let mut i = 0;
202    for data_point_index in 0..3 {
203        beat_length[data_point_index] += 1;
204        i +=1;
205        while i < beat_grid.len() && beat_grid[i].len() == 0 {
206            beat_length[data_point_index] += 1;
207            i += 1;
208        }
209    }
210    beat_length.sort();
211
212    return beat_length[2] - beat_length[0] <= 2 && beat_length[2] as usize > beat_grid.len() / 4;
213}
214
215/// This function generates a note wrapper for a triplet. The `duration` for the note will be
216/// the appropriate dupal counterpart. For example, eight note triplets will be stored as eigth 
217/// notes in a triplet wrapper.
218fn gen_triplet(beat_data: &Vec<Vec<(u8, u8)>>, beat_type: u8) -> NoteWrapper {
219    let mut triplet = Vec::new();
220    for div in beat_data {
221        if div.len() > 0 {
222            triplet.push(gen_wrapper(div, 0.5, beat_type));
223        }
224    }
225    return NoteWrapper::ModifiedNote(NoteModifier::Triplet(triplet));
226}
227
228/// This function generates a note wrapper for a given note or set of notes.
229/// 
230/// If `cur_note` as a length of 1, the NoteWrapper is that of a single note. Otherwize, a chord is
231/// generated made up of all the entries in `cur_note`.
232/// 
233/// `cur_note.len()` must be greater than 0.
234fn gen_wrapper(cur_note: &Vec<(u8, u8)>, beat_length: f32, beat_type: u8) -> NoteWrapper {
235    let mut chord = Vec::new();
236    for note_data in cur_note {
237        let value = note_data.0;
238        let velocity = note_data.1;
239        if value != 255 { 
240            chord.push(parse_note_data((value, velocity), beat_length, beat_type));
241        }
242    }
243    if chord.len() == 0 {
244        let duration = DurationType::beat_type_map(beat_length, beat_type);
245        return NoteWrapper::build_note_wrapper(255, duration, 0);
246    } else if chord.len() == 1 {
247        return chord[0].clone();
248    }
249    return NoteWrapper::ModifiedNote(NoteModifier::Chord(chord));
250} 
251
252/// A helper function for building a `NoteWrapper`.
253fn parse_note_data((value, velocity): (u8, u8), beat_length: f32, beat_type: u8) -> NoteWrapper {
254    let duration = DurationType::beat_type_map(beat_length, beat_type);
255    if duration.duration == NoteDuration::NaN {
256        return NoteWrapper::ModifiedNote(get_tied_note((value, beat_length, velocity), beat_type));
257    } else {
258        return NoteWrapper::build_note_wrapper(value, duration, velocity);
259    }
260}
261
262/// This snaps all of the notes found in `track` to a grid. 
263/// 
264/// The function returns a vector of tuplets (representing beats) made up of a vector and a number. 
265/// The vector in the tuplet represents the grid of subdivisions for each beat and the number shows
266/// how many unique onsets are in that beat.
267fn quantize(
268    midi: &Midi, 
269    track: &Vec<midly::TrackEvent>, 
270    divisions: f32
271) -> Vec<(Vec<Vec<(u8, u8)>>, u8)> {
272    let mut notes = Vec::new();
273
274    let mut ticks_per_beat = midi.ticks_per_beat;
275    let mut scalar = 1;
276    if midi.ticks_per_beat % 12.0 != 0.0 {
277        scalar = 12;
278        ticks_per_beat *= 12.0;
279    }
280
281    let mut flag = true;
282    let mut raw_note_data = get_raw_note_data(track, ticks_per_beat, scalar);
283    if raw_note_data.len() == 0 {
284        return Vec::new();
285    }
286
287    let mut cur_beat = ticks_per_beat as u32;
288    let mut note = raw_note_data.pop_front().unwrap();
289    while flag {
290        let mut beat_container = vec![Vec::new(); divisions as usize];
291        let mut note_count = 0;
292        while note.onset < cur_beat {
293            let onset = note.onset - (cur_beat - ticks_per_beat as u32);
294            let position = (onset as f32 * (1.0 / ticks_per_beat) * divisions).floor() as usize;
295            beat_container[position].push((note.key, note.vel));
296            note_count += 1;
297            if raw_note_data.is_empty() {
298                flag = false;
299                break;
300            }
301            note = raw_note_data.pop_front().unwrap();
302        }
303        cur_beat += ticks_per_beat as u32;
304        notes.push((beat_container, note_count));
305    }
306
307    if notes[0].0[0].len() == 0 {
308        notes[0].0[0].push((255, 0));
309        notes[0].1 += 1;
310    }
311
312    return notes;
313}
314
315/// Gets the raw note data in a midi track.
316fn get_raw_note_data(
317    track: &Vec<midly::TrackEvent>, 
318    ticks_per_beat: f32, 
319    scalar: u32
320) -> VecDeque<RawNoteData> {
321    let mut cur_time: u32 = 0;
322    let mut cur_velocity: u8 = 0;
323    let mut note_on_time: u32 = 0;
324    let mut note_off_time: u32 = 0;
325    let mut data: VecDeque<RawNoteData> = VecDeque::new();
326
327    for event in track {
328        let delta_t: u32 = event.delta.into();
329        cur_time += delta_t * scalar;
330
331        if let midly::TrackEventKind::Midi { channel: _, message } = event.kind {
332            if let midly::MidiMessage::NoteOn {key: _, vel } = message {
333                cur_velocity = vel.into();
334                note_on_time = cur_time;
335                if note_on_time - note_off_time >= (ticks_per_beat *  0.125).ceil() as u32 {
336                    data.push_back(RawNoteData {
337                        key: 255,
338                        onset: note_off_time,
339                        vel: 0,
340                    });
341                }
342            }
343            else if let midly::MidiMessage::NoteOff { key , vel: _ } = message {
344                data.push_back(RawNoteData {
345                    key: key.into(),
346                    onset: note_on_time,
347                    vel: cur_velocity,
348                });
349                note_off_time = cur_time;
350            }
351        }
352    }
353
354    return data;
355}
356
357fn get_tied_note((value, duration, velocity): (u8, f32, u8), beat_type: u8) -> NoteModifier {
358    let mut notes: Vec<NoteWrapper> = Vec::new();
359    let mut remaining_beats: f32 = duration;
360    while remaining_beats > 0.0 {
361        let nested_beat_value = get_nested_beat_value(remaining_beats);
362        let new_duration = DurationType::beat_type_map(nested_beat_value, beat_type);
363        remaining_beats -= nested_beat_value;
364        notes.push(NoteWrapper::build_note_wrapper(value, new_duration, velocity));
365    }
366    return NoteModifier::TiedNote(notes);
367}
368
369/// A helper function for parsing tied notes.
370fn get_nested_beat_value(beats: f32) -> f32 {
371    for i in 1..POSSIBLE_NOTE_LENGTHS.len() {
372        if POSSIBLE_NOTE_LENGTHS[i] > beats {
373            return POSSIBLE_NOTE_LENGTHS[i - 1];
374        }
375    }
376    return POSSIBLE_NOTE_LENGTHS[POSSIBLE_NOTE_LENGTHS.len() - 1];
377}