Spaces:
Running on Zero
Running on Zero
File size: 5,161 Bytes
53094d2 1fae0ab b4a11b5 b433e4a 53094d2 c50a31c 53094d2 1fe780b c630b06 c50a31c c630b06 53094d2 37a71d1 53094d2 c50a31c 53094d2 b433e4a 53094d2 c50a31c 53094d2 b4a11b5 53094d2 c50a31c 53094d2 3d9003b b4a11b5 610db00 b4a11b5 53094d2 c50a31c 53094d2 1fae0ab 53094d2 b4a11b5 c0f047b 3d9003b b4a11b5 3d9003b b4a11b5 53094d2 c18b5cc 30d24f8 53094d2 c18b5cc 411cb0f c50a31c c18b5cc c50a31c 53094d2 c18b5cc 53094d2 2c78d2d | 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 | import spaces
from transkun.ModelTransformer import TransKun, writeMidi
import numpy as np
import torch
import librosa
import tempfile
import os
import soundfile as sf
# setuptools dropped this module in version 81, and it is only needed here
# to locate data files inside the installed transkun package, which
# importlib.resources does from the standard library.
from importlib.resources import files
import moduleconf
from pyharp.core import ModelCard, build_endpoint
from pyharp.media.audio import load_audio
from pyharp.tags import Channels, SampleRate, Subcategory
import gradio as gr
model_card = ModelCard(
name="Transkun",
description=(
"Transcribes solo piano performance into MIDI notation, with onsets, offsets, and "
"velocities. The input is mixed to mono and resampled to 44.1 kHz internally. "
"Repository: https://github.com/Yujia-Yan/Transkun Paper: "
"https://arxiv.org/abs/2404.09466"
),
author="Yujia Yan, Zhiyao Duan",
tags=[Subcategory.MUSIC_ANALYSIS, SampleRate(44100), Channels(1), 'domain:music']
)
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
def load_model(device: str):
defaultWeight = str(files("transkun") / "pretrained" / "2.0.pt")
defaultConf = str(files("transkun") / "pretrained" / "2.0.conf")
confManager = moduleconf.parseFromFile(defaultConf)
conf = confManager["Model"].config
checkpoint = torch.load(defaultWeight, map_location="cpu")
model = TransKun(conf=conf)
# Checkpoints keep their weights under either key
if "best_state_dict" in checkpoint:
model.load_state_dict(checkpoint["best_state_dict"], strict=False)
else:
model.load_state_dict(checkpoint["state_dict"], strict=False)
model.eval()
return model.to(device)
# pyharp runs process_fn in a worker, which imports this file as __mp_main__ and starts with the
# server, so the model is built and placed there alone, rather than inside every GPU call
if __name__ != "__main__":
model = load_model(DEVICE)
# The recording is transcribed in overlapping segments, so the cost grows with its 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_file):
# Measured at about 2 to 8 s for 30 s of piano and 10 to 15 s for 300 s, upload included,
# padded. The model is built when the worker starts, not inside the call.
return 8 + 0.05 * sf.info(input_file).duration
@spaces.GPU(duration=gpu_seconds)
def transcribe(input_file):
signal = load_audio(input_file)
waveform = np.asarray(signal.audio_data)
sr = int(signal.sample_rate)
waveform = np.squeeze(waveform)
# If 2D, assume (channels, samples). Make mono.
if waveform.ndim == 2:
if waveform.shape[0] > 1:
waveform = waveform.mean(axis=0)
else:
waveform = waveform.reshape(-1)
if sr != model.fs:
waveform = librosa.resample(waveform.astype(np.float32), orig_sr=sr, target_sr=model.fs)
sr = model.fs
x = torch.from_numpy(waveform.reshape(-1, 1)).to(DEVICE)
notesEst = model.transcribe(x, discardSecondHalf=False)
outputMidi = writeMidi(notesEst)
out_fd, out_path = tempfile.mkstemp(suffix=".mid")
os.close(out_fd)
outputMidi.write(out_path)
return out_path
def process_fn(input_audio_path: str) -> str:
# Checked before the GPU is reserved, so a request the model cannot take spends none of the
# caller's ZeroGPU allowance
if not input_audio_path:
raise gr.Error("Provide the piano recording to transcribe.")
try:
duration = sf.info(input_audio_path).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.")
if gpu_seconds(input_audio_path) > MAX_GPU_SECONDS:
raise gr.Error(
f"The recording is {duration:.0f} s long, more than one request can transcribe in "
f"the {MAX_GPU_SECONDS // 60} minutes it may run. Trim it."
)
midi_path = transcribe(input_audio_path)
return midi_path
if __name__ == "__main__":
with gr.Blocks() as demo:
input_audio = gr.Audio(label="Upload Solo Piano Audio", type="filepath").harp_required(True).set_info((
"A solo piano recording. Trained on piano alone, so other "
"instruments in the recording will confuse it."
))
output_midi = gr.File(
label="Transcription",
file_types=[".mid", ".midi"],
type="filepath"
).set_info("The transcribed notes, with onsets, offsets and velocities.")
app = build_endpoint(
model_card=model_card,
input_components=[input_audio],
output_components=[output_midi],
process_fn=process_fn
)
demo.queue().launch(pwa=True, show_error=True, ssr_mode=False)
|