MelodyFlow / app.py
harp-dev's picture
Label the output with what it holds, which HARP shows as its name.
e455a60 verified
Raw History Blame Contribute Delete
6.13 kB
"""
MelodyFlow: edits music to follow a text description while keeping its timing and structure.
audiocraft is vendored from the authors' Space (facebook/MelodyFlow), whose fork moves it to the
torch release ZeroGPU requires, and runs here rather than being called over the network.
"""
import spaces # imported before torch, as ZeroGPU requires
import os
import tempfile
import gradio as gr
import torch
# audiocraft's checkpoints pickle their omegaconf configuration, which torch 2.6 and newer refuse to
# load by default (weights_only=True). The authors' Space restores the old default the same way, and
# only Meta's own checkpoints are loaded here. Patched before audiocraft is imported.
_torch_load = torch.load
def _load_audiocraft_checkpoint(*args, **kwargs):
kwargs.setdefault("weights_only", False)
return _torch_load(*args, **kwargs)
torch.load = _load_audiocraft_checkpoint
from pyharp import *
from audiocraft.data.audio import audio_read, audio_write
from audiocraft.data.audio_utils import convert_audio
from audiocraft.models import MelodyFlow
MODEL_NAME = "facebook/melodyflow-t24-30secs"
TAKES = 3
# The rate and channel count the model's compression model was trained on
MODEL_SR, MODEL_CHANNELS = 48000, 2
# Fetched when the Space builds (preload_from_hub in README.md)
model = MelodyFlow.get_pretrained(MODEL_NAME)
model_card = ModelCard(
name="MelodyFlow",
description=(
"Edits music to follow a text description while keeping the timing and structure of the "
"recording it was given. Flow matching carries the input part of the way along a path "
"and regenerates from there, so a low target flow step stays close to the original and a "
"high one commits to the prompt. With no input audio it generates from the text alone, up "
"to thirty seconds. The code is MIT, but the MelodyFlow weights are CC-BY-NC-4.0, so "
"results cannot be used commercially. Repository: "
"https://github.com/facebookresearch/audiocraft Paper: https://arxiv.org/abs/2407.03648"
),
author="Gael Le Lan, Bowen Shi, Zhaoheng Ni, et al.",
tags=[Subcategory.EDITING],
)
@spaces.GPU(duration=90)
@torch.inference_mode()
def process_fn(text, steps, target_flowstep, regularize, regularization_strength, duration, melody):
steps = int(steps)
if melody is not None:
# Editing inverts the input first, which the authors' demo pays for with half the steps
steps = steps // 2
model.set_generation_params(solver="midpoint", steps=steps, duration=duration)
model.set_editing_params(solver="midpoint", steps=steps, target_flowstep=target_flowstep,
regularize=regularize, lambda_kl=regularization_strength)
texts = [text] * TAKES
if melody is not None:
wav, sr = audio_read(melody)
if wav.dim() == 2:
wav = wav[None]
wav = wav[..., : int(sr * model.duration)]
wav = convert_audio(wav, sr, MODEL_SR, MODEL_CHANNELS)
tokens = model.encode_audio(wav.to(model.device))
outputs = model.edit(prompt_tokens=tokens.repeat(TAKES, 1, 1), descriptions=texts,
src_descriptions=[""] * TAKES, return_tokens=False)
else:
outputs = model.generate(texts, return_tokens=False)
out_dir = tempfile.mkdtemp()
paths = []
for i, output in enumerate(outputs.detach().cpu().float(), start=1):
path = os.path.join(out_dir, f"melodyflow_take_{i}.wav")
# The loudness normalization the authors' demo applies
audio_write(path, output, model.sample_rate, strategy="loudness",
loudness_headroom_db=16, loudness_compressor=True, add_suffix=False)
paths.append(path)
return tuple(paths)
# The processing worker imports this file, so the app must not be built there
if __name__ == "__main__":
with gr.Blocks() as demo:
input_components = [
gr.Textbox(label="Input Text",
info=(
"Describe what the music should become. The timing and structure of "
"the input are kept, so this changes the character rather than the "
"arrangement."
)),
gr.Slider(minimum=1, maximum=250, step=1, value=128.0, label="Inference steps",
info="More steps give a cleaner result and take longer."),
gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=0.0, label="Target Flow step",
info=(
"How far the edit travels from the input. Low values stay close to "
"the original, high values commit to the prompt."
)),
gr.Checkbox(value=False, label="Regularize",
info=(
"Keeps the result closer to the input's structure. Turn it off for "
"a freer edit."
)),
gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=0.2, label="Regularization Strength",
info=(
"How strongly that structure is held. Only used when Regularize is "
"on."
)),
gr.Slider(minimum=1, maximum=30, step=1, value=30.0, label="Duration",
info="Length of the result, in seconds."),
gr.Audio(type="filepath", label="File or Microphone").harp_required(False).set_info(
"The music to edit. Leave it empty to generate from the text alone."
),
]
output_components = [
gr.Audio(type="filepath", label="Variation 1").set_info((
"First of three takes from the same prompt and settings."
)),
gr.Audio(type="filepath", label="Variation 2").set_info((
"Second take."
)),
gr.Audio(type="filepath", label="Variation 3").set_info("Third take."),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
demo.queue().launch(show_error=True, pwa=True)