Spaces:
Runtime error
Runtime error
| import time | |
| import os | |
| import sys | |
| sys.path.append('../../') | |
| import pretty_midi as pm | |
| import numpy as np | |
| from src.music.utils import get_out_path | |
| from src.music.config import MIN_LEN, MIN_NB_NOTES, MAX_GAP_IN_SONG, REMOVE_FIRST_AND_LAST | |
| def sort_notes(notes): | |
| starts = np.array([n.start for n in notes]) | |
| index_sorted = np.argsort(starts) | |
| return [notes[i] for i in index_sorted].copy() | |
| def delete_notes_end_after_start(notes): | |
| indexes_to_keep = [i for i, n in enumerate(notes) if n.start < n.end] | |
| return [notes[i] for i in indexes_to_keep].copy() | |
| def compute_largest_gap(notes): | |
| gaps = [] | |
| latest_note_end_so_far = notes[0].end | |
| for i in range(len(notes) - 1): | |
| note_start = notes[i + 1].start | |
| if latest_note_end_so_far < note_start: | |
| gaps.append(note_start - latest_note_end_so_far) | |
| latest_note_end_so_far = max(latest_note_end_so_far, notes[i+1].end) | |
| if len(gaps) > 0: | |
| largest_gap = np.max(gaps) | |
| else: | |
| largest_gap = 0 | |
| return largest_gap | |
| def analyze_instrument(inst): | |
| # test that piano plays throughout | |
| init = time.time() | |
| notes = inst.notes.copy() | |
| nb_notes = len(notes) | |
| start = notes[0].start | |
| end = inst.get_end_time() | |
| duration = end - start | |
| largest_gap = compute_largest_gap(notes) | |
| return nb_notes, start, end, duration, largest_gap | |
| def remove_beginning_and_end(midi, end_time): | |
| notes = midi.instruments[0].notes.copy() | |
| new_notes = [n for n in notes if n.start > REMOVE_FIRST_AND_LAST and n.end < end_time - REMOVE_FIRST_AND_LAST] | |
| midi.instruments[0].notes = new_notes | |
| return midi | |
| def remove_blanks_beginning_and_end(midi): | |
| # remove blanks and the beginning and the end | |
| shift = midi.instruments[0].notes[0].start | |
| for n in midi.instruments[0].notes: | |
| n.start = max(0, n.start - shift) | |
| n.end = max(0, n.end - shift) | |
| for ksc in midi.key_signature_changes: | |
| ksc.time = max(0, ksc.time - shift) | |
| for tsc in midi.time_signature_changes: | |
| tsc.time = max(0, tsc.time - shift) | |
| for pb in midi.instruments[0].pitch_bends: | |
| pb.time = max(0, pb.time - shift) | |
| for cc in midi.instruments[0].control_changes: | |
| cc.time = max(0, cc.time - shift) | |
| return midi | |
| def is_valid_inst(largest_gap, duration, nb_notes, gap_counts=True): | |
| error_msg = '' | |
| valid = True | |
| if largest_gap > MAX_GAP_IN_SONG and gap_counts: | |
| valid = False | |
| error_msg += f'wide gap ({largest_gap:.2f} secs), ' | |
| if duration < (MIN_LEN + 2 * REMOVE_FIRST_AND_LAST): | |
| valid = False | |
| error_msg += f'too short ({duration:.2f} secs), ' | |
| if nb_notes < MIN_NB_NOTES * duration / 60: # nb of notes needs to be superior to the minimum number / min * the duration in minute | |
| valid = False | |
| error_msg += f'too few notes ({nb_notes}), ' | |
| return valid, error_msg | |
| def midi2processed(midi_path, processed_path=None, apply_filtering=True, verbose=False, level=0): | |
| assert midi_path.split('.')[-1] in ['mid', 'midi'] | |
| if not processed_path: | |
| processed_path, _, _ = get_out_path(in_path=midi_path, in_word='midi', out_word='processed', out_extension='.mid') | |
| if verbose: print(' ' * level + f'Processing {midi_path}.') | |
| if os.path.exists(processed_path): | |
| if verbose: print(' ' * (level + 2) + 'Processed midi file already exists.') | |
| return processed_path, '' | |
| error_msg = 'Error in scrubbing. ' | |
| #try: | |
| inst_error_msg = '' | |
| # load mid file | |
| error_msg += 'Error in midi loading?' | |
| midi = pm.PrettyMIDI(midi_path) | |
| error_msg += ' Nope. Removing invalid notes?' | |
| midi.remove_invalid_notes() # filter invalid notes | |
| error_msg += ' Nope. Filtering instruments?' | |
| # filter instruments | |
| instruments = midi.instruments.copy() | |
| new_instru = [] | |
| instruments_data = [] | |
| for i_inst, inst in enumerate(instruments): | |
| if inst.program <= 7 and not inst.is_drum and len(inst.notes) > 5: | |
| # inst is a piano | |
| # check data | |
| inst.notes = sort_notes(inst.notes) # sort notes | |
| inst.notes = delete_notes_end_after_start(inst.notes) # delete invalid notes | |
| nb_notes, start, end, duration, largest_gap = analyze_instrument(inst) | |
| is_valid, err_msg = is_valid_inst(largest_gap=largest_gap, duration=duration, nb_notes=nb_notes, gap_counts='maestro' not in midi_path) | |
| if is_valid or not apply_filtering: | |
| new_instru.append(inst) | |
| instruments_data.append([nb_notes, start, end, duration, largest_gap]) | |
| else: | |
| inst_error_msg += 'inst1: ' + err_msg + '\n' | |
| instruments_data = np.array(instruments_data) | |
| error_msg += ' Nope. Taking one instrument?' | |
| if len(new_instru) == 0: | |
| error_msg = f'No piano instrument. {inst_error_msg}' | |
| assert False | |
| elif len(new_instru) > 1: | |
| # take instrument playing the most notes | |
| instrument = new_instru[np.argmax(instruments_data[:, 0])] | |
| else: | |
| instrument = new_instru[0] | |
| instrument.program = 0 # set the instrument to Grand Piano. | |
| midi.instruments = [instrument] # put instrument in midi file | |
| error_msg += ' Nope. Removing blanks?' | |
| # remove first and last REMOVE_FIRST_AND_LAST seconds (avoid clapping and jingles) | |
| end_time = midi.get_end_time() | |
| if apply_filtering: midi = remove_beginning_and_end(midi, end_time) | |
| # remove beginning and end | |
| midi = remove_blanks_beginning_and_end(midi) | |
| error_msg += ' Nope. Saving?' | |
| # save midi file | |
| midi.write(processed_path) | |
| error_msg += ' Nope.' | |
| if verbose: | |
| extra = f' Saved to {processed_path}' if midi_path else '' | |
| print(' ' * (level + 2) + f'Success! {extra}') | |
| return processed_path, '' | |
| #except: | |
| # if verbose: print(' ' * (level + 2) + 'Scrubbing failed.') | |
| #if os.path.exists(processed_path): | |
| # os.remove(processed_path) | |
| #return None, error_msg + ' Yes.' | |