File size: 3,741 Bytes
3d87583
 
 
0d3c93a
3d87583
0d3c93a
 
4f4f58e
0d3c93a
4f4f58e
0d3c93a
3d87583
0d3c93a
3d87583
 
 
 
 
ac5d955
3d87583
0d3c93a
 
 
 
 
 
 
 
 
 
 
3d87583
 
0d3c93a
3d87583
0d3c93a
3d87583
 
 
0d3c93a
 
 
3d87583
 
0d3c93a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d87583
 
 
 
 
 
 
0d3c93a
3d87583
 
 
 
 
 
 
0d3c93a
 
 
3d87583
 
 
 
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
"""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()