Spaces:
Running on Zero
Running on Zero
| #========================================================== | |
| # https://huggingface.co/spaces/asigalov61/Orpheus-Morpheus | |
| #========================================================== | |
| # ----------------------------- | |
| # CONFIGURATION & GLOBALS | |
| # ----------------------------- | |
| TIME_ZONE = 'US/Pacific' | |
| SEQ_LEN = 2561 | |
| PAD_IDX = 18819 | |
| MODELS_CHECKPOINTS = [ | |
| { | |
| 'checkpoint_tag': 'Morpheus Base 1ep Model', | |
| 'checkpoint_name': 'Orpheus_Morpheus_Music_Transformer_Trained_Model_21654_steps_0.9978_loss_0.7132_acc.pth', | |
| 'checkpoint_depth': 12, | |
| 'checkpoint_heads': 16 | |
| }, | |
| { | |
| 'checkpoint_tag': 'Morpheus Base 2ep Model', | |
| 'checkpoint_name': 'Orpheus_Morpheus_Music_Transformer_Trained_Model_38124_steps_0.8201_loss_0.7554_acc.pth', | |
| 'checkpoint_depth': 12, | |
| 'checkpoint_heads': 16 | |
| } | |
| ] | |
| MODEL_DEVICE = 'cuda' | |
| SOUNDFONT_BANK = 'SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2' | |
| AUDIO_SAMPLE_RATE = 16000 | |
| AUDIO_FORMAT = 'mp3' | |
| NUM_OUT_BATCHES = 10 | |
| OUTPUT_MIDIS_DIR = 'output_midis' | |
| # ----------------------------- | |
| # START-UP INFO FUNCTIONS | |
| # ----------------------------- | |
| SEP = '=' * 70 | |
| def print_sep(): | |
| print(SEP) | |
| print_sep() | |
| print("Orpheus Morpheus Gradio App") | |
| print_sep() | |
| print("Loading modules...") | |
| # ----------------------------- | |
| # ENVIRONMENT & MODULES IMPORTS | |
| # ----------------------------- | |
| import os | |
| os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1" | |
| RUNNING_IN_SPACE = ( | |
| os.environ.get("SYSTEM", "").lower() == "spaces" | |
| or "SPACE_ID" in os.environ | |
| or "HF_SPACE_ID" in os.environ | |
| ) | |
| import argparse | |
| from pathlib import Path | |
| from io import BytesIO | |
| import time as reqtime | |
| import datetime | |
| from pytz import timezone | |
| PDT = timezone(TIME_ZONE) | |
| import random | |
| if RUNNING_IN_SPACE: | |
| import spaces | |
| GPU = spaces.GPU | |
| else: | |
| def GPU(*args, **kwargs): | |
| def wrapper(fn): | |
| return fn | |
| return wrapper | |
| import gradio as gr | |
| import TMIDIX | |
| from midi_to_colab_audio import midi_to_colab_audio | |
| import matplotlib.pyplot as plt | |
| from huggingface_hub import hf_hub_download | |
| # ----------------------------- | |
| # PyTorch | |
| # ----------------------------- | |
| import torch | |
| os.environ['USE_FLASH_ATTENTION'] = '1' | |
| torch.set_float32_matmul_precision('high') | |
| torch.backends.cuda.matmul.allow_tf32 = True | |
| torch.backends.cudnn.allow_tf32 = True | |
| torch.backends.cuda.enable_mem_efficient_sdp(True) | |
| torch.backends.cuda.enable_math_sdp(True) | |
| torch.backends.cuda.enable_flash_sdp(True) | |
| torch.backends.cuda.enable_cudnn_sdp(True) | |
| MODEL_DTYPE = torch.bfloat16 | |
| # ----------------------------- | |
| # X-Transformer | |
| # ----------------------------- | |
| from x_transformer_2_3_1 import TransformerWrapper, AutoregressiveWrapper, Decoder, top_p | |
| print_sep() | |
| print("PyTorch version:", torch.__version__) | |
| print("Done loading modules!") | |
| print_sep() | |
| # ----------------------------- | |
| # SPACES AND LOCAL ARGS | |
| # ----------------------------- | |
| def parse_local_args(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--soundfont-name", type=str, default="SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2") | |
| return parser.parse_args() | |
| args = parse_local_args() if not RUNNING_IN_SPACE else None | |
| if args: | |
| SOUNDFONT_BANK = args.soundfont_name | |
| # ----------------------------- | |
| # MODELS INIT FUNCTIONS | |
| # ----------------------------- | |
| print_sep() | |
| #------------------------------------------------------------------------ | |
| def load_model(model_dic): | |
| print('Instantiating model...') | |
| model = TransformerWrapper( | |
| num_tokens=PAD_IDX + 1, | |
| max_seq_len=SEQ_LEN, | |
| attn_layers=Decoder( | |
| dim=2048, | |
| depth=model_dic['checkpoint_depth'], | |
| heads=model_dic['checkpoint_heads'], | |
| rotary_pos_emb=True, | |
| attn_flash=True | |
| ) | |
| ) | |
| model = AutoregressiveWrapper(model, | |
| ignore_index=PAD_IDX, | |
| pad_value=PAD_IDX | |
| ) | |
| print('Done!') | |
| print_sep() | |
| print("Model will use", MODEL_DTYPE.__repr__().split('.')[-1], "precision...") | |
| print("Model will use", MODEL_DEVICE, "device...") | |
| print_sep() | |
| print("Loading model checkpoint...") | |
| print('Checkpoint name:', model_dic['checkpoint_name']) | |
| print_sep() | |
| checkpoint = hf_hub_download( | |
| repo_id='asigalov61/Orpheus-Music-Transformer', | |
| filename=model_dic['checkpoint_name'] | |
| ) | |
| model.load_state_dict(torch.load(checkpoint, map_location='cpu')) | |
| model.eval() | |
| model.cpu() | |
| model = torch.compile(model, mode='max-autotune') | |
| print_sep() | |
| print("Done!") | |
| print_sep() | |
| return model_dic['checkpoint_tag'], model | |
| #------------------------------------------------------------------------ | |
| models_dict = {} | |
| for model_dic in MODELS_CHECKPOINTS: | |
| tag, model = load_model(model_dic) | |
| models_dict[tag] = model | |
| #------------------------------------------------------------------------ | |
| ctx = torch.amp.autocast(device_type=MODEL_DEVICE, | |
| dtype=MODEL_DTYPE | |
| ) | |
| print_sep() | |
| print("Done!") | |
| print_sep() | |
| # ----------------------------- | |
| # SOUNDFONT LOADING FUNCTION | |
| # ----------------------------- | |
| print('Loading SoundFont...') | |
| print_sep() | |
| SOUNDFONT_PATH = hf_hub_download(repo_id='projectlosangeles/soundfonts4u', | |
| repo_type='dataset', | |
| filename=SOUNDFONT_BANK | |
| ) | |
| print_sep() | |
| print('Done!') | |
| print('=' * 70) | |
| # ----------------------------- | |
| # MIDI PROCESSING FUNCTIONS | |
| # ----------------------------- | |
| def load_midi(input_midi): | |
| """Process the input MIDI file and create a token sequence.""" | |
| raw_score = TMIDIX.midi2single_track_ms_score(input_midi.name) | |
| escore_notes = TMIDIX.advanced_score_processor(raw_score, | |
| return_enhanced_score_notes=True, | |
| apply_sustain=True | |
| ) | |
| if escore_notes and escore_notes[0]: | |
| escore_notes = TMIDIX.augment_enhanced_score_notes(escore_notes[0], | |
| sort_drums_last=True | |
| ) | |
| escore_notes = TMIDIX.remove_duplicate_pitches_from_escore_notes(escore_notes) | |
| escore_notes = TMIDIX.fix_escore_notes_durations(escore_notes, | |
| min_notes_gap=0 | |
| ) | |
| dscore = TMIDIX.delta_score_notes(escore_notes) | |
| dcscore = TMIDIX.chordify_score([d[1:] for d in dscore]) | |
| melody_chords = [18816] | |
| #======================================================= | |
| # MAIN PROCESSING CYCLE | |
| #======================================================= | |
| for i, c in enumerate(dcscore): | |
| delta_time = c[0][0] | |
| melody_chords.append(delta_time) | |
| for e in c: | |
| #======================================================= | |
| # Durations | |
| dur = max(1, min(255, e[1])) | |
| # Patches | |
| pat = max(0, min(128, e[5])) | |
| # Pitches | |
| ptc = max(1, min(127, e[3])) | |
| # Velocities | |
| # Calculating octo-velocity | |
| vel = max(8, min(127, e[4])) | |
| velocity = round(vel / 15)-1 | |
| #======================================================= | |
| # FINAL NOTE SEQ | |
| #======================================================= | |
| # Writing final note | |
| pat_ptc = (128 * pat) + ptc | |
| dur_vel = (8 * dur) + velocity | |
| melody_chords.extend([pat_ptc+256, dur_vel+16768]) | |
| return melody_chords | |
| else: | |
| return [18816] | |
| def save_midi(tokens): | |
| """Convert token sequence back to a MIDI score and write it using TMIDIX. | |
| """ | |
| time = 0 | |
| dur = 1 | |
| vel = 90 | |
| pitch = 60 | |
| channel = 0 | |
| patch = 0 | |
| patches = [-1] * 16 | |
| channels = [0] * 16 | |
| channels[9] = 1 | |
| song_f = [] | |
| for ss in tokens: | |
| if 0 <= ss < 256: | |
| time += ss * 16 | |
| if 256 <= ss < 16768: | |
| patch = (ss-256) // 128 | |
| if patch < 128: | |
| if patch not in patches: | |
| if 0 in channels: | |
| cha = channels.index(0) | |
| channels[cha] = 1 | |
| else: | |
| cha = 15 | |
| patches[cha] = patch | |
| channel = patches.index(patch) | |
| else: | |
| channel = patches.index(patch) | |
| if patch == 128: | |
| channel = 9 | |
| pitch = (ss-256) % 128 | |
| if 16768 <= ss < 18816: | |
| dur = ((ss-16768) // 8) * 16 | |
| vel = (((ss-16768) % 8)+1) * 15 | |
| song_f.append(['note', time, dur, channel, pitch, vel, patch]) | |
| if song_f is not None and song_f: | |
| song_f = TMIDIX.remove_duplicate_pitches_from_escore_notes(song_f) | |
| song_f = TMIDIX.fix_escore_notes_durations(song_f, | |
| min_notes_gap=0 | |
| ) | |
| output_score, patches, overflow_patches = TMIDIX.patch_enhanced_score_notes(song_f) | |
| now = datetime.datetime.now(PDT) | |
| ms4 = now.strftime("%f")[:4] # first four digits of microseconds | |
| fname = ( | |
| "Orpheus-Morpheus-Composition-" | |
| + now.strftime(f"%Y-%m-%d-%H-%M-%S-{ms4}") | |
| ) | |
| os.makedirs(OUTPUT_MIDIS_DIR, exist_ok=True) | |
| output_fname = os.path.join(OUTPUT_MIDIS_DIR, fname) | |
| TMIDIX.Tegridy_ms_SONG_to_MIDI_Converter( | |
| output_score, | |
| output_signature='Orpheus Morpheus', | |
| output_file_name=output_fname, | |
| track_name='Project Los Angeles', | |
| list_of_MIDI_patches=patches, | |
| verbose=False | |
| ) | |
| return output_fname, output_score | |
| else: | |
| return None, None | |
| # ----------------------------- | |
| # MUSIC GENERATION FUNCTIONS | |
| # ----------------------------- | |
| def generate_music(prime, | |
| num_gen_tokens, | |
| num_gen_batches, | |
| model_temperature, | |
| model_top_p, | |
| model_selector | |
| ): | |
| """Generate music tokens given prime tokens and parameters.""" | |
| print(f'Will use {model_selector[0]}...') | |
| model = models_dict[model_selector[0]] | |
| model.to(MODEL_DEVICE) | |
| print("Generating...") | |
| inp = torch.LongTensor([prime] * num_gen_batches).to(MODEL_DEVICE) | |
| if model_top_p < 1: | |
| with ctx: | |
| out = model.generate( | |
| inp, | |
| num_gen_tokens, | |
| filter_logits_fn=top_p, | |
| filter_kwargs={'thres': model_top_p}, | |
| temperature=model_temperature, | |
| eos_token=18818, | |
| return_prime=False, | |
| verbose=False | |
| ) | |
| else: | |
| with ctx: | |
| out = model.generate( | |
| inp, | |
| num_gen_tokens, | |
| temperature=model_temperature, | |
| eos_token=18818, | |
| return_prime=False, | |
| verbose=False | |
| ) | |
| model.cpu() | |
| print("Done!") | |
| print_sep() | |
| return out.tolist() | |
| def generate_music_and_state(input_midi, | |
| num_prime_tokens, | |
| num_gen_tokens, | |
| model_temperature, | |
| model_top_p, | |
| final_composition, | |
| model_selector | |
| ): | |
| """ | |
| Generate tokens using the model, update the composition state, and prepare outputs. | |
| This function combines seed loading, token generation, and UI output packaging. | |
| """ | |
| if input_midi is not None: | |
| print_sep() | |
| print("Request start time:", datetime.datetime.now(PDT).strftime("%Y-%m-%d %H:%M:%S")) | |
| start_time = reqtime.time() | |
| print_sep() | |
| print('Requested model:', model_selector[0]) | |
| fn = os.path.basename(input_midi.name) | |
| fn1 = fn.split('.')[0] | |
| print('Input file name:', fn) | |
| print('Num prime tokens:', num_prime_tokens) | |
| print('Num gen tokens:', num_gen_tokens) | |
| print('Model temp:', model_temperature) | |
| print('Model top p:', model_top_p) | |
| final_composition = load_midi(input_midi) | |
| print_sep() | |
| print('Composition has', len(final_composition), 'tokens') | |
| print_sep() | |
| final_composition = final_composition[:1280] | |
| final_composition += [18817] | |
| final_composition += final_composition[1:num_prime_tokens+1] | |
| batched_gen_tokens = generate_music(final_composition, | |
| num_gen_tokens, | |
| NUM_OUT_BATCHES, | |
| model_temperature, | |
| model_top_p, | |
| model_selector | |
| ) | |
| output_batches = [] | |
| for i, tokens in enumerate(batched_gen_tokens): | |
| plot_kwargs = {'plot_title': f'Batch # {i}', 'return_plt': True} | |
| midi_fname, midi_score = save_midi(tokens) | |
| midi_plot = TMIDIX.plot_ms_SONG(midi_score, | |
| **plot_kwargs | |
| ) | |
| gradio_audio = midi_to_colab_audio(midi_fname + '.mid', | |
| soundfont_path=SOUNDFONT_PATH, | |
| sample_rate=AUDIO_SAMPLE_RATE, | |
| output_for_gradio=True) | |
| output_batches.append([(AUDIO_SAMPLE_RATE, gradio_audio), midi_plot, tokens, midi_fname + '.mid']) | |
| # Flatten outputs: states then audio and plots for each batch. | |
| outputs_flat = [] | |
| for batch in output_batches: | |
| outputs_flat.extend([batch[0], batch[1], batch[3]]) | |
| print("Request end time:", datetime.datetime.now(PDT).strftime("%Y-%m-%d %H:%M:%S")) | |
| print_sep() | |
| end_time = reqtime.time() | |
| execution_time = end_time - start_time | |
| print(f"Request execution time: {execution_time} seconds") | |
| print_sep() | |
| return [final_composition] + outputs_flat | |
| return [None] * 31 | |
| # ----------------------------- | |
| # MISC FUNCTIONS | |
| # ----------------------------- | |
| def reset(final_composition=[]): | |
| """Reset composition state.""" | |
| print_sep() | |
| print('Reset composition...') | |
| print_sep() | |
| return [] | |
| def update_state_from_dropdown(choice, state): | |
| """Store the dropdown value inside the global state list""" | |
| print_sep() | |
| print('Changed model from', state[0], 'to', choice) | |
| print_sep() | |
| state[0] = choice | |
| return state | |
| # ----------------------------- | |
| # GRADIO INTERFACE SETUP | |
| # ----------------------------- | |
| with gr.Blocks() as orpheus_morpheus_app: | |
| gr.Markdown("<h1 style='text-align: left; margin-bottom: 1rem'>Orpheus Morpheus</h1>") | |
| gr.Markdown("<h1 style='text-align: left; margin-bottom: 1rem'>Generate unique similar compositions to any MIDI</h1>") | |
| with gr.Row(elem_classes="duplicate-row"): | |
| gr.DuplicateButton( | |
| value="π€ Duplicate π€", | |
| variant="huggingface", | |
| size="md", | |
| link="https://huggingface.co/spaces/projectlosangeles/Orpheus-Morpheus?duplicate=true", | |
| link_target="_blank" | |
| ) | |
| gr.Button( | |
| value="β€οΈ Models β€οΈ", | |
| variant="huggingface", | |
| size="md", | |
| link="https://huggingface.co/asigalov61/Orpheus-Music-Transformer", | |
| link_target="_blank" | |
| ) | |
| gr.Button( | |
| value="π Spaces π", | |
| variant="huggingface", | |
| size="md", | |
| link="https://huggingface.co/collections/asigalov61/Orpheus-Music-Transformer", | |
| link_target="_blank" | |
| ) | |
| gr.Button( | |
| value="π¦ Dataset π¦", | |
| variant="huggingface", | |
| size="md", | |
| link="https://huggingface.co/datasets/projectlosangeles/Godzilla-MIDI-Dataset", | |
| link_target="_blank" | |
| ) | |
| # Global state variables for composition | |
| final_composition = gr.State([]) | |
| model_selector = gr.State([list(models_dict.keys())[0]]) | |
| gr.Markdown("## Upload your MIDI") | |
| gr.Markdown("### PLEASE NOTE: Source MIDI must have at least 300 notes (1280 tokens) for the demo to work properly") | |
| input_midi = gr.File(label="Input MIDI", file_types=[".midi", ".mid", ".kar"]) | |
| input_midi.upload(reset, [final_composition], | |
| [final_composition]) | |
| gr.Markdown("## Generation options") | |
| num_prime_tokens = gr.Slider(0, 32, value=0, step=1, label="Number of prime tokens", | |
| info="Increasing number of prime tokens will increase output similarity to the source composition" | |
| ) | |
| num_gen_tokens = gr.Slider(256, 1280, value=1280, step=1, label="Number of tokens to generate") | |
| requested_model = gr.Dropdown(label="Model to use", | |
| choices=list(models_dict.keys()), | |
| value=list(models_dict.keys())[0], | |
| ) | |
| model_temperature = gr.Slider(0.1, 1, value=0.9, step=0.01, label="Model temperature", | |
| info="Increase for more creative output, decrease for more conservative output" | |
| ) | |
| model_top_p = gr.Slider(0.1, 1.0, value=1.0, step=0.01, label="Model sampling top p value", | |
| info="1 == Disabled" | |
| ) | |
| generate_btn = gr.Button("Generate", variant="primary") | |
| gr.Markdown("## Batch Previews") | |
| outputs = [final_composition] | |
| # Two outputs (audio and plot) for each batch | |
| for i in range(NUM_OUT_BATCHES): | |
| with gr.Tab(f"Batch # {i}"): | |
| audio_output = gr.Audio(label=f"Batch # {i} MIDI Audio", format=AUDIO_FORMAT) | |
| plot_output = gr.Plot(label=f"Batch # {i} MIDI Plot") | |
| midi_file = gr.File(label=f"Batch # {i} MIDI File") | |
| outputs.extend([audio_output, plot_output, midi_file]) | |
| requested_model.change( | |
| fn=update_state_from_dropdown, | |
| inputs=[requested_model, model_selector], | |
| outputs=model_selector | |
| ) | |
| generate_btn.click( | |
| generate_music_and_state, | |
| [input_midi, | |
| num_prime_tokens, | |
| num_gen_tokens, | |
| model_temperature, | |
| model_top_p, | |
| final_composition, | |
| model_selector | |
| ], | |
| outputs | |
| ) | |
| # ----------------------------- | |
| # APP LAUNCHER | |
| # ----------------------------- | |
| if __name__ == "__main__": | |
| orpheus_morpheus_app.launch( | |
| mcp_server=RUNNING_IN_SPACE, # MCP only on HF | |
| share=not RUNNING_IN_SPACE, # Share only locally | |
| server_name="0.0.0.0", | |
| server_port=7860 | |
| ) |