text: 
  from __future__ import print_function
  import sys
  from music21 import *
  import numpy as np
  from typing import TypeVar
  
  from grammar import *
  from qa import *
  from preprocess import * 
  from music_utils import *
  from data_utils import *
  from keras.models import load_model, Model
  from keras.layers import Dense, Activation, Dropout, Input, LSTM, Reshape, Lambda, RepeatVector
  from keras.initializers import glorot_uniform
  from keras.utils import to_categorical
  from keras.optimizers import Adam
  from keras import backend as K
  
  # Types
  ReshapeType = TypeVar('keras.layers.core.Reshape')
  LSTMType = TypeVar('keras.layers.recurrent.LSTM')
  DenseType = TypeVar('keras.layers.core.Dense')
  
  class S():
      n_values : int
      reshapor : ReshapeType
      LSTM_cell : LSTMType
      densor : DenseType
  
  def djmodel(Tx, n_a, n_values):
      """
      Implement the model
      
      Arguments:
      Tx -- length of the sequence in a corpus
      n_a -- the number of activations used in our model
      n_values -- number of unique values in the music data 
      
      Returns:
      model -- a keras instance model with n_a activations
      """
      
      # Define the input layer and specify the shape
      X = Input(shape=(Tx, n_values))
      
      # Define the initial hidden state a0 and initial cell state c0
      # using `Input`
      a0 = Input(shape=(n_a,), name='a0')
      c0 = Input(shape=(n_a,), name='c0')
      a = a0
      c = c0
      
      ### START CODE HERE ### 
      # Step 1: Create empty list to append the outputs while you iterate (≈1 line)
      outputs = []
      
      # Step 2: Loop
      for t in range(Tx):
          
          # Step 2.A: select the "t"th time step vector from X. 
          x = Lambda(lambda x: x[:,t,:])(X)
          # Step 2.B: Use reshapor to reshape x to be (1, n_values) (≈1 line)
          x = S.reshapor(x)
          # Step 2.C: Perform one step of the LSTM_cell
          a, _, c = S.LSTM_cell(inputs=x, initial_state=[a, c])
          # Step 2.D: Apply densor to the hidden state output of LSTM_Cell
          out = S.densor(a)
          # Step 2.E: add the output to "outputs"
          outputs.append(out)
          
      # Step 3: Create model instance
      model = Model(inputs=[X, a0, c0], outputs=outputs)
      
      ### END CODE HERE ###
      
      return model
  
  def music_inference_model(LSTM_cell, densor, n_values = 78, n_a = 64, Ty = 100):
      """
      Uses the trained "LSTM_cell" and "densor" from model() to generate a sequence of values.
      
      Arguments:
      LSTM_cell -- the trained "LSTM_cell" from model(), Keras layer object
      densor -- the trained "densor" from model(), Keras layer object
      n_values -- integer, number of unique values
      n_a -- number of units in the LSTM_cell
      Ty -- integer, number of time steps to generate
      
      Returns:
      inference_model -- Keras model instance
      """
      
      # Define the input of your model with a shape 
      x0 = Input(shape=(1, n_values))
      
      # Define s0, initial hidden state for the decoder LSTM
      a0 = Input(shape=(n_a,), name='a0')
      c0 = Input(shape=(n_a,), name='c0')
      a = a0
      c = c0
      x = x0
  
      ### START CODE HERE ###
      # Step 1: Create an empty list of "outputs" to later store your predicted values (≈1 line)
      outputs = []
      
      # Step 2: Loop over Ty and generate a value at every time step
      for t in range(Ty):
          
          # Step 2.A: Perform one step of LSTM_cell (≈1 line)
          a, _, c = LSTM_cell(x, initial_state=[a, c])
          
          # Step 2.B: Apply Dense layer to the hidden state output of the LSTM_cell (≈1 line)
          out = densor(a)
  
          # Step 2.C: Append the prediction "out" to "outputs". out.shape = (None, 78) (≈1 line)
          outputs.append(out)
          
          # Step 2.D: Select the next value according to "out", and set "x" to be the one-hot representation of the
          #           selected value, which will be passed as the input to LSTM_cell on the next step. We have provided 
          #           the line of code you need to do this. 
          x = Lambda(one_hot)(out)
          
      # Step 3: Create model instance with the correct "inputs" and "outputs" (≈1 line)
      inference_model = Model(inputs=[x0, a0, c0], outputs=outputs)
      
      ### END CODE HERE ###
      
      return inference_model
  
  def predict_and_sample(inference_model, x_initializer = x_initializer, a_initializer = a_initializer, 
                         c_initializer = c_initializer):
      """
      Predicts the next value of values using the inference model.
      
      Arguments:
      inference_model -- Keras model instance for inference time
      x_initializer -- numpy array of shape (1, 1, 78), one-hot vector initializing the values generation
      a_initializer -- numpy array of shape (1, n_a), initializing the hidden state of the LSTM_cell
      c_initializer -- numpy array of shape (1, n_a), initializing the cell state of the LSTM_cel
      
      Returns:
      results -- numpy-array of shape (Ty, 78), matrix of one-hot vectors representing the values generated
      indices -- numpy-array of shape (Ty, 1), matrix of indices representing the values generated
      """
      
      ### START CODE HERE ###
      # Step 1: Use your inference model to predict an output sequence given x_initializer, a_initializer and c_initializer.
      pred = inference_model.predict([x_initializer, a_initializer, c_initializer])
      # <Gema> Generates output predictions for the input samples.Computation is done in batches.
      # Step 2: Convert "pred" into an np.array() of indices with the maximum probabilities
      indices = np.argmax(pred, axis=-1)
      # <Gema> given some 2D matrix, argmax() will find the index of the greatest value. 
      # But, you might rather be interested in index of the maximum value within each row or column. 
      #The axis argument lets you control that.In the jazz code, pred has a list of numpy arrays. 
      #Without the axis argument, I believe it's just finding the index of the largest value if all the arrays were flattened. 
      # But what you're really trying to find is within each array what is the index of the largest value. 
      #This is because each array represents the probabilities of notes at that time step, and we're stringing together notes across time steps. 
      #We want to find the highest probability note at each time step i.e. within each vector.
      # Step 3: Convert indices to one-hot vectors, the shape of the results should be (1, )
      results = to_categorical(indices, num_classes=78)
      ### END CODE HERE ###
      
      return results, indices
  
  def main():
      X, Y, n_values, indices_values = load_music_utils()
      print('number of training examples:', X.shape[0])
      print('Tx (length of sequence):', X.shape[1])
      print('total # of unique values:', n_values)
      print('shape of X:', X.shape)
      print('Shape of Y:', Y.shape)
  
      # number of dimensions for the hidden state of each LSTM cell.
      n_a = 64
  
      S.n_values = 78 # number of music values
      S.reshapor = Reshape((1, S.n_values))                        # Used in Step 2.B of djmodel(), below
      S.LSTM_cell = LSTM(n_a, return_state = True)         # Used in Step 2.C
      S.densor = Dense(S.n_values, activation='softmax')     # Used in Step 2.D
  
      model = djmodel(Tx = 30 , n_a = 64, n_values = 78)
      model.summary()
  
      opt = Adam(lr=0.01, beta_1=0.9, beta_2=0.999, decay=0.01)
  
      model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
  
      m = 60
      a0 = np.zeros((m, n_a))
      c0 = np.zeros((m, n_a))
  
      model.fit([X, a0, c0], list(Y), steps_per_epoch=60, epochs=1000)
  
      inference_model = music_inference_model(S.LSTM_cell, S.densor, n_values = 78, n_a = 64, Ty = 50)
      inference_model.summary()
  
      x_initializer = np.zeros((1, 1, 78))
      a_initializer = np.zeros((1, n_a))
      c_initializer = np.zeros((1, n_a))
  
      results, indices = predict_and_sample(inference_model, x_initializer, a_initializer, c_initializer)
      print("np.argmax(results[12]) =", np.argmax(results[12]))
      print("np.argmax(results[17]) =", np.argmax(results[17]))
      print("list(indices[12:18]) =", list(indices[12:18]))
  
      out_stream = generate_music(inference_model)
  
  
  if __name__=="__main__":
      main()
  