Files
Retrieval-based-Voice-Conve…/train/dataset/extract_f0.py

231 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import traceback
import parselmouth
import logging
import numpy as np
from i18n.i18n import I18nAuto
from tools.progress import should_report
i18n = I18nAuto()
logging.getLogger("numba").setLevel(logging.WARNING)
from multiprocessing import Process
mode = sys.argv[1].lower()
if mode == "cpu":
exp_dir = sys.argv[2]
n_p = int(sys.argv[3])
f0method = sys.argv[4]
device = "cpu"
is_half = False
elif mode == "cuda":
n_part = int(sys.argv[2])
i_part = int(sys.argv[3])
i_gpu = sys.argv[4]
os.environ["CUDA_VISIBLE_DEVICES"] = str(i_gpu)
exp_dir = sys.argv[5]
is_half = sys.argv[6].lower() == "true"
f0method = "rmvpe"
device = "cuda"
elif mode in ("dml", "directml"):
exp_dir = sys.argv[2]
f0method = "rmvpe"
is_half = False
import torch_directml
device = torch_directml.device(torch_directml.default_device())
else:
raise ValueError("Unsupported F0 extraction mode: %s" % mode)
# CUDA_VISIBLE_DEVICES must be set before infer.audio imports torch/configs.
from infer.audio import load_audio
f = open("%s/extract_f0_feature.log" % exp_dir, "a", encoding="utf8")
def printt(strr):
print(strr)
f.write("%s\n" % strr)
f.flush()
class FeatureInput(object):
def __init__(self, samplerate=16000, hop_size=160):
self.fs = samplerate
self.hop = hop_size
self.f0_bin = 256
self.f0_max = 1100.0
self.f0_min = 50.0
self.f0_mel_min = 1127 * np.log(1 + self.f0_min / 700)
self.f0_mel_max = 1127 * np.log(1 + self.f0_max / 700)
def compute_f0(self, path, f0_method):
if f0_method not in ("pm", "rmvpe"):
raise ValueError(i18n("仅支持pm和rmvpe音高提取算法"))
x = load_audio(path, self.fs)
p_len = x.shape[0] // self.hop
if f0_method == "pm":
time_step = 160 / 16000 * 1000
f0_min = 50
f0_max = 1100
f0 = (
parselmouth.Sound(x, self.fs)
.to_pitch_ac(
time_step=time_step / 1000,
voicing_threshold=0.6,
pitch_floor=f0_min,
pitch_ceiling=f0_max,
)
.selected_array["frequency"]
)
pad_size = (p_len - len(f0) + 1) // 2
if pad_size > 0 or p_len - len(f0) - pad_size > 0:
f0 = np.pad(
f0, [[pad_size, p_len - len(f0) - pad_size]], mode="constant"
)
elif f0_method == "rmvpe":
if hasattr(self, "model_rmvpe") == False:
from infer.rmvpe import RMVPE
printt(i18n("正在加载RMVPE模型"))
self.model_rmvpe = RMVPE(
"assets/rmvpe/rmvpe.pt", is_half=is_half, device=device
)
f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03)
f0 = np.asarray(f0)
try:
uv = f0 == 0
f0[uv] = np.interp(np.where(uv)[0], np.where(~uv)[0], f0[~uv])
except Exception:
traceback.print_exc()
return None
return f0
def coarse_f0(self, f0):
f0_mel = 1127 * np.log(1 + f0 / 700)
f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - self.f0_mel_min) * (
self.f0_bin - 2
) / (self.f0_mel_max - self.f0_mel_min) + 1
# use 0 or 1
f0_mel[f0_mel <= 1] = 1
f0_mel[f0_mel > self.f0_bin - 1] = self.f0_bin - 1
f0_coarse = np.rint(f0_mel).astype(int)
assert f0_coarse.max() <= 255 and f0_coarse.min() >= 1, (
f0_coarse.max(),
f0_coarse.min(),
)
return f0_coarse
def go(self, paths, f0_method, max_updates=5):
success = 0
skipped = 0
failed = 0
if len(paths) == 0:
printt(i18n("[F0提取] 无待处理音频,已全部跳过"))
else:
printt(i18n("[F0提取] 待处理:%s") % len(paths))
for idx, (inp_path, opt_path1, opt_path2) in enumerate(paths):
try:
if (
os.path.exists(opt_path1 + ".npy") == True
and os.path.exists(opt_path2 + ".npy") == True
):
skipped += 1
continue
featur_pit = self.compute_f0(inp_path, f0_method)
if featur_pit is None:
skipped += 1
printt(i18n("音高全部为0该音频无意义跳过%s") % inp_path)
continue
np.save(
opt_path2,
featur_pit,
allow_pickle=False,
) # nsf
coarse_pit = self.coarse_f0(featur_pit)
np.save(
opt_path1,
coarse_pit,
allow_pickle=False,
) # ori
success += 1
if should_report(idx, len(paths), max_updates):
printt(
i18n("[F0提取] 进度:%s/%s | 成功:%s | 跳过:%s | %s")
% (idx + 1, len(paths), success, skipped, os.path.basename(inp_path))
)
except Exception:
failed += 1
printt(
i18n("[F0提取][失败] %s\n%s")
% (inp_path, traceback.format_exc())
)
printt(
i18n("[F0提取] 完成 | 成功:%s | 跳过:%s | 失败:%s")
% (success, skipped, failed)
)
if __name__ == "__main__":
# exp_dir=r"E:\codes\py39\dataset\mi-test"
# n_p=16
featureInput = FeatureInput()
paths = []
inp_root = "%s/1_16k_wavs" % (exp_dir)
opt_root1 = "%s/2a_f0" % (exp_dir)
opt_root2 = "%s/2b-f0nsf" % (exp_dir)
os.makedirs(opt_root1, exist_ok=True)
os.makedirs(opt_root2, exist_ok=True)
for name in sorted(list(os.listdir(inp_root))):
inp_path = "%s/%s" % (inp_root, name)
if "spec" in inp_path:
continue
opt_path1 = "%s/%s" % (opt_root1, name)
opt_path2 = "%s/%s" % (opt_root2, name)
if os.path.exists(opt_path1 + ".npy") and os.path.exists(
opt_path2 + ".npy"
):
continue
paths.append([inp_path, opt_path1, opt_path2])
if mode == "cpu":
if not paths:
featureInput.go([], f0method, 1)
else:
worker_count = min(max(1, n_p), len(paths))
ps = []
for i in range(worker_count):
p = Process(
target=featureInput.go,
args=(
paths[i::worker_count],
f0method,
max(1, (12 + worker_count - 1) // worker_count),
),
)
ps.append(p)
p.start()
for p in ps:
p.join()
elif mode == "cuda":
try:
featureInput.go(
paths[i_part::n_part],
"rmvpe",
max(1, (12 + n_part - 1) // n_part),
)
except Exception:
printt(i18n("[F0提取][失败] %s") % traceback.format_exc())
else:
try:
featureInput.go(paths, "rmvpe", 5)
except Exception:
printt(i18n("[F0提取][失败] %s") % traceback.format_exc())