|
import pickle |
|
import pretty_midi |
|
import gradio as gr |
|
from music21 import * |
|
from midi2audio import FluidSynth |
|
|
|
import torch |
|
import torch.nn as nn |
|
from torch.nn import functional as F |
|
|
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
|
|
|
file_path = './objects/int_to_note.pkl' |
|
with open(file_path, 'rb') as f: |
|
int_to_note = pickle.load(f) |
|
|
|
file_path = './objects/note_to_int.pkl' |
|
with open(file_path, 'rb') as f: |
|
note_to_int = pickle.load(f) |
|
|
|
|
|
class GenerationRNN(nn.Module): |
|
def __init__(self, input_size, hidden_size, output_size, n_layers=1): |
|
super(GenerationRNN, self).__init__() |
|
self.input_size = input_size |
|
self.hidden_size = hidden_size |
|
self.output_size = output_size |
|
self.n_layers = n_layers |
|
|
|
self.embedding = nn.Embedding(input_size, hidden_size) |
|
self.gru = nn.GRU(hidden_size, hidden_size, n_layers) |
|
self.decoder = nn.Linear(hidden_size * n_layers, output_size) |
|
|
|
def forward(self, input, hidden): |
|
|
|
|
|
input = self.embedding(input.view(1, -1)) |
|
|
|
output, hidden = self.gru(input, hidden) |
|
|
|
|
|
output = self.decoder(hidden.view(1, -1)) |
|
|
|
return output, hidden |
|
|
|
def init_hidden(self): |
|
return torch.zeros(self.n_layers, 1, self.hidden_size).to(device) |
|
|
|
|
|
def predict_multimomial(net, prime_seq, predict_len, temperature=0.8): |
|
''' |
|
Arguments: |
|
prime_seq - priming sequence (converted t) |
|
predict_len - number of notes to predict for after prime sequence |
|
''' |
|
hidden = net.init_hidden() |
|
|
|
predicted = prime_seq.copy() |
|
prime_seq = torch.tensor(prime_seq, dtype = torch.long).to(device) |
|
|
|
|
|
|
|
for p in range(len(prime_seq) - 1): |
|
input = prime_seq[p] |
|
_, hidden = net(input, hidden) |
|
|
|
|
|
input = prime_seq[-1] |
|
|
|
|
|
for p in range(predict_len): |
|
|
|
|
|
output, hidden = net(input, hidden) |
|
|
|
output = output.data.view(-1).div(temperature).exp() |
|
predicted_id = torch.multinomial(output, 1) |
|
|
|
|
|
predicted.append(predicted_id.item()) |
|
input = predicted_id |
|
|
|
return predicted |
|
|
|
|
|
def create_midi(prediction_output): |
|
""" convert the output from the prediction to notes and create a midi file |
|
from the notes """ |
|
offset = 0 |
|
output_notes = [] |
|
|
|
|
|
for pattern in prediction_output: |
|
|
|
if ('.' in pattern) or pattern.isdigit(): |
|
notes_in_chord = pattern.split('.') |
|
notes = [] |
|
for current_note in notes_in_chord: |
|
new_note = note.Note(int(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(pattern) |
|
new_note.offset = offset |
|
new_note.storedInstrument = instrument.Piano() |
|
output_notes.append(new_note) |
|
|
|
|
|
offset += 0.5 |
|
|
|
midi_stream = stream.Stream(output_notes) |
|
|
|
return midi_stream |
|
|
|
|
|
def get_note_names(midi): |
|
s2 = instrument.partitionByInstrument(midi) |
|
|
|
piano_part = None |
|
|
|
instr = instrument.Piano |
|
for part in s2: |
|
if isinstance(part.getInstrument(), instr): |
|
piano_part = part |
|
|
|
notes_song = [] |
|
if not piano_part: |
|
|
|
piano_part = s2[0] |
|
|
|
for element in piano_part: |
|
if isinstance(element, note.Note): |
|
|
|
notes_song.append(str(element.pitch)) |
|
elif isinstance(element, chord.Chord): |
|
|
|
notes_song.append('.'.join(str(n) for n in element.normalOrder)) |
|
|
|
return notes_song |
|
|
|
|
|
def process_input(input_midi_file, input_randomness, input_duration): |
|
print(input_midi_file.name) |
|
midi = converter.parse(input_midi_file.name) |
|
note_names = get_note_names(midi) |
|
int_notes = [note_to_int[note_name] for note_name in note_names] |
|
|
|
generated_seq_multinomial = predict_multimomial(model, int_notes, predict_len = 100, temperature = 2.2) |
|
generated_seq_multinomial = [int_to_note[e] for e in generated_seq_multinomial] |
|
pred_midi_multinomial = create_midi(generated_seq_multinomial) |
|
|
|
pred_midi_multinomial.write('midi', fp='result.midi') |
|
|
|
|
|
FluidSynth().midi_to_audio('result.midi', 'result.wav') |
|
return 'result.wav', 'result.midi' |
|
|
|
|
|
file_path = './objects/model_cpu.pkl' |
|
with open(file_path, 'rb') as f: |
|
model = pickle.load(f) |
|
|
|
|
|
midi_file_desc = """ |
|
This model allows to generate music based on your input. |
|
Please upload a MIDI file below, choose music randomness and duration. Enjoy! |
|
""" |
|
|
|
article = """# Music Generation |
|
This project has been created by the students of Ukrainian Catholic University for our ML course. |
|
|
|
We are using a GRU model to output new notes based on the given input. You can find more information at our Git repo: https://github.com/DmytroLopushanskyy/music-generation |
|
We are using a language model to create music by treating a musical standard MIDI a simple text, with tokens for note values, note duration, and separations to denote movement forward in time. |
|
""" |
|
|
|
iface = gr.Interface( |
|
fn=process_input, |
|
inputs=[ |
|
gr.inputs.File(label=midi_file_desc), |
|
gr.inputs.Slider(0, 250, default=100, step=50), |
|
gr.inputs.Radio([10, 20, 30], type="value", default=20) |
|
], |
|
outputs=["audio", "file"], |
|
article=article, |
|
|
|
) |
|
|
|
iface.launch() |
|
|