get it working, on music21 and sequence style enoding
This commit is contained in:
parent
76f26837cd
commit
fede93c36e
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import midi
|
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
import pypianoroll as roll
|
import pypianoroll as roll
|
||||||
from keras.layers import Input, Dense, Conv2D
|
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
|
from keras.models import Model, Sequential
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import settings
|
import settings
|
||||||
|
import random
|
||||||
import pickle
|
import pickle
|
||||||
|
from tqdm import trange, tqdm
|
||||||
import sys
|
import sys
|
||||||
|
from music21 import converter, instrument, note, chord, stream
|
||||||
|
|
||||||
trained_model_path = sys.argv[1]
|
trained_model_path = sys.argv[1]
|
||||||
output_path = sys.argv[2]
|
output_path = sys.argv[2]
|
||||||
treshold = float(sys.argv[3])
|
|
||||||
|
|
||||||
# load and predict
|
# load and predict
|
||||||
|
print('Loading... {}'.format(trained_model_path))
|
||||||
model = pickle.load(open(trained_model_path, 'rb'))
|
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))
|
seed = [random.randint(0,50) for x in range(8)]
|
||||||
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)
|
|
||||||
|
|
||||||
generated_sample = music.reshape(4*96,128)
|
music = []
|
||||||
|
|
||||||
# binarize generated music
|
print('Generating...')
|
||||||
generated_sample = generated_sample > treshold * generated_sample.max()
|
for i in trange(500):
|
||||||
# generated_sample = np.clip(generated_sample,0,1) * 128
|
predicted_vector = model.predict(np.array(seed).reshape(1,8,1))
|
||||||
|
predicted_index = np.argmax(predicted_vector)
|
||||||
|
|
||||||
# save to midi
|
music.append(int_to_note[predicted_index])
|
||||||
generated_midi = midi.to_midi(generated_sample, output_path='{}.mid'.format(output_path), is_drum=True, program=0, )
|
|
||||||
|
|
||||||
#save plot for preview
|
seed.append(predicted_index)
|
||||||
roll.plot(generated_midi, filename='{}.png'.format(output_path))
|
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!')
|
||||||
|
@ -9,74 +9,62 @@ from math import floor
|
|||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import pickle
|
import pickle
|
||||||
|
from music21 import converter, instrument, note, chord, stream
|
||||||
|
import music21
|
||||||
|
|
||||||
midi_folder_path = sys.argv[1]
|
midi_folder_path = sys.argv[1]
|
||||||
output_path = sys.argv[2]
|
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
|
for instrument, sequence in seq_by_instrument.items():
|
||||||
# np.roll(sample, pitch_interval, axis=1) for transposition
|
for i in range(len(sequence)-8) :
|
||||||
# np.roll(sample, time_steps, axis=0) for time shifting
|
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 : [] )
|
return X_train_by_instrument, y_train_by_instrument
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print('Exporting...')
|
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 directory, subdirectories, files in os.walk(midi_folder_path):
|
||||||
for midi_file in tqdm(files):
|
for midi_file in tqdm(files):
|
||||||
midi_file_path = os.path.join(directory, midi_file)
|
midi_file_path = os.path.join(directory, midi_file)
|
||||||
#load midi ro pypianoroll - Multirack
|
_X_train, _y_train = to_sequence(midi_file_path)
|
||||||
try:
|
|
||||||
multitrack = roll.parse(midi_file_path)
|
|
||||||
except:
|
|
||||||
# IDEA: Log errors, and save to file?
|
|
||||||
continue
|
|
||||||
|
|
||||||
for key, value in to_samples(multitrack).items():
|
for (X_key, X_value), (y_key, y_value) in zip(_X_train.items(), _y_train.items()):
|
||||||
samples_pack_by_instrument[key].extend(value)
|
train_X[X_key].extend(np.array(X_value))
|
||||||
|
train_y[y_key].extend(np.array(y_value))
|
||||||
|
|
||||||
# this is for intrument separation
|
# this is for intrument separation
|
||||||
print('Saving...')
|
print('Saving...')
|
||||||
if not os.path.exists(output_path):
|
if not os.path.exists(output_path):
|
||||||
os.makedirs(output_path)
|
os.makedirs(output_path)
|
||||||
for key, value in tqdm(samples_pack_by_instrument.items()):
|
for (X_key, X_value), (y_key, y_value) in tqdm(zip(train_X.items(), train_y.items())):
|
||||||
np.savez_compressed('{}/{}.npz'.format(output_path, key), np.array(value))
|
if X_key == y_key:
|
||||||
|
np.savez_compressed('{}/{}.npz'.format(output_path, X_key), np.array(X_value), np.array(y_value))
|
||||||
|
|
||||||
print('Done!')
|
print('Done!')
|
||||||
|
|
||||||
|
@ -5,35 +5,62 @@ import settings
|
|||||||
from tensorflow.keras import layers
|
from tensorflow.keras import layers
|
||||||
from keras.layers import Input, Dense, Conv2D, Flatten, LSTM, Dropout, TimeDistributed, RepeatVector, Activation, Bidirectional, Reshape
|
from keras.layers import Input, Dense, Conv2D, Flatten, LSTM, Dropout, TimeDistributed, RepeatVector, Activation, Bidirectional, Reshape
|
||||||
from keras.models import Model, Sequential
|
from keras.models import Model, Sequential
|
||||||
|
from keras.utils.np_utils import to_categorical
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import sys
|
import sys
|
||||||
import pickle
|
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_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]
|
save_model_path = sys.argv[2]
|
||||||
epochs = int(sys.argv[3])
|
epochs = int(sys.argv[3])
|
||||||
|
|
||||||
model = Sequential()
|
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(Dropout(0.3))
|
||||||
model.add(LSTM(512, return_sequences=True))
|
model.add(LSTM(512, return_sequences=True))
|
||||||
model.add(Dropout(0.3))
|
model.add(Dropout(0.3))
|
||||||
model.add(LSTM(128))
|
model.add(LSTM(512))
|
||||||
model.add(Dense(128))
|
model.add(Dense(256))
|
||||||
model.add(Dropout(0.3))
|
model.add(Dropout(0.3))
|
||||||
model.add(Dense(128*96))
|
model.add(Dense(n_vocab))
|
||||||
model.add(Activation('softmax'))
|
model.add(Activation('softmax'))
|
||||||
model.add(Reshape((96, 128)))
|
|
||||||
model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
|
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 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
|
# save trained model
|
||||||
pickle_path = '{}.pickle'.format(save_model_path)
|
pickle.dump(model, open(save_model_path,'wb'))
|
||||||
pickle.dump(model, open(pickle_path,'wb'))
|
pickle.dump(int_to_note, open('{}_dict'.format(save_model_path),'wb'))
|
||||||
print("Model save to {}".format(pickle_path))
|
print('Done!')
|
||||||
|
print("Model saved to: {}".format(save_model_path))
|
||||||
|
Loading…
Reference in New Issue
Block a user