def parse_measure(self, measure, divisions): """ Parses a measure according to the pitch duration token parsing process. :param measure: a measure dict in the format created by src/processing/conversion/xml_to_json.py :param divisions: the number of divisions a quarter note is split into in this song's MusicXML representation :return: a dict containing a list of groups containing the pitch numbers, durations, and bar positions for each harmony in a measure """ parsed_measure = {"groups": []} tick_idx = 0 for group in measure["groups"]: parsed_group = { "harmony": {}, "pitch_numbers": [], "duration_tags": [], "bar_positions": [] } harmony = Harmony(group["harmony"]) parsed_group["harmony"]["root"] = harmony.get_one_hot_root() parsed_group["harmony"][ "pitch_classes"] = harmony.get_seventh_pitch_classes_binary() dur_ticks_list = [] for note_dict in group["notes"]: # want monophonic, so we'll just take the top note if "chord" in note_dict.keys() or "grace" in note_dict.keys(): continue else: pitch_num, dur_tag, dur_ticks = self.parse_note( note_dict, divisions) parsed_group["pitch_numbers"].append(pitch_num) parsed_group["duration_tags"].append(dur_tag) dur_ticks_list.append(dur_ticks) unnorm_barpos = [ tick_idx + sum(dur_ticks_list[:i]) for i in range(len(dur_ticks_list)) ] bar_positions = [ int((dur_ticks / (4 * divisions)) * 96) for dur_ticks in unnorm_barpos ] parsed_group["bar_positions"] = bar_positions parsed_measure["groups"].append(parsed_group) tick_idx += sum(dur_ticks_list) return parsed_measure
def parse_measure(self, measure, scale_factor, prev_harmony): """ For a measure, returns a set of ticks grouped by associated harmony in. :param measure: a measure dict in the format created by src/processing/conversion/xml_to_json.py :param scale_factor: the scale factor between XML divisions and midi ticks :param prev_harmony: a reference to the last harmony used in case a measure has none :return: a dict containing a list of groups that contains a harmony and the midi ticks associated with that harmony """ parsed_measure = {"groups": []} total_ticks = 0 for group in measure["groups"]: # Set note value for each tick in the measure group_ticks = [] for note in group["notes"]: if not "duration" in note: print("Skipping grace note...") continue divisions = int(note["duration"]["text"]) num_ticks = int(scale_factor * divisions) index = self.get_note_index(note) for i in range(num_ticks): tick = [0 for _ in range(MIDI_RANGE)] tick[index] = 1 group_ticks.append(tick) total_ticks += len(group_ticks) if not group["harmony"]: parsed_measure["groups"].append({ "harmony": prev_harmony, "ticks": group_ticks }) else: harmony = Harmony(group["harmony"]) harmony_dict = { "root": harmony.get_one_hot_root(), "pitch_classes": harmony.get_seventh_pitch_classes_binary() } prev_harmony = harmony_dict parsed_measure["groups"].append({ "harmony": harmony_dict, "ticks": group_ticks }) # Mitigate ticks for chords that occur mid-note for i, group in enumerate(parsed_measure["groups"]): try: if not group["ticks"]: # Handle the case of no harmony at the start of the bar if not 0 in measure["harmonies_start"]: measure["harmonies_start"].insert(0, 0) correct_len_of_prev_harmony = int( scale_factor * (measure["harmonies_start"][i] - measure["harmonies_start"][i - 1])) group["ticks"].extend(parsed_measure["groups"][ i - 1]["ticks"][correct_len_of_prev_harmony:]) parsed_measure["groups"][ i - 1]["ticks"] = parsed_measure["groups"][ i - 1]["ticks"][:correct_len_of_prev_harmony] except: import pdb pdb.set_trace() raise ( "No ticks in the first group of a measure! (in fix for chords mid-note)" ) if total_ticks > TICKS_PER_BEAT * 4: raise Exception("OH NO BRO. YOUR TICKS ARE TOO MUCH YO") i = 0 while total_ticks < TICKS_PER_BEAT * 4: group = parsed_measure["groups"][i] spacer_tick = [0 for _ in range(MIDI_RANGE)] spacer_tick[0] = 1 # fill with rests group["ticks"].append(spacer_tick) i = (i + 1) % len(parsed_measure["groups"]) total_ticks += 1 parsed_measure["num_ticks"] = total_ticks return parsed_measure, prev_harmony