Spaces:
Running
on
Zero
Running
on
Zero
#======================================================================= | |
# https://huggingface.co/spaces/asigalov61/Guided-Rock-Music-Transformer | |
#======================================================================= | |
import os | |
import time as reqtime | |
import datetime | |
from pytz import timezone | |
import copy | |
from itertools import groupby | |
import tqdm | |
import spaces | |
import gradio as gr | |
import torch | |
from x_transformer_1_23_2 import * | |
import random | |
import TMIDIX | |
from midi_to_colab_audio import midi_to_colab_audio | |
# ================================================================================================= | |
def Generate_Rock_Song(input_midi, | |
input_gen_type, | |
input_number_prime_chords, | |
input_number_gen_chords, | |
input_use_original_durations, | |
input_match_original_pitches_counts, | |
input_number_prime_tokens, | |
input_number_gen_tokens, | |
input_num_memory_tokens, | |
input_model_temperature, | |
input_model_top_p | |
): | |
#=============================================================================== | |
print('=' * 70) | |
print('Req start time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT))) | |
start_time = reqtime.time() | |
print('=' * 70) | |
fn = os.path.basename(input_midi) | |
fn1 = fn.split('.')[0] | |
print('=' * 70) | |
print('Requested settings:') | |
print('=' * 70) | |
print('Input MIDI file name:', fn) | |
print('Generation type:', input_gen_type) | |
print('Number of prime chords:', input_number_prime_chords) | |
print('Number of chords to generate:', input_number_gen_chords) | |
print('Use original durations:', input_use_original_durations) | |
print('Match original pitches counts:', input_match_original_pitches_counts) | |
print('Number of prime tokens:', input_number_prime_tokens) | |
print('Number of tokens to generate:', input_number_gen_tokens) | |
print('Number of memory tokens:', input_num_memory_tokens) | |
print('Model temperature:', input_model_temperature) | |
print('Model sampling top p value:', input_model_top_p) | |
print('=' * 70) | |
#=============================================================================== | |
print('Loading model...') | |
SEQ_LEN = 4096 | |
PAD_IDX = 673 | |
DEVICE = 'cuda' # 'cpu' | |
# instantiate the model | |
model = TransformerWrapper( | |
num_tokens = PAD_IDX+1, | |
max_seq_len = SEQ_LEN, | |
attn_layers = Decoder(dim = 1024, depth = 16, heads = 16, rotary_pos_emb=True, attn_flash = True) | |
) | |
model = AutoregressiveWrapper(model, ignore_index = PAD_IDX) | |
model.to(DEVICE) | |
print('=' * 70) | |
print('Loading model checkpoint...') | |
model.load_state_dict( | |
torch.load('Guided_Rpck_Music_Transformer_Trained_Model_12081_steps_0.4113_loss_0.8747_acc.pth', | |
map_location=DEVICE)) | |
print('=' * 70) | |
model.eval() | |
if DEVICE == 'cpu': | |
dtype = torch.bfloat16 | |
else: | |
dtype = torch.bfloat16 | |
ctx = torch.amp.autocast(device_type=DEVICE, dtype=dtype) | |
print('Done!') | |
print('=' * 70) | |
#=============================================================================== | |
print('Loading MIDI...') | |
#=============================================================================== | |
# Raw single-track ms score | |
raw_score = TMIDIX.midi2single_track_ms_score(input_midi.name) | |
#=============================================================================== | |
# Enhanced score notes | |
escore_notes = TMIDIX.advanced_score_processor(raw_score, return_enhanced_score_notes=True)[0] | |
escore_notes = [e for e in escore_notes if e[6] < 72 or e[6] == 128] | |
#======================================================= | |
# PRE-PROCESSING | |
#=============================================================================== | |
# Augmented enhanced score notes | |
escore_notes = TMIDIX.augment_enhanced_score_notes(escore_notes, timings_divider=32, legacy_timings=True) | |
#=============================================================================== | |
dscore = TMIDIX.enhanced_delta_score_notes(escore_notes) | |
cscore = TMIDIX.chordify_score(dscore) | |
#=============================================================================== | |
score_toks = [] | |
control_toks = [] | |
prime_toks = [] | |
for c in cscore: | |
ctime = c[0][0] | |
#================================================================= | |
chord = sorted(c, key=lambda x: -x[5]) | |
gnotes = [] | |
gdrums = [] | |
for k, v in groupby(chord, key=lambda x: x[5]): | |
if k == 128: | |
gdrums.extend(sorted(v, key=lambda x: x[3], reverse=True)) | |
else: | |
gnotes.append(sorted(v, key=lambda x: x[3], reverse=True)) | |
#================================================================= | |
chord_toks = [] | |
ctoks = [] | |
ptoks = [] | |
chord_toks.append(ctime) | |
ptoks.append(ctime) | |
if gdrums: | |
chord_toks.extend([e[3]+128 for e in gdrums] + [128]) | |
ptoks.extend([e[3]+128 for e in gdrums] + [128]) | |
else: | |
chord_toks.append(128) | |
ptoks.append(128) | |
if gnotes: | |
for g in gnotes: | |
durs = [e[1] // 4 for e in g] | |
clipped_dur = max(1, min(31, min(durs))) | |
chan = max(0, min(8, g[0][5] // 8)) | |
chan_dur_tok = ((chan * 32) + clipped_dur) + 256 | |
ctoks.append([chan_dur_tok, len(g)]) | |
ptoks.append(chan_dur_tok) | |
ptoks.extend([e[3]+544 for e in g]) | |
score_toks.append(chord_toks) | |
control_toks.append(ctoks) | |
prime_toks.append(ptoks) | |
print('Done!') | |
print('=' * 70) | |
#================================================================== | |
print('Sample output events', prime_toks[:16]) | |
print('=' * 70) | |
print('Generating...') | |
#================================================================== | |
def generate_continuation(num_prime_tokens, num_gen_tokens): | |
x = torch.tensor(TMIDIX.flatten(prime_toks)[:num_prime_tokens], dtype=torch.long, device=DEVICE) | |
with ctx: | |
out = model.generate(x, | |
num_gen_tokens, | |
filter_logits_fn=top_p, | |
filter_kwargs={'thres': input_model_top_p}, | |
temperature=input_model_temperature, | |
return_prime=True, | |
verbose=True) | |
y = out.tolist()[0] | |
return y | |
#================================================================== | |
def generate_tokens(seq, max_num_ptcs=5, max_tries=10): | |
input = copy.deepcopy(seq) | |
pcount = 0 | |
y = 545 | |
tries = 0 | |
gen_tokens = [] | |
seen = False | |
if 256 < input[-1] < 544: | |
seen = True | |
while pcount < max_num_ptcs and y > 255 and tries < max_tries: | |
x = torch.tensor(input[-input_num_memory_tokens:], dtype=torch.long, device=DEVICE) | |
with ctx: | |
out = model.generate(x, | |
1, | |
filter_logits_fn=top_p, | |
filter_kwargs={'thres': input_model_top_p}, | |
return_prime=False, | |
verbose=False) | |
y = out[0].tolist()[0] | |
if 256 < y < 544: | |
if not seen: | |
input.append(y) | |
gen_tokens.append(y) | |
seen = True | |
else: | |
tries += 1 | |
if y > 544 and seen: | |
if pcount < max_num_ptcs and y not in gen_tokens: | |
input.append(y) | |
gen_tokens.append(y) | |
pcount += 1 | |
else: | |
tries += 1 | |
return gen_tokens | |
#================================================================== | |
song = [] | |
if input_gen_type == 'Freestyle': | |
output = generate_continuation(input_number_prime_tokens, input_number_gen_tokens) | |
song.extend(output) | |
else: | |
for i in range(input_number_prime_chords): | |
song.extend(prime_toks[i]) | |
for i in tqdm.tqdm(range(input_number_prime_chords, input_number_prime_chords+input_number_gen_chords)): | |
song.extend(score_toks[i]) | |
if control_toks[i]: | |
for ct in control_toks[i]: | |
if input_use_original_durations: | |
song.append(ct[0]) | |
if input_match_original_pitches_counts: | |
out_seq = generate_tokens(song, ct[1]) | |
else: | |
out_seq = generate_tokens(song) | |
song.extend(out_seq) | |
print('=' * 70) | |
print('Done!') | |
print('=' * 70) | |
#=============================================================================== | |
print('Rendering results...') | |
print('=' * 70) | |
print('Sample INTs', song[:15]) | |
print('=' * 70) | |
if len(song) != 0: | |
song_f = [] | |
time = 0 | |
dur = 32 | |
channel = 0 | |
pitch = 60 | |
vel = 90 | |
patches = [0, 10, 19, 24, 35, 40, 52, 56, 65, 9, 0, 0, 0, 0, 0, 0] | |
velocities = [80, 100, 90, 100, 110, 100, 100, 100, 100, 110] | |
for ss in song: | |
if 0 <= ss < 128: | |
time += ss * 32 | |
if 128 < ss < 256: | |
song_f.append(['note', time, 32, 9, ss-128, velocities[9], 128]) | |
if 256 < ss < 544: | |
dur = ((ss-256) % 32) * 4 * 32 | |
channel = (ss-256) // 32 | |
if 544 < ss < 672: | |
patch = channel * 8 | |
pitch = ss-544 | |
song_f.append(['note', time, dur, channel, pitch, velocities[channel], patch]) | |
fn1 = "Guided-Rock-Music-Transformer-Composition" | |
detailed_stats = TMIDIX.Tegridy_ms_SONG_to_MIDI_Converter(song_f, | |
output_signature = 'Guided Rock Music Transformer', | |
output_file_name = fn1, | |
track_name='Project Los Angeles', | |
list_of_MIDI_patches=patches | |
) | |
new_fn = fn1+'.mid' | |
audio = midi_to_colab_audio(new_fn, | |
soundfont_path=soundfont, | |
sample_rate=16000, | |
volume_scale=10, | |
output_for_gradio=True | |
) | |
print('Done!') | |
print('=' * 70) | |
#======================================================== | |
output_midi_title = str(fn1) | |
output_midi_summary = str(song_f[:3]) | |
output_midi = str(new_fn) | |
output_audio = (16000, audio) | |
output_plot = TMIDIX.plot_ms_SONG(song_f, plot_title=output_midi, return_plt=True) | |
print('Output MIDI file name:', output_midi) | |
print('Output MIDI title:', output_midi_title) | |
print('Output MIDI summary:', output_midi_summary) | |
print('=' * 70) | |
#======================================================== | |
print('-' * 70) | |
print('Req end time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT))) | |
print('-' * 70) | |
print('Req execution time:', (reqtime.time() - start_time), 'sec') | |
return output_midi_title, output_midi_summary, output_midi, output_audio, output_plot | |
# ================================================================================================= | |
if __name__ == "__main__": | |
PDT = timezone('US/Pacific') | |
print('=' * 70) | |
print('App start time: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now(PDT))) | |
print('=' * 70) | |
soundfont = "SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2" | |
app = gr.Blocks() | |
with app: | |
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Guided Rock Music Transformer</h1>") | |
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Generate unique rock music compositions with source augmented RoPE music transformer</h1>") | |
gr.Markdown( | |
"![Visitors](https://api.visitorbadge.io/api/visitors?path=asigalov61.Guided-Rock-Music-Transformer&style=flat)\n\n") | |
gr.Markdown("## Upload your MIDI or select a sample example MIDI below") | |
gr.Markdown("### For best results use MIDIs with 1:2 notes to drums ratio") | |
input_midi = gr.File(label="Input MIDI", file_types=[".midi", ".mid", ".kar"]) | |
gr.Markdown("## Select generation type") | |
input_gen_type = gr.Radio(["Controlled", "Freestyle"], value='Controlled', label="Generation type") | |
gr.Markdown("## Controlled generation options") | |
input_number_prime_chords = gr.Slider(0, 512, value=0, step=8, label="Number of prime chords") | |
input_number_gen_chords = gr.Slider(16, 512, value=256, step=8, label="Number of chords to generate") | |
input_use_original_durations = gr.Checkbox(label="Use original durations", value=True) | |
input_match_original_pitches_counts = gr.Checkbox(label="Match original pitches counts", value=True) | |
gr.Markdown("## Freestyle continuation options") | |
input_number_prime_tokens = gr.Slider(0, 1024, value=512, step=16, label="Number of prime tokens") | |
input_number_gen_tokens = gr.Slider(0, 3072, value=1024, step=16, label="Number of tokens to generate") | |
gr.Markdown("## Model options") | |
input_num_memory_tokens = gr.Slider(1024, 4096, value=2048, step=16, label="Number of memory tokens") | |
input_model_temperature = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Model temperature") | |
input_model_top_p = gr.Slider(0.1, 1, value=0.96, step=0.01, label="Model sampling top p value") | |
run_btn = gr.Button("generate", variant="primary") | |
gr.Markdown("## Generation results") | |
output_midi_title = gr.Textbox(label="Output MIDI title") | |
output_midi_summary = gr.Textbox(label="Output MIDI summary") | |
output_audio = gr.Audio(label="Output MIDI audio", format="wav", elem_id="midi_audio") | |
output_plot = gr.Plot(label="Output MIDI score plot") | |
output_midi = gr.File(label="Output MIDI file", file_types=[".mid"]) | |
run_event = run_btn.click(Generate_Rock_Song, [input_midi, | |
input_gen_type, | |
input_number_prime_chords, | |
input_number_gen_chords, | |
input_use_original_durations, | |
input_match_original_pitches_counts, | |
input_number_prime_tokens, | |
input_number_gen_tokens, | |
input_num_memory_tokens, | |
input_model_temperature, | |
input_model_top_p, | |
], | |
[output_midi_title, output_midi_summary, output_midi, output_audio, output_plot]) | |
gr.Examples( | |
[["Rock Violin.mid", "Controlled", 0, 512, True, True, 512, 1024, 2048, 0.9, 0.96], | |
["Come To My Window.mid", "Controlled", 128, 256, False, False, 512, 1024, 2048, 0.9, 0.96], | |
["Sharing The Night Together.kar", "Controlled", 128, 256, True, True, 512, 1024, 2048, 0.9, 0.96], | |
["Hotel California.mid", "Controlled", 128, 256, True, True, 512, 1024, 2048, 0.9, 0.96], | |
["Nothing Else Matters.kar", "Controlled", 128, 256, True, True, 512, 1024, 2048, 0.9, 0.96], | |
], | |
[input_midi, | |
input_gen_type, | |
input_number_prime_chords, | |
input_number_gen_chords, | |
input_use_original_durations, | |
input_match_original_pitches_counts, | |
input_number_prime_tokens, | |
input_number_gen_tokens, | |
input_num_memory_tokens, | |
input_model_temperature, | |
input_model_top_p, | |
], | |
[output_midi_title, output_midi_summary, output_midi, output_audio, output_plot], | |
Generate_Rock_Song, | |
cache_examples=True, | |
cache_mode='eager' | |
) | |
app.queue().launch() |