projectlosangeles's picture
Update app.py
1cb7024 verified
Raw
History Blame Contribute Delete
19.9 kB
#==========================================================
# 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
# -----------------------------
@GPU
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
)