get it working, on music21 and sequence style enoding

This commit is contained in:
Cezary Pukownik 2019-06-01 17:05:38 +02:00
parent 76f26837cd
commit fede93c36e
3 changed files with 118 additions and 74 deletions

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3
import numpy as np
import midi
import tensorflow as tf
import pypianoroll as roll
from keras.layers import Input, Dense, Conv2D
@ -11,29 +10,59 @@ from keras.layers import Input, Dense, Conv2D, Flatten, LSTM, Dropout, TimeDistr
from keras.models import Model, Sequential
import matplotlib.pyplot as plt
import settings
import random
import pickle
from tqdm import trange, tqdm
import sys
from music21 import converter, instrument, note, chord, stream
trained_model_path = sys.argv[1]
output_path = sys.argv[2]
treshold = float(sys.argv[3])
# load and predict
print('Loading... {}'.format(trained_model_path))
model = pickle.load(open(trained_model_path, 'rb'))
int_to_note = pickle.load(open('{}_dict'.format(trained_model_path), 'rb'))
music = np.empty((4,96,128))
for x in range(4):
generate_seed = np.random.randint(0, 127, 12288).reshape(1,96,128)
music[x] = model.predict(generate_seed).reshape(96,128)
seed = [random.randint(0,50) for x in range(8)]
generated_sample = music.reshape(4*96,128)
music = []
# binarize generated music
generated_sample = generated_sample > treshold * generated_sample.max()
# generated_sample = np.clip(generated_sample,0,1) * 128
print('Generating...')
for i in trange(500):
predicted_vector = model.predict(np.array(seed).reshape(1,8,1))
predicted_index = np.argmax(predicted_vector)
# save to midi
generated_midi = midi.to_midi(generated_sample, output_path='{}.mid'.format(output_path), is_drum=True, program=0, )
music.append(int_to_note[predicted_index])
#save plot for preview
roll.plot(generated_midi, filename='{}.png'.format(output_path))
seed.append(predicted_index)
seed = seed[1:9]
print('Saving...')
offset = 0
output_notes = []
for event in tqdm(music):
if (' ' in event) or event.isdigit():
notes_in_chord = event.split(' ')
notes = []
for current_note in notes_in_chord:
new_note = note.Note(current_note)
new_note.storedInstrument = instrument.Piano()
notes.append(new_note)
new_chord = chord.Chord(notes)
new_chord.offset = offset
output_notes.append(new_chord)
else:
new_note = note.Note(event)
new_note.offset = offset
new_note.storedInstrument = instrument.Piano()
output_notes.append(new_note)
offset += 0.5
midi_stream = stream.Stream(output_notes)
midi_stream.write('midi', fp='{}.mid'.format(output_path))
print('Done!')

View File

@ -9,74 +9,62 @@ from math import floor
import sys
from collections import defaultdict
import pickle
from music21 import converter, instrument, note, chord, stream
import music21
midi_folder_path = sys.argv[1]
output_path = sys.argv[2]
def to_samples(multitrack, midi_res=settings.midi_resolution, how='by_group'):
def to_sequence(midi_path):
seq_by_instrument = defaultdict( lambda : [] )
midi_file = music21.converter.parse(midi_path)
stream = music21.instrument.partitionByInstrument(midi_file)
for part in stream:
for event in part:
if part.partName != None:
# TODO: add note lenght as parameter
if isinstance(event, music21.note.Note):
# to_export_event = (str(event.pitch), event.quarterLength)
to_export_event = str(event.pitch)
seq_by_instrument[part.partName].append(to_export_event)
elif isinstance(event, music21.chord.Chord):
to_export_event = ' '.join(str(note) for note in event.pitches)
# to_export_event = (' '.join(str(note) for note in event.pitches), event.quarterLength)
seq_by_instrument[part.partName].append(to_export_event)
#how = 'by_group', 'by_instrument', 'merged',
X_train_by_instrument = defaultdict( lambda : [] )
y_train_by_instrument = defaultdict( lambda : [] )
# TODO: add transpositions of every sample to every possible key transposition
# np.roll(sample, pitch_interval, axis=1) for transposition
# np.roll(sample, time_steps, axis=0) for time shifting
for instrument, sequence in seq_by_instrument.items():
for i in range(len(sequence)-8) :
X_train_by_instrument[instrument].append(np.array(sequence[i: i + 8])) # <seq lenth
y_train_by_instrument[instrument].append(np.array(sequence[i + 8]))
# TODO: Notes to integers
samples_by_instrument = defaultdict( lambda : [] )
for track in multitrack.tracks:
key = settings.midi_group[track.program + 1] if not track.is_drum else 'Drums'
# this makes pack of samples of N x 96 x 128 shape
number_of_beats = floor(track.pianoroll.shape[0] / midi_res)
track_pianoroll = track.pianoroll[: number_of_beats * midi_res]
track_beats = track_pianoroll.reshape(number_of_beats, midi_res, 128)
# save collected pack of data to dictionary with samples packs for groups of instruments
for sample in track_beats:
if sample.sum() != 0:
samples_by_instrument[key].append(sample)
# TODO: add posibility of choosing between saving samples to groups of instrument, or to every instrument separatly or with no differance
# TODO: add option, for looking only for one instrument/group
# TODO: add option for colecting, more than one beat per sample (min 4)
return samples_by_instrument
def to_midi(samples, output_path=settings.generated_midi_path, program=0, tempo=120, is_drum=False, beat_resolution=settings.beat_resolution):
tracks = [roll.Track(samples, program=program, is_drum=is_drum)]
return_midi = roll.Multitrack(tracks=tracks, tempo=tempo, downbeat=[0, 96, 192, 288], beat_resolution=beat_resolution)
roll.write(return_midi, output_path)
return return_midi
# TODO: Make optial function to erase information of note lenth - ??
def ignore_note_lenght():
pass
return X_train_by_instrument, y_train_by_instrument
def main():
print('Exporting...')
samples_pack_by_instrument = defaultdict( lambda : list() )
train_X = defaultdict( lambda : [] )
train_y = defaultdict( lambda : [] )
for directory, subdirectories, files in os.walk(midi_folder_path):
for midi_file in tqdm(files):
midi_file_path = os.path.join(directory, midi_file)
#load midi ro pypianoroll - Multirack
try:
multitrack = roll.parse(midi_file_path)
except:
# IDEA: Log errors, and save to file?
continue
_X_train, _y_train = to_sequence(midi_file_path)
for key, value in to_samples(multitrack).items():
samples_pack_by_instrument[key].extend(value)
for (X_key, X_value), (y_key, y_value) in zip(_X_train.items(), _y_train.items()):
train_X[X_key].extend(np.array(X_value))
train_y[y_key].extend(np.array(y_value))
# this is for intrument separation
print('Saving...')
if not os.path.exists(output_path):
os.makedirs(output_path)
for key, value in tqdm(samples_pack_by_instrument.items()):
np.savez_compressed('{}/{}.npz'.format(output_path, key), np.array(value))
for (X_key, X_value), (y_key, y_value) in tqdm(zip(train_X.items(), train_y.items())):
if X_key == y_key:
np.savez_compressed('{}/{}.npz'.format(output_path, X_key), np.array(X_value), np.array(y_value))
print('Done!')

View File

@ -5,35 +5,62 @@ import settings
from tensorflow.keras import layers
from keras.layers import Input, Dense, Conv2D, Flatten, LSTM, Dropout, TimeDistributed, RepeatVector, Activation, Bidirectional, Reshape
from keras.models import Model, Sequential
from keras.utils.np_utils import to_categorical
import numpy as np
import sys
import pickle
def load_data(samples_path):
print('Loading... {}'.format(train_data_path))
train_X = np.load(train_data_path, allow_pickle=True)['arr_0']
train_y = np.load(train_data_path, allow_pickle=True)['arr_1']
return train_X, train_y
# TODO: make transformer class with fit, transform and reverse definitions
def preprocess_samples(train_X, train_y):
vocab = np.unique(train_X)
n_vocab = vocab.shape[0]
note_to_int = dict((note, number) for number, note in enumerate(vocab))
int_to_note = dict((number, note) for number, note in enumerate(vocab))
_train_X = []
_train_y = []
for sample in train_X:
# TODO: add normalizasion
_train_X.append([note_to_int[note] for note in sample])
train_X = np.array(_train_X).reshape(train_X.shape[0], train_X.shape[1], 1)
train_y = np.array([note_to_int[note] for note in train_y]).reshape(-1,1)
train_y = to_categorical(train_y)
return train_X, train_y, n_vocab, int_to_note
train_data_path = sys.argv[1]
train_X, train_y = load_data(train_data_path)
train_X, train_y, n_vocab, int_to_note = preprocess_samples(train_X, train_y)
save_model_path = sys.argv[2]
epochs = int(sys.argv[3])
model = Sequential()
model.add(LSTM(128,input_shape=(96, 128),return_sequences=True))
model.add(LSTM(512, input_shape=(train_X.shape[1], train_X.shape[2]), return_sequences=True))
model.add(Dropout(0.3))
model.add(LSTM(512, return_sequences=True))
model.add(Dropout(0.3))
model.add(LSTM(128))
model.add(Dense(128))
model.add(LSTM(512))
model.add(Dense(256))
model.add(Dropout(0.3))
model.add(Dense(128*96))
model.add(Dense(n_vocab))
model.add(Activation('softmax'))
model.add(Reshape((96, 128)))
model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
# load training data
print('Traing Samples: {}'.format(train_data_path))
train_X = np.load(train_data_path)['arr_0']
# model training
model.fit(train_X, train_X, epochs=epochs, batch_size=32)
print('Training...')
model.fit(train_X, train_y, epochs=epochs, batch_size=64)
# save trained model
pickle_path = '{}.pickle'.format(save_model_path)
pickle.dump(model, open(pickle_path,'wb'))
print("Model save to {}".format(pickle_path))
pickle.dump(model, open(save_model_path,'wb'))
pickle.dump(int_to_note, open('{}_dict'.format(save_model_path),'wb'))
print('Done!')
print("Model saved to: {}".format(save_model_path))