|
import tensorflow.keras as keras
|
|
import json
|
|
import numpy as np
|
|
|
|
|
|
class MelodyGenerator:
|
|
"""
|
|
This class represents a melody generator. It uses a pre-trained model to generate new melodies based on a given seed.
|
|
"""
|
|
|
|
def __init__(self, model_path="model.h5", mapping_path="mapping.json", sequence_length=64):
|
|
"""
|
|
Initializes the MelodyGenerator object.
|
|
|
|
:param model_path: Path to the trained model (default: "model.h5").
|
|
:param mapping_path: Path to the mapping file for symbols to integers (default: "mapping.json").
|
|
:param sequence_length: The length of input sequences for the model (default: 64).
|
|
"""
|
|
self.model = keras.models.load_model(model_path)
|
|
self.sequence_length = sequence_length
|
|
|
|
|
|
with open(mapping_path, "r") as fp:
|
|
self._mappings = json.load(fp)
|
|
|
|
|
|
self._start_symbols = ["/"] * sequence_length
|
|
|
|
def generate_melody(self, seed, num_steps, max_sequence_length, temperature):
|
|
"""
|
|
Generates a melody based on the given seed.
|
|
|
|
:param seed: Initial sequence of musical symbols (e.g., "60 _ _ r").
|
|
:param num_steps: Number of steps (time units) to generate.
|
|
:param max_sequence_length: Maximum length of the input sequence for the model.
|
|
:param temperature: Controls the randomness of the generated melody. Higher temperature -> more random.
|
|
:return: The generated melody as a list of symbols.
|
|
"""
|
|
seed = seed.split()
|
|
melody = seed
|
|
seed = self._start_symbols + seed
|
|
|
|
|
|
seed = [self._mappings[symbol] for symbol in seed]
|
|
|
|
|
|
for _ in range(num_steps):
|
|
seed = seed[-max_sequence_length:]
|
|
onehot_seed = keras.utils.to_categorical(seed, num_classes=len(self._mappings))
|
|
onehot_seed = onehot_seed[np.newaxis, ...]
|
|
|
|
|
|
probabilities = self.model.predict(onehot_seed)[0]
|
|
|
|
|
|
output_int = self._sample_with_temperature(probabilities, temperature)
|
|
seed.append(output_int)
|
|
|
|
|
|
output_symbol = [k for k, v in self._mappings.items() if v == output_int][0]
|
|
|
|
|
|
if output_symbol == "/":
|
|
break
|
|
|
|
melody.append(output_symbol)
|
|
|
|
return melody
|
|
|
|
def _sample_with_temperature(self, probabilities, temperature):
|
|
"""
|
|
Samples an index from the given probabilities with temperature adjustment.
|
|
|
|
:param probabilities: List of probabilities for each symbol.
|
|
:param temperature: The temperature for sampling.
|
|
:return: The sampled index.
|
|
"""
|
|
|
|
predictions = np.log(probabilities) / temperature
|
|
probabilities = np.exp(predictions) / np.sum(np.exp(predictions))
|
|
|
|
|
|
choices = range(len(probabilities))
|
|
index = np.random.choice(choices, p=probabilities)
|
|
|
|
return index
|
|
|
|
|
|
|
|
def load_model(model_path="model.h5", mapping_path="mapping.json"):
|
|
return MelodyGenerator(model_path, mapping_path)
|
|
|