|
import collections |
|
|
|
|
|
import numpy as np |
|
|
|
import pandas as pd |
|
import pretty_midi |
|
import seaborn as sns |
|
|
|
from matplotlib import pyplot as plt |
|
from typing import Optional |
|
|
|
import tensorflow as tf |
|
|
|
import keras |
|
|
|
from tensorflow.keras.utils import custom_object_scope |
|
|
|
import streamlit as st |
|
|
|
from midi2audio import FluidSynth |
|
import tempfile |
|
import os |
|
import base64 |
|
|
|
def midi_to_notes(midi_file: str) -> pd.DataFrame: |
|
pm = pretty_midi.PrettyMIDI(midi_file) |
|
instrument = pm.instruments[0] |
|
notes = collections.defaultdict(list) |
|
|
|
sorted_notes = sorted(instrument.notes, key=lambda note: note.start) |
|
prev_start = sorted_notes[0].start |
|
|
|
for note in sorted_notes: |
|
start = note.start |
|
end = note.end |
|
notes['pitch'].append(note.pitch) |
|
notes['start'].append(start) |
|
notes['end'].append(end) |
|
notes['step'].append(start - prev_start) |
|
notes['duration'].append(end - start) |
|
prev_start = start |
|
|
|
return pd.DataFrame({name: np.array(value) for name, value in notes.items()}) |
|
|
|
def notes_to_midi( |
|
notes: pd.DataFrame, |
|
out_file: str, |
|
instrument_name: str, |
|
velocity: int = 100, |
|
) -> pretty_midi.PrettyMIDI: |
|
|
|
pm = pretty_midi.PrettyMIDI() |
|
instrument = pretty_midi.Instrument( |
|
program=pretty_midi.instrument_name_to_program( |
|
instrument_name)) |
|
|
|
prev_start = 0 |
|
for i, note in notes.iterrows(): |
|
start = float(prev_start + note['step']) |
|
end = float(start + note['duration']) |
|
note = pretty_midi.Note( |
|
velocity=velocity, |
|
pitch=int(note['pitch']), |
|
start=start, |
|
end=end, |
|
) |
|
instrument.notes.append(note) |
|
prev_start = start |
|
|
|
pm.instruments.append(instrument) |
|
pm.write(out_file) |
|
return pm |
|
|
|
def plot_roll(notes: pd.DataFrame, count: Optional[int] = None): |
|
if count: |
|
title = f'First {count} notes' |
|
else: |
|
title = f'Whole track' |
|
count = len(notes['pitch']) |
|
plt.figure(figsize=(20, 4)) |
|
plot_pitch = np.stack([notes['pitch'], notes['pitch']], axis=0) |
|
plot_start_stop = np.stack([notes['start'], notes['end']], axis=0) |
|
plt.plot( |
|
plot_start_stop[:, :count], plot_pitch[:, :count], color="b", marker=".") |
|
plt.xlabel('Time [s]') |
|
plt.ylabel('Pitch') |
|
_ = plt.title(title) |
|
|
|
def plot_distributions(notes: pd.DataFrame, drop_percentile=2.5): |
|
plt.figure(figsize=[15, 5]) |
|
plt.subplot(1, 3, 1) |
|
sns.histplot(notes, x="pitch", bins=20) |
|
|
|
plt.subplot(1, 3, 2) |
|
max_step = np.percentile(notes['step'], 100 - drop_percentile) |
|
sns.histplot(notes, x="step", bins=np.linspace(0, max_step, 21)) |
|
|
|
def predict_next_note( |
|
notes: np.ndarray, |
|
model: tf.keras.Model, |
|
temperature: float = 1.0) -> tuple[int, float, float]: |
|
|
|
assert temperature > 0 |
|
|
|
inputs = tf.expand_dims(notes, 0) |
|
|
|
predictions = model.predict(inputs) |
|
pitch_logits = predictions['pitch'] |
|
step = predictions['step'] |
|
duration = predictions['duration'] |
|
|
|
pitch_logits /= temperature |
|
pitch = tf.random.categorical(pitch_logits, num_samples=1) |
|
pitch = tf.squeeze(pitch, axis=-1) |
|
duration = tf.squeeze(duration, axis=-1) |
|
step = tf.squeeze(step, axis=-1) |
|
|
|
step = tf.maximum(0, step) |
|
duration = tf.maximum(0, duration) |
|
|
|
return int(pitch), float(step), float(duration) |
|
|
|
def mse_with_positive_pressure(y_true: tf.Tensor, y_pred: tf.Tensor): |
|
mse = (y_true - y_pred) ** 2 |
|
positive_pressure = 10 * tf.maximum(-y_pred, 0.0) |
|
return tf.reduce_mean(mse + positive_pressure) |
|
|
|
def calcular_duracion_midi(archivo_midi): |
|
midi = pretty_midi.PrettyMIDI(archivo_midi) |
|
return midi.get_end_time() |
|
|
|
def main(): |
|
seed = 42 |
|
tf.random.set_seed(seed) |
|
np.random.seed(seed) |
|
|
|
st.title('GENERADOR DE MELODIAS CON RNN') |
|
|
|
|
|
|
|
|
|
out_file = 'output.mid' |
|
uploaded_file = st.file_uploader("Sube un archivo MIDI") |
|
|
|
model='' |
|
pesos='' |
|
|
|
with st.container(height = None, border = True): |
|
option = st.selectbox( |
|
"Elige con que modelo entrenar", |
|
("Maestro", "Lakh")) |
|
|
|
option_musica = st.selectbox( |
|
"Elige instrumento a generar las melodias", |
|
("Piano", "Chromatic Percussion", "Organ", "Guitar", "Bass", "Strings", "Ensemble", "Brass", |
|
"Reed", "Pipe", "Synth Lead", "Synth Pad", "Synth Effects", "Ethnic", "Percussive", "Sound Effects")) |
|
|
|
num_predictions = st.number_input("Ingrese el n煤mero de notas:", min_value=100, max_value=150, value=120, step=1) |
|
|
|
if uploaded_file and option is not None: |
|
|
|
if option=="Maestro": |
|
model="mi_modelo_music.h5" |
|
pesos="mi_pesos_music.h5" |
|
else: |
|
model="mi_modelo03_music.h5" |
|
pesos="mi_pesos03_music.h5" |
|
|
|
st.subheader("Archivo cargado:") |
|
st.write(uploaded_file.name) |
|
|
|
|
|
with open(uploaded_file.name, 'wb') as f: |
|
f.write(uploaded_file.getbuffer()) |
|
|
|
sample_file=uploaded_file.name |
|
|
|
|
|
duracion = calcular_duracion_midi(sample_file) |
|
minutos, segundos = divmod(duracion, 60) |
|
|
|
st.write(f"La duraci贸n del archivo MIDI subido es: {int(minutos)}:{int(segundos):02d}") |
|
|
|
st.subheader("Modelo elegido:") |
|
|
|
with st.container(height = None, border = True): |
|
st.write(option, f" de tipo instrumental ", option_musica) |
|
|
|
|
|
with custom_object_scope({'mse_with_positive_pressure': mse_with_positive_pressure}): |
|
model = keras.models.load_model(model) |
|
|
|
model.load_weights(pesos, skip_mismatch=False, by_name=False, options=None) |
|
|
|
|
|
pm = pretty_midi.PrettyMIDI(sample_file) |
|
instrument_name = "" |
|
|
|
if option_musica is not None: |
|
if option_musica=="Piano": |
|
instrument_name="Acoustic Grand Piano" |
|
elif option_musica=="Chromatic Percussion": |
|
instrument_name="Celesta" |
|
elif option_musica=="Organ": |
|
instrument_name="Hammond Organ" |
|
elif option_musica=="Guitar": |
|
instrument_name="Acoustic Guitar (nylon)" |
|
elif option_musica=="Bass": |
|
instrument_name="Acoustic Bass" |
|
elif option_musica=="Strings": |
|
instrument_name="Violin" |
|
elif option_musica=="Ensemble": |
|
instrument_name="String Ensemble 1" |
|
elif option_musica=="Brass": |
|
instrument_name="Trumpet" |
|
elif option_musica=="Reed": |
|
instrument_name="Soprano Sax" |
|
elif option_musica=="Pipe": |
|
instrument_name="Piccolo" |
|
elif option_musica=="Synth Lead": |
|
instrument_name="Lead 2 (sawtooth)" |
|
elif option_musica=="Synth Pad": |
|
instrument_name="Pad 2 (warm)" |
|
elif option_musica=="Synth Effects": |
|
instrument_name="FX 2 (soundtrack)" |
|
elif option_musica=="Ethnic": |
|
instrument_name="Banjo" |
|
elif option_musica=="Percussive": |
|
instrument_name="Melodic Tom" |
|
elif option_musica=="Sound Effects": |
|
instrument_name="Guitar Fret Noise" |
|
else: |
|
instrument_name=pretty_midi.program_to_instrument_name(pm.instruments[0].program) |
|
|
|
raw_notes = midi_to_notes(sample_file) |
|
key_order = ['pitch', 'step', 'duration'] |
|
seq_length = 25 |
|
vocab_size = 128 |
|
temperature = 2.0 |
|
|
|
sample_notes = np.stack([raw_notes[key] for key in key_order], axis=1) |
|
input_notes = (sample_notes[:seq_length] / np.array([vocab_size, 1, 1])) |
|
generated_notes = [] |
|
prev_start = 0 |
|
for _ in range(num_predictions): |
|
pitch, step, duration = predict_next_note(input_notes, model, temperature) |
|
start = prev_start + step |
|
end = start + duration |
|
input_note = (pitch, step, duration) |
|
generated_notes.append((*input_note, start, end)) |
|
input_notes = np.delete(input_notes, 0, axis=0) |
|
input_notes = np.append(input_notes, np.expand_dims(input_note, 0), axis=0) |
|
prev_start = start |
|
|
|
generated_notes = pd.DataFrame( |
|
generated_notes, columns=(*key_order, 'start', 'end')) |
|
|
|
notes_to_midi( |
|
generated_notes, out_file=out_file, instrument_name=instrument_name) |
|
|
|
|
|
st.title("Generador de notas musicales") |
|
|
|
archivo_midi = open(out_file, 'rb').read() |
|
|
|
with st.container(height = None, border = True): |
|
st.download_button( |
|
label="Descargar MIDI", |
|
data=archivo_midi, |
|
file_name=out_file, |
|
mime='audio/midi' |
|
) |
|
|
|
|
|
duracion_f = calcular_duracion_midi(out_file) |
|
minutos_f, segundos_f = divmod(duracion_f, 60) |
|
|
|
st.write(f"La duraci贸n del archivo MIDI resultante es: {int(minutos_f)}:{int(segundos_f):02d}") |
|
|
|
if __name__ == "__main__": |
|
main() |