def _read_tab_file_path(chords_from_tab_file_path: str, alphabet: ChordAlphabet) -> (int, np.array, np.array, np.array): """ Load chord information from chords_from_tab_file_path :param chords_from_tab_file_path: File that contains chord information :return: (nr_of_chords_in_tab, chord_ids, is_first_in_line, is_last_in_line) """ # Load .txt file consisting of: [line_nr, segment_nr, system_nr, chord_x, chord_str] (UntimedChordSequence) untimed_chord_sequence = read_untimed_chord_sequence(chords_from_tab_file_path) nr_of_chords_in_tab = len(untimed_chord_sequence.untimed_chord_sequence_item_items) # If we found less than 5 chords, we will not use this tab if nr_of_chords_in_tab < 5: return nr_of_chords_in_tab, [], [], [] # Chord id's line_nrs = [ucs_item.line_nr for ucs_item in untimed_chord_sequence.untimed_chord_sequence_item_items] chord_ids = np.zeros(nr_of_chords_in_tab).astype(int) for i in range(nr_of_chords_in_tab): chord_ids[i] = alphabet.get_index_of_chord_in_alphabet( Chord.from_harte_chord_string(untimed_chord_sequence.untimed_chord_sequence_item_items[i].chord_str)) # Array: is this chord first and/or last in its line? is_first_in_line = np.zeros(nr_of_chords_in_tab).astype(int) is_first_in_line[0] = 1 for i in range(1, nr_of_chords_in_tab): if line_nrs[i] != line_nrs[i - 1]: is_first_in_line[i] = 1 is_last_in_line = np.zeros(nr_of_chords_in_tab).astype(int) is_last_in_line[-1] = 1 for i in range(nr_of_chords_in_tab - 1): if line_nrs[i] != line_nrs[i + 1]: is_last_in_line[i] = 1 return nr_of_chords_in_tab, chord_ids, is_first_in_line, is_last_in_line
def _add_chord_from_str(self, chord_str: str, chord_x: int): """ Find the Chord from chord_str and add it to self.chords :param chord_str: String of the Chord :param chord_x: Integer index of the Chord """ chord = Chord.from_common_tab_notation_string(chord_str) if chord is not None: self.chords.append((chord_x, chord))
def _chord_label_to_chord_str(chord_label: int, alphabet: ChordAlphabet) -> str: """ Translate the integer chord label to a chord string :param chord_label: Chord index in the chord_vocabulary (integer) :return: Chord string (str) """ if chord_label == 0: return 'N' return str(Chord.from_common_tab_notation_string(alphabet.alphabet_list[chord_label]))
def _write_final_labels(final_labels, lab_path, alphabet): """ Write the row of final labels (after data fusion) from the chord matrix to a .json file at lab_path :param final_labels: The row of final labels after data fusion :param lab_path: The path to write the final .json file to :param alphabet: The chord vocabulary (used to translate chord indices to chord strings) """ # First, we fill the write_labels list with (start_time, end_time, chord_string) 3-tuples write_labels = [] current_beat = 1 final_list = [] start_time = -1 last_chord = '' last_added = True for i in range(len(final_labels)): if final_labels[i] == last_chord: last_added = False else: if not last_added: # Write previous chord write_labels.append((start_time, float(i) / 100, last_chord)) # Set parameters for next start_time = float(i) / 100 last_chord = final_labels[i] if not last_added: # The last chord has not been added yet, so we do it now write_labels.append( (start_time, float(len(final_labels) - 1) / 100, last_chord)) # Now write the chords to the .json file in .json format with open(lab_path, 'w') as write_file: for write_label in write_labels: chord_string = str( Chord.from_common_tab_notation_string( alphabet[write_label[2]])) if chord_string == 'None': chord_string = 'N' dictionary = {} dictionary['current_beat'] = current_beat dictionary['current_beat_time'] = write_label[0] dictionary['estimated_chord'] = chord_string final_list.append(dictionary) current_beat += 1 json.dump(final_list, write_file, indent=4)
def add_tab_block(self, tab_block_str: [Line]): """ Process the content of a tab block (6 subsequent lines of LineType Tablature), add chords to self.chords :param tab_block_str: Six subsequent Lines of the LineType Tablature """ smallest_line_length = min( [len(block_line_str) for block_line_str in tab_block_str]) for chord_x in range(0, smallest_line_length): # Read all six strings together per x-coordinate finger_list = [] for tab_block_line_str in reversed(tab_block_str): finger_list.append(tab_block_line_str[chord_x]) fingers = ''.join(finger_list) if re.match(r'[x0-9]{6}', fingers) and fingers != 'xxxxxx': # A chord sounds at this x position! fingering = Fingering('1', fingers[0], fingers[1], fingers[2], fingers[3], fingers[4], fingers[5]) fingering_chroma = fingering.get_extended_chroma_vector() # Find nearest chord from vocabulary smallest_distance = 2 best_matching_chord_str = 'X' for chord_template in ALL_CHORDS_LIST.chord_templates: cosine_distance = ssd.cosine(fingering_chroma[:12], chord_template.chroma_list) if cosine_distance < smallest_distance: smallest_distance = cosine_distance best_matching_chord_str = str( PitchClass( chord_template.key))[0] + chord_template.mode chord = Chord.from_common_tab_notation_string( best_matching_chord_str) # Fix bass note bass_note = PitchClass(fingering_chroma[12]) bass_interval = Interval.from_pitch_class_distances( chord.root_note, bass_note) chord.bass_degree = bass_interval # Add to self.chords self.chords.append((chord_x, chord))
def find_most_likely_chord(self, chord_vocabulary: ChordVocabulary) -> Tuple[ChordAnnotationItem, float]: """ Find the chord to which the chroma of this event matches best. :param chord_vocabulary: List of all chords for classification """ best_matching_chord_score = -2.99 best_matching_chord_str = 'X' best_key_note_weight = 0 for chord_template in chord_vocabulary.chord_templates: chord_score = self._score_compared_to_template(chord_template) if chord_score > best_matching_chord_score or (chord_score == best_matching_chord_score and self.chroma[chord_template.key] > best_key_note_weight): best_matching_chord_score = chord_score best_matching_chord_str = str(PitchClass(chord_template.key)) + chord_template.mode best_key_note_weight = self.chroma[chord_template.key] if best_matching_chord_str == 'X': most_likely_chord = None else: most_likely_chord = Chord.from_common_tab_notation_string(best_matching_chord_str) return ChordAnnotationItem(self.start_time, self.end_time, most_likely_chord), best_matching_chord_score
def _write_final_labels(final_labels, lab_path, alphabet): """ Write the row of final labels (after data fusion) from the chord matrix to a .lab file at lab_path :param final_labels: The row of final labels after data fusion :param lab_path: The path to write the final lab file to :param alphabet: The chord vocabulary (used to translate chord indices to chord strings) """ # First, we fill the write_labels list with (start_time, end_time, chord_string) 3-tuples write_labels = [] start_time = -1 last_chord = '' last_added = True for i in range(len(final_labels)): if final_labels[i] == last_chord: last_added = False else: if not last_added: # Write previous chord write_labels.append((start_time, float(i) / 100, last_chord)) # Set parameters for next start_time = float(i) / 100 last_chord = final_labels[i] if not last_added: # The last chord has not been added yet, so we do it now write_labels.append( (start_time, float(len(final_labels) - 1) / 100, last_chord)) # Now write the chords to the .lab file in Harte format with open(lab_path, 'w') as write_file: for write_label in write_labels: chord_string = str( Chord.from_common_tab_notation_string( alphabet[write_label[2]])) if chord_string == 'None': chord_string = 'N' write_file.write('{0} {1} {2}\n'.format(str(write_label[0]), str(write_label[1]), chord_string))
def _load_lab_file_into_chord_matrix(self, lab_path: str, i: int): """ Load a chord file (in Harte's chord annotation format) into a chord matrix. :param lab_path: Path to the .lab file with chord annotation/estimation :param i: Index to the row in the chord_matrix to fill """ with open(lab_path, 'r') as read_file: # Read chord annotation from file chord_annotation = read_file.readlines() chord_annotation = [x.rstrip().split() for x in chord_annotation] for y in chord_annotation: # Parse start and end time, retrieve index of chord start_time, end_time = float(y[0]), float(y[1]) chord = Chord.from_harte_chord_string(y[2]) chord_label_alphabet_index = self._chord_alphabet.get_index_of_chord_in_alphabet( chord) # Add chord index to each entry in the chord_matrix that is between start and end time for s in range( int(start_time * 100), min(int(end_time * 100), self._nr_of_samples - 1)): self.array[i, s] = chord_label_alphabet_index
def train(chord_vocabulary: ChordVocabulary, train_songs: Dict[int, Song]) -> HMMParameters: """ Train the HMM parameters on training_set for the given chords_list vocabulary :param chord_vocabulary: List of chords in our vocabulary :param train_songs: Set of songs for training :return: HMM Parameters """ # Convert the vocabulary to a ChordAlphabet alphabet = ChordAlphabet(chord_vocabulary) alphabet_size = len(alphabet.alphabet_list) # Initialize chord_beat_matrix_per_chord: a list with |chord_vocabulary| x |beats| list for each chord chroma_beat_matrix_per_chord = [[] for _ in alphabet.alphabet_list] # Initialize transition_matrix and init_matrix trans = np.ones((alphabet_size, alphabet_size)) init = np.ones(alphabet_size) # Iterate over the songs; fill chroma_beat_matrix_per_chord, init_matrix and transition_matrix for train_song_key, train_song in train_songs.items(): train_song.audio_features_path = filehandler.get_full_audio_features_path( train_song_key) if train_song.audio_features_path != '': # We have audio features and labels for this song; load them (otherwise ignore the song) features = np.load(train_song.audio_features_path) chord_index_list = [] # Iterate over the beats, fill chroma_beat_matrix_per_chord and chord_index_list for frame_index in range(features.shape[0]): chroma = features[frame_index, 1:13].astype(float) chord_index = alphabet.get_index_of_chord_in_alphabet( Chord.from_harte_chord_string(features[frame_index, 13])) chord_index_list.append(chord_index) chroma_beat_matrix_per_chord[chord_index].append(chroma) # Add first chord to init_matrix init[chord_index_list[0]] += 1 # Add each chord transition to transition_matrix for i in range(0, len(chord_index_list) - 1): trans[chord_index_list[i], chord_index_list[i + 1]] += 1 # Normalize transition and init matrices init = init / sum(init) trans = np.array([trans[i] / sum(trans[i]) for i in range(alphabet_size)]) # Calculate mean and covariance matrices obs_mu = np.zeros((alphabet_size, 12)) obs_sigma = np.zeros((alphabet_size, 12, 12)) for i in range(alphabet_size): chroma_beat_matrix_per_chord[i] = np.array( chroma_beat_matrix_per_chord[i]).T obs_mu[i] = np.mean(chroma_beat_matrix_per_chord[i], axis=1) obs_sigma[i] = np.cov(chroma_beat_matrix_per_chord[i], ddof=0) # Calculate additional values so we can calculate the emission probability more easily twelve_log_two_pi = 12 * np.log(2 * np.pi) log_det_sigma = np.zeros(alphabet_size) sigma_inverse = np.zeros(obs_sigma.shape) for i in range(alphabet_size): log_det_sigma[i] = np.log(np.linalg.det(obs_sigma[i])) sigma_inverse[i] = np.mat(np.linalg.pinv(obs_sigma[i])) return HMMParameters(alphabet=alphabet, trans=trans, init=init, obs_mu=obs_mu, obs_sigma=obs_sigma, log_det_sigma=log_det_sigma, sigma_inverse=sigma_inverse, twelve_log_two_pi=twelve_log_two_pi, trained_on_keys=list(train_songs.keys()))