UDREAM / app.py
Vansh Chugh
cleanup: document fidelity deviations, drop dead mono/multi-channel branch
61431ae
Raw
History Blame Contribute Delete
4.04 kB
import sys
sys.stdout.reconfigure(line_buffering=True)
try:
import spaces
except ImportError:
# keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
class spaces:
class GPU:
def __init__(self, func=None, duration=60):
self.func = func
def __call__(self, *args, **kwargs):
if self.func is not None:
return self.func(*args, **kwargs)
func = args[0]
return func
import gradio as gr
import soundfile as sf
import torch
import torchaudio.functional as AF
from pyharp import ModelCard, build_endpoint
from model.speech_models.metricgan_generator import MetricGANGenerator
from model.speech_models.fullsubnet import PhaseInvariantFullSubNet
# Constructed directly rather than via the original repo's LightningCLI config loader,
# since only speech_model (never reverb_model/joint_loss_module) is used at inference.
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
PRESETS = {
"Standard": {
"cls": MetricGANGenerator,
"ckpt": "checkpoints/unsupervised_bilstm/model.ckpt",
},
"Enhanced (phase-aware, weak supervision)": {
"cls": PhaseInvariantFullSubNet,
"ckpt": "checkpoints/phaseinv_fsn/model.ckpt",
},
}
_models = {} # preset name -> model instance already resident on DEVICE
def get_model(preset):
"""Builds and caches a preset's model."""
if preset not in _models:
cfg = PRESETS[preset]
model = cfg["cls"](metrics=[]) # config's metrics are for validation logging, unused at inference
model.load_state_dict_from_joint_model(cfg["ckpt"])
model.eval() # skips the original's .freeze(); @torch.inference_mode() already covers it
# .to() recurses into FirstLevelModule's shared stft/istft singleton too
model = model.to(DEVICE)
_models[preset] = model
return _models[preset]
model_card = ModelCard(
name="U-DREAM",
description="Removes room reverb from a speech recording. Mono only; stereo input is downmixed.",
author="Louis Bahrman, Marius Rodrigues, Mathieu Fontaine, Gaël Richard",
tags=["dereverberation", "speech enhancement"],
)
@spaces.GPU
@torch.inference_mode()
def process_fn(input_audio_path: str, preset: str) -> str:
"""Runs the selected dereverberation model on one audio file."""
model = get_model(preset)
data, fs = sf.read(input_audio_path, dtype="float32", always_2d=True)
y = torch.from_numpy(data.T).to(DEVICE) # (channels, samples)
if y.size(0) > 1:
y = y.mean(dim=0, keepdim=True) # model is monaural-only (per paper)
if fs != model.fs:
# musicians shouldn't have to pre-convert sample rate themselves
y = AF.resample(y, orig_freq=fs, new_freq=model.fs)
fs = model.fs
pred = model(y[None, ...])
s = model.get_time(pred)
s = s / s.abs().max()
output_audio_path = input_audio_path.rsplit(".", 1)[0] + "_predicted_dry.wav"
out = s[0].cpu().numpy() # (1, samples): mono in guarantees mono out
sf.write(output_audio_path, out[0], fs)
return output_audio_path
with gr.Blocks() as demo:
input_components = [
gr.Audio(type="filepath", label="Input Audio").harp_required(True),
gr.Dropdown(
choices=[("Standard", "Standard"), ("Enhanced", "Enhanced (phase-aware, weak supervision)")],
value="Standard",
label="Preset",
info="Standard: general-purpose reverb removal (paper's recommended BiLSTM default). "
"Enhanced: can be better or worse (phase-invariant FullSubNet).",
),
]
output_components = [
gr.Audio(type="filepath", label="Output Audio").set_info("Dereverberated (dry) speech."),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
if __name__ == "__main__":
demo.queue().launch(pwa=True)