Spaces:
Configuration error
Configuration error
| import guitarpro | |
| from guitarpro import * | |
| from matplotlib import pyplot as plt | |
| import mgzip | |
| import numpy as np | |
| import os | |
| import pickle | |
| from tqdm import tqdm | |
| import tensorflow as tf | |
| from tensorflow import keras | |
| from keras.callbacks import ModelCheckpoint | |
| from keras.models import Sequential | |
| from keras.layers import Activation, Dense, LSTM, Dropout, Flatten | |
| from _Decompressor import SongWriter | |
| # Define some constants: | |
| # PITCH[i] = the pitch associated with midi note number i. | |
| # For example, PITCH[69] = 'A4' | |
| PITCH = {val : str(GuitarString(number=0, value=val)) for val in range(128)} | |
| # MIDI[string] = the midi number associated with the note described by string. | |
| # For example, MIDI['A4'] = 69. | |
| MIDI = {str(GuitarString(number=0, value=val)) : val for val in range(128)} | |
| # Generation helper methods: | |
| def thirty_seconds_to_duration(count): | |
| if count % 3 == 0: | |
| # If the note is dotted, do 32 / (i * 2/3), and return isDotted = True. | |
| return (48//count, True) | |
| else: | |
| # If the note is not dotted, to 32 / i, and return isDotted = False. | |
| return (32//count, False) | |
| def quantize_thirty_seconds(value): | |
| # 32nd-note values of each fundamental type of note (not including 64th-notes, of course). | |
| vals = np.array([32, # whole | |
| 24, # dotted half | |
| 16, # half | |
| 12, # dotted quarter | |
| 8, # quarter | |
| 6, # dotted eigth | |
| 4, # eigth | |
| 3, # dotted sixteenth | |
| 2, # sixteenth | |
| 1]) # thirty-second | |
| list_out = [] | |
| for v in vals: | |
| if v <= value: | |
| list_out.append(thirty_seconds_to_duration(v)) | |
| value -= v | |
| return np.array(list_out) | |
| def adjust_to_4_4(prediction_output): | |
| ''' | |
| Adjust prediction output to be in 4/4 time. | |
| Then, separate the beats into measures. | |
| ''' | |
| # This will be the prediction output | |
| new_prediction_output = [] | |
| time = 0 | |
| for beat in prediction_output: | |
| # Calculate the fraction of a measure encompassed by the current beat / chord. | |
| beat_time = (1 / beat[1]) * (1 + 0.5 * beat[2]) | |
| # Calculate the fraction of a measure taken up by all notes in the measure. | |
| # Calculate any residual time to see if this measure (in 4/4 time) is longer than 1 measure. | |
| measure_time = time + beat_time | |
| leftover_time = (measure_time) % 1 | |
| # If the measure count (i.e., the measure integer) has changed and there is significant left-over beat time: | |
| if (int(measure_time) > int(time)) and (leftover_time > 1/128): | |
| # Calculate the initial 32nd notes encompassed by this beat in the current measure. | |
| this_measure_thirty_seconds = int(32 * (1 - time % 1)) | |
| # Calculate the remaining 32nd notes encompassed by this beat in the next measure. | |
| next_measure_thirty_seconds = int(32 * leftover_time) | |
| # Get the Duration object parameters for this measure and the next measure. | |
| this_measure_durations = quantize_thirty_seconds(this_measure_thirty_seconds) | |
| next_measure_durations = quantize_thirty_seconds(next_measure_thirty_seconds) | |
| #print(f'{{ {32 / beat[1]}') | |
| for duration_idx, duration in enumerate(this_measure_durations): | |
| time += (1 / duration[0]) * (1 + 0.5 * duration[1]) | |
| #print(time, '\t', time * 32) | |
| chord = beat[0] if duration_idx == 0 else 'tied' | |
| new_prediction_output.append((chord, duration[0], duration[1], beat[3])) | |
| for duration in next_measure_durations: | |
| time += (1 / duration[0]) * (1 + 0.5 * duration[1]) | |
| #print(time, '\t', time * 32) | |
| new_prediction_output.append(('tied', duration[0], duration[1], beat[3])) | |
| continue | |
| time += beat_time | |
| new_prediction_output.append((beat[0], beat[1], beat[2], beat[3])) | |
| #print(time, '\t', time * 32) | |
| ''' | |
| # Code for debugging | |
| time = 0 | |
| time2 = 0 | |
| idx = 0 | |
| for idx2, beat2 in enumerate(new_prediction_output[:100]): | |
| beat = prediction_output[idx] | |
| if time == time2: | |
| print(beat[0], '\t', time, '\t\t', beat2[0], '\t', time2) | |
| idx += 1 | |
| time += (1 / beat[1]) * (1 + 0.5 * beat[2]) | |
| else: | |
| print('\t\t\t\t', beat2[0], '\t', time2) | |
| time2 += (1 / beat2[1]) * (1 + 0.5 * beat2[2]) | |
| '''; | |
| # Use the previously calculated cumulative time as the number of measures in the new 4/4 song. | |
| num_measures = int(np.ceil(time)) | |
| song = np.empty(num_measures, dtype=object) | |
| time = 0 | |
| m_idx = 0 | |
| timestamps = [] | |
| for beat in new_prediction_output: | |
| #print(time) | |
| timestamps.append(time) | |
| m_idx = int(time) | |
| if song[m_idx] is None: | |
| song[m_idx] = [beat] | |
| else: | |
| song[m_idx].append(beat) | |
| time += (1 / beat[1]) * (1 + 0.5 * beat[2]) | |
| print(f'4/4 adjusted correctly: {set(range(num_measures)).issubset(set(timestamps))}') | |
| return song | |
| class Generator: | |
| def __init__(self, num_tracks_to_generate=5, as_fingerings=True, sequence_length=100): | |
| with mgzip.open('data\\notes_data.pickle.gz', 'rb') as filepath: | |
| self.notes = pickle.load(filepath) | |
| self.note_to_int = pickle.load(filepath) | |
| self.int_to_note = pickle.load(filepath) | |
| self.n_vocab = pickle.load(filepath) | |
| self.NUM_TRACKS_TO_GENERATE = num_tracks_to_generate | |
| self.as_fingerings = as_fingerings | |
| self.sequence_length = sequence_length | |
| with mgzip.open('data\\track_data.pickle.gz', 'rb') as filepath: | |
| self.track_data = pickle.load(filepath) | |
| self.model = keras.models.load_model('minigpt') | |
| self.ints = np.array([self.note_to_int[x] for x in self.notes]) | |
| def generate_track(self, track_idx=None): | |
| if track_idx is None: | |
| # Choose a random track | |
| track_idx = np.random.choice(len(self.track_data)) | |
| # Get the note indices corresponding to the beginning and ending of the track | |
| song_note_idx_first = self.track_data.loc[track_idx]['noteStartIdx'] | |
| song_note_idx_last = self.track_data.loc[track_idx+1]['noteStartIdx'] | |
| # Choose a random starting point within the track | |
| start_idx = np.random.randint(low=song_note_idx_first, | |
| high=song_note_idx_last) | |
| # Choose a number of initial notes to select from the track, at most 100. | |
| #num_initial_notes = np.random.choice(min(100, song_note_idx_last - start_idx)) | |
| num_initial_notes = np.random.choice(min(100, song_note_idx_last - start_idx)) | |
| # Select the initial notes (tokens) | |
| start_tokens = [_ for _ in self.ints[start_idx:start_idx+num_initial_notes]] | |
| max_tokens = 100 | |
| def sample_from(logits, top_k=10): | |
| logits, indices = tf.math.top_k(logits, k=top_k, sorted=True) | |
| indices = np.asarray(indices).astype("int32") | |
| preds = keras.activations.softmax(tf.expand_dims(logits, 0))[0] | |
| preds = np.asarray(preds).astype("float32") | |
| return np.random.choice(indices, p=preds) | |
| num_tokens_generated = 0 | |
| tokens_generated = [] | |
| while num_tokens_generated <= max_tokens: | |
| pad_len = self.sequence_length - len(start_tokens) | |
| sample_index = len(start_tokens) - 1 | |
| if pad_len < 0: | |
| x = start_tokens[:self.sequence_length] | |
| sample_index = self.sequence_length - 1 | |
| elif pad_len > 0: | |
| x = start_tokens + [0] * pad_len | |
| else: | |
| x = start_tokens | |
| x = np.array([x]) | |
| y, _ = self.model.predict(x) | |
| sample_token = sample_from(y[0][sample_index]) | |
| tokens_generated.append(sample_token) | |
| start_tokens.append(sample_token) | |
| num_tokens_generated = len(tokens_generated) | |
| generated_notes = [self.int_to_note[num] for num in np.concatenate((start_tokens, tokens_generated))] | |
| return track_idx, generated_notes | |
| def generate_track_batch(self, artist=None): | |
| self.track_indices = np.zeros(self.NUM_TRACKS_TO_GENERATE) | |
| self.tracks = np.zeros(self.NUM_TRACKS_TO_GENERATE, dtype=object) | |
| for i in tqdm(range(self.NUM_TRACKS_TO_GENERATE)): | |
| if artist is None: | |
| idx, t = self.generate_track() | |
| else: | |
| idx, t = self.generate_track(track_idx=np.random.choice(list(self.track_data[self.track_data.artist==artist].index))) | |
| self.track_indices[i] = idx | |
| self.tracks[i] = t | |
| def save_tracks(self, filepath='_generation.gp5'): | |
| songWriter = SongWriter(initialTempo=self.track_data.loc[self.track_indices[0]]['tempo']) | |
| for idx in range(len(self.tracks)): | |
| new_track = adjust_to_4_4(self.tracks[idx]) | |
| # Get the tempo and tuning (lowest string note) of the song: | |
| #print( track_data.loc[track_indices[idx]]) | |
| tempo = self.track_data.loc[self.track_indices[idx]]['tempo'] | |
| instrument = self.track_data.loc[self.track_indices[idx]]['instrument'] | |
| name = self.track_data.loc[self.track_indices[idx]]['song'] | |
| lowest_string = self.track_data.loc[self.track_indices[idx]]['tuning'] | |
| if not self.as_fingerings: | |
| # Get all the unique pitch values from the new track | |
| pitchnames = set.union(*[set([beat[0].split('_')[0] for beat in measure]) for measure in new_track]) | |
| pitchnames.discard('rest') # Ignore rests | |
| pitchnames.discard('tied') # Ignore tied notes | |
| pitchnames.discard('dead') # Ignore dead/ghost notes | |
| lowest_string = min([MIDI[pitch] for pitch in pitchnames]) # Get the lowest MIDI value / pitch | |
| lowest_string = min(lowest_string, MIDI['E2']) # Don't allow any tunings higher than standard. | |
| # Standard tuning | |
| tuning = {1: MIDI['E4'], | |
| 2: MIDI['B3'], | |
| 3: MIDI['G3'], | |
| 4: MIDI['D3'], | |
| 5: MIDI['A2'], | |
| 6: MIDI['E2']} | |
| if lowest_string <= MIDI['B1']: | |
| # 7-string guitar case | |
| tuning[7] = MIDI['B1'] | |
| downtune = MIDI['B1'] - lowest_string | |
| else: | |
| # downtune the tuning by however much is necessary. | |
| downtune = MIDI['E2'] - lowest_string | |
| tuning = {k: v - downtune for k, v in tuning.items()} # Adjust to the new tuning | |
| # Write the track to the song writer | |
| songWriter.decompress_track(new_track, tuning, tempo=tempo, instrument=instrument, name=name, as_fingerings=self.as_fingerings) | |
| songWriter.write(filepath) | |
| print('Finished') | |
| ''' | |
| def init_generator(): | |
| global NUM_TRACKS_TO_GENERATE, notes, note_to_int, int_to_note, n_vocab, track_data, model, ints | |
| with mgzip.open('data\\notes_data.pickle.gz', 'rb') as filepath: | |
| notes = pickle.load(filepath) | |
| note_to_int = pickle.load(filepath) | |
| int_to_note = pickle.load(filepath) | |
| n_vocab = pickle.load(filepath) | |
| with mgzip.open('data\\track_data.pickle.gz', 'rb') as filepath: | |
| track_data = pickle.load(filepath) | |
| #with mgzip.open('output\\generated_songs.pickle.gz', 'rb') as filepath: | |
| # track_indices = pickle.load(filepath) | |
| # tracks = pickle.load(filepath) | |
| model = keras.models.load_model('minigpt') | |
| ints = np.array([note_to_int[x] for x in notes]) | |
| def generate_track(track_idx=None): | |
| global track_data, ints, int_to_note | |
| if track_idx is None: | |
| # Choose a random track | |
| track_idx = np.random.choice(len(track_data)) | |
| # Get the note indices corresponding to the beginning and ending of the track | |
| song_note_idx_first = track_data.loc[track_idx]['noteStartIdx'] | |
| song_note_idx_last = track_data.loc[track_idx+1]['noteStartIdx'] | |
| # Choose a random starting point within the track | |
| start_idx = np.random.randint(low=song_note_idx_first, | |
| high=song_note_idx_last) | |
| # Choose a number of initial notes to select from the track, at most 100. | |
| #num_initial_notes = np.random.choice(min(100, song_note_idx_last - start_idx)) | |
| num_initial_notes = np.random.choice(min(100, song_note_idx_last - start_idx)) | |
| # Select the initial notes (tokens) | |
| start_tokens = [_ for _ in ints[start_idx:start_idx+num_initial_notes]] | |
| max_tokens = 100 | |
| def sample_from(logits, top_k=10): | |
| logits, indices = tf.math.top_k(logits, k=top_k, sorted=True) | |
| indices = np.asarray(indices).astype("int32") | |
| preds = keras.activations.softmax(tf.expand_dims(logits, 0))[0] | |
| preds = np.asarray(preds).astype("float32") | |
| return np.random.choice(indices, p=preds) | |
| num_tokens_generated = 0 | |
| tokens_generated = [] | |
| while num_tokens_generated <= max_tokens: | |
| pad_len = maxlen - len(start_tokens) | |
| sample_index = len(start_tokens) - 1 | |
| if pad_len < 0: | |
| x = start_tokens[:maxlen] | |
| sample_index = maxlen - 1 | |
| elif pad_len > 0: | |
| x = start_tokens + [0] * pad_len | |
| else: | |
| x = start_tokens | |
| x = np.array([x]) | |
| y, _ = model.predict(x) | |
| sample_token = sample_from(y[0][sample_index]) | |
| tokens_generated.append(sample_token) | |
| start_tokens.append(sample_token) | |
| num_tokens_generated = len(tokens_generated) | |
| generated_notes = [int_to_note[num] for num in np.concatenate((start_tokens, tokens_generated))] | |
| return track_idx, generated_notes | |
| def generate_track_batch(artist=None): | |
| global track_indices, tracks, NUM_TRACKS_TO_GENERATE, track_data | |
| track_indices = np.zeros(NUM_TRACKS_TO_GENERATE) | |
| tracks = np.zeros(NUM_TRACKS_TO_GENERATE, dtype=object) | |
| for i in tqdm(range(NUM_TRACKS_TO_GENERATE)): | |
| if artist is None: | |
| idx, t = generate_track() | |
| else: | |
| idx, t = generate_track(track_idx=np.random.choice(list(track_data[track_data.artist==artist].index))) | |
| track_indices[i] = idx | |
| tracks[i] = t | |
| # Generation helper methods: | |
| def thirty_seconds_to_duration(count): | |
| if count % 3 == 0: | |
| # If the note is dotted, do 32 / (i * 2/3), and return isDotted = True. | |
| return (48//count, True) | |
| else: | |
| # If the note is not dotted, to 32 / i, and return isDotted = False. | |
| return (32//count, False) | |
| def quantize_thirty_seconds(value): | |
| # 32nd-note values of each fundamental type of note (not including 64th-notes, of course). | |
| vals = np.array([32, # whole | |
| 24, # dotted half | |
| 16, # half | |
| 12, # dotted quarter | |
| 8, # quarter | |
| 6, # dotted eigth | |
| 4, # eigth | |
| 3, # dotted sixteenth | |
| 2, # sixteenth | |
| 1]) # thirty-second | |
| list_out = [] | |
| for v in vals: | |
| if v <= value: | |
| list_out.append(thirty_seconds_to_duration(v)) | |
| value -= v | |
| return np.array(list_out) | |
| def adjust_to_4_4(prediction_output): | |
| #Adjust prediction output to be in 4/4 time. | |
| #Then, separate the beats into measures. | |
| # This will be the prediction output | |
| new_prediction_output = [] | |
| time = 0 | |
| for beat in prediction_output: | |
| # Calculate the fraction of a measure encompassed by the current beat / chord. | |
| beat_time = (1 / beat[1]) * (1 + 0.5 * beat[2]) | |
| # Calculate the fraction of a measure taken up by all notes in the measure. | |
| # Calculate any residual time to see if this measure (in 4/4 time) is longer than 1 measure. | |
| measure_time = time + beat_time | |
| leftover_time = (measure_time) % 1 | |
| # If the measure count (i.e., the measure integer) has changed and there is significant left-over beat time: | |
| if (int(measure_time) > int(time)) and (leftover_time > 1/128): | |
| # Calculate the initial 32nd notes encompassed by this beat in the current measure. | |
| this_measure_thirty_seconds = int(32 * (1 - time % 1)) | |
| # Calculate the remaining 32nd notes encompassed by this beat in the next measure. | |
| next_measure_thirty_seconds = int(32 * leftover_time) | |
| # Get the Duration object parameters for this measure and the next measure. | |
| this_measure_durations = quantize_thirty_seconds(this_measure_thirty_seconds) | |
| next_measure_durations = quantize_thirty_seconds(next_measure_thirty_seconds) | |
| #print(f'{{ {32 / beat[1]}') | |
| for duration_idx, duration in enumerate(this_measure_durations): | |
| time += (1 / duration[0]) * (1 + 0.5 * duration[1]) | |
| #print(time, '\t', time * 32) | |
| chord = beat[0] if duration_idx == 0 else 'tied' | |
| new_prediction_output.append((chord, duration[0], duration[1])) | |
| for duration in next_measure_durations: | |
| time += (1 / duration[0]) * (1 + 0.5 * duration[1]) | |
| #print(time, '\t', time * 32) | |
| new_prediction_output.append(('tied', duration[0], duration[1])) | |
| continue | |
| time += beat_time | |
| new_prediction_output.append((beat[0], beat[1], beat[2])) | |
| #print(time, '\t', time * 32) | |
| # Code for debugging | |
| #time = 0 | |
| #time2 = 0 | |
| #idx = 0 | |
| #for idx2, beat2 in enumerate(new_prediction_output[:100]): | |
| # beat = prediction_output[idx] | |
| # if time == time2: | |
| # print(beat[0], '\t', time, '\t\t', beat2[0], '\t', time2) | |
| # idx += 1 | |
| # time += (1 / beat[1]) * (1 + 0.5 * beat[2]) | |
| # else: | |
| # print('\t\t\t\t', beat2[0], '\t', time2) | |
| # time2 += (1 / beat2[1]) * (1 + 0.5 * beat2[2]) | |
| # Use the previously calculated cumulative time as the number of measures in the new 4/4 song. | |
| num_measures = int(np.ceil(time)) | |
| song = np.empty(num_measures, dtype=object) | |
| time = 0 | |
| m_idx = 0 | |
| timestamps = [] | |
| for beat in new_prediction_output: | |
| #print(time) | |
| timestamps.append(time) | |
| m_idx = int(time) | |
| if song[m_idx] is None: | |
| song[m_idx] = [beat] | |
| else: | |
| song[m_idx].append(beat) | |
| time += (1 / beat[1]) * (1 + 0.5 * beat[2]) | |
| print(f'4/4 adjusted correctly: {set(range(num_measures)).issubset(set(timestamps))}') | |
| return song | |
| def save_tracks(filepath='_generation.gp5'): | |
| global track_data, track_indice, tracks | |
| songWriter = SongWriter(initialTempo=track_data.loc[track_indices[0]]['tempo']) | |
| for idx in range(len(tracks)): | |
| new_track = adjust_to_4_4(tracks[idx]) | |
| # Get the tempo and tuning (lowest string note) of the song: | |
| #print( track_data.loc[track_indices[idx]]) | |
| tempo = track_data.loc[track_indices[idx]]['tempo'] | |
| instrument = track_data.loc[track_indices[idx]]['instrument'] | |
| name = track_data.loc[track_indices[idx]]['song'] | |
| lowest_string = track_data.loc[track_indices[idx]]['tuning'] | |
| if not as_fingerings: | |
| # Get all the unique pitch values from the new track | |
| pitchnames = set.union(*[set([beat[0].split('_')[0] for beat in measure]) for measure in new_track]) | |
| pitchnames.discard('rest') # Ignore rests | |
| pitchnames.discard('tied') # Ignore tied notes | |
| pitchnames.discard('dead') # Ignore dead/ghost notes | |
| lowest_string = min([MIDI[pitch] for pitch in pitchnames]) # Get the lowest MIDI value / pitch | |
| lowest_string = min(lowest_string, MIDI['E2']) # Don't allow any tunings higher than standard. | |
| # Standard tuning | |
| tuning = {1: MIDI['E4'], | |
| 2: MIDI['B3'], | |
| 3: MIDI['G3'], | |
| 4: MIDI['D3'], | |
| 5: MIDI['A2'], | |
| 6: MIDI['E2']} | |
| if lowest_string <= MIDI['B1']: | |
| # 7-string guitar case | |
| tuning[7] = MIDI['B1'] | |
| downtune = MIDI['B1'] - lowest_string | |
| else: | |
| # downtune the tuning by however much is necessary. | |
| downtune = MIDI['E2'] - lowest_string | |
| tuning = {k: v - downtune for k, v in tuning.items()} # Adjust to the new tuning | |
| # Write the track to the song writer | |
| songWriter.decompress_track(new_track, tuning, tempo=tempo, instrument=instrument, name=name, as_fingerings=as_fingerings) | |
| songWriter.write(filepath) | |
| print('Finished') | |
| ''' |