def evaluate_midis(all_songs) -> None: """ Evaluate all lab files based on MIDI alignment and chord estimation :param all_songs: All songs in the data set """ for segmentation_type in 'bar', 'beat': result_csv_path = filehandler.MIDILABS_RESULTS_PATHS[segmentation_type] if not path.isfile(result_csv_path): # Results were not calculated yet with open(result_csv_path, 'w') as write_file: for song_key in all_songs: song = all_songs[song_key] for midi_path in song.full_midi_paths: midi_name = filehandler.get_file_name_from_full_path( midi_path) alignment_score = \ decibel.import_export.midi_alignment_score_io.read_chord_alignment_score(midi_name) chord_probability = filehandler.read_midi_chord_probability( segmentation_type, midi_name) midi_lab_path = filehandler.get_full_midi_chord_labs_path( midi_name, segmentation_type) # Calculate CSR and write csr, overseg, underseg, seg = evaluate( song.full_ground_truth_chord_labs_path, midi_lab_path) write_file.write( '{0};{1};{2};{3};{4};{5};{6};{7};{8}\n'.format( str(song_key), str(song.duration), str(midi_name), str(alignment_score), str(chord_probability), str(csr), str(overseg), str(underseg), str(seg)))
def synthesize_midi_to_wav(midi_file_path_from: str, sampling_rate: int = 22050): """ Converts a midi file, specified to its path, to a waveform and writes the result as a wav file :param midi_file_path_from: Path to the midi file which will be converted :param sampling_rate: Sampling rate of the audio """ midi_file_name = fh.get_file_name_from_full_path(midi_file_path_from) wav_file_path_to = fh.get_full_synthesized_midi_path(midi_file_name) midi_object = pretty_midi.PrettyMIDI(midi_file_path_from) midi_audio = midi_object.fluidsynth(sampling_rate, fh.SOUND_FONT_PATH) librosa.output.write_wav(wav_file_path_to, midi_audio, sampling_rate)
def align_midi(audio_cqt: np.ndarray, audio_times: np.ndarray, full_synthesized_midi_path: str, full_alignment_write_path: str, alignment_parameters: Optional[AlignmentParameters] = None): """ Align audio (specified by CQT) to synthesized MIDI (specified by path), return path and score of the alignment :param alignment_parameters: Parameters for alignment :param audio_cqt: The CQT of the audio of the alignment :param audio_times: Array of times of the audio (from compute_cqt function) :param full_synthesized_midi_path: The path to the synthesized MIDI file :param full_alignment_write_path: The path to write the alignment to """ # Make sure to have alignment parameters if alignment_parameters is None: alignment_parameters = AlignmentParameters() # Open the synthesized midi file midi_audio, _ = librosa.load(full_synthesized_midi_path, sr=alignment_parameters.sampling_rate) # Compute log-magnitude CQT of the synthesized midi file midi_cqt, midi_times = _compute_cqt(midi_audio, alignment_parameters) # Compute the distance matrix of the midi and audio CQTs, using cosine distance distance_matrix = scipy.spatial.distance.cdist(midi_cqt, audio_cqt, 'cosine') additive_penalty = float(np.median(np.ravel(distance_matrix))) multiplicative_penalty = 1. # Get lowest cost path in the distance matrix p, q, score = _dtw(distance_matrix, alignment_parameters.gully, additive_penalty, multiplicative_penalty) # Compute MIDIAlignment midi_alignment = MIDIAlignment(midi_times.__getitem__(p), audio_times.__getitem__(q)) # Normalize by path length and the distance matrix sub-matrix within the path score = score / len(p) score = score / distance_matrix[p.min():p.max(), q.min():q.max()].mean() # Write score midi_name = fh.get_file_name_from_full_path(full_synthesized_midi_path) decibel.import_export.midi_alignment_score_io.write_chord_alignment_score( midi_name, score) # Write alignment decibel.import_export.midi_alignment_io.write_alignment_file( midi_alignment, full_alignment_write_path)
def get_well_aligned_midis(song: Song) -> [str]: """ Return names of only the well-aligned MIDIs for this Song (excluding duplicates) :param song: Song in our data set """ # Find duplicate MIDIs in this song; we will exclude them duplicate_midis = filehandler.find_duplicate_midis(song) well_aligned_midis = [] for full_midi_path in song.full_midi_paths: midi_name = filehandler.get_file_name_from_full_path(full_midi_path) if midi_name not in duplicate_midis: alignment_score = read_chord_alignment_score(midi_name) if alignment_score.is_well_aligned: well_aligned_midis.append(midi_name) return well_aligned_midis
def align_single_song( song: Song, alignment_parameters: Optional[AlignmentParameters] = None): """ Align each MIDI file that is matched to this song to the song. As part of the procedure, each MIDI will be synthesized and the alignment of each MIDI will be written to a file. :param alignment_parameters: Parameters for alignment :param song: The Song object for which we align each MIDI file """ # Make sure to have alignment parameters if alignment_parameters is None: alignment_parameters = AlignmentParameters() audio_loaded = False audio_cqt = np.ndarray([]) audio_times = np.ndarray([]) for midi_path in song.full_midi_paths: midi_name = fh.get_file_name_from_full_path(midi_path) write_path = fh.get_full_alignment_path(midi_name) if not fh.file_exists(write_path): # There is no alignment yet for this audio-midi combination, so let's calculate the alignment try: synthesized_midi_path = fh.get_full_synthesized_midi_path( midi_name) if not fh.file_exists(synthesized_midi_path): # The MIDI has not been synthesized yet synthesizer.synthesize_midi_to_wav( midi_path, alignment_parameters.sampling_rate) if not audio_loaded: # Load audio if it is not loaded yet audio_data, _ = librosa.load( song.full_audio_path, sr=alignment_parameters.sampling_rate) audio_cqt, audio_times = _compute_cqt( audio_data, alignment_parameters) audio_loaded = True align_midi(audio_cqt, audio_times, synthesized_midi_path, write_path, alignment_parameters) fh.remove_file(synthesized_midi_path) except: print(write_path + " failed.")
def classify_aligned_midis_for_song(song: Song, chord_vocabulary: ChordVocabulary, segmenter: MIDISegmenterInterface): """ Find chord labels for all re-aligned MIDIs of this song :param song: Song object for which we want to find the chord labels :param chord_vocabulary: List of all chords :param segmenter: Bar or beat segmenter """ for full_midi_path in song.full_midi_paths: midi_name = filehandler.get_file_name_from_full_path(full_midi_path) full_alignment_path = filehandler.get_full_alignment_path(midi_name) write_path = filehandler.get_full_midi_chord_labs_path( midi_name, segmenter.segmenter_name) if not filehandler.file_exists(write_path): # The file does not exist yet, so we need to find the chords # try: # Realign the MIDI using the alignment path realigned_midi = RealignedMIDI(full_midi_path, full_alignment_path) # Find Events, using the specified partition method events = segmenter.find_events(realigned_midi) # Assign most likely chords to each event most_likely_chords = _assign_most_likely_chords( events, chord_vocabulary) # Compute average chord probability midi_chord_probability = _compute_midi_chord_probability( most_likely_chords) # Concatenate annotation items with the same chord labels into one annotation. concatenated_annotation = _get_midi_chord_annotation( most_likely_chords) # Export results export_chord_annotation(concatenated_annotation, write_path) filehandler.write_midi_chord_probability(segmenter.segmenter_name, midi_name, midi_chord_probability)
def get_expected_best_midi(song: Song) -> (str, str): """ Find name of the expected best well-aligned MIDI and segmentation type for this Song (based on MIDI chord probability) :param song: Song in our data set """ # We only consider well-aligned MIDIs well_aligned_midis = get_well_aligned_midis(song) # Return path to the best MIDI of the song best_midi_name, best_midi_quality, best_segmentation = '', -9999999999, '' for segmentation_type in 'bar', 'beat': for full_midi_path in well_aligned_midis: midi_name = filehandler.get_file_name_from_full_path( full_midi_path) midi_chord_probability = filehandler.read_midi_chord_probability( segmentation_type, midi_name) if midi_chord_probability > best_midi_quality: # This is the best MIDI & segmentation type we have seen until now best_midi_name, best_midi_quality, best_segmentation = \ midi_name, midi_chord_probability, segmentation_type return best_midi_name, best_segmentation
def export_result_image(song: Song, chords_vocabulary: ChordVocabulary, midi: bool = True, tab: bool = True, audio: str = 'CHF_2017', df: bool = True): """ Export visualisation to a png file. :param song: Song for which we want to export the visualisation :param chords_vocabulary: Chord vocabulary :param midi: Show MIDI files? :param tab: Show Tab files? :param audio: Audio ACE method :param df: Show all DF results? """ if filehandler.file_exists( filehandler.get_lab_visualisation_path(song, audio)): return song.title + " was already visualised for the ACE method " + audio + "." nr_of_samples = int(ceil(song.duration * 100)) alphabet = ChordAlphabet(chords_vocabulary) # Select labs based on parameter setting label_data = [{ 'name': 'Ground truth', 'index': 0, 'lab_path': song.full_ground_truth_chord_labs_path, 'csr': 1.0, 'ovs': 1.0, 'uns': 1.0, 'seg': 1.0 }] i = 1 best_indices = [] # For expected best MIDI and tab if midi: duplicate_midis = filehandler.find_duplicate_midis(song) best_midi_name, best_segmentation = data_fusion.get_expected_best_midi( song) full_midi_paths = song.full_midi_paths full_midi_paths.sort() for full_midi_path in full_midi_paths: midi_name = filehandler.get_file_name_from_full_path( full_midi_path) for segmentation_method in ['bar', 'beat']: full_midi_chords_path = filehandler.get_full_midi_chord_labs_path( midi_name, segmentation_method) if filehandler.file_exists(full_midi_chords_path) \ and midi_name not in duplicate_midis: # Evaluate song csr, ovs, uns, seg = evaluate( song.full_ground_truth_chord_labs_path, full_midi_chords_path) # Save evaluation values to label_data label_data.append({ 'name': 'MIDI ' + midi_name + ' | ' + segmentation_method, 'index': i, 'lab_path': full_midi_chords_path, 'csr': csr, 'ovs': ovs, 'uns': uns, 'seg': seg }) # Check if this is the expected best MIDI & segmentation method for this song if midi_name == best_midi_name and segmentation_method == best_segmentation: best_indices.append(i) i += 1 if tab: best_tab = data_fusion.get_expected_best_tab_lab(song) for tab_counter, full_tab_path in enumerate(song.full_tab_paths, 1): tab_chord_labs_path = filehandler.get_full_tab_chord_labs_path( full_tab_path) if filehandler.file_exists(tab_chord_labs_path): # Evaluate song csr, ovs, uns, seg = evaluate( song.full_ground_truth_chord_labs_path, tab_chord_labs_path) # Save evaluation values to label_data label_data.append({ 'name': 'Tab ' + str(tab_counter), 'index': i, 'lab_path': tab_chord_labs_path, 'csr': csr, 'ovs': ovs, 'uns': uns, 'seg': seg }) if tab_chord_labs_path == best_tab: best_indices.append(i) i += 1 if df: csr, ovs, uns, seg = evaluate( song.full_ground_truth_chord_labs_path, filehandler.get_full_mirex_chord_labs_path(song, audio)) label_data.append({ 'name': audio, 'index': i, 'lab_path': filehandler.get_full_mirex_chord_labs_path(song, audio), 'csr': csr, 'ovs': ovs, 'uns': uns, 'seg': seg }) for selection_name in 'all', 'best': for combination_name in 'rnd', 'mv', 'df': df_lab_path = filehandler.get_data_fusion_path( song.key, combination_name, selection_name, audio) csr, ovs, uns, seg = evaluate( song.full_ground_truth_chord_labs_path, df_lab_path) label_data.append({ 'name': audio + '-' + combination_name.upper() + '-' + selection_name.upper(), 'index': i, 'lab_path': df_lab_path, 'csr': csr, 'ovs': ovs, 'uns': uns, 'seg': seg }) # Fill a numpy array with chord labels for each of the lab files chord_matrix = np.zeros((len(label_data), nr_of_samples), dtype=int) for lab_nr in range(len(label_data)): data_fusion.load_lab_file_into_chord_matrix( label_data[lab_nr]['lab_path'], lab_nr, chord_matrix, alphabet, nr_of_samples) all_chords = [chord_matrix[x] for x in range(len(label_data))] # Find names names = [label_dict['name'] for label_dict in label_data] # Find results results = ['CSR OvS UnS Seg'] for label_dict in label_data[1:]: results.append(' '.join([ str(round(label_dict[measure], 2)).ljust(4, '0') for measure in ['csr', 'ovs', 'uns', 'seg'] ])) # Show result plt1 = _show_chord_sequences(song, all_chords, best_indices, names, results, alphabet) plt1.savefig(filehandler.get_lab_visualisation_path(song, audio), bbox_inches="tight", pad_inches=0) return song.title + " was visualised for the ACE method " + audio + "."