Clearline-NC-GPU / example.py
huseinzolkepliscicom's picture
Card and example: long-recording agreement measured on the shipped weights
4f4f58e verified
Raw History Blame Contribute Delete
3.74 kB
"""Clearline-NC-GPU on a recording: background noise out, every voice kept.
pip install -r requirements.txt
python example.py noisy.wav enhanced.wav [--live] [--device cuda]
The model is Voice Isolator's released network (its code is in vi/) fine-tuned to keep every talker, and it is never
given an enrollment. It is causal (it looks 10 ms ahead) and remembers a whole call, so a recording is never cut into
pieces (60 s pieces with 10 s of context match it to only 20.6 dB): up to two minutes it runs in one pass, longer ones
through Voice Isolator's live engine in float16 (memory stays at a few GB; over 600 s it matches an exact float32 run
to 55.1 dB, three times faster than real time). --live writes what a live call would hear: the same engine, 20 ms at a
time, the output 10 ms behind the input.
"""
import argparse, copy, importlib, importlib.util, os, sys
from pathlib import Path
import numpy as np
import torch
HERE = Path(os.path.abspath(__file__)).parent # not resolve(): a Hub snapshot's files are links into its blob store
SR = 16000
_PKG = "clearline_nc_gpu_vi" # vi/ under its own name, so a `vi` package imported from elsewhere cannot shadow it
def _vi(sub):
if _PKG not in sys.modules:
spec = importlib.util.spec_from_file_location(_PKG, HERE / "vi" / "__init__.py",
submodule_search_locations=[str(HERE / "vi")])
mod = importlib.util.module_from_spec(spec)
sys.modules[_PKG] = mod
spec.loader.exec_module(mod)
return importlib.import_module(f"{_PKG}.{sub}")
def load(path=HERE, device="cuda"):
"""The model from a copy of this repository (config.json and model.safetensors)."""
return _vi("infer").load_model(str(path), device=device)
@torch.no_grad()
def enhance(model, x, one_pass_s=120.0):
"""x: float32 mono at 16 kHz -> the enhanced signal, the same length and aligned with x. One pass up to one_pass_s
(its causal WavLM's attention mask grows with the square of the length), else the live engine on a float16 copy."""
dev = next(model.parameters()).device
x = np.ascontiguousarray(x, dtype=np.float32)
if len(x) <= one_pass_s * SR:
return model(torch.from_numpy(x)[None].to(dev))[0].float().cpu().numpy()
out = stream(copy.deepcopy(model), x, graphs=dev.type == "cuda")
k = out["delay"]
y = np.zeros_like(x)
y[: len(out["y"]) - k] = out["y"][k:]
return y
def stream(model, x, chunk_hops=2, dtype=torch.float16, graphs=True):
"""x as one live call through Voice Isolator's engine (vi/stream.py), self-enrollment off: the model keeps its null
enrollment, as it was trained. Converts `model` to `dtype` in place (load a second copy for offline use). Returns
dict(y=the output of every whole 20 ms chunk, delay=the samples it lags the input)."""
dev = next(model.parameters()).device
s = _vi("stream").Stream(model, chunk_hops=chunk_hops, device=str(dev), dtype=dtype, graphs=graphs)
s.srv.self_enroll = False
return dict(y=s.push(np.asarray(x, np.float32)), delay=s.delay)
def main():
import soundfile as sf
ap = argparse.ArgumentParser()
ap.add_argument("inp")
ap.add_argument("out")
ap.add_argument("--live", action="store_true")
ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
a = ap.parse_args()
x, sr = sf.read(a.inp, dtype="float32", always_2d=True)
x = x.mean(1)
if sr != SR:
import soxr
x = soxr.resample(x, sr, SR).astype(np.float32)
m = load(device=a.device)
y = stream(m, x, graphs=a.device != "cpu")["y"] if a.live else enhance(m, x)
sf.write(a.out, y, SR)
if __name__ == "__main__":
main()