Spaces:
Running on Zero
Running on Zero
File size: 11,493 Bytes
01f0475 a8759fd cbafa30 a8759fd a189727 3a0da16 a189727 01f0475 f021bc1 a189727 01f0475 a189727 01f0475 9eb90fb 01f0475 d8afa22 01f0475 a189727 01f0475 a189727 01f0475 9eb90fb aa97841 9eb90fb 01f0475 9eb90fb 01f0475 a189727 01f0475 a189727 01f0475 a189727 01f0475 a189727 9eb90fb a189727 01f0475 a189727 01f0475 a189727 01f0475 a189727 e78811f 3a0da16 01f0475 3a0da16 a017c1f 3a0da16 e78811f 3a0da16 e78811f 3a0da16 a189727 91a5618 a189727 01f0475 a189727 91a5618 9cd9496 a189727 0ea1873 7fd48be e78811f 7fd48be 0ea1873 01f0475 0ea1873 3a0da16 0ea1873 7fd48be 0ea1873 653006d 01f0475 653006d 3a0da16 653006d 0ea1873 01f0475 b71c4b3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | import spaces # imported first, as ZeroGPU requires
try: # torch>=2.6 flipped torch.load(weights_only) to True; legacy ckpts need False
import torch as _torch
if getattr(_torch.load, "__harp_compat__", False) is False:
_torch_load_orig = _torch.load
def _torch_load_compat(*args, **kwargs):
# Lightning passes weights_only=None, which torch reads as True
if kwargs.get("weights_only") is None:
kwargs["weights_only"] = False
return _torch_load_orig(*args, **kwargs)
_torch_load_compat.__harp_compat__ = True
_torch.load = _torch_load_compat
except Exception: # torch not installed / unexpected API -- nothing to patch
pass
import time
import numpy as np
import soundfile as sf
import audiotools as at
import torch
from pyharp.core import build_endpoint, ModelCard
from pyharp.media.audio import save_audio
from pyharp.tags import Subcategory, SampleRate, Channels
import gradio as gr
from vampnet.interface import Interface
from vampnet import mask as pmask
model_card = ModelCard(
name="VampNet",
description=(
"Generates new audio by remixing and extending your input, filling in "
"the blanks to create a coherent continuation in the same style. "
"Besides the default, three fine-tuned models are available. cat10 was "
"fine-tuned on expressive cat sounds, lazaro-ros-sep on Afro-Cuban "
"percussion extracted from the Lázaro Ros discography, and supermetroid "
"on Super Metroid (SNES) gameplay audio, for retro, game-style "
"soundscapes. Generation runs in mono at the model's own rate. The "
"result is resampled back to the input's rate and copied across its "
"channels, so the output has the format it came in with but no stereo "
"image. The code is MIT, but the "
"pretrained weights are CC-BY-NC-SA-4.0, so results cannot be used "
"commercially and must be shared alike. Repository: "
"https://github.com/hugofloresgarcia/vampnet Paper: "
"https://arxiv.org/abs/2307.04686"
),
author="Hugo Flores Garcia, Prem Seetharaman, Rithesh Kumar, Bryan Pardo",
tags=[Subcategory.INFILLING, SampleRate(44100), Channels(1), 'sound-effects', 'domain:music']
)
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
init_model_choice = open("DEFAULT_MODEL").read().strip()
MODEL_CHOICES = Interface.available_models()
# pyharp runs process_fn in a worker, which imports this file as __mp_main__ and starts with the
# server, so the default model is built and placed there alone. Each weight set is about 2.4 GB,
# too much to hold all four together, so the others are only fetched here, and built inside the
# request that picks one. Their paths are kept, since load_finetuned would list the model
# repository over the network from inside that request's GPU reservation.
if __name__ != "__main__":
from vampnet import download_default, download_finetuned
interface = Interface.default()
interface.load_finetuned(init_model_choice)
interface.to(DEVICE)
CHECKPOINTS = {
name: download_default() if name == "default" else download_finetuned(name)
for name in MODEL_CHOICES
}
print(f"Loaded initial model: {init_model_choice}")
from torch_pitch_shift import pitch_shift, get_fast_shifts
def shift_pitch(signal, interval: int):
signal.samples = pitch_shift(
signal.samples,
shift=interval,
sample_rate=signal.sample_rate
)
return signal
def _vamp_internal(
seed, input_audio, model_choice,
pitch_shift_amt, periodic_p,
n_mask_codebooks, onset_mask_width,
dropout, sampletemp, typical_filtering,
typical_mass, typical_min_tokens, top_p,
sample_cutoff, stretch_factor, sampling_steps, beat_mask_ms, num_feedback_steps
):
t0 = time.time()
_seed = seed if seed > 0 else None
if _seed is None:
_seed = int(torch.randint(0, 2**32, (1,)).item())
at.util.seed(_seed)
if input_audio is None:
raise gr.Error("no input audio received!")
sr, input_audio = input_audio
input_audio = input_audio / np.iinfo(input_audio.dtype).max
sig = at.AudioSignal(input_audio, sr).to_mono()
loudness = sig.loudness()
sig = interface._preprocess(sig)
# Builds the chosen model's checkpoints, unless they are the ones already built
interface.reload(*CHECKPOINTS[model_choice])
if pitch_shift_amt != 0:
sig = shift_pitch(sig, pitch_shift_amt)
codes = interface.encode(sig)
mask = interface.build_mask(
codes,
sig=sig,
periodic_prompt=periodic_p,
onset_mask_width=onset_mask_width,
_dropout=dropout,
upper_codebook_mask=n_mask_codebooks,
)
if beat_mask_ms > 0:
mask = pmask.mask_and(
mask, interface.make_beat_mask(
sig, after_beat_s=beat_mask_ms/1000.,
)
)
mask = pmask.codebook_mask(mask, n_mask_codebooks)
interface.set_chunk_size(10.0)
# lord help me
if top_p is not None:
if top_p > 0:
pass
else:
top_p = None
codes, mask_z = interface.vamp(
codes, mask,
batch_size=2,
feedback_steps=num_feedback_steps,
_sampling_steps=sampling_steps,
time_stretch_factor=stretch_factor,
return_mask=True,
temperature=sampletemp,
typical_filtering=typical_filtering,
typical_mass=typical_mass,
typical_min_tokens=typical_min_tokens,
top_p=top_p,
seed=_seed,
sample_cutoff=sample_cutoff,
)
print(f"vamp took {time.time() - t0} seconds")
sig = interface.decode(codes)
sig = sig.normalize(loudness)
return sig
# Generation covers the whole input, stretched, in 10 s chunks, so the cost grows with the
# output's length
# PyHARP stops a job that runs past its timeout_s, 15 minutes by default, so a request whose
# reservation would exceed that is refused rather than started. A shorter one is left to
# ZeroGPU, which admits it when the caller's own allowance covers it.
MAX_GPU_SECONDS = 900
def gpu_seconds(input_audio, sampletemp, periodic_p, dropout, n_mask_codebooks, model_choice,
stretch_factor):
# Measured at about 15 s for 30 s of output and 19 s for 64 s, padded, with room for loading
# a fine-tuned model
return 15 + 0.4 * sf.info(input_audio).duration * max(1, int(stretch_factor))
def process_fn(input_audio, sampletemp, periodic_p, dropout, n_mask_codebooks, model_choice,
stretch_factor):
# Checked before the GPU is reserved, so a request the model cannot take spends none of the
# caller's ZeroGPU allowance
if model_choice not in MODEL_CHOICES:
raise gr.Error(f"Unknown model: {model_choice}")
if not input_audio:
raise gr.Error("Provide the audio to rework.")
try:
duration = sf.info(input_audio).duration
except Exception as exc:
raise gr.Error(f"Could not read the audio file: {exc}") from exc
if duration == 0:
raise gr.Error("The audio file is empty.")
output_seconds = duration * max(1, int(stretch_factor))
if gpu_seconds(input_audio, sampletemp, periodic_p, dropout, n_mask_codebooks, model_choice,
stretch_factor) > MAX_GPU_SECONDS:
raise gr.Error(
f"That would generate {output_seconds:.0f} s of audio, more than one request can in "
f"the {MAX_GPU_SECONDS // 60} minutes it may run. Use a shorter input or a smaller "
"stretch factor."
)
return harp_vamp(input_audio, sampletemp, periodic_p, dropout, n_mask_codebooks,
model_choice, stretch_factor)
@spaces.GPU(duration=gpu_seconds)
def harp_vamp(input_audio, sampletemp, periodic_p, dropout, n_mask_codebooks, model_choice, stretch_factor):
sig = at.AudioSignal(input_audio)
# Captured before the downmix, because generation runs in mono at VampNet's
# own rate and both have to be put back afterwards
original_sr = sig.sample_rate
original_channels = sig.num_channels
sig = sig.to_mono()
input_audio = sig.cpu().detach().numpy()[0][0]
input_audio = input_audio * np.iinfo(np.int16).max
input_audio = input_audio.astype(np.int16)
input_audio = input_audio.reshape(1, -1)
input_audio = (sig.sample_rate, input_audio)
sig = _vamp_internal(
seed=0,
input_audio=input_audio,
model_choice=model_choice,
pitch_shift_amt=0,
periodic_p=int(periodic_p),
n_mask_codebooks=int(n_mask_codebooks),
onset_mask_width=0,
dropout=dropout,
sampletemp=sampletemp,
typical_filtering=False,
typical_mass=0.15,
typical_min_tokens=1,
top_p=None,
sample_cutoff=1.0,
stretch_factor=stretch_factor,
sampling_steps=36,
beat_mask_ms=int(0),
num_feedback_steps=1,
)
sig = sig.detach().cpu()
if sig.sample_rate != original_sr:
sig = sig.resample(original_sr)
# Copies of one generated channel, not a stereo image, which the card says
if original_channels > 1 and sig.num_channels == 1:
sig.audio_data = sig.audio_data.repeat(1, original_channels, 1)
return save_audio(sig)
if __name__ == "__main__":
with gr.Blocks() as demo:
harp_input_components = [
gr.Audio(type="filepath", label="Input Audio").harp_required(True).set_info((
"The audio to rework. The model masks out parts of this recording "
"and fills them back in, so the result keeps its character."
)),
gr.Slider(label="Randomness", minimum=0.1, maximum=10.0, value=1.0, step=0.001,
info="Controls sampling diversity- higher values yield more novel variations."),
gr.Slider(label="Rhythmic Reference", minimum=0, maximum=13, step=1, value=7,
info="Determines how frequently rhythmic cues are repeated in generation."),
gr.Slider(label="Variation Amount", minimum=0.0, maximum=1.0, step=0.01, value=0.0,
info="How much of the input the model changes. 0 = close copy, higher = greater remix."),
gr.Slider(label="Timbral Reference", value=3, minimum=1, maximum=14, step=1,
info="Controls the richness of timbre retained from the source audio. Lower = simpler, higher = richer timbre."),
gr.Dropdown(label="Model Choice", choices=list(MODEL_CHOICES), value=init_model_choice,
info="Select which fine-tuned VampNet model to use."),
gr.Slider(label="Time Stretch Factor", minimum=1, maximum=8, step=1, value=1,
info="Scales the output's length relative to the input. 1 = the same length, 2 = twice as long."),
]
harp_output_components = [
gr.Audio(type="filepath", label="Generated Audio").set_info((
"The reworked audio, at the same sample rate as the input."
)),
]
build_endpoint(
model_card=model_card,
input_components=harp_input_components,
output_components=harp_output_components,
process_fn=process_fn,
)
demo.queue()
demo.launch(pwa=True, show_error=True, ssr_mode=False)
|