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') '''