def generate_mono(filename='generated_mono.gp5', tempo=120, pitches=None): if pitches is None: pitches = set(range(MIN_PITCH, MAX_PITCH + 1)) tracks = [ Track( "Electric Guitar", len(TUNING), TUNING + (-1,), 1, 1, 2, N_FRETS, 0, (200, 55, 55, 0), 27 ), ] measures = [] beats = [] onset_times = [] list_of_pitches = [] onset_time = START_OFFSET_SECONDS quarter_note_seconds = 60 / tempo for pitch in range(MIN_PITCH, MAX_PITCH + 1): if pitch in pitches: for string, fret in get_string_fret_possibilities(pitch): print('pitch={}, string={}, fret={}'.format(pitch, string, fret)) if len(measures) == 0: measures.append(Measure(4, 4, beam8notes=(2, 2, 2, 2))) else: measures.append(Measure()) beats_measure = create_measure() beats_measure[0][0].append(Beat([None] * 7, pause=True)) onset_time += quarter_note_seconds notes = [None] * 7 notes[string] = Note(fret) beats_measure[0][0].append(Beat(notes)) onset_times.append(onset_time) list_of_pitches.append([pitch]) onset_time += quarter_note_seconds beats_measure[0][0].append(Beat([None] * 7, pause=True)) onset_time += quarter_note_seconds beats_measure[0][0].append(Beat([None] * 7, pause=True)) onset_time += quarter_note_seconds beats.append(beats_measure) path_to_gp5_file = os.path.join(r'..\tmp', filename) write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5_file) path_to_truth_file = os.path.join(r'..\tmp', GP5_ENDING_REGEX.sub('', filename) + '.xml') write_truth_file([path_to_truth_file], [onset_times], [list_of_pitches])
recording_name = recording_name[:-4] if args.track_title is None: track_title = recording_name else: track_title = args.track_title if args.path_to_gp5 is None: path_to_gp5 = recording_name + '.gp5' else: path_to_gp5 = args.path_to_gp5 path_to_midi = recording_name + '.mid' write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5, header=Header(title=track_title)) print('Exporting to Midi file') from midiutil import MIDIFile track = 0 channel = 0 time = 0 # In beats duration = 1 # In beats #tempo = 60 # In BPM volume = 100 # 0-127, as per the MIDI standard MyMIDI = MIDIFile(1) # One track, defaults to format 1 (tempo track is created
def generate_poly(chord_type, filename='generated_poly.gp5', n_measures_per_file=10000, tempo=120, pitches=None): if pitches is None: pitches = set(range(MIN_PITCH, MAX_PITCH + 1)) files = [] tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file() # chords with 2 pitches if chord_type == 2: for pitch_1 in range(MIN_PITCH, MAX_PITCH + 1): if pitch_1 in pitches: for pitch_2 in range(pitch_1 + 1, MAX_PITCH + 1): if pitch_1 != pitch_2: possibilities = plausibility.get_all_fret_possibilities((pitch_1, pitch_2), tuning=TUNING, n_frets=N_FRETS) if len(possibilities) > 0: measure, beats_measure, onset_after_seconds, measure_duration_seconds, printable_notes = create_measure_beats_measure_printable_notes( len(measures), possibilities[0], tempo ) measures.append(measure) beats.append(beats_measure) onset_times.append(onset_time + onset_after_seconds) onset_time += measure_duration_seconds list_of_pitches.append([pitch_1, pitch_2]) print('pitch_1={}, pitch_2={}, notes={}'.format( pitch_1, pitch_2, printable_notes )) if len(measures) >= n_measures_per_file: if len(files) == 0: current_filename = filename else: current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5' files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches)) tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file() # chords with 3 pitches elif chord_type == 3: for pitch_1 in range(MIN_PITCH, MAX_PITCH + 1): if pitch_1 in pitches: for pitch_2 in range(pitch_1 + 1, MAX_PITCH + 1): for pitch_3 in range(pitch_2 + 1, MAX_PITCH + 1): if pitch_1 != pitch_2 and pitch_1 != pitch_3 and pitch_2 != pitch_3: possibilities = plausibility.get_all_fret_possibilities((pitch_1, pitch_2, pitch_3), tuning=TUNING, n_frets=N_FRETS) if len(possibilities) > 0: measure, beats_measure, onset_after_seconds, measure_duration_seconds, printable_notes = create_measure_beats_measure_printable_notes( len(measures), possibilities[0], tempo ) measures.append(measure) beats.append(beats_measure) onset_times.append(onset_time + onset_after_seconds) onset_time += measure_duration_seconds list_of_pitches.append([pitch_1, pitch_2, pitch_3]) print('pitch_1={}, pitch_2={}, pitch_3={}, notes={}'.format( pitch_1, pitch_2, pitch_3, printable_notes )) if len(measures) >= n_measures_per_file: if len(files) == 0: current_filename = filename else: current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5' files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches)) tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file() # chords with 4 pitches elif chord_type == 4: for pitch_1 in range(MIN_PITCH, MAX_PITCH + 1): if pitch_1 in pitches: for pitch_2 in range(pitch_1 + 1, MAX_PITCH + 1): for pitch_3 in range(pitch_2 + 1, MAX_PITCH + 1): for pitch_4 in range(pitch_3 + 1, MAX_PITCH + 1): if (pitch_1 != pitch_2 and pitch_1 != pitch_3 and pitch_1 != pitch_4 and pitch_2 != pitch_3 and pitch_2 != pitch_4 and pitch_3 != pitch_4): possibilities = plausibility.get_all_fret_possibilities((pitch_1, pitch_2, pitch_3, pitch_4), tuning=TUNING, n_frets=N_FRETS) if len(possibilities) > 0: measure, beats_measure, onset_after_seconds, measure_duration_seconds, printable_notes = create_measure_beats_measure_printable_notes( len(measures), possibilities[0], tempo ) measures.append(measure) beats.append(beats_measure) onset_times.append(onset_time + onset_after_seconds) onset_time += measure_duration_seconds list_of_pitches.append([pitch_1, pitch_2, pitch_3, pitch_4]) print('pitch_1={}, pitch_2={}, pitch_3={}, pitch_4={}, notes={}'.format( pitch_1, pitch_2, pitch_3, pitch_4, printable_notes )) if len(measures) >= n_measures_per_file: if len(files) == 0: current_filename = filename else: current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5' files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches)) tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file() else: raise ValueError('Unsupported chord type {}'.format(chord_type)) if len(measures) > 0: if len(files) == 0: current_filename = filename else: current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5' files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches)) for filename, measures, tracks, beats, onset_times, list_of_pitches in files: path_to_gp5_file = os.path.join(r'..\tmp', filename) write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5_file) path_to_truth_file = os.path.join(r'..\tmp', GP5_ENDING_REGEX.sub('', filename) + '.xml') write_truth_file([path_to_truth_file], [onset_times], [list_of_pitches])
assert args.track2 > 0, 'Invalid track number for track 2, should be > 0' filename1 = os.path.basename(args.file1).rstrip('.gp5') filename2 = os.path.basename(args.file2).rstrip('.gp5') if args.outfile is None: out_path = filename1 + ' VS ' + filename2 + '.gp5' else: out_path = args.outfile gp5file1 = GP5File(args.file1) gp5file2 = GP5File(args.file2) assert args.track1 <= gp5file1.nTracks, 'Invalid track number for track 1, should be <= number of tracks' assert args.track2 <= gp5file2.nTracks, 'Invalid track number for track 2, should be <= number of tracks' assert [gp5file1.tracks[args.track1 - 1].channel, gp5file2.tracks[args.track2 - 1].channel].count(10) != 1, \ 'Invalid tracks, cannot compare a melodic to a percussion track' common_markers = meta_comparison(gp5file1, gp5file2) measures, beats = compare(gp5file1, gp5file2, args.track1, args.track2, TUNING, common_markers, args.compare_positions) tracks = [ gp5file1.tracks[args.track1 - 1], copy(gp5file1.tracks[args.track1 - 1]), gp5file2.tracks[args.track2 - 1] ] tracks[0].name = 'common notes' tracks[1].name = 'differences file 1' tracks[2].name = 'differences file 2' write_gp5(measures, tracks, beats, tempo=gp5file1.tempo, outfile=out_path)
def convert_midi2gp5(path_to_midi, outfile, shortest_note=0.25, init_tempo=120, time_signature=(4, 4), force_drums=False, default_instrument=30, verbose=False): """ Convert a MIDI file to GP5 Parameters ---------- path_to_midi: str path to MIDI file outfile: str path to GP5 output file shortest_note: float, optional shortest note to be considered, in quarters. Default: 0.25 (16th notes) init_tempo: int, optional Tempo in beats per minute (bpm) to assume, if the file doesn't specify a tempo. Ignored otherwise. time_signature: (int, int), optional Time signature (numerator, denominator). Default: 4/4 force_drums: bool, optional Force to be considered a drum track, even if the track is not on MIDI channel 10 as it should be due to the MIDI standard. Default: False default_instrument: int, optional Instrument to use if not specified in the MIDI file. Default: 30 (distortion guitar) verbose: bool, optional Print raw info from the MIDI track. Default: False """ gp5_measures = [] gp5_tracks = [] gp5_beats = [] event_queue = [] track_mapping = {} track_name = {} track_instrument = {} track_min_note = {} track_max_note = {} unused_midi_channels = list(range(1, 65)) unused_midi_channels.remove(10) # remove drum track # read file with open(path_to_midi, 'rb') as f: midi = f.read() score = midi2score(midi) ticks_per_quarter = score[0] for (i, track) in enumerate(score[1:]): # determine track mapping track_mapping[i] = -1 track_name[i] = "Track" + str(i) track_instrument[i] = default_instrument track_max_note[i] = -1 track_min_note[i] = 128 elements_to_push = [] for event in track: if event[0] in midi_events: if event[0] == 'patch_change' and event[1] == 0: track_instrument[i] = event[3] if event[ 2] == 9: # channel 10 (0-based here) indicates drum track! (midi standard) track_instrument[i] = -1 - track_instrument[ i] # save drums as negative number (-1 bc -0 == 0) if verbose: print( 'initial patch [raw track:{}]: chan:{}, patch:{}'. format(i, event[2], event[3])) else: elements_to_push.append(event) if event[ 0] == 'note': # start_time, duration, channel, note, velocity track_max_note[i] = max(track_max_note[i], event[4]) track_min_note[i] = min(track_min_note[i], event[4]) track_mapping[i] = i else: if event[0] == 'track_name': track_name[i] = event[2].decode( 'ISO-8859-1' ) # decode('ascii','ignore') / decode('UTF-8') else: elements_to_push.append(event) for event in elements_to_push: heappush(event_queue, (event[1], track_mapping[i], event)) heappush(event_queue, (float('inf'), -1, ['song_end', float('inf') ])) # add end of song event # rearrange track indices to fill gaps of tracks that only contain meta-events idx = 0 for key, value in sorted(track_mapping.items()): if value > -1: # actual track, no meta-track track_mapping[key] = idx idx += 1 # default values, assume drum track tuning = (0, 0, 0, 0, 0, 0, 0 ) # apparently file format writes this tuning for drums n_strings = 6 # apparently file format writes drum tracks have 6 strings channel1 = channel2 = 10 # default assume drum track if track_instrument[ key] >= 0 and not force_drums: # not a drum track channel1 = min(unused_midi_channels) unused_midi_channels.remove(channel1) channel2 = min(unused_midi_channels) unused_midi_channels.remove(channel2) tuning = determine_tuning(track_min_note[key]) n_strings = 7 - tuning.count(-1) elif track_instrument[key] < 0: track_instrument[key] = -1 - track_instrument[ key] # get back correct instrument number elif force_drums: track_instrument[key] = 0 gp5_tracks.append( Track( track_name[key], # track name n_strings, # number of strings tuning, # tuning (7 strings: highest to lowest) 1, # midi port (default always 1) channel1, # channel 1 channel2, # channel 2 (effects channel) 87 if channel1 == 10 else 30, # frets: use some reserve frets in case the tuning was determined wrongly 0, # capo (0 if channel1 == 10 else 255, 0, 0, 0), # color (we use black for drums, red for guitar) track_instrument[key] # instrument )) track_struct = [] for t in gp5_tracks: track_struct.append( ([], [])) # add t tuples with two empty lists (for each voice) if verbose: print(t) numerator, denominator = time_signature note_accuracy = 1 / shortest_note # number of notes per quarter gp5_duration = int(log2(note_accuracy)) # calculate gp5-duration time_signature_changed = True # first measure needs to contain time signature cur_marker_name = "" cur_beat_start_ticks = 0.0 # current beat started at tick 0.0 next_beat_start_ticks = (4 * numerator / denominator ) # next beat starts at tick cur_measure = 0 # start with measure 0 gp5_beats.append(deepcopy(track_struct)) # append empty measure 0 gp5_note_overflows = [(0, None)] * len( gp5_tracks ) # number of notes (G_GP5DURATION) that didn't fit in last measure for i in range(len(event_queue)): (time, track, event) = heappop(event_queue) ticks = event[1] / ticks_per_quarter if ticks >= next_beat_start_ticks: nn = numerator if time_signature_changed else 0 # numerator dd = denominator if time_signature_changed else 0 # denominator time_signature_changed = False # fill measure up with pauses for t in range(len(gp5_tracks)): past_notes = round( note_accuracy * (next_beat_start_ticks - cur_beat_start_ticks)) cur_gp5_beats = gp5_beats[cur_measure][t][0] for b in range(len(cur_gp5_beats), past_notes): # insert pauses cur_gp5_beats.append( Beat([None] * 7, duration=gp5_duration, pause=True)) # repeat_open repeat_close repeat_alt m_name marker_color maj_key min_key double_bar beam8notes triplet_feel gp5_measures.append(Measure(nn, dd, marker_name=cur_marker_name)) cur_measure += 1 if event[0] != 'song_end': # don't create new measure in the end TODO write overflow notes at end gp5_beats.append( deepcopy(track_struct)) # append empty measure 0 for j in range( len(gp5_note_overflows)): # write overflowing notes of_cur_notes = min( gp5_note_overflows[j][0], int(4 * numerator / denominator * note_accuracy)) for b in range(of_cur_notes): gp5_beats[cur_measure][j][0].append( Beat(gp5_note_overflows[j][1], duration=gp5_duration)) gp5_note_overflows[j] = (gp5_note_overflows[j][0] - of_cur_notes, gp5_note_overflows[j][1]) cur_marker_name = "" # reset name cur_beat_start_ticks = next_beat_start_ticks next_beat_start_ticks += (4 * numerator / denominator) if event[0] == 'note': # start_time, duration, channel, note, velocity dur = event[ 2] / ticks_per_quarter # duration of the midi note [in quarters] dur_remaining = next_beat_start_ticks - ticks # remaining quarters that fit in the current measure dur_past = ticks - cur_beat_start_ticks # past quarters in current measure before current note n_notes = max(1, round(dur * note_accuracy)) # total notes to be written remaining_notes = round( dur_remaining * note_accuracy ) # max notes that can be written to current measure past_notes = round(dur_past * note_accuracy) # note offset from measure start cur_notes = min( n_notes, remaining_notes) # notes to write into current measure # at this point every note should be exactly of length note_accuracy cur_track = track_mapping[ track] # real track index (without meta-tracks) cur_gp5_beats = gp5_beats[cur_measure][cur_track][0] for b in range(len(cur_gp5_beats), past_notes): # insert pauses cur_gp5_beats.append( Beat([None] * 7, duration=gp5_duration, pause=True)) overflow_notes = [None] * 7 is_tied = False # first beat untied for cur_beat_idx in range(past_notes, past_notes + cur_notes): if len(cur_gp5_beats ) <= cur_beat_idx: # insert new beat if needed cur_gp5_beats.append(Beat([None] * 7)) assert len(cur_gp5_beats ) > cur_beat_idx, "ERR: len:{}, idx:{}".format( len(cur_gp5_beats), cur_beat_idx) notes = cur_gp5_beats[cur_beat_idx].notes tuning = gp5_tracks[cur_track].tuning for t in range(6, -1, -1): if 0 <= tuning[t] <= event[4] and t < gp5_tracks[ cur_track].nStrings and notes[t] is None: notes[t] = Note(event[4] - tuning[t], tied=is_tied) overflow_notes = notes break # TODO this doesnt make sure a note is written! # better: get all prev notes, list all possible positions for each note -> get most plausible # right now a high E is written on lowest string, when followed by a low E -> impossible! cur_gp5_beats[cur_beat_idx] = Beat(notes, duration=gp5_duration) is_tied = True # following beats tied! gp5_note_overflows[cur_track] = [ max(0, n_notes - cur_notes), overflow_notes ] if verbose: print('{}[{}]: note:{}, dur:{}, chan:{}, v:{}'.format( ticks, cur_track, event[4], dur, event[3], event[5])) elif event[0] == 'set_tempo': tempo = round(1 / (event[2] / 60000000)) init_tempo = tempo if ticks == 0 else init_tempo # update init tempo if necessary if verbose: print('{}: set tempo: {}'.format(ticks, tempo)) elif event[ 0] == 'time_signature': # event, time, nn, dd, metronome_clicks, speed (num of 32ths to the quarter) numerator = event[2] # nn / numerator denominator = pow( 2, event[3]) # dd / log_denominator -> 2=quarter, 3=eights, etc. time_signature_changed = True next_beat_start_ticks = cur_beat_start_ticks + (4 * numerator / denominator) if verbose: print('{}: time signature: {} {} {} {}'.format( ticks, event[2], event[3], event[4], event[5])) elif event[0] == 'marker': cur_marker_name = event[2].decode( 'ISO-8859-1') # decode('ascii','ignore') / decode('UTF-8') if verbose: print('{}: set marker: {}'.format(ticks, cur_marker_name)) elif event[0] == 'patch_change' and verbose: # instrument change print('{}: patch change: chan:{}, patch:{}'.format( ticks, event[2], event[3])) # elif event[0] == 'control_change': # track volume, pan, usw. - not needed here # print('{}: control change: chan:{}, control:{}, val:{}'.format(ticks, event[2],event[3],event[4])) # elif event[0] == 'pitch_wheel_change': # bend-release - not needed here # print('{}: pitch wheel change: chan:{}, pitch wheel:{}'.format(ticks, event[2], event[3])) elif verbose: print('{}: -- unknown event: {}'.format(ticks, event[0])) if False and verbose: for m in gp5_measures: print(m) collapse_beats(gp5_beats, gp5_duration) assert len(gp5_measures) == len(gp5_beats), "ERR: Mlen {}!={}".format( len(gp5_measures), len(gp5_beats)) if False and verbose: for m in range(len(gp5_beats)): print('Measure {}'.format(m + 1)) assert len(gp5_tracks) == len( gp5_beats[m]), "ERR: Tlen {}!={}".format( len(gp5_tracks), len(gp5_beats[m])) for t in range(len(gp5_beats[m])): print('\tTrack {}'.format(t + 1)) for b in gp5_beats[m][t][0]: print("\t\tBeat") if b.pause or b.empty: print("\t\t\t", b.notes) else: for n in b.notes: print("\t\t\t", n) print("\t\t\t dur:{}, dot:{}, pause:{}, empty:{}".format( b.duration, b.dotted, b.pause, b.empty)) write_gp5(gp5_measures, gp5_tracks, gp5_beats, init_tempo, outfile=outfile)
onset_times_seconds = read_onset_times(path_to_truth, 1, 'xml', 0.05) onset_times_grouped, list_of_pitch_sets = _read_onset_times_pitches(path_to_truth, 40, 88, 1, 0.05) assert onset_times_seconds == onset_times_grouped string_fret_detector = SequenceStringFretDetection(tuning, n_frets) list_of_string_lists, list_of_fret_lists = string_fret_detector.predict_strings_and_frets( None, onset_times_seconds, list_of_pitch_sets ) beat_transformer = SimpleBeatTransformer(shortest_note=shortest_note, beats_per_measure=float(4.0)) beats = beat_transformer.transform(path_to_wav, onset_times_seconds, list_of_string_lists, list_of_fret_lists, tempo) measures = [] for i, measure in enumerate(beats): if i == 0: measures.append(Measure(4, 4, beam8notes=(2, 2, 2, 2))) else: measures.append(Measure()) tracks = [Track("Electric Guitar", len(tuning), tuning + (-1,), 1, 1, 2, n_frets, 0, (200, 55, 55, 0), 25)] recording_name = os.path.basename(path_to_wav) if recording_name.endswith('.wav'): recording_name = recording_name[:-4] path_to_gp5 = '..\\tmp\\' + recording_name + '.gp5' write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5)
( # track 1 [ # voice 1: notes duration pause empty dotted ntuple_feel chord text effect mix_change Beat([None, None, None, None, Note(3), Note(0), None], duration=-2) ], [] # voice 2 ) ], [ # measure 8 ( # track 1 [ # voice 1: notes duration pause empty dotted ntuple_feel chord text effect mix_change Beat([None, None, None, None, Note(3), Note(0), None], duration=-2) ], [] # voice 2 ) ], ] write_gp5( measures, tracks, beats, tempo=133, outfile="out.gp5", # header=Header('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'), # lyrics=Lyrics(1, [(1, "I have no"), (4, "Idea what you talk"), (1, "about, wtf")]), )