# Bach chorale melody generation
# RNN (LSTM)
# Jean-Pierre Briot
# 03/04/2019

import random

from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM

from representation import *
from metrics import *

config.deep_music_analysis_verbose = False	# analysis verbose flag - default value = False
config.deep_music_training_verbose = True	# training verbose flag - default value = False
config.deep_music_generate_verbose = True	# generation verbose flag - default value = False

print('Loading and parsing the corpus (Bach chorales)')

corpus_names_list = []

for i in range(80):
	corpus_names_list.append('bach/bwv' + str(344 + i))

corpus_list = load_corpus(corpus_names_list)

print('Analyze the corpus (Bach chorales)')

analyze_corpus(corpus_list)

print('Durations (in quarter length): ' + str(music_duration_quarter_length_v))

max_number_quarters = min(music_duration_quarter_length_v)

config.max_number_time_steps = int(max_number_quarters / config.time_step_quarter_length)

print('Max number quarters (in quarter length): ' + str(max_number_quarters))
print('Max number time steps: ' + str(config.max_number_time_steps))

# print('Parse Brazilian hymn')

# br_score = converter.parse('mid/National_Anthems_-_Brazil.mid')
# br_parts = br_score.getElementsByClass('Part')
# br_soprano_part = br_parts[0]

# print('Reanalyze/compute lowest and highest pitches of soprano part')

# analyze_soprano_part(br_soprano_part)

print('Encode the corpus (Bach chorales)')

corpus_data = encode_corpus(corpus_list)
# structure: hierarchy: music/part/encoded_data

print('Construction of the training and validation (test) datasets')

X_train = []
y_train = []

# max_number_time_steps = config.max_number_time_steps
one_hot_size = config.one_hot_size_v[0]

for i in range(len(corpus_data)):
	soprano_data = corpus_data[i][0]
	number_time_steps = int(len(soprano_data) / one_hot_size)
	for j in range(number_time_steps - 1):
		X_train.append([soprano_data[j * one_hot_size:(j + 1) * one_hot_size]])
		y_train.append(soprano_data[(j + 1) * one_hot_size:(j + 2) * one_hot_size])

# X_train : [[[0, 0, 0 ..  0]], ... [[0, 0, 0 ..  0]], (:) ... (:) [[0, 0, 0 ..  0]], ... [[0, 0, 0 ..  0]]]
#             	 chorale 1   					      chorale P
#		   time step 1	    time step N-1	       time step 1		time step N-1

# y_train : [[0, 0, 0 ..  0], ... [0, 0, 0 ..  0], (:) ... (:) [0, 0, 0 ..  0], ... [0, 0, 0 ..  0]]
#          	 chorale 1						 chorale P
#		 time step 2	 time step N		  time step 2	 time step N

X_train, y_train, X_test, y_test = split_training_test(X_train, y_train, 4)

# numpy representations

X_train = np.array(X_train)
y_train = np.array(y_train)
X_test = np.array(X_test)
y_test = np.array(y_test)

print('X_train shape: ' + str(X_train.shape))
print('y_train shape: ' + str(y_train.shape))
print('X_test shape: ' + str(X_test.shape))
print('y_test shape: ' + str(y_test.shape))

# Define the deep learning model/architecture

one_hot_size = config.one_hot_size_v[0]

input_size = one_hot_size

output_size = input_size

input_number_elements = 1			# one time step (element) for each training example

print('Input size: ' + str(input_size))
print('Output size: ' + str(output_size))

hidden_layer_1_size = 200
# hidden_layer_2_size = 200

print('Define and create the model/network')

# expected input data shape:
model = Sequential()
model.add(LSTM(hidden_layer_1_size,
	input_shape = (input_number_elements, input_size)))
model.add(Activation('relu'))
# model.add(LSTM(hidden_layer_2_size))
# model.add(Activation('relu'))
model.add(Dense(output_size))
model.add(Activation('softmax'))

model.compile(loss = 'categorical_crossentropy',
	optimizer = 'rmsprop',
	metrics =	['accuracy'])

# Training

print('Training the model/network')

history = model.fit(X_train,
	y_train,
	batch_size = 20,
	epochs = 50,
	verbose = keras_verbose(),
	validation_data = (X_test, y_test))

print('Model trained')

if config.deep_music_training_verbose:
	print('Show metrics')
	show_metrics(model, history, X_train, y_train, X_test, y_test)

def generate_melody_from_first_note(first_note, generation_number_time_steps):
	melody = []
	current_encoded_note = encode_note(first_note, one_hot_size)		# the duration of the first note is the smallest duration
	melody.extend(current_encoded_note)
	for i in range(generation_number_time_steps):
		current_encoded_note = model.predict(np.array([[current_encoded_note]]))[0]
		melody.extend(current_encoded_note)
	return(melody)

first_note_melody = note.Note('A4', quarterLength = 0.5)

melody_number_time_steps = 500

config.is_deterministic = False

print('Create melody starting with ' + str(first_note_melody.pitch) + ' of duration: ' + str(first_note_melody.duration) + ' with ' + str(melody_number_time_steps) + ' time steps')

melody_data = generate_melody_from_first_note(first_note_melody, melody_number_time_steps)

if not(config.is_deterministic) and (config.multinomial_error_number > 0):
	print(str((config.multinomial_error_number / config.multinomial_sampling_number) * 100) + '% of random multinomial errors when sampling')

print('Create the score')

score = create_score(1, [melody_data])

print('Write the score')

score.write('midi', 'mid/lstm_melody.mid')




