Spaces:
Running
on
Zero
Running
on
Zero
#======================================================================= | |
# https://huggingface.co./spaces/asigalov61/Score-2-Performance-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 | |
from huggingface_hub import hf_hub_download | |
# ================================================================================================= | |
print('Loading model...') | |
SEQ_LEN = 1802 | |
PAD_IDX = 771 | |
DEVICE = 'cuda' # 'cpu' | |
# instantiate the model | |
model = TransformerWrapper( | |
num_tokens = PAD_IDX+1, | |
max_seq_len = SEQ_LEN, | |
attn_layers = Decoder(dim = 1024, | |
depth = 8, | |
heads = 8, | |
rotary_pos_emb=True, | |
attn_flash = True | |
) | |
) | |
model = AutoregressiveWrapper(model, ignore_index = PAD_IDX) | |
print('=' * 70) | |
print('Loading model checkpoint...') | |
model_checkpoint = hf_hub_download(repo_id='asigalov61/Score-2-Performance-Transformer', | |
filename='Score_2_Performance_Transformer_Final_Small_Trained_Model_4496_steps_1.5185_loss_0.5589_acc.pth' | |
) | |
model.load_state_dict(torch.load(model_checkpoint, map_location='cpu', weights_only=True)) | |
model = torch.compile(model, mode='max-autotune') | |
dtype = torch.bfloat16 | |
ctx = torch.amp.autocast(device_type=DEVICE, dtype=dtype) | |
print('=' * 70) | |
print('Done!') | |
print('=' * 70) | |
# ================================================================================================= | |
def load_midi(midi_file) | |
raw_score = TMIDIX.midi2single_track_ms_score(midi_file) | |
escore_notes = TMIDIX.advanced_score_processor(raw_score, return_enhanced_score_notes=True) | |
if escore_notes[0]: | |
escore_notes = TMIDIX.augment_enhanced_score_notes(escore_notes[0], timings_divider=16) | |
pe = escore_notes[0] | |
melody_chords = [] | |
seen = [] | |
for e in escore_notes: | |
if e[3] != 9: | |
#======================================================= | |
dtime = max(0, min(255, e[1]-pe[1])) | |
if dtime != 0: | |
seen = [] | |
# Durations | |
dur = max(1, min(255, e[2])) | |
# Pitches | |
ptc = max(1, min(127, e[4])) | |
vel = max(1, min(127, e[5])) | |
if ptc not in seen: | |
melody_chords.append([dtime, dur, ptc, vel]) | |
seen.append(ptc) | |
pe = e | |
print('=' * 70) | |
print('Number of notes in a composition:', len(melody_chords)) | |
print('=' * 70) | |
src_melody_chords_f = [] | |
melody_chords_f = [] | |
for i in range(0, len(melody_chords), 300): | |
chunk = melody_chords[i:i+300] | |
src = [] | |
src1 = [] | |
trg = [] | |
if len(chunk) == 300: | |
for mm in chunk: | |
src.extend([mm[0], mm[2]+256]) | |
src1.append([mm[0], mm[2]+256, mm[1]+384, mm[3]+640]) | |
trg.extend([mm[0], mm[2]+256, mm[1]+384, mm[3]+640]) | |
src_melody_chords_f.append(src1) | |
melody_chords_f.append([768] + src + [769] + trg + [770]) | |
print('Done!') | |
print('=' * 70) | |
print('Number of composition chunks:', len(melody_chords_f)) | |
print('=' * 70) | |
return melody_chords_f, src_melody_chords_f | |
def Convert_Score_to_Performance(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 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...') | |
model.to(DEVICE) | |
model.eval() | |
#================================================================== | |
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 = "Score-2-Performance-Transformer-Composition" | |
detailed_stats = TMIDIX.Tegridy_ms_SONG_to_MIDI_Converter(song_f, | |
output_signature = 'Score 2 Performance 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'>Score 2 Performance Transformer</h1>") | |
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Convert any MIDI score to a nice performance</h1>") | |
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(Convert_Score_to_Performance, [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() |