From 1d8a7a1c939ba42d252e4be52546106884f33e57 Mon Sep 17 00:00:00 2001 From: AIFSH <1509359472@qq.com> Date: Mon, 29 Apr 2024 11:41:01 +0000 Subject: [PATCH] first commit --- LICENSE | 7 + README.md | 32 + __init__.py | 69 + download_models.py | 56 + nodes.py | 252 ++++ requirements.txt | 47 + rvc/__init__.py | 0 rvc/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 149 bytes rvc/__pycache__/train.cpython-310.pyc | Bin 0 -> 11457 bytes .../__pycache__/config.cpython-310.pyc | Bin 0 -> 5783 bytes rvc/configs/config.json | 1 + rvc/configs/config.py | 264 ++++ rvc/configs/inuse/.gitignore | 4 + rvc/configs/inuse/v1/.gitignore | 2 + rvc/configs/inuse/v2/.gitignore | 2 + rvc/configs/v1/32k.json | 46 + rvc/configs/v1/40k.json | 46 + rvc/configs/v1/48k.json | 46 + rvc/configs/v2/32k.json | 46 + rvc/configs/v2/48k.json | 46 + rvc/i18n/__pycache__/i18n.cpython-310.pyc | Bin 0 -> 1286 bytes rvc/i18n/i18n.py | 27 + rvc/i18n/locale/en_US.json | 138 ++ rvc/i18n/locale/es_ES.json | 138 ++ rvc/i18n/locale/fr_FR.json | 138 ++ rvc/i18n/locale/it_IT.json | 138 ++ rvc/i18n/locale/ja_JP.json | 138 ++ rvc/i18n/locale/ko_KR.json | 138 ++ rvc/i18n/locale/pt_BR.json | 138 ++ rvc/i18n/locale/ru_RU.json | 138 ++ rvc/i18n/locale/tr_TR.json | 138 ++ rvc/i18n/locale/zh_CN.json | 138 ++ rvc/i18n/locale/zh_HK.json | 138 ++ rvc/i18n/locale/zh_SG.json | 138 ++ rvc/i18n/locale/zh_TW.json | 138 ++ rvc/i18n/locale_diff.py | 47 + rvc/i18n/scan_i18n.py | 75 + .../lib/__pycache__/audio.cpython-310.pyc | Bin 0 -> 1380 bytes .../lib/__pycache__/rmvpe.cpython-310.pyc | Bin 0 -> 18575 bytes .../lib/__pycache__/rvcmd.cpython-310.pyc | Bin 0 -> 6010 bytes .../lib/__pycache__/slicer2.cpython-310.pyc | Bin 0 -> 5200 bytes rvc/infer/lib/audio.py | 50 + .../__pycache__/attentions.cpython-310.pyc | Bin 0 -> 10429 bytes .../__pycache__/commons.cpython-310.pyc | Bin 0 -> 5649 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 25908 bytes .../__pycache__/modules.cpython-310.pyc | Bin 0 -> 14193 bytes .../__pycache__/transforms.cpython-310.pyc | Bin 0 -> 3934 bytes rvc/infer/lib/infer_pack/attentions.py | 459 ++++++ rvc/infer/lib/infer_pack/attentions_onnx.py | 459 ++++++ rvc/infer/lib/infer_pack/commons.py | 172 +++ rvc/infer/lib/infer_pack/models.py | 1242 +++++++++++++++++ rvc/infer/lib/infer_pack/models_onnx.py | 881 ++++++++++++ rvc/infer/lib/infer_pack/modules.py | 615 ++++++++ .../modules/F0Predictor/DioF0Predictor.py | 91 ++ .../modules/F0Predictor/F0Predictor.py | 16 + .../modules/F0Predictor/HarvestF0Predictor.py | 87 ++ .../modules/F0Predictor/PMF0Predictor.py | 98 ++ .../modules/F0Predictor/__init__.py | 0 rvc/infer/lib/infer_pack/onnx_inference.py | 149 ++ rvc/infer/lib/infer_pack/transforms.py | 207 +++ rvc/infer/lib/jit/__init__.py | 163 +++ .../jit/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 4155 bytes rvc/infer/lib/jit/get_hubert.py | 342 +++++ rvc/infer/lib/jit/get_rmvpe.py | 12 + rvc/infer/lib/jit/get_synthesizer.py | 38 + rvc/infer/lib/rmvpe.py | 670 +++++++++ rvc/infer/lib/rtrvc.py | 466 +++++++ rvc/infer/lib/rvcmd.py | 243 ++++ rvc/infer/lib/slicer2.py | 260 ++++ .../__pycache__/data_utils.cpython-310.pyc | Bin 0 -> 12243 bytes .../train/__pycache__/losses.cpython-310.pyc | Bin 0 -> 1491 bytes .../mel_processing.cpython-310.pyc | Bin 0 -> 3033 bytes .../__pycache__/process_ckpt.cpython-310.pyc | Bin 0 -> 4498 bytes .../train/__pycache__/utils.cpython-310.pyc | Bin 0 -> 11665 bytes rvc/infer/lib/train/data_utils.py | 517 +++++++ rvc/infer/lib/train/losses.py | 58 + rvc/infer/lib/train/mel_processing.py | 127 ++ rvc/infer/lib/train/process_ckpt.py | 261 ++++ rvc/infer/lib/train/utils.py | 478 +++++++ rvc/infer/lib/uvr5_pack/lib_v5/dataset.py | 183 +++ rvc/infer/lib/uvr5_pack/lib_v5/layers.py | 118 ++ .../lib/uvr5_pack/lib_v5/layers_123812KB .py | 118 ++ .../lib/uvr5_pack/lib_v5/layers_123821KB.py | 118 ++ .../lib/uvr5_pack/lib_v5/layers_33966KB.py | 126 ++ .../lib/uvr5_pack/lib_v5/layers_537227KB.py | 126 ++ .../lib/uvr5_pack/lib_v5/layers_537238KB.py | 126 ++ rvc/infer/lib/uvr5_pack/lib_v5/layers_new.py | 125 ++ .../lib/uvr5_pack/lib_v5/model_param_init.py | 69 + .../modelparams/1band_sr16000_hl512.json | 19 + .../modelparams/1band_sr32000_hl512.json | 19 + .../modelparams/1band_sr33075_hl384.json | 19 + .../modelparams/1band_sr44100_hl1024.json | 19 + .../modelparams/1band_sr44100_hl256.json | 19 + .../modelparams/1band_sr44100_hl512.json | 19 + .../modelparams/1band_sr44100_hl512_cut.json | 19 + .../lib_v5/modelparams/2band_32000.json | 30 + .../lib_v5/modelparams/2band_44100_lofi.json | 30 + .../lib_v5/modelparams/2band_48000.json | 30 + .../lib_v5/modelparams/3band_44100.json | 42 + .../lib_v5/modelparams/3band_44100_mid.json | 43 + .../lib_v5/modelparams/3band_44100_msb2.json | 43 + .../lib_v5/modelparams/4band_44100.json | 54 + .../lib_v5/modelparams/4band_44100_mid.json | 55 + .../lib_v5/modelparams/4band_44100_msb.json | 55 + .../lib_v5/modelparams/4band_44100_msb2.json | 55 + .../modelparams/4band_44100_reverse.json | 55 + .../lib_v5/modelparams/4band_44100_sw.json | 55 + .../lib_v5/modelparams/4band_v2.json | 54 + .../lib_v5/modelparams/4band_v2_sn.json | 55 + .../lib_v5/modelparams/4band_v3.json | 54 + .../lib_v5/modelparams/ensemble.json | 43 + rvc/infer/lib/uvr5_pack/lib_v5/nets.py | 123 ++ .../lib/uvr5_pack/lib_v5/nets_123812KB.py | 122 ++ .../lib/uvr5_pack/lib_v5/nets_123821KB.py | 122 ++ .../lib/uvr5_pack/lib_v5/nets_33966KB.py | 122 ++ .../lib/uvr5_pack/lib_v5/nets_537227KB.py | 123 ++ .../lib/uvr5_pack/lib_v5/nets_537238KB.py | 123 ++ .../lib/uvr5_pack/lib_v5/nets_61968KB.py | 122 ++ rvc/infer/lib/uvr5_pack/lib_v5/nets_new.py | 133 ++ rvc/infer/lib/uvr5_pack/lib_v5/spec_utils.py | 672 +++++++++ rvc/infer/lib/uvr5_pack/name_params.json | 263 ++++ rvc/infer/lib/uvr5_pack/utils.py | 121 ++ rvc/infer/modules/gui/__init__.py | 13 + rvc/infer/modules/gui/torchgate.py | 280 ++++ rvc/infer/modules/gui/utils.py | 70 + rvc/infer/modules/ipex/__init__.py | 190 +++ rvc/infer/modules/ipex/attention.py | 218 +++ rvc/infer/modules/ipex/gradscaler.py | 187 +++ rvc/infer/modules/ipex/hijacks.py | 365 +++++ rvc/infer/modules/onnx/export.py | 52 + .../modules/train/extract/extract_f0_print.py | 175 +++ .../modules/train/extract/extract_f0_rmvpe.py | 141 ++ .../train/extract/extract_f0_rmvpe_dml.py | 139 ++ .../modules/train/extract_feature_print.py | 142 ++ rvc/infer/modules/train/preprocess.py | 142 ++ rvc/infer/modules/train/train.py | 640 +++++++++ rvc/infer/modules/uvr5/mdxnet.py | 256 ++++ rvc/infer/modules/uvr5/modules.py | 108 ++ rvc/infer/modules/uvr5/vr.py | 368 +++++ rvc/infer/modules/vc/__init__.py | 0 .../vc/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 166 bytes .../vc/__pycache__/modules.cpython-310.pyc | Bin 0 -> 5396 bytes .../vc/__pycache__/pipeline.cpython-310.pyc | Bin 0 -> 9303 bytes .../vc/__pycache__/utils.cpython-310.pyc | Bin 0 -> 1272 bytes rvc/infer/modules/vc/modules.py | 304 ++++ rvc/infer/modules/vc/pipeline.py | 475 +++++++ rvc/infer/modules/vc/utils.py | 33 + rvc/logs/mute/0_gt_wavs/mute32k.wav | Bin 0 -> 192078 bytes rvc/logs/mute/0_gt_wavs/mute40k.spec.pt | Bin 0 -> 1230337 bytes rvc/logs/mute/0_gt_wavs/mute40k.wav | Bin 0 -> 240078 bytes rvc/logs/mute/0_gt_wavs/mute48k.spec.pt | Bin 0 -> 1230337 bytes rvc/logs/mute/0_gt_wavs/mute48k.wav | Bin 0 -> 288078 bytes rvc/logs/mute/1_16k_wavs/mute.wav | Bin 0 -> 96078 bytes rvc/logs/mute/2a_f0/mute.wav.npy | Bin 0 -> 1332 bytes rvc/logs/mute/2b-f0nsf/mute.wav.npy | Bin 0 -> 2536 bytes rvc/logs/mute/3_feature256/mute.npy | Bin 0 -> 152704 bytes rvc/logs/mute/3_feature768/mute.npy | Bin 0 -> 457856 bytes rvc/train.py | 619 ++++++++ web/js/alertMSG.js | 21 + web/js/previewAudio.js | 155 ++ web/js/refreshPath.js | 36 + web/js/uploadAudio.js | 203 +++ 162 files changed, 20443 insertions(+) create mode 100644 __init__.py create mode 100644 download_models.py create mode 100644 nodes.py create mode 100644 requirements.txt create mode 100644 rvc/__init__.py create mode 100644 rvc/__pycache__/__init__.cpython-310.pyc create mode 100644 rvc/__pycache__/train.cpython-310.pyc create mode 100644 rvc/configs/__pycache__/config.cpython-310.pyc create mode 100644 rvc/configs/config.json create mode 100644 rvc/configs/config.py create mode 100644 rvc/configs/inuse/.gitignore create mode 100644 rvc/configs/inuse/v1/.gitignore create mode 100644 rvc/configs/inuse/v2/.gitignore create mode 100644 rvc/configs/v1/32k.json create mode 100644 rvc/configs/v1/40k.json create mode 100644 rvc/configs/v1/48k.json create mode 100644 rvc/configs/v2/32k.json create mode 100644 rvc/configs/v2/48k.json create mode 100644 rvc/i18n/__pycache__/i18n.cpython-310.pyc create mode 100644 rvc/i18n/i18n.py create mode 100644 rvc/i18n/locale/en_US.json create mode 100644 rvc/i18n/locale/es_ES.json create mode 100644 rvc/i18n/locale/fr_FR.json create mode 100644 rvc/i18n/locale/it_IT.json create mode 100644 rvc/i18n/locale/ja_JP.json create mode 100644 rvc/i18n/locale/ko_KR.json create mode 100644 rvc/i18n/locale/pt_BR.json create mode 100644 rvc/i18n/locale/ru_RU.json create mode 100644 rvc/i18n/locale/tr_TR.json create mode 100644 rvc/i18n/locale/zh_CN.json create mode 100644 rvc/i18n/locale/zh_HK.json create mode 100644 rvc/i18n/locale/zh_SG.json create mode 100644 rvc/i18n/locale/zh_TW.json create mode 100644 rvc/i18n/locale_diff.py create mode 100644 rvc/i18n/scan_i18n.py create mode 100644 rvc/infer/lib/__pycache__/audio.cpython-310.pyc create mode 100644 rvc/infer/lib/__pycache__/rmvpe.cpython-310.pyc create mode 100644 rvc/infer/lib/__pycache__/rvcmd.cpython-310.pyc create mode 100644 rvc/infer/lib/__pycache__/slicer2.cpython-310.pyc create mode 100644 rvc/infer/lib/audio.py create mode 100644 rvc/infer/lib/infer_pack/__pycache__/attentions.cpython-310.pyc create mode 100644 rvc/infer/lib/infer_pack/__pycache__/commons.cpython-310.pyc create mode 100644 rvc/infer/lib/infer_pack/__pycache__/models.cpython-310.pyc create mode 100644 rvc/infer/lib/infer_pack/__pycache__/modules.cpython-310.pyc create mode 100644 rvc/infer/lib/infer_pack/__pycache__/transforms.cpython-310.pyc create mode 100644 rvc/infer/lib/infer_pack/attentions.py create mode 100644 rvc/infer/lib/infer_pack/attentions_onnx.py create mode 100644 rvc/infer/lib/infer_pack/commons.py create mode 100644 rvc/infer/lib/infer_pack/models.py create mode 100644 rvc/infer/lib/infer_pack/models_onnx.py create mode 100644 rvc/infer/lib/infer_pack/modules.py create mode 100644 rvc/infer/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py create mode 100644 rvc/infer/lib/infer_pack/modules/F0Predictor/F0Predictor.py create mode 100644 rvc/infer/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py create mode 100644 rvc/infer/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py create mode 100644 rvc/infer/lib/infer_pack/modules/F0Predictor/__init__.py create mode 100644 rvc/infer/lib/infer_pack/onnx_inference.py create mode 100644 rvc/infer/lib/infer_pack/transforms.py create mode 100644 rvc/infer/lib/jit/__init__.py create mode 100644 rvc/infer/lib/jit/__pycache__/__init__.cpython-310.pyc create mode 100644 rvc/infer/lib/jit/get_hubert.py create mode 100644 rvc/infer/lib/jit/get_rmvpe.py create mode 100644 rvc/infer/lib/jit/get_synthesizer.py create mode 100644 rvc/infer/lib/rmvpe.py create mode 100644 rvc/infer/lib/rtrvc.py create mode 100644 rvc/infer/lib/rvcmd.py create mode 100644 rvc/infer/lib/slicer2.py create mode 100644 rvc/infer/lib/train/__pycache__/data_utils.cpython-310.pyc create mode 100644 rvc/infer/lib/train/__pycache__/losses.cpython-310.pyc create mode 100644 rvc/infer/lib/train/__pycache__/mel_processing.cpython-310.pyc create mode 100644 rvc/infer/lib/train/__pycache__/process_ckpt.cpython-310.pyc create mode 100644 rvc/infer/lib/train/__pycache__/utils.cpython-310.pyc create mode 100644 rvc/infer/lib/train/data_utils.py create mode 100644 rvc/infer/lib/train/losses.py create mode 100644 rvc/infer/lib/train/mel_processing.py create mode 100644 rvc/infer/lib/train/process_ckpt.py create mode 100644 rvc/infer/lib/train/utils.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/dataset.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers_123812KB .py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers_123821KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers_33966KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers_537227KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers_537238KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/layers_new.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/model_param_init.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr16000_hl512.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl1024.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl256.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512_cut.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_44100_lofi.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_48000.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_mid.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb2.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_reverse.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_sw.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v3.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/modelparams/ensemble.json create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_123812KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_123821KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_33966KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_537227KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_537238KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_61968KB.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/nets_new.py create mode 100644 rvc/infer/lib/uvr5_pack/lib_v5/spec_utils.py create mode 100644 rvc/infer/lib/uvr5_pack/name_params.json create mode 100644 rvc/infer/lib/uvr5_pack/utils.py create mode 100644 rvc/infer/modules/gui/__init__.py create mode 100644 rvc/infer/modules/gui/torchgate.py create mode 100644 rvc/infer/modules/gui/utils.py create mode 100644 rvc/infer/modules/ipex/__init__.py create mode 100644 rvc/infer/modules/ipex/attention.py create mode 100644 rvc/infer/modules/ipex/gradscaler.py create mode 100644 rvc/infer/modules/ipex/hijacks.py create mode 100644 rvc/infer/modules/onnx/export.py create mode 100644 rvc/infer/modules/train/extract/extract_f0_print.py create mode 100644 rvc/infer/modules/train/extract/extract_f0_rmvpe.py create mode 100644 rvc/infer/modules/train/extract/extract_f0_rmvpe_dml.py create mode 100644 rvc/infer/modules/train/extract_feature_print.py create mode 100644 rvc/infer/modules/train/preprocess.py create mode 100644 rvc/infer/modules/train/train.py create mode 100644 rvc/infer/modules/uvr5/mdxnet.py create mode 100644 rvc/infer/modules/uvr5/modules.py create mode 100644 rvc/infer/modules/uvr5/vr.py create mode 100644 rvc/infer/modules/vc/__init__.py create mode 100644 rvc/infer/modules/vc/__pycache__/__init__.cpython-310.pyc create mode 100644 rvc/infer/modules/vc/__pycache__/modules.cpython-310.pyc create mode 100644 rvc/infer/modules/vc/__pycache__/pipeline.cpython-310.pyc create mode 100644 rvc/infer/modules/vc/__pycache__/utils.cpython-310.pyc create mode 100644 rvc/infer/modules/vc/modules.py create mode 100644 rvc/infer/modules/vc/pipeline.py create mode 100644 rvc/infer/modules/vc/utils.py create mode 100644 rvc/logs/mute/0_gt_wavs/mute32k.wav create mode 100644 rvc/logs/mute/0_gt_wavs/mute40k.spec.pt create mode 100644 rvc/logs/mute/0_gt_wavs/mute40k.wav create mode 100644 rvc/logs/mute/0_gt_wavs/mute48k.spec.pt create mode 100644 rvc/logs/mute/0_gt_wavs/mute48k.wav create mode 100644 rvc/logs/mute/1_16k_wavs/mute.wav create mode 100644 rvc/logs/mute/2a_f0/mute.wav.npy create mode 100644 rvc/logs/mute/2b-f0nsf/mute.wav.npy create mode 100644 rvc/logs/mute/3_feature256/mute.npy create mode 100644 rvc/logs/mute/3_feature768/mute.npy create mode 100644 rvc/train.py create mode 100644 web/js/alertMSG.js create mode 100644 web/js/previewAudio.js create mode 100644 web/js/refreshPath.js create mode 100644 web/js/uploadAudio.js diff --git a/LICENSE b/LICENSE index f8c6fac..e366f23 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,10 @@ +本软件及其相关代码以MIT协议开源,作者不对软件具备任何控制力,使用软件者、传播软件导出的声音者自负全责。 +如不认可该条款,则不能使用或引用软件包内任何代码和文件。 + +特此授予任何获得本软件和相关文档文件(以下简称“软件”)副本的人免费使用、复制、修改、合并、出版、分发、再授权和/或销售本软件的权利,以及授予本软件所提供的人使用本软件的权利,但须符合以下条件: +上述版权声明和本许可声明应包含在软件的所有副本或实质部分中。 +软件是“按原样”提供的,没有任何明示或暗示的保证,包括但不限于适销性、适用于特定目的和不侵权的保证。在任何情况下,作者或版权持有人均不承担因软件或软件的使用或其他交易而产生、产生或与之相关的任何索赔、损害赔偿或其他责任,无论是在合同诉讼、侵权诉讼还是其他诉讼中。 + MIT License Copyright (c) 2024 AIFSH diff --git a/README.md b/README.md index 6fc1d1a..5526ae3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ # ComfyUI-RVC a comfyui custom node for [Retrieval-based-Voice-Conversion-WebUI](https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git),you can Voice-Conversion in comfyui now! + +## How to use +make sure `ffmpeg` is worked in your commandline +for Linux +``` +apt update +apt install ffmpeg +``` +for Windows,you can install `ffmpeg` by [WingetUI](https://github.com/marticliment/WingetUI) automatically + +then! +``` +git clone https://github.com/AIFSH/ComfyUI-RVC.git +cd ComfyUI-RVC +pip install -r requirements.txt +``` +`weights` will be download from huggingface automatically!if you in china,make sure your internet attach the huggingface +or if you still struggle with huggingface, you may try follow [hf-mirror](https://hf-mirror.com/) to config your env. + +## Tutorial +todo + +## WeChat Group && Donate +
+
+ Wechat + donate +
+
+ +## Thanks +- [Retrieval-based-Voice-Conversion-WebUI](https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..8cde5cd --- /dev/null +++ b/__init__.py @@ -0,0 +1,69 @@ +import os +import sys,site +from subprocess import Popen +from server import PromptServer +now_dir = os.path.dirname(os.path.abspath(__file__)) + +site_packages_roots = [] +for path in site.getsitepackages(): + if "packages" in path: + site_packages_roots.append(path) +if(site_packages_roots==[]):site_packages_roots=["%s/runtime/Lib/site-packages" % now_dir] +#os.environ["OPENBLAS_NUM_THREADS"] = "4" +for site_packages_root in site_packages_roots: + if os.path.exists(site_packages_root): + try: + with open("%s/users.pth" % (site_packages_root), "a") as f: + f.write( + "%s\n%s/rvc\n%s/rvc/infer" + % (now_dir,now_dir,now_dir) + ) + break + except PermissionError: + raise PermissionError + +if os.path.isfile("%s/users.pth" % (site_packages_root)): + print("!!!RVC path was added to " + "%s/users.pth" % (site_packages_root) + + "\n if meet `No module` error,try `python main.py` again") + +model_path = os.path.join(now_dir,"rvc", "assets") + +if not os.path.exists(os.path.join(model_path, "pretrained_v2")): + cmd = "python %s/download_models.py" % (now_dir) + p = Popen(cmd, shell=True, cwd=now_dir) + p.wait() +else: + print("!!!RVC use cache models,make sure your 'assets' complete") + + +WEB_DIRECTORY = "./web" +from .nodes import LoadAudio, PreViewAudio,RVC_Train,RVC_Infer + +# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension +# WEB_DIRECTORY = "./somejs" + +# A dictionary that contains all nodes you want to export with their names +# NOTE: names should be globally unique +NODE_CLASS_MAPPINGS = { + "LoadAudio": LoadAudio, + "PreViewAudio": PreViewAudio, + "RVC_Train": RVC_Train, + "RVC_Infer": RVC_Infer +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "LoadAudio": "AudioLoader", + "PreViewAudio": "PreView Audio", + "RVC_Train": "RVC_Train", + "RVC_Infer": "RVC_Infer" +} + +@PromptServer.instance.routes.get("/rvc/reboot") +def restart(self): + try: + sys.stdout.close_log() + except Exception as e: + pass + + return os.execv(sys.executable, [sys.executable] + sys.argv) \ No newline at end of file diff --git a/download_models.py b/download_models.py new file mode 100644 index 0000000..a3d6ea5 --- /dev/null +++ b/download_models.py @@ -0,0 +1,56 @@ +import os +from pathlib import Path +from huggingface_hub import hf_hub_download + +now_dir = os.path.dirname(os.path.abspath(__file__)) +BASE_DIR = os.path.join(now_dir, "rvc") + + +if __name__ == "__main__": + os.makedirs(os.path.join(BASE_DIR ,"assets","weights"), exist_ok=True) + weights_path = os.path.join(BASE_DIR ,"assets") + print("Downloading hubert_base.pt...") + hf_hub_download(repo_id="lj1995/VoiceConversionWebUI", + filename="hubert_base.pt", + subfolder= "", + local_dir= os.path.join(weights_path, "hubert")) + print("Downloading rmvpe.pt...") + hf_hub_download(repo_id="lj1995/VoiceConversionWebUI", + filename="rmvpe.pt", + subfolder= "", + local_dir= os.path.join(weights_path, "rmvpe")) + + + print("Downloading pretrained models:") + + model_names = [ + "D40k.pth", + "D48k.pth", + "G32k.pth", + "G40k.pth", + "G48k.pth", + "f0D32k.pth", + "f0D40k.pth", + "f0D48k.pth", + "f0G32k.pth", + "f0G40k.pth", + "f0G48k.pth", + ] + for model in model_names: + print(f"Downloading {model}...") + hf_hub_download(repo_id="lj1995/VoiceConversionWebUI", + filename=model, + subfolder= "pretrained", + local_dir= weights_path) + + + print("Downloading pretrained models v2:") + + for model in model_names: + print(f"Downloading {model}...") + hf_hub_download(repo_id="lj1995/VoiceConversionWebUI", + filename=model, + subfolder= "pretrained_v2", + local_dir= weights_path) + + print("All models downloaded!") diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..b849e8c --- /dev/null +++ b/nodes.py @@ -0,0 +1,252 @@ +import os +import time +import shutil +import folder_paths +import numpy as np +from multiprocessing import cpu_count +from scipy.io.wavfile import write as wavwrite +from .rvc.train import vc,gpus,default_batch_size,preprocess_dataset,extract_f0_feature,train1key + +now_dir = os.path.dirname(os.path.abspath(__file__)) +out_path = folder_paths.get_output_directory() +input_path = folder_paths.get_input_directory() +rvc_out_path = os.path.join(out_path,"RVC") +os.environ["weight_root"] = rvc_out_path +os.environ["index_root"] = rvc_out_path +os.environ["rmvpe_root"] = os.path.join(now_dir,"rvc","assets","rmvpe") +os.environ["hubert_base"] = os.path.join(now_dir,"rvc","assets","hubert","hubert_base.pt") +class RVC_Infer: + @classmethod + def INPUT_TYPES(s): + pth_list = [f for f in os.listdir(rvc_out_path) if os.path.isfile(os.path.join(rvc_out_path, f)) and f.split('.')[-1] == "pth"] + index_list = [f for f in os.listdir(rvc_out_path) if os.path.isfile(os.path.join(rvc_out_path, f)) and f.split('.')[-1] == "index"] + return {"required":{ + "audio": ("AUDIO",), + "sid":(pth_list,), + "spk_item":("INT",{ + "min":0, + "max":4, + "step": 1, + "default": 0, + "display": "slider" + }), + "vc_transform":("INT",{ + "min":-12, + "max":12, + "step": 1, + "default": 0, + "display": "slider" + }), + "file_index": (index_list,), + + "f0_method":(["pm", "harvest", "crepe", "rmvpe"], + { + "default": "rmvpe" + + }), + "resample_sr":("INT",{ + "min":0, + "max":48000, + "step": 1, + "default": 0, + "display": "slider" + }), + "rms_mix_rate":("FLOAT",{ + "min":0, + "max":1.0, + "step": 0.05, + "default": 0.25, + "display": "slider" + }), + "protect":("FLOAT",{ + "min":0, + "max":0.5, + "step": 0.01, + "default": 0.33, + "display": "slider" + }), + "filter_radius":("INT",{ + "min":0, + "max":7, + "step": 1, + "default": 3, + "display": "slider" + }), + "index_rate":("FLOAT",{ + "min":0, + "max":1, + "step": 0.05, + "default": 0.75, + "display": "slider" + }), + }} + + CATEGORY = "AIFSH_RVC" + RETURN_TYPES = ("AUDIO",) + OUTPUT_NODE = False + + FUNCTION = "inference" + + def inference(self,audio,sid,spk_item,vc_transform, + file_index,f0_method,resample_sr,rms_mix_rate, + protect,filter_radius,index_rate): + audio_path = folder_paths.get_annotated_filepath(audio) + file_index = os.path.join(rvc_out_path,file_index) + vc.get_vc(sid,protect,protect) + f0_file = "" + info, (tgt_sr, audio_opt) = vc.vc_single(spk_item,input_audio_path=audio_path,f0_up_key=vc_transform, + f0_file=f0_file,f0_method=f0_method,file_index=file_index, + file_index2=file_index,index_rate=index_rate,filter_radius=filter_radius, + resample_sr=resample_sr,rms_mix_rate=rms_mix_rate, + protect=protect) + print(info) + audio_name = os.path.basename(audio_path) + out_file = os.path.join(out_path,f"{time.time()}_rvc_{audio_name}") + wavwrite(out_file, tgt_sr,audio_opt) + return (out_file,) + + +class RVC_Train: + @classmethod + def INPUT_TYPES(s): + return {"required":{ + "audio":("AUDIO",), + "exp_name": ("STRING",{ + "default": "singer1" + }), + "sr":(["32k","40k", "48k"],{ + "default":"40k" + }), + "if_f0_3":("BOOLEAN",{ + "default": True + }), + "version":(["v1", "v2"],{ + "default": "v2" + }), + "speaker_id":("INT",{ + "display": "slider", + "min": 0, + "max": 4, + "step":1, + "default":1 + }), + "f0_method":(["pm", "harvest", "dio", "rmvpe", "rmvpe_gpu"],{ + "default": "rmvpe_gpu", + }), + "save_epoch":("INT",{ + "display": "slider", + "min": 1, + "max": 50, + "step":1, + "default":5 + }), + "total_epoch":("INT",{ + "display": "slider", + "min": 2, + "max": 1000, + "step":1, + "default":20 + }), + "batch_size":("INT",{ + "display": "slider", + "min": 1, + "max": 40, + "step":1, + "default":default_batch_size + }), + "if_save_latest":("BOOLEAN",{ + "default": False + }), + "if_cache_gpu":("BOOLEAN",{ + "default": False + }), + "if_save_every_weights":("BOOLEAN",{ + "default": False + }), + }} + + CATEGORY = "AIFSH_RVC" + RETURN_TYPES = () + OUTPUT_NODE = True + + FUNCTION = "train" + + def train(self,audio,exp_name,sr,if_f0_3,version, + speaker_id,f0_method,save_epoch,total_epoch, + batch_size,if_save_latest,if_cache_gpu, + if_save_every_weights): + # step 1 + if sr == "32k" and version == "v1": + sr = "40k" + shutil.rmtree(os.path.join(now_dir,"rvc","logs",exp_name),ignore_errors=True) + wav_path = folder_paths.get_annotated_filepath(audio) + exp_path = os.path.join(now_dir,"rvc","logs",exp_name) + trainset_dir = os.path.join(exp_path,"wavs") + os.makedirs(trainset_dir, exist_ok=True) + shutil.copy(wav_path,os.path.join(trainset_dir,os.path.basename(wav_path))) + n_p = int(np.ceil(cpu_count() / 1.5)) + gpus_rmvpe="%s-%s" % (gpus, gpus) + preprocess_dataset(trainset_dir,exp_dir=exp_name,sr=sr,n_p=n_p) + + # step 2 + extract_f0_feature(gpus,n_p,f0method=f0_method,if_f0=if_f0_3,exp_dir=exp_name,version19=version,gpus_rmvpe=gpus_rmvpe) + + path_str = "" if version == "v1" else "_v2" + f0_str = "f0" if if_f0_3 else "" + ''' + pretrained_G = os.path.join(now_dir,"rvc","assets",f"pretrained{path_str}",f"{f0_str}G{sr}.pth") + pretrained_D = os.path.join(now_dir,"rvc","assets",f"pretrained{path_str}",f"{f0_str}D{sr}.pth") + shutil.copy(pretrained_G,os.path.join(exp_path,"%sG%s.pth" % (f0_str, sr))) + shutil.copy(pretrained_D,os.path.join(exp_path,"%sD%s.pth" % (f0_str, sr))) + ''' + # step 3 "assets/pretrained%s/%sD%s.pth" % (path_str, f0_str, sr) + train1key(exp_dir1=exp_name,sr2=sr,if_f0_3=if_f0_3,trainset_dir4=trainset_dir,spk_id5=speaker_id, + np7=n_p,f0method8=f0_method,save_epoch10=save_epoch,total_epoch11=total_epoch, + batch_size12=batch_size,if_save_latest13=if_save_latest, + pretrained_G14="assets/pretrained%s/%sG%s.pth" % (path_str, f0_str, sr), + pretrained_D15="assets/pretrained%s/%sD%s.pth" % (path_str, f0_str, sr), + gpus16=gpus,if_cache_gpu17=if_cache_gpu,if_save_every_weights18=if_save_every_weights, + version19=version,gpus_rmvpe=gpus_rmvpe) + weight_path = os.path.join(now_dir,"rvc","assets","weights",f"{exp_name}.pth") + shutil.copy(weight_path, os.path.join(rvc_out_path,f"{exp_name}.pth")) + return {"ui": {"train":[rvc_out_path]}} + + +class PreViewAudio: + @classmethod + def INPUT_TYPES(s): + return {"required": + {"audio": ("AUDIO",),} + } + + CATEGORY = "AIFSH_RVC" + DESCRIPTION = "hello world!" + + RETURN_TYPES = () + + OUTPUT_NODE = True + + FUNCTION = "load_audio" + + def load_audio(self, audio): + audio_name = os.path.basename(audio) + tmp_path = os.path.dirname(audio) + audio_root = os.path.basename(tmp_path) + return {"ui": {"audio":[audio_name,audio_root]}} + +class LoadAudio: + @classmethod + def INPUT_TYPES(s): + files = [f for f in os.listdir(input_path) if os.path.isfile(os.path.join(input_path, f)) and f.split('.')[-1] in ["wav", "mp3","WAV","flac","m4a"]] + return {"required": + {"audio": (sorted(files),)}, + } + + CATEGORY = "AIFSH_RVC" + + RETURN_TYPES = ("AUDIO",) + FUNCTION = "load_audio" + + def load_audio(self, audio): + audio_path = folder_paths.get_annotated_filepath(audio) + return (audio_path,) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..845b29e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,47 @@ +joblib>=1.1.0 +numba +numpy==1.23.5 +scipy +librosa==0.9.1 +llvmlite +fairseq +faiss-cpu +Cython +pydub>=0.25.1 +soundfile>=0.12.1 +ffmpeg-python>=0.2.0 +tensorboardX +Jinja2>=3.1.2 +json5 +Markdown +matplotlib>=3.7.0 +matplotlib-inline>=0.1.3 +praat-parselmouth>=0.4.2 +Pillow>=9.1.1 +resampy>=0.4.2 +scikit-learn +tensorboard +tqdm>=4.63.1 +tornado>=6.1 +Werkzeug>=2.2.3 +uc-micro-py>=1.0.1 +sympy>=1.11.1 +tabulate>=0.8.10 +PyYAML>=6.0 +pyasn1>=0.4.8 +pyasn1-modules>=0.2.8 +fsspec>=2022.11.0 +absl-py>=1.2.0 +audioread +uvicorn>=0.21.1 +colorama>=0.4.5 +pyworld==0.3.2 +httpx +onnxruntime; sys_platform == 'darwin' +onnxruntime-gpu; sys_platform != 'darwin' +torchcrepe==0.0.20 +fastapi +torchfcpe +ffmpy==0.3.1 +python-dotenv>=1.0.0 +av diff --git a/rvc/__init__.py b/rvc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rvc/__pycache__/__init__.cpython-310.pyc b/rvc/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d39878ccdfef22df0fa6eaf3a966a6bed1ad473 GIT binary patch literal 149 zcmd1j<>g`k0+z3OX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6v4KciB&B)34{ zIX^e8GSpK)xwN<>KQ}%vKP9yoCa4<}=B!^-maHEipP83g5+AQuPPx# literal 0 HcmV?d00001 diff --git a/rvc/__pycache__/train.cpython-310.pyc b/rvc/__pycache__/train.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dca1009a6b7619065105eb4f03b106c74729857e GIT binary patch literal 11457 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%0A_J@Vm6E~0T{R8G~wT;MsHBCpMoDy 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 literal 0 HcmV?d00001 diff --git a/rvc/configs/config.json b/rvc/configs/config.json new file mode 100644 index 0000000..e79bd50 --- /dev/null +++ b/rvc/configs/config.json @@ -0,0 +1 @@ +{"pth_path": "assets/weights/kikiV1.pth", "index_path": "logs/kikiV1.index", "sg_hostapi": "MME", "sg_wasapi_exclusive": false, "sg_input_device": "VoiceMeeter Output (VB-Audio Vo", "sg_output_device": "VoiceMeeter Input (VB-Audio Voi", "sr_type": "sr_device", "threhold": -60.0, "pitch": 12.0, "formant": 0.0, "rms_mix_rate": 0.5, "index_rate": 0.0, "block_time": 0.15, "crossfade_length": 0.08, "extra_time": 2.0, "n_cpu": 4.0, "use_jit": false, "use_pv": false, "f0method": "fcpe"} \ No newline at end of file diff --git a/rvc/configs/config.py b/rvc/configs/config.py new file mode 100644 index 0000000..fae8085 --- /dev/null +++ b/rvc/configs/config.py @@ -0,0 +1,264 @@ +import argparse +import os +import sys +import json +import shutil +from multiprocessing import cpu_count + +import torch +now_dir = os.path.dirname(os.path.abspath(__file__)) +try: + import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import + + if torch.xpu.is_available(): + from infer.modules.ipex import ipex_init + + ipex_init() +except Exception: # pylint: disable=broad-exception-caught + pass +import logging + +logger = logging.getLogger(__name__) + + +version_config_list = [ + "v1/32k.json", + "v1/40k.json", + "v1/48k.json", + "v2/48k.json", + "v2/32k.json", +] + + +def singleton_variable(func): + def wrapper(*args, **kwargs): + if not wrapper.instance: + wrapper.instance = func(*args, **kwargs) + return wrapper.instance + + wrapper.instance = None + return wrapper + + +@singleton_variable +class Config: + def __init__(self): + self.device = "cuda:0" + self.is_half = True + self.use_jit = False + self.n_cpu = 0 + self.gpu_name = None + self.json_config = self.load_config_json() + self.gpu_mem = None + ( + self.python_cmd, + self.listen_port, + self.iscolab, + self.noparallel, + self.noautoopen, + self.dml, + self.nocheck, + self.update, + ) = self.arg_parse() + self.instead = "" + self.preprocess_per = 3.7 + self.x_pad, self.x_query, self.x_center, self.x_max = self.device_config() + + @staticmethod + def load_config_json() -> dict: + d = {} + for config_file in version_config_list: + p = f"{now_dir}/inuse/{config_file}" + if not os.path.exists(p): + shutil.copy(f"{now_dir}/{config_file}", p) + with open(f"{now_dir}/inuse/{config_file}", "r") as f: + d[config_file] = json.load(f) + return d + + @staticmethod + def arg_parse() -> tuple: + exe = sys.executable or "python" + parser = argparse.ArgumentParser() + parser.add_argument("--port", type=int, default=7865, help="Listen port") + parser.add_argument("--pycmd", type=str, default=exe, help="Python command") + parser.add_argument("--colab", action="store_true", help="Launch in colab") + parser.add_argument( + "--noparallel", action="store_true", help="Disable parallel processing" + ) + parser.add_argument( + "--noautoopen", + action="store_true", + help="Do not open in browser automatically", + ) + parser.add_argument( + "--dml", + action="store_true", + help="torch_dml", + ) + parser.add_argument( + "--nocheck", action="store_true", help="Run without checking assets" + ) + parser.add_argument( + "--update", action="store_true", help="Update to latest assets" + ) + cmd_opts = parser.parse_args() + + cmd_opts.port = cmd_opts.port if 0 <= cmd_opts.port <= 65535 else 7865 + + return ( + cmd_opts.pycmd, + cmd_opts.port, + cmd_opts.colab, + cmd_opts.noparallel, + cmd_opts.noautoopen, + cmd_opts.dml, + cmd_opts.nocheck, + cmd_opts.update, + ) + + # has_mps is only available in nightly pytorch (for now) and MasOS 12.3+. + # check `getattr` and try it for compatibility + @staticmethod + def has_mps() -> bool: + if not torch.backends.mps.is_available(): + return False + try: + torch.zeros(1).to(torch.device("mps")) + return True + except Exception: + return False + + @staticmethod + def has_xpu() -> bool: + if hasattr(torch, "xpu") and torch.xpu.is_available(): + return True + else: + return False + + def use_fp32_config(self): + for config_file in version_config_list: + self.json_config[config_file]["train"]["fp16_run"] = False + with open(f"{now_dir}/inuse/{config_file}", "r") as f: + strr = f.read().replace("true", "false") + with open(f"{now_dir}/inuse/{config_file}", "w") as f: + f.write(strr) + logger.info("overwrite " + config_file) + self.preprocess_per = 3.0 + logger.info("overwrite preprocess_per to %d" % (self.preprocess_per)) + + def device_config(self) -> tuple: + if torch.cuda.is_available(): + if self.has_xpu(): + self.device = self.instead = "xpu:0" + self.is_half = True + i_device = int(self.device.split(":")[-1]) + self.gpu_name = torch.cuda.get_device_name(i_device) + if ( + ("16" in self.gpu_name and "V100" not in self.gpu_name.upper()) + or "P40" in self.gpu_name.upper() + or "P10" in self.gpu_name.upper() + or "1060" in self.gpu_name + or "1070" in self.gpu_name + or "1080" in self.gpu_name + ): + logger.info("Found GPU %s, force to fp32", self.gpu_name) + self.is_half = False + self.use_fp32_config() + else: + logger.info("Found GPU %s", self.gpu_name) + self.gpu_mem = int( + torch.cuda.get_device_properties(i_device).total_memory + / 1024 + / 1024 + / 1024 + + 0.4 + ) + if self.gpu_mem <= 4: + self.preprocess_per = 3.0 + elif self.has_mps(): + logger.info("No supported Nvidia GPU found") + self.device = self.instead = "mps" + self.is_half = False + self.use_fp32_config() + else: + logger.info("No supported Nvidia GPU found") + self.device = self.instead = "cpu" + self.is_half = False + self.use_fp32_config() + + if self.n_cpu == 0: + self.n_cpu = cpu_count() + + if self.is_half: + # 6G显存配置 + x_pad = 3 + x_query = 10 + x_center = 60 + x_max = 65 + else: + # 5G显存配置 + x_pad = 1 + x_query = 6 + x_center = 38 + x_max = 41 + + if self.gpu_mem is not None and self.gpu_mem <= 4: + x_pad = 1 + x_query = 5 + x_center = 30 + x_max = 32 + if self.dml: + logger.info("Use DirectML instead") + if ( + os.path.exists( + "runtime\Lib\site-packages\onnxruntime\capi\DirectML.dll" + ) + == False + ): + try: + os.rename( + "runtime\Lib\site-packages\onnxruntime", + "runtime\Lib\site-packages\onnxruntime-cuda", + ) + except: + pass + try: + os.rename( + "runtime\Lib\site-packages\onnxruntime-dml", + "runtime\Lib\site-packages\onnxruntime", + ) + except: + pass + # if self.device != "cpu": + import torch_directml + + self.device = torch_directml.device(torch_directml.default_device()) + self.is_half = False + else: + if self.instead: + logger.info(f"Use {self.instead} instead") + if ( + os.path.exists( + "runtime\Lib\site-packages\onnxruntime\capi\onnxruntime_providers_cuda.dll" + ) + == False + ): + try: + os.rename( + "runtime\Lib\site-packages\onnxruntime", + "runtime\Lib\site-packages\onnxruntime-dml", + ) + except: + pass + try: + os.rename( + "runtime\Lib\site-packages\onnxruntime-cuda", + "runtime\Lib\site-packages\onnxruntime", + ) + except: + pass + logger.info( + "Half-precision floating-point: %s, device: %s" + % (self.is_half, self.device) + ) + return x_pad, x_query, x_center, x_max diff --git a/rvc/configs/inuse/.gitignore b/rvc/configs/inuse/.gitignore new file mode 100644 index 0000000..4194236 --- /dev/null +++ b/rvc/configs/inuse/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!v1 +!v2 diff --git a/rvc/configs/inuse/v1/.gitignore b/rvc/configs/inuse/v1/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/rvc/configs/inuse/v1/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/rvc/configs/inuse/v2/.gitignore b/rvc/configs/inuse/v2/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/rvc/configs/inuse/v2/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/rvc/configs/v1/32k.json b/rvc/configs/v1/32k.json new file mode 100644 index 0000000..d5f16d6 --- /dev/null +++ b/rvc/configs/v1/32k.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 12800, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 32000, + "filter_length": 1024, + "hop_length": 320, + "win_length": 1024, + "n_mel_channels": 80, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,4,2,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [16,16,4,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/rvc/configs/v1/40k.json b/rvc/configs/v1/40k.json new file mode 100644 index 0000000..4ffc87b --- /dev/null +++ b/rvc/configs/v1/40k.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 12800, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 40000, + "filter_length": 2048, + "hop_length": 400, + "win_length": 2048, + "n_mel_channels": 125, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,10,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [16,16,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/rvc/configs/v1/48k.json b/rvc/configs/v1/48k.json new file mode 100644 index 0000000..2d0e05b --- /dev/null +++ b/rvc/configs/v1/48k.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 11520, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 48000, + "filter_length": 2048, + "hop_length": 480, + "win_length": 2048, + "n_mel_channels": 128, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,6,2,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [16,16,4,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/rvc/configs/v2/32k.json b/rvc/configs/v2/32k.json new file mode 100644 index 0000000..70e534f --- /dev/null +++ b/rvc/configs/v2/32k.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 12800, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 32000, + "filter_length": 1024, + "hop_length": 320, + "win_length": 1024, + "n_mel_channels": 80, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [10,8,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [20,16,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/rvc/configs/v2/48k.json b/rvc/configs/v2/48k.json new file mode 100644 index 0000000..75f770c --- /dev/null +++ b/rvc/configs/v2/48k.json @@ -0,0 +1,46 @@ +{ + "train": { + "log_interval": 200, + "seed": 1234, + "epochs": 20000, + "learning_rate": 1e-4, + "betas": [0.8, 0.99], + "eps": 1e-9, + "batch_size": 4, + "fp16_run": true, + "lr_decay": 0.999875, + "segment_size": 17280, + "init_lr_ratio": 1, + "warmup_epochs": 0, + "c_mel": 45, + "c_kl": 1.0 + }, + "data": { + "max_wav_value": 32768.0, + "sampling_rate": 48000, + "filter_length": 2048, + "hop_length": 480, + "win_length": 2048, + "n_mel_channels": 128, + "mel_fmin": 0.0, + "mel_fmax": null + }, + "model": { + "inter_channels": 192, + "hidden_channels": 192, + "filter_channels": 768, + "n_heads": 2, + "n_layers": 6, + "kernel_size": 3, + "p_dropout": 0, + "resblock": "1", + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + "upsample_rates": [12,10,2,2], + "upsample_initial_channel": 512, + "upsample_kernel_sizes": [24,20,4,4], + "use_spectral_norm": false, + "gin_channels": 256, + "spk_embed_dim": 109 + } +} diff --git a/rvc/i18n/__pycache__/i18n.cpython-310.pyc b/rvc/i18n/__pycache__/i18n.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6799f8ddd8d38e59163023a020fa88dae8475498 GIT binary patch literal 1286 zcmZuwO>Yx15cPPyn{AYSP`+9~&=b-g+K3w;`)Sv`SCy z74ZWQQgh@l`O1mE&;u~zZGZw^c|0C_=Z)X9i`m&a!*)2o)_oc<_LU}owh$-xu-k`d z6jP9~b~Qp9{G7MB<~zZW@HW)Z1SZg~DXxM;){Y-BB~*CGRCvJKGYU3Y5`Dv5tknS; zP8wpf82dVQ`xs5hO8A6$V}_Q07Al}TAA*l_`%vd_v6`eHG&Es%w#}>H*vloua?&{41T&(j>q563*5l$3Cox56IypyVL zoba1PR-`HkT%4tOZE?0!TnKLv^pIBn>O+Z+pF;aoiQkQ%S^pw zM=(J>mKmu)s+S?^gKD4^pTz|1k_z`>jJnCFtc~G-yjU@>L&BZ#Xoj>m1Y@Xh7wTMY zZ6-4=s&sM-JzXl@O-EUI65)k)VwjfOF4FIjacgSyJ6&JTh->_!ZjcTM$?B|It&=of zA2Ua2v6TJ1UrI@FyMQK!2snrzOMyUd4))GWiCLTO{f8id|9OHa=a}svHF#{8;FbFV zt%RE;jJp62g8s7iU+dkf3}4W+7QC35loxS_B}V>2uU1bz79x*!dUPUyp2-foNF?*t z-rSbeD^E_Y>g|;TJP9~Z5&ywAdT6A)go_@(e){y)s=18INkr7;=|D^A>QW90HOk0u zNcm=zX4Q(JOy(+?Yh-?t|LfF-IH!AU>CMNi4spE^_-Il$6f=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": "If >=3: apply median filtering to the harvested pitch results. The value represents the filter radius and can reduce breathiness.", + "A模型权重": "Weight (w) for Model A:", + "A模型路径": "Path to Model A:", + "B模型路径": "Path to Model B:", + "E:\\语音音频+标注\\米津玄师\\src": "C:\\Users\\Desktop\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0 curve file (optional). One pitch per line. Replaces the default F0 and pitch modulation:", + "Index Rate": "Index Rate", + "Onnx导出": "Export Onnx", + "Onnx输出路径": "Onnx Export Path:", + "RVC模型路径": "RVC Model Path:", + "ckpt处理": "ckpt Processing", + "harvest进程数": "Number of CPU processes used for harvest pitch algorithm", + "index文件路径不可包含中文": "index文件路径不可包含中文", + "pth文件路径不可包含中文": "pth文件路径不可包含中文", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "Enter the GPU index(es) separated by '-', e.g., 0-0-1 to use 2 processes in GPU0 and 1 process in GPU1", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Step 1: Fill in the experimental configuration. Experimental data is stored in the 'logs' folder, with each experiment having a separate folder. Manually enter the experiment name path, which contains the experimental configuration, logs, and trained model files.", + "step1:正在处理数据": "Step 1: Processing data", + "step2:正在提取音高&正在提取特征": "step2:Pitch extraction & feature extraction", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Step 2a: Automatically traverse all files in the training folder that can be decoded into audio and perform slice normalization. Generates 2 wav folders in the experiment directory. Currently, only single-singer/speaker training is supported.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Step 2b: Use CPU to extract pitch (if the model has pitch), use GPU to extract features (select GPU index):", + "step3: 填写训练设置, 开始训练模型和索引": "Step 3: Fill in the training settings and start training the model and index", + "step3a:正在训练模型": "Step 3a: Model training started", + "一键训练": "One-click training", + "也可批量输入音频文件, 二选一, 优先读文件夹": "Multiple audio files can also be imported. If a folder path exists, this input is ignored.", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Batch processing for vocal accompaniment separation using the UVR5 model.
Example of a valid folder path format: D:\\path\\to\\input\\folder (copy it from the file manager address bar).
The model is divided into three categories:
1. Preserve vocals: Choose this option for audio without harmonies. It preserves vocals better than HP5. It includes two built-in models: HP2 and HP3. HP3 may slightly leak accompaniment but preserves vocals slightly better than HP2.
2. Preserve main vocals only: Choose this option for audio with harmonies. It may weaken the main vocals. It includes one built-in model: HP5.
3. De-reverb and de-delay models (by FoxJoy):
  (1) MDX-Net: The best choice for stereo reverb removal but cannot remove mono reverb;
 (234) DeEcho: Removes delay effects. Aggressive mode removes more thoroughly than Normal mode. DeReverb additionally removes reverb and can remove mono reverb, but not very effectively for heavily reverberated high-frequency content.
De-reverb/de-delay notes:
1. The processing time for the DeEcho-DeReverb model is approximately twice as long as the other two DeEcho models.
2. The MDX-Net-Dereverb model is quite slow.
3. The recommended cleanest configuration is to apply MDX-Net first and then DeEcho-Aggressive.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "Enter the GPU index(es) separated by '-', e.g., 0-1-2 to use GPU 0, 1, and 2:", + "伴奏人声分离&去混响&去回声": "Vocals/Accompaniment Separation & Reverberation Removal", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Save name:", + "保存的文件名, 默认空为和源文件同名": "Save file name (default: same as the source file):", + "保存的模型名不带后缀": "Saved model name (without extension):", + "保存频率save_every_epoch": "Save frequency (save_every_epoch):", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Protect voiceless consonants and breath sounds to prevent artifacts such as tearing in electronic music. Set to 0.5 to disable. Decrease the value to increase protection, but it may reduce indexing accuracy:", + "修改": "Modify", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Modify model information (only supported for small model files extracted from the 'weights' folder)", + "停止音频转换": "Stop audio conversion", + "全流程结束!": "All processes have been completed!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Refresh voice list and index path", + "加载模型": "Load model", + "加载预训练底模D路径": "Load pre-trained base model D path:", + "加载预训练底模G路径": "Load pre-trained base model G path:", + "单次推理": "Single Inference", + "卸载音色省显存": "Unload voice to save GPU memory:", + "变调(整数, 半音数量, 升八度12降八度-12)": "Transpose (integer, number of semitones, raise by an octave: 12, lower by an octave: -12):", + "后处理重采样至最终采样率,0为不进行重采样": "Resample the output audio in post-processing to the final sample rate. Set to 0 for no resampling:", + "否": "No", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Response threshold", + "响度因子": "loudness factor", + "处理数据": "Process data", + "导出Onnx模型": "Export Onnx Model", + "导出文件格式": "Export file format", + "常见问题解答": "FAQ (Frequently Asked Questions)", + "常规设置": "General settings", + "开始音频转换": "Start audio conversion", + "很遗憾您这没有能用的显卡来支持您训练": "Unfortunately, there is no compatible GPU available to support your training.", + "性能设置": "Performance settings", + "总训练轮数total_epoch": "Total training epochs (total_epoch):", + "批量推理": "Batch Inference", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Batch conversion. Enter the folder containing the audio files to be converted or upload multiple audio files. The converted audio will be output in the specified folder (default: 'opt').", + "指定输出主人声文件夹": "Specify the output folder for vocals:", + "指定输出文件夹": "Specify output folder:", + "指定输出非主人声文件夹": "Specify the output folder for accompaniment:", + "推理时间(ms):": "Inference time (ms):", + "推理音色": "Inferencing voice:", + "提取": "Extract", + "提取音高和处理数据使用的CPU进程数": "Number of CPU processes used for pitch extraction and data processing:", + "是": "Yes", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Save only the latest '.ckpt' file to save disk space:", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Save a small final model to the 'weights' folder at each save point:", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Cache all training sets to GPU memory. Caching small datasets (less than 10 minutes) can speed up training, but caching large datasets will consume a lot of GPU memory and may not provide much speed improvement:", + "显卡信息": "GPU Information", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "This software is open source under the MIT license. The author does not have any control over the software. Users who use the software and distribute the sounds exported by the software are solely responsible.
If you do not agree with this clause, you cannot use or reference any codes and files within the software package. See the root directory Agreement-LICENSE.txt for details.", + "查看": "View", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "View model information (only supported for small model files extracted from the 'weights' folder)", + "检索特征占比": "Search feature ratio (controls accent strength, too high has artifacting):", + "模型": "Model", + "模型推理": "Model Inference", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Model extraction (enter the path of the large file model under the 'logs' folder). This is useful if you want to stop training halfway and manually extract and save a small model file, or if you want to test an intermediate model:", + "模型是否带音高指导": "Whether the model has pitch guidance:", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Whether the model has pitch guidance (required for singing, optional for speech):", + "模型是否带音高指导,1是0否": "Whether the model has pitch guidance (1: yes, 0: no):", + "模型版本型号": "Model architecture version:", + "模型融合, 可用于测试音色融合": "Model fusion, can be used to test timbre fusion", + "模型路径": "Path to Model:", + "每张显卡的batch_size": "Batch size per GPU:", + "淡入淡出长度": "Fade length", + "版本": "Version", + "特征提取": "Feature extraction", + "特征检索库文件路径,为空则使用下拉的选择结果": "Path to the feature index file. Leave blank to use the selected result from the dropdown:", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Recommended +12 key for male to female conversion, and -12 key for female to male conversion. If the sound range goes too far and the voice is distorted, you can also adjust it to the appropriate range by yourself.", + "目标采样率": "Target sample rate:", + "算法延迟(ms):": "Algorithmic delays(ms):", + "自动检测index路径,下拉式选择(dropdown)": "Auto-detect index path and select from the dropdown:", + "融合": "Fusion", + "要改的模型信息": "Model information to be modified:", + "要置入的模型信息": "Model information to be placed:", + "训练": "Train", + "训练模型": "Train model", + "训练特征索引": "Train feature index", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Training complete. You can check the training logs in the console or the 'train.log' file under the experiment folder.", + "设备类型": "设备类型", + "请指定说话人id": "Please specify the speaker/singer ID:", + "请选择index文件": "Please choose the .index file", + "请选择pth文件": "Please choose the .pth file", + "请选择说话人id": "Select Speaker/Singer ID:", + "转换": "Convert", + "输入实验名": "Enter the experiment name:", + "输入待处理音频文件夹路径": "Enter the path of the audio folder to be processed:", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "Enter the path of the audio folder to be processed (copy it from the address bar of the file manager):", + "输入待处理音频文件路径(默认是正确格式示例)": "Enter the path of the audio file to be processed (default is the correct format example):", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "Adjust the volume envelope scaling. Closer to 0, the more it mimicks the volume of the original vocals. Can help mask noise and make volume sound more natural when set relatively low. Closer to 1 will be more of a consistently loud volume:", + "输入监听": "Input voice monitor", + "输入训练文件夹路径": "Enter the path of the training folder:", + "输入设备": "Input device", + "输入降噪": "Input noise reduction", + "输出信息": "Output information", + "输出变声": "Output converted voice", + "输出设备": "Output device", + "输出降噪": "Output noise reduction", + "输出音频(右下角三个点,点了可以下载)": "Export audio (click on the three dots in the lower right corner to download)", + "选择.index文件": "Select the .index file", + "选择.pth文件": "Select the .pth file", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Select the pitch extraction algorithm ('pm': faster extraction but lower-quality speech; 'harvest': better bass but extremely slow; 'crepe': better quality but GPU intensive), 'rmvpe': best quality, and little GPU requirement", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "Select the pitch extraction algorithm: when extracting singing, you can use 'pm' to speed up. For high-quality speech with fast performance, but worse CPU usage, you can use 'dio'. 'harvest' results in better quality but is slower. 'rmvpe' has the best results and consumes less CPU/GPU", + "采样率:": "采样率:", + "采样长度": "Sample length", + "重载设备列表": "Reload device list", + "音调设置": "Pitch settings", + "音频设备": "Audio device", + "音高算法": "pitch detection algorithm", + "额外推理时长": "Extra inference time" +} diff --git a/rvc/i18n/locale/es_ES.json b/rvc/i18n/locale/es_ES.json new file mode 100644 index 0000000..99961ec --- /dev/null +++ b/rvc/i18n/locale/es_ES.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": "Si es >=3, entonces use el resultado del reconocimiento de tono de 'harvest' con filtro de mediana, el valor es el radio del filtro, su uso puede debilitar el sonido sordo", + "A模型权重": "Un peso modelo para el modelo A.", + "A模型路径": "Modelo A ruta.", + "B模型路径": "Modelo B ruta.", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "Archivo de curva F0, opcional, un tono por línea, en lugar de F0 predeterminado y cambio de tono", + "Index Rate": "Tasa de índice", + "Onnx导出": "Exportar Onnx", + "Onnx输出路径": "Ruta de salida Onnx", + "RVC模型路径": "Ruta del modelo RVC", + "ckpt处理": "Procesamiento de recibos", + "harvest进程数": "Número de procesos", + "index文件路径不可包含中文": "La ruta del archivo .index no debe contener caracteres chinos.", + "pth文件路径不可包含中文": "La ruta del archivo .pth no debe contener caracteres chinos.", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "Separe los números de identificación de la GPU con '-' al ingresarlos. Por ejemplo, '0-1-2' significa usar GPU 0, GPU 1 y GPU 2.", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Paso 1: Complete la configuración del experimento. Los datos del experimento se almacenan en el directorio 'logs', con cada experimento en una carpeta separada. La ruta del nombre del experimento debe ingresarse manualmente y debe contener la configuración del experimento, los registros y los archivos del modelo entrenado.", + "step1:正在处理数据": "Paso 1: Procesando datos", + "step2:正在提取音高&正在提取特征": "Paso 2: Extracción del tono y extracción de características", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Paso 2a: Recorra automáticamente la carpeta de capacitación y corte y normalice todos los archivos de audio que se pueden decodificar en audio. Se generarán dos carpetas 'wav' en el directorio del experimento. Actualmente, solo se admite la capacitación de una sola persona.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Paso 2b: Use la CPU para extraer el tono (si el modelo tiene guía de tono) y la GPU para extraer características (seleccione el número de tarjeta).", + "step3: 填写训练设置, 开始训练模型和索引": "Paso 3: Complete la configuración de entrenamiento y comience a entrenar el modelo y el índice.", + "step3a:正在训练模型": "Paso 3a: Entrenando el modelo", + "一键训练": "Entrenamiento con un clic", + "也可批量输入音频文件, 二选一, 优先读文件夹": "También se pueden importar varios archivos de audio. Si existe una ruta de carpeta, esta entrada se ignora.", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Procesamiento por lotes para la separación de acompañamiento vocal utilizando el modelo UVR5.
Ejemplo de formato de ruta de carpeta válido: D:\\ruta\\a\\la\\carpeta\\de\\entrada (copiar desde la barra de direcciones del administrador de archivos).
El modelo se divide en tres categorías:
1. Preservar voces: Elija esta opción para audio sin armonías. Preserva las voces mejor que HP5. Incluye dos modelos incorporados: HP2 y HP3. HP3 puede filtrar ligeramente el acompañamiento pero conserva las voces un poco mejor que HP2.
2. Preservar solo voces principales: Elija esta opción para audio con armonías. Puede debilitar las voces principales. Incluye un modelo incorporado: HP5.
3. Modelos de des-reverberación y des-retardo (por FoxJoy):
  (1) MDX-Net: La mejor opción para la eliminación de reverberación estéreo pero no puede eliminar la reverberación mono;
 (234) DeEcho: Elimina efectos de retardo. El modo Agresivo elimina más a fondo que el modo Normal. DeReverb adicionalmente elimina la reverberación y puede eliminar la reverberación mono, pero no muy efectivamente para contenido de alta frecuencia fuertemente reverberado.
Notas de des-reverberación/des-retardo:
1. El tiempo de procesamiento para el modelo DeEcho-DeReverb es aproximadamente el doble que los otros dos modelos DeEcho.
2. El modelo MDX-Net-Dereverb es bastante lento.
3. La configuración más limpia recomendada es aplicar primero MDX-Net y luego DeEcho-Agresivo.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "Separe los números de identificación de la GPU con '-' al ingresarlos. Por ejemplo, '0-1-2' significa usar GPU 0, GPU 1 y GPU 2.", + "伴奏人声分离&去混响&去回声": "Separación de voz acompañante & eliminación de reverberación & eco", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Guardar nombre", + "保存的文件名, 默认空为和源文件同名": "Nombre del archivo que se guardará, el valor predeterminado es el mismo que el nombre del archivo de origen", + "保存的模型名不带后缀": "Nombre del modelo guardado sin extensión.", + "保存频率save_every_epoch": "Frecuencia de guardado (save_every_epoch)", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Proteger las consonantes claras y la respiración, prevenir artefactos como la distorsión de sonido electrónico, 0.5 no está activado, reducir aumentará la protección pero puede reducir el efecto del índice", + "修改": "Modificar", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Modificar la información del modelo (solo admite archivos de modelos pequeños extraídos en la carpeta weights)", + "停止音频转换": "Detener la conversión de audio", + "全流程结束!": "¡Todo el proceso ha terminado!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Actualizar la lista de modelos e índice de rutas", + "加载模型": "Cargar modelo", + "加载预训练底模D路径": "Cargue la ruta del modelo D base pre-entrenada.", + "加载预训练底模G路径": "Cargue la ruta del modelo G base pre-entrenada.", + "单次推理": "单次推理", + "卸载音色省显存": "Descargue la voz para ahorrar memoria GPU", + "变调(整数, 半音数量, 升八度12降八度-12)": "Cambio de tono (entero, número de semitonos, subir una octava +12 o bajar una octava -12)", + "后处理重采样至最终采样率,0为不进行重采样": "Remuestreo posterior al proceso a la tasa de muestreo final, 0 significa no remuestrear", + "否": "No", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Umbral de respuesta", + "响度因子": "factor de sonoridad", + "处理数据": "Procesar datos", + "导出Onnx模型": "Exportar modelo Onnx", + "导出文件格式": "Formato de archivo de exportación", + "常见问题解答": "Preguntas frecuentes", + "常规设置": "Configuración general", + "开始音频转换": "Iniciar conversión de audio", + "很遗憾您这没有能用的显卡来支持您训练": "Lamentablemente, no tiene una tarjeta gráfica adecuada para soportar su entrenamiento", + "性能设置": "Configuración de rendimiento", + "总训练轮数total_epoch": "Total de épocas de entrenamiento (total_epoch)", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Conversión por lotes, ingrese la carpeta que contiene los archivos de audio para convertir o cargue varios archivos de audio. El audio convertido se emitirá en la carpeta especificada (opción predeterminada).", + "指定输出主人声文件夹": "Especifique la carpeta de salida para la voz principal", + "指定输出文件夹": "Especificar carpeta de salida", + "指定输出非主人声文件夹": "Especifique la carpeta de salida para las voces no principales", + "推理时间(ms):": "Inferir tiempo (ms):", + "推理音色": "inferencia de voz", + "提取": "Extraer", + "提取音高和处理数据使用的CPU进程数": "Número de procesos de CPU utilizados para extraer el tono y procesar los datos", + "是": "Sí", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Guardar solo el archivo ckpt más reciente para ahorrar espacio en disco", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Guardar pequeño modelo final en la carpeta 'weights' en cada punto de guardado", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Si almacenar en caché todos los conjuntos de entrenamiento en la memoria de la GPU. Los conjuntos de datos pequeños (menos de 10 minutos) se pueden almacenar en caché para acelerar el entrenamiento, pero el almacenamiento en caché de conjuntos de datos grandes puede causar errores de memoria en la GPU y no aumenta la velocidad de manera significativa.", + "显卡信息": "información de la GPU", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "Este software es de código abierto bajo la licencia MIT, el autor no tiene ningún control sobre el software, y aquellos que usan el software y difunden los sonidos exportados por el software son los únicos responsables.
Si no está de acuerdo con esta cláusula , no puede utilizar ni citar ningún código ni archivo del paquete de software Consulte el directorio raíz Agreement-LICENSE.txt para obtener más información.", + "查看": "Ver", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "Ver información del modelo (solo aplicable a archivos de modelos pequeños extraídos de la carpeta 'pesos')", + "检索特征占比": "Proporción de función de búsqueda", + "模型": "Modelo", + "模型推理": "inferencia del modelo", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Extracción de modelo (ingrese la ruta de un archivo de modelo grande en la carpeta 'logs'), aplicable cuando desea extraer un archivo de modelo pequeño después de entrenar a mitad de camino y no se guardó automáticamente, o cuando desea probar un modelo intermedio", + "模型是否带音高指导": "Si el modelo tiene guía de tono.", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Si el modelo tiene guía de tono (necesaria para cantar, pero no para hablar)", + "模型是否带音高指导,1是0否": "Si el modelo tiene guía de tono, 1 para sí, 0 para no", + "模型版本型号": "Versión y modelo del modelo", + "模型融合, 可用于测试音色融合": "Fusión de modelos, se puede utilizar para fusionar diferentes voces", + "模型路径": "Ruta del modelo", + "每张显卡的batch_size": "Tamaño del lote (batch_size) por tarjeta gráfica", + "淡入淡出长度": "Duración del fundido de entrada/salida", + "版本": "Versión", + "特征提取": "Extracción de características", + "特征检索库文件路径,为空则使用下拉的选择结果": "Ruta del archivo de la biblioteca de características, si está vacío, se utilizará el resultado de la selección desplegable", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Tecla +12 recomendada para conversión de voz de hombre a mujer, tecla -12 para conversión de voz de mujer a hombre. Si el rango de tono es demasiado amplio y causa distorsión, ajústelo usted mismo a un rango adecuado.", + "目标采样率": "Tasa de muestreo objetivo", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "Detección automática de la ruta del índice, selección desplegable (dropdown)", + "融合": "Fusión", + "要改的模型信息": "Información del modelo a modificar", + "要置入的模型信息": "Información del modelo a colocar.", + "训练": "Entrenamiento", + "训练模型": "Entrenar Modelo", + "训练特征索引": "Índice de características", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Entrenamiento finalizado, puede ver el registro de entrenamiento en la consola o en el archivo train.log en la carpeta del experimento", + "设备类型": "设备类型", + "请指定说话人id": "ID del modelo", + "请选择index文件": "Seleccione el archivo .index", + "请选择pth文件": "Seleccione el archivo .pth", + "请选择说话人id": "Seleccione una identificación de altavoz", + "转换": "Conversión", + "输入实验名": "Ingrese el nombre del modelo", + "输入待处理音频文件夹路径": "Ingrese la ruta a la carpeta de audio que se procesará", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "Ingrese la ruta a la carpeta de audio que se procesará (simplemente cópiela desde la barra de direcciones del administrador de archivos)", + "输入待处理音频文件路径(默认是正确格式示例)": "Ingrese la ruta del archivo del audio que se procesará (el formato predeterminado es el ejemplo correcto)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "Proporción de fusión para reemplazar el sobre de volumen de entrada con el sobre de volumen de salida, cuanto más cerca de 1, más se utiliza el sobre de salida", + "输入监听": "输入监听", + "输入训练文件夹路径": "Introduzca la ruta de la carpeta de entrenamiento", + "输入设备": "Dispositivo de entrada", + "输入降噪": "Reducción de ruido de entrada", + "输出信息": "Información de salida", + "输出变声": "输出变声", + "输出设备": "Dispositivo de salida", + "输出降噪": "Reducción de ruido de salida", + "输出音频(右下角三个点,点了可以下载)": "Salida de audio (haga clic en los tres puntos en la esquina inferior derecha para descargar)", + "选择.index文件": "Seleccione el archivo .index", + "选择.pth文件": "Seleccione el archivo .pth", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "Seleccione el algoritmo de extracción de tono, las voces de entrada se pueden acelerar con pm, harvest tiene buenos graves pero es muy lento, crepe es bueno pero se come las GPUs", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Seleccione el algoritmo de extracción de tono, use 'pm' para acelerar la entrada de canto, 'harvest' es bueno para los graves pero extremadamente lento, 'crepe' tiene buenos resultados pero consume GPU", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "Seleccione el algoritmo de extracción de tono: la canción de entrada se puede acelerar con pm, la voz de alta calidad pero CPU pobre se puede acelerar con dio, harvest es mejor pero más lento, rmvpe es el mejor y se come ligeramente la CPU/GPU", + "采样率:": "采样率:", + "采样长度": "Longitud de muestreo", + "重载设备列表": "Actualizar lista de dispositivos", + "音调设置": "Ajuste de tono", + "音频设备": "Dispositivo de audio", + "音高算法": "Algoritmo de tono", + "额外推理时长": "Tiempo de inferencia adicional" +} diff --git a/rvc/i18n/locale/fr_FR.json b/rvc/i18n/locale/fr_FR.json new file mode 100644 index 0000000..7fb7710 --- /dev/null +++ b/rvc/i18n/locale/fr_FR.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": "Si >=3 : appliquer un filtrage médian aux résultats de la reconnaissance de la hauteur de récolte. La valeur représente le rayon du filtre et peut réduire la respiration.", + "A模型权重": "Poids (w) pour le modèle A :", + "A模型路径": "Chemin d'accès au modèle A :", + "B模型路径": "Chemin d'accès au modèle B :", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "Fichier de courbe F0 (facultatif). Une hauteur par ligne. Remplace la fréquence fondamentale par défaut et la modulation de la hauteur :", + "Index Rate": "Taux d'indexation", + "Onnx导出": "Exporter en ONNX", + "Onnx输出路径": "Chemin d'exportation ONNX :", + "RVC模型路径": "Chemin du modèle RVC :", + "ckpt处理": "Traitement des fichiers .ckpt", + "harvest进程数": "Nombre de processus CPU utilisés pour l'algorithme de reconnaissance de la hauteur (pitch) dans le cadre de la récolte (harvest).", + "index文件路径不可包含中文": "Le chemin du fichier d'index ne doit pas contenir de caractères chinois.", + "pth文件路径不可包含中文": "Le chemin du fichier .pth ne doit pas contenir de caractères chinois.", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "Configuration des numéros de carte RMVPE : séparez les index GPU par des tirets \"-\", par exemple, 0-0-1 pour utiliser 2 processus sur GPU0 et 1 processus sur GPU1.", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Étape 1 : Remplissez la configuration expérimentale. Les données expérimentales sont stockées dans le dossier 'logs', avec chaque expérience ayant un dossier distinct. Entrez manuellement le chemin du nom de l'expérience, qui contient la configuration expérimentale, les journaux et les fichiers de modèle entraînés.", + "step1:正在处理数据": "Étape 1 : Traitement des données en cours.", + "step2:正在提取音高&正在提取特征": "Étape 2 : Extraction de la hauteur et extraction des caractéristiques en cours.", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Étape 2a : Parcours automatique de tous les fichiers du dossier d'entraînement qui peuvent être décodés en fichiers audio et réalisation d'une normalisation par tranches. Génère 2 dossiers wav dans le répertoire de l'expérience. Actuellement, seule la formation avec un seul chanteur/locuteur est prise en charge.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Étape 2b : Utilisez le CPU pour extraire la hauteur (si le modèle le permet), utilisez le GPU pour extraire les caractéristiques (sélectionnez l'index du GPU) :", + "step3: 填写训练设置, 开始训练模型和索引": "Étape 3 : Remplissez les paramètres d'entraînement et démarrez l'entraînement du modèle ainsi que l'indexation.", + "step3a:正在训练模型": "Étape 3a : L'entraînement du modèle a commencé.", + "一键训练": "Entraînement en un clic", + "也可批量输入音频文件, 二选一, 优先读文件夹": "Il est également possible d'importer plusieurs fichiers audio. Si un chemin de dossier existe, cette entrée est ignorée.", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Traitement en lot pour la séparation de la voix et de l'accompagnement vocal à l'aide du modèle UVR5.
Exemple d'un format de chemin de dossier valide : D:\\chemin\\vers\\dossier\\d'entrée (copiez-le depuis la barre d'adresse du gestionnaire de fichiers).
Le modèle est divisé en trois catégories :
1. Préserver la voix : Choisissez cette option pour l'audio sans harmonies. Elle préserve la voix mieux que HP5. Il comprend deux modèles intégrés : HP2 et HP3. HP3 peut légèrement laisser passer l'accompagnement mais préserve légèrement mieux la voix que HP2.
2. Préserver uniquement la voix principale : Choisissez cette option pour l'audio avec harmonies. Cela peut affaiblir la voix principale. Il comprend un modèle intégré : HP5.
3. Modèles de suppression de la réverbération et du délai (par FoxJoy) :
  (1) MDX-Net : Le meilleur choix pour la suppression de la réverbération stéréo, mais ne peut pas supprimer la réverbération mono.
  (234) DeEcho : Supprime les effets de délai. Le mode Aggressive supprime plus efficacement que le mode Normal. DeReverb supprime également la réverbération et peut supprimer la réverbération mono, mais pas très efficacement pour les contenus à haute fréquence fortement réverbérés.
Notes sur la suppression de la réverbération et du délai :
1. Le temps de traitement pour le modèle DeEcho-DeReverb est environ deux fois plus long que pour les autres deux modèles DeEcho.
2. Le modèle MDX-Net-Dereverb est assez lent.
3. La configuration la plus propre recommandée est d'appliquer d'abord MDX-Net, puis DeEcho-Aggressive.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "Entrez le(s) index GPU séparé(s) par '-', par exemple, 0-1-2 pour utiliser les GPU 0, 1 et 2 :", + "伴奏人声分离&去混响&去回声": "Séparation des voix/accompagnement et suppression de la réverbération", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Nom de sauvegarde :", + "保存的文件名, 默认空为和源文件同名": "Nom du fichier de sauvegarde (par défaut : identique au nom du fichier source) :", + "保存的模型名不带后缀": "Nom du modèle enregistré (sans extension) :", + "保存频率save_every_epoch": "Fréquence de sauvegarde (save_every_epoch) :", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Protéger les consonnes sourdes et les bruits de respiration pour éviter les artefacts tels que le déchirement dans la musique électronique. Réglez à 0,5 pour désactiver. Diminuez la valeur pour renforcer la protection, mais cela peut réduire la précision de l'indexation :", + "修改": "Modifier", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Modifier les informations du modèle (uniquement pris en charge pour les petits fichiers de modèle extraits du dossier 'weights')", + "停止音频转换": "Arrêter la conversion audio", + "全流程结束!": "Toutes les étapes ont été terminées !", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Actualiser la liste des voix et le vers l'index.", + "加载模型": "Charger le modèle.", + "加载预训练底模D路径": "Charger le chemin du modèle de base pré-entraîné D :", + "加载预训练底模G路径": "Charger le chemin du modèle de base pré-entraîné G :", + "单次推理": "单次推理", + "卸载音色省显存": "Décharger la voix pour économiser la mémoire GPU.", + "变调(整数, 半音数量, 升八度12降八度-12)": "Transposer (entier, nombre de demi-tons, monter d'une octave : 12, descendre d'une octave : -12) :", + "后处理重采样至最终采样率,0为不进行重采样": "Rééchantillonner l'audio de sortie en post-traitement à la fréquence d'échantillonnage finale. Réglez sur 0 pour ne pas effectuer de rééchantillonnage :", + "否": "Non", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Seuil de réponse", + "响度因子": "Facteur de volume sonore", + "处理数据": "Traitement des données", + "导出Onnx模型": "Exporter le modèle au format ONNX.", + "导出文件格式": "Format de fichier d'exportation", + "常见问题解答": "FAQ (Foire Aux Questions)", + "常规设置": "Paramètres généraux", + "开始音频转换": "Démarrer la conversion audio.", + "很遗憾您这没有能用的显卡来支持您训练": "Malheureusement, il n'y a pas de GPU compatible disponible pour prendre en charge votre entrainement.", + "性能设置": "Paramètres de performance", + "总训练轮数total_epoch": "Nombre total d'époques d'entraînement (total_epoch) :", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Conversion en lot. Entrez le dossier contenant les fichiers audio à convertir ou téléchargez plusieurs fichiers audio. Les fichiers audio convertis seront enregistrés dans le dossier spécifié (par défaut : 'opt').", + "指定输出主人声文件夹": "Spécifiez le dossier de sortie pour les fichiers de voix :", + "指定输出文件夹": "Spécifiez le dossier de sortie :", + "指定输出非主人声文件夹": "Spécifiez le dossier de sortie pour l'accompagnement :", + "推理时间(ms):": "Temps d'inférence (ms) :", + "推理音色": "Voix pour l'inférence", + "提取": "Extraire", + "提取音高和处理数据使用的CPU进程数": "Nombre de processus CPU utilisés pour l'extraction de la hauteur et le traitement des données :", + "是": "Oui", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Enregistrer uniquement le dernier fichier '.ckpt' pour économiser de l'espace disque :", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Enregistrer un petit modèle final dans le dossier 'weights' à chaque point de sauvegarde :", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Mettre en cache tous les ensembles d'entrainement dans la mémoire GPU. Mettre en cache de petits ensembles de données (moins de 10 minutes) peut accélérer l'entrainement, mais mettre en cache de grands ensembles de données consommera beaucoup de mémoire GPU et peut ne pas apporter beaucoup d'amélioration de vitesse :", + "显卡信息": "Informations sur la carte graphique (GPU)", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "Ce logiciel est open source sous la licence MIT. L'auteur n'a aucun contrôle sur le logiciel. Les utilisateurs qui utilisent le logiciel et distribuent les sons exportés par le logiciel en sont entièrement responsables.
Si vous n'acceptez pas cette clause, vous ne pouvez pas utiliser ou faire référence à aucun code ni fichier contenu dans le package logiciel. Consultez le fichier Agreement-LICENSE.txt dans le répertoire racine pour plus de détails.", + "查看": "Voir", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "Afficher les informations sur le modèle (uniquement pour les petits fichiers de modèle extraits du dossier \"weights\")", + "检索特征占比": "Rapport de recherche de caractéristiques (contrôle l'intensité de l'accent, un rapport trop élevé provoque des artefacts) :", + "模型": "Modèle", + "模型推理": "Inférence du modèle", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Extraction du modèle (saisissez le chemin d'accès au modèle du grand fichier dans le dossier \"logs\"). Cette fonction est utile si vous souhaitez arrêter l'entrainement à mi-chemin et extraire et enregistrer manuellement un petit fichier de modèle, ou si vous souhaitez tester un modèle intermédiaire :", + "模型是否带音高指导": "Indique si le modèle dispose d'un guidage en hauteur :", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Indique si le modèle dispose d'un système de guidage de la hauteur (obligatoire pour le chant, facultatif pour la parole) :", + "模型是否带音高指导,1是0否": "Le modèle dispose-t-il d'un guide de hauteur (1 : oui, 0 : non) ?", + "模型版本型号": "Version de l'architecture du modèle :", + "模型融合, 可用于测试音色融合": "Fusion de modèles, peut être utilisée pour tester la fusion de timbres", + "模型路径": "Le chemin vers le modèle :", + "每张显卡的batch_size": "Taille du batch par GPU :", + "淡入淡出长度": "Longueur de la transition", + "版本": "Version", + "特征提取": "Extraction des caractéristiques", + "特征检索库文件路径,为空则使用下拉的选择结果": "Chemin d'accès au fichier d'index des caractéristiques. Laisser vide pour utiliser le résultat sélectionné dans la liste déroulante :", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Il est recommandé d'utiliser la clé +12 pour la conversion homme-femme et la clé -12 pour la conversion femme-homme. Si la plage sonore est trop large et que la voix est déformée, vous pouvez également l'ajuster vous-même à la plage appropriée.", + "目标采样率": "Taux d'échantillonnage cible :", + "算法延迟(ms):": "Délais algorithmiques (ms):", + "自动检测index路径,下拉式选择(dropdown)": "Détecter automatiquement le chemin d'accès à l'index et le sélectionner dans la liste déroulante :", + "融合": "Fusion", + "要改的模型信息": "Informations sur le modèle à modifier :", + "要置入的模型信息": "Informations sur le modèle à placer :", + "训练": "Entraîner", + "训练模型": "Entraîner le modèle", + "训练特征索引": "Entraîner l'index des caractéristiques", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Entraînement terminé. Vous pouvez consulter les rapports d'entraînement dans la console ou dans le fichier 'train.log' situé dans le dossier de l'expérience.", + "设备类型": "设备类型", + "请指定说话人id": "Veuillez spécifier l'ID de l'orateur ou du chanteur :", + "请选择index文件": "Veuillez sélectionner le fichier d'index", + "请选择pth文件": "Veuillez sélectionner le fichier pth", + "请选择说话人id": "Sélectionner l'ID de l'orateur ou du chanteur :", + "转换": "Convertir", + "输入实验名": "Saisissez le nom de l'expérience :", + "输入待处理音频文件夹路径": "Entrez le chemin du dossier audio à traiter :", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "Entrez le chemin du dossier audio à traiter (copiez-le depuis la barre d'adresse du gestionnaire de fichiers) :", + "输入待处理音频文件路径(默认是正确格式示例)": "Entrez le chemin d'accès du fichier audio à traiter (par défaut, l'exemple de format correct) :", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "Ajustez l'échelle de l'enveloppe de volume. Plus il est proche de 0, plus il imite le volume des voix originales. Cela peut aider à masquer les bruits et à rendre le volume plus naturel lorsqu'il est réglé relativement bas. Plus le volume est proche de 1, plus le volume sera fort et constant :", + "输入监听": "Moniteur vocal d'entrée", + "输入训练文件夹路径": "Indiquez le chemin d'accès au dossier d'entraînement :", + "输入设备": "Dispositif d'entrée", + "输入降噪": "Réduction du bruit d'entrée", + "输出信息": "Informations sur la sortie", + "输出变声": "Sortie voix convertie", + "输出设备": "Dispositif de sortie", + "输出降噪": "Réduction du bruit de sortie", + "输出音频(右下角三个点,点了可以下载)": "Exporter l'audio (cliquer sur les trois points dans le coin inférieur droit pour télécharger)", + "选择.index文件": "Sélectionner le fichier .index", + "选择.pth文件": "Sélectionner le fichier .pth", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "Sélection de l'algorithme d'extraction de la hauteur, les voix d'entrée peuvent être accélérées avec pm, harvest a de bonnes basses mais est très lent, crepe est bon mais consomme beaucoup de ressources GPU.", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Sélectionnez l'algorithme d'extraction de la hauteur de ton (\"pm\" : extraction plus rapide mais parole de moindre qualité ; \"harvest\" : meilleure basse mais extrêmement lente ; \"crepe\" : meilleure qualité mais utilisation intensive du GPU), \"rmvpe\" : meilleure qualité et peu d'utilisation du GPU.", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "Sélection de l'algorithme d'extraction de la hauteur : la chanson d'entrée peut être traitée plus rapidement par pm, avec une voix de haute qualité mais un CPU médiocre, par dio, harvest est meilleur mais plus lent, rmvpe est le meilleur, mais consomme légèrement le CPU/GPU.", + "采样率:": "采样率:", + "采样长度": "Longueur de l'échantillon", + "重载设备列表": "Recharger la liste des dispositifs", + "音调设置": "Réglages de la hauteur", + "音频设备": "Périphérique audio", + "音高算法": "algorithme de détection de la hauteur", + "额外推理时长": "Temps d'inférence supplémentaire" +} diff --git a/rvc/i18n/locale/it_IT.json b/rvc/i18n/locale/it_IT.json new file mode 100644 index 0000000..deb3ff4 --- /dev/null +++ b/rvc/i18n/locale/it_IT.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": "Se >=3: applica il filtro mediano ai risultati del pitch raccolto. ", + "A模型权重": "Peso (w) per il modello A:", + "A模型路径": "Percorso per il modello A:", + "B模型路径": "Percorso per il modello B:", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "File curva F0 (opzionale). ", + "Index Rate": "Tasso di indice", + "Onnx导出": "Esporta Onnx", + "Onnx输出路径": "Percorso di esportazione Onnx:", + "RVC模型路径": "Percorso modello RVC:", + "ckpt处理": "Elaborazione ckpt", + "harvest进程数": "harvest进程数", + "index文件路径不可包含中文": "index文件路径不可包含中文", + "pth文件路径不可包含中文": "pth è un'app per il futuro", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Passaggio 1: compilare la configurazione sperimentale. ", + "step1:正在处理数据": "Passaggio 1: elaborazione dei dati", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Passaggio 2a: attraversa automaticamente tutti i file nella cartella di addestramento che possono essere decodificati in audio ed esegui la normalizzazione delle sezioni. ", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Passaggio 2b: utilizzare la CPU per estrarre il tono (se il modello ha il tono), utilizzare la GPU per estrarre le caratteristiche (selezionare l'indice GPU):", + "step3: 填写训练设置, 开始训练模型和索引": "Passaggio 3: compilare le impostazioni di addestramento e avviare l'addestramento del modello e dell'indice", + "step3a:正在训练模型": "Passaggio 3a: è iniziato l'addestramento del modello", + "一键训练": "Addestramento con un clic", + "也可批量输入音频文件, 二选一, 优先读文件夹": "也可批量输入音频文件, 二选一, 优先读文件夹", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Elaborazione batch per la separazione dell'accompagnamento vocale utilizzando il modello UVR5.
Esempio di un formato di percorso di cartella valido: D:\\path\\to\\input\\folder (copialo dalla barra degli indirizzi del file manager).
Il modello è suddiviso in tre categorie:
1. Conserva la voce: scegli questa opzione per l'audio senza armonie.
2. Mantieni solo la voce principale: scegli questa opzione per l'audio con armonie.
3. Modelli di de-riverbero e de-delay (di FoxJoy):
  (1) MDX-Net: la scelta migliore per la rimozione del riverbero stereo ma non può rimuovere il riverbero mono;

Note di de-riverbero/de-delay:
1. Il tempo di elaborazione per il modello DeEcho-DeReverb è circa il doppio rispetto agli altri due modelli DeEcho.
2. Il modello MDX-Net-Dereverb è piuttosto lento.
3. La configurazione più pulita consigliata consiste nell'applicare prima MDX-Net e poi DeEcho-Aggressive.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "Inserisci gli indici GPU separati da '-', ad esempio 0-1-2 per utilizzare GPU 0, 1 e 2:", + "伴奏人声分离&去混响&去回声": "Separazione voce/accompagnamento", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Salva nome:", + "保存的文件名, 默认空为和源文件同名": "Salva il nome del file (predefinito: uguale al file di origine):", + "保存的模型名不带后缀": "Nome del modello salvato (senza estensione):", + "保存频率save_every_epoch": "Frequenza di salvataggio (save_every_epoch):", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Proteggi le consonanti senza voce e i suoni del respiro per evitare artefatti come il tearing nella musica elettronica. ", + "修改": "Modificare", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Modifica le informazioni sul modello (supportato solo per i file di modello di piccole dimensioni estratti dalla cartella 'weights')", + "停止音频转换": "Arresta la conversione audio", + "全流程结束!": "Tutti i processi sono stati completati!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Aggiorna l'elenco delle voci e il percorso dell'indice", + "加载模型": "Carica modello", + "加载预训练底模D路径": "Carica il percorso D del modello base pre-addestrato:", + "加载预训练底模G路径": "Carica il percorso G del modello base pre-addestrato:", + "单次推理": "单次推理", + "卸载音色省显存": "Scarica la voce per risparmiare memoria della GPU:", + "变调(整数, 半音数量, 升八度12降八度-12)": "Trasposizione (numero intero, numero di semitoni, alza di un'ottava: 12, abbassa di un'ottava: -12):", + "后处理重采样至最终采样率,0为不进行重采样": "Ricampiona l'audio di output in post-elaborazione alla frequenza di campionamento finale. ", + "否": "NO", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Soglia di risposta", + "响度因子": "fattore di sonorità", + "处理数据": "Processa dati", + "导出Onnx模型": "Esporta modello Onnx", + "导出文件格式": "Formato file di esportazione", + "常见问题解答": "FAQ (Domande frequenti)", + "常规设置": "Impostazioni generali", + "开始音频转换": "Avvia la conversione audio", + "很遗憾您这没有能用的显卡来支持您训练": "Sfortunatamente, non è disponibile alcuna GPU compatibile per supportare l'addestramento.", + "性能设置": "Impostazioni delle prestazioni", + "总训练轮数total_epoch": "Epoch totali di addestramento (total_epoch):", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Conversione massiva. Inserisci il percorso della cartella che contiene i file da convertire o carica più file audio. I file convertiti finiranno nella cartella specificata. (default: opt) ", + "指定输出主人声文件夹": "Specifica la cartella di output per le voci:", + "指定输出文件夹": "Specifica la cartella di output:", + "指定输出非主人声文件夹": "Specificare la cartella di output per l'accompagnamento:", + "推理时间(ms):": "Tempo di inferenza (ms):", + "推理音色": "Voce di inferenza:", + "提取": "Estrai", + "提取音高和处理数据使用的CPU进程数": "Numero di processi CPU utilizzati per l'estrazione del tono e l'elaborazione dei dati:", + "是": "SÌ", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Salva solo l'ultimo file '.ckpt' per risparmiare spazio su disco:", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Salva un piccolo modello finale nella cartella \"weights\" in ogni punto di salvataggio:", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Memorizza nella cache tutti i set di addestramento nella memoria della GPU. ", + "显卡信息": "Informazioni GPU", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "Questo software è open source con licenza MIT.
Se non si accetta questa clausola, non è possibile utilizzare o fare riferimento a codici e file all'interno del pacchetto software. Contratto-LICENZA.txt per dettagli.", + "查看": "Visualizzazione", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "Visualizza le informazioni sul modello (supportato solo per file di modello piccoli estratti dalla cartella 'weights')", + "检索特征占比": "Rapporto funzionalità di ricerca (controlla la forza dell'accento, troppo alto ha artefatti):", + "模型": "Modello", + "模型推理": "Inferenza del modello", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Estrazione del modello (inserire il percorso del modello di file di grandi dimensioni nella cartella \"logs\"). ", + "模型是否带音高指导": "Se il modello ha una guida del tono:", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Se il modello ha una guida del tono (necessario per il canto, facoltativo per il parlato):", + "模型是否带音高指导,1是0否": "Se il modello ha una guida del tono (1: sì, 0: no):", + "模型版本型号": "Versione dell'architettura del modello:", + "模型融合, 可用于测试音色融合": "Model fusion, può essere utilizzato per testare la fusione timbrica", + "模型路径": "Percorso al modello:", + "每张显卡的batch_size": "Dimensione batch per GPU:", + "淡入淡出长度": "Lunghezza dissolvenza", + "版本": "Versione", + "特征提取": "Estrazione delle caratteristiche", + "特征检索库文件路径,为空则使用下拉的选择结果": "Percorso del file di indice delle caratteristiche. ", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Tonalità +12 consigliata per la conversione da maschio a femmina e tonalità -12 per la conversione da femmina a maschio. ", + "目标采样率": "Frequenza di campionamento target:", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "Rileva automaticamente il percorso dell'indice e seleziona dal menu a tendina:", + "融合": "Fusione", + "要改的模型信息": "Informazioni sul modello da modificare:", + "要置入的模型信息": "Informazioni sul modello da posizionare:", + "训练": "Addestramento", + "训练模型": "Addestra modello", + "训练特征索引": "Addestra indice delle caratteristiche", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Addestramento completato. ", + "设备类型": "设备类型", + "请指定说话人id": "Si prega di specificare l'ID del locutore/cantante:", + "请选择index文件": "请选择index文件", + "请选择pth文件": "请选择pth 文件", + "请选择说话人id": "Seleziona ID locutore/cantante:", + "转换": "Convertire", + "输入实验名": "Inserisci il nome dell'esperimento:", + "输入待处理音频文件夹路径": "Immettere il percorso della cartella audio da elaborare:", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "Immettere il percorso della cartella audio da elaborare (copiarlo dalla barra degli indirizzi del file manager):", + "输入待处理音频文件路径(默认是正确格式示例)": "Immettere il percorso del file audio da elaborare (l'impostazione predefinita è l'esempio di formato corretto):", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "Regola il ridimensionamento dell'inviluppo del volume. ", + "输入监听": "输入监听", + "输入训练文件夹路径": "Inserisci il percorso della cartella di addestramento:", + "输入设备": "Dispositivo di input", + "输入降噪": "Riduzione del rumore in ingresso", + "输出信息": "Informazioni sull'uscita", + "输出变声": "输出变声", + "输出设备": "Dispositivo di uscita", + "输出降噪": "Riduzione del rumore in uscita", + "输出音频(右下角三个点,点了可以下载)": "Esporta audio (clicca sui tre puntini in basso a destra per scaricarlo)", + "选择.index文件": "Seleziona il file .index", + "选择.pth文件": "Seleziona il file .pth", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Seleziona l'algoritmo di estrazione del tono (\"pm\": estrazione più veloce ma risultato di qualità inferiore; \"harvest\": bassi migliori ma estremamente lenti; \"crepe\": qualità migliore ma utilizzo intensivo della GPU):", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "Lunghezza del campione", + "重载设备列表": "Ricaricare l'elenco dei dispositivi", + "音调设置": "Impostazioni del tono", + "音频设备": "Dispositivo audio", + "音高算法": "音高算法", + "额外推理时长": "Tempo di inferenza extra" +} diff --git a/rvc/i18n/locale/ja_JP.json b/rvc/i18n/locale/ja_JP.json new file mode 100644 index 0000000..a5d03aa --- /dev/null +++ b/rvc/i18n/locale/ja_JP.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3 次に、harvestピッチの認識結果に対してメディアンフィルタを使用します。値はフィルター半径で、ミュートを減衰させるために使用します。", + "A模型权重": "Aモデルの重み", + "A模型路径": "Aモデルのパス", + "B模型路径": "Bモデルのパス", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0(最低共振周波数)カーブファイル(オプション、1行に1ピッチ、デフォルトのF0(最低共振周波数)とエレベーションを置き換えます。)", + "Index Rate": "Index Rate", + "Onnx导出": "Onnxエクスポート", + "Onnx输出路径": "Onnx出力パス", + "RVC模型路径": "RVCモデルパス", + "ckpt处理": "ckptファイルの処理", + "harvest进程数": "harvestプロセス数", + "index文件路径不可包含中文": "indexファイルのパスに漢字を含んではいけません", + "pth文件路径不可包含中文": "pthファイルのパスに漢字を含んではいけません", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpeカード番号設定:異なるプロセスに使用するカード番号を入力する。例えば、0-0-1でカード0に2つのプロセス、カード1に1つのプロセスを実行する。", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "ステップ1:実験設定を入力します。実験データはlogsに保存され、各実験にはフォルダーがあります。実験名のパスを手動で入力する必要があり、実験設定、ログ、トレーニングされたモデルファイルが含まれます。", + "step1:正在处理数据": "step1:処理中のデータ", + "step2:正在提取音高&正在提取特征": "step2:ピッチ抽出と特徴抽出", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "ステップ2a: 訓練フォルダー内のすべての音声ファイルを自動的に探索し、スライスと正規化を行い、2つのwavフォルダーを実験ディレクトリに生成します。現在は一人でのトレーニングのみをサポートしています。", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "ステップ2b: CPUを使用して音高を抽出する(モデルに音高がある場合)、GPUを使用して特徴を抽出する(GPUの番号を選択する)", + "step3: 填写训练设置, 开始训练模型和索引": "ステップ3: トレーニング設定を入力して、モデルとインデックスのトレーニングを開始します", + "step3a:正在训练模型": "step3a:トレーニング中のモデル", + "一键训练": "ワンクリックトレーニング", + "也可批量输入音频文件, 二选一, 优先读文件夹": "複数のオーディオファイルをインポートすることもできます。フォルダパスが存在する場合、この入力は無視されます。", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "UVR5モデルを使用したボーカル伴奏の分離バッチ処理。
有効なフォルダーパスフォーマットの例: D:\\path\\to\\input\\folder (エクスプローラーのアドレスバーからコピーします)。
モデルは三つのカテゴリに分かれています:
1. ボーカルを保持: ハーモニーのないオーディオに対してこれを選択します。HP5よりもボーカルをより良く保持します。HP2とHP3の二つの内蔵モデルが含まれています。HP3は伴奏をわずかに漏らす可能性がありますが、HP2よりもわずかにボーカルをより良く保持します。
2. 主なボーカルのみを保持: ハーモニーのあるオーディオに対してこれを選択します。主なボーカルを弱める可能性があります。HP5の一つの内蔵モデルが含まれています。
3. ディリバーブとディレイモデル (by FoxJoy):
  (1) MDX-Net: ステレオリバーブの除去に最適な選択肢ですが、モノリバーブは除去できません;
 (234) DeEcho: ディレイ効果を除去します。AggressiveモードはNormalモードよりも徹底的に除去します。DeReverbはさらにリバーブを除去し、モノリバーブを除去することができますが、高周波のリバーブが強い内容に対しては非常に効果的ではありません。
ディリバーブ/ディレイに関する注意点:
1. DeEcho-DeReverbモデルの処理時間は、他の二つのDeEchoモデルの約二倍です。
2. MDX-Net-Dereverbモデルは非常に遅いです。
3. 推奨される最もクリーンな設定は、最初にMDX-Netを適用し、その後にDeEcho-Aggressiveを適用することです。", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "ハイフンで区切って使用するGPUの番号を入力します。例えば0-1-2はGPU0、GPU1、GPU2を使用します", + "伴奏人声分离&去混响&去回声": "伴奏ボーカル分離&残響除去&エコー除去", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "保存ファイル名", + "保存的文件名, 默认空为和源文件同名": "保存するファイル名、デフォルトでは空欄で元のファイル名と同じ名前になります", + "保存的模型名不带后缀": "拡張子のない保存するモデル名", + "保存频率save_every_epoch": "エポックごとの保存頻度", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "明確な子音と呼吸音を保護し、電子音の途切れやその他のアーティファクトを防止します。0.5でオフになります。下げると保護が強化されますが、indexの効果が低下する可能性があります。", + "修改": "変更", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "モデル情報の修正(weightsフォルダから抽出された小さなモデルファイルのみ対応)", + "停止音频转换": "音声変換を停止", + "全流程结束!": "全工程が完了!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "音源リストとインデックスパスの更新", + "加载模型": "モデルをロード", + "加载预训练底模D路径": "事前学習済みのDモデルのパス", + "加载预训练底模G路径": "事前学習済みのGモデルのパス", + "单次推理": "单次推理", + "卸载音色省显存": "音源を削除してメモリを節約", + "变调(整数, 半音数量, 升八度12降八度-12)": "ピッチ変更(整数、半音数、上下オクターブ12-12)", + "后处理重采样至最终采样率,0为不进行重采样": "最終的なサンプリングレートへのポストプロセッシングのリサンプリング リサンプリングしない場合は0", + "否": "いいえ", + "启用相位声码器": "启用相位声码器", + "响应阈值": "反応閾値", + "响度因子": "ラウドネス係数", + "处理数据": "データ処理", + "导出Onnx模型": "Onnxに変換", + "导出文件格式": "エクスポート形式", + "常见问题解答": "よくある質問", + "常规设置": "一般設定", + "开始音频转换": "音声変換を開始", + "很遗憾您这没有能用的显卡来支持您训练": "トレーニングに対応したGPUが動作しないのは残念です。", + "性能设置": "パフォーマンス設定", + "总训练轮数total_epoch": "総エポック数", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "一括変換、変換する音声フォルダを入力、または複数の音声ファイルをアップロードし、指定したフォルダ(デフォルトのopt)に変換した音声を出力します。", + "指定输出主人声文件夹": "マスターの出力音声フォルダーを指定する", + "指定输出文件夹": "出力フォルダを指定してください", + "指定输出非主人声文件夹": "マスター以外の出力音声フォルダーを指定する", + "推理时间(ms):": "推論時間(ms):", + "推理音色": "音源推論", + "提取": "抽出", + "提取音高和处理数据使用的CPU进程数": "ピッチの抽出やデータ処理に使用するCPUスレッド数", + "是": "はい", + "是否仅保存最新的ckpt文件以节省硬盘空间": "ハードディスク容量を節約するため、最新のckptファイルのみを保存しますか?", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "各保存時点の小モデルを全部weightsフォルダに保存するかどうか", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "すべてのトレーニングデータをメモリにキャッシュするかどうか。10分以下の小さなデータはキャッシュしてトレーニングを高速化できますが、大きなデータをキャッシュするとメモリが破裂し、あまり速度が上がりません。", + "显卡信息": "GPU情報", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "本ソフトウェアはMITライセンスに基づくオープンソースであり、製作者は本ソフトウェアに対していかなる責任を持ちません。本ソフトウェアの利用者および本ソフトウェアから派生した音源(成果物)を配布する者は、本ソフトウェアに対して自身で責任を負うものとします。
この条項に同意しない場合、パッケージ内のコードやファイルを使用や参照を禁じます。詳しくはLICENSEをご覧ください。", + "查看": "表示", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "モデル情報を表示する(小さいモデルファイルはweightsフォルダーからのみサポートされています)", + "检索特征占比": "検索特徴率", + "模型": "モデル", + "模型推理": "モデル推論", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "モデル抽出(ログフォルダー内の大きなファイルのモデルパスを入力)、モデルを半分までトレーニングし、自動的に小さいファイルモデルを保存しなかったり、中間モデルをテストしたい場合に適用されます。", + "模型是否带音高指导": "モデルに音高ガイドを付けるかどうか", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "モデルに音高ガイドがあるかどうか(歌唱には必要ですが、音声には必要ありません)", + "模型是否带音高指导,1是0否": "モデルに音高ガイドを付けるかどうか、1は付ける、0は付けない", + "模型版本型号": "モデルのバージョン", + "模型融合, 可用于测试音色融合": "モデルのマージ、音源のマージテストに使用できます", + "模型路径": "モデルパス", + "每张显卡的batch_size": "GPUごとのバッチサイズ", + "淡入淡出长度": "フェードイン/フェードアウト長", + "版本": "バージョン", + "特征提取": "特徴抽出", + "特征检索库文件路径,为空则使用下拉的选择结果": "特徴検索ライブラリへのパス 空の場合はドロップダウンで選択", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "男性から女性へは+12キーをお勧めします。女性から男性へは-12キーをお勧めします。音域が広すぎて音質が劣化した場合は、適切な音域に自分で調整してください。", + "目标采样率": "目標サンプリングレート", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "インデックスパスの自動検出 ドロップダウンで選択", + "融合": "マージ", + "要改的模型信息": "変更するモデル情報", + "要置入的模型信息": "挿入するモデル情報", + "训练": "トレーニング", + "训练模型": "モデルのトレーニング", + "训练特征索引": "特徴インデックスのトレーニング", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "トレーニング終了時に、トレーニングログやフォルダ内のtrain.logを確認することができます", + "设备类型": "设备类型", + "请指定说话人id": "話者IDを指定してください", + "请选择index文件": "indexファイルを選択してください", + "请选择pth文件": "pthファイルを選択してください", + "请选择说话人id": "話者IDを選択してください", + "转换": "変換", + "输入实验名": "モデル名", + "输入待处理音频文件夹路径": "処理するオーディオファイルのフォルダパスを入力してください", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "処理対象音声フォルダーのパスを入力してください(エクスプローラーのアドレスバーからコピーしてください)", + "输入待处理音频文件路径(默认是正确格式示例)": "処理対象音声ファイルのパスを入力してください(デフォルトは正しいフォーマットの例です)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "入力ソースの音量エンベロープと出力音量エンベロープの融合率 1に近づくほど、出力音量エンベロープの割合が高くなる", + "输入监听": "输入监听", + "输入训练文件夹路径": "トレーニング用フォルダのパスを入力してください", + "输入设备": "入力デバイス", + "输入降噪": "入力ノイズの低減", + "输出信息": "出力情報", + "输出变声": "输出变声", + "输出设备": "出力デバイス", + "输出降噪": "出力ノイズの低減", + "输出音频(右下角三个点,点了可以下载)": "出力音声(右下の三点をクリックしてダウンロードできます)", + "选择.index文件": ".indexファイルを選択", + "选择.pth文件": ".pthファイルを選択", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "ピッチ抽出アルゴリズムの選択、歌声はpmで高速化でき、harvestは低音が良いが信じられないほど遅く、crepeは良く動くがGPUを食います。", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "ピッチ抽出アルゴリズムの選択、歌声はpmで高速化でき、harvestは低音が良いが信じられないほど遅く、crepeは良く動くがGPUを喰います", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "ピッチ抽出アルゴリズムの選択:歌声はpmで高速化でき、入力した音声が高音質でCPUが貧弱な場合はdioで高速化でき、harvestの方が良いが遅く、rmvpeがベストだがCPU/GPUを若干食います。", + "采样率:": "采样率:", + "采样长度": "サンプル長", + "重载设备列表": "デバイスリストをリロードする", + "音调设置": "音程設定", + "音频设备": "オーディオデバイス", + "音高算法": "ピッチアルゴリズム", + "额外推理时长": "追加推論時間" +} diff --git a/rvc/i18n/locale/ko_KR.json b/rvc/i18n/locale/ko_KR.json new file mode 100644 index 0000000..dfc9140 --- /dev/null +++ b/rvc/i18n/locale/ko_KR.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3인 경우 harvest 피치 인식 결과에 중간값 필터 적용, 필터 반경은 값으로 지정, 사용 시 무성음 감소 가능", + "A模型权重": "A 모델 가중치", + "A模型路径": "A 모델 경로", + "B模型路径": "B 모델 경로", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\음성 오디오+표시\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0 곡선 파일, 선택적, 한 줄에 하나의 피치, 기본 F0 및 음높이 조절 대체", + "Index Rate": "인덱스 비율", + "Onnx导出": "Onnx 내보내기", + "Onnx输出路径": "Onnx 출력 경로", + "RVC模型路径": "RVC 모델 경로", + "ckpt处理": "ckpt 처리", + "harvest进程数": "harvest 프로세스 수", + "index文件路径不可包含中文": "index 파일 경로는 중국어를 포함할 수 없음", + "pth文件路径不可包含中文": "pth 파일 경로는 중국어를 포함할 수 없음", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe 카드 번호 설정: -로 구분된 입력 사용 카드 번호, 예: 0-0-1은 카드 0에서 2개 프로세스, 카드 1에서 1개 프로세스 실행", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "step1: 실험 구성 작성. 실험 데이터는 logs에 저장, 각 실험은 하나의 폴더, 수동으로 실험 이름 경로 입력 필요, 실험 구성, 로그, 훈련된 모델 파일 포함.", + "step1:正在处理数据": "step1: 데이터 처리 중", + "step2:正在提取音高&正在提取特征": "step2: 음높이 추출 & 특징 추출 중", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "step2a: 훈련 폴더 아래 모든 오디오로 디코딩 가능한 파일을 자동 순회하며 슬라이스 정규화 진행, 실험 디렉토리 아래 2개의 wav 폴더 생성; 현재 단일 사용자 훈련만 지원.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "step2b: CPU를 사용하여 음높이 추출(모델이 음높이 포함 시), GPU를 사용하여 특징 추출(카드 번호 선택)", + "step3: 填写训练设置, 开始训练模型和索引": "step3: 훈련 설정 작성, 모델 및 인덱스 훈련 시작", + "step3a:正在训练模型": "step3a: 모델 훈련 중", + "一键训练": "원클릭 훈련", + "也可批量输入音频文件, 二选一, 优先读文件夹": "여러 오디오 파일을 일괄 입력할 수도 있음, 둘 중 하나 선택, 폴더 우선 읽기", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "인간 목소리와 반주 분리 배치 처리, UVR5 모델 사용.
적절한 폴더 경로 예시: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(파일 관리자 주소 표시줄에서 복사하면 됨).
모델은 세 가지 유형으로 나뉨:
1. 인간 목소리 보존: 화음이 없는 오디오에 이것을 선택, HP5보다 주된 인간 목소리 보존에 더 좋음. 내장된 HP2와 HP3 두 모델, HP3는 약간의 반주 누락 가능성이 있지만 HP2보다 주된 인간 목소리 보존이 약간 더 좋음;
2. 주된 인간 목소리만 보존: 화음이 있는 오디오에 이것을 선택, 주된 인간 목소리에 약간의 약화 가능성 있음. 내장된 HP5 모델 하나;
3. 혼효음 제거, 지연 제거 모델(by FoxJoy):
  (1)MDX-Net(onnx_dereverb): 이중 채널 혼효음에는 최선의 선택, 단일 채널 혼효음은 제거할 수 없음;
 (234)DeEcho: 지연 제거 효과. Aggressive는 Normal보다 더 철저하게 제거, DeReverb는 추가로 혼효음을 제거, 단일 채널 혼효음은 제거 가능하지만 고주파 중심의 판 혼효음은 완전히 제거하기 어려움.
혼효음/지연 제거, 부록:
1. DeEcho-DeReverb 모델의 처리 시간은 다른 두 개의 DeEcho 모델의 거의 2배임;
2. MDX-Net-Dereverb 모델은 상당히 느림;
3. 개인적으로 추천하는 가장 깨끗한 구성은 MDX-Net 다음에 DeEcho-Aggressive 사용.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "-로 구분하여 입력하는 카드 번호, 예: 0-1-2는 카드 0, 카드 1, 카드 2 사용", + "伴奏人声分离&去混响&去回声": "반주 인간 목소리 분리 & 혼효음 제거 & 에코 제거", + "使用模型采样率": "모델 샘플링 레이트 사용", + "使用设备采样率": "장치 샘플링 레이트 사용", + "保存名": "저장 이름", + "保存的文件名, 默认空为和源文件同名": "저장될 파일명, 기본적으로 빈 공간은 원본 파일과 동일한 이름으로", + "保存的模型名不带后缀": "저장된 모델명은 접미사 없음", + "保存频率save_every_epoch": "저장 빈도 save_every_epoch", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "청자음과 호흡 소리를 보호, 전자음 찢김 등의 아티팩트 방지, 0.5까지 올려서 비활성화, 낮추면 보호 강도 증가하지만 인덱스 효과 감소 가능성 있음", + "修改": "수정", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "모델 정보 수정(오직 weights 폴더 아래에서 추출된 작은 모델 파일만 지원)", + "停止音频转换": "오디오 변환 중지", + "全流程结束!": "전체 과정 완료!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "음색 목록 및 인덱스 경로 새로고침", + "加载模型": "모델 로드", + "加载预训练底模D路径": "미리 훈련된 베이스 모델 D 경로 로드", + "加载预训练底模G路径": "미리 훈련된 베이스 모델 G 경로 로드", + "单次推理": "단일 추론", + "卸载音色省显存": "음색 언로드로 디스플레이 메모리 절약", + "变调(整数, 半音数量, 升八度12降八度-12)": "키 변경(정수, 반음 수, 옥타브 상승 12, 옥타브 하강 -12)", + "后处理重采样至最终采样率,0为不进行重采样": "후처리 재샘플링을 최종 샘플링 레이트로, 0은 재샘플링하지 않음", + "否": "아니오", + "启用相位声码器": "위상 보코더 활성화", + "响应阈值": "응답 임계값", + "响度因子": "음량 인자", + "处理数据": "데이터 처리", + "导出Onnx模型": "Onnx 모델 내보내기", + "导出文件格式": "내보낼 파일 형식", + "常见问题解答": "자주 묻는 질문", + "常规设置": "일반 설정", + "开始音频转换": "오디오 변환 시작", + "很遗憾您这没有能用的显卡来支持您训练": "사용 가능한 그래픽 카드가 없어 훈련을 지원할 수 없습니다", + "性能设置": "성능 설정", + "总训练轮数total_epoch": "총 훈련 라운드 수 total_epoch", + "批量推理": "일괄 추론", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "일괄 변환, 변환할 오디오 파일 폴더 입력 또는 여러 오디오 파일 업로드, 지정된 폴더(기본값 opt)에 변환된 오디오 출력.", + "指定输出主人声文件夹": "주된 목소리 출력 폴더 지정", + "指定输出文件夹": "출력 파일 폴더 지정", + "指定输出非主人声文件夹": "주된 목소리가 아닌 출력 폴더 지정", + "推理时间(ms):": "추론 시간(ms):", + "推理音色": "추론 음색", + "提取": "추출", + "提取音高和处理数据使用的CPU进程数": "음높이 추출 및 데이터 처리에 사용되는 CPU 프로세스 수", + "是": "예", + "是否仅保存最新的ckpt文件以节省硬盘空间": "디스크 공간을 절약하기 위해 최신 ckpt 파일만 저장할지 여부", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "저장 시마다 최종 소형 모델을 weights 폴더에 저장할지 여부", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "모든 훈련 세트를 VRAM에 캐시할지 여부. 10분 미만의 소량 데이터는 캐시하여 훈련 속도를 높일 수 있지만, 대량 데이터 캐시는 VRAM을 과부하시키고 속도를 크게 향상시키지 못함", + "显卡信息": "그래픽 카드 정보", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "이 소프트웨어는 MIT 라이선스로 공개되며, 저자는 소프트웨어에 대해 어떠한 통제권도 가지지 않습니다. 모든 귀책사유는 소프트웨어 사용자 및 소프트웨어에서 생성된 결과물을 사용하는 당사자에게 있습니다.
해당 조항을 인정하지 않는 경우, 소프트웨어 패키지의 어떠한 코드나 파일도 사용하거나 인용할 수 없습니다. 자세한 내용은 루트 디렉토리의 LICENSE를 참조하세요.", + "查看": "보기", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "모델 정보 보기(오직 weights 폴더에서 추출된 소형 모델 파일만 지원)", + "检索特征占比": "검색 특징 비율", + "模型": "모델", + "模型推理": "모델 추론", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "모델 추출(logs 폴더 아래의 큰 파일 모델 경로 입력), 훈련 중간에 중단한 모델의 자동 추출 및 소형 파일 모델 저장이 안 되거나 중간 모델을 테스트하고 싶은 경우에 적합", + "模型是否带音高指导": "모델이 음높이 지도를 포함하는지 여부", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "모델이 음높이 지도를 포함하는지 여부(노래에는 반드시 필요, 음성에는 필요 없음)", + "模型是否带音高指导,1是0否": "모델이 음높이 지도를 포함하는지 여부, 1은 예, 0은 아니오", + "模型版本型号": "모델 버전 및 모델", + "模型融合, 可用于测试音色融合": "모델 융합, 음색 융합 테스트에 사용 가능", + "模型路径": "모델 경로", + "每张显卡的batch_size": "각 그래픽 카드의 batch_size", + "淡入淡出长度": "페이드 인/아웃 길이", + "版本": "버전", + "特征提取": "특징 추출", + "特征检索库文件路径,为空则使用下拉的选择结果": "특징 검색 라이브러리 파일 경로, 비어 있으면 드롭다운 선택 결과 사용", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "남성에서 여성으로 변경 시 +12 키 권장, 여성에서 남성으로 변경 시 -12 키 권장, 음역대 폭발로 음색이 왜곡되면 적절한 음역대로 조정 가능.", + "目标采样率": "목표 샘플링률", + "算法延迟(ms):": "알고리즘 지연(ms):", + "自动检测index路径,下拉式选择(dropdown)": "자동으로 index 경로 감지, 드롭다운 선택(dropdown)", + "融合": "융합", + "要改的模型信息": "변경할 모델 정보", + "要置入的模型信息": "삽입할 모델 정보", + "训练": "훈련", + "训练模型": "모델 훈련", + "训练特征索引": "특징 인덱스 훈련", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "훈련 완료, 콘솔 훈련 로그 또는 실험 폴더 내의 train.log 확인 가능", + "设备类型": "设备类型", + "请指定说话人id": "화자 ID 지정 필요", + "请选择index文件": "index 파일 선택", + "请选择pth文件": "pth 파일 선택", + "请选择说话人id": "화자 ID 선택", + "转换": "변환", + "输入实验名": "실험명 입력", + "输入待处理音频文件夹路径": "처리할 오디오 파일 폴더 경로 입력", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "처리할 오디오 파일 폴더 경로 입력(파일 탐색기 주소 표시줄에서 복사)", + "输入待处理音频文件路径(默认是正确格式示例)": "처리할 오디오 파일 경로 입력(기본적으로 올바른 형식 예시)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "입력 소스 볼륨 엔벨로프와 출력 볼륨 엔벨로프의 결합 비율 입력, 1에 가까울수록 출력 엔벨로프 사용", + "输入监听": "입력 모니터링", + "输入训练文件夹路径": "훈련 파일 폴더 경로 입력", + "输入设备": "입력 장치", + "输入降噪": "입력 노이즈 감소", + "输出信息": "출력 정보", + "输出变声": "출력 음성 변조", + "输出设备": "출력 장치", + "输出降噪": "출력 노이즈 감소", + "输出音频(右下角三个点,点了可以下载)": "출력 오디오(오른쪽 하단 세 개의 점, 클릭하면 다운로드 가능)", + "选择.index文件": ".index 파일 선택", + "选择.pth文件": ".pth 파일 선택", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "음높이 추출 알고리즘 선택, 노래 입력 시 pm으로 속도 향상, harvest는 저음이 좋지만 매우 느림, crepe는 효과가 좋지만 GPU 사용", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "음높이 추출 알고리즘 선택, 노래 입력 시 pm으로 속도 향상, harvest는 저음이 좋지만 매우 느림, crepe는 효과가 좋지만 GPU 사용, rmvpe는 효과가 가장 좋으며 GPU를 적게 사용", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "음높이 추출 알고리즘 선택: 노래 입력 시 pm으로 속도 향상, 고품질 음성에는 CPU가 부족할 때 dio 사용, harvest는 품질이 더 좋지만 느림, rmvpe는 효과가 가장 좋으며 CPU/GPU를 적게 사용", + "采样率:": "샘플링률:", + "采样长度": "샘플링 길이", + "重载设备列表": "장치 목록 재로드", + "音调设置": "음조 설정", + "音频设备": "音频设备", + "音高算法": "음높이 알고리즘", + "额外推理时长": "추가 추론 시간" +} diff --git a/rvc/i18n/locale/pt_BR.json b/rvc/i18n/locale/pt_BR.json new file mode 100644 index 0000000..74b5a3b --- /dev/null +++ b/rvc/i18n/locale/pt_BR.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3, use o filtro mediano para o resultado do reconhecimento do tom da heverst, e o valor é o raio do filtro, que pode enfraquecer o mudo.", + "A模型权重": "Peso (w) para o modelo A:", + "A模型路径": "Caminho para o Modelo A:", + "B模型路径": "Caminho para o Modelo B:", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\meu-dataset", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "Arquivo de curva F0 (opcional). Um arremesso por linha. Substitui a modulação padrão F0 e tom:", + "Index Rate": "Taxa do Index", + "Onnx导出": "Exportar Onnx", + "Onnx输出路径": "Caminho de exportação ONNX:", + "RVC模型路径": "Caminho do Modelo RVC:", + "ckpt处理": "processamento ckpt", + "harvest进程数": "Número de processos harvest", + "index文件路径不可包含中文": "O caminho do arquivo de Index não pode conter caracteres chineses", + "pth文件路径不可包含中文": "o caminho do arquivo pth não pode conter caracteres chineses", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "Configuração do número do cartão rmvpe: Use - para separar os números dos cartões de entrada de diferentes processos. Por exemplo, 0-0-1 é usado para executar 2 processos no cartão 0 e 1 processo no cartão 1.", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Etapa 1: Preencha a configuração experimental. Os dados experimentais são armazenados na pasta 'logs', com cada experimento tendo uma pasta separada. Digite manualmente o caminho do nome do experimento, que contém a configuração experimental, os logs e os arquivos de modelo treinados.", + "step1:正在处理数据": "Etapa 1: Processamento de dados", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Etapa 2a: Percorra automaticamente todos os arquivos na pasta de treinamento que podem ser decodificados em áudio e execute a normalização da fatia. Gera 2 pastas wav no diretório do experimento. Atualmente, apenas o treinamento de um único cantor/palestrante é suportado.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Etapa 2b: Use a CPU para extrair o tom (se o modelo tiver tom), use a GPU para extrair recursos (selecione o índice da GPU):", + "step3: 填写训练设置, 开始训练模型和索引": "Etapa 3: Preencha as configurações de treinamento e comece a treinar o modelo e o Index", + "step3a:正在训练模型": "Etapa 3a: Treinamento do modelo iniciado", + "一键训练": "Treinamento com um clique", + "也可批量输入音频文件, 二选一, 优先读文件夹": "Você também pode inserir arquivos de áudio em lotes. Escolha uma das duas opções. É dada prioridade à leitura da pasta.", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Processamento em lote para separação de acompanhamento vocal usando o modelo UVR5.
Exemplo de um formato de caminho de pasta válido: D:\\caminho\\para a pasta\\entrada\\ (copie-o da barra de endereços do gerenciador de arquivos).
O modelo é dividido em três categorias:
1. Preservar vocais: Escolha esta opção para áudio sem harmonias. Ele preserva os vocais melhor do que o HP5. Inclui dois modelos integrados: HP2 e HP3. O HP3 pode vazar ligeiramente o acompanhamento, mas preserva os vocais um pouco melhor do que o HP2.
2 Preservar apenas os vocais principais: Escolha esta opção para áudio com harmonias. Isso pode enfraquecer os vocais principais. Ele inclui um modelo embutido: HP5.
3. Modelos de de-reverb e de-delay (por FoxJoy):
  (1) MDX-Net: A melhor escolha para remoção de reverb estéreo, mas não pode remover reverb mono;
 (234) DeEcho: Remove efeitos de atraso. O modo agressivo remove mais completamente do que o modo normal. O DeReverb também remove reverb e pode remover reverb mono, mas não de forma muito eficaz para conteúdo de alta frequência fortemente reverberado.
Notas de de-reverb/de-delay:
1. O tempo de processamento para o modelo DeEcho-DeReverb é aproximadamente duas vezes maior que os outros dois modelos DeEcho.
2 O modelo MDX-Net-Dereverb é bastante lento.
3. A configuração mais limpa recomendada é aplicar MDX-Net primeiro e depois DeEcho-Aggressive.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "Digite o (s) índice(s) da GPU separados por '-', por exemplo, 0-1-2 para usar a GPU 0, 1 e 2:", + "伴奏人声分离&去混响&去回声": "UVR5", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Salvar nome", + "保存的文件名, 默认空为和源文件同名": "Salvar nome do arquivo (padrão: igual ao arquivo de origem):", + "保存的模型名不带后缀": "Nome do modelo salvo (sem extensão):", + "保存频率save_every_epoch": "Faça backup a cada # de Epoch:", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Proteja consoantes sem voz e sons respiratórios, evite artefatos como quebra de som eletrônico e desligue-o quando estiver cheio de 0,5. Diminua-o para aumentar a proteção, mas pode reduzir o efeito de indexação:", + "修改": "Editar", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Modificar informações do modelo (suportado apenas para arquivos de modelo pequenos extraídos da pasta 'weights')", + "停止音频转换": "Conversão de áudio", + "全流程结束!": "Todos os processos foram concluídos!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Atualizar lista de voz e caminho do Index", + "加载模型": "Modelo", + "加载预训练底模D路径": "Carregue o caminho D do modelo base pré-treinado:", + "加载预训练底模G路径": "Carregue o caminho G do modelo base pré-treinado:", + "单次推理": "Único", + "卸载音色省显存": "Descarregue a voz para liberar a memória da GPU:", + "变调(整数, 半音数量, 升八度12降八度-12)": "Mude o tom aqui. Se a voz for do mesmo sexo, não é necessario alterar (12 caso seja Masculino para feminino, -12 caso seja ao contrário).", + "后处理重采样至最终采样率,0为不进行重采样": "Reamostragem pós-processamento para a taxa de amostragem final, 0 significa sem reamostragem:", + "否": "Não", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Limiar de resposta", + "响度因子": "Fator de volume", + "处理数据": "Processar o Conjunto de Dados", + "导出Onnx模型": "Exportar Modelo Onnx", + "导出文件格式": "Qual formato de arquivo você prefere?", + "常见问题解答": "FAQ (Perguntas frequentes)", + "常规设置": "Configurações gerais", + "开始音频转换": "Iniciar conversão de áudio", + "很遗憾您这没有能用的显卡来支持您训练": "Infelizmente, não há GPU compatível disponível para apoiar o seu treinamento.", + "性能设置": "Configurações de desempenho.", + "总训练轮数total_epoch": "Número total de ciclos(epoch) de treino (se escolher um valor alto demais, o seu modelo parecerá terrivelmente sobretreinado):", + "批量推理": "Conversão em Lote", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Conversão em Massa.", + "指定输出主人声文件夹": "Especifique a pasta de saída para vocais:", + "指定输出文件夹": "Especifique a pasta de saída:", + "指定输出非主人声文件夹": "Informar a pasta de saída para acompanhamento:", + "推理时间(ms):": "Tempo de inferência (ms):", + "推理音色": "Escolha o seu Modelo:", + "提取": "Extrato", + "提取音高和处理数据使用的CPU进程数": "Número de processos de CPU usados para extração de tom e processamento de dados:", + "是": "Sim", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Só deve salvar apenas o arquivo ckpt mais recente para economizar espaço em disco:", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Salve um pequeno modelo final na pasta 'weights' em cada ponto de salvamento:", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Se deve armazenar em cache todos os conjuntos de treinamento na memória de vídeo. Pequenos dados com menos de 10 minutos podem ser armazenados em cache para acelerar o treinamento, e um cache de dados grande irá explodir a memória de vídeo e não aumentar muito a velocidade:", + "显卡信息": "Informações da GPU", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "
The Mangio-RVC 💻 | Tradução por Krisp e Rafael Godoy Ebert | AI HUB BRASIL
Este software é de código aberto sob a licença MIT. O autor não tem qualquer controle sobre o software. Aqueles que usam o software e divulgam os sons exportados pelo software são totalmente responsáveis.
Se você não concorda com este termo, você não pode usar ou citar nenhum código e arquivo no pacote de software. Para obter detalhes, consulte o diretório raiz O acordo a ser seguido para uso LICENSE
", + "查看": "Visualizar", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "Exibir informações do modelo (suportado apenas para arquivos de modelo pequenos extraídos da pasta 'weights')", + "检索特征占比": "Taxa de recurso de recuperação:", + "模型": "Modelo", + "模型推理": "Inference", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Extração do modelo (insira o caminho do modelo de arquivo grande na pasta 'logs'). Isso é útil se você quiser interromper o treinamento no meio do caminho e extrair e salvar manualmente um arquivo de modelo pequeno, ou se quiser testar um modelo intermediário:", + "模型是否带音高指导": "Se o modelo tem orientação de tom:", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Se o modelo tem orientação de tom (necessário para cantar, opcional para fala):", + "模型是否带音高指导,1是0否": "Se o modelo tem orientação de passo (1: sim, 0: não):", + "模型版本型号": "Versão:", + "模型融合, 可用于测试音色融合": "A fusão modelo, pode ser usada para testar a fusão do timbre", + "模型路径": "Caminho para o Modelo:", + "每张显卡的batch_size": "Batch Size (DEIXE COMO ESTÁ a menos que saiba o que está fazendo, no Colab pode deixar até 20!):", + "淡入淡出长度": "Comprimento de desvanecimento", + "版本": "Versão", + "特征提取": "Extrair Tom", + "特征检索库文件路径,为空则使用下拉的选择结果": "Caminho para o arquivo de Index. Deixe em branco para usar o resultado selecionado no menu debaixo:", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Recomendado +12 chave para conversão de homem para mulher e -12 chave para conversão de mulher para homem. Se a faixa de som for muito longe e a voz estiver distorcida, você também pode ajustá-la à faixa apropriada por conta própria.", + "目标采样率": "Taxa de amostragem:", + "算法延迟(ms):": "Atrasos algorítmicos (ms):", + "自动检测index路径,下拉式选择(dropdown)": "Detecte automaticamente o caminho do Index e selecione no menu suspenso:", + "融合": "Fusão", + "要改的模型信息": "Informações do modelo a ser modificado:", + "要置入的模型信息": "Informações do modelo a ser colocado:", + "训练": "Treinar", + "训练模型": "Treinar Modelo", + "训练特征索引": "Treinar Index", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Após o término do treinamento, você pode verificar o log de treinamento do console ou train.log na pasta de experimentos", + "设备类型": "设备类型", + "请指定说话人id": "Especifique o ID do locutor/cantor:", + "请选择index文件": "Selecione o arquivo de Index", + "请选择pth文件": "Selecione o arquivo pth", + "请选择说话人id": "Selecione Palestrantes/Cantores ID:", + "转换": "Converter", + "输入实验名": "Nome da voz:", + "输入待处理音频文件夹路径": "Caminho da pasta de áudio a ser processada:", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "Caminho da pasta de áudio a ser processada (copie-o da barra de endereços do gerenciador de arquivos):", + "输入待处理音频文件路径(默认是正确格式示例)": "Caminho para o seu conjunto de dados (áudios, não zipado):", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "O envelope de volume da fonte de entrada substitui a taxa de fusão do envelope de volume de saída, quanto mais próximo de 1, mais o envelope de saída é usado:", + "输入监听": "Monitoramento de entrada", + "输入训练文件夹路径": "Caminho da pasta de treinamento:", + "输入设备": "Dispositivo de entrada", + "输入降噪": "Redução de ruído de entrada", + "输出信息": "Informação de saída", + "输出变声": "Mudança de voz de saída", + "输出设备": "Dispositivo de saída", + "输出降噪": "Redução de ruído de saída", + "输出音频(右下角三个点,点了可以下载)": "Exportar áudio (clique nos três pontos no canto inferior direito para baixar)", + "选择.index文件": "Selecione o Index", + "选择.pth文件": "Selecione o Arquivo", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Selecione o algoritmo de extração de tom \n'pm': extração mais rápida, mas discurso de qualidade inferior; \n'harvest': graves melhores, mas extremamente lentos; \n'harvest': melhor qualidade, mas extração mais lenta); 'crepe': melhor qualidade, mas intensivo em GPU; 'magio-crepe': melhor opção; 'RMVPE': um modelo robusto para estimativa de afinação vocal em música polifônica;", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "Selecione o algoritmo de extração de tom \n'pm': extração mais rápida, mas discurso de qualidade inferior; \n'harvest': graves melhores, mas extremamente lentos; \n'crepe': melhor qualidade (mas intensivo em GPU);\n rmvpe tem o melhor efeito e consome menos CPU/GPU.", + "采样率:": "采样率:", + "采样长度": "Comprimento da Amostra", + "重载设备列表": "Recarregar lista de dispositivos", + "音调设置": "Configurações de tom", + "音频设备": "音频设备", + "音高算法": "Algoritmo de detecção de pitch", + "额外推理时长": "Tempo extra de inferência" +} diff --git a/rvc/i18n/locale/ru_RU.json b/rvc/i18n/locale/ru_RU.json new file mode 100644 index 0000000..f75ebd1 --- /dev/null +++ b/rvc/i18n/locale/ru_RU.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": "Если значение больше 3: применить медианную фильтрацию к вытащенным тональностям. Значение контролирует радиус фильтра и может уменьшить излишнее дыхание.", + "A模型权重": "Весы (w) модели А:", + "A模型路径": "Путь к модели А:", + "B模型路径": "Путь к модели Б:", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "Файл дуги F0 (не обязательно). Одна тональность на каждую строчку. Заменяет обычный F0 и модуляцию тональности:", + "Index Rate": "Темп индекса", + "Onnx导出": "Экспорт ONNX", + "Onnx输出路径": "Путь для сохранения модели в формате ONNX:", + "RVC模型路径": "Путь к модели RVC:", + "ckpt处理": "Обработка ckpt", + "harvest进程数": "Количество процессор harvest", + "index文件路径不可包含中文": "Путь к файлу индекса", + "pth文件路径不可包含中文": "Путь к файлу pth", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "Введите номера графических процессоров, разделенные символом «-», например, 0-0-1, чтобы запустить два процесса на GPU 0 и один процесс на GPU 1:", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Шаг 1. Конфигурирование модели. Данные обучения модели сохраняются в папку 'logs', и для каждой модели создаётся отдельная папка. Введите вручную путь к настройкам для модели, в которой находятся логи и тренировочные файлы.", + "step1:正在处理数据": "Шаг 1. Переработка данных", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Шаг 2А. Автоматическая обработка исходных аудиозаписей для обучения и выполнение нормализации среза. Создаст 2 папки wav в папке модели. В данный момент поддерживается обучение только на одноголосных записях.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Шаг 2Б. Оценка и извлечение тональности в аудиофайлах с помощью процессора (если включена поддержка изменения высоты звука), извлечение черт с помощью GPU (выберите номер GPU):", + "step3: 填写训练设置, 开始训练模型和索引": "Шаг 3. Заполнение дополнительных настроек обучения и запуск обучения модели и индекса", + "step3a:正在训练模型": "Шаг 3. Запуск обучения модели", + "一键训练": "Обучение в одно нажатие", + "也可批量输入音频文件, 二选一, 优先读文件夹": "Можно также импортировать несколько аудиофайлов. Если путь к папке существует, то этот ввод игнорируется.", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Пакетная обработка для разделения вокального сопровождения с использованием модели UVR5.
Пример допустимого формата пути к папке: D:\\path\\to\\input\\folder
Модель разделена на три категории:
1. Сохранить вокал: выберите этот вариант для звука без гармоний. Он сохраняет вокал лучше, чем HP5. Он включает в себя две встроенные модели: HP2 и HP3. HP3 может немного пропускать инструментал, но сохраняет вокал немного лучше, чем HP2.
2. Сохранить только основной вокал: выберите этот вариант для звука с гармониями. Это может ослабить основной вокал. Он включает одну встроенную модель: HP5.
3. Модели удаления реверберации и задержки (от FoxJoy):
  (1) MDX-Net: лучший выбор для удаления стереореверберации, но он не может удалить монореверберацию;
 (234) DeEcho: удаляет эффекты задержки. Агрессивный режим удаляет более тщательно, чем Нормальный режим. DeReverb дополнительно удаляет реверберацию и может удалять монореверберацию, но не очень эффективно для сильно реверберированного высокочастотного контента.
Примечания по удалению реверберации/задержки:
1. Время обработки для модели DeEcho-DeReverb примерно в два раза больше, чем для двух других моделей DeEcho.
2. Модель MDX-Net-Dereverb довольно медленная.
3. Рекомендуемая самая чистая конфигурация — сначала применить MDX-Net, а затем DeEcho-Aggressive.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "Введите, какие(-ую) GPU(-у) хотите использовать через '-', например 0-1-2, чтобы использовать GPU с номерами 0, 1 и 2:", + "伴奏人声分离&去混响&去回声": "Разделение вокала/аккомпанемента и удаление эхо", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Имя файла для сохранения:", + "保存的文件名, 默认空为和源文件同名": "Название сохранённого файла (по умолчанию: такое же, как и у входного):", + "保存的模型名不带后缀": "Имя файла модели для сохранения (без расширения):", + "保存频率save_every_epoch": "Частота сохранения (save_every_epoch):", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Защитить глухие согласные и звуки дыхания для предотвращения артефактов, например, разрывания в электронной музыке. Поставьте на 0.5, чтобы выключить. Уменьшите значение для повышения защиты, но учтите, что при этом может ухудшиться точность индексирования:", + "修改": "Изменить", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Изменить информацию о модели (работает только с маленькими моделями, взятыми из папки 'weights')", + "停止音频转换": "Закончить конвертацию аудио", + "全流程结束!": "Все процессы завершены!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Обновить список голосов и индексов", + "加载模型": "Загрузить модель", + "加载预训练底模D路径": "Путь к предварительно обученной базовой модели D:", + "加载预训练底模G路径": "Путь к предварительно обученной базовой модели G:", + "单次推理": "单次推理", + "卸载音色省显存": "Выгрузить модель из памяти GPU для освобождения ресурсов", + "变调(整数, 半音数量, 升八度12降八度-12)": "Изменить высоту голоса (укажите количество полутонов; чтобы поднять голос на октаву, выберите 12, понизить на октаву — -12):", + "后处理重采样至最终采样率,0为不进行重采样": "Изменить частоту дискретизации в выходном файле на финальную. Поставьте 0, чтобы ничего не изменялось:", + "否": "Нет", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Порог ответа", + "响度因子": "коэффициент громкости", + "处理数据": "Обработать данные", + "导出Onnx模型": "Экспортировать модель", + "导出文件格式": "Формат выходных файлов", + "常见问题解答": "ЧаВо (часто задаваемые вопросы)", + "常规设置": "Основные настройки", + "开始音频转换": "Начать конвертацию аудио", + "很遗憾您这没有能用的显卡来支持您训练": "К сожалению, у вас нету графического процессора, который поддерживает обучение моделей.", + "性能设置": "Настройки быстроты", + "总训练轮数total_epoch": "Полное количество эпох (total_epoch):", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Массовое преобразование. Введите путь к папке, в которой находятся файлы для преобразования голоса или выгрузите несколько аудиофайлов. Сконвертированные файлы будут сохранены в указанной папке (по умолчанию: 'opt').", + "指定输出主人声文件夹": "Путь к папке для сохранения вокала:", + "指定输出文件夹": "Папка для результатов:", + "指定输出非主人声文件夹": "Путь к папке для сохранения аккомпанемента:", + "推理时间(ms):": "Время переработки (мс):", + "推理音色": "Желаемый голос:", + "提取": "Создать модель", + "提取音高和处理数据使用的CPU进程数": "Число процессов ЦП, используемое для оценки высоты голоса и обработки данных:", + "是": "Да", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Сохранять только последний файл '.ckpt', чтобы сохранить место на диске:", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Сохранять маленькую финальную модель в папку 'weights' на каждой точке сохранения:", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Кэшировать все тренировочные сеты в видеопамять. Кэширование маленький датасетов (меньше 10 минут) может ускорить тренировку, но кэширование больших, наоборот, займёт много видеопамяти и не сильно ускорит тренировку:", + "显卡信息": "Информация о графических процессорах (GPUs):", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "Это программное обеспечение с открытым исходным кодом распространяется по лицензии MIT. Автор никак не контролирует это программное обеспечение. Пользователи, которые используют эту программу и распространяют аудиозаписи, полученные с помощью этой программы, несут полную ответственность за это. Если вы не согласны с этим, вы не можете использовать какие-либо коды и файлы в рамках этой программы или ссылаться на них. Подробнее в файле Agreement-LICENSE.txt в корневом каталоге программы.", + "查看": "Просмотреть информацию", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "Просмотреть информацию о модели (работает только с маленькими моделями, взятыми из папки 'weights')", + "检索特征占比": "Соотношение поиска черт:", + "模型": "Модели", + "模型推理": "Изменение голоса", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Создание модели из данных, полученных в процессе обучения (введите путь к большому файлу модели в папке 'logs'). Может пригодиться, если вам нужно завершить обучение и получить маленький файл готовой модели, или если вам нужно проверить недообученную модель:", + "模型是否带音高指导": "Поддерживает ли модель изменение высоты голоса (1: да, 0: нет):", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Поддержка изменения высоты звука (обязательно для пения, необязательно для речи):", + "模型是否带音高指导,1是0否": "Поддерживает ли модель изменение высоты голоса (1: да, 0: нет):", + "模型版本型号": "Версия архитектуры модели:", + "模型融合, 可用于测试音色融合": "Слияние моделей, может быть использовано для проверки слияния тембра", + "模型路径": "Путь к папке:", + "每张显卡的batch_size": "Размер пачки для GPU:", + "淡入淡出长度": "Длина затухания", + "版本": "Версия архитектуры модели:", + "特征提取": "Извлечь черты", + "特征检索库文件路径,为空则使用下拉的选择结果": "Путь к файлу индекса черт. Оставьте пустым, чтобы использовать выбранный вариант из списка ниже:", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Рекомендуется выбрать +12 для конвертирования мужского голоса в женский и -12 для конвертирования женского в мужской. Если диапазон голоса слишком велик, и голос искажается, можно выбрать значение на свой вкус.", + "目标采样率": "Частота дискретизации аудио:", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "Автоматически найденные файлы индексов черт (выберите вариант из списка):", + "融合": "Запустить слияние", + "要改的模型信息": "Информация, которая будет изменена:", + "要置入的模型信息": "Информация о модели:", + "训练": "Обучение модели", + "训练模型": "Обучить модель", + "训练特征索引": "Обучить индекс черт", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Обучение модели завершено. Журнал обучения можно просмотреть в консоли или в файле 'train.log' в папке с моделью.", + "设备类型": "设备类型", + "请指定说话人id": "Номер говорящего/поющего:", + "请选择index文件": "Пожалуйста, выберите файл индекса", + "请选择pth文件": "Пожалуйста, выберите файл pth", + "请选择说话人id": "Номер говорящего:", + "转换": "Преобразовать", + "输入实验名": "Название модели:", + "输入待处理音频文件夹路径": "Путь к папке с аудиофайлами для обработки:", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "Путь к папке с аудиофайлами для переработки (можно скопировать путь из адресной строки файлового менеджера):", + "输入待处理音频文件路径(默认是正确格式示例)": "Путь к аудиофайлу, который хотите обработать (ниже указан пример пути к файлу):", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "Использовать громкость входного файла для замены или перемешивания с громкостью выходного файла. Чем ближе соотношение к 1, тем больше используется звука из выходного файла:", + "输入监听": "输入监听", + "输入训练文件夹路径": "Путь к папке с аудиозаписями, на которых будет обучаться модель:", + "输入设备": "Входное устройство", + "输入降噪": "Уменьшение входного шума", + "输出信息": "Статистика", + "输出变声": "输出变声", + "输出设备": "Выходное устройство", + "输出降噪": "Уменьшение выходного шума", + "输出音频(右下角三个点,点了可以下载)": "Аудиофайл (чтобы скачать, нажмите на три точки справа в плеере)", + "选择.index文件": "Выбрать файл .index", + "选择.pth文件": "Выбрать файл .pth", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Выберите алгоритм оценки высоты голоса ('pm': работает быстро, но даёт низкое качество речи; 'harvest': басы лучше, но работает очень медленно; 'crepe': лучшее качество, но сильно нагружает GPU; 'rmvpe': лучшее качество и минимальная нагрузка на GPU):", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "Длина сэмпла", + "重载设备列表": "Обновить список устройств", + "音调设置": "Настройка высоты звука", + "音频设备": "Аудиоустройство", + "音高算法": "Алгоритм оценки высоты звука", + "额外推理时长": "Доп. время переработки" +} diff --git a/rvc/i18n/locale/tr_TR.json b/rvc/i18n/locale/tr_TR.json new file mode 100644 index 0000000..e81eca4 --- /dev/null +++ b/rvc/i18n/locale/tr_TR.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": "Eğer >=3 ise, elde edilen pitch sonuçlarına median filtreleme uygula. Bu değer, filtre yarıçapını temsil eder ve nefesliliği azaltabilir.", + "A模型权重": "A Modeli Ağırlığı:", + "A模型路径": "A Modeli Yolu:", + "B模型路径": "B Modeli Yolu:", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0 eğrisi dosyası (isteğe bağlı). Her satırda bir pitch değeri bulunur. Varsayılan F0 ve pitch modülasyonunu değiştirir:", + "Index Rate": "Index Oranı", + "Onnx导出": "Onnx Dışa Aktar", + "Onnx输出路径": "Onnx Dışa Aktarım Yolu:", + "RVC模型路径": "RVC Model Yolu:", + "ckpt处理": "ckpt İşleme", + "harvest进程数": "harvest进程数", + "index文件路径不可包含中文": ".index dosya yolu Çince karakter içeremez", + "pth文件路径不可包含中文": ".pth dosya yolu Çince karakter içeremez", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "Adım 1: Deneysel yapılandırmayı doldurun. Deneysel veriler 'logs' klasöründe saklanır ve her bir deney için ayrı bir klasör vardır. Deneysel adı yolu manuel olarak girin; bu yol, deneysel yapılandırmayı, günlükleri ve eğitilmiş model dosyalarını içerir.", + "step1:正在处理数据": "Adım 1: Veri işleme", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "Adım 2a: Eğitim klasöründe ses dosyalarını otomatik olarak gezinerek dilimleme normalizasyonu yapın. Deney dizini içinde 2 wav klasörü oluşturur. Şu anda sadece tek kişilik eğitim desteklenmektedir.", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "Adım 2b: Ses yüksekliği (Pitch) çıkartmak için CPU kullanın (eğer model ses yüksekliği içeriyorsa), özellikleri çıkartmak için GPU kullanın (GPU indeksini seçin):", + "step3: 填写训练设置, 开始训练模型和索引": "Adım 3: Eğitim ayarlarını doldurun ve modeli ve dizini eğitmeye başlayın", + "step3a:正在训练模型": "Adım 3a: Model eğitimi başladı", + "一键训练": "Tek Tuşla Eğit", + "也可批量输入音频文件, 二选一, 优先读文件夹": "Ses dosyaları ayrıca toplu olarak, iki seçimle, öncelikli okuma klasörüyle içe aktarılabilir", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "Batch işleme kullanarak vokal eşlik ayrımı için UVR5 modeli kullanılır.
Geçerli bir klasör yol formatı örneği: D:\\path\\to\\input\\folder (dosya yöneticisi adres çubuğundan kopyalanır).
Model üç kategoriye ayrılır:
1. Vokalleri koru: Bu seçeneği, harmoni içermeyen sesler için kullanın. HP5'ten daha iyi bir şekilde vokalleri korur. İki dahili model içerir: HP2 ve HP3. HP3, eşlik sesini hafifçe sızdırabilir, ancak vokalleri HP2'den biraz daha iyi korur.
2. Sadece ana vokalleri koru: Bu seçeneği, harmoni içeren sesler için kullanın. Ana vokalleri zayıflatabilir. Bir dahili model içerir: HP5.
3. Reverb ve gecikme modelleri (FoxJoy tarafından):
  (1) MDX-Net: Stereo reverb'i kaldırmak için en iyi seçenek, ancak mono reverb'i kaldıramaz;
 (234) DeEcho: Gecikme efektlerini kaldırır. Agresif mod, Normal moda göre daha kapsamlı bir şekilde kaldırma yapar. DeReverb ayrıca reverb'i kaldırır ve mono reverb'i kaldırabilir, ancak yoğun yankılı yüksek frekanslı içerikler için çok etkili değildir.
Reverb/gecikme notları:
1. DeEcho-DeReverb modelinin işleme süresi diğer iki DeEcho modeline göre yaklaşık olarak iki kat daha uzundur.
2. MDX-Net-Dereverb modeli oldukça yavaştır.
3. Tavsiye edilen en temiz yapılandırma önce MDX-Net'i uygulamak ve ardından DeEcho-Aggressive uygulamaktır.", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "GPU indekslerini '-' ile ayırarak girin, örneğin 0-1-2, GPU 0, 1 ve 2'yi kullanmak için:", + "伴奏人声分离&去混响&去回声": "Vokal/Müzik Ayrıştırma ve Yankı Giderme", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "Kaydetme Adı:", + "保存的文件名, 默认空为和源文件同名": "Kaydedilecek dosya adı (varsayılan: kaynak dosya ile aynı):", + "保存的模型名不带后缀": "Kaydedilecek model adı (uzantı olmadan):", + "保存频率save_every_epoch": "Kaydetme sıklığı (save_every_epoch):", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "Sessiz ünsüzleri ve nefes seslerini koruyarak elektronik müzikte yırtılma gibi sanal hataların oluşmasını engeller. 0.5 olarak ayarlandığında devre dışı kalır. Değerin azaltılması korumayı artırabilir, ancak indeksleme doğruluğunu azaltabilir:", + "修改": "Düzenle", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "Model bilgilerini düzenle (sadece 'weights' klasöründen çıkarılan küçük model dosyaları desteklenir)", + "停止音频转换": "Ses dönüştürmeyi durdur", + "全流程结束!": "Tüm işlemler tamamlandı!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "Ses listesini ve indeks yolunu yenile", + "加载模型": "Model yükle", + "加载预训练底模D路径": "Önceden eğitilmiş temel D modelini yükleme yolu:", + "加载预训练底模G路径": "Önceden eğitilmiş temel G modelini yükleme yolu:", + "单次推理": "单次推理", + "卸载音色省显存": "GPU bellek kullanımını azaltmak için sesi kaldır", + "变调(整数, 半音数量, 升八度12降八度-12)": "Transpoze et (tamsayı, yarıton sayısıyla; bir oktav yükseltmek için: 12, bir oktav düşürmek için: -12):", + "后处理重采样至最终采样率,0为不进行重采样": "Son işleme aşamasında çıktı sesini son örnekleme hızına yeniden örnekle. 0 değeri için yeniden örnekleme yapılmaz:", + "否": "Hayır", + "启用相位声码器": "启用相位声码器", + "响应阈值": "Tepki eşiği", + "响度因子": "ses yüksekliği faktörü", + "处理数据": "Verileri işle", + "导出Onnx模型": "Onnx Modeli Dışa Aktar", + "导出文件格式": "Dışa aktarma dosya formatı", + "常见问题解答": "Sıkça Sorulan Sorular (SSS)", + "常规设置": "Genel ayarlar", + "开始音频转换": "Ses dönüştürmeyi başlat", + "很遗憾您这没有能用的显卡来支持您训练": "Maalesef, eğitiminizi desteklemek için uyumlu bir GPU bulunmamaktadır.", + "性能设置": "Performans ayarları", + "总训练轮数total_epoch": "Toplam eğitim turu (total_epoch):", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "Toplu dönüştür. Dönüştürülecek ses dosyalarının bulunduğu klasörü girin veya birden çok ses dosyasını yükleyin. Dönüştürülen ses dosyaları belirtilen klasöre ('opt' varsayılan olarak) dönüştürülecektir", + "指定输出主人声文件夹": "Vokal için çıkış klasörünü belirtin:", + "指定输出文件夹": "Çıkış klasörünü belirt:", + "指定输出非主人声文件夹": "Müzik ve diğer sesler için çıkış klasörünü belirtin:", + "推理时间(ms):": "Çıkarsama süresi (ms):", + "推理音色": "Ses çıkartma (Inference):", + "提取": "Çıkart", + "提取音高和处理数据使用的CPU进程数": "Ses yüksekliği çıkartmak (Pitch) ve verileri işlemek için kullanılacak CPU işlemci sayısı:", + "是": "Evet", + "是否仅保存最新的ckpt文件以节省硬盘空间": "Sadece en son '.ckpt' dosyasını kaydet:", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "Her kaydetme noktasında son küçük bir modeli 'weights' klasörüne kaydetmek için:", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "Tüm eğitim verilerini GPU belleğine önbelleğe alıp almayacağınızı belirtin. Küçük veri setlerini (10 dakikadan az) önbelleğe almak eğitimi hızlandırabilir, ancak büyük veri setlerini önbelleğe almak çok fazla GPU belleği tüketir ve çok fazla hız artışı sağlamaz:", + "显卡信息": "GPU Bilgisi", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "Bu yazılım, MIT lisansı altında açık kaynaklıdır. Yazarın yazılım üzerinde herhangi bir kontrolü yoktur. Yazılımı kullanan ve yazılım tarafından dışa aktarılan sesleri dağıtan kullanıcılar sorumludur.
Eğer bu maddeyle aynı fikirde değilseniz, yazılım paketi içindeki herhangi bir kod veya dosyayı kullanamaz veya referans göremezsiniz. Detaylar için kök dizindeki Agreement-LICENSE.txt dosyasına bakınız.", + "查看": "Görüntüle", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "Model bilgilerini görüntüle (sadece 'weights' klasöründen çıkarılan küçük model dosyaları desteklenir)", + "检索特征占比": "Arama özelliği oranı (vurgu gücünü kontrol eder, çok yüksek olması sanal etkilere neden olur)", + "模型": "Model", + "模型推理": "Model çıkartma (Inference)", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "Model çıkartma (büyük dosya modeli yolunu 'logs' klasöründe girin). Bu, eğitimi yarıda bırakmak istediğinizde ve manuel olarak küçük bir model dosyası çıkartmak ve kaydetmek istediğinizde veya bir ara modeli test etmek istediğinizde kullanışlıdır:", + "模型是否带音高指导": "Modelin ses yüksekliği rehberi içerip içermediği:", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "Modelin ses yüksekliği (Pitch) rehberliği içerip içermediği (şarkı söyleme için şarttır, konuşma için isteğe bağlıdır):", + "模型是否带音高指导,1是0否": "Modelin ses yüksekliği rehberi içerip içermediği (1: evet, 0: hayır):", + "模型版本型号": "Model mimari versiyonu:", + "模型融合, 可用于测试音色融合": "Model birleştirme, ses rengi birleştirmesi için kullanılabilir", + "模型路径": "Model Yolu:", + "每张显卡的batch_size": "Her GPU için yığın boyutu (batch_size):", + "淡入淡出长度": "Geçiş (Fade) uzunluğu", + "版本": "Sürüm", + "特征提取": "Özellik çıkartma", + "特征检索库文件路径,为空则使用下拉的选择结果": "Özellik indeksi dosyasının yolunu belirtin. Seçilen sonucu kullanmak için boş bırakın veya açılır menüden seçim yapın.", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "Erkekten kadına çevirmek için +12 tuş önerilir, kadından erkeğe çevirmek için ise -12 tuş önerilir. Eğer ses aralığı çok fazla genişler ve ses bozulursa, isteğe bağlı olarak uygun aralığa kendiniz de ayarlayabilirsiniz.", + "目标采样率": "Hedef örnekleme oranı:", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "İndeks yolunu otomatik olarak tespit et ve açılır menüden seçim yap.", + "融合": "Birleştir", + "要改的模型信息": "Düzenlenecek model bilgileri:", + "要置入的模型信息": "Eklemek için model bilgileri:", + "训练": "Eğitim", + "训练模型": "Modeli Eğit", + "训练特征索引": "Özellik Dizinini Eğit", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "Eğitim tamamlandı. Eğitim günlüklerini konsolda veya deney klasörü altındaki train.log dosyasında kontrol edebilirsiniz.", + "设备类型": "设备类型", + "请指定说话人id": "Lütfen konuşmacı/sanatçı no belirtin:", + "请选择index文件": "Lütfen .index dosyası seçin", + "请选择pth文件": "Lütfen .pth dosyası seçin", + "请选择说话人id": "Konuşmacı/Şarkıcı No seçin:", + "转换": "Dönüştür", + "输入实验名": "Deneysel adı girin:", + "输入待处理音频文件夹路径": "İşlenecek ses klasörünün yolunu girin:", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "İşlenecek ses klasörünün yolunu girin (dosya yöneticisinin adres çubuğundan kopyalayın):", + "输入待处理音频文件路径(默认是正确格式示例)": "İşlenecek ses dosyasının yolunu girin (varsayılan doğru format örneğidir):", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "Sesin hacim zarfını ayarlayın. 0'a yakın değerler, sesin orijinal vokallerin hacmine benzer olmasını sağlar. Düşük bir değerle ses gürültüsünü maskeleyebilir ve hacmi daha doğal bir şekilde duyulabilir hale getirebilirsiniz. 1'e yaklaştıkça sürekli bir yüksek ses seviyesi elde edilir:", + "输入监听": "输入监听", + "输入训练文件夹路径": "Eğitim klasörünün yolunu girin:", + "输入设备": "Giriş cihazı", + "输入降噪": "Giriş gürültü azaltma", + "输出信息": "Çıkış bilgisi", + "输出变声": "输出变声", + "输出设备": "Çıkış cihazı", + "输出降噪": "Çıkış gürültü azaltma", + "输出音频(右下角三个点,点了可以下载)": "Ses dosyasını dışa aktar (indirmek için sağ alt köşedeki üç noktaya tıklayın)", + "选择.index文件": ".index dosyası seç", + "选择.pth文件": ".pth dosyası seç", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "Pitch algoritmasını seçin ('pm': daha hızlı çıkarır ancak daha düşük kaliteli konuşma; 'harvest': daha iyi konuşma sesi ancak son derece yavaş; 'crepe': daha da iyi kalite ancak GPU yoğunluğu gerektirir):", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "Örnekleme uzunluğu", + "重载设备列表": "Cihaz listesini yeniden yükle", + "音调设置": "Pitch ayarları", + "音频设备": "Ses cihazı", + "音高算法": "音高算法", + "额外推理时长": "Ekstra çıkartma süresi" +} diff --git a/rvc/i18n/locale/zh_CN.json b/rvc/i18n/locale/zh_CN.json new file mode 100644 index 0000000..8d84760 --- /dev/null +++ b/rvc/i18n/locale/zh_CN.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音", + "A模型权重": "A模型权重", + "A模型路径": "A模型路径", + "B模型路径": "B模型路径", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调", + "Index Rate": "Index Rate", + "Onnx导出": "Onnx导出", + "Onnx输出路径": "Onnx输出路径", + "RVC模型路径": "RVC模型路径", + "ckpt处理": "ckpt处理", + "harvest进程数": "harvest进程数", + "index文件路径不可包含中文": "index文件路径不可包含中文", + "pth文件路径不可包含中文": "pth文件路径不可包含中文", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ", + "step1:正在处理数据": "step1:正在处理数据", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)", + "step3: 填写训练设置, 开始训练模型和索引": "step3: 填写训练设置, 开始训练模型和索引", + "step3a:正在训练模型": "step3a:正在训练模型", + "一键训练": "一键训练", + "也可批量输入音频文件, 二选一, 优先读文件夹": "也可批量输入音频文件, 二选一, 优先读文件夹", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2", + "伴奏人声分离&去混响&去回声": "伴奏人声分离&去混响&去回声", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "保存名", + "保存的文件名, 默认空为和源文件同名": "保存的文件名, 默认空为和源文件同名", + "保存的模型名不带后缀": "保存的模型名不带后缀", + "保存频率save_every_epoch": "保存频率save_every_epoch", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果", + "修改": "修改", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "修改模型信息(仅支持weights文件夹下提取的小模型文件)", + "停止音频转换": "停止音频转换", + "全流程结束!": "全流程结束!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "刷新音色列表和索引路径", + "加载模型": "加载模型", + "加载预训练底模D路径": "加载预训练底模D路径", + "加载预训练底模G路径": "加载预训练底模G路径", + "单次推理": "单次推理", + "卸载音色省显存": "卸载音色省显存", + "变调(整数, 半音数量, 升八度12降八度-12)": "变调(整数, 半音数量, 升八度12降八度-12)", + "后处理重采样至最终采样率,0为不进行重采样": "后处理重采样至最终采样率,0为不进行重采样", + "否": "否", + "启用相位声码器": "启用相位声码器", + "响应阈值": "响应阈值", + "响度因子": "响度因子", + "处理数据": "处理数据", + "导出Onnx模型": "导出Onnx模型", + "导出文件格式": "导出文件格式", + "常见问题解答": "常见问题解答", + "常规设置": "常规设置", + "开始音频转换": "开始音频转换", + "很遗憾您这没有能用的显卡来支持您训练": "很遗憾您这没有能用的显卡来支持您训练", + "性能设置": "性能设置", + "总训练轮数total_epoch": "总训练轮数total_epoch", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ", + "指定输出主人声文件夹": "指定输出主人声文件夹", + "指定输出文件夹": "指定输出文件夹", + "指定输出非主人声文件夹": "指定输出非主人声文件夹", + "推理时间(ms):": "推理时间(ms):", + "推理音色": "推理音色", + "提取": "提取", + "提取音高和处理数据使用的CPU进程数": "提取音高和处理数据使用的CPU进程数", + "是": "是", + "是否仅保存最新的ckpt文件以节省硬盘空间": "是否仅保存最新的ckpt文件以节省硬盘空间", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "是否在每次保存时间点将最终小模型保存至weights文件夹", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速", + "显卡信息": "显卡信息", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.", + "查看": "查看", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "查看模型信息(仅支持weights文件夹下提取的小模型文件)", + "检索特征占比": "检索特征占比", + "模型": "模型", + "模型推理": "模型推理", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况", + "模型是否带音高指导": "模型是否带音高指导", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "模型是否带音高指导(唱歌一定要, 语音可以不要)", + "模型是否带音高指导,1是0否": "模型是否带音高指导,1是0否", + "模型版本型号": "模型版本型号", + "模型融合, 可用于测试音色融合": "模型融合, 可用于测试音色融合", + "模型路径": "模型路径", + "每张显卡的batch_size": "每张显卡的batch_size", + "淡入淡出长度": "淡入淡出长度", + "版本": "版本", + "特征提取": "特征提取", + "特征检索库文件路径,为空则使用下拉的选择结果": "特征检索库文件路径,为空则使用下拉的选择结果", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ", + "目标采样率": "目标采样率", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "自动检测index路径,下拉式选择(dropdown)", + "融合": "融合", + "要改的模型信息": "要改的模型信息", + "要置入的模型信息": "要置入的模型信息", + "训练": "训练", + "训练模型": "训练模型", + "训练特征索引": "训练特征索引", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log", + "设备类型": "设备类型", + "请指定说话人id": "请指定说话人id", + "请选择index文件": "请选择index文件", + "请选择pth文件": "请选择pth文件", + "请选择说话人id": "请选择说话人id", + "转换": "转换", + "输入实验名": "输入实验名", + "输入待处理音频文件夹路径": "输入待处理音频文件夹路径", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)", + "输入待处理音频文件路径(默认是正确格式示例)": "输入待处理音频文件路径(默认是正确格式示例)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络", + "输入监听": "输入监听", + "输入训练文件夹路径": "输入训练文件夹路径", + "输入设备": "输入设备", + "输入降噪": "输入降噪", + "输出信息": "输出信息", + "输出变声": "输出变声", + "输出设备": "输出设备", + "输出降噪": "输出降噪", + "输出音频(右下角三个点,点了可以下载)": "输出音频(右下角三个点,点了可以下载)", + "选择.index文件": "选择.index文件", + "选择.pth文件": "选择.pth文件", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "采样长度", + "重载设备列表": "重载设备列表", + "音调设置": "音调设置", + "音频设备": "音频设备", + "音高算法": "音高算法", + "额外推理时长": "额外推理时长" +} diff --git a/rvc/i18n/locale/zh_HK.json b/rvc/i18n/locale/zh_HK.json new file mode 100644 index 0000000..4b3774a --- /dev/null +++ b/rvc/i18n/locale/zh_HK.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3則使用對harvest音高識別的結果使用中值濾波,數值為濾波半徑,使用可以削弱啞音", + "A模型权重": "A模型權重", + "A模型路径": "A模型路徑", + "B模型路径": "B模型路徑", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0曲線檔案,可選,一行一個音高,代替預設的F0及升降調", + "Index Rate": "Index Rate", + "Onnx导出": "Onnx导出", + "Onnx输出路径": "Onnx输出路径", + "RVC模型路径": "RVC模型路径", + "ckpt处理": "ckpt處理", + "harvest进程数": "harvest進程數", + "index文件路径不可包含中文": "index文件路径不可包含中文", + "pth文件路径不可包含中文": "pth文件路径不可包含中文", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe卡號配置:以-分隔輸入使用的不同進程卡號,例如0-0-1使用在卡0上跑2個進程並在卡1上跑1個進程", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "step1:填寫實驗配置。實驗數據放在logs下,每個實驗一個資料夾,需手動輸入實驗名路徑,內含實驗配置、日誌、訓練得到的模型檔案。", + "step1:正在处理数据": "step1:正在处理数据", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "step2a:自動遍歷訓練資料夾下所有可解碼成音頻的檔案並進行切片歸一化,在實驗目錄下生成2個wav資料夾;暫時只支援單人訓練。", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "步驟2b: 使用CPU提取音高(如果模型帶音高), 使用GPU提取特徵(選擇卡號)", + "step3: 填写训练设置, 开始训练模型和索引": "步驟3: 填寫訓練設定, 開始訓練模型和索引", + "step3a:正在训练模型": "step3a:正在训练模型", + "一键训练": "一鍵訓練", + "也可批量输入音频文件, 二选一, 优先读文件夹": "也可批量输入音频文件, 二选一, 优先读文件夹", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "使用UVR5模型進行人聲伴奏分離的批次處理。
有效資料夾路徑格式的例子:D:\\path\\to\\input\\folder(從檔案管理員地址欄複製)。
模型分為三類:
1. 保留人聲:選擇這個選項適用於沒有和聲的音訊。它比HP5更好地保留了人聲。它包括兩個內建模型:HP2和HP3。HP3可能輕微漏出伴奏,但比HP2更好地保留了人聲;
2. 僅保留主人聲:選擇這個選項適用於有和聲的音訊。它可能會削弱主人聲。它包括一個內建模型:HP5。
3. 消除混響和延遲模型(由FoxJoy提供):
  (1) MDX-Net:對於立體聲混響的移除是最好的選擇,但不能移除單聲道混響;
 (234) DeEcho:移除延遲效果。Aggressive模式比Normal模式移除得更徹底。DeReverb另外移除混響,可以移除單聲道混響,但對於高頻重的板式混響移除不乾淨。
消除混響/延遲注意事項:
1. DeEcho-DeReverb模型的處理時間是其他兩個DeEcho模型的近兩倍;
2. MDX-Net-Dereverb模型相當慢;
3. 個人推薦的最乾淨配置是先使用MDX-Net,然後使用DeEcho-Aggressive。", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "以-分隔輸入使用的卡號, 例如 0-1-2 使用卡0和卡1和卡2", + "伴奏人声分离&去混响&去回声": "伴奏人聲分離&去混響&去回聲", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "儲存名", + "保存的文件名, 默认空为和源文件同名": "儲存的檔案名,預設空為與來源檔案同名", + "保存的模型名不带后缀": "儲存的模型名不帶副檔名", + "保存频率save_every_epoch": "保存頻率save_every_epoch", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "保護清輔音和呼吸聲,防止電音撕裂等artifact,拉滿0.5不開啟,調低加大保護力度但可能降低索引效果", + "修改": "修改", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "修改模型資訊(僅支援weights資料夾下提取的小模型檔案)", + "停止音频转换": "停止音訊轉換", + "全流程结束!": "全流程结束!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "刷新音色列表和索引路徑", + "加载模型": "載入模型", + "加载预训练底模D路径": "加載預訓練底模D路徑", + "加载预训练底模G路径": "加載預訓練底模G路徑", + "单次推理": "单次推理", + "卸载音色省显存": "卸載音色節省 VRAM", + "变调(整数, 半音数量, 升八度12降八度-12)": "變調(整數、半音數量、升八度12降八度-12)", + "后处理重采样至最终采样率,0为不进行重采样": "後處理重採樣至最終採樣率,0為不進行重採樣", + "否": "否", + "启用相位声码器": "启用相位声码器", + "响应阈值": "響應閾值", + "响度因子": "響度因子", + "处理数据": "處理資料", + "导出Onnx模型": "导出Onnx模型", + "导出文件格式": "導出檔格式", + "常见问题解答": "常見問題解答", + "常规设置": "一般設定", + "开始音频转换": "開始音訊轉換", + "很遗憾您这没有能用的显卡来支持您训练": "很遗憾您这没有能用的显卡来支持您训练", + "性能设置": "效能設定", + "总训练轮数total_epoch": "總訓練輪數total_epoch", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "批量轉換,輸入待轉換音頻資料夾,或上傳多個音頻檔案,在指定資料夾(默認opt)下輸出轉換的音頻。", + "指定输出主人声文件夹": "指定输出主人声文件夹", + "指定输出文件夹": "指定輸出資料夾", + "指定输出非主人声文件夹": "指定输出非主人声文件夹", + "推理时间(ms):": "推理時間(ms):", + "推理音色": "推理音色", + "提取": "提取", + "提取音高和处理数据使用的CPU进程数": "提取音高和處理數據使用的CPU進程數", + "是": "是", + "是否仅保存最新的ckpt文件以节省硬盘空间": "是否僅保存最新的ckpt檔案以節省硬碟空間", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "是否在每次保存時間點將最終小模型保存至weights檔夾", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "是否緩存所有訓練集至 VRAM。小於10分鐘的小數據可緩存以加速訓練,大數據緩存會爆 VRAM 也加不了多少速度", + "显卡信息": "顯示卡資訊", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "本軟體以MIT協議開源,作者不對軟體具備任何控制力,使用軟體者、傳播軟體導出的聲音者自負全責。
如不認可該條款,則不能使用或引用軟體包內任何程式碼和檔案。詳見根目錄使用需遵守的協議-LICENSE.txt。", + "查看": "查看", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "查看模型資訊(僅支援weights資料夾下提取的小模型檔案)", + "检索特征占比": "檢索特徵佔比", + "模型": "模型", + "模型推理": "模型推理", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "模型提取(輸入logs資料夾下大檔案模型路徑),適用於訓一半不想訓了模型沒有自動提取儲存小檔案模型,或者想測試中間模型的情況", + "模型是否带音高指导": "模型是否帶音高指導", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "模型是否帶音高指導(唱歌一定要,語音可以不要)", + "模型是否带音高指导,1是0否": "模型是否帶音高指導,1是0否", + "模型版本型号": "模型版本型號", + "模型融合, 可用于测试音色融合": "模型融合,可用於測試音色融合", + "模型路径": "模型路徑", + "每张显卡的batch_size": "每张显卡的batch_size", + "淡入淡出长度": "淡入淡出長度", + "版本": "版本", + "特征提取": "特徵提取", + "特征检索库文件路径,为空则使用下拉的选择结果": "特徵檢索庫檔路徑,為空則使用下拉的選擇結果", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "男性轉女性推薦+12key,女性轉男性推薦-12key,如果音域爆炸導致音色失真也可以自己調整到合適音域。", + "目标采样率": "目標取樣率", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "自動檢測index路徑,下拉式選擇(dropdown)", + "融合": "融合", + "要改的模型信息": "要改的模型資訊", + "要置入的模型信息": "要置入的模型資訊", + "训练": "訓練", + "训练模型": "訓練模型", + "训练特征索引": "訓練特徵索引", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log", + "设备类型": "设备类型", + "请指定说话人id": "請指定說話人id", + "请选择index文件": "请选择index文件", + "请选择pth文件": "请选择pth文件", + "请选择说话人id": "請選擇說話人ID", + "转换": "轉換", + "输入实验名": "輸入實驗名稱", + "输入待处理音频文件夹路径": "輸入待處理音頻資料夾路徑", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "輸入待處理音頻資料夾路徑(去檔案管理器地址欄拷貝即可)", + "输入待处理音频文件路径(默认是正确格式示例)": "輸入待處理音頻檔案路徑(預設是正確格式示例)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "輸入源音量包絡替換輸出音量包絡融合比例,越靠近1越使用輸出包絡", + "输入监听": "输入监听", + "输入训练文件夹路径": "輸入訓練檔案夾路徑", + "输入设备": "輸入設備", + "输入降噪": "輸入降噪", + "输出信息": "輸出訊息", + "输出变声": "输出变声", + "输出设备": "輸出設備", + "输出降噪": "輸出降噪", + "输出音频(右下角三个点,点了可以下载)": "輸出音頻(右下角三個點,點了可以下載)", + "选择.index文件": "選擇 .index 檔案", + "选择.pth文件": "選擇 .pth 檔案", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "選擇音高提取演算法,輸入歌聲可用pm提速,harvest低音好但巨慢無比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "取樣長度", + "重载设备列表": "重載設備列表", + "音调设置": "音調設定", + "音频设备": "音訊設備", + "音高算法": "音高演算法", + "额外推理时长": "額外推理時長" +} diff --git a/rvc/i18n/locale/zh_SG.json b/rvc/i18n/locale/zh_SG.json new file mode 100644 index 0000000..4b3774a --- /dev/null +++ b/rvc/i18n/locale/zh_SG.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3則使用對harvest音高識別的結果使用中值濾波,數值為濾波半徑,使用可以削弱啞音", + "A模型权重": "A模型權重", + "A模型路径": "A模型路徑", + "B模型路径": "B模型路徑", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0曲線檔案,可選,一行一個音高,代替預設的F0及升降調", + "Index Rate": "Index Rate", + "Onnx导出": "Onnx导出", + "Onnx输出路径": "Onnx输出路径", + "RVC模型路径": "RVC模型路径", + "ckpt处理": "ckpt處理", + "harvest进程数": "harvest進程數", + "index文件路径不可包含中文": "index文件路径不可包含中文", + "pth文件路径不可包含中文": "pth文件路径不可包含中文", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe卡號配置:以-分隔輸入使用的不同進程卡號,例如0-0-1使用在卡0上跑2個進程並在卡1上跑1個進程", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "step1:填寫實驗配置。實驗數據放在logs下,每個實驗一個資料夾,需手動輸入實驗名路徑,內含實驗配置、日誌、訓練得到的模型檔案。", + "step1:正在处理数据": "step1:正在处理数据", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "step2a:自動遍歷訓練資料夾下所有可解碼成音頻的檔案並進行切片歸一化,在實驗目錄下生成2個wav資料夾;暫時只支援單人訓練。", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "步驟2b: 使用CPU提取音高(如果模型帶音高), 使用GPU提取特徵(選擇卡號)", + "step3: 填写训练设置, 开始训练模型和索引": "步驟3: 填寫訓練設定, 開始訓練模型和索引", + "step3a:正在训练模型": "step3a:正在训练模型", + "一键训练": "一鍵訓練", + "也可批量输入音频文件, 二选一, 优先读文件夹": "也可批量输入音频文件, 二选一, 优先读文件夹", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "使用UVR5模型進行人聲伴奏分離的批次處理。
有效資料夾路徑格式的例子:D:\\path\\to\\input\\folder(從檔案管理員地址欄複製)。
模型分為三類:
1. 保留人聲:選擇這個選項適用於沒有和聲的音訊。它比HP5更好地保留了人聲。它包括兩個內建模型:HP2和HP3。HP3可能輕微漏出伴奏,但比HP2更好地保留了人聲;
2. 僅保留主人聲:選擇這個選項適用於有和聲的音訊。它可能會削弱主人聲。它包括一個內建模型:HP5。
3. 消除混響和延遲模型(由FoxJoy提供):
  (1) MDX-Net:對於立體聲混響的移除是最好的選擇,但不能移除單聲道混響;
 (234) DeEcho:移除延遲效果。Aggressive模式比Normal模式移除得更徹底。DeReverb另外移除混響,可以移除單聲道混響,但對於高頻重的板式混響移除不乾淨。
消除混響/延遲注意事項:
1. DeEcho-DeReverb模型的處理時間是其他兩個DeEcho模型的近兩倍;
2. MDX-Net-Dereverb模型相當慢;
3. 個人推薦的最乾淨配置是先使用MDX-Net,然後使用DeEcho-Aggressive。", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "以-分隔輸入使用的卡號, 例如 0-1-2 使用卡0和卡1和卡2", + "伴奏人声分离&去混响&去回声": "伴奏人聲分離&去混響&去回聲", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "儲存名", + "保存的文件名, 默认空为和源文件同名": "儲存的檔案名,預設空為與來源檔案同名", + "保存的模型名不带后缀": "儲存的模型名不帶副檔名", + "保存频率save_every_epoch": "保存頻率save_every_epoch", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "保護清輔音和呼吸聲,防止電音撕裂等artifact,拉滿0.5不開啟,調低加大保護力度但可能降低索引效果", + "修改": "修改", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "修改模型資訊(僅支援weights資料夾下提取的小模型檔案)", + "停止音频转换": "停止音訊轉換", + "全流程结束!": "全流程结束!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "刷新音色列表和索引路徑", + "加载模型": "載入模型", + "加载预训练底模D路径": "加載預訓練底模D路徑", + "加载预训练底模G路径": "加載預訓練底模G路徑", + "单次推理": "单次推理", + "卸载音色省显存": "卸載音色節省 VRAM", + "变调(整数, 半音数量, 升八度12降八度-12)": "變調(整數、半音數量、升八度12降八度-12)", + "后处理重采样至最终采样率,0为不进行重采样": "後處理重採樣至最終採樣率,0為不進行重採樣", + "否": "否", + "启用相位声码器": "启用相位声码器", + "响应阈值": "響應閾值", + "响度因子": "響度因子", + "处理数据": "處理資料", + "导出Onnx模型": "导出Onnx模型", + "导出文件格式": "導出檔格式", + "常见问题解答": "常見問題解答", + "常规设置": "一般設定", + "开始音频转换": "開始音訊轉換", + "很遗憾您这没有能用的显卡来支持您训练": "很遗憾您这没有能用的显卡来支持您训练", + "性能设置": "效能設定", + "总训练轮数total_epoch": "總訓練輪數total_epoch", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "批量轉換,輸入待轉換音頻資料夾,或上傳多個音頻檔案,在指定資料夾(默認opt)下輸出轉換的音頻。", + "指定输出主人声文件夹": "指定输出主人声文件夹", + "指定输出文件夹": "指定輸出資料夾", + "指定输出非主人声文件夹": "指定输出非主人声文件夹", + "推理时间(ms):": "推理時間(ms):", + "推理音色": "推理音色", + "提取": "提取", + "提取音高和处理数据使用的CPU进程数": "提取音高和處理數據使用的CPU進程數", + "是": "是", + "是否仅保存最新的ckpt文件以节省硬盘空间": "是否僅保存最新的ckpt檔案以節省硬碟空間", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "是否在每次保存時間點將最終小模型保存至weights檔夾", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "是否緩存所有訓練集至 VRAM。小於10分鐘的小數據可緩存以加速訓練,大數據緩存會爆 VRAM 也加不了多少速度", + "显卡信息": "顯示卡資訊", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "本軟體以MIT協議開源,作者不對軟體具備任何控制力,使用軟體者、傳播軟體導出的聲音者自負全責。
如不認可該條款,則不能使用或引用軟體包內任何程式碼和檔案。詳見根目錄使用需遵守的協議-LICENSE.txt。", + "查看": "查看", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "查看模型資訊(僅支援weights資料夾下提取的小模型檔案)", + "检索特征占比": "檢索特徵佔比", + "模型": "模型", + "模型推理": "模型推理", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "模型提取(輸入logs資料夾下大檔案模型路徑),適用於訓一半不想訓了模型沒有自動提取儲存小檔案模型,或者想測試中間模型的情況", + "模型是否带音高指导": "模型是否帶音高指導", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "模型是否帶音高指導(唱歌一定要,語音可以不要)", + "模型是否带音高指导,1是0否": "模型是否帶音高指導,1是0否", + "模型版本型号": "模型版本型號", + "模型融合, 可用于测试音色融合": "模型融合,可用於測試音色融合", + "模型路径": "模型路徑", + "每张显卡的batch_size": "每张显卡的batch_size", + "淡入淡出长度": "淡入淡出長度", + "版本": "版本", + "特征提取": "特徵提取", + "特征检索库文件路径,为空则使用下拉的选择结果": "特徵檢索庫檔路徑,為空則使用下拉的選擇結果", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "男性轉女性推薦+12key,女性轉男性推薦-12key,如果音域爆炸導致音色失真也可以自己調整到合適音域。", + "目标采样率": "目標取樣率", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "自動檢測index路徑,下拉式選擇(dropdown)", + "融合": "融合", + "要改的模型信息": "要改的模型資訊", + "要置入的模型信息": "要置入的模型資訊", + "训练": "訓練", + "训练模型": "訓練模型", + "训练特征索引": "訓練特徵索引", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log", + "设备类型": "设备类型", + "请指定说话人id": "請指定說話人id", + "请选择index文件": "请选择index文件", + "请选择pth文件": "请选择pth文件", + "请选择说话人id": "請選擇說話人ID", + "转换": "轉換", + "输入实验名": "輸入實驗名稱", + "输入待处理音频文件夹路径": "輸入待處理音頻資料夾路徑", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "輸入待處理音頻資料夾路徑(去檔案管理器地址欄拷貝即可)", + "输入待处理音频文件路径(默认是正确格式示例)": "輸入待處理音頻檔案路徑(預設是正確格式示例)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "輸入源音量包絡替換輸出音量包絡融合比例,越靠近1越使用輸出包絡", + "输入监听": "输入监听", + "输入训练文件夹路径": "輸入訓練檔案夾路徑", + "输入设备": "輸入設備", + "输入降噪": "輸入降噪", + "输出信息": "輸出訊息", + "输出变声": "输出变声", + "输出设备": "輸出設備", + "输出降噪": "輸出降噪", + "输出音频(右下角三个点,点了可以下载)": "輸出音頻(右下角三個點,點了可以下載)", + "选择.index文件": "選擇 .index 檔案", + "选择.pth文件": "選擇 .pth 檔案", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "選擇音高提取演算法,輸入歌聲可用pm提速,harvest低音好但巨慢無比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "取樣長度", + "重载设备列表": "重載設備列表", + "音调设置": "音調設定", + "音频设备": "音訊設備", + "音高算法": "音高演算法", + "额外推理时长": "額外推理時長" +} diff --git a/rvc/i18n/locale/zh_TW.json b/rvc/i18n/locale/zh_TW.json new file mode 100644 index 0000000..4b3774a --- /dev/null +++ b/rvc/i18n/locale/zh_TW.json @@ -0,0 +1,138 @@ +{ + ">=3则使用对harvest音高识别的结果使用中值滤波,数值为滤波半径,使用可以削弱哑音": ">=3則使用對harvest音高識別的結果使用中值濾波,數值為濾波半徑,使用可以削弱啞音", + "A模型权重": "A模型權重", + "A模型路径": "A模型路徑", + "B模型路径": "B模型路徑", + "E:\\语音音频+标注\\米津玄师\\src": "E:\\语音音频+标注\\米津玄师\\src", + "F0曲线文件, 可选, 一行一个音高, 代替默认F0及升降调": "F0曲線檔案,可選,一行一個音高,代替預設的F0及升降調", + "Index Rate": "Index Rate", + "Onnx导出": "Onnx导出", + "Onnx输出路径": "Onnx输出路径", + "RVC模型路径": "RVC模型路径", + "ckpt处理": "ckpt處理", + "harvest进程数": "harvest進程數", + "index文件路径不可包含中文": "index文件路径不可包含中文", + "pth文件路径不可包含中文": "pth文件路径不可包含中文", + "rmvpe卡号配置:以-分隔输入使用的不同进程卡号,例如0-0-1使用在卡0上跑2个进程并在卡1上跑1个进程": "rmvpe卡號配置:以-分隔輸入使用的不同進程卡號,例如0-0-1使用在卡0上跑2個進程並在卡1上跑1個進程", + "step1: 填写实验配置. 实验数据放在logs下, 每个实验一个文件夹, 需手工输入实验名路径, 内含实验配置, 日志, 训练得到的模型文件. ": "step1:填寫實驗配置。實驗數據放在logs下,每個實驗一個資料夾,需手動輸入實驗名路徑,內含實驗配置、日誌、訓練得到的模型檔案。", + "step1:正在处理数据": "step1:正在处理数据", + "step2:正在提取音高&正在提取特征": "step2:正在提取音高&正在提取特征", + "step2a: 自动遍历训练文件夹下所有可解码成音频的文件并进行切片归一化, 在实验目录下生成2个wav文件夹; 暂时只支持单人训练. ": "step2a:自動遍歷訓練資料夾下所有可解碼成音頻的檔案並進行切片歸一化,在實驗目錄下生成2個wav資料夾;暫時只支援單人訓練。", + "step2b: 使用CPU提取音高(如果模型带音高), 使用GPU提取特征(选择卡号)": "步驟2b: 使用CPU提取音高(如果模型帶音高), 使用GPU提取特徵(選擇卡號)", + "step3: 填写训练设置, 开始训练模型和索引": "步驟3: 填寫訓練設定, 開始訓練模型和索引", + "step3a:正在训练模型": "step3a:正在训练模型", + "一键训练": "一鍵訓練", + "也可批量输入音频文件, 二选一, 优先读文件夹": "也可批量输入音频文件, 二选一, 优先读文件夹", + "人声伴奏分离批量处理, 使用UVR5模型。
合格的文件夹路径格式举例: E:\\codes\\py39\\vits_vc_gpu\\白鹭霜华测试样例(去文件管理器地址栏拷就行了)。
模型分为三类:
1、保留人声:不带和声的音频选这个,对主人声保留比HP5更好。内置HP2和HP3两个模型,HP3可能轻微漏伴奏但对主人声保留比HP2稍微好一丁点;
2、仅保留主人声:带和声的音频选这个,对主人声可能有削弱。内置HP5一个模型;
3、去混响、去延迟模型(by FoxJoy):
  (1)MDX-Net(onnx_dereverb):对于双通道混响是最好的选择,不能去除单通道混响;
 (234)DeEcho:去除延迟效果。Aggressive比Normal去除得更彻底,DeReverb额外去除混响,可去除单声道混响,但是对高频重的板式混响去不干净。
去混响/去延迟,附:
1、DeEcho-DeReverb模型的耗时是另外2个DeEcho模型的接近2倍;
2、MDX-Net-Dereverb模型挺慢的;
3、个人推荐的最干净的配置是先MDX-Net再DeEcho-Aggressive。": "使用UVR5模型進行人聲伴奏分離的批次處理。
有效資料夾路徑格式的例子:D:\\path\\to\\input\\folder(從檔案管理員地址欄複製)。
模型分為三類:
1. 保留人聲:選擇這個選項適用於沒有和聲的音訊。它比HP5更好地保留了人聲。它包括兩個內建模型:HP2和HP3。HP3可能輕微漏出伴奏,但比HP2更好地保留了人聲;
2. 僅保留主人聲:選擇這個選項適用於有和聲的音訊。它可能會削弱主人聲。它包括一個內建模型:HP5。
3. 消除混響和延遲模型(由FoxJoy提供):
  (1) MDX-Net:對於立體聲混響的移除是最好的選擇,但不能移除單聲道混響;
 (234) DeEcho:移除延遲效果。Aggressive模式比Normal模式移除得更徹底。DeReverb另外移除混響,可以移除單聲道混響,但對於高頻重的板式混響移除不乾淨。
消除混響/延遲注意事項:
1. DeEcho-DeReverb模型的處理時間是其他兩個DeEcho模型的近兩倍;
2. MDX-Net-Dereverb模型相當慢;
3. 個人推薦的最乾淨配置是先使用MDX-Net,然後使用DeEcho-Aggressive。", + "以-分隔输入使用的卡号, 例如 0-1-2 使用卡0和卡1和卡2": "以-分隔輸入使用的卡號, 例如 0-1-2 使用卡0和卡1和卡2", + "伴奏人声分离&去混响&去回声": "伴奏人聲分離&去混響&去回聲", + "使用模型采样率": "使用模型采样率", + "使用设备采样率": "使用设备采样率", + "保存名": "儲存名", + "保存的文件名, 默认空为和源文件同名": "儲存的檔案名,預設空為與來源檔案同名", + "保存的模型名不带后缀": "儲存的模型名不帶副檔名", + "保存频率save_every_epoch": "保存頻率save_every_epoch", + "保护清辅音和呼吸声,防止电音撕裂等artifact,拉满0.5不开启,调低加大保护力度但可能降低索引效果": "保護清輔音和呼吸聲,防止電音撕裂等artifact,拉滿0.5不開啟,調低加大保護力度但可能降低索引效果", + "修改": "修改", + "修改模型信息(仅支持weights文件夹下提取的小模型文件)": "修改模型資訊(僅支援weights資料夾下提取的小模型檔案)", + "停止音频转换": "停止音訊轉換", + "全流程结束!": "全流程结束!", + "共振偏移": "共振偏移", + "刷新音色列表和索引路径": "刷新音色列表和索引路徑", + "加载模型": "載入模型", + "加载预训练底模D路径": "加載預訓練底模D路徑", + "加载预训练底模G路径": "加載預訓練底模G路徑", + "单次推理": "单次推理", + "卸载音色省显存": "卸載音色節省 VRAM", + "变调(整数, 半音数量, 升八度12降八度-12)": "變調(整數、半音數量、升八度12降八度-12)", + "后处理重采样至最终采样率,0为不进行重采样": "後處理重採樣至最終採樣率,0為不進行重採樣", + "否": "否", + "启用相位声码器": "启用相位声码器", + "响应阈值": "響應閾值", + "响度因子": "響度因子", + "处理数据": "處理資料", + "导出Onnx模型": "导出Onnx模型", + "导出文件格式": "導出檔格式", + "常见问题解答": "常見問題解答", + "常规设置": "一般設定", + "开始音频转换": "開始音訊轉換", + "很遗憾您这没有能用的显卡来支持您训练": "很遗憾您这没有能用的显卡来支持您训练", + "性能设置": "效能設定", + "总训练轮数total_epoch": "總訓練輪數total_epoch", + "批量推理": "批量推理", + "批量转换, 输入待转换音频文件夹, 或上传多个音频文件, 在指定文件夹(默认opt)下输出转换的音频. ": "批量轉換,輸入待轉換音頻資料夾,或上傳多個音頻檔案,在指定資料夾(默認opt)下輸出轉換的音頻。", + "指定输出主人声文件夹": "指定输出主人声文件夹", + "指定输出文件夹": "指定輸出資料夾", + "指定输出非主人声文件夹": "指定输出非主人声文件夹", + "推理时间(ms):": "推理時間(ms):", + "推理音色": "推理音色", + "提取": "提取", + "提取音高和处理数据使用的CPU进程数": "提取音高和處理數據使用的CPU進程數", + "是": "是", + "是否仅保存最新的ckpt文件以节省硬盘空间": "是否僅保存最新的ckpt檔案以節省硬碟空間", + "是否在每次保存时间点将最终小模型保存至weights文件夹": "是否在每次保存時間點將最終小模型保存至weights檔夾", + "是否缓存所有训练集至显存. 10min以下小数据可缓存以加速训练, 大数据缓存会炸显存也加不了多少速": "是否緩存所有訓練集至 VRAM。小於10分鐘的小數據可緩存以加速訓練,大數據緩存會爆 VRAM 也加不了多少速度", + "显卡信息": "顯示卡資訊", + "本软件以MIT协议开源, 作者不对软件具备任何控制力, 使用软件者、传播软件导出的声音者自负全责.
如不认可该条款, 则不能使用或引用软件包内任何代码和文件. 详见根目录LICENSE.": "本軟體以MIT協議開源,作者不對軟體具備任何控制力,使用軟體者、傳播軟體導出的聲音者自負全責。
如不認可該條款,則不能使用或引用軟體包內任何程式碼和檔案。詳見根目錄使用需遵守的協議-LICENSE.txt。", + "查看": "查看", + "查看模型信息(仅支持weights文件夹下提取的小模型文件)": "查看模型資訊(僅支援weights資料夾下提取的小模型檔案)", + "检索特征占比": "檢索特徵佔比", + "模型": "模型", + "模型推理": "模型推理", + "模型提取(输入logs文件夹下大文件模型路径),适用于训一半不想训了模型没有自动提取保存小文件模型,或者想测试中间模型的情况": "模型提取(輸入logs資料夾下大檔案模型路徑),適用於訓一半不想訓了模型沒有自動提取儲存小檔案模型,或者想測試中間模型的情況", + "模型是否带音高指导": "模型是否帶音高指導", + "模型是否带音高指导(唱歌一定要, 语音可以不要)": "模型是否帶音高指導(唱歌一定要,語音可以不要)", + "模型是否带音高指导,1是0否": "模型是否帶音高指導,1是0否", + "模型版本型号": "模型版本型號", + "模型融合, 可用于测试音色融合": "模型融合,可用於測試音色融合", + "模型路径": "模型路徑", + "每张显卡的batch_size": "每张显卡的batch_size", + "淡入淡出长度": "淡入淡出長度", + "版本": "版本", + "特征提取": "特徵提取", + "特征检索库文件路径,为空则使用下拉的选择结果": "特徵檢索庫檔路徑,為空則使用下拉的選擇結果", + "独占 WASAPI 设备": "独占 WASAPI 设备", + "男转女推荐+12key, 女转男推荐-12key, 如果音域爆炸导致音色失真也可以自己调整到合适音域. ": "男性轉女性推薦+12key,女性轉男性推薦-12key,如果音域爆炸導致音色失真也可以自己調整到合適音域。", + "目标采样率": "目標取樣率", + "算法延迟(ms):": "算法延迟(ms):", + "自动检测index路径,下拉式选择(dropdown)": "自動檢測index路徑,下拉式選擇(dropdown)", + "融合": "融合", + "要改的模型信息": "要改的模型資訊", + "要置入的模型信息": "要置入的模型資訊", + "训练": "訓練", + "训练模型": "訓練模型", + "训练特征索引": "訓練特徵索引", + "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log": "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log", + "设备类型": "设备类型", + "请指定说话人id": "請指定說話人id", + "请选择index文件": "请选择index文件", + "请选择pth文件": "请选择pth文件", + "请选择说话人id": "請選擇說話人ID", + "转换": "轉換", + "输入实验名": "輸入實驗名稱", + "输入待处理音频文件夹路径": "輸入待處理音頻資料夾路徑", + "输入待处理音频文件夹路径(去文件管理器地址栏拷就行了)": "輸入待處理音頻資料夾路徑(去檔案管理器地址欄拷貝即可)", + "输入待处理音频文件路径(默认是正确格式示例)": "輸入待處理音頻檔案路徑(預設是正確格式示例)", + "输入源音量包络替换输出音量包络融合比例,越靠近1越使用输出包络": "輸入源音量包絡替換輸出音量包絡融合比例,越靠近1越使用輸出包絡", + "输入监听": "输入监听", + "输入训练文件夹路径": "輸入訓練檔案夾路徑", + "输入设备": "輸入設備", + "输入降噪": "輸入降噪", + "输出信息": "輸出訊息", + "输出变声": "输出变声", + "输出设备": "輸出設備", + "输出降噪": "輸出降噪", + "输出音频(右下角三个点,点了可以下载)": "輸出音頻(右下角三個點,點了可以下載)", + "选择.index文件": "選擇 .index 檔案", + "选择.pth文件": "選擇 .pth 檔案", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU": "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU", + "选择音高提取算法,输入歌声可用pm提速,harvest低音好但巨慢无比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU": "選擇音高提取演算法,輸入歌聲可用pm提速,harvest低音好但巨慢無比,crepe效果好但吃GPU,rmvpe效果最好且微吃GPU", + "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU": "选择音高提取算法:输入歌声可用pm提速,高质量语音但CPU差可用dio提速,harvest质量更好但慢,rmvpe效果最好且微吃CPU/GPU", + "采样率:": "采样率:", + "采样长度": "取樣長度", + "重载设备列表": "重載設備列表", + "音调设置": "音調設定", + "音频设备": "音訊設備", + "音高算法": "音高演算法", + "额外推理时长": "額外推理時長" +} diff --git a/rvc/i18n/locale_diff.py b/rvc/i18n/locale_diff.py new file mode 100644 index 0000000..674f7dd --- /dev/null +++ b/rvc/i18n/locale_diff.py @@ -0,0 +1,47 @@ +import json +import os +from collections import OrderedDict + +# Define the standard file name +standard_file = "locale/zh_CN.json" + +# Find all JSON files in the directory +dir_path = "locale/" +languages = [ + os.path.join(dir_path, f) + for f in os.listdir(dir_path) + if f.endswith(".json") and f != standard_file +] + +# Load the standard file +with open(standard_file, "r", encoding="utf-8") as f: + standard_data = json.load(f, object_pairs_hook=OrderedDict) + +# Loop through each language file +for lang_file in languages: + # Load the language file + with open(lang_file, "r", encoding="utf-8") as f: + lang_data = json.load(f, object_pairs_hook=OrderedDict) + + # Find the difference between the language file and the standard file + diff = set(standard_data.keys()) - set(lang_data.keys()) + + miss = set(lang_data.keys()) - set(standard_data.keys()) + + # Add any missing keys to the language file + for key in diff: + lang_data[key] = key + + # Del any extra keys to the language file + for key in miss: + del lang_data[key] + + # Sort the keys of the language file to match the order of the standard file + lang_data = OrderedDict( + sorted(lang_data.items(), key=lambda x: list(standard_data.keys()).index(x[0])) + ) + + # Save the updated language file + with open(lang_file, "w", encoding="utf-8") as f: + json.dump(lang_data, f, ensure_ascii=False, indent=4, sort_keys=True) + f.write("\n") diff --git a/rvc/i18n/scan_i18n.py b/rvc/i18n/scan_i18n.py new file mode 100644 index 0000000..f3e52cf --- /dev/null +++ b/rvc/i18n/scan_i18n.py @@ -0,0 +1,75 @@ +import ast +import glob +import json +from collections import OrderedDict + + +def extract_i18n_strings(node): + i18n_strings = [] + + if ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "i18n" + ): + for arg in node.args: + if isinstance(arg, ast.Str): + i18n_strings.append(arg.s) + + for child_node in ast.iter_child_nodes(node): + i18n_strings.extend(extract_i18n_strings(child_node)) + + return i18n_strings + + +# scan the directory for all .py files (recursively) +# for each file, parse the code into an AST +# for each AST, extract the i18n strings + +strings = [] +for filename in glob.iglob("**/*.py", recursive=True): + with open(filename, "r") as f: + code = f.read() + if "I18nAuto" in code: + tree = ast.parse(code) + i18n_strings = extract_i18n_strings(tree) + print(filename, len(i18n_strings)) + strings.extend(i18n_strings) +code_keys = set(strings) +""" +n_i18n.py +gui_v1.py 26 +app.py 16 +infer-web.py 147 +scan_i18n.py 0 +i18n.py 0 +lib/train/process_ckpt.py 1 +""" +print() +print("Total unique:", len(code_keys)) + + +standard_file = "i18n/locale/zh_CN.json" +with open(standard_file, "r", encoding="utf-8") as f: + standard_data = json.load(f, object_pairs_hook=OrderedDict) +standard_keys = set(standard_data.keys()) + +# Define the standard file name +unused_keys = standard_keys - code_keys +print("Unused keys:", len(unused_keys)) +for unused_key in unused_keys: + print("\t", unused_key) + +missing_keys = code_keys - standard_keys +print("Missing keys:", len(missing_keys)) +for missing_key in missing_keys: + print("\t", missing_key) + +code_keys_dict = OrderedDict() +for s in strings: + code_keys_dict[s] = s + +# write back +with open(standard_file, "w", encoding="utf-8") as f: + json.dump(code_keys_dict, f, ensure_ascii=False, indent=4, sort_keys=True) + f.write("\n") diff --git a/rvc/infer/lib/__pycache__/audio.cpython-310.pyc b/rvc/infer/lib/__pycache__/audio.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9c388f9a81528bbf0b9e48405b5c0b0e65082ad GIT binary patch literal 1380 zcmZuwON$#v5bo;kd1zKEJ2BY7heYflAc8EAor^JvVdFq9dvG3x!9mPU_pC?JJlxZ> zS}ijn#JTDhgn$%s$zSSgPA30A2XCrpcf%6WL)WWos;ld(D)#pSg6rMyQ zb5T^D;;x<`5k$a(gqIvb=yV#gj`NTUD%dY1bOaX;a#y&*L+%OqnvDFn7=awc%(AQH zBgb3GA@1rAB${lX!CSVpWepYd2deslwN#TiG$1(IoHc7Lck%4L@LGy8LwP8Cl=%kr zEwoO<=1lZNu!b+l_qSTNaW=ehw^s975B;3R+X9f+)7PKaGeQ36>?!)WTLPqEms}8h z$=;CxWmxtoLQ+05upEJLlR{;YHmo=~344q5__R>RX%(_4iWT6~(8q~tkrpG*(CF0g zqLjG_A|Ym#Rx-+rD`Z>+FC0f5g-ab4*mo8@>zb!{9!|L$<2PUA_MCsIu?>q(d5 zgK}l~GCG}l7^*rz(gz=~q~5QSD$HI?2Q7LWaFE z&SstXw!?{cMGoD_f)FECb}Ixp$x3;0?vHaUx=8bHM$E7{6X76^N?j|7I0_5OO#?_DRsxjhnWG{HOM8o zXN`f)YSv|Clc??^q2TjAqRKD&l)nou2F?KdpXkN=Og+Hk*jDO#S()SRaDI$}o#HK` zYp1s%8=PaoaH0|W4I;e-q)tb19{-imj!1b&#(R@`p+xX5EGl=uIvc)v2_oivyFW9DO#@TpVwKr uQXgAG&l20z-HSWl8!_#WsjhtfldKT+LVjlJPz{jy#K%FwkNNld@BIVTuU=#T literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/__pycache__/rmvpe.cpython-310.pyc b/rvc/infer/lib/__pycache__/rmvpe.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70bb1430d9736cf04d3dd6f980d7ac5d8d0f0df9 GIT binary patch literal 18575 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 M-(8$voIhdz9|_J1ivR!s literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/__pycache__/slicer2.cpython-310.pyc b/rvc/infer/lib/__pycache__/slicer2.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a19ba7c5608ce3db9f714ce12148f91aa8aaa8d9 GIT binary patch literal 5200 zcmb_g&5t8T74NG4bo*mGADNxmuQ;FtIb?Q72qc0{*sz-*0!$#giI5erdR^_A_E&qW z+q*laEhM~=xQvto5<*;NxaG=$1E>5MsKku}j5u&XLiyU^_p0q3Z$5+~(NeYMi_sVg(>>GIQ|6!~B&o3Ls->9+pm}p$Vn_NR748eLvgB5teELfr>Y~kEv4gNht zxWc<<2ye<8rYH#ip3$&ETNJxo)>P(5q7w$wJ8c%ji^gm`3SnJ`#Z-_mQiQQ>c=8HKEZR$h{Bbf>;=P6xk)M` z5hjwz$M-Sd20E?aZj>mm8-_y>^^8m6tfrI zROOsw6MS3UMEOC|%qc>7{rGmMS=&i3YGLjG>>*qj43rt%Oq7+}l4-r7*uE;YWzY|s zy>PIb?kRsS9?t6?L}=26l#7~(2D`FE%NOHOI$JAIOLv=kha^Y89|~2_y?WF9?j2Q3 z1GyWfO)ZY2#Yq&qtL$NXCzS6RvJRFXytKWyzm@if+gIa$d;g8^Z?{HC8uyz6te(t? zTd%))bz9zUZAXK4D7T@6ZOu{$PuZ^+g9GQa%+Wn((dN{XvCLY7+nzHfmasA|>~qGdkr_>va`f4G z-;{v@rv}ZMGU1SOuNhOge$&?D&>|j0KL^(X-J!7yZC8(TJ~hr022|(njDK=tFRZoW zUN61_L#!b#i71WYL9Gu%AEW#_X!2Pq zzKNpl!a>7cTRu$`n!7N7rt4AKbMoRz(3IDNfkbJu`4zO1Cs3>uIOEJZ;V1dmGxKlB ztiU7I*#@h!^K877o8_hX{D)#aZH7!qo6*lvp$OlB*$CFP2_L{OCQt{IK-+`GcbPm#z;&Hqj1-`uPFM%#p)2?i zo0z-gUB<)~96;!0=9FDCe)gZY_{5!ffH$L40Ia`eYgA{xm~={!#HUmcf#QOBO9eOPJ>=kr*TQ6IA024E2p(L8g!FS1VwoU0{;MS@*au`s~}CP zG6ylyM>KRezi*iqvj7f0_qYYR$2nW!6<%WezGo4o!Z(mmRq=HIB^+SFPb|!=m>j%x zo$-(P$%YHa(LMacvnyETEO9YEZ;WpfPYdIGByLqCYa8HCt5|#mV6_IhYRo%XfrO}^ zSmwAii{3iK@6>I%gCRjxEg85fidZf<9@Qs*3k)j8xT@1lNw z0{A$*pl4r%eCKuTQLF%|IyG@YoD&xhpjiYkK-#)-!~qoE5%hWM_c{%IbSF5l`Mgi_hTNJQ~?oia*Yt);O}N-5zNbkj@saSOEe!rpLfZ*Ad@G=B26Q92x@ zH4(|MmBwh5l5ufsOWbVcSA$ogzu@{feuCB#-^GmCeF53Atj)Z? zCc+d~^VI`ql5w?R-FO_eCJ}zx-ouuDQKy7u5&fa|vh+<^{YQrGFw zT-!k#hVzW%}<%Q^UK#UHV<3Ao=n||GGS!O(rjJW7)3Yb;_^=v zd4t$nh;c#(qDJ9?d41+pgAhYGA^xpM@qa}>=!W2!$d_s12Z*cK4Q1bvQ3^xv#B{&8 z89;rst|RlUmKBc^y*EjB3E9+q^Bv>g5Qr@)CTj6=4d;IuNV!8~0xe~0P+N$_`u1b> zgJ|$4^e4YT;o*kwagRBG5CRhiKrxTYoH-v_1WU8u7ioE}1K3+9xWkV?GXFUr_+AO{ z3Gl*dv?BFi1oQy`Y^?ac^|4GzdLR%<+H4|?p(}_z%#DOpDW5OQdF|D(!d#xK2P??;5|{{=*N5&{4K literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/audio.py b/rvc/infer/lib/audio.py new file mode 100644 index 0000000..89799ab --- /dev/null +++ b/rvc/infer/lib/audio.py @@ -0,0 +1,50 @@ +import platform +import ffmpeg +import numpy as np +import av + + +def wav2(i, o, format): + inp = av.open(i, "r") + if format == "m4a": + format = "mp4" + out = av.open(o, "w", format=format) + if format == "ogg": + format = "libvorbis" + if format == "mp4": + format = "aac" + + ostream = out.add_stream(format) + + for frame in inp.decode(audio=0): + for p in ostream.encode(frame): + out.mux(p) + + for p in ostream.encode(None): + out.mux(p) + + out.close() + inp.close() + + +def load_audio(file, sr): + try: + # https://github.com/openai/whisper/blob/main/whisper/audio.py#L26 + # This launches a subprocess to decode audio while down-mixing and resampling as necessary. + # Requires the ffmpeg CLI and `ffmpeg-python` package to be installed. + file = clean_path(file) # 防止小白拷路径头尾带了空格和"和回车 + out, _ = ( + ffmpeg.input(file, threads=0) + .output("-", format="f32le", acodec="pcm_f32le", ac=1, ar=sr) + .run(cmd=["ffmpeg", "-nostdin"], capture_stdout=True, capture_stderr=True) + ) + except Exception as e: + raise RuntimeError(f"Failed to load audio: {e}") + + return np.frombuffer(out, np.float32).flatten() + + +def clean_path(path_str): + if platform.system() == "Windows": + path_str = path_str.replace("/", "\\") + return path_str.strip(" ").strip('"').strip("\n").strip('"').strip(" ") diff --git a/rvc/infer/lib/infer_pack/__pycache__/attentions.cpython-310.pyc b/rvc/infer/lib/infer_pack/__pycache__/attentions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa4be5ece7ad8343d76cab74e3d4c6545960ba2a GIT binary patch literal 10429 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 literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/infer_pack/__pycache__/commons.cpython-310.pyc b/rvc/infer/lib/infer_pack/__pycache__/commons.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a532dbcc9b6ccdaf1d698dcc9b42ac13cd132e6a GIT binary patch literal 5649 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 literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/infer_pack/attentions.py b/rvc/infer/lib/infer_pack/attentions.py new file mode 100644 index 0000000..2cc745a --- /dev/null +++ b/rvc/infer/lib/infer_pack/attentions.py @@ -0,0 +1,459 @@ +import copy +import math +from typing import Optional + +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F + +from infer.lib.infer_pack import commons, modules +from infer.lib.infer_pack.modules import LayerNorm + + +class Encoder(nn.Module): + def __init__( + self, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size=1, + p_dropout=0.0, + window_size=10, + **kwargs + ): + super(Encoder, self).__init__() + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = int(n_layers) + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.window_size = window_size + + self.drop = nn.Dropout(p_dropout) + self.attn_layers = nn.ModuleList() + self.norm_layers_1 = nn.ModuleList() + self.ffn_layers = nn.ModuleList() + self.norm_layers_2 = nn.ModuleList() + for i in range(self.n_layers): + self.attn_layers.append( + MultiHeadAttention( + hidden_channels, + hidden_channels, + n_heads, + p_dropout=p_dropout, + window_size=window_size, + ) + ) + self.norm_layers_1.append(LayerNorm(hidden_channels)) + self.ffn_layers.append( + FFN( + hidden_channels, + hidden_channels, + filter_channels, + kernel_size, + p_dropout=p_dropout, + ) + ) + self.norm_layers_2.append(LayerNorm(hidden_channels)) + + def forward(self, x, x_mask): + attn_mask = x_mask.unsqueeze(2) * x_mask.unsqueeze(-1) + x = x * x_mask + zippep = zip( + self.attn_layers, self.norm_layers_1, self.ffn_layers, self.norm_layers_2 + ) + for attn_layers, norm_layers_1, ffn_layers, norm_layers_2 in zippep: + y = attn_layers(x, x, attn_mask) + y = self.drop(y) + x = norm_layers_1(x + y) + + y = ffn_layers(x, x_mask) + y = self.drop(y) + x = norm_layers_2(x + y) + x = x * x_mask + return x + + +class Decoder(nn.Module): + def __init__( + self, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size=1, + p_dropout=0.0, + proximal_bias=False, + proximal_init=True, + **kwargs + ): + super(Decoder, self).__init__() + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.proximal_bias = proximal_bias + self.proximal_init = proximal_init + + self.drop = nn.Dropout(p_dropout) + self.self_attn_layers = nn.ModuleList() + self.norm_layers_0 = nn.ModuleList() + self.encdec_attn_layers = nn.ModuleList() + self.norm_layers_1 = nn.ModuleList() + self.ffn_layers = nn.ModuleList() + self.norm_layers_2 = nn.ModuleList() + for i in range(self.n_layers): + self.self_attn_layers.append( + MultiHeadAttention( + hidden_channels, + hidden_channels, + n_heads, + p_dropout=p_dropout, + proximal_bias=proximal_bias, + proximal_init=proximal_init, + ) + ) + self.norm_layers_0.append(LayerNorm(hidden_channels)) + self.encdec_attn_layers.append( + MultiHeadAttention( + hidden_channels, hidden_channels, n_heads, p_dropout=p_dropout + ) + ) + self.norm_layers_1.append(LayerNorm(hidden_channels)) + self.ffn_layers.append( + FFN( + hidden_channels, + hidden_channels, + filter_channels, + kernel_size, + p_dropout=p_dropout, + causal=True, + ) + ) + self.norm_layers_2.append(LayerNorm(hidden_channels)) + + def forward(self, x, x_mask, h, h_mask): + """ + x: decoder input + h: encoder output + """ + self_attn_mask = commons.subsequent_mask(x_mask.size(2)).to( + device=x.device, dtype=x.dtype + ) + encdec_attn_mask = h_mask.unsqueeze(2) * x_mask.unsqueeze(-1) + x = x * x_mask + for i in range(self.n_layers): + y = self.self_attn_layers[i](x, x, self_attn_mask) + y = self.drop(y) + x = self.norm_layers_0[i](x + y) + + y = self.encdec_attn_layers[i](x, h, encdec_attn_mask) + y = self.drop(y) + x = self.norm_layers_1[i](x + y) + + y = self.ffn_layers[i](x, x_mask) + y = self.drop(y) + x = self.norm_layers_2[i](x + y) + x = x * x_mask + return x + + +class MultiHeadAttention(nn.Module): + def __init__( + self, + channels, + out_channels, + n_heads, + p_dropout=0.0, + window_size=None, + heads_share=True, + block_length=None, + proximal_bias=False, + proximal_init=False, + ): + super(MultiHeadAttention, self).__init__() + assert channels % n_heads == 0 + + self.channels = channels + self.out_channels = out_channels + self.n_heads = n_heads + self.p_dropout = p_dropout + self.window_size = window_size + self.heads_share = heads_share + self.block_length = block_length + self.proximal_bias = proximal_bias + self.proximal_init = proximal_init + self.attn = None + + self.k_channels = channels // n_heads + self.conv_q = nn.Conv1d(channels, channels, 1) + self.conv_k = nn.Conv1d(channels, channels, 1) + self.conv_v = nn.Conv1d(channels, channels, 1) + self.conv_o = nn.Conv1d(channels, out_channels, 1) + self.drop = nn.Dropout(p_dropout) + + if window_size is not None: + n_heads_rel = 1 if heads_share else n_heads + rel_stddev = self.k_channels**-0.5 + self.emb_rel_k = nn.Parameter( + torch.randn(n_heads_rel, window_size * 2 + 1, self.k_channels) + * rel_stddev + ) + self.emb_rel_v = nn.Parameter( + torch.randn(n_heads_rel, window_size * 2 + 1, self.k_channels) + * rel_stddev + ) + + nn.init.xavier_uniform_(self.conv_q.weight) + nn.init.xavier_uniform_(self.conv_k.weight) + nn.init.xavier_uniform_(self.conv_v.weight) + if proximal_init: + with torch.no_grad(): + self.conv_k.weight.copy_(self.conv_q.weight) + self.conv_k.bias.copy_(self.conv_q.bias) + + def forward( + self, x: torch.Tensor, c: torch.Tensor, attn_mask: Optional[torch.Tensor] = None + ): + q = self.conv_q(x) + k = self.conv_k(c) + v = self.conv_v(c) + + x, _ = self.attention(q, k, v, mask=attn_mask) + + x = self.conv_o(x) + return x + + def attention( + self, + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + mask: Optional[torch.Tensor] = None, + ): + # reshape [b, d, t] -> [b, n_h, t, d_k] + b, d, t_s = key.size() + t_t = query.size(2) + query = query.view(b, self.n_heads, self.k_channels, t_t).transpose(2, 3) + key = key.view(b, self.n_heads, self.k_channels, t_s).transpose(2, 3) + value = value.view(b, self.n_heads, self.k_channels, t_s).transpose(2, 3) + + scores = torch.matmul(query / math.sqrt(self.k_channels), key.transpose(-2, -1)) + if self.window_size is not None: + assert ( + t_s == t_t + ), "Relative attention is only available for self-attention." + key_relative_embeddings = self._get_relative_embeddings(self.emb_rel_k, t_s) + rel_logits = self._matmul_with_relative_keys( + query / math.sqrt(self.k_channels), key_relative_embeddings + ) + scores_local = self._relative_position_to_absolute_position(rel_logits) + scores = scores + scores_local + if self.proximal_bias: + assert t_s == t_t, "Proximal bias is only available for self-attention." + scores = scores + self._attention_bias_proximal(t_s).to( + device=scores.device, dtype=scores.dtype + ) + if mask is not None: + scores = scores.masked_fill(mask == 0, -1e4) + if self.block_length is not None: + assert ( + t_s == t_t + ), "Local attention is only available for self-attention." + block_mask = ( + torch.ones_like(scores) + .triu(-self.block_length) + .tril(self.block_length) + ) + scores = scores.masked_fill(block_mask == 0, -1e4) + p_attn = F.softmax(scores, dim=-1) # [b, n_h, t_t, t_s] + p_attn = self.drop(p_attn) + output = torch.matmul(p_attn, value) + if self.window_size is not None: + relative_weights = self._absolute_position_to_relative_position(p_attn) + value_relative_embeddings = self._get_relative_embeddings( + self.emb_rel_v, t_s + ) + output = output + self._matmul_with_relative_values( + relative_weights, value_relative_embeddings + ) + output = ( + output.transpose(2, 3).contiguous().view(b, d, t_t) + ) # [b, n_h, t_t, d_k] -> [b, d, t_t] + return output, p_attn + + def _matmul_with_relative_values(self, x, y): + """ + x: [b, h, l, m] + y: [h or 1, m, d] + ret: [b, h, l, d] + """ + ret = torch.matmul(x, y.unsqueeze(0)) + return ret + + def _matmul_with_relative_keys(self, x, y): + """ + x: [b, h, l, d] + y: [h or 1, m, d] + ret: [b, h, l, m] + """ + ret = torch.matmul(x, y.unsqueeze(0).transpose(-2, -1)) + return ret + + def _get_relative_embeddings(self, relative_embeddings, length: int): + max_relative_position = 2 * self.window_size + 1 + # Pad first before slice to avoid using cond ops. + pad_length: int = max(length - (self.window_size + 1), 0) + slice_start_position = max((self.window_size + 1) - length, 0) + slice_end_position = slice_start_position + 2 * length - 1 + if pad_length > 0: + padded_relative_embeddings = F.pad( + relative_embeddings, + # commons.convert_pad_shape([[0, 0], [pad_length, pad_length], [0, 0]]), + [0, 0, pad_length, pad_length, 0, 0], + ) + else: + padded_relative_embeddings = relative_embeddings + used_relative_embeddings = padded_relative_embeddings[ + :, slice_start_position:slice_end_position + ] + return used_relative_embeddings + + def _relative_position_to_absolute_position(self, x): + """ + x: [b, h, l, 2*l-1] + ret: [b, h, l, l] + """ + batch, heads, length, _ = x.size() + # Concat columns of pad to shift from relative to absolute indexing. + x = F.pad( + x, + # commons.convert_pad_shape([[0, 0], [0, 0], [0, 0], [0, 1]]) + [0, 1, 0, 0, 0, 0, 0, 0], + ) + + # Concat extra elements so to add up to shape (len+1, 2*len-1). + x_flat = x.view([batch, heads, length * 2 * length]) + x_flat = F.pad( + x_flat, + # commons.convert_pad_shape([[0, 0], [0, 0], [0, int(length) - 1]]) + [0, int(length) - 1, 0, 0, 0, 0], + ) + + # Reshape and slice out the padded elements. + x_final = x_flat.view([batch, heads, length + 1, 2 * length - 1])[ + :, :, :length, length - 1 : + ] + return x_final + + def _absolute_position_to_relative_position(self, x): + """ + x: [b, h, l, l] + ret: [b, h, l, 2*l-1] + """ + batch, heads, length, _ = x.size() + # padd along column + x = F.pad( + x, + # commons.convert_pad_shape([[0, 0], [0, 0], [0, 0], [0, int(length) - 1]]) + [0, int(length) - 1, 0, 0, 0, 0, 0, 0], + ) + x_flat = x.view([batch, heads, int(length**2) + int(length * (length - 1))]) + # add 0's in the beginning that will skew the elements after reshape + x_flat = F.pad( + x_flat, + # commons.convert_pad_shape([[0, 0], [0, 0], [int(length), 0]]) + [length, 0, 0, 0, 0, 0], + ) + x_final = x_flat.view([batch, heads, length, 2 * length])[:, :, :, 1:] + return x_final + + def _attention_bias_proximal(self, length: int): + """Bias for self-attention to encourage attention to close positions. + Args: + length: an integer scalar. + Returns: + a Tensor with shape [1, 1, length, length] + """ + r = torch.arange(length, dtype=torch.float32) + diff = torch.unsqueeze(r, 0) - torch.unsqueeze(r, 1) + return torch.unsqueeze(torch.unsqueeze(-torch.log1p(torch.abs(diff)), 0), 0) + + +class FFN(nn.Module): + def __init__( + self, + in_channels, + out_channels, + filter_channels, + kernel_size, + p_dropout=0.0, + activation: str = None, + causal=False, + ): + super(FFN, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.filter_channels = filter_channels + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.activation = activation + self.causal = causal + self.is_activation = True if activation == "gelu" else False + # if causal: + # self.padding = self._causal_padding + # else: + # self.padding = self._same_padding + + self.conv_1 = nn.Conv1d(in_channels, filter_channels, kernel_size) + self.conv_2 = nn.Conv1d(filter_channels, out_channels, kernel_size) + self.drop = nn.Dropout(p_dropout) + + def padding(self, x: torch.Tensor, x_mask: torch.Tensor) -> torch.Tensor: + if self.causal: + padding = self._causal_padding(x * x_mask) + else: + padding = self._same_padding(x * x_mask) + return padding + + def forward(self, x: torch.Tensor, x_mask: torch.Tensor): + x = self.conv_1(self.padding(x, x_mask)) + if self.is_activation: + x = x * torch.sigmoid(1.702 * x) + else: + x = torch.relu(x) + x = self.drop(x) + + x = self.conv_2(self.padding(x, x_mask)) + return x * x_mask + + def _causal_padding(self, x): + if self.kernel_size == 1: + return x + pad_l: int = self.kernel_size - 1 + pad_r: int = 0 + # padding = [[0, 0], [0, 0], [pad_l, pad_r]] + x = F.pad( + x, + # commons.convert_pad_shape(padding) + [pad_l, pad_r, 0, 0, 0, 0], + ) + return x + + def _same_padding(self, x): + if self.kernel_size == 1: + return x + pad_l: int = (self.kernel_size - 1) // 2 + pad_r: int = self.kernel_size // 2 + # padding = [[0, 0], [0, 0], [pad_l, pad_r]] + x = F.pad( + x, + # commons.convert_pad_shape(padding) + [pad_l, pad_r, 0, 0, 0, 0], + ) + return x diff --git a/rvc/infer/lib/infer_pack/attentions_onnx.py b/rvc/infer/lib/infer_pack/attentions_onnx.py new file mode 100644 index 0000000..934c58b --- /dev/null +++ b/rvc/infer/lib/infer_pack/attentions_onnx.py @@ -0,0 +1,459 @@ +import copy +import math +from typing import Optional + +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F + +from infer.lib.infer_pack import commons, modules +from infer.lib.infer_pack.modules import LayerNorm + + +class Encoder(nn.Module): + def __init__( + self, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size=1, + p_dropout=0.0, + window_size=10, + **kwargs + ): + super(Encoder, self).__init__() + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = int(n_layers) + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.window_size = window_size + + self.drop = nn.Dropout(p_dropout) + self.attn_layers = nn.ModuleList() + self.norm_layers_1 = nn.ModuleList() + self.ffn_layers = nn.ModuleList() + self.norm_layers_2 = nn.ModuleList() + for i in range(self.n_layers): + self.attn_layers.append( + MultiHeadAttention( + hidden_channels, + hidden_channels, + n_heads, + p_dropout=p_dropout, + window_size=window_size, + ) + ) + self.norm_layers_1.append(LayerNorm(hidden_channels)) + self.ffn_layers.append( + FFN( + hidden_channels, + hidden_channels, + filter_channels, + kernel_size, + p_dropout=p_dropout, + ) + ) + self.norm_layers_2.append(LayerNorm(hidden_channels)) + + def forward(self, x, x_mask): + attn_mask = x_mask.unsqueeze(2) * x_mask.unsqueeze(-1) + x = x * x_mask + zippep = zip( + self.attn_layers, self.norm_layers_1, self.ffn_layers, self.norm_layers_2 + ) + for attn_layers, norm_layers_1, ffn_layers, norm_layers_2 in zippep: + y = attn_layers(x, x, attn_mask) + y = self.drop(y) + x = norm_layers_1(x + y) + + y = ffn_layers(x, x_mask) + y = self.drop(y) + x = norm_layers_2(x + y) + x = x * x_mask + return x + + +class Decoder(nn.Module): + def __init__( + self, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size=1, + p_dropout=0.0, + proximal_bias=False, + proximal_init=True, + **kwargs + ): + super(Decoder, self).__init__() + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.proximal_bias = proximal_bias + self.proximal_init = proximal_init + + self.drop = nn.Dropout(p_dropout) + self.self_attn_layers = nn.ModuleList() + self.norm_layers_0 = nn.ModuleList() + self.encdec_attn_layers = nn.ModuleList() + self.norm_layers_1 = nn.ModuleList() + self.ffn_layers = nn.ModuleList() + self.norm_layers_2 = nn.ModuleList() + for i in range(self.n_layers): + self.self_attn_layers.append( + MultiHeadAttention( + hidden_channels, + hidden_channels, + n_heads, + p_dropout=p_dropout, + proximal_bias=proximal_bias, + proximal_init=proximal_init, + ) + ) + self.norm_layers_0.append(LayerNorm(hidden_channels)) + self.encdec_attn_layers.append( + MultiHeadAttention( + hidden_channels, hidden_channels, n_heads, p_dropout=p_dropout + ) + ) + self.norm_layers_1.append(LayerNorm(hidden_channels)) + self.ffn_layers.append( + FFN( + hidden_channels, + hidden_channels, + filter_channels, + kernel_size, + p_dropout=p_dropout, + causal=True, + ) + ) + self.norm_layers_2.append(LayerNorm(hidden_channels)) + + def forward(self, x, x_mask, h, h_mask): + """ + x: decoder input + h: encoder output + """ + self_attn_mask = commons.subsequent_mask(x_mask.size(2)).to( + device=x.device, dtype=x.dtype + ) + encdec_attn_mask = h_mask.unsqueeze(2) * x_mask.unsqueeze(-1) + x = x * x_mask + for i in range(self.n_layers): + y = self.self_attn_layers[i](x, x, self_attn_mask) + y = self.drop(y) + x = self.norm_layers_0[i](x + y) + + y = self.encdec_attn_layers[i](x, h, encdec_attn_mask) + y = self.drop(y) + x = self.norm_layers_1[i](x + y) + + y = self.ffn_layers[i](x, x_mask) + y = self.drop(y) + x = self.norm_layers_2[i](x + y) + x = x * x_mask + return x + + +class MultiHeadAttention(nn.Module): + def __init__( + self, + channels, + out_channels, + n_heads, + p_dropout=0.0, + window_size=None, + heads_share=True, + block_length=None, + proximal_bias=False, + proximal_init=False, + ): + super(MultiHeadAttention, self).__init__() + assert channels % n_heads == 0 + + self.channels = channels + self.out_channels = out_channels + self.n_heads = n_heads + self.p_dropout = p_dropout + self.window_size = window_size + self.heads_share = heads_share + self.block_length = block_length + self.proximal_bias = proximal_bias + self.proximal_init = proximal_init + self.attn = None + + self.k_channels = channels // n_heads + self.conv_q = nn.Conv1d(channels, channels, 1) + self.conv_k = nn.Conv1d(channels, channels, 1) + self.conv_v = nn.Conv1d(channels, channels, 1) + self.conv_o = nn.Conv1d(channels, out_channels, 1) + self.drop = nn.Dropout(p_dropout) + + if window_size is not None: + n_heads_rel = 1 if heads_share else n_heads + rel_stddev = self.k_channels**-0.5 + self.emb_rel_k = nn.Parameter( + torch.randn(n_heads_rel, window_size * 2 + 1, self.k_channels) + * rel_stddev + ) + self.emb_rel_v = nn.Parameter( + torch.randn(n_heads_rel, window_size * 2 + 1, self.k_channels) + * rel_stddev + ) + + nn.init.xavier_uniform_(self.conv_q.weight) + nn.init.xavier_uniform_(self.conv_k.weight) + nn.init.xavier_uniform_(self.conv_v.weight) + if proximal_init: + with torch.no_grad(): + self.conv_k.weight.copy_(self.conv_q.weight) + self.conv_k.bias.copy_(self.conv_q.bias) + + def forward( + self, x: torch.Tensor, c: torch.Tensor, attn_mask: Optional[torch.Tensor] = None + ): + q = self.conv_q(x) + k = self.conv_k(c) + v = self.conv_v(c) + + x, _ = self.attention(q, k, v, mask=attn_mask) + + x = self.conv_o(x) + return x + + def attention( + self, + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + mask: Optional[torch.Tensor] = None, + ): + # reshape [b, d, t] -> [b, n_h, t, d_k] + b, d, t_s = key.size() + t_t = query.size(2) + query = query.view(b, self.n_heads, self.k_channels, t_t).transpose(2, 3) + key = key.view(b, self.n_heads, self.k_channels, t_s).transpose(2, 3) + value = value.view(b, self.n_heads, self.k_channels, t_s).transpose(2, 3) + + scores = torch.matmul(query / math.sqrt(self.k_channels), key.transpose(-2, -1)) + if self.window_size is not None: + assert ( + t_s == t_t + ), "Relative attention is only available for self-attention." + key_relative_embeddings = self._get_relative_embeddings(self.emb_rel_k, t_s) + rel_logits = self._matmul_with_relative_keys( + query / math.sqrt(self.k_channels), key_relative_embeddings + ) + scores_local = self._relative_position_to_absolute_position(rel_logits) + scores = scores + scores_local + if self.proximal_bias: + assert t_s == t_t, "Proximal bias is only available for self-attention." + scores = scores + self._attention_bias_proximal(t_s).to( + device=scores.device, dtype=scores.dtype + ) + if mask is not None: + scores = scores.masked_fill(mask == 0, -1e4) + if self.block_length is not None: + assert ( + t_s == t_t + ), "Local attention is only available for self-attention." + block_mask = ( + torch.ones_like(scores) + .triu(-self.block_length) + .tril(self.block_length) + ) + scores = scores.masked_fill(block_mask == 0, -1e4) + p_attn = F.softmax(scores, dim=-1) # [b, n_h, t_t, t_s] + p_attn = self.drop(p_attn) + output = torch.matmul(p_attn, value) + if self.window_size is not None: + relative_weights = self._absolute_position_to_relative_position(p_attn) + value_relative_embeddings = self._get_relative_embeddings( + self.emb_rel_v, t_s + ) + output = output + self._matmul_with_relative_values( + relative_weights, value_relative_embeddings + ) + output = ( + output.transpose(2, 3).contiguous().view(b, d, t_t) + ) # [b, n_h, t_t, d_k] -> [b, d, t_t] + return output, p_attn + + def _matmul_with_relative_values(self, x, y): + """ + x: [b, h, l, m] + y: [h or 1, m, d] + ret: [b, h, l, d] + """ + ret = torch.matmul(x, y.unsqueeze(0)) + return ret + + def _matmul_with_relative_keys(self, x, y): + """ + x: [b, h, l, d] + y: [h or 1, m, d] + ret: [b, h, l, m] + """ + ret = torch.matmul(x, y.unsqueeze(0).transpose(-2, -1)) + return ret + + def _get_relative_embeddings(self, relative_embeddings, length: int): + max_relative_position = 2 * self.window_size + 1 + # Pad first before slice to avoid using cond ops. + pad_length: int = max(length - (self.window_size + 1), 0) + slice_start_position = max((self.window_size + 1) - length, 0) + slice_end_position = slice_start_position + 2 * length - 1 + if pad_length > 0: + padded_relative_embeddings = F.pad( + relative_embeddings, + # commons.convert_pad_shape([[0, 0], [pad_length, pad_length], [0, 0]]), + [0, 0, pad_length, pad_length, 0, 0], + ) + else: + padded_relative_embeddings = relative_embeddings + used_relative_embeddings = padded_relative_embeddings[ + :, slice_start_position:slice_end_position + ] + return used_relative_embeddings + + def _relative_position_to_absolute_position(self, x): + """ + x: [b, h, l, 2*l-1] + ret: [b, h, l, l] + """ + batch, heads, length, _ = x.size() + # Concat columns of pad to shift from relative to absolute indexing. + x = F.pad( + x, + # commons.convert_pad_shape([[0, 0], [0, 0], [0, 0], [0, 1]]) + [0, 1, 0, 0, 0, 0, 0, 0], + ) + + # Concat extra elements so to add up to shape (len+1, 2*len-1). + x_flat = x.view([batch, heads, length * 2 * length]) + x_flat = F.pad( + x_flat, + # commons.convert_pad_shape([[0, 0], [0, 0], [0, int(length) - 1]]) + [0, length - 1, 0, 0, 0, 0], + ) + + # Reshape and slice out the padded elements. + x_final = x_flat.view([batch, heads, length + 1, 2 * length - 1])[ + :, :, :length, length - 1 : + ] + return x_final + + def _absolute_position_to_relative_position(self, x): + """ + x: [b, h, l, l] + ret: [b, h, l, 2*l-1] + """ + batch, heads, length, _ = x.size() + # padd along column + x = F.pad( + x, + # commons.convert_pad_shape([[0, 0], [0, 0], [0, 0], [0, int(length) - 1]]) + [0, length - 1, 0, 0, 0, 0, 0, 0], + ) + x_flat = x.view([batch, heads, (length**2) + (length * (length - 1))]) + # add 0's in the beginning that will skew the elements after reshape + x_flat = F.pad( + x_flat, + # commons.convert_pad_shape([[0, 0], [0, 0], [int(length), 0]]) + [length, 0, 0, 0, 0, 0], + ) + x_final = x_flat.view([batch, heads, length, 2 * length])[:, :, :, 1:] + return x_final + + def _attention_bias_proximal(self, length: int): + """Bias for self-attention to encourage attention to close positions. + Args: + length: an integer scalar. + Returns: + a Tensor with shape [1, 1, length, length] + """ + r = torch.arange(length, dtype=torch.float32) + diff = torch.unsqueeze(r, 0) - torch.unsqueeze(r, 1) + return torch.unsqueeze(torch.unsqueeze(-torch.log1p(torch.abs(diff)), 0), 0) + + +class FFN(nn.Module): + def __init__( + self, + in_channels, + out_channels, + filter_channels, + kernel_size, + p_dropout=0.0, + activation: str = None, + causal=False, + ): + super(FFN, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.filter_channels = filter_channels + self.kernel_size = kernel_size + self.p_dropout = p_dropout + self.activation = activation + self.causal = causal + self.is_activation = True if activation == "gelu" else False + # if causal: + # self.padding = self._causal_padding + # else: + # self.padding = self._same_padding + + self.conv_1 = nn.Conv1d(in_channels, filter_channels, kernel_size) + self.conv_2 = nn.Conv1d(filter_channels, out_channels, kernel_size) + self.drop = nn.Dropout(p_dropout) + + def padding(self, x: torch.Tensor, x_mask: torch.Tensor) -> torch.Tensor: + if self.causal: + padding = self._causal_padding(x * x_mask) + else: + padding = self._same_padding(x * x_mask) + return padding + + def forward(self, x: torch.Tensor, x_mask: torch.Tensor): + x = self.conv_1(self.padding(x, x_mask)) + if self.is_activation: + x = x * torch.sigmoid(1.702 * x) + else: + x = torch.relu(x) + x = self.drop(x) + + x = self.conv_2(self.padding(x, x_mask)) + return x * x_mask + + def _causal_padding(self, x): + if self.kernel_size == 1: + return x + pad_l: int = self.kernel_size - 1 + pad_r: int = 0 + # padding = [[0, 0], [0, 0], [pad_l, pad_r]] + x = F.pad( + x, + # commons.convert_pad_shape(padding) + [pad_l, pad_r, 0, 0, 0, 0], + ) + return x + + def _same_padding(self, x): + if self.kernel_size == 1: + return x + pad_l: int = (self.kernel_size - 1) // 2 + pad_r: int = self.kernel_size // 2 + # padding = [[0, 0], [0, 0], [pad_l, pad_r]] + x = F.pad( + x, + # commons.convert_pad_shape(padding) + [pad_l, pad_r, 0, 0, 0, 0], + ) + return x diff --git a/rvc/infer/lib/infer_pack/commons.py b/rvc/infer/lib/infer_pack/commons.py new file mode 100644 index 0000000..4ec6c24 --- /dev/null +++ b/rvc/infer/lib/infer_pack/commons.py @@ -0,0 +1,172 @@ +from typing import List, Optional +import math + +import numpy as np +import torch +from torch import nn +from torch.nn import functional as F + + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size * dilation - dilation) / 2) + + +# def convert_pad_shape(pad_shape): +# l = pad_shape[::-1] +# pad_shape = [item for sublist in l for item in sublist] +# return pad_shape + + +def kl_divergence(m_p, logs_p, m_q, logs_q): + """KL(P||Q)""" + kl = (logs_q - logs_p) - 0.5 + kl += ( + 0.5 * (torch.exp(2.0 * logs_p) + ((m_p - m_q) ** 2)) * torch.exp(-2.0 * logs_q) + ) + return kl + + +def rand_gumbel(shape): + """Sample from the Gumbel distribution, protect from overflows.""" + uniform_samples = torch.rand(shape) * 0.99998 + 0.00001 + return -torch.log(-torch.log(uniform_samples)) + + +def rand_gumbel_like(x): + g = rand_gumbel(x.size()).to(dtype=x.dtype, device=x.device) + return g + + +def slice_segments(x, ids_str, segment_size=4): + ret = torch.zeros_like(x[:, :, :segment_size]) + for i in range(x.size(0)): + idx_str = ids_str[i] + idx_end = idx_str + segment_size + ret[i] = x[i, :, idx_str:idx_end] + return ret + + +def slice_segments2(x, ids_str, segment_size=4): + ret = torch.zeros_like(x[:, :segment_size]) + for i in range(x.size(0)): + idx_str = ids_str[i] + idx_end = idx_str + segment_size + ret[i] = x[i, idx_str:idx_end] + return ret + + +def rand_slice_segments(x, x_lengths=None, segment_size=4): + b, d, t = x.size() + if x_lengths is None: + x_lengths = t + ids_str_max = x_lengths - segment_size + 1 + ids_str = (torch.rand([b]).to(device=x.device) * ids_str_max).to(dtype=torch.long) + ret = slice_segments(x, ids_str, segment_size) + return ret, ids_str + + +def get_timing_signal_1d(length, channels, min_timescale=1.0, max_timescale=1.0e4): + position = torch.arange(length, dtype=torch.float) + num_timescales = channels // 2 + log_timescale_increment = math.log(float(max_timescale) / float(min_timescale)) / ( + num_timescales - 1 + ) + inv_timescales = min_timescale * torch.exp( + torch.arange(num_timescales, dtype=torch.float) * -log_timescale_increment + ) + scaled_time = position.unsqueeze(0) * inv_timescales.unsqueeze(1) + signal = torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], 0) + signal = F.pad(signal, [0, 0, 0, channels % 2]) + signal = signal.view(1, channels, length) + return signal + + +def add_timing_signal_1d(x, min_timescale=1.0, max_timescale=1.0e4): + b, channels, length = x.size() + signal = get_timing_signal_1d(length, channels, min_timescale, max_timescale) + return x + signal.to(dtype=x.dtype, device=x.device) + + +def cat_timing_signal_1d(x, min_timescale=1.0, max_timescale=1.0e4, axis=1): + b, channels, length = x.size() + signal = get_timing_signal_1d(length, channels, min_timescale, max_timescale) + return torch.cat([x, signal.to(dtype=x.dtype, device=x.device)], axis) + + +def subsequent_mask(length): + mask = torch.tril(torch.ones(length, length)).unsqueeze(0).unsqueeze(0) + return mask + + +@torch.jit.script +def fused_add_tanh_sigmoid_multiply(input_a, input_b, n_channels): + n_channels_int = n_channels[0] + in_act = input_a + input_b + t_act = torch.tanh(in_act[:, :n_channels_int, :]) + s_act = torch.sigmoid(in_act[:, n_channels_int:, :]) + acts = t_act * s_act + return acts + + +# def convert_pad_shape(pad_shape): +# l = pad_shape[::-1] +# pad_shape = [item for sublist in l for item in sublist] +# return pad_shape + + +def convert_pad_shape(pad_shape: List[List[int]]) -> List[int]: + return torch.tensor(pad_shape).flip(0).reshape(-1).int().tolist() + + +def shift_1d(x): + x = F.pad(x, convert_pad_shape([[0, 0], [0, 0], [1, 0]]))[:, :, :-1] + return x + + +def sequence_mask(length: torch.Tensor, max_length: Optional[int] = None): + if max_length is None: + max_length = length.max() + x = torch.arange(max_length, dtype=length.dtype, device=length.device) + return x.unsqueeze(0) < length.unsqueeze(1) + + +def generate_path(duration, mask): + """ + duration: [b, 1, t_x] + mask: [b, 1, t_y, t_x] + """ + device = duration.device + + b, _, t_y, t_x = mask.shape + cum_duration = torch.cumsum(duration, -1) + + cum_duration_flat = cum_duration.view(b * t_x) + path = sequence_mask(cum_duration_flat, t_y).to(mask.dtype) + path = path.view(b, t_x, t_y) + path = path - F.pad(path, convert_pad_shape([[0, 0], [1, 0], [0, 0]]))[:, :-1] + path = path.unsqueeze(1).transpose(2, 3) * mask + return path + + +def clip_grad_value_(parameters, clip_value, norm_type=2): + if isinstance(parameters, torch.Tensor): + parameters = [parameters] + parameters = list(filter(lambda p: p.grad is not None, parameters)) + norm_type = float(norm_type) + if clip_value is not None: + clip_value = float(clip_value) + + total_norm = 0 + for p in parameters: + param_norm = p.grad.data.norm(norm_type) + total_norm += param_norm.item() ** norm_type + if clip_value is not None: + p.grad.data.clamp_(min=-clip_value, max=clip_value) + total_norm = total_norm ** (1.0 / norm_type) + return total_norm diff --git a/rvc/infer/lib/infer_pack/models.py b/rvc/infer/lib/infer_pack/models.py new file mode 100644 index 0000000..a1a27e2 --- /dev/null +++ b/rvc/infer/lib/infer_pack/models.py @@ -0,0 +1,1242 @@ +import math +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + +import numpy as np +import torch +from torch import nn +from torch.nn import AvgPool1d, Conv1d, Conv2d, ConvTranspose1d +from torch.nn import functional as F +from torch.nn.utils import remove_weight_norm, spectral_norm, weight_norm +from infer.lib.infer_pack import attentions, commons, modules +from infer.lib.infer_pack.commons import get_padding, init_weights + +has_xpu = bool(hasattr(torch, "xpu") and torch.xpu.is_available()) + + +class TextEncoder(nn.Module): + def __init__( + self, + in_channels, + out_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + f0=True, + ): + super(TextEncoder, self).__init__() + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.emb_phone = nn.Linear(in_channels, hidden_channels) + self.lrelu = nn.LeakyReLU(0.1, inplace=True) + if f0 == True: + self.emb_pitch = nn.Embedding(256, hidden_channels) # pitch 256 + self.encoder = attentions.Encoder( + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward( + self, + phone: torch.Tensor, + pitch: torch.Tensor, + lengths: torch.Tensor, + skip_head: Optional[torch.Tensor] = None, + ): + if pitch is None: + x = self.emb_phone(phone) + else: + x = self.emb_phone(phone) + self.emb_pitch(pitch) + x = x * math.sqrt(self.hidden_channels) # [b, t, h] + x = self.lrelu(x) + x = torch.transpose(x, 1, -1) # [b, h, t] + x_mask = torch.unsqueeze(commons.sequence_mask(lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.encoder(x * x_mask, x_mask) + if skip_head is not None: + assert isinstance(skip_head, torch.Tensor) + head = int(skip_head.item()) + x = x[:, :, head:] + x_mask = x_mask[:, :, head:] + stats = self.proj(x) * x_mask + m, logs = torch.split(stats, self.out_channels, dim=1) + return m, logs, x_mask + + +class ResidualCouplingBlock(nn.Module): + def __init__( + self, + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + n_flows=4, + gin_channels=0, + ): + super(ResidualCouplingBlock, self).__init__() + self.channels = channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.n_flows = n_flows + self.gin_channels = gin_channels + + self.flows = nn.ModuleList() + for i in range(n_flows): + self.flows.append( + modules.ResidualCouplingLayer( + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=gin_channels, + mean_only=True, + ) + ) + self.flows.append(modules.Flip()) + + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse: bool = False, + ): + if not reverse: + for flow in self.flows: + x, _ = flow(x, x_mask, g=g, reverse=reverse) + else: + for flow in self.flows[::-1]: + x, _ = flow.forward(x, x_mask, g=g, reverse=reverse) + return x + + def remove_weight_norm(self): + for i in range(self.n_flows): + self.flows[i * 2].remove_weight_norm() + + def __prepare_scriptable__(self): + for i in range(self.n_flows): + for hook in self.flows[i * 2]._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flows[i * 2]) + + return self + + +class PosteriorEncoder(nn.Module): + def __init__( + self, + in_channels, + out_channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=0, + ): + super(PosteriorEncoder, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.gin_channels = gin_channels + + self.pre = nn.Conv1d(in_channels, hidden_channels, 1) + self.enc = modules.WN( + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=gin_channels, + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward( + self, x: torch.Tensor, x_lengths: torch.Tensor, g: Optional[torch.Tensor] = None + ): + x_mask = torch.unsqueeze(commons.sequence_mask(x_lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.pre(x) * x_mask + x = self.enc(x, x_mask, g=g) + stats = self.proj(x) * x_mask + m, logs = torch.split(stats, self.out_channels, dim=1) + z = (m + torch.randn_like(m) * torch.exp(logs)) * x_mask + return z, m, logs, x_mask + + def remove_weight_norm(self): + self.enc.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.enc._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc) + return self + + +class Generator(torch.nn.Module): + def __init__( + self, + initial_channel, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=0, + ): + super(Generator, self).__init__() + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + self.conv_pre = Conv1d( + initial_channel, upsample_initial_channel, 7, 1, padding=3 + ) + resblock = modules.ResBlock1 if resblock == "1" else modules.ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + self.ups.append( + weight_norm( + ConvTranspose1d( + upsample_initial_channel // (2**i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(resblock(ch, k, d)) + + self.conv_post = Conv1d(ch, 1, 7, 1, padding=3, bias=False) + self.ups.apply(init_weights) + + if gin_channels != 0: + self.cond = nn.Conv1d(gin_channels, upsample_initial_channel, 1) + + def forward( + self, + x: torch.Tensor, + g: Optional[torch.Tensor] = None, + n_res: Optional[torch.Tensor] = None, + ): + if n_res is not None: + assert isinstance(n_res, torch.Tensor) + n = int(n_res.item()) + if n != x.shape[-1]: + x = F.interpolate(x, size=n, mode="linear") + x = self.conv_pre(x) + if g is not None: + x = x + self.cond(g) + + for i in range(self.num_upsamples): + x = F.leaky_relu(x, modules.LRELU_SLOPE) + x = self.ups[i](x) + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i * self.num_kernels + j](x) + else: + xs += self.resblocks[i * self.num_kernels + j](x) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + + return x + + def __prepare_scriptable__(self): + for l in self.ups: + for hook in l._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + + for l in self.resblocks: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + + +class SineGen(torch.nn.Module): + """Definition of sine generator + SineGen(samp_rate, harmonic_num = 0, + sine_amp = 0.1, noise_std = 0.003, + voiced_threshold = 0, + flag_for_pulse=False) + samp_rate: sampling rate in Hz + harmonic_num: number of harmonic overtones (default 0) + sine_amp: amplitude of sine-wavefrom (default 0.1) + noise_std: std of Gaussian noise (default 0.003) + voiced_thoreshold: F0 threshold for U/V classification (default 0) + flag_for_pulse: this SinGen is used inside PulseGen (default False) + Note: when flag_for_pulse is True, the first time step of a voiced + segment is always sin(torch.pi) or cos(0) + """ + + def __init__( + self, + samp_rate, + harmonic_num=0, + sine_amp=0.1, + noise_std=0.003, + voiced_threshold=0, + flag_for_pulse=False, + ): + super(SineGen, self).__init__() + self.sine_amp = sine_amp + self.noise_std = noise_std + self.harmonic_num = harmonic_num + self.dim = self.harmonic_num + 1 + self.sampling_rate = samp_rate + self.voiced_threshold = voiced_threshold + + def _f02uv(self, f0): + # generate uv signal + uv = torch.ones_like(f0) + uv = uv * (f0 > self.voiced_threshold) + if uv.device.type == "privateuseone": # for DirectML + uv = uv.float() + return uv + + def forward(self, f0: torch.Tensor, upp: int): + """sine_tensor, uv = forward(f0) + input F0: tensor(batchsize=1, length, dim=1) + f0 for unvoiced steps should be 0 + output sine_tensor: tensor(batchsize=1, length, dim) + output uv: tensor(batchsize=1, length, 1) + """ + with torch.no_grad(): + f0 = f0[:, None].transpose(1, 2) + f0_buf = torch.zeros(f0.shape[0], f0.shape[1], self.dim, device=f0.device) + # fundamental component + f0_buf[:, :, 0] = f0[:, :, 0] + for idx in range(self.harmonic_num): + f0_buf[:, :, idx + 1] = f0_buf[:, :, 0] * ( + idx + 2 + ) # idx + 2: the (idx+1)-th overtone, (idx+2)-th harmonic + rad_values = ( + f0_buf / self.sampling_rate + ) % 1 ###%1意味着n_har的乘积无法后处理优化 + rand_ini = torch.rand( + f0_buf.shape[0], f0_buf.shape[2], device=f0_buf.device + ) + rand_ini[:, 0] = 0 + rad_values[:, 0, :] = rad_values[:, 0, :] + rand_ini + tmp_over_one = torch.cumsum( + rad_values, 1 + ) # % 1 #####%1意味着后面的cumsum无法再优化 + tmp_over_one *= upp + tmp_over_one = F.interpolate( + tmp_over_one.transpose(2, 1), + scale_factor=float(upp), + mode="linear", + align_corners=True, + ).transpose(2, 1) + rad_values = F.interpolate( + rad_values.transpose(2, 1), scale_factor=float(upp), mode="nearest" + ).transpose( + 2, 1 + ) ####### + tmp_over_one %= 1 + tmp_over_one_idx = (tmp_over_one[:, 1:, :] - tmp_over_one[:, :-1, :]) < 0 + cumsum_shift = torch.zeros_like(rad_values) + cumsum_shift[:, 1:, :] = tmp_over_one_idx * -1.0 + sine_waves = torch.sin( + torch.cumsum(rad_values + cumsum_shift, dim=1) * 2 * torch.pi + ) + sine_waves = sine_waves * self.sine_amp + uv = self._f02uv(f0) + uv = F.interpolate( + uv.transpose(2, 1), scale_factor=float(upp), mode="nearest" + ).transpose(2, 1) + noise_amp = uv * self.noise_std + (1 - uv) * self.sine_amp / 3 + noise = noise_amp * torch.randn_like(sine_waves) + sine_waves = sine_waves * uv + noise + return sine_waves, uv, noise + + +class SourceModuleHnNSF(torch.nn.Module): + """SourceModule for hn-nsf + SourceModule(sampling_rate, harmonic_num=0, sine_amp=0.1, + add_noise_std=0.003, voiced_threshod=0) + sampling_rate: sampling_rate in Hz + harmonic_num: number of harmonic above F0 (default: 0) + sine_amp: amplitude of sine source signal (default: 0.1) + add_noise_std: std of additive Gaussian noise (default: 0.003) + note that amplitude of noise in unvoiced is decided + by sine_amp + voiced_threshold: threhold to set U/V given F0 (default: 0) + Sine_source, noise_source = SourceModuleHnNSF(F0_sampled) + F0_sampled (batchsize, length, 1) + Sine_source (batchsize, length, 1) + noise_source (batchsize, length 1) + uv (batchsize, length, 1) + """ + + def __init__( + self, + sampling_rate, + harmonic_num=0, + sine_amp=0.1, + add_noise_std=0.003, + voiced_threshod=0, + is_half=True, + ): + super(SourceModuleHnNSF, self).__init__() + + self.sine_amp = sine_amp + self.noise_std = add_noise_std + self.is_half = is_half + # to produce sine waveforms + self.l_sin_gen = SineGen( + sampling_rate, harmonic_num, sine_amp, add_noise_std, voiced_threshod + ) + + # to merge source harmonics into a single excitation + self.l_linear = torch.nn.Linear(harmonic_num + 1, 1) + self.l_tanh = torch.nn.Tanh() + # self.ddtype:int = -1 + + def forward(self, x: torch.Tensor, upp: int = 1): + # if self.ddtype ==-1: + # self.ddtype = self.l_linear.weight.dtype + sine_wavs, uv, _ = self.l_sin_gen(x, upp) + # print(x.dtype,sine_wavs.dtype,self.l_linear.weight.dtype) + # if self.is_half: + # sine_wavs = sine_wavs.half() + # sine_merge = self.l_tanh(self.l_linear(sine_wavs.to(x))) + # print(sine_wavs.dtype,self.ddtype) + # if sine_wavs.dtype != self.l_linear.weight.dtype: + sine_wavs = sine_wavs.to(dtype=self.l_linear.weight.dtype) + sine_merge = self.l_tanh(self.l_linear(sine_wavs)) + return sine_merge, None, None # noise, uv + + +class GeneratorNSF(torch.nn.Module): + def __init__( + self, + initial_channel, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels, + sr, + is_half=False, + ): + super(GeneratorNSF, self).__init__() + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + + self.f0_upsamp = torch.nn.Upsample(scale_factor=math.prod(upsample_rates)) + self.m_source = SourceModuleHnNSF( + sampling_rate=sr, harmonic_num=0, is_half=is_half + ) + self.noise_convs = nn.ModuleList() + self.conv_pre = Conv1d( + initial_channel, upsample_initial_channel, 7, 1, padding=3 + ) + resblock = modules.ResBlock1 if resblock == "1" else modules.ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + c_cur = upsample_initial_channel // (2 ** (i + 1)) + self.ups.append( + weight_norm( + ConvTranspose1d( + upsample_initial_channel // (2**i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + if i + 1 < len(upsample_rates): + stride_f0 = math.prod(upsample_rates[i + 1 :]) + self.noise_convs.append( + Conv1d( + 1, + c_cur, + kernel_size=stride_f0 * 2, + stride=stride_f0, + padding=stride_f0 // 2, + ) + ) + else: + self.noise_convs.append(Conv1d(1, c_cur, kernel_size=1)) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(resblock(ch, k, d)) + + self.conv_post = Conv1d(ch, 1, 7, 1, padding=3, bias=False) + self.ups.apply(init_weights) + + if gin_channels != 0: + self.cond = nn.Conv1d(gin_channels, upsample_initial_channel, 1) + + self.upp = math.prod(upsample_rates) + + self.lrelu_slope = modules.LRELU_SLOPE + + def forward( + self, + x, + f0, + g: Optional[torch.Tensor] = None, + n_res: Optional[torch.Tensor] = None, + ): + har_source, noi_source, uv = self.m_source(f0, self.upp) + har_source = har_source.transpose(1, 2) + if n_res is not None: + assert isinstance(n_res, torch.Tensor) + n = int(n_res.item()) + if n * self.upp != har_source.shape[-1]: + har_source = F.interpolate(har_source, size=n * self.upp, mode="linear") + if n != x.shape[-1]: + x = F.interpolate(x, size=n, mode="linear") + x = self.conv_pre(x) + if g is not None: + x = x + self.cond(g) + # torch.jit.script() does not support direct indexing of torch modules + # That's why I wrote this + for i, (ups, noise_convs) in enumerate(zip(self.ups, self.noise_convs)): + if i < self.num_upsamples: + x = F.leaky_relu(x, self.lrelu_slope) + x = ups(x) + x_source = noise_convs(har_source) + x = x + x_source + xs: Optional[torch.Tensor] = None + l = [i * self.num_kernels + j for j in range(self.num_kernels)] + for j, resblock in enumerate(self.resblocks): + if j in l: + if xs is None: + xs = resblock(x) + else: + xs += resblock(x) + # This assertion cannot be ignored! \ + # If ignored, it will cause torch.jit.script() compilation errors + assert isinstance(xs, torch.Tensor) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + + return x + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + + def __prepare_scriptable__(self): + for l in self.ups: + for hook in l._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + for l in self.resblocks: + for hook in self.resblocks._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + +sr2sr = { + "32k": 32000, + "40k": 40000, + "48k": 48000, +} + + +class SynthesizerTrnMs256NSFsid(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + **kwargs + ): + super(SynthesizerTrnMs256NSFsid, self).__init__() + if isinstance(sr, str): + sr = sr2sr[sr] + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder( + 256, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.dec = GeneratorNSF( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + sr=sr, + is_half=kwargs["is_half"], + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + logger.debug( + "gin_channels: " + + str(gin_channels) + + ", self.spk_embed_dim: " + + str(self.spk_embed_dim) + ) + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + if hasattr(self, "enc_q"): + self.enc_q.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.dec._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.dec) + for hook in self.flow._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flow) + if hasattr(self, "enc_q"): + for hook in self.enc_q._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc_q) + return self + + @torch.jit.ignore + def forward( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + pitch: torch.Tensor, + pitchf: torch.Tensor, + y: torch.Tensor, + y_lengths: torch.Tensor, + ds: Optional[torch.Tensor] = None, + ): # 这里ds是id,[bs,1] + # print(1,pitch.shape)#[bs,t] + g = self.emb_g(ds).unsqueeze(-1) # [b, 256, 1]##1是t,广播的 + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g) + z_p = self.flow(z, y_mask, g=g) + z_slice, ids_slice = commons.rand_slice_segments( + z, y_lengths, self.segment_size + ) + # print(-1,pitchf.shape,ids_slice,self.segment_size,self.hop_length,self.segment_size//self.hop_length) + pitchf = commons.slice_segments2(pitchf, ids_slice, self.segment_size) + # print(-2,pitchf.shape,z_slice.shape) + o = self.dec(z_slice, pitchf, g=g) + return o, ids_slice, x_mask, y_mask, (z, z_p, m_p, logs_p, m_q, logs_q) + + @torch.jit.export + def infer( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + pitch: torch.Tensor, + nsff0: torch.Tensor, + sid: torch.Tensor, + skip_head: Optional[torch.Tensor] = None, + return_length: Optional[torch.Tensor] = None, + return_length2: Optional[torch.Tensor] = None, + ): + g = self.emb_g(sid).unsqueeze(-1) + if skip_head is not None and return_length is not None: + assert isinstance(skip_head, torch.Tensor) + assert isinstance(return_length, torch.Tensor) + head = int(skip_head.item()) + length = int(return_length.item()) + flow_head = torch.clamp(skip_head - 24, min=0) + dec_head = head - int(flow_head.item()) + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths, flow_head) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + z = self.flow(z_p, x_mask, g=g, reverse=True) + z = z[:, :, dec_head : dec_head + length] + x_mask = x_mask[:, :, dec_head : dec_head + length] + nsff0 = nsff0[:, head : head + length] + else: + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec(z * x_mask, nsff0, g=g, n_res=return_length2) + return o, x_mask, (z, z_p, m_p, logs_p) + + +class SynthesizerTrnMs768NSFsid(SynthesizerTrnMs256NSFsid): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + **kwargs + ): + super(SynthesizerTrnMs768NSFsid, self).__init__( + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + **kwargs + ) + del self.enc_p + self.enc_p = TextEncoder( + 768, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + + +class SynthesizerTrnMs256NSFsid_nono(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr=None, + **kwargs + ): + super(SynthesizerTrnMs256NSFsid_nono, self).__init__() + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder( + 256, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + f0=False, + ) + self.dec = Generator( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + logger.debug( + "gin_channels: " + + str(gin_channels) + + ", self.spk_embed_dim: " + + str(self.spk_embed_dim) + ) + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + if hasattr(self, "enc_q"): + self.enc_q.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.dec._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.dec) + for hook in self.flow._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flow) + if hasattr(self, "enc_q"): + for hook in self.enc_q._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc_q) + return self + + @torch.jit.ignore + def forward(self, phone, phone_lengths, y, y_lengths, ds): # 这里ds是id,[bs,1] + g = self.emb_g(ds).unsqueeze(-1) # [b, 256, 1]##1是t,广播的 + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths) + z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g) + z_p = self.flow(z, y_mask, g=g) + z_slice, ids_slice = commons.rand_slice_segments( + z, y_lengths, self.segment_size + ) + o = self.dec(z_slice, g=g) + return o, ids_slice, x_mask, y_mask, (z, z_p, m_p, logs_p, m_q, logs_q) + + @torch.jit.export + def infer( + self, + phone: torch.Tensor, + phone_lengths: torch.Tensor, + sid: torch.Tensor, + skip_head: Optional[torch.Tensor] = None, + return_length: Optional[torch.Tensor] = None, + return_length2: Optional[torch.Tensor] = None, + ): + g = self.emb_g(sid).unsqueeze(-1) + if skip_head is not None and return_length is not None: + assert isinstance(skip_head, torch.Tensor) + assert isinstance(return_length, torch.Tensor) + head = int(skip_head.item()) + length = int(return_length.item()) + flow_head = torch.clamp(skip_head - 24, min=0) + dec_head = head - int(flow_head.item()) + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths, flow_head) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + z = self.flow(z_p, x_mask, g=g, reverse=True) + z = z[:, :, dec_head : dec_head + length] + x_mask = x_mask[:, :, dec_head : dec_head + length] + else: + m_p, logs_p, x_mask = self.enc_p(phone, None, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * torch.randn_like(m_p) * 0.66666) * x_mask + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec(z * x_mask, g=g, n_res=return_length2) + return o, x_mask, (z, z_p, m_p, logs_p) + + +class SynthesizerTrnMs768NSFsid_nono(SynthesizerTrnMs256NSFsid_nono): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr=None, + **kwargs + ): + super(SynthesizerTrnMs768NSFsid_nono, self).__init__( + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + **kwargs + ) + del self.enc_p + self.enc_p = TextEncoder( + 768, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + f0=False, + ) + + +class MultiPeriodDiscriminator(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(MultiPeriodDiscriminator, self).__init__() + periods = [2, 3, 5, 7, 11, 17] + # periods = [3, 5, 7, 11, 17, 23, 37] + + discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)] + discs = discs + [ + DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods + ] + self.discriminators = nn.ModuleList(discs) + + def forward(self, y, y_hat): + y_d_rs = [] # + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + # for j in range(len(fmap_r)): + # print(i,j,y.shape,y_hat.shape,fmap_r[j].shape,fmap_g[j].shape) + y_d_rs.append(y_d_r) + y_d_gs.append(y_d_g) + fmap_rs.append(fmap_r) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class MultiPeriodDiscriminatorV2(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(MultiPeriodDiscriminatorV2, self).__init__() + # periods = [2, 3, 5, 7, 11, 17] + periods = [2, 3, 5, 7, 11, 17, 23, 37] + + discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)] + discs = discs + [ + DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods + ] + self.discriminators = nn.ModuleList(discs) + + def forward(self, y, y_hat): + y_d_rs = [] # + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + # for j in range(len(fmap_r)): + # print(i,j,y.shape,y_hat.shape,fmap_r[j].shape,fmap_g[j].shape) + y_d_rs.append(y_d_r) + y_d_gs.append(y_d_g) + fmap_rs.append(fmap_r) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class DiscriminatorS(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(DiscriminatorS, self).__init__() + norm_f = weight_norm if use_spectral_norm == False else spectral_norm + self.convs = nn.ModuleList( + [ + norm_f(Conv1d(1, 16, 15, 1, padding=7)), + norm_f(Conv1d(16, 64, 41, 4, groups=4, padding=20)), + norm_f(Conv1d(64, 256, 41, 4, groups=16, padding=20)), + norm_f(Conv1d(256, 1024, 41, 4, groups=64, padding=20)), + norm_f(Conv1d(1024, 1024, 41, 4, groups=256, padding=20)), + norm_f(Conv1d(1024, 1024, 5, 1, padding=2)), + ] + ) + self.conv_post = norm_f(Conv1d(1024, 1, 3, 1, padding=1)) + + def forward(self, x): + fmap = [] + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, modules.LRELU_SLOPE) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap + + +class DiscriminatorP(torch.nn.Module): + def __init__(self, period, kernel_size=5, stride=3, use_spectral_norm=False): + super(DiscriminatorP, self).__init__() + self.period = period + self.use_spectral_norm = use_spectral_norm + norm_f = weight_norm if use_spectral_norm == False else spectral_norm + self.convs = nn.ModuleList( + [ + norm_f( + Conv2d( + 1, + 32, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 32, + 128, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 128, + 512, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 512, + 1024, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 1024, + 1024, + (kernel_size, 1), + 1, + padding=(get_padding(kernel_size, 1), 0), + ) + ), + ] + ) + self.conv_post = norm_f(Conv2d(1024, 1, (3, 1), 1, padding=(1, 0))) + + def forward(self, x): + fmap = [] + + # 1d to 2d + b, c, t = x.shape + if t % self.period != 0: # pad first + n_pad = self.period - (t % self.period) + if has_xpu and x.dtype == torch.bfloat16: + x = F.pad(x.to(dtype=torch.float16), (0, n_pad), "reflect").to( + dtype=torch.bfloat16 + ) + else: + x = F.pad(x, (0, n_pad), "reflect") + t = t + n_pad + x = x.view(b, c, t // self.period, self.period) + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, modules.LRELU_SLOPE) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap diff --git a/rvc/infer/lib/infer_pack/models_onnx.py b/rvc/infer/lib/infer_pack/models_onnx.py new file mode 100644 index 0000000..b06bb9a --- /dev/null +++ b/rvc/infer/lib/infer_pack/models_onnx.py @@ -0,0 +1,881 @@ +import math +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + +import numpy as np +import torch +from torch import nn +from torch.nn import AvgPool1d, Conv1d, Conv2d, ConvTranspose1d +from torch.nn import functional as F +from torch.nn.utils import remove_weight_norm, spectral_norm, weight_norm + +from infer.lib.infer_pack import commons, modules +from infer.lib.infer_pack.commons import get_padding, init_weights +import infer.lib.infer_pack.attentions_onnx as attentions + +has_xpu = bool(hasattr(torch, "xpu") and torch.xpu.is_available()) + + +class TextEncoder(nn.Module): + def __init__( + self, + in_channels, + out_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + f0=True, + ): + super(TextEncoder, self).__init__() + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.emb_phone = nn.Linear(in_channels, hidden_channels) + self.lrelu = nn.LeakyReLU(0.1, inplace=True) + if f0 == True: + self.emb_pitch = nn.Embedding(256, hidden_channels) # pitch 256 + self.encoder = attentions.Encoder( + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward( + self, + phone: torch.Tensor, + pitch: torch.Tensor, + lengths: torch.Tensor, + skip_head: Optional[torch.Tensor] = None, + ): + if pitch is None: + x = self.emb_phone(phone) + else: + x = self.emb_phone(phone) + self.emb_pitch(pitch) + x = x * math.sqrt(self.hidden_channels) # [b, t, h] + x = self.lrelu(x) + x = torch.transpose(x, 1, -1) # [b, h, t] + x_mask = torch.unsqueeze(commons.sequence_mask(lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.encoder(x * x_mask, x_mask) + stats = self.proj(x) * x_mask + m, logs = torch.split(stats, self.out_channels, dim=1) + return m, logs, x_mask + + +class ResidualCouplingBlock(nn.Module): + def __init__( + self, + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + n_flows=4, + gin_channels=0, + ): + super(ResidualCouplingBlock, self).__init__() + self.channels = channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.n_flows = n_flows + self.gin_channels = gin_channels + + self.flows = nn.ModuleList() + for i in range(n_flows): + self.flows.append( + modules.ResidualCouplingLayer( + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=gin_channels, + mean_only=True, + ) + ) + self.flows.append(modules.Flip()) + + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse: bool = False, + ): + if not reverse: + for flow in self.flows: + x, _ = flow(x, x_mask, g=g, reverse=reverse) + else: + for flow in reversed(self.flows): + x, _ = flow.forward(x, x_mask, g=g, reverse=reverse) + return x + + def remove_weight_norm(self): + for i in range(self.n_flows): + self.flows[i * 2].remove_weight_norm() + + def __prepare_scriptable__(self): + for i in range(self.n_flows): + for hook in self.flows[i * 2]._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.flows[i * 2]) + + return self + + +class PosteriorEncoder(nn.Module): + def __init__( + self, + in_channels, + out_channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=0, + ): + super(PosteriorEncoder, self).__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.gin_channels = gin_channels + + self.pre = nn.Conv1d(in_channels, hidden_channels, 1) + self.enc = modules.WN( + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=gin_channels, + ) + self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1) + + def forward( + self, x: torch.Tensor, x_lengths: torch.Tensor, g: Optional[torch.Tensor] = None + ): + x_mask = torch.unsqueeze(commons.sequence_mask(x_lengths, x.size(2)), 1).to( + x.dtype + ) + x = self.pre(x) * x_mask + x = self.enc(x, x_mask, g=g) + stats = self.proj(x) * x_mask + m, logs = torch.split(stats, self.out_channels, dim=1) + z = (m + torch.randn_like(m) * torch.exp(logs)) * x_mask + return z, m, logs, x_mask + + def remove_weight_norm(self): + self.enc.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.enc._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc) + return self + + +class Generator(torch.nn.Module): + def __init__( + self, + initial_channel, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=0, + ): + super(Generator, self).__init__() + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + self.conv_pre = Conv1d( + initial_channel, upsample_initial_channel, 7, 1, padding=3 + ) + resblock = modules.ResBlock1 if resblock == "1" else modules.ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + self.ups.append( + weight_norm( + ConvTranspose1d( + upsample_initial_channel // (2**i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(resblock(ch, k, d)) + + self.conv_post = Conv1d(ch, 1, 7, 1, padding=3, bias=False) + self.ups.apply(init_weights) + + if gin_channels != 0: + self.cond = nn.Conv1d(gin_channels, upsample_initial_channel, 1) + + def forward(self, x: torch.Tensor, g: Optional[torch.Tensor] = None): + x = self.conv_pre(x) + if g is not None: + x = x + self.cond(g) + + for i in range(self.num_upsamples): + x = F.leaky_relu(x, modules.LRELU_SLOPE) + x = self.ups[i](x) + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i * self.num_kernels + j](x) + else: + xs += self.resblocks[i * self.num_kernels + j](x) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + + return x + + def __prepare_scriptable__(self): + for l in self.ups: + for hook in l._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + + for l in self.resblocks: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + + +class SineGen(torch.nn.Module): + """Definition of sine generator + SineGen(samp_rate, harmonic_num = 0, + sine_amp = 0.1, noise_std = 0.003, + voiced_threshold = 0, + flag_for_pulse=False) + samp_rate: sampling rate in Hz + harmonic_num: number of harmonic overtones (default 0) + sine_amp: amplitude of sine-wavefrom (default 0.1) + noise_std: std of Gaussian noise (default 0.003) + voiced_thoreshold: F0 threshold for U/V classification (default 0) + flag_for_pulse: this SinGen is used inside PulseGen (default False) + Note: when flag_for_pulse is True, the first time step of a voiced + segment is always sin(torch.pi) or cos(0) + """ + + def __init__( + self, + samp_rate, + harmonic_num=0, + sine_amp=0.1, + noise_std=0.003, + voiced_threshold=0, + flag_for_pulse=False, + ): + super(SineGen, self).__init__() + self.sine_amp = sine_amp + self.noise_std = noise_std + self.harmonic_num = harmonic_num + self.dim = self.harmonic_num + 1 + self.sampling_rate = samp_rate + self.voiced_threshold = voiced_threshold + + def _f02uv(self, f0): + # generate uv signal + uv = torch.ones_like(f0) + uv = uv * (f0 > self.voiced_threshold) + if uv.device.type == "privateuseone": # for DirectML + uv = uv.float() + return uv + + def forward(self, f0: torch.Tensor, upp: int): + """sine_tensor, uv = forward(f0) + input F0: tensor(batchsize=1, length, dim=1) + f0 for unvoiced steps should be 0 + output sine_tensor: tensor(batchsize=1, length, dim) + output uv: tensor(batchsize=1, length, 1) + """ + with torch.no_grad(): + f0 = f0[:, None].transpose(1, 2) + f0_buf = torch.zeros(f0.shape[0], f0.shape[1], self.dim, device=f0.device) + # fundamental component + f0_buf[:, :, 0] = f0[:, :, 0] + for idx in range(self.harmonic_num): + f0_buf[:, :, idx + 1] = f0_buf[:, :, 0] * ( + idx + 2 + ) # idx + 2: the (idx+1)-th overtone, (idx+2)-th harmonic + rad_values = ( + f0_buf / self.sampling_rate + ) % 1 ###%1意味着n_har的乘积无法后处理优化 + rand_ini = torch.rand( + f0_buf.shape[0], f0_buf.shape[2], device=f0_buf.device + ) + rand_ini[:, 0] = 0 + rad_values[:, 0, :] = rad_values[:, 0, :] + rand_ini + tmp_over_one = torch.cumsum( + rad_values, 1 + ) # % 1 #####%1意味着后面的cumsum无法再优化 + tmp_over_one *= upp + tmp_over_one = F.interpolate( + tmp_over_one.transpose(2, 1), + scale_factor=float(upp), + mode="linear", + align_corners=True, + ).transpose(2, 1) + rad_values = F.interpolate( + rad_values.transpose(2, 1), scale_factor=float(upp), mode="nearest" + ).transpose( + 2, 1 + ) ####### + tmp_over_one %= 1 + tmp_over_one_idx = (tmp_over_one[:, 1:, :] - tmp_over_one[:, :-1, :]) < 0 + cumsum_shift = torch.zeros_like(rad_values) + cumsum_shift[:, 1:, :] = tmp_over_one_idx * -1.0 + sine_waves = torch.sin( + torch.cumsum(rad_values + cumsum_shift, dim=1) * 2 * torch.pi + ) + sine_waves = sine_waves * self.sine_amp + uv = self._f02uv(f0) + uv = F.interpolate( + uv.transpose(2, 1), scale_factor=float(upp), mode="nearest" + ).transpose(2, 1) + noise_amp = uv * self.noise_std + (1 - uv) * self.sine_amp / 3 + noise = noise_amp * torch.randn_like(sine_waves) + sine_waves = sine_waves * uv + noise + return sine_waves, uv, noise + + +class SourceModuleHnNSF(torch.nn.Module): + """SourceModule for hn-nsf + SourceModule(sampling_rate, harmonic_num=0, sine_amp=0.1, + add_noise_std=0.003, voiced_threshod=0) + sampling_rate: sampling_rate in Hz + harmonic_num: number of harmonic above F0 (default: 0) + sine_amp: amplitude of sine source signal (default: 0.1) + add_noise_std: std of additive Gaussian noise (default: 0.003) + note that amplitude of noise in unvoiced is decided + by sine_amp + voiced_threshold: threhold to set U/V given F0 (default: 0) + Sine_source, noise_source = SourceModuleHnNSF(F0_sampled) + F0_sampled (batchsize, length, 1) + Sine_source (batchsize, length, 1) + noise_source (batchsize, length 1) + uv (batchsize, length, 1) + """ + + def __init__( + self, + sampling_rate, + harmonic_num=0, + sine_amp=0.1, + add_noise_std=0.003, + voiced_threshod=0, + is_half=True, + ): + super(SourceModuleHnNSF, self).__init__() + + self.sine_amp = sine_amp + self.noise_std = add_noise_std + self.is_half = is_half + # to produce sine waveforms + self.l_sin_gen = SineGen( + sampling_rate, harmonic_num, sine_amp, add_noise_std, voiced_threshod + ) + + # to merge source harmonics into a single excitation + self.l_linear = torch.nn.Linear(harmonic_num + 1, 1) + self.l_tanh = torch.nn.Tanh() + # self.ddtype:int = -1 + + def forward(self, x: torch.Tensor, upp: int = 1): + # if self.ddtype ==-1: + # self.ddtype = self.l_linear.weight.dtype + sine_wavs, uv, _ = self.l_sin_gen(x, upp) + # print(x.dtype,sine_wavs.dtype,self.l_linear.weight.dtype) + # if self.is_half: + # sine_wavs = sine_wavs.half() + # sine_merge = self.l_tanh(self.l_linear(sine_wavs.to(x))) + # print(sine_wavs.dtype,self.ddtype) + # if sine_wavs.dtype != self.l_linear.weight.dtype: + sine_wavs = sine_wavs.to(dtype=self.l_linear.weight.dtype) + sine_merge = self.l_tanh(self.l_linear(sine_wavs)) + return sine_merge, None, None # noise, uv + + +class GeneratorNSF(torch.nn.Module): + def __init__( + self, + initial_channel, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels, + sr, + is_half=False, + ): + super(GeneratorNSF, self).__init__() + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_rates) + + self.f0_upsamp = torch.nn.Upsample(scale_factor=math.prod(upsample_rates)) + self.m_source = SourceModuleHnNSF( + sampling_rate=sr, harmonic_num=0, is_half=is_half + ) + self.noise_convs = nn.ModuleList() + self.conv_pre = Conv1d( + initial_channel, upsample_initial_channel, 7, 1, padding=3 + ) + resblock = modules.ResBlock1 if resblock == "1" else modules.ResBlock2 + + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)): + c_cur = upsample_initial_channel // (2 ** (i + 1)) + self.ups.append( + weight_norm( + ConvTranspose1d( + upsample_initial_channel // (2**i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + if i + 1 < len(upsample_rates): + stride_f0 = math.prod(upsample_rates[i + 1 :]) + self.noise_convs.append( + Conv1d( + 1, + c_cur, + kernel_size=stride_f0 * 2, + stride=stride_f0, + padding=stride_f0 // 2, + ) + ) + else: + self.noise_convs.append(Conv1d(1, c_cur, kernel_size=1)) + + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(resblock(ch, k, d)) + + self.conv_post = Conv1d(ch, 1, 7, 1, padding=3, bias=False) + self.ups.apply(init_weights) + + if gin_channels != 0: + self.cond = nn.Conv1d(gin_channels, upsample_initial_channel, 1) + + self.upp = math.prod(upsample_rates) + + self.lrelu_slope = modules.LRELU_SLOPE + + def forward(self, x, f0, g: Optional[torch.Tensor] = None): + har_source, noi_source, uv = self.m_source(f0, self.upp) + har_source = har_source.transpose(1, 2) + x = self.conv_pre(x) + if g is not None: + x = x + self.cond(g) + # torch.jit.script() does not support direct indexing of torch modules + # That's why I wrote this + for i, (ups, noise_convs) in enumerate(zip(self.ups, self.noise_convs)): + if i < self.num_upsamples: + x = F.leaky_relu(x, self.lrelu_slope) + x = ups(x) + x_source = noise_convs(har_source) + x = x + x_source + xs: Optional[torch.Tensor] = None + l = [i * self.num_kernels + j for j in range(self.num_kernels)] + for j, resblock in enumerate(self.resblocks): + if j in l: + if xs is None: + xs = resblock(x) + else: + xs += resblock(x) + # This assertion cannot be ignored! \ + # If ignored, it will cause torch.jit.script() compilation errors + assert isinstance(xs, torch.Tensor) + x = xs / self.num_kernels + x = F.leaky_relu(x) + x = self.conv_post(x) + x = torch.tanh(x) + return x + + def remove_weight_norm(self): + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + + def __prepare_scriptable__(self): + for l in self.ups: + for hook in l._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + for l in self.resblocks: + for hook in self.resblocks._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + +sr2sr = { + "32k": 32000, + "40k": 40000, + "48k": 48000, +} + + +class SynthesizerTrnMsNSFsidM(nn.Module): + def __init__( + self, + spec_channels, + segment_size, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + p_dropout, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + spk_embed_dim, + gin_channels, + sr, + encoder_dim, + **kwargs + ): + super(SynthesizerTrnMsNSFsidM, self).__init__() + if isinstance(sr, str): + sr = sr2sr[sr] + self.spec_channels = spec_channels + self.inter_channels = inter_channels + self.hidden_channels = hidden_channels + self.filter_channels = filter_channels + self.n_heads = n_heads + self.n_layers = n_layers + self.kernel_size = kernel_size + self.p_dropout = float(p_dropout) + self.resblock = resblock + self.resblock_kernel_sizes = resblock_kernel_sizes + self.resblock_dilation_sizes = resblock_dilation_sizes + self.upsample_rates = upsample_rates + self.upsample_initial_channel = upsample_initial_channel + self.upsample_kernel_sizes = upsample_kernel_sizes + self.segment_size = segment_size + self.gin_channels = gin_channels + # self.hop_length = hop_length# + self.spk_embed_dim = spk_embed_dim + self.enc_p = TextEncoder( + encoder_dim, + inter_channels, + hidden_channels, + filter_channels, + n_heads, + n_layers, + kernel_size, + float(p_dropout), + ) + self.dec = GeneratorNSF( + inter_channels, + resblock, + resblock_kernel_sizes, + resblock_dilation_sizes, + upsample_rates, + upsample_initial_channel, + upsample_kernel_sizes, + gin_channels=gin_channels, + sr=sr, + is_half=kwargs["is_half"], + ) + self.enc_q = PosteriorEncoder( + spec_channels, + inter_channels, + hidden_channels, + 5, + 1, + 16, + gin_channels=gin_channels, + ) + self.flow = ResidualCouplingBlock( + inter_channels, hidden_channels, 5, 1, 3, gin_channels=gin_channels + ) + self.emb_g = nn.Embedding(self.spk_embed_dim, gin_channels) + logger.debug( + "gin_channels: " + + str(gin_channels) + + ", self.spk_embed_dim: " + + str(self.spk_embed_dim) + ) + self.speaker_map = None + + def remove_weight_norm(self): + self.dec.remove_weight_norm() + self.flow.remove_weight_norm() + self.enc_q.remove_weight_norm() + + def construct_spkmixmap(self): + self.speaker_map = torch.zeros((self.n_speaker, 1, 1, self.gin_channels)) + for i in range(self.n_speaker): + self.speaker_map[i] = self.emb_g(torch.LongTensor([[i]])) + self.speaker_map = self.speaker_map.unsqueeze(0) + + def forward(self, phone, phone_lengths, pitch, nsff0, g, rnd, max_len=None): + if self.speaker_map is not None: # [N, S] * [S, B, 1, H] + g = g.reshape((g.shape[0], g.shape[1], 1, 1, 1)) # [N, S, B, 1, 1] + g = g * self.speaker_map # [N, S, B, 1, H] + g = torch.sum(g, dim=1) # [N, 1, B, 1, H] + g = g.transpose(0, -1).transpose(0, -2).squeeze(0) # [B, H, N] + else: + g = g.unsqueeze(0) + g = self.emb_g(g).transpose(1, 2) + + m_p, logs_p, x_mask = self.enc_p(phone, pitch, phone_lengths) + z_p = (m_p + torch.exp(logs_p) * rnd) * x_mask + z = self.flow(z_p, x_mask, g=g, reverse=True) + o = self.dec((z * x_mask)[:, :, :max_len], nsff0, g=g) + return o + + +class MultiPeriodDiscriminator(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(MultiPeriodDiscriminator, self).__init__() + periods = [2, 3, 5, 7, 11, 17] + # periods = [3, 5, 7, 11, 17, 23, 37] + + discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)] + discs = discs + [ + DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods + ] + self.discriminators = nn.ModuleList(discs) + + def forward(self, y, y_hat): + y_d_rs = [] # + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + # for j in range(len(fmap_r)): + # print(i,j,y.shape,y_hat.shape,fmap_r[j].shape,fmap_g[j].shape) + y_d_rs.append(y_d_r) + y_d_gs.append(y_d_g) + fmap_rs.append(fmap_r) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class MultiPeriodDiscriminatorV2(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(MultiPeriodDiscriminatorV2, self).__init__() + # periods = [2, 3, 5, 7, 11, 17] + periods = [2, 3, 5, 7, 11, 17, 23, 37] + + discs = [DiscriminatorS(use_spectral_norm=use_spectral_norm)] + discs = discs + [ + DiscriminatorP(i, use_spectral_norm=use_spectral_norm) for i in periods + ] + self.discriminators = nn.ModuleList(discs) + + def forward(self, y, y_hat): + y_d_rs = [] # + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + # for j in range(len(fmap_r)): + # print(i,j,y.shape,y_hat.shape,fmap_r[j].shape,fmap_g[j].shape) + y_d_rs.append(y_d_r) + y_d_gs.append(y_d_g) + fmap_rs.append(fmap_r) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class DiscriminatorS(torch.nn.Module): + def __init__(self, use_spectral_norm=False): + super(DiscriminatorS, self).__init__() + norm_f = weight_norm if use_spectral_norm == False else spectral_norm + self.convs = nn.ModuleList( + [ + norm_f(Conv1d(1, 16, 15, 1, padding=7)), + norm_f(Conv1d(16, 64, 41, 4, groups=4, padding=20)), + norm_f(Conv1d(64, 256, 41, 4, groups=16, padding=20)), + norm_f(Conv1d(256, 1024, 41, 4, groups=64, padding=20)), + norm_f(Conv1d(1024, 1024, 41, 4, groups=256, padding=20)), + norm_f(Conv1d(1024, 1024, 5, 1, padding=2)), + ] + ) + self.conv_post = norm_f(Conv1d(1024, 1, 3, 1, padding=1)) + + def forward(self, x): + fmap = [] + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, modules.LRELU_SLOPE) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap + + +class DiscriminatorP(torch.nn.Module): + def __init__(self, period, kernel_size=5, stride=3, use_spectral_norm=False): + super(DiscriminatorP, self).__init__() + self.period = period + self.use_spectral_norm = use_spectral_norm + norm_f = weight_norm if use_spectral_norm == False else spectral_norm + self.convs = nn.ModuleList( + [ + norm_f( + Conv2d( + 1, + 32, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 32, + 128, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 128, + 512, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 512, + 1024, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(kernel_size, 1), 0), + ) + ), + norm_f( + Conv2d( + 1024, + 1024, + (kernel_size, 1), + 1, + padding=(get_padding(kernel_size, 1), 0), + ) + ), + ] + ) + self.conv_post = norm_f(Conv2d(1024, 1, (3, 1), 1, padding=(1, 0))) + + def forward(self, x): + fmap = [] + + # 1d to 2d + b, c, t = x.shape + if t % self.period != 0: # pad first + n_pad = self.period - (t % self.period) + if has_xpu and x.dtype == torch.bfloat16: + x = F.pad(x.to(dtype=torch.float16), (0, n_pad), "reflect").to( + dtype=torch.bfloat16 + ) + else: + x = F.pad(x, (0, n_pad), "reflect") + t = t + n_pad + x = x.view(b, c, t // self.period, self.period) + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, modules.LRELU_SLOPE) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap diff --git a/rvc/infer/lib/infer_pack/modules.py b/rvc/infer/lib/infer_pack/modules.py new file mode 100644 index 0000000..51aeaf0 --- /dev/null +++ b/rvc/infer/lib/infer_pack/modules.py @@ -0,0 +1,615 @@ +import copy +import math +from typing import Optional, Tuple + +import numpy as np +import scipy +import torch +from torch import nn +from torch.nn import AvgPool1d, Conv1d, Conv2d, ConvTranspose1d +from torch.nn import functional as F +from torch.nn.utils import remove_weight_norm, weight_norm + +from infer.lib.infer_pack import commons +from infer.lib.infer_pack.commons import get_padding, init_weights +from infer.lib.infer_pack.transforms import piecewise_rational_quadratic_transform + +LRELU_SLOPE = 0.1 + + +class LayerNorm(nn.Module): + def __init__(self, channels, eps=1e-5): + super(LayerNorm, self).__init__() + self.channels = channels + self.eps = eps + + self.gamma = nn.Parameter(torch.ones(channels)) + self.beta = nn.Parameter(torch.zeros(channels)) + + def forward(self, x): + x = x.transpose(1, -1) + x = F.layer_norm(x, (self.channels,), self.gamma, self.beta, self.eps) + return x.transpose(1, -1) + + +class ConvReluNorm(nn.Module): + def __init__( + self, + in_channels, + hidden_channels, + out_channels, + kernel_size, + n_layers, + p_dropout, + ): + super(ConvReluNorm, self).__init__() + self.in_channels = in_channels + self.hidden_channels = hidden_channels + self.out_channels = out_channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.p_dropout = float(p_dropout) + assert n_layers > 1, "Number of layers should be larger than 0." + + self.conv_layers = nn.ModuleList() + self.norm_layers = nn.ModuleList() + self.conv_layers.append( + nn.Conv1d( + in_channels, hidden_channels, kernel_size, padding=kernel_size // 2 + ) + ) + self.norm_layers.append(LayerNorm(hidden_channels)) + self.relu_drop = nn.Sequential(nn.ReLU(), nn.Dropout(float(p_dropout))) + for _ in range(n_layers - 1): + self.conv_layers.append( + nn.Conv1d( + hidden_channels, + hidden_channels, + kernel_size, + padding=kernel_size // 2, + ) + ) + self.norm_layers.append(LayerNorm(hidden_channels)) + self.proj = nn.Conv1d(hidden_channels, out_channels, 1) + self.proj.weight.data.zero_() + self.proj.bias.data.zero_() + + def forward(self, x, x_mask): + x_org = x + for i in range(self.n_layers): + x = self.conv_layers[i](x * x_mask) + x = self.norm_layers[i](x) + x = self.relu_drop(x) + x = x_org + self.proj(x) + return x * x_mask + + +class DDSConv(nn.Module): + """ + Dialted and Depth-Separable Convolution + """ + + def __init__(self, channels, kernel_size, n_layers, p_dropout=0.0): + super(DDSConv, self).__init__() + self.channels = channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.p_dropout = float(p_dropout) + + self.drop = nn.Dropout(float(p_dropout)) + self.convs_sep = nn.ModuleList() + self.convs_1x1 = nn.ModuleList() + self.norms_1 = nn.ModuleList() + self.norms_2 = nn.ModuleList() + for i in range(n_layers): + dilation = kernel_size**i + padding = (kernel_size * dilation - dilation) // 2 + self.convs_sep.append( + nn.Conv1d( + channels, + channels, + kernel_size, + groups=channels, + dilation=dilation, + padding=padding, + ) + ) + self.convs_1x1.append(nn.Conv1d(channels, channels, 1)) + self.norms_1.append(LayerNorm(channels)) + self.norms_2.append(LayerNorm(channels)) + + def forward(self, x, x_mask, g: Optional[torch.Tensor] = None): + if g is not None: + x = x + g + for i in range(self.n_layers): + y = self.convs_sep[i](x * x_mask) + y = self.norms_1[i](y) + y = F.gelu(y) + y = self.convs_1x1[i](y) + y = self.norms_2[i](y) + y = F.gelu(y) + y = self.drop(y) + x = x + y + return x * x_mask + + +class WN(torch.nn.Module): + def __init__( + self, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + gin_channels=0, + p_dropout=0, + ): + super(WN, self).__init__() + assert kernel_size % 2 == 1 + self.hidden_channels = hidden_channels + self.kernel_size = (kernel_size,) + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.gin_channels = gin_channels + self.p_dropout = float(p_dropout) + + self.in_layers = torch.nn.ModuleList() + self.res_skip_layers = torch.nn.ModuleList() + self.drop = nn.Dropout(float(p_dropout)) + + if gin_channels != 0: + cond_layer = torch.nn.Conv1d( + gin_channels, 2 * hidden_channels * n_layers, 1 + ) + self.cond_layer = torch.nn.utils.weight_norm(cond_layer, name="weight") + + for i in range(n_layers): + dilation = dilation_rate**i + padding = int((kernel_size * dilation - dilation) / 2) + in_layer = torch.nn.Conv1d( + hidden_channels, + 2 * hidden_channels, + kernel_size, + dilation=dilation, + padding=padding, + ) + in_layer = torch.nn.utils.weight_norm(in_layer, name="weight") + self.in_layers.append(in_layer) + + # last one is not necessary + if i < n_layers - 1: + res_skip_channels = 2 * hidden_channels + else: + res_skip_channels = hidden_channels + + res_skip_layer = torch.nn.Conv1d(hidden_channels, res_skip_channels, 1) + res_skip_layer = torch.nn.utils.weight_norm(res_skip_layer, name="weight") + self.res_skip_layers.append(res_skip_layer) + + def forward( + self, x: torch.Tensor, x_mask: torch.Tensor, g: Optional[torch.Tensor] = None + ): + output = torch.zeros_like(x) + n_channels_tensor = torch.IntTensor([self.hidden_channels]) + + if g is not None: + g = self.cond_layer(g) + + for i, (in_layer, res_skip_layer) in enumerate( + zip(self.in_layers, self.res_skip_layers) + ): + x_in = in_layer(x) + if g is not None: + cond_offset = i * 2 * self.hidden_channels + g_l = g[:, cond_offset : cond_offset + 2 * self.hidden_channels, :] + else: + g_l = torch.zeros_like(x_in) + + acts = commons.fused_add_tanh_sigmoid_multiply(x_in, g_l, n_channels_tensor) + acts = self.drop(acts) + + res_skip_acts = res_skip_layer(acts) + if i < self.n_layers - 1: + res_acts = res_skip_acts[:, : self.hidden_channels, :] + x = (x + res_acts) * x_mask + output = output + res_skip_acts[:, self.hidden_channels :, :] + else: + output = output + res_skip_acts + return output * x_mask + + def remove_weight_norm(self): + if self.gin_channels != 0: + torch.nn.utils.remove_weight_norm(self.cond_layer) + for l in self.in_layers: + torch.nn.utils.remove_weight_norm(l) + for l in self.res_skip_layers: + torch.nn.utils.remove_weight_norm(l) + + def __prepare_scriptable__(self): + if self.gin_channels != 0: + for hook in self.cond_layer._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.cond_layer) + for l in self.in_layers: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + for l in self.res_skip_layers: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + +class ResBlock1(torch.nn.Module): + def __init__(self, channels, kernel_size=3, dilation=(1, 3, 5)): + super(ResBlock1, self).__init__() + self.convs1 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[2], + padding=get_padding(kernel_size, dilation[2]), + ) + ), + ] + ) + self.convs1.apply(init_weights) + + self.convs2 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + ] + ) + self.convs2.apply(init_weights) + self.lrelu_slope = LRELU_SLOPE + + def forward(self, x: torch.Tensor, x_mask: Optional[torch.Tensor] = None): + for c1, c2 in zip(self.convs1, self.convs2): + xt = F.leaky_relu(x, self.lrelu_slope) + if x_mask is not None: + xt = xt * x_mask + xt = c1(xt) + xt = F.leaky_relu(xt, self.lrelu_slope) + if x_mask is not None: + xt = xt * x_mask + xt = c2(xt) + x = xt + x + if x_mask is not None: + x = x * x_mask + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + def __prepare_scriptable__(self): + for l in self.convs1: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + for l in self.convs2: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + +class ResBlock2(torch.nn.Module): + def __init__(self, channels, kernel_size=3, dilation=(1, 3)): + super(ResBlock2, self).__init__() + self.convs = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]), + ) + ), + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]), + ) + ), + ] + ) + self.convs.apply(init_weights) + self.lrelu_slope = LRELU_SLOPE + + def forward(self, x, x_mask: Optional[torch.Tensor] = None): + for c in self.convs: + xt = F.leaky_relu(x, self.lrelu_slope) + if x_mask is not None: + xt = xt * x_mask + xt = c(xt) + x = xt + x + if x_mask is not None: + x = x * x_mask + return x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + def __prepare_scriptable__(self): + for l in self.convs: + for hook in l._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(l) + return self + + +class Log(nn.Module): + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse: bool = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: + if not reverse: + y = torch.log(torch.clamp_min(x, 1e-5)) * x_mask + logdet = torch.sum(-y, [1, 2]) + return y, logdet + else: + x = torch.exp(x) * x_mask + return x + + +class Flip(nn.Module): + # torch.jit.script() Compiled functions \ + # can't take variable number of arguments or \ + # use keyword-only arguments with defaults + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse: bool = False, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: + x = torch.flip(x, [1]) + if not reverse: + logdet = torch.zeros(x.size(0)).to(dtype=x.dtype, device=x.device) + return x, logdet + else: + return x, torch.zeros([1], device=x.device) + + +class ElementwiseAffine(nn.Module): + def __init__(self, channels): + super(ElementwiseAffine, self).__init__() + self.channels = channels + self.m = nn.Parameter(torch.zeros(channels, 1)) + self.logs = nn.Parameter(torch.zeros(channels, 1)) + + def forward(self, x, x_mask, reverse=False, **kwargs): + if not reverse: + y = self.m + torch.exp(self.logs) * x + y = y * x_mask + logdet = torch.sum(self.logs * x_mask, [1, 2]) + return y, logdet + else: + x = (x - self.m) * torch.exp(-self.logs) * x_mask + return x + + +class ResidualCouplingLayer(nn.Module): + def __init__( + self, + channels, + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + p_dropout=0, + gin_channels=0, + mean_only=False, + ): + assert channels % 2 == 0, "channels should be divisible by 2" + super(ResidualCouplingLayer, self).__init__() + self.channels = channels + self.hidden_channels = hidden_channels + self.kernel_size = kernel_size + self.dilation_rate = dilation_rate + self.n_layers = n_layers + self.half_channels = channels // 2 + self.mean_only = mean_only + + self.pre = nn.Conv1d(self.half_channels, hidden_channels, 1) + self.enc = WN( + hidden_channels, + kernel_size, + dilation_rate, + n_layers, + p_dropout=float(p_dropout), + gin_channels=gin_channels, + ) + self.post = nn.Conv1d(hidden_channels, self.half_channels * (2 - mean_only), 1) + self.post.weight.data.zero_() + self.post.bias.data.zero_() + + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse: bool = False, + ): + x0, x1 = torch.split(x, [self.half_channels] * 2, 1) + h = self.pre(x0) * x_mask + h = self.enc(h, x_mask, g=g) + stats = self.post(h) * x_mask + if not self.mean_only: + m, logs = torch.split(stats, [self.half_channels] * 2, 1) + else: + m = stats + logs = torch.zeros_like(m) + + if not reverse: + x1 = m + x1 * torch.exp(logs) * x_mask + x = torch.cat([x0, x1], 1) + logdet = torch.sum(logs, [1, 2]) + return x, logdet + else: + x1 = (x1 - m) * torch.exp(-logs) * x_mask + x = torch.cat([x0, x1], 1) + return x, torch.zeros([1]) + + def remove_weight_norm(self): + self.enc.remove_weight_norm() + + def __prepare_scriptable__(self): + for hook in self.enc._forward_pre_hooks.values(): + if ( + hook.__module__ == "torch.nn.utils.weight_norm" + and hook.__class__.__name__ == "WeightNorm" + ): + torch.nn.utils.remove_weight_norm(self.enc) + return self + + +class ConvFlow(nn.Module): + def __init__( + self, + in_channels, + filter_channels, + kernel_size, + n_layers, + num_bins=10, + tail_bound=5.0, + ): + super(ConvFlow, self).__init__() + self.in_channels = in_channels + self.filter_channels = filter_channels + self.kernel_size = kernel_size + self.n_layers = n_layers + self.num_bins = num_bins + self.tail_bound = tail_bound + self.half_channels = in_channels // 2 + + self.pre = nn.Conv1d(self.half_channels, filter_channels, 1) + self.convs = DDSConv(filter_channels, kernel_size, n_layers, p_dropout=0.0) + self.proj = nn.Conv1d( + filter_channels, self.half_channels * (num_bins * 3 - 1), 1 + ) + self.proj.weight.data.zero_() + self.proj.bias.data.zero_() + + def forward( + self, + x: torch.Tensor, + x_mask: torch.Tensor, + g: Optional[torch.Tensor] = None, + reverse=False, + ): + x0, x1 = torch.split(x, [self.half_channels] * 2, 1) + h = self.pre(x0) + h = self.convs(h, x_mask, g=g) + h = self.proj(h) * x_mask + + b, c, t = x0.shape + h = h.reshape(b, c, -1, t).permute(0, 1, 3, 2) # [b, cx?, t] -> [b, c, t, ?] + + unnormalized_widths = h[..., : self.num_bins] / math.sqrt(self.filter_channels) + unnormalized_heights = h[..., self.num_bins : 2 * self.num_bins] / math.sqrt( + self.filter_channels + ) + unnormalized_derivatives = h[..., 2 * self.num_bins :] + + x1, logabsdet = piecewise_rational_quadratic_transform( + x1, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=reverse, + tails="linear", + tail_bound=self.tail_bound, + ) + + x = torch.cat([x0, x1], 1) * x_mask + logdet = torch.sum(logabsdet * x_mask, [1, 2]) + if not reverse: + return x, logdet + else: + return x diff --git a/rvc/infer/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py b/rvc/infer/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py new file mode 100644 index 0000000..e69a603 --- /dev/null +++ b/rvc/infer/lib/infer_pack/modules/F0Predictor/DioF0Predictor.py @@ -0,0 +1,91 @@ +import numpy as np +import pyworld + +from infer.lib.infer_pack.modules.F0Predictor.F0Predictor import F0Predictor + + +class DioF0Predictor(F0Predictor): + def __init__(self, hop_length=512, f0_min=50, f0_max=1100, sampling_rate=44100): + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + self.sampling_rate = sampling_rate + + def interpolate_f0(self, f0): + """ + 对F0进行插值处理 + """ + + data = np.reshape(f0, (f0.size, 1)) + + vuv_vector = np.zeros((data.size, 1), dtype=np.float32) + vuv_vector[data > 0.0] = 1.0 + vuv_vector[data <= 0.0] = 0.0 + + ip_data = data + + frame_number = data.size + last_value = 0.0 + for i in range(frame_number): + if data[i] <= 0.0: + j = i + 1 + for j in range(i + 1, frame_number): + if data[j] > 0.0: + break + if j < frame_number - 1: + if last_value > 0.0: + step = (data[j] - data[i - 1]) / float(j - i) + for k in range(i, j): + ip_data[k] = data[i - 1] + step * (k - i + 1) + else: + for k in range(i, j): + ip_data[k] = data[j] + else: + for k in range(i, frame_number): + ip_data[k] = last_value + else: + ip_data[i] = data[i] # 这里可能存在一个没有必要的拷贝 + last_value = data[i] + + return ip_data[:, 0], vuv_vector[:, 0] + + def resize_f0(self, x, target_len): + source = np.array(x) + source[source < 0.001] = np.nan + target = np.interp( + np.arange(0, len(source) * target_len, len(source)) / target_len, + np.arange(0, len(source)), + source, + ) + res = np.nan_to_num(target) + return res + + def compute_f0(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.dio( + wav.astype(np.double), + fs=self.sampling_rate, + f0_floor=self.f0_min, + f0_ceil=self.f0_max, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.sampling_rate) + for index, pitch in enumerate(f0): + f0[index] = round(pitch, 1) + return self.interpolate_f0(self.resize_f0(f0, p_len))[0] + + def compute_f0_uv(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.dio( + wav.astype(np.double), + fs=self.sampling_rate, + f0_floor=self.f0_min, + f0_ceil=self.f0_max, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.sampling_rate) + for index, pitch in enumerate(f0): + f0[index] = round(pitch, 1) + return self.interpolate_f0(self.resize_f0(f0, p_len)) diff --git a/rvc/infer/lib/infer_pack/modules/F0Predictor/F0Predictor.py b/rvc/infer/lib/infer_pack/modules/F0Predictor/F0Predictor.py new file mode 100644 index 0000000..0d81b05 --- /dev/null +++ b/rvc/infer/lib/infer_pack/modules/F0Predictor/F0Predictor.py @@ -0,0 +1,16 @@ +class F0Predictor(object): + def compute_f0(self, wav, p_len): + """ + input: wav:[signal_length] + p_len:int + output: f0:[signal_length//hop_length] + """ + pass + + def compute_f0_uv(self, wav, p_len): + """ + input: wav:[signal_length] + p_len:int + output: f0:[signal_length//hop_length],uv:[signal_length//hop_length] + """ + pass diff --git a/rvc/infer/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py b/rvc/infer/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py new file mode 100644 index 0000000..2b13917 --- /dev/null +++ b/rvc/infer/lib/infer_pack/modules/F0Predictor/HarvestF0Predictor.py @@ -0,0 +1,87 @@ +import numpy as np +import pyworld + +from infer.lib.infer_pack.modules.F0Predictor.F0Predictor import F0Predictor + + +class HarvestF0Predictor(F0Predictor): + def __init__(self, hop_length=512, f0_min=50, f0_max=1100, sampling_rate=44100): + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + self.sampling_rate = sampling_rate + + def interpolate_f0(self, f0): + """ + 对F0进行插值处理 + """ + + data = np.reshape(f0, (f0.size, 1)) + + vuv_vector = np.zeros((data.size, 1), dtype=np.float32) + vuv_vector[data > 0.0] = 1.0 + vuv_vector[data <= 0.0] = 0.0 + + ip_data = data + + frame_number = data.size + last_value = 0.0 + for i in range(frame_number): + if data[i] <= 0.0: + j = i + 1 + for j in range(i + 1, frame_number): + if data[j] > 0.0: + break + if j < frame_number - 1: + if last_value > 0.0: + step = (data[j] - data[i - 1]) / float(j - i) + for k in range(i, j): + ip_data[k] = data[i - 1] + step * (k - i + 1) + else: + for k in range(i, j): + ip_data[k] = data[j] + else: + for k in range(i, frame_number): + ip_data[k] = last_value + else: + ip_data[i] = data[i] # 这里可能存在一个没有必要的拷贝 + last_value = data[i] + + return ip_data[:, 0], vuv_vector[:, 0] + + def resize_f0(self, x, target_len): + source = np.array(x) + source[source < 0.001] = np.nan + target = np.interp( + np.arange(0, len(source) * target_len, len(source)) / target_len, + np.arange(0, len(source)), + source, + ) + res = np.nan_to_num(target) + return res + + def compute_f0(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.harvest( + wav.astype(np.double), + fs=self.sampling_rate, + f0_ceil=self.f0_max, + f0_floor=self.f0_min, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.fs) + return self.interpolate_f0(self.resize_f0(f0, p_len))[0] + + def compute_f0_uv(self, wav, p_len=None): + if p_len is None: + p_len = wav.shape[0] // self.hop_length + f0, t = pyworld.harvest( + wav.astype(np.double), + fs=self.sampling_rate, + f0_floor=self.f0_min, + f0_ceil=self.f0_max, + frame_period=1000 * self.hop_length / self.sampling_rate, + ) + f0 = pyworld.stonemask(wav.astype(np.double), f0, t, self.sampling_rate) + return self.interpolate_f0(self.resize_f0(f0, p_len)) diff --git a/rvc/infer/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py b/rvc/infer/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py new file mode 100644 index 0000000..957ec46 --- /dev/null +++ b/rvc/infer/lib/infer_pack/modules/F0Predictor/PMF0Predictor.py @@ -0,0 +1,98 @@ +import numpy as np +import parselmouth + +from infer.lib.infer_pack.modules.F0Predictor.F0Predictor import F0Predictor + + +class PMF0Predictor(F0Predictor): + def __init__(self, hop_length=512, f0_min=50, f0_max=1100, sampling_rate=44100): + self.hop_length = hop_length + self.f0_min = f0_min + self.f0_max = f0_max + self.sampling_rate = sampling_rate + + def interpolate_f0(self, f0): + """ + 对F0进行插值处理 + """ + + data = np.reshape(f0, (f0.size, 1)) + + vuv_vector = np.zeros((data.size, 1), dtype=np.float32) + vuv_vector[data > 0.0] = 1.0 + vuv_vector[data <= 0.0] = 0.0 + + ip_data = data + + frame_number = data.size + last_value = 0.0 + for i in range(frame_number): + if data[i] <= 0.0: + j = i + 1 + for j in range(i + 1, frame_number): + if data[j] > 0.0: + break + if j < frame_number - 1: + if last_value > 0.0: + step = (data[j] - data[i - 1]) / float(j - i) + for k in range(i, j): + ip_data[k] = data[i - 1] + step * (k - i + 1) + else: + for k in range(i, j): + ip_data[k] = data[j] + else: + for k in range(i, frame_number): + ip_data[k] = last_value + else: + ip_data[i] = data[i] # 这里可能存在一个没有必要的拷贝 + last_value = data[i] + + return ip_data[:, 0], vuv_vector[:, 0] + + def compute_f0(self, wav, p_len=None): + x = wav + if p_len is None: + p_len = x.shape[0] // self.hop_length + else: + assert abs(p_len - x.shape[0] // self.hop_length) < 4, "pad length error" + time_step = self.hop_length / self.sampling_rate * 1000 + f0 = ( + parselmouth.Sound(x, self.sampling_rate) + .to_pitch_ac( + time_step=time_step / 1000, + voicing_threshold=0.6, + pitch_floor=self.f0_min, + pitch_ceiling=self.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") + f0, uv = self.interpolate_f0(f0) + return f0 + + def compute_f0_uv(self, wav, p_len=None): + x = wav + if p_len is None: + p_len = x.shape[0] // self.hop_length + else: + assert abs(p_len - x.shape[0] // self.hop_length) < 4, "pad length error" + time_step = self.hop_length / self.sampling_rate * 1000 + f0 = ( + parselmouth.Sound(x, self.sampling_rate) + .to_pitch_ac( + time_step=time_step / 1000, + voicing_threshold=0.6, + pitch_floor=self.f0_min, + pitch_ceiling=self.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") + f0, uv = self.interpolate_f0(f0) + return f0, uv diff --git a/rvc/infer/lib/infer_pack/modules/F0Predictor/__init__.py b/rvc/infer/lib/infer_pack/modules/F0Predictor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rvc/infer/lib/infer_pack/onnx_inference.py b/rvc/infer/lib/infer_pack/onnx_inference.py new file mode 100644 index 0000000..3d8328b --- /dev/null +++ b/rvc/infer/lib/infer_pack/onnx_inference.py @@ -0,0 +1,149 @@ +import librosa +import numpy as np +import onnxruntime +import soundfile + +import logging + +logger = logging.getLogger(__name__) + + +class ContentVec: + def __init__(self, vec_path="pretrained/vec-768-layer-12.onnx", device=None): + logger.info("Load model(s) from {}".format(vec_path)) + if device == "cpu" or device is None: + providers = ["CPUExecutionProvider"] + elif device == "cuda": + providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] + elif device == "dml": + providers = ["DmlExecutionProvider"] + else: + raise RuntimeError("Unsportted Device") + self.model = onnxruntime.InferenceSession(vec_path, providers=providers) + + def __call__(self, wav): + return self.forward(wav) + + def forward(self, wav): + feats = wav + if feats.ndim == 2: # double channels + feats = feats.mean(-1) + assert feats.ndim == 1, feats.ndim + feats = np.expand_dims(np.expand_dims(feats, 0), 0) + onnx_input = {self.model.get_inputs()[0].name: feats} + logits = self.model.run(None, onnx_input)[0] + return logits.transpose(0, 2, 1) + + +def get_f0_predictor(f0_predictor, hop_length, sampling_rate, **kargs): + if f0_predictor == "pm": + from lib.infer_pack.modules.F0Predictor.PMF0Predictor import PMF0Predictor + + f0_predictor_object = PMF0Predictor( + hop_length=hop_length, sampling_rate=sampling_rate + ) + elif f0_predictor == "harvest": + from lib.infer_pack.modules.F0Predictor.HarvestF0Predictor import ( + HarvestF0Predictor, + ) + + f0_predictor_object = HarvestF0Predictor( + hop_length=hop_length, sampling_rate=sampling_rate + ) + elif f0_predictor == "dio": + from lib.infer_pack.modules.F0Predictor.DioF0Predictor import DioF0Predictor + + f0_predictor_object = DioF0Predictor( + hop_length=hop_length, sampling_rate=sampling_rate + ) + else: + raise Exception("Unknown f0 predictor") + return f0_predictor_object + + +class OnnxRVC: + def __init__( + self, + model_path, + sr=40000, + hop_size=512, + vec_path="vec-768-layer-12", + device="cpu", + ): + vec_path = f"pretrained/{vec_path}.onnx" + self.vec_model = ContentVec(vec_path, device) + if device == "cpu" or device is None: + providers = ["CPUExecutionProvider"] + elif device == "cuda": + providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] + elif device == "dml": + providers = ["DmlExecutionProvider"] + else: + raise RuntimeError("Unsportted Device") + self.model = onnxruntime.InferenceSession(model_path, providers=providers) + self.sampling_rate = sr + self.hop_size = hop_size + + def forward(self, hubert, hubert_length, pitch, pitchf, ds, rnd): + onnx_input = { + self.model.get_inputs()[0].name: hubert, + self.model.get_inputs()[1].name: hubert_length, + self.model.get_inputs()[2].name: pitch, + self.model.get_inputs()[3].name: pitchf, + self.model.get_inputs()[4].name: ds, + self.model.get_inputs()[5].name: rnd, + } + return (self.model.run(None, onnx_input)[0] * 32767).astype(np.int16) + + def inference( + self, + raw_path, + sid, + f0_method="dio", + f0_up_key=0, + pad_time=0.5, + cr_threshold=0.02, + ): + f0_min = 50 + f0_max = 1100 + f0_mel_min = 1127 * np.log(1 + f0_min / 700) + f0_mel_max = 1127 * np.log(1 + f0_max / 700) + f0_predictor = get_f0_predictor( + f0_method, + hop_length=self.hop_size, + sampling_rate=self.sampling_rate, + threshold=cr_threshold, + ) + wav, sr = librosa.load(raw_path, sr=self.sampling_rate) + org_length = len(wav) + if org_length / sr > 50.0: + raise RuntimeError("Reached Max Length") + + wav16k = librosa.resample(wav, orig_sr=self.sampling_rate, target_sr=16000) + wav16k = wav16k + + hubert = self.vec_model(wav16k) + hubert = np.repeat(hubert, 2, axis=2).transpose(0, 2, 1).astype(np.float32) + hubert_length = hubert.shape[1] + + pitchf = f0_predictor.compute_f0(wav, hubert_length) + pitchf = pitchf * 2 ** (f0_up_key / 12) + pitch = pitchf.copy() + f0_mel = 1127 * np.log(1 + pitch / 700) + f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - f0_mel_min) * 254 / ( + f0_mel_max - f0_mel_min + ) + 1 + f0_mel[f0_mel <= 1] = 1 + f0_mel[f0_mel > 255] = 255 + pitch = np.rint(f0_mel).astype(np.int64) + + pitchf = pitchf.reshape(1, len(pitchf)).astype(np.float32) + pitch = pitch.reshape(1, len(pitch)) + ds = np.array([sid]).astype(np.int64) + + rnd = np.random.randn(1, 192, hubert_length).astype(np.float32) + hubert_length = np.array([hubert_length]).astype(np.int64) + + out_wav = self.forward(hubert, hubert_length, pitch, pitchf, ds, rnd).squeeze() + out_wav = np.pad(out_wav, (0, 2 * self.hop_size), "constant") + return out_wav[0:org_length] diff --git a/rvc/infer/lib/infer_pack/transforms.py b/rvc/infer/lib/infer_pack/transforms.py new file mode 100644 index 0000000..6d07b3b --- /dev/null +++ b/rvc/infer/lib/infer_pack/transforms.py @@ -0,0 +1,207 @@ +import numpy as np +import torch +from torch.nn import functional as F + +DEFAULT_MIN_BIN_WIDTH = 1e-3 +DEFAULT_MIN_BIN_HEIGHT = 1e-3 +DEFAULT_MIN_DERIVATIVE = 1e-3 + + +def piecewise_rational_quadratic_transform( + inputs, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=False, + tails=None, + tail_bound=1.0, + min_bin_width=DEFAULT_MIN_BIN_WIDTH, + min_bin_height=DEFAULT_MIN_BIN_HEIGHT, + min_derivative=DEFAULT_MIN_DERIVATIVE, +): + if tails is None: + spline_fn = rational_quadratic_spline + spline_kwargs = {} + else: + spline_fn = unconstrained_rational_quadratic_spline + spline_kwargs = {"tails": tails, "tail_bound": tail_bound} + + outputs, logabsdet = spline_fn( + inputs=inputs, + unnormalized_widths=unnormalized_widths, + unnormalized_heights=unnormalized_heights, + unnormalized_derivatives=unnormalized_derivatives, + inverse=inverse, + min_bin_width=min_bin_width, + min_bin_height=min_bin_height, + min_derivative=min_derivative, + **spline_kwargs + ) + return outputs, logabsdet + + +def searchsorted(bin_locations, inputs, eps=1e-6): + bin_locations[..., -1] += eps + return torch.sum(inputs[..., None] >= bin_locations, dim=-1) - 1 + + +def unconstrained_rational_quadratic_spline( + inputs, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=False, + tails="linear", + tail_bound=1.0, + min_bin_width=DEFAULT_MIN_BIN_WIDTH, + min_bin_height=DEFAULT_MIN_BIN_HEIGHT, + min_derivative=DEFAULT_MIN_DERIVATIVE, +): + inside_interval_mask = (inputs >= -tail_bound) & (inputs <= tail_bound) + outside_interval_mask = ~inside_interval_mask + + outputs = torch.zeros_like(inputs) + logabsdet = torch.zeros_like(inputs) + + if tails == "linear": + unnormalized_derivatives = F.pad(unnormalized_derivatives, pad=(1, 1)) + constant = np.log(np.exp(1 - min_derivative) - 1) + unnormalized_derivatives[..., 0] = constant + unnormalized_derivatives[..., -1] = constant + + outputs[outside_interval_mask] = inputs[outside_interval_mask] + logabsdet[outside_interval_mask] = 0 + else: + raise RuntimeError("{} tails are not implemented.".format(tails)) + + ( + outputs[inside_interval_mask], + logabsdet[inside_interval_mask], + ) = rational_quadratic_spline( + inputs=inputs[inside_interval_mask], + unnormalized_widths=unnormalized_widths[inside_interval_mask, :], + unnormalized_heights=unnormalized_heights[inside_interval_mask, :], + unnormalized_derivatives=unnormalized_derivatives[inside_interval_mask, :], + inverse=inverse, + left=-tail_bound, + right=tail_bound, + bottom=-tail_bound, + top=tail_bound, + min_bin_width=min_bin_width, + min_bin_height=min_bin_height, + min_derivative=min_derivative, + ) + + return outputs, logabsdet + + +def rational_quadratic_spline( + inputs, + unnormalized_widths, + unnormalized_heights, + unnormalized_derivatives, + inverse=False, + left=0.0, + right=1.0, + bottom=0.0, + top=1.0, + min_bin_width=DEFAULT_MIN_BIN_WIDTH, + min_bin_height=DEFAULT_MIN_BIN_HEIGHT, + min_derivative=DEFAULT_MIN_DERIVATIVE, +): + if torch.min(inputs) < left or torch.max(inputs) > right: + raise ValueError("Input to a transform is not within its domain") + + num_bins = unnormalized_widths.shape[-1] + + if min_bin_width * num_bins > 1.0: + raise ValueError("Minimal bin width too large for the number of bins") + if min_bin_height * num_bins > 1.0: + raise ValueError("Minimal bin height too large for the number of bins") + + widths = F.softmax(unnormalized_widths, dim=-1) + widths = min_bin_width + (1 - min_bin_width * num_bins) * widths + cumwidths = torch.cumsum(widths, dim=-1) + cumwidths = F.pad(cumwidths, pad=(1, 0), mode="constant", value=0.0) + cumwidths = (right - left) * cumwidths + left + cumwidths[..., 0] = left + cumwidths[..., -1] = right + widths = cumwidths[..., 1:] - cumwidths[..., :-1] + + derivatives = min_derivative + F.softplus(unnormalized_derivatives) + + heights = F.softmax(unnormalized_heights, dim=-1) + heights = min_bin_height + (1 - min_bin_height * num_bins) * heights + cumheights = torch.cumsum(heights, dim=-1) + cumheights = F.pad(cumheights, pad=(1, 0), mode="constant", value=0.0) + cumheights = (top - bottom) * cumheights + bottom + cumheights[..., 0] = bottom + cumheights[..., -1] = top + heights = cumheights[..., 1:] - cumheights[..., :-1] + + if inverse: + bin_idx = searchsorted(cumheights, inputs)[..., None] + else: + bin_idx = searchsorted(cumwidths, inputs)[..., None] + + input_cumwidths = cumwidths.gather(-1, bin_idx)[..., 0] + input_bin_widths = widths.gather(-1, bin_idx)[..., 0] + + input_cumheights = cumheights.gather(-1, bin_idx)[..., 0] + delta = heights / widths + input_delta = delta.gather(-1, bin_idx)[..., 0] + + input_derivatives = derivatives.gather(-1, bin_idx)[..., 0] + input_derivatives_plus_one = derivatives[..., 1:].gather(-1, bin_idx)[..., 0] + + input_heights = heights.gather(-1, bin_idx)[..., 0] + + if inverse: + a = (inputs - input_cumheights) * ( + input_derivatives + input_derivatives_plus_one - 2 * input_delta + ) + input_heights * (input_delta - input_derivatives) + b = input_heights * input_derivatives - (inputs - input_cumheights) * ( + input_derivatives + input_derivatives_plus_one - 2 * input_delta + ) + c = -input_delta * (inputs - input_cumheights) + + discriminant = b.pow(2) - 4 * a * c + assert (discriminant >= 0).all() + + root = (2 * c) / (-b - torch.sqrt(discriminant)) + outputs = root * input_bin_widths + input_cumwidths + + theta_one_minus_theta = root * (1 - root) + denominator = input_delta + ( + (input_derivatives + input_derivatives_plus_one - 2 * input_delta) + * theta_one_minus_theta + ) + derivative_numerator = input_delta.pow(2) * ( + input_derivatives_plus_one * root.pow(2) + + 2 * input_delta * theta_one_minus_theta + + input_derivatives * (1 - root).pow(2) + ) + logabsdet = torch.log(derivative_numerator) - 2 * torch.log(denominator) + + return outputs, -logabsdet + else: + theta = (inputs - input_cumwidths) / input_bin_widths + theta_one_minus_theta = theta * (1 - theta) + + numerator = input_heights * ( + input_delta * theta.pow(2) + input_derivatives * theta_one_minus_theta + ) + denominator = input_delta + ( + (input_derivatives + input_derivatives_plus_one - 2 * input_delta) + * theta_one_minus_theta + ) + outputs = input_cumheights + numerator / denominator + + derivative_numerator = input_delta.pow(2) * ( + input_derivatives_plus_one * theta.pow(2) + + 2 * input_delta * theta_one_minus_theta + + input_derivatives * (1 - theta).pow(2) + ) + logabsdet = torch.log(derivative_numerator) - 2 * torch.log(denominator) + + return outputs, logabsdet diff --git a/rvc/infer/lib/jit/__init__.py b/rvc/infer/lib/jit/__init__.py new file mode 100644 index 0000000..d7f41dd --- /dev/null +++ b/rvc/infer/lib/jit/__init__.py @@ -0,0 +1,163 @@ +from io import BytesIO +import pickle +import time +import torch +from tqdm import tqdm +from collections import OrderedDict + + +def load_inputs(path, device, is_half=False): + parm = torch.load(path, map_location=torch.device("cpu")) + for key in parm.keys(): + parm[key] = parm[key].to(device) + if is_half and parm[key].dtype == torch.float32: + parm[key] = parm[key].half() + elif not is_half and parm[key].dtype == torch.float16: + parm[key] = parm[key].float() + return parm + + +def benchmark( + model, inputs_path, device=torch.device("cpu"), epoch=1000, is_half=False +): + parm = load_inputs(inputs_path, device, is_half) + total_ts = 0.0 + bar = tqdm(range(epoch)) + for i in bar: + start_time = time.perf_counter() + o = model(**parm) + total_ts += time.perf_counter() - start_time + print(f"num_epoch: {epoch} | avg time(ms): {(total_ts*1000)/epoch}") + + +def jit_warm_up(model, inputs_path, device=torch.device("cpu"), epoch=5, is_half=False): + benchmark(model, inputs_path, device, epoch=epoch, is_half=is_half) + + +def to_jit_model( + model_path, + model_type: str, + mode: str = "trace", + inputs_path: str = None, + device=torch.device("cpu"), + is_half=False, +): + model = None + if model_type.lower() == "synthesizer": + from .get_synthesizer import get_synthesizer + + model, _ = get_synthesizer(model_path, device) + model.forward = model.infer + elif model_type.lower() == "rmvpe": + from .get_rmvpe import get_rmvpe + + model = get_rmvpe(model_path, device) + elif model_type.lower() == "hubert": + from .get_hubert import get_hubert_model + + model = get_hubert_model(model_path, device) + model.forward = model.infer + else: + raise ValueError(f"No model type named {model_type}") + model = model.eval() + model = model.half() if is_half else model.float() + if mode == "trace": + assert not inputs_path + inputs = load_inputs(inputs_path, device, is_half) + model_jit = torch.jit.trace(model, example_kwarg_inputs=inputs) + elif mode == "script": + model_jit = torch.jit.script(model) + model_jit.to(device) + model_jit = model_jit.half() if is_half else model_jit.float() + # model = model.half() if is_half else model.float() + return (model, model_jit) + + +def export( + model: torch.nn.Module, + mode: str = "trace", + inputs: dict = None, + device=torch.device("cpu"), + is_half: bool = False, +) -> dict: + model = model.half() if is_half else model.float() + model.eval() + if mode == "trace": + assert inputs is not None + model_jit = torch.jit.trace(model, example_kwarg_inputs=inputs) + elif mode == "script": + model_jit = torch.jit.script(model) + model_jit.to(device) + model_jit = model_jit.half() if is_half else model_jit.float() + buffer = BytesIO() + # model_jit=model_jit.cpu() + torch.jit.save(model_jit, buffer) + del model_jit + cpt = OrderedDict() + cpt["model"] = buffer.getvalue() + cpt["is_half"] = is_half + return cpt + + +def load(path: str): + with open(path, "rb") as f: + return pickle.load(f) + + +def save(ckpt: dict, save_path: str): + with open(save_path, "wb") as f: + pickle.dump(ckpt, f) + + +def rmvpe_jit_export( + model_path: str, + mode: str = "script", + inputs_path: str = None, + save_path: str = None, + device=torch.device("cpu"), + is_half=False, +): + if not save_path: + save_path = model_path.rstrip(".pth") + save_path += ".half.jit" if is_half else ".jit" + if "cuda" in str(device) and ":" not in str(device): + device = torch.device("cuda:0") + from .get_rmvpe import get_rmvpe + + model = get_rmvpe(model_path, device) + inputs = None + if mode == "trace": + inputs = load_inputs(inputs_path, device, is_half) + ckpt = export(model, mode, inputs, device, is_half) + ckpt["device"] = str(device) + save(ckpt, save_path) + return ckpt + + +def synthesizer_jit_export( + model_path: str, + mode: str = "script", + inputs_path: str = None, + save_path: str = None, + device=torch.device("cpu"), + is_half=False, +): + if not save_path: + save_path = model_path.rstrip(".pth") + save_path += ".half.jit" if is_half else ".jit" + if "cuda" in str(device) and ":" not in str(device): + device = torch.device("cuda:0") + from .get_synthesizer import get_synthesizer + + model, cpt = get_synthesizer(model_path, device) + assert isinstance(cpt, dict) + model.forward = model.infer + inputs = None + if mode == "trace": + inputs = load_inputs(inputs_path, device, is_half) + ckpt = export(model, mode, inputs, device, is_half) + cpt.pop("weight") + cpt["model"] = ckpt["model"] + cpt["device"] = device + save(cpt, save_path) + return cpt diff --git a/rvc/infer/lib/jit/__pycache__/__init__.cpython-310.pyc b/rvc/infer/lib/jit/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b17c2eeda29618176ee621ac8f524b22f647bc6 GIT binary patch literal 4155 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 literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/jit/get_hubert.py b/rvc/infer/lib/jit/get_hubert.py new file mode 100644 index 0000000..aec7132 --- /dev/null +++ b/rvc/infer/lib/jit/get_hubert.py @@ -0,0 +1,342 @@ +import math +import random +from typing import Optional, Tuple +from fairseq.checkpoint_utils import load_model_ensemble_and_task +import numpy as np +import torch +import torch.nn.functional as F + +# from fairseq.data.data_utils import compute_mask_indices +from fairseq.utils import index_put + + +# @torch.jit.script +def pad_to_multiple(x, multiple, dim=-1, value=0): + # Inspired from https://github.com/lucidrains/local-attention/blob/master/local_attention/local_attention.py#L41 + if x is None: + return None, 0 + tsz = x.size(dim) + m = tsz / multiple + remainder = math.ceil(m) * multiple - tsz + if int(tsz % multiple) == 0: + return x, 0 + pad_offset = (0,) * (-1 - dim) * 2 + + return F.pad(x, (*pad_offset, 0, remainder), value=value), remainder + + +def extract_features( + self, + x, + padding_mask=None, + tgt_layer=None, + min_layer=0, +): + if padding_mask is not None: + x = index_put(x, padding_mask, 0) + + x_conv = self.pos_conv(x.transpose(1, 2)) + x_conv = x_conv.transpose(1, 2) + x = x + x_conv + + if not self.layer_norm_first: + x = self.layer_norm(x) + + # pad to the sequence length dimension + x, pad_length = pad_to_multiple(x, self.required_seq_len_multiple, dim=-2, value=0) + if pad_length > 0 and padding_mask is None: + padding_mask = x.new_zeros((x.size(0), x.size(1)), dtype=torch.bool) + padding_mask[:, -pad_length:] = True + else: + padding_mask, _ = pad_to_multiple( + padding_mask, self.required_seq_len_multiple, dim=-1, value=True + ) + x = F.dropout(x, p=self.dropout, training=self.training) + + # B x T x C -> T x B x C + x = x.transpose(0, 1) + + layer_results = [] + r = None + for i, layer in enumerate(self.layers): + dropout_probability = np.random.random() if self.layerdrop > 0 else 1 + if not self.training or (dropout_probability > self.layerdrop): + x, (z, lr) = layer( + x, self_attn_padding_mask=padding_mask, need_weights=False + ) + if i >= min_layer: + layer_results.append((x, z, lr)) + if i == tgt_layer: + r = x + break + + if r is not None: + x = r + + # T x B x C -> B x T x C + x = x.transpose(0, 1) + + # undo paddding + if pad_length > 0: + x = x[:, :-pad_length] + + def undo_pad(a, b, c): + return ( + a[:-pad_length], + b[:-pad_length] if b is not None else b, + c[:-pad_length], + ) + + layer_results = [undo_pad(*u) for u in layer_results] + + return x, layer_results + + +def compute_mask_indices( + shape: Tuple[int, int], + padding_mask: Optional[torch.Tensor], + mask_prob: float, + mask_length: int, + mask_type: str = "static", + mask_other: float = 0.0, + min_masks: int = 0, + no_overlap: bool = False, + min_space: int = 0, + require_same_masks: bool = True, + mask_dropout: float = 0.0, +) -> torch.Tensor: + """ + Computes random mask spans for a given shape + + Args: + shape: the the shape for which to compute masks. + should be of size 2 where first element is batch size and 2nd is timesteps + padding_mask: optional padding mask of the same size as shape, which will prevent masking padded elements + mask_prob: probability for each token to be chosen as start of the span to be masked. this will be multiplied by + number of timesteps divided by length of mask span to mask approximately this percentage of all elements. + however due to overlaps, the actual number will be smaller (unless no_overlap is True) + mask_type: how to compute mask lengths + static = fixed size + uniform = sample from uniform distribution [mask_other, mask_length*2] + normal = sample from normal distribution with mean mask_length and stdev mask_other. mask is min 1 element + poisson = sample from possion distribution with lambda = mask length + min_masks: minimum number of masked spans + no_overlap: if false, will switch to an alternative recursive algorithm that prevents spans from overlapping + min_space: only used if no_overlap is True, this is how many elements to keep unmasked between spans + require_same_masks: if true, will randomly drop out masks until same amount of masks remains in each sample + mask_dropout: randomly dropout this percentage of masks in each example + """ + + bsz, all_sz = shape + mask = torch.full((bsz, all_sz), False) + + all_num_mask = int( + # add a random number for probabilistic rounding + mask_prob * all_sz / float(mask_length) + + torch.rand([1]).item() + ) + + all_num_mask = max(min_masks, all_num_mask) + + mask_idcs = [] + for i in range(bsz): + if padding_mask is not None: + sz = all_sz - padding_mask[i].long().sum().item() + num_mask = int(mask_prob * sz / float(mask_length) + np.random.rand()) + num_mask = max(min_masks, num_mask) + else: + sz = all_sz + num_mask = all_num_mask + + if mask_type == "static": + lengths = torch.full([num_mask], mask_length) + elif mask_type == "uniform": + lengths = torch.randint(mask_other, mask_length * 2 + 1, size=[num_mask]) + elif mask_type == "normal": + lengths = torch.normal(mask_length, mask_other, size=[num_mask]) + lengths = [max(1, int(round(x))) for x in lengths] + else: + raise Exception("unknown mask selection " + mask_type) + + if sum(lengths) == 0: + lengths[0] = min(mask_length, sz - 1) + + if no_overlap: + mask_idc = [] + + def arrange(s, e, length, keep_length): + span_start = torch.randint(low=s, high=e - length, size=[1]).item() + mask_idc.extend(span_start + i for i in range(length)) + + new_parts = [] + if span_start - s - min_space >= keep_length: + new_parts.append((s, span_start - min_space + 1)) + if e - span_start - length - min_space > keep_length: + new_parts.append((span_start + length + min_space, e)) + return new_parts + + parts = [(0, sz)] + min_length = min(lengths) + for length in sorted(lengths, reverse=True): + t = [e - s if e - s >= length + min_space else 0 for s, e in parts] + lens = torch.asarray(t, dtype=torch.int) + l_sum = torch.sum(lens) + if l_sum == 0: + break + probs = lens / torch.sum(lens) + c = torch.multinomial(probs.float(), len(parts)).item() + s, e = parts.pop(c) + parts.extend(arrange(s, e, length, min_length)) + mask_idc = torch.asarray(mask_idc) + else: + min_len = min(lengths) + if sz - min_len <= num_mask: + min_len = sz - num_mask - 1 + mask_idc = torch.asarray( + random.sample([i for i in range(sz - min_len)], num_mask) + ) + mask_idc = torch.asarray( + [ + mask_idc[j] + offset + for j in range(len(mask_idc)) + for offset in range(lengths[j]) + ] + ) + + mask_idcs.append(torch.unique(mask_idc[mask_idc < sz])) + + min_len = min([len(m) for m in mask_idcs]) + for i, mask_idc in enumerate(mask_idcs): + if isinstance(mask_idc, torch.Tensor): + mask_idc = torch.asarray(mask_idc, dtype=torch.float) + if len(mask_idc) > min_len and require_same_masks: + mask_idc = torch.asarray( + random.sample([i for i in range(mask_idc)], min_len) + ) + if mask_dropout > 0: + num_holes = int(round(len(mask_idc) * mask_dropout)) + mask_idc = torch.asarray( + random.sample([i for i in range(mask_idc)], len(mask_idc) - num_holes) + ) + + mask[i, mask_idc.int()] = True + + return mask + + +def apply_mask(self, x, padding_mask, target_list): + B, T, C = x.shape + torch.zeros_like(x) + if self.mask_prob > 0: + mask_indices = compute_mask_indices( + (B, T), + padding_mask, + self.mask_prob, + self.mask_length, + self.mask_selection, + self.mask_other, + min_masks=2, + no_overlap=self.no_mask_overlap, + min_space=self.mask_min_space, + ) + mask_indices = mask_indices.to(x.device) + x[mask_indices] = self.mask_emb + else: + mask_indices = None + + if self.mask_channel_prob > 0: + mask_channel_indices = compute_mask_indices( + (B, C), + None, + self.mask_channel_prob, + self.mask_channel_length, + self.mask_channel_selection, + self.mask_channel_other, + no_overlap=self.no_mask_channel_overlap, + min_space=self.mask_channel_min_space, + ) + mask_channel_indices = ( + mask_channel_indices.to(x.device).unsqueeze(1).expand(-1, T, -1) + ) + x[mask_channel_indices] = 0 + + return x, mask_indices + + +def get_hubert_model( + model_path="assets/hubert/hubert_base.pt", device=torch.device("cpu") +): + models, _, _ = load_model_ensemble_and_task( + [model_path], + suffix="", + ) + hubert_model = models[0] + hubert_model = hubert_model.to(device) + + def _apply_mask(x, padding_mask, target_list): + return apply_mask(hubert_model, x, padding_mask, target_list) + + hubert_model.apply_mask = _apply_mask + + def _extract_features( + x, + padding_mask=None, + tgt_layer=None, + min_layer=0, + ): + return extract_features( + hubert_model.encoder, + x, + padding_mask=padding_mask, + tgt_layer=tgt_layer, + min_layer=min_layer, + ) + + hubert_model.encoder.extract_features = _extract_features + + hubert_model._forward = hubert_model.forward + + def hubert_extract_features( + self, + source: torch.Tensor, + padding_mask: Optional[torch.Tensor] = None, + mask: bool = False, + ret_conv: bool = False, + output_layer: Optional[int] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + res = self._forward( + source, + padding_mask=padding_mask, + mask=mask, + features_only=True, + output_layer=output_layer, + ) + feature = res["features"] if ret_conv else res["x"] + return feature, res["padding_mask"] + + def _hubert_extract_features( + source: torch.Tensor, + padding_mask: Optional[torch.Tensor] = None, + mask: bool = False, + ret_conv: bool = False, + output_layer: Optional[int] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + return hubert_extract_features( + hubert_model, source, padding_mask, mask, ret_conv, output_layer + ) + + hubert_model.extract_features = _hubert_extract_features + + def infer(source, padding_mask, output_layer: torch.Tensor): + output_layer = output_layer.item() + logits = hubert_model.extract_features( + source=source, padding_mask=padding_mask, output_layer=output_layer + ) + feats = hubert_model.final_proj(logits[0]) if output_layer == 9 else logits[0] + return feats + + hubert_model.infer = infer + # hubert_model.forward=infer + # hubert_model.forward + + return hubert_model diff --git a/rvc/infer/lib/jit/get_rmvpe.py b/rvc/infer/lib/jit/get_rmvpe.py new file mode 100644 index 0000000..e71c39f --- /dev/null +++ b/rvc/infer/lib/jit/get_rmvpe.py @@ -0,0 +1,12 @@ +import torch + + +def get_rmvpe(model_path="assets/rmvpe/rmvpe.pt", device=torch.device("cpu")): + from infer.lib.rmvpe import E2E + + model = E2E(4, 1, (2, 2)) + ckpt = torch.load(model_path, map_location=device) + model.load_state_dict(ckpt) + model.eval() + model = model.to(device) + return model diff --git a/rvc/infer/lib/jit/get_synthesizer.py b/rvc/infer/lib/jit/get_synthesizer.py new file mode 100644 index 0000000..b8db4fa --- /dev/null +++ b/rvc/infer/lib/jit/get_synthesizer.py @@ -0,0 +1,38 @@ +import torch + + +def get_synthesizer(pth_path, device=torch.device("cpu")): + from infer.lib.infer_pack.models import ( + SynthesizerTrnMs256NSFsid, + SynthesizerTrnMs256NSFsid_nono, + SynthesizerTrnMs768NSFsid, + SynthesizerTrnMs768NSFsid_nono, + ) + + cpt = torch.load(pth_path, map_location=torch.device("cpu")) + # tgt_sr = cpt["config"][-1] + cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0] + if_f0 = cpt.get("f0", 1) + version = cpt.get("version", "v1") + if version == "v1": + if if_f0 == 1: + net_g = SynthesizerTrnMs256NSFsid(*cpt["config"], is_half=False) + else: + net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"]) + elif version == "v2": + if if_f0 == 1: + net_g = SynthesizerTrnMs768NSFsid(*cpt["config"], is_half=False) + else: + net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"]) + del net_g.enc_q + # net_g.forward = net_g.infer + # ckpt = {} + # ckpt["config"] = cpt["config"] + # ckpt["f0"] = if_f0 + # ckpt["version"] = version + # ckpt["info"] = cpt.get("info", "0epoch") + net_g.load_state_dict(cpt["weight"], strict=False) + net_g = net_g.float() + net_g.eval().to(device) + net_g.remove_weight_norm() + return net_g, cpt diff --git a/rvc/infer/lib/rmvpe.py b/rvc/infer/lib/rmvpe.py new file mode 100644 index 0000000..86c6899 --- /dev/null +++ b/rvc/infer/lib/rmvpe.py @@ -0,0 +1,670 @@ +from io import BytesIO +import os +from typing import List, Optional, Tuple +import numpy as np +import torch + +from infer.lib import jit + +try: + # Fix "Torch not compiled with CUDA enabled" + import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import + + if torch.xpu.is_available(): + from infer.modules.ipex import ipex_init + + ipex_init() +except Exception: # pylint: disable=broad-exception-caught + pass +import torch.nn as nn +import torch.nn.functional as F +from librosa.util import normalize, pad_center, tiny +from scipy.signal import get_window + +import logging + +logger = logging.getLogger(__name__) + + +class STFT(torch.nn.Module): + def __init__( + self, filter_length=1024, hop_length=512, win_length=None, window="hann" + ): + """ + This module implements an STFT using 1D convolution and 1D transpose convolutions. + This is a bit tricky so there are some cases that probably won't work as working + out the same sizes before and after in all overlap add setups is tough. Right now, + this code should work with hop lengths that are half the filter length (50% overlap + between frames). + + Keyword Arguments: + filter_length {int} -- Length of filters used (default: {1024}) + hop_length {int} -- Hop length of STFT (restrict to 50% overlap between frames) (default: {512}) + win_length {[type]} -- Length of the window function applied to each frame (if not specified, it + equals the filter length). (default: {None}) + window {str} -- Type of window to use (options are bartlett, hann, hamming, blackman, blackmanharris) + (default: {'hann'}) + """ + super(STFT, self).__init__() + self.filter_length = filter_length + self.hop_length = hop_length + self.win_length = win_length if win_length else filter_length + self.window = window + self.forward_transform = None + self.pad_amount = int(self.filter_length / 2) + fourier_basis = np.fft.fft(np.eye(self.filter_length)) + + cutoff = int((self.filter_length / 2 + 1)) + fourier_basis = np.vstack( + [np.real(fourier_basis[:cutoff, :]), np.imag(fourier_basis[:cutoff, :])] + ) + forward_basis = torch.FloatTensor(fourier_basis) + inverse_basis = torch.FloatTensor(np.linalg.pinv(fourier_basis)) + + assert filter_length >= self.win_length + # get window and zero center pad it to filter_length + fft_window = get_window(window, self.win_length, fftbins=True) + fft_window = pad_center(fft_window, size=filter_length) + fft_window = torch.from_numpy(fft_window).float() + + # window the bases + forward_basis *= fft_window + inverse_basis = (inverse_basis.T * fft_window).T + + self.register_buffer("forward_basis", forward_basis.float()) + self.register_buffer("inverse_basis", inverse_basis.float()) + self.register_buffer("fft_window", fft_window.float()) + + def transform(self, input_data, return_phase=False): + """Take input data (audio) to STFT domain. + + Arguments: + input_data {tensor} -- Tensor of floats, with shape (num_batch, num_samples) + + Returns: + magnitude {tensor} -- Magnitude of STFT with shape (num_batch, + num_frequencies, num_frames) + phase {tensor} -- Phase of STFT with shape (num_batch, + num_frequencies, num_frames) + """ + input_data = F.pad( + input_data, + (self.pad_amount, self.pad_amount), + mode="reflect", + ) + forward_transform = input_data.unfold( + 1, self.filter_length, self.hop_length + ).permute(0, 2, 1) + forward_transform = torch.matmul(self.forward_basis, forward_transform) + cutoff = int((self.filter_length / 2) + 1) + real_part = forward_transform[:, :cutoff, :] + imag_part = forward_transform[:, cutoff:, :] + magnitude = torch.sqrt(real_part**2 + imag_part**2) + if return_phase: + phase = torch.atan2(imag_part.data, real_part.data) + return magnitude, phase + else: + return magnitude + + def inverse(self, magnitude, phase): + """Call the inverse STFT (iSTFT), given magnitude and phase tensors produced + by the ```transform``` function. + + Arguments: + magnitude {tensor} -- Magnitude of STFT with shape (num_batch, + num_frequencies, num_frames) + phase {tensor} -- Phase of STFT with shape (num_batch, + num_frequencies, num_frames) + + Returns: + inverse_transform {tensor} -- Reconstructed audio given magnitude and phase. Of + shape (num_batch, num_samples) + """ + cat = torch.cat( + [magnitude * torch.cos(phase), magnitude * torch.sin(phase)], dim=1 + ) + fold = torch.nn.Fold( + output_size=(1, (cat.size(-1) - 1) * self.hop_length + self.filter_length), + kernel_size=(1, self.filter_length), + stride=(1, self.hop_length), + ) + inverse_transform = torch.matmul(self.inverse_basis, cat) + inverse_transform = fold(inverse_transform)[ + :, 0, 0, self.pad_amount : -self.pad_amount + ] + window_square_sum = ( + self.fft_window.pow(2).repeat(cat.size(-1), 1).T.unsqueeze(0) + ) + window_square_sum = fold(window_square_sum)[ + :, 0, 0, self.pad_amount : -self.pad_amount + ] + inverse_transform /= window_square_sum + return inverse_transform + + def forward(self, input_data): + """Take input data (audio) to STFT domain and then back to audio. + + Arguments: + input_data {tensor} -- Tensor of floats, with shape (num_batch, num_samples) + + Returns: + reconstruction {tensor} -- Reconstructed audio given magnitude and phase. Of + shape (num_batch, num_samples) + """ + self.magnitude, self.phase = self.transform(input_data, return_phase=True) + reconstruction = self.inverse(self.magnitude, self.phase) + return reconstruction + + +from time import time as ttime + + +class BiGRU(nn.Module): + def __init__(self, input_features, hidden_features, num_layers): + super(BiGRU, self).__init__() + self.gru = nn.GRU( + input_features, + hidden_features, + num_layers=num_layers, + batch_first=True, + bidirectional=True, + ) + + def forward(self, x): + return self.gru(x)[0] + + +class ConvBlockRes(nn.Module): + def __init__(self, in_channels, out_channels, momentum=0.01): + super(ConvBlockRes, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + nn.Conv2d( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + ) + # self.shortcut:Optional[nn.Module] = None + if in_channels != out_channels: + self.shortcut = nn.Conv2d(in_channels, out_channels, (1, 1)) + + def forward(self, x: torch.Tensor): + if not hasattr(self, "shortcut"): + return self.conv(x) + x + else: + return self.conv(x) + self.shortcut(x) + + +class Encoder(nn.Module): + def __init__( + self, + in_channels, + in_size, + n_encoders, + kernel_size, + n_blocks, + out_channels=16, + momentum=0.01, + ): + super(Encoder, self).__init__() + self.n_encoders = n_encoders + self.bn = nn.BatchNorm2d(in_channels, momentum=momentum) + self.layers = nn.ModuleList() + self.latent_channels = [] + for i in range(self.n_encoders): + self.layers.append( + ResEncoderBlock( + in_channels, out_channels, kernel_size, n_blocks, momentum=momentum + ) + ) + self.latent_channels.append([out_channels, in_size]) + in_channels = out_channels + out_channels *= 2 + in_size //= 2 + self.out_size = in_size + self.out_channel = out_channels + + def forward(self, x: torch.Tensor): + concat_tensors: List[torch.Tensor] = [] + x = self.bn(x) + for i, layer in enumerate(self.layers): + t, x = layer(x) + concat_tensors.append(t) + return x, concat_tensors + + +class ResEncoderBlock(nn.Module): + def __init__( + self, in_channels, out_channels, kernel_size, n_blocks=1, momentum=0.01 + ): + super(ResEncoderBlock, self).__init__() + self.n_blocks = n_blocks + self.conv = nn.ModuleList() + self.conv.append(ConvBlockRes(in_channels, out_channels, momentum)) + for i in range(n_blocks - 1): + self.conv.append(ConvBlockRes(out_channels, out_channels, momentum)) + self.kernel_size = kernel_size + if self.kernel_size is not None: + self.pool = nn.AvgPool2d(kernel_size=kernel_size) + + def forward(self, x): + for i, conv in enumerate(self.conv): + x = conv(x) + if self.kernel_size is not None: + return x, self.pool(x) + else: + return x + + +class Intermediate(nn.Module): # + def __init__(self, in_channels, out_channels, n_inters, n_blocks, momentum=0.01): + super(Intermediate, self).__init__() + self.n_inters = n_inters + self.layers = nn.ModuleList() + self.layers.append( + ResEncoderBlock(in_channels, out_channels, None, n_blocks, momentum) + ) + for i in range(self.n_inters - 1): + self.layers.append( + ResEncoderBlock(out_channels, out_channels, None, n_blocks, momentum) + ) + + def forward(self, x): + for i, layer in enumerate(self.layers): + x = layer(x) + return x + + +class ResDecoderBlock(nn.Module): + def __init__(self, in_channels, out_channels, stride, n_blocks=1, momentum=0.01): + super(ResDecoderBlock, self).__init__() + out_padding = (0, 1) if stride == (1, 2) else (1, 1) + self.n_blocks = n_blocks + self.conv1 = nn.Sequential( + nn.ConvTranspose2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=(3, 3), + stride=stride, + padding=(1, 1), + output_padding=out_padding, + bias=False, + ), + nn.BatchNorm2d(out_channels, momentum=momentum), + nn.ReLU(), + ) + self.conv2 = nn.ModuleList() + self.conv2.append(ConvBlockRes(out_channels * 2, out_channels, momentum)) + for i in range(n_blocks - 1): + self.conv2.append(ConvBlockRes(out_channels, out_channels, momentum)) + + def forward(self, x, concat_tensor): + x = self.conv1(x) + x = torch.cat((x, concat_tensor), dim=1) + for i, conv2 in enumerate(self.conv2): + x = conv2(x) + return x + + +class Decoder(nn.Module): + def __init__(self, in_channels, n_decoders, stride, n_blocks, momentum=0.01): + super(Decoder, self).__init__() + self.layers = nn.ModuleList() + self.n_decoders = n_decoders + for i in range(self.n_decoders): + out_channels = in_channels // 2 + self.layers.append( + ResDecoderBlock(in_channels, out_channels, stride, n_blocks, momentum) + ) + in_channels = out_channels + + def forward(self, x: torch.Tensor, concat_tensors: List[torch.Tensor]): + for i, layer in enumerate(self.layers): + x = layer(x, concat_tensors[-1 - i]) + return x + + +class DeepUnet(nn.Module): + def __init__( + self, + kernel_size, + n_blocks, + en_de_layers=5, + inter_layers=4, + in_channels=1, + en_out_channels=16, + ): + super(DeepUnet, self).__init__() + self.encoder = Encoder( + in_channels, 128, en_de_layers, kernel_size, n_blocks, en_out_channels + ) + self.intermediate = Intermediate( + self.encoder.out_channel // 2, + self.encoder.out_channel, + inter_layers, + n_blocks, + ) + self.decoder = Decoder( + self.encoder.out_channel, en_de_layers, kernel_size, n_blocks + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x, concat_tensors = self.encoder(x) + x = self.intermediate(x) + x = self.decoder(x, concat_tensors) + return x + + +class E2E(nn.Module): + def __init__( + self, + n_blocks, + n_gru, + kernel_size, + en_de_layers=5, + inter_layers=4, + in_channels=1, + en_out_channels=16, + ): + super(E2E, self).__init__() + self.unet = DeepUnet( + kernel_size, + n_blocks, + en_de_layers, + inter_layers, + in_channels, + en_out_channels, + ) + self.cnn = nn.Conv2d(en_out_channels, 3, (3, 3), padding=(1, 1)) + if n_gru: + self.fc = nn.Sequential( + BiGRU(3 * 128, 256, n_gru), + nn.Linear(512, 360), + nn.Dropout(0.25), + nn.Sigmoid(), + ) + else: + self.fc = nn.Sequential( + nn.Linear(3 * nn.N_MELS, nn.N_CLASS), nn.Dropout(0.25), nn.Sigmoid() + ) + + def forward(self, mel): + # print(mel.shape) + mel = mel.transpose(-1, -2).unsqueeze(1) + x = self.cnn(self.unet(mel)).transpose(1, 2).flatten(-2) + x = self.fc(x) + # print(x.shape) + return x + + +from librosa.filters import mel + + +class MelSpectrogram(torch.nn.Module): + def __init__( + self, + is_half, + n_mel_channels, + sampling_rate, + win_length, + hop_length, + n_fft=None, + mel_fmin=0, + mel_fmax=None, + clamp=1e-5, + ): + super().__init__() + n_fft = win_length if n_fft is None else n_fft + self.hann_window = {} + mel_basis = mel( + sr=sampling_rate, + n_fft=n_fft, + n_mels=n_mel_channels, + fmin=mel_fmin, + fmax=mel_fmax, + htk=True, + ) + mel_basis = torch.from_numpy(mel_basis).float() + self.register_buffer("mel_basis", mel_basis) + self.n_fft = win_length if n_fft is None else n_fft + self.hop_length = hop_length + self.win_length = win_length + self.sampling_rate = sampling_rate + self.n_mel_channels = n_mel_channels + self.clamp = clamp + self.is_half = is_half + + def forward(self, audio, keyshift=0, speed=1, center=True): + factor = 2 ** (keyshift / 12) + n_fft_new = int(np.round(self.n_fft * factor)) + win_length_new = int(np.round(self.win_length * factor)) + hop_length_new = int(np.round(self.hop_length * speed)) + keyshift_key = str(keyshift) + "_" + str(audio.device) + if keyshift_key not in self.hann_window: + self.hann_window[keyshift_key] = torch.hann_window(win_length_new).to( + audio.device + ) + if "privateuseone" in str(audio.device): + if not hasattr(self, "stft"): + self.stft = STFT( + filter_length=n_fft_new, + hop_length=hop_length_new, + win_length=win_length_new, + window="hann", + ).to(audio.device) + magnitude = self.stft.transform(audio) + else: + fft = torch.stft( + audio, + n_fft=n_fft_new, + hop_length=hop_length_new, + win_length=win_length_new, + window=self.hann_window[keyshift_key], + center=center, + return_complex=True, + ) + magnitude = torch.sqrt(fft.real.pow(2) + fft.imag.pow(2)) + if keyshift != 0: + size = self.n_fft // 2 + 1 + resize = magnitude.size(1) + if resize < size: + magnitude = F.pad(magnitude, (0, 0, 0, size - resize)) + magnitude = magnitude[:, :size, :] * self.win_length / win_length_new + mel_output = torch.matmul(self.mel_basis, magnitude) + if self.is_half == True: + mel_output = mel_output.half() + log_mel_spec = torch.log(torch.clamp(mel_output, min=self.clamp)) + return log_mel_spec + + +class RMVPE: + def __init__(self, model_path: str, is_half, device=None, use_jit=False): + self.resample_kernel = {} + self.resample_kernel = {} + self.is_half = is_half + if device is None: + device = "cuda:0" if torch.cuda.is_available() else "cpu" + self.device = device + self.mel_extractor = MelSpectrogram( + is_half, 128, 16000, 1024, 160, None, 30, 8000 + ).to(device) + if "privateuseone" in str(device): + import onnxruntime as ort + + ort_session = ort.InferenceSession( + "%s/rmvpe.onnx" % os.environ["rmvpe_root"], + providers=["DmlExecutionProvider"], + ) + self.model = ort_session + else: + if str(self.device) == "cuda": + self.device = torch.device("cuda:0") + + def get_jit_model(): + jit_model_path = model_path.rstrip(".pth") + jit_model_path += ".half.jit" if is_half else ".jit" + reload = False + if os.path.exists(jit_model_path): + ckpt = jit.load(jit_model_path) + model_device = ckpt["device"] + if model_device != str(self.device): + reload = True + else: + reload = True + + if reload: + ckpt = jit.rmvpe_jit_export( + model_path=model_path, + mode="script", + inputs_path=None, + save_path=jit_model_path, + device=device, + is_half=is_half, + ) + model = torch.jit.load(BytesIO(ckpt["model"]), map_location=device) + return model + + def get_default_model(): + model = E2E(4, 1, (2, 2)) + ckpt = torch.load(model_path, map_location="cpu") + model.load_state_dict(ckpt) + model.eval() + if is_half: + model = model.half() + else: + model = model.float() + return model + + if use_jit: + if is_half and "cpu" in str(self.device): + logger.warning( + "Use default rmvpe model. \ + Jit is not supported on the CPU for half floating point" + ) + self.model = get_default_model() + else: + self.model = get_jit_model() + else: + self.model = get_default_model() + + self.model = self.model.to(device) + cents_mapping = 20 * np.arange(360) + 1997.3794084376191 + self.cents_mapping = np.pad(cents_mapping, (4, 4)) # 368 + + def mel2hidden(self, mel): + with torch.no_grad(): + n_frames = mel.shape[-1] + n_pad = 32 * ((n_frames - 1) // 32 + 1) - n_frames + if n_pad > 0: + mel = F.pad(mel, (0, n_pad), mode="constant") + if "privateuseone" in str(self.device): + onnx_input_name = self.model.get_inputs()[0].name + onnx_outputs_names = self.model.get_outputs()[0].name + hidden = self.model.run( + [onnx_outputs_names], + input_feed={onnx_input_name: mel.cpu().numpy()}, + )[0] + else: + mel = mel.half() if self.is_half else mel.float() + hidden = self.model(mel) + return hidden[:, :n_frames] + + def decode(self, hidden, thred=0.03): + cents_pred = self.to_local_average_cents(hidden, thred=thred) + f0 = 10 * (2 ** (cents_pred / 1200)) + f0[f0 == 10] = 0 + # f0 = np.array([10 * (2 ** (cent_pred / 1200)) if cent_pred else 0 for cent_pred in cents_pred]) + return f0 + + def infer_from_audio(self, audio, thred=0.03): + # torch.cuda.synchronize() + # t0 = ttime() + if not torch.is_tensor(audio): + audio = torch.from_numpy(audio) + mel = self.mel_extractor( + audio.float().to(self.device).unsqueeze(0), center=True + ) + # print(123123123,mel.device.type) + # torch.cuda.synchronize() + # t1 = ttime() + hidden = self.mel2hidden(mel) + # torch.cuda.synchronize() + # t2 = ttime() + # print(234234,hidden.device.type) + if "privateuseone" not in str(self.device): + hidden = hidden.squeeze(0).cpu().numpy() + else: + hidden = hidden[0] + if self.is_half == True: + hidden = hidden.astype("float32") + + f0 = self.decode(hidden, thred=thred) + # torch.cuda.synchronize() + # t3 = ttime() + # print("hmvpe:%s\t%s\t%s\t%s"%(t1-t0,t2-t1,t3-t2,t3-t0)) + return f0 + + def to_local_average_cents(self, salience, thred=0.05): + # t0 = ttime() + center = np.argmax(salience, axis=1) # 帧长#index + salience = np.pad(salience, ((0, 0), (4, 4))) # 帧长,368 + # t1 = ttime() + center += 4 + todo_salience = [] + todo_cents_mapping = [] + starts = center - 4 + ends = center + 5 + for idx in range(salience.shape[0]): + todo_salience.append(salience[:, starts[idx] : ends[idx]][idx]) + todo_cents_mapping.append(self.cents_mapping[starts[idx] : ends[idx]]) + # t2 = ttime() + todo_salience = np.array(todo_salience) # 帧长,9 + todo_cents_mapping = np.array(todo_cents_mapping) # 帧长,9 + product_sum = np.sum(todo_salience * todo_cents_mapping, 1) + weight_sum = np.sum(todo_salience, 1) # 帧长 + devided = product_sum / weight_sum # 帧长 + # t3 = ttime() + maxx = np.max(salience, axis=1) # 帧长 + devided[maxx <= thred] = 0 + # t4 = ttime() + # print("decode:%s\t%s\t%s\t%s" % (t1 - t0, t2 - t1, t3 - t2, t4 - t3)) + return devided + + +if __name__ == "__main__": + import librosa + import soundfile as sf + + audio, sampling_rate = sf.read(r"C:\Users\liujing04\Desktop\Z\冬之花clip1.wav") + if len(audio.shape) > 1: + audio = librosa.to_mono(audio.transpose(1, 0)) + audio_bak = audio.copy() + if sampling_rate != 16000: + audio = librosa.resample(audio, orig_sr=sampling_rate, target_sr=16000) + model_path = r"D:\BaiduNetdiskDownload\RVC-beta-v2-0727AMD_realtime\rmvpe.pt" + thred = 0.03 # 0.01 + device = "cuda" if torch.cuda.is_available() else "cpu" + rmvpe = RMVPE(model_path, is_half=False, device=device) + t0 = ttime() + f0 = rmvpe.infer_from_audio(audio, thred=thred) + # f0 = rmvpe.infer_from_audio(audio, thred=thred) + # f0 = rmvpe.infer_from_audio(audio, thred=thred) + # f0 = rmvpe.infer_from_audio(audio, thred=thred) + # f0 = rmvpe.infer_from_audio(audio, thred=thred) + t1 = ttime() + logger.info("%s %.2f", f0.shape, t1 - t0) diff --git a/rvc/infer/lib/rtrvc.py b/rvc/infer/lib/rtrvc.py new file mode 100644 index 0000000..6e5a5e8 --- /dev/null +++ b/rvc/infer/lib/rtrvc.py @@ -0,0 +1,466 @@ +from io import BytesIO +import os +import sys +import traceback +from infer.lib import jit +from infer.lib.jit.get_synthesizer import get_synthesizer +from time import time as ttime +import fairseq +import faiss +import numpy as np +import parselmouth +import pyworld +import scipy.signal as signal +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchcrepe +from torchaudio.transforms import Resample + +now_dir = os.getcwd() +sys.path.append(now_dir) +from multiprocessing import Manager as M + +from configs.config import Config + +# config = Config() + +mm = M() + + +def printt(strr, *args): + if len(args) == 0: + print(strr) + else: + print(strr % args) + + +# config.device=torch.device("cpu")########强制cpu测试 +# config.is_half=False########强制cpu测试 +class RVC: + def __init__( + self, + key, + formant, + pth_path, + index_path, + index_rate, + n_cpu, + inp_q, + opt_q, + config: Config, + last_rvc=None, + ) -> None: + """ + 初始化 + """ + try: + if config.dml == True: + + def forward_dml(ctx, x, scale): + ctx.scale = scale + res = x.clone().detach() + return res + + fairseq.modules.grad_multiply.GradMultiply.forward = forward_dml + # global config + self.config = config + self.inp_q = inp_q + self.opt_q = opt_q + # device="cpu"########强制cpu测试 + self.device = config.device + self.f0_up_key = key + self.formant_shift = formant + self.f0_min = 50 + self.f0_max = 1100 + self.f0_mel_min = 1127 * np.log(1 + self.f0_min / 700) + self.f0_mel_max = 1127 * np.log(1 + self.f0_max / 700) + self.n_cpu = n_cpu + self.use_jit = self.config.use_jit + self.is_half = config.is_half + + if index_rate != 0: + self.index = faiss.read_index(index_path) + self.big_npy = self.index.reconstruct_n(0, self.index.ntotal) + printt("Index search enabled") + self.pth_path: str = pth_path + self.index_path = index_path + self.index_rate = index_rate + self.cache_pitch: torch.Tensor = torch.zeros( + 1024, device=self.device, dtype=torch.long + ) + self.cache_pitchf = torch.zeros( + 1024, device=self.device, dtype=torch.float32 + ) + + self.resample_kernel = {} + + if last_rvc is None: + models, _, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task( + ["assets/hubert/hubert_base.pt"], + suffix="", + ) + hubert_model = models[0] + hubert_model = hubert_model.to(self.device) + if self.is_half: + hubert_model = hubert_model.half() + else: + hubert_model = hubert_model.float() + hubert_model.eval() + self.model = hubert_model + else: + self.model = last_rvc.model + + self.net_g: nn.Module = None + + def set_default_model(): + self.net_g, cpt = get_synthesizer(self.pth_path, self.device) + self.tgt_sr = cpt["config"][-1] + cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0] + self.if_f0 = cpt.get("f0", 1) + self.version = cpt.get("version", "v1") + if self.is_half: + self.net_g = self.net_g.half() + else: + self.net_g = self.net_g.float() + + def set_jit_model(): + jit_pth_path = self.pth_path.rstrip(".pth") + jit_pth_path += ".half.jit" if self.is_half else ".jit" + reload = False + if str(self.device) == "cuda": + self.device = torch.device("cuda:0") + if os.path.exists(jit_pth_path): + cpt = jit.load(jit_pth_path) + model_device = cpt["device"] + if model_device != str(self.device): + reload = True + else: + reload = True + + if reload: + cpt = jit.synthesizer_jit_export( + self.pth_path, + "script", + None, + device=self.device, + is_half=self.is_half, + ) + + self.tgt_sr = cpt["config"][-1] + self.if_f0 = cpt.get("f0", 1) + self.version = cpt.get("version", "v1") + self.net_g = torch.jit.load( + BytesIO(cpt["model"]), map_location=self.device + ) + self.net_g.infer = self.net_g.forward + self.net_g.eval().to(self.device) + + def set_synthesizer(): + if self.use_jit and not config.dml: + if self.is_half and "cpu" in str(self.device): + printt( + "Use default Synthesizer model. \ + Jit is not supported on the CPU for half floating point" + ) + set_default_model() + else: + set_jit_model() + else: + set_default_model() + + if last_rvc is None or last_rvc.pth_path != self.pth_path: + set_synthesizer() + else: + self.tgt_sr = last_rvc.tgt_sr + self.if_f0 = last_rvc.if_f0 + self.version = last_rvc.version + self.is_half = last_rvc.is_half + if last_rvc.use_jit != self.use_jit: + set_synthesizer() + else: + self.net_g = last_rvc.net_g + + if last_rvc is not None and hasattr(last_rvc, "model_rmvpe"): + self.model_rmvpe = last_rvc.model_rmvpe + if last_rvc is not None and hasattr(last_rvc, "model_fcpe"): + self.device_fcpe = last_rvc.device_fcpe + self.model_fcpe = last_rvc.model_fcpe + except: + printt(traceback.format_exc()) + + def change_key(self, new_key): + self.f0_up_key = new_key + + def change_formant(self, new_formant): + self.formant_shift = new_formant + + def change_index_rate(self, new_index_rate): + if new_index_rate != 0 and self.index_rate == 0: + self.index = faiss.read_index(self.index_path) + self.big_npy = self.index.reconstruct_n(0, self.index.ntotal) + printt("Index search enabled") + self.index_rate = new_index_rate + + def get_f0_post(self, f0): + if not torch.is_tensor(f0): + f0 = torch.from_numpy(f0) + f0 = f0.float().to(self.device).squeeze() + f0_mel = 1127 * torch.log(1 + f0 / 700) + f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - self.f0_mel_min) * 254 / ( + self.f0_mel_max - self.f0_mel_min + ) + 1 + f0_mel[f0_mel <= 1] = 1 + f0_mel[f0_mel > 255] = 255 + f0_coarse = torch.round(f0_mel).long() + return f0_coarse, f0 + + def get_f0(self, x, f0_up_key, n_cpu, method="harvest"): + n_cpu = int(n_cpu) + if method == "crepe": + return self.get_f0_crepe(x, f0_up_key) + if method == "rmvpe": + return self.get_f0_rmvpe(x, f0_up_key) + if method == "fcpe": + return self.get_f0_fcpe(x, f0_up_key) + x = x.cpu().numpy() + if method == "pm": + p_len = x.shape[0] // 160 + 1 + f0_min = 65 + l_pad = int(np.ceil(1.5 / f0_min * 16000)) + r_pad = l_pad + 1 + s = parselmouth.Sound(np.pad(x, (l_pad, r_pad)), 16000).to_pitch_ac( + time_step=0.01, + voicing_threshold=0.6, + pitch_floor=f0_min, + pitch_ceiling=1100, + ) + assert np.abs(s.t1 - 1.5 / f0_min) < 0.001 + f0 = s.selected_array["frequency"] + if len(f0) < p_len: + f0 = np.pad(f0, (0, p_len - len(f0))) + f0 = f0[:p_len] + f0 *= pow(2, f0_up_key / 12) + return self.get_f0_post(f0) + if n_cpu == 1: + f0, t = pyworld.harvest( + x.astype(np.double), + fs=16000, + f0_ceil=1100, + f0_floor=50, + frame_period=10, + ) + f0 = signal.medfilt(f0, 3) + f0 *= pow(2, f0_up_key / 12) + return self.get_f0_post(f0) + f0bak = np.zeros(x.shape[0] // 160 + 1, dtype=np.float64) + length = len(x) + part_length = 160 * ((length // 160 - 1) // n_cpu + 1) + n_cpu = (length // 160 - 1) // (part_length // 160) + 1 + ts = ttime() + res_f0 = mm.dict() + for idx in range(n_cpu): + tail = part_length * (idx + 1) + 320 + if idx == 0: + self.inp_q.put((idx, x[:tail], res_f0, n_cpu, ts)) + else: + self.inp_q.put( + (idx, x[part_length * idx - 320 : tail], res_f0, n_cpu, ts) + ) + while 1: + res_ts = self.opt_q.get() + if res_ts == ts: + break + f0s = [i[1] for i in sorted(res_f0.items(), key=lambda x: x[0])] + for idx, f0 in enumerate(f0s): + if idx == 0: + f0 = f0[:-3] + elif idx != n_cpu - 1: + f0 = f0[2:-3] + else: + f0 = f0[2:] + f0bak[part_length * idx // 160 : part_length * idx // 160 + f0.shape[0]] = ( + f0 + ) + f0bak = signal.medfilt(f0bak, 3) + f0bak *= pow(2, f0_up_key / 12) + return self.get_f0_post(f0bak) + + def get_f0_crepe(self, x, f0_up_key): + if "privateuseone" in str( + self.device + ): ###不支持dml,cpu又太慢用不成,拿fcpe顶替 + return self.get_f0(x, f0_up_key, 1, "fcpe") + # printt("using crepe,device:%s"%self.device) + f0, pd = torchcrepe.predict( + x.unsqueeze(0).float(), + 16000, + 160, + self.f0_min, + self.f0_max, + "full", + batch_size=512, + # device=self.device if self.device.type!="privateuseone" else "cpu",###crepe不用半精度全部是全精度所以不愁###cpu延迟高到没法用 + device=self.device, + return_periodicity=True, + ) + pd = torchcrepe.filter.median(pd, 3) + f0 = torchcrepe.filter.mean(f0, 3) + f0[pd < 0.1] = 0 + f0 *= pow(2, f0_up_key / 12) + return self.get_f0_post(f0) + + def get_f0_rmvpe(self, x, f0_up_key): + if hasattr(self, "model_rmvpe") == False: + from infer.lib.rmvpe import RMVPE + + printt("Loading rmvpe model") + self.model_rmvpe = RMVPE( + "assets/rmvpe/rmvpe.pt", + is_half=self.is_half, + device=self.device, + use_jit=self.config.use_jit, + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + f0 *= pow(2, f0_up_key / 12) + return self.get_f0_post(f0) + + def get_f0_fcpe(self, x, f0_up_key): + if hasattr(self, "model_fcpe") == False: + from torchfcpe import spawn_bundled_infer_model + + printt("Loading fcpe model") + if "privateuseone" in str(self.device): + self.device_fcpe = "cpu" + else: + self.device_fcpe = self.device + self.model_fcpe = spawn_bundled_infer_model(self.device_fcpe) + f0 = self.model_fcpe.infer( + x.to(self.device_fcpe).unsqueeze(0).float(), + sr=16000, + decoder_mode="local_argmax", + threshold=0.006, + ) + f0 *= pow(2, f0_up_key / 12) + return self.get_f0_post(f0) + + def infer( + self, + input_wav: torch.Tensor, + block_frame_16k, + skip_head, + return_length, + f0method, + ) -> np.ndarray: + t1 = ttime() + with torch.no_grad(): + if self.config.is_half: + feats = input_wav.half().view(1, -1) + else: + feats = input_wav.float().view(1, -1) + padding_mask = torch.BoolTensor(feats.shape).to(self.device).fill_(False) + inputs = { + "source": feats, + "padding_mask": padding_mask, + "output_layer": 9 if self.version == "v1" else 12, + } + logits = self.model.extract_features(**inputs) + feats = ( + self.model.final_proj(logits[0]) if self.version == "v1" else logits[0] + ) + feats = torch.cat((feats, feats[:, -1:, :]), 1) + t2 = ttime() + try: + if hasattr(self, "index") and self.index_rate != 0: + npy = feats[0][skip_head // 2 :].cpu().numpy().astype("float32") + score, ix = self.index.search(npy, k=8) + if (ix >= 0).all(): + weight = np.square(1 / score) + weight /= weight.sum(axis=1, keepdims=True) + npy = np.sum( + self.big_npy[ix] * np.expand_dims(weight, axis=2), axis=1 + ) + if self.config.is_half: + npy = npy.astype("float16") + feats[0][skip_head // 2 :] = ( + torch.from_numpy(npy).unsqueeze(0).to(self.device) + * self.index_rate + + (1 - self.index_rate) * feats[0][skip_head // 2 :] + ) + else: + printt( + "Invalid index. You MUST use added_xxxx.index but not trained_xxxx.index!" + ) + else: + printt("Index search FAILED or disabled") + except: + traceback.print_exc() + printt("Index search FAILED") + t3 = ttime() + p_len = input_wav.shape[0] // 160 + factor = pow(2, self.formant_shift / 12) + return_length2 = int(np.ceil(return_length * factor)) + if self.if_f0 == 1: + f0_extractor_frame = block_frame_16k + 800 + if f0method == "rmvpe": + f0_extractor_frame = 5120 * ((f0_extractor_frame - 1) // 5120 + 1) - 160 + pitch, pitchf = self.get_f0( + input_wav[-f0_extractor_frame:], + self.f0_up_key - self.formant_shift, + self.n_cpu, + f0method, + ) + shift = block_frame_16k // 160 + self.cache_pitch[:-shift] = self.cache_pitch[shift:].clone() + self.cache_pitchf[:-shift] = self.cache_pitchf[shift:].clone() + self.cache_pitch[4 - pitch.shape[0] :] = pitch[3:-1] + self.cache_pitchf[4 - pitch.shape[0] :] = pitchf[3:-1] + cache_pitch = self.cache_pitch[None, -p_len:] + cache_pitchf = ( + self.cache_pitchf[None, -p_len:] * return_length2 / return_length + ) + t4 = ttime() + feats = F.interpolate(feats.permute(0, 2, 1), scale_factor=2).permute(0, 2, 1) + feats = feats[:, :p_len, :] + p_len = torch.LongTensor([p_len]).to(self.device) + sid = torch.LongTensor([0]).to(self.device) + skip_head = torch.LongTensor([skip_head]) + return_length2 = torch.LongTensor([return_length2]) + return_length = torch.LongTensor([return_length]) + with torch.no_grad(): + if self.if_f0 == 1: + infered_audio, _, _ = self.net_g.infer( + feats, + p_len, + cache_pitch, + cache_pitchf, + sid, + skip_head, + return_length, + return_length2, + ) + else: + infered_audio, _, _ = self.net_g.infer( + feats, p_len, sid, skip_head, return_length, return_length2 + ) + infered_audio = infered_audio.squeeze(1).float() + upp_res = int(np.floor(factor * self.tgt_sr // 100)) + if upp_res != self.tgt_sr // 100: + if upp_res not in self.resample_kernel: + self.resample_kernel[upp_res] = Resample( + orig_freq=upp_res, + new_freq=self.tgt_sr // 100, + dtype=torch.float32, + ).to(self.device) + infered_audio = self.resample_kernel[upp_res]( + infered_audio[:, : return_length * upp_res] + ) + t5 = ttime() + printt( + "Spent time: fea = %.3fs, index = %.3fs, f0 = %.3fs, model = %.3fs", + t2 - t1, + t3 - t2, + t4 - t3, + t5 - t4, + ) + return infered_audio.squeeze() diff --git a/rvc/infer/lib/rvcmd.py b/rvc/infer/lib/rvcmd.py new file mode 100644 index 0000000..fe69274 --- /dev/null +++ b/rvc/infer/lib/rvcmd.py @@ -0,0 +1,243 @@ +import os +from pathlib import Path +import hashlib +import requests +from io import BytesIO +import logging + +logger = logging.getLogger(__name__) + + +def sha256(f) -> str: + sha256_hash = hashlib.sha256() + # Read and update hash in chunks of 4M + for byte_block in iter(lambda: f.read(4 * 1024 * 1024), b""): + sha256_hash.update(byte_block) + return sha256_hash.hexdigest() + + +def check_model( + dir_name: Path, model_name: str, hash: str, remove_incorrect=False +) -> bool: + target = dir_name / model_name + relname = target.as_posix() + relname = relname[relname.rindex("assets/") :] + logger.debug(f"checking {relname}...") + if not os.path.exists(target): + logger.info(f"{target} not exist.") + return False + with open(target, "rb") as f: + digest = sha256(f) + bakfile = f"{target}.bak" + if digest != hash: + logger.warn(f"{target} sha256 hash mismatch.") + logger.info(f"expected: {hash}") + logger.info(f"real val: {digest}") + logger.warn("please add parameter --update to download the latest assets.") + if remove_incorrect: + if not os.path.exists(bakfile): + os.rename(str(target), bakfile) + else: + os.remove(str(target)) + return False + if remove_incorrect and os.path.exists(bakfile): + os.remove(bakfile) + return True + + +def check_all_assets(update=False) -> bool: + BASE_DIR = Path(__file__).resolve().parent.parent.parent + + logger.info("checking hubret & rmvpe...") + + if not check_model( + BASE_DIR / "assets" / "hubert", + "hubert_base.pt", + os.environ["sha256_hubert_base_pt"], + update, + ): + return False + if not check_model( + BASE_DIR / "assets" / "rmvpe", + "rmvpe.pt", + os.environ["sha256_rmvpe_pt"], + update, + ): + return False + if not check_model( + BASE_DIR / "assets" / "rmvpe", + "rmvpe.onnx", + os.environ["sha256_rmvpe_onnx"], + update, + ): + return False + + rvc_models_dir = BASE_DIR / "assets" / "pretrained" + logger.info("checking pretrained models...") + model_names = [ + "D32k.pth", + "D40k.pth", + "D48k.pth", + "G32k.pth", + "G40k.pth", + "G48k.pth", + "f0D32k.pth", + "f0D40k.pth", + "f0D48k.pth", + "f0G32k.pth", + "f0G40k.pth", + "f0G48k.pth", + ] + for model in model_names: + menv = model.replace(".", "_") + if not check_model( + rvc_models_dir, model, os.environ[f"sha256_v1_{menv}"], update + ): + return False + + rvc_models_dir = BASE_DIR / "assets" / "pretrained_v2" + logger.info("checking pretrained models v2...") + for model in model_names: + menv = model.replace(".", "_") + if not check_model( + rvc_models_dir, model, os.environ[f"sha256_v2_{menv}"], update + ): + return False + + logger.info("checking uvr5_weights...") + rvc_models_dir = BASE_DIR / "assets" / "uvr5_weights" + model_names = [ + "HP2-人声vocals+非人声instrumentals.pth", + "HP2_all_vocals.pth", + "HP3_all_vocals.pth", + "HP5-主旋律人声vocals+其他instrumentals.pth", + "HP5_only_main_vocal.pth", + "VR-DeEchoAggressive.pth", + "VR-DeEchoDeReverb.pth", + "VR-DeEchoNormal.pth", + ] + for model in model_names: + menv = model.replace(".", "_") + if not check_model( + rvc_models_dir, model, os.environ[f"sha256_uvr5_{menv}"], update + ): + return False + if not check_model( + BASE_DIR / "assets" / "uvr5_weights" / "onnx_dereverb_By_FoxJoy", + "vocals.onnx", + os.environ[f"sha256_uvr5_vocals_onnx"], + update, + ): + return False + + logger.info("all assets are already latest.") + return True + + +def download_and_extract_tar_gz(url: str, folder: str): + import tarfile + + logger.info(f"downloading {url}") + response = requests.get(url, stream=True, timeout=(5, 10)) + with BytesIO() as out_file: + out_file.write(response.content) + out_file.seek(0) + logger.info(f"downloaded.") + with tarfile.open(fileobj=out_file, mode="r:gz") as tar: + tar.extractall(folder) + logger.info(f"extracted into {folder}") + + +def download_and_extract_zip(url: str, folder: str): + import zipfile + + logger.info(f"downloading {url}") + response = requests.get(url, stream=True, timeout=(5, 10)) + with BytesIO() as out_file: + out_file.write(response.content) + out_file.seek(0) + logger.info(f"downloaded.") + with zipfile.ZipFile(out_file) as zip_ref: + zip_ref.extractall(folder) + logger.info(f"extracted into {folder}") + + +def download_dns_yaml(url: str, folder: str): + logger.info(f"downloading {url}") + response = requests.get(url, stream=True, timeout=(5, 10)) + with open(os.path.join(folder, "dns.yaml"), "wb") as out_file: + out_file.write(response.content) + logger.info(f"downloaded into {folder}") + + +def download_all_assets(tmpdir: str, version="0.2.2"): + import subprocess + import platform + + archs = { + "aarch64": "arm64", + "armv8l": "arm64", + "arm64": "arm64", + "x86": "386", + "i386": "386", + "i686": "386", + "386": "386", + "x86_64": "amd64", + "x64": "amd64", + "amd64": "amd64", + } + system_type = platform.system().lower() + architecture = platform.machine().lower() + is_win = system_type == "windows" + + architecture = archs.get(architecture, None) + if not architecture: + logger.error(f"architecture {architecture} is not supported") + exit(1) + try: + BASE_URL = ( + "https://github.com/RVC-Project/RVC-Models-Downloader/releases/download/" + ) + suffix = "zip" if is_win else "tar.gz" + RVCMD_URL = BASE_URL + f"v{version}/rvcmd_{system_type}_{architecture}.{suffix}" + cmdfile = os.path.join(tmpdir, "rvcmd") + if is_win: + download_and_extract_zip(RVCMD_URL, tmpdir) + cmdfile += ".exe" + else: + download_and_extract_tar_gz(RVCMD_URL, tmpdir) + os.chmod(cmdfile, 0o755) + subprocess.run([cmdfile, "-notui", "-w", "0", "assets/all"]) + except Exception: + BASE_URL = "https://raw.gitcode.com/u011570312/RVC-Models-Downloader/assets/" + suffix = { + "darwin_amd64": "421", + "darwin_arm64": "422", + "linux_386": "423", + "linux_amd64": "424", + "linux_arm64": "425", + "windows_386": "426", + "windows_amd64": "427", + }[f"{system_type}_{architecture}"] + RVCMD_URL = BASE_URL + suffix + download_dns_yaml( + "https://raw.gitcode.com/u011570312/RVC-Models-Downloader/raw/main/dns.yaml", + tmpdir, + ) + if is_win: + download_and_extract_zip(RVCMD_URL, tmpdir) + cmdfile += ".exe" + else: + download_and_extract_tar_gz(RVCMD_URL, tmpdir) + os.chmod(cmdfile, 0o755) + subprocess.run( + [ + cmdfile, + "-notui", + "-w", + "0", + "-dns", + os.path.join(tmpdir, "dns.yaml"), + "assets/all", + ] + ) diff --git a/rvc/infer/lib/slicer2.py b/rvc/infer/lib/slicer2.py new file mode 100644 index 0000000..7d9d16d --- /dev/null +++ b/rvc/infer/lib/slicer2.py @@ -0,0 +1,260 @@ +import numpy as np + + +# This function is obtained from librosa. +def get_rms( + y, + frame_length=2048, + hop_length=512, + pad_mode="constant", +): + padding = (int(frame_length // 2), int(frame_length // 2)) + y = np.pad(y, padding, mode=pad_mode) + + axis = -1 + # put our new within-frame axis at the end for now + out_strides = y.strides + tuple([y.strides[axis]]) + # Reduce the shape on the framing axis + x_shape_trimmed = list(y.shape) + x_shape_trimmed[axis] -= frame_length - 1 + out_shape = tuple(x_shape_trimmed) + tuple([frame_length]) + xw = np.lib.stride_tricks.as_strided(y, shape=out_shape, strides=out_strides) + if axis < 0: + target_axis = axis - 1 + else: + target_axis = axis + 1 + xw = np.moveaxis(xw, -1, target_axis) + # Downsample along the target axis + slices = [slice(None)] * xw.ndim + slices[axis] = slice(0, None, hop_length) + x = xw[tuple(slices)] + + # Calculate power + power = np.mean(np.abs(x) ** 2, axis=-2, keepdims=True) + + return np.sqrt(power) + + +class Slicer: + def __init__( + self, + sr: int, + threshold: float = -40.0, + min_length: int = 5000, + min_interval: int = 300, + hop_size: int = 20, + max_sil_kept: int = 5000, + ): + if not min_length >= min_interval >= hop_size: + raise ValueError( + "The following condition must be satisfied: min_length >= min_interval >= hop_size" + ) + if not max_sil_kept >= hop_size: + raise ValueError( + "The following condition must be satisfied: max_sil_kept >= hop_size" + ) + min_interval = sr * min_interval / 1000 + self.threshold = 10 ** (threshold / 20.0) + self.hop_size = round(sr * hop_size / 1000) + self.win_size = min(round(min_interval), 4 * self.hop_size) + self.min_length = round(sr * min_length / 1000 / self.hop_size) + self.min_interval = round(min_interval / self.hop_size) + self.max_sil_kept = round(sr * max_sil_kept / 1000 / self.hop_size) + + def _apply_slice(self, waveform, begin, end): + if len(waveform.shape) > 1: + return waveform[ + :, begin * self.hop_size : min(waveform.shape[1], end * self.hop_size) + ] + else: + return waveform[ + begin * self.hop_size : min(waveform.shape[0], end * self.hop_size) + ] + + # @timeit + def slice(self, waveform): + if len(waveform.shape) > 1: + samples = waveform.mean(axis=0) + else: + samples = waveform + if samples.shape[0] <= self.min_length: + return [waveform] + rms_list = get_rms( + y=samples, frame_length=self.win_size, hop_length=self.hop_size + ).squeeze(0) + sil_tags = [] + silence_start = None + clip_start = 0 + for i, rms in enumerate(rms_list): + # Keep looping while frame is silent. + if rms < self.threshold: + # Record start of silent frames. + if silence_start is None: + silence_start = i + continue + # Keep looping while frame is not silent and silence start has not been recorded. + if silence_start is None: + continue + # Clear recorded silence start if interval is not enough or clip is too short + is_leading_silence = silence_start == 0 and i > self.max_sil_kept + need_slice_middle = ( + i - silence_start >= self.min_interval + and i - clip_start >= self.min_length + ) + if not is_leading_silence and not need_slice_middle: + silence_start = None + continue + # Need slicing. Record the range of silent frames to be removed. + if i - silence_start <= self.max_sil_kept: + pos = rms_list[silence_start : i + 1].argmin() + silence_start + if silence_start == 0: + sil_tags.append((0, pos)) + else: + sil_tags.append((pos, pos)) + clip_start = pos + elif i - silence_start <= self.max_sil_kept * 2: + pos = rms_list[ + i - self.max_sil_kept : silence_start + self.max_sil_kept + 1 + ].argmin() + pos += i - self.max_sil_kept + pos_l = ( + rms_list[ + silence_start : silence_start + self.max_sil_kept + 1 + ].argmin() + + silence_start + ) + pos_r = ( + rms_list[i - self.max_sil_kept : i + 1].argmin() + + i + - self.max_sil_kept + ) + if silence_start == 0: + sil_tags.append((0, pos_r)) + clip_start = pos_r + else: + sil_tags.append((min(pos_l, pos), max(pos_r, pos))) + clip_start = max(pos_r, pos) + else: + pos_l = ( + rms_list[ + silence_start : silence_start + self.max_sil_kept + 1 + ].argmin() + + silence_start + ) + pos_r = ( + rms_list[i - self.max_sil_kept : i + 1].argmin() + + i + - self.max_sil_kept + ) + if silence_start == 0: + sil_tags.append((0, pos_r)) + else: + sil_tags.append((pos_l, pos_r)) + clip_start = pos_r + silence_start = None + # Deal with trailing silence. + total_frames = rms_list.shape[0] + if ( + silence_start is not None + and total_frames - silence_start >= self.min_interval + ): + silence_end = min(total_frames, silence_start + self.max_sil_kept) + pos = rms_list[silence_start : silence_end + 1].argmin() + silence_start + sil_tags.append((pos, total_frames + 1)) + # Apply and return slices. + if len(sil_tags) == 0: + return [waveform] + else: + chunks = [] + if sil_tags[0][0] > 0: + chunks.append(self._apply_slice(waveform, 0, sil_tags[0][0])) + for i in range(len(sil_tags) - 1): + chunks.append( + self._apply_slice(waveform, sil_tags[i][1], sil_tags[i + 1][0]) + ) + if sil_tags[-1][1] < total_frames: + chunks.append( + self._apply_slice(waveform, sil_tags[-1][1], total_frames) + ) + return chunks + + +def main(): + import os.path + from argparse import ArgumentParser + + import librosa + import soundfile + + parser = ArgumentParser() + parser.add_argument("audio", type=str, help="The audio to be sliced") + parser.add_argument( + "--out", type=str, help="Output directory of the sliced audio clips" + ) + parser.add_argument( + "--db_thresh", + type=float, + required=False, + default=-40, + help="The dB threshold for silence detection", + ) + parser.add_argument( + "--min_length", + type=int, + required=False, + default=5000, + help="The minimum milliseconds required for each sliced audio clip", + ) + parser.add_argument( + "--min_interval", + type=int, + required=False, + default=300, + help="The minimum milliseconds for a silence part to be sliced", + ) + parser.add_argument( + "--hop_size", + type=int, + required=False, + default=10, + help="Frame length in milliseconds", + ) + parser.add_argument( + "--max_sil_kept", + type=int, + required=False, + default=500, + help="The maximum silence length kept around the sliced clip, presented in milliseconds", + ) + args = parser.parse_args() + out = args.out + if out is None: + out = os.path.dirname(os.path.abspath(args.audio)) + audio, sr = librosa.load(args.audio, sr=None, mono=False) + slicer = Slicer( + sr=sr, + threshold=args.db_thresh, + min_length=args.min_length, + min_interval=args.min_interval, + hop_size=args.hop_size, + max_sil_kept=args.max_sil_kept, + ) + chunks = slicer.slice(audio) + if not os.path.exists(out): + os.makedirs(out) + for i, chunk in enumerate(chunks): + if len(chunk.shape) > 1: + chunk = chunk.T + soundfile.write( + os.path.join( + out, + f"%s_%d.wav" + % (os.path.basename(args.audio).rsplit(".", maxsplit=1)[0], i), + ), + chunk, + sr, + ) + + +if __name__ == "__main__": + main() diff --git a/rvc/infer/lib/train/__pycache__/data_utils.cpython-310.pyc b/rvc/infer/lib/train/__pycache__/data_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6287fabdc3e3902bae1c840cea03876062e313a2 GIT binary patch literal 12243 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 VUJH=bi?;ElQfl#XDZ1~%e*>T}1B(Cv literal 0 HcmV?d00001 diff --git a/rvc/infer/lib/train/__pycache__/losses.cpython-310.pyc b/rvc/infer/lib/train/__pycache__/losses.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dec9a2c17557d26682b6b20e598296c05e3ddd1a GIT binary patch literal 1491 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 z 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 any batch is included either {x | b1 < length(x) <=b2} or {x | b2 < length(x) <= b3}. + + It removes samples which are not included in the boundaries. + Ex) boundaries = [b1, b2, b3] -> any x s.t. length(x) <= b1 or length(x) > b3 are discarded. + """ + + def __init__( + self, + dataset, + batch_size, + boundaries, + num_replicas=None, + rank=None, + shuffle=True, + ): + super().__init__(dataset, num_replicas=num_replicas, rank=rank, shuffle=shuffle) + self.lengths = dataset.lengths + self.batch_size = batch_size + self.boundaries = boundaries + + self.buckets, self.num_samples_per_bucket = self._create_buckets() + self.total_size = sum(self.num_samples_per_bucket) + self.num_samples = self.total_size // self.num_replicas + + def _create_buckets(self): + buckets = [[] for _ in range(len(self.boundaries) - 1)] + for i in range(len(self.lengths)): + length = self.lengths[i] + idx_bucket = self._bisect(length) + if idx_bucket != -1: + buckets[idx_bucket].append(i) + + for i in range(len(buckets) - 1, -1, -1): # + if len(buckets[i]) == 0: + buckets.pop(i) + self.boundaries.pop(i + 1) + + num_samples_per_bucket = [] + for i in range(len(buckets)): + len_bucket = len(buckets[i]) + total_batch_size = self.num_replicas * self.batch_size + rem = ( + total_batch_size - (len_bucket % total_batch_size) + ) % total_batch_size + num_samples_per_bucket.append(len_bucket + rem) + return buckets, num_samples_per_bucket + + def __iter__(self): + # deterministically shuffle based on epoch + g = torch.Generator() + g.manual_seed(self.epoch) + + indices = [] + if self.shuffle: + for bucket in self.buckets: + indices.append(torch.randperm(len(bucket), generator=g).tolist()) + else: + for bucket in self.buckets: + indices.append(list(range(len(bucket)))) + + batches = [] + for i in range(len(self.buckets)): + bucket = self.buckets[i] + len_bucket = len(bucket) + ids_bucket = indices[i] + num_samples_bucket = self.num_samples_per_bucket[i] + + # add extra samples to make it evenly divisible + rem = num_samples_bucket - len_bucket + ids_bucket = ( + ids_bucket + + ids_bucket * (rem // len_bucket) + + ids_bucket[: (rem % len_bucket)] + ) + + # subsample + ids_bucket = ids_bucket[self.rank :: self.num_replicas] + + # batching + for j in range(len(ids_bucket) // self.batch_size): + batch = [ + bucket[idx] + for idx in ids_bucket[ + j * self.batch_size : (j + 1) * self.batch_size + ] + ] + batches.append(batch) + + if self.shuffle: + batch_ids = torch.randperm(len(batches), generator=g).tolist() + batches = [batches[i] for i in batch_ids] + self.batches = batches + + assert len(self.batches) * self.batch_size == self.num_samples + return iter(self.batches) + + def _bisect(self, x, lo=0, hi=None): + if hi is None: + hi = len(self.boundaries) - 1 + + if hi > lo: + mid = (hi + lo) // 2 + if self.boundaries[mid] < x and x <= self.boundaries[mid + 1]: + return mid + elif x <= self.boundaries[mid]: + return self._bisect(x, lo, mid) + else: + return self._bisect(x, mid + 1, hi) + else: + return -1 + + def __len__(self): + return self.num_samples // self.batch_size diff --git a/rvc/infer/lib/train/losses.py b/rvc/infer/lib/train/losses.py new file mode 100644 index 0000000..aa7bd81 --- /dev/null +++ b/rvc/infer/lib/train/losses.py @@ -0,0 +1,58 @@ +import torch + + +def feature_loss(fmap_r, fmap_g): + loss = 0 + for dr, dg in zip(fmap_r, fmap_g): + for rl, gl in zip(dr, dg): + rl = rl.float().detach() + gl = gl.float() + loss += torch.mean(torch.abs(rl - gl)) + + return loss * 2 + + +def discriminator_loss(disc_real_outputs, disc_generated_outputs): + loss = 0 + r_losses = [] + g_losses = [] + for dr, dg in zip(disc_real_outputs, disc_generated_outputs): + dr = dr.float() + dg = dg.float() + r_loss = torch.mean((1 - dr) ** 2) + g_loss = torch.mean(dg**2) + loss += r_loss + g_loss + r_losses.append(r_loss.item()) + g_losses.append(g_loss.item()) + + return loss, r_losses, g_losses + + +def generator_loss(disc_outputs): + loss = 0 + gen_losses = [] + for dg in disc_outputs: + dg = dg.float() + l = torch.mean((1 - dg) ** 2) + gen_losses.append(l) + loss += l + + return loss, gen_losses + + +def kl_loss(z_p, logs_q, m_p, logs_p, z_mask): + """ + z_p, logs_q: [b, h, t_t] + m_p, logs_p: [b, h, t_t] + """ + z_p = z_p.float() + logs_q = logs_q.float() + m_p = m_p.float() + logs_p = logs_p.float() + z_mask = z_mask.float() + + kl = logs_p - logs_q - 0.5 + kl += 0.5 * ((z_p - m_p) ** 2) * torch.exp(-2.0 * logs_p) + kl = torch.sum(kl * z_mask) + l = kl / torch.sum(z_mask) + return l diff --git a/rvc/infer/lib/train/mel_processing.py b/rvc/infer/lib/train/mel_processing.py new file mode 100644 index 0000000..3751f1e --- /dev/null +++ b/rvc/infer/lib/train/mel_processing.py @@ -0,0 +1,127 @@ +import torch +import torch.utils.data +from librosa.filters import mel as librosa_mel_fn +import logging + +logger = logging.getLogger(__name__) + +MAX_WAV_VALUE = 32768.0 + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + """ + PARAMS + ------ + C: compression factor + """ + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + """ + PARAMS + ------ + C: compression factor used to compress + """ + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + return dynamic_range_compression_torch(magnitudes) + + +def spectral_de_normalize_torch(magnitudes): + return dynamic_range_decompression_torch(magnitudes) + + +# Reusable banks +mel_basis = {} +hann_window = {} + + +def spectrogram_torch(y, n_fft, sampling_rate, hop_size, win_size, center=False): + """Convert waveform into Linear-frequency Linear-amplitude spectrogram. + + Args: + y :: (B, T) - Audio waveforms + n_fft + sampling_rate + hop_size + win_size + center + Returns: + :: (B, Freq, Frame) - Linear-frequency Linear-amplitude spectrogram + """ + + # Window - Cache if needed + global hann_window + dtype_device = str(y.dtype) + "_" + str(y.device) + wnsize_dtype_device = str(win_size) + "_" + dtype_device + if wnsize_dtype_device not in hann_window: + hann_window[wnsize_dtype_device] = torch.hann_window(win_size).to( + dtype=y.dtype, device=y.device + ) + + # Padding + y = torch.nn.functional.pad( + y.unsqueeze(1), + (int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)), + mode="reflect", + ) + y = y.squeeze(1) + + # Complex Spectrogram :: (B, T) -> (B, Freq, Frame, RealComplex=2) + spec = torch.stft( + y, + n_fft, + hop_length=hop_size, + win_length=win_size, + window=hann_window[wnsize_dtype_device], + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + + # Linear-frequency Linear-amplitude spectrogram :: (B, Freq, Frame, RealComplex=2) -> (B, Freq, Frame) + spec = torch.sqrt(spec.real.pow(2) + spec.imag.pow(2) + 1e-6) + return spec + + +def spec_to_mel_torch(spec, n_fft, num_mels, sampling_rate, fmin, fmax): + # MelBasis - Cache if needed + global mel_basis + dtype_device = str(spec.dtype) + "_" + str(spec.device) + fmax_dtype_device = str(fmax) + "_" + dtype_device + if fmax_dtype_device not in mel_basis: + mel = librosa_mel_fn( + sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax + ) + mel_basis[fmax_dtype_device] = torch.from_numpy(mel).to( + dtype=spec.dtype, device=spec.device + ) + + # Mel-frequency Log-amplitude spectrogram :: (B, Freq=num_mels, Frame) + melspec = torch.matmul(mel_basis[fmax_dtype_device], spec) + melspec = spectral_normalize_torch(melspec) + return melspec + + +def mel_spectrogram_torch( + y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False +): + """Convert waveform into Mel-frequency Log-amplitude spectrogram. + + Args: + y :: (B, T) - Waveforms + Returns: + melspec :: (B, Freq, Frame) - Mel-frequency Log-amplitude spectrogram + """ + # Linear-frequency Linear-amplitude spectrogram :: (B, T) -> (B, Freq, Frame) + spec = spectrogram_torch(y, n_fft, sampling_rate, hop_size, win_size, center) + + # Mel-frequency Log-amplitude spectrogram :: (B, Freq, Frame) -> (B, Freq=num_mels, Frame) + melspec = spec_to_mel_torch(spec, n_fft, num_mels, sampling_rate, fmin, fmax) + + return melspec diff --git a/rvc/infer/lib/train/process_ckpt.py b/rvc/infer/lib/train/process_ckpt.py new file mode 100644 index 0000000..2529ccf --- /dev/null +++ b/rvc/infer/lib/train/process_ckpt.py @@ -0,0 +1,261 @@ +import os +import sys +import traceback +from collections import OrderedDict + +import torch + +from i18n.i18n import I18nAuto + +i18n = I18nAuto() + + +def savee(ckpt, sr, if_f0, name, epoch, version, hps): + try: + opt = OrderedDict() + opt["weight"] = {} + for key in ckpt.keys(): + if "enc_q" in key: + continue + opt["weight"][key] = ckpt[key].half() + opt["config"] = [ + hps.data.filter_length // 2 + 1, + 32, + hps.model.inter_channels, + hps.model.hidden_channels, + hps.model.filter_channels, + hps.model.n_heads, + hps.model.n_layers, + hps.model.kernel_size, + hps.model.p_dropout, + hps.model.resblock, + hps.model.resblock_kernel_sizes, + hps.model.resblock_dilation_sizes, + hps.model.upsample_rates, + hps.model.upsample_initial_channel, + hps.model.upsample_kernel_sizes, + hps.model.spk_embed_dim, + hps.model.gin_channels, + hps.data.sampling_rate, + ] + opt["info"] = "%sepoch" % epoch + opt["sr"] = sr + opt["f0"] = if_f0 + opt["version"] = version + torch.save(opt, "assets/weights/%s.pth" % name) + return "Success." + except: + return traceback.format_exc() + + +def show_info(path): + try: + a = torch.load(path, map_location="cpu") + return "模型信息:%s\n采样率:%s\n模型是否输入音高引导:%s\n版本:%s" % ( + a.get("info", "None"), + a.get("sr", "None"), + a.get("f0", "None"), + a.get("version", "None"), + ) + except: + return traceback.format_exc() + + +def extract_small_model(path, name, sr, if_f0, info, version): + try: + ckpt = torch.load(path, map_location="cpu") + if "model" in ckpt: + ckpt = ckpt["model"] + opt = OrderedDict() + opt["weight"] = {} + for key in ckpt.keys(): + if "enc_q" in key: + continue + opt["weight"][key] = ckpt[key].half() + if sr == "40k": + opt["config"] = [ + 1025, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 10, 2, 2], + 512, + [16, 16, 4, 4], + 109, + 256, + 40000, + ] + elif sr == "48k": + if version == "v1": + opt["config"] = [ + 1025, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 6, 2, 2, 2], + 512, + [16, 16, 4, 4, 4], + 109, + 256, + 48000, + ] + else: + opt["config"] = [ + 1025, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [12, 10, 2, 2], + 512, + [24, 20, 4, 4], + 109, + 256, + 48000, + ] + elif sr == "32k": + if version == "v1": + opt["config"] = [ + 513, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 4, 2, 2, 2], + 512, + [16, 16, 4, 4, 4], + 109, + 256, + 32000, + ] + else: + opt["config"] = [ + 513, + 32, + 192, + 192, + 768, + 2, + 6, + 3, + 0, + "1", + [3, 7, 11], + [[1, 3, 5], [1, 3, 5], [1, 3, 5]], + [10, 8, 2, 2], + 512, + [20, 16, 4, 4], + 109, + 256, + 32000, + ] + if info == "": + info = "Extracted model." + opt["info"] = info + opt["version"] = version + opt["sr"] = sr + opt["f0"] = int(if_f0) + torch.save(opt, "assets/weights/%s.pth" % name) + return "Success." + except: + return traceback.format_exc() + + +def change_info(path, info, name): + try: + ckpt = torch.load(path, map_location="cpu") + ckpt["info"] = info + if name == "": + name = os.path.basename(path) + torch.save(ckpt, "assets/weights/%s" % name) + return "Success." + except: + return traceback.format_exc() + + +def merge(path1, path2, alpha1, sr, f0, info, name, version): + try: + + def extract(ckpt): + a = ckpt["model"] + opt = OrderedDict() + opt["weight"] = {} + for key in a.keys(): + if "enc_q" in key: + continue + opt["weight"][key] = a[key] + return opt + + ckpt1 = torch.load(path1, map_location="cpu") + ckpt2 = torch.load(path2, map_location="cpu") + cfg = ckpt1["config"] + if "model" in ckpt1: + ckpt1 = extract(ckpt1) + else: + ckpt1 = ckpt1["weight"] + if "model" in ckpt2: + ckpt2 = extract(ckpt2) + else: + ckpt2 = ckpt2["weight"] + if sorted(list(ckpt1.keys())) != sorted(list(ckpt2.keys())): + return "Fail to merge the models. The model architectures are not the same." + opt = OrderedDict() + opt["weight"] = {} + for key in ckpt1.keys(): + # try: + if key == "emb_g.weight" and ckpt1[key].shape != ckpt2[key].shape: + min_shape0 = min(ckpt1[key].shape[0], ckpt2[key].shape[0]) + opt["weight"][key] = ( + alpha1 * (ckpt1[key][:min_shape0].float()) + + (1 - alpha1) * (ckpt2[key][:min_shape0].float()) + ).half() + else: + opt["weight"][key] = ( + alpha1 * (ckpt1[key].float()) + (1 - alpha1) * (ckpt2[key].float()) + ).half() + # except: + # pdb.set_trace() + opt["config"] = cfg + """ + if(sr=="40k"):opt["config"] = [1025, 32, 192, 192, 768, 2, 6, 3, 0, "1", [3, 7, 11], [[1, 3, 5], [1, 3, 5], [1, 3, 5]], [10, 10, 2, 2], 512, [16, 16, 4, 4,4], 109, 256, 40000] + elif(sr=="48k"):opt["config"] = [1025, 32, 192, 192, 768, 2, 6, 3, 0, "1", [3, 7, 11], [[1, 3, 5], [1, 3, 5], [1, 3, 5]], [10,6,2,2,2], 512, [16, 16, 4, 4], 109, 256, 48000] + elif(sr=="32k"):opt["config"] = [513, 32, 192, 192, 768, 2, 6, 3, 0, "1", [3, 7, 11], [[1, 3, 5], [1, 3, 5], [1, 3, 5]], [10, 4, 2, 2, 2], 512, [16, 16, 4, 4,4], 109, 256, 32000] + """ + opt["sr"] = sr + opt["f0"] = 1 if f0 == i18n("是") else 0 + opt["version"] = version + opt["info"] = info + torch.save(opt, "assets/weights/%s.pth" % name) + return "Success." + except: + return traceback.format_exc() diff --git a/rvc/infer/lib/train/utils.py b/rvc/infer/lib/train/utils.py new file mode 100644 index 0000000..e7bb783 --- /dev/null +++ b/rvc/infer/lib/train/utils.py @@ -0,0 +1,478 @@ +import argparse +import glob +import json +import logging +import os +import subprocess +import sys +import shutil + +import numpy as np +import torch +from scipy.io.wavfile import read + +MATPLOTLIB_FLAG = False + +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logger = logging + + +def load_checkpoint_d(checkpoint_path, combd, sbd, optimizer=None, load_opt=1): + assert os.path.isfile(checkpoint_path) + checkpoint_dict = torch.load(checkpoint_path, map_location="cpu") + + ################## + def go(model, bkey): + saved_state_dict = checkpoint_dict[bkey] + if hasattr(model, "module"): + state_dict = model.module.state_dict() + else: + state_dict = model.state_dict() + new_state_dict = {} + for k, v in state_dict.items(): # 模型需要的shape + try: + new_state_dict[k] = saved_state_dict[k] + if saved_state_dict[k].shape != state_dict[k].shape: + logger.warning( + "shape-%s-mismatch. need: %s, get: %s", + k, + state_dict[k].shape, + saved_state_dict[k].shape, + ) # + raise KeyError + except: + # logger.info(traceback.format_exc()) + logger.info("%s is not in the checkpoint", k) # pretrain缺失的 + new_state_dict[k] = v # 模型自带的随机值 + if hasattr(model, "module"): + model.module.load_state_dict(new_state_dict, strict=False) + else: + model.load_state_dict(new_state_dict, strict=False) + return model + + go(combd, "combd") + model = go(sbd, "sbd") + ############# + logger.info("Loaded model weights") + + iteration = checkpoint_dict["iteration"] + learning_rate = checkpoint_dict["learning_rate"] + if ( + optimizer is not None and load_opt == 1 + ): ###加载不了,如果是空的的话,重新初始化,可能还会影响lr时间表的更新,因此在train文件最外围catch + # try: + optimizer.load_state_dict(checkpoint_dict["optimizer"]) + # except: + # traceback.print_exc() + logger.info("Loaded checkpoint '{}' (epoch {})".format(checkpoint_path, iteration)) + return model, optimizer, learning_rate, iteration + + +# def load_checkpoint(checkpoint_path, model, optimizer=None): +# assert os.path.isfile(checkpoint_path) +# checkpoint_dict = torch.load(checkpoint_path, map_location='cpu') +# iteration = checkpoint_dict['iteration'] +# learning_rate = checkpoint_dict['learning_rate'] +# if optimizer is not None: +# optimizer.load_state_dict(checkpoint_dict['optimizer']) +# # print(1111) +# saved_state_dict = checkpoint_dict['model'] +# # print(1111) +# +# if hasattr(model, 'module'): +# state_dict = model.module.state_dict() +# else: +# state_dict = model.state_dict() +# new_state_dict= {} +# for k, v in state_dict.items(): +# try: +# new_state_dict[k] = saved_state_dict[k] +# except: +# logger.info("%s is not in the checkpoint" % k) +# new_state_dict[k] = v +# if hasattr(model, 'module'): +# model.module.load_state_dict(new_state_dict) +# else: +# model.load_state_dict(new_state_dict) +# logger.info("Loaded checkpoint '{}' (epoch {})" .format( +# checkpoint_path, iteration)) +# return model, optimizer, learning_rate, iteration +def load_checkpoint(checkpoint_path, model, optimizer=None, load_opt=1): + assert os.path.isfile(checkpoint_path) + checkpoint_dict = torch.load(checkpoint_path, map_location="cpu") + + saved_state_dict = checkpoint_dict["model"] + if hasattr(model, "module"): + state_dict = model.module.state_dict() + else: + state_dict = model.state_dict() + new_state_dict = {} + for k, v in state_dict.items(): # 模型需要的shape + try: + new_state_dict[k] = saved_state_dict[k] + if saved_state_dict[k].shape != state_dict[k].shape: + logger.warning( + "shape-%s-mismatch|need-%s|get-%s", + k, + state_dict[k].shape, + saved_state_dict[k].shape, + ) # + raise KeyError + except: + # logger.info(traceback.format_exc()) + logger.info("%s is not in the checkpoint", k) # pretrain缺失的 + new_state_dict[k] = v # 模型自带的随机值 + if hasattr(model, "module"): + model.module.load_state_dict(new_state_dict, strict=False) + else: + model.load_state_dict(new_state_dict, strict=False) + logger.info("Loaded model weights") + + iteration = checkpoint_dict["iteration"] + learning_rate = checkpoint_dict["learning_rate"] + if ( + optimizer is not None and load_opt == 1 + ): ###加载不了,如果是空的的话,重新初始化,可能还会影响lr时间表的更新,因此在train文件最外围catch + # try: + optimizer.load_state_dict(checkpoint_dict["optimizer"]) + # except: + # traceback.print_exc() + logger.info("Loaded checkpoint '{}' (epoch {})".format(checkpoint_path, iteration)) + return model, optimizer, learning_rate, iteration + + +def save_checkpoint(model, optimizer, learning_rate, iteration, checkpoint_path): + logger.info( + "Saving model and optimizer state at epoch {} to {}".format( + iteration, checkpoint_path + ) + ) + if hasattr(model, "module"): + state_dict = model.module.state_dict() + else: + state_dict = model.state_dict() + torch.save( + { + "model": state_dict, + "iteration": iteration, + "optimizer": optimizer.state_dict(), + "learning_rate": learning_rate, + }, + checkpoint_path, + ) + + +def save_checkpoint_d(combd, sbd, optimizer, learning_rate, iteration, checkpoint_path): + logger.info( + "Saving model and optimizer state at epoch {} to {}".format( + iteration, checkpoint_path + ) + ) + if hasattr(combd, "module"): + state_dict_combd = combd.module.state_dict() + else: + state_dict_combd = combd.state_dict() + if hasattr(sbd, "module"): + state_dict_sbd = sbd.module.state_dict() + else: + state_dict_sbd = sbd.state_dict() + torch.save( + { + "combd": state_dict_combd, + "sbd": state_dict_sbd, + "iteration": iteration, + "optimizer": optimizer.state_dict(), + "learning_rate": learning_rate, + }, + checkpoint_path, + ) + + +def summarize( + writer, + global_step, + scalars={}, + histograms={}, + images={}, + audios={}, + audio_sampling_rate=22050, +): + for k, v in scalars.items(): + writer.add_scalar(k, v, global_step) + for k, v in histograms.items(): + writer.add_histogram(k, v, global_step) + for k, v in images.items(): + writer.add_image(k, v, global_step, dataformats="HWC") + for k, v in audios.items(): + writer.add_audio(k, v, global_step, audio_sampling_rate) + + +def latest_checkpoint_path(dir_path, regex="G_*.pth"): + f_list = glob.glob(os.path.join(dir_path, regex)) + f_list.sort(key=lambda f: int("".join(filter(str.isdigit, f)))) + x = f_list[-1] + logger.debug(x) + return x + + +def plot_spectrogram_to_numpy(spectrogram): + global MATPLOTLIB_FLAG + if not MATPLOTLIB_FLAG: + import matplotlib + + matplotlib.use("Agg") + MATPLOTLIB_FLAG = True + mpl_logger = logging.getLogger("matplotlib") + mpl_logger.setLevel(logging.WARNING) + import matplotlib.pylab as plt + import numpy as np + + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + plt.xlabel("Frames") + plt.ylabel("Channels") + plt.tight_layout() + + fig.canvas.draw() + data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep="") + data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) + plt.close() + return data + + +def plot_alignment_to_numpy(alignment, info=None): + global MATPLOTLIB_FLAG + if not MATPLOTLIB_FLAG: + import matplotlib + + matplotlib.use("Agg") + MATPLOTLIB_FLAG = True + mpl_logger = logging.getLogger("matplotlib") + mpl_logger.setLevel(logging.WARNING) + import matplotlib.pylab as plt + import numpy as np + + fig, ax = plt.subplots(figsize=(6, 4)) + im = ax.imshow( + alignment.transpose(), aspect="auto", origin="lower", interpolation="none" + ) + fig.colorbar(im, ax=ax) + xlabel = "Decoder timestep" + if info is not None: + xlabel += "\n\n" + info + plt.xlabel(xlabel) + plt.ylabel("Encoder timestep") + plt.tight_layout() + + fig.canvas.draw() + data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep="") + data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) + plt.close() + return data + + +def load_wav_to_torch(full_path): + sampling_rate, data = read(full_path) + return torch.FloatTensor(data.astype(np.float32)), sampling_rate + + +def load_filepaths_and_text(filename, split="|"): + with open(filename, encoding="utf-8") as f: + filepaths_and_text = [line.strip().split(split) for line in f] + return filepaths_and_text + + +def get_hparams(init=True): + """ + todo: + 结尾七人组: + 保存频率、总epoch done + bs done + pretrainG、pretrainD done + 卡号:os.en["CUDA_VISIBLE_DEVICES"] done + if_latest done + 模型:if_f0 done + 采样率:自动选择config done + 是否缓存数据集进GPU:if_cache_data_in_gpu done + + -m: + 自动决定training_files路径,改掉train_nsf_load_pretrain.py里的hps.data.training_files done + -c不要了 + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "-se", + "--save_every_epoch", + type=int, + required=True, + help="checkpoint save frequency (epoch)", + ) + parser.add_argument( + "-te", "--total_epoch", type=int, required=True, help="total_epoch" + ) + parser.add_argument( + "-pg", "--pretrainG", type=str, default="", help="Pretrained Generator path" + ) + parser.add_argument( + "-pd", "--pretrainD", type=str, default="", help="Pretrained Discriminator path" + ) + parser.add_argument("-g", "--gpus", type=str, default="0", help="split by -") + parser.add_argument( + "-bs", "--batch_size", type=int, required=True, help="batch size" + ) + parser.add_argument( + "-e", "--experiment_dir", type=str, required=True, help="experiment dir" + ) # -m + parser.add_argument( + "-sr", "--sample_rate", type=str, required=True, help="sample rate, 32k/40k/48k" + ) + parser.add_argument( + "-sw", + "--save_every_weights", + type=str, + default="0", + help="save the extracted model in weights directory when saving checkpoints", + ) + parser.add_argument( + "-v", "--version", type=str, required=True, help="model version" + ) + parser.add_argument( + "-f0", + "--if_f0", + type=int, + required=True, + help="use f0 as one of the inputs of the model, 1 or 0", + ) + parser.add_argument( + "-l", + "--if_latest", + type=int, + required=True, + help="if only save the latest G/D pth file, 1 or 0", + ) + parser.add_argument( + "-c", + "--if_cache_data_in_gpu", + type=int, + required=True, + help="if caching the dataset in GPU memory, 1 or 0", + ) + + args = parser.parse_args() + name = args.experiment_dir + experiment_dir = os.path.join("./logs", args.experiment_dir) + + config_save_path = os.path.join(experiment_dir, "config.json") + with open(config_save_path, "r") as f: + config = json.load(f) + + hparams = HParams(**config) + hparams.model_dir = hparams.experiment_dir = experiment_dir + hparams.save_every_epoch = args.save_every_epoch + hparams.name = name + hparams.total_epoch = args.total_epoch + hparams.pretrainG = args.pretrainG + hparams.pretrainD = args.pretrainD + hparams.version = args.version + hparams.gpus = args.gpus + hparams.train.batch_size = args.batch_size + hparams.sample_rate = args.sample_rate + hparams.if_f0 = args.if_f0 + hparams.if_latest = args.if_latest + hparams.save_every_weights = args.save_every_weights + hparams.if_cache_data_in_gpu = args.if_cache_data_in_gpu + hparams.data.training_files = "%s/filelist.txt" % experiment_dir + return hparams + + +def get_hparams_from_dir(model_dir): + config_save_path = os.path.join(model_dir, "config.json") + with open(config_save_path, "r") as f: + data = f.read() + config = json.loads(data) + + hparams = HParams(**config) + hparams.model_dir = model_dir + return hparams + + +def get_hparams_from_file(config_path): + with open(config_path, "r") as f: + data = f.read() + config = json.loads(data) + + hparams = HParams(**config) + return hparams + + +def check_git_hash(model_dir): + source_dir = os.path.dirname(os.path.realpath(__file__)) + if not os.path.exists(os.path.join(source_dir, ".git")): + logger.warning( + "{} is not a git repository, therefore hash value comparison will be ignored.".format( + source_dir + ) + ) + return + + cur_hash = subprocess.getoutput("git rev-parse HEAD") + + path = os.path.join(model_dir, "githash") + if os.path.exists(path): + saved_hash = open(path).read() + if saved_hash != cur_hash: + logger.warning( + "git hash values are different. {}(saved) != {}(current)".format( + saved_hash[:8], cur_hash[:8] + ) + ) + else: + open(path, "w").write(cur_hash) + + +def get_logger(model_dir, filename="train.log"): + global logger + logger = logging.getLogger(os.path.basename(model_dir)) + logger.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s") + if not os.path.exists(model_dir): + os.makedirs(model_dir) + h = logging.FileHandler(os.path.join(model_dir, filename)) + h.setLevel(logging.DEBUG) + h.setFormatter(formatter) + logger.addHandler(h) + return logger + + +class HParams: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + if type(v) == dict: + v = HParams(**v) + self[k] = v + + def keys(self): + return self.__dict__.keys() + + def items(self): + return self.__dict__.items() + + def values(self): + return self.__dict__.values() + + def __len__(self): + return len(self.__dict__) + + def __getitem__(self, key): + return getattr(self, key) + + def __setitem__(self, key, value): + return setattr(self, key, value) + + def __contains__(self, key): + return key in self.__dict__ + + def __repr__(self): + return self.__dict__.__repr__() diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/dataset.py b/rvc/infer/lib/uvr5_pack/lib_v5/dataset.py new file mode 100644 index 0000000..cfd01a1 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/dataset.py @@ -0,0 +1,183 @@ +import os +import random + +import numpy as np +import torch +import torch.utils.data +from tqdm import tqdm + +from . import spec_utils + + +class VocalRemoverValidationSet(torch.utils.data.Dataset): + def __init__(self, patch_list): + self.patch_list = patch_list + + def __len__(self): + return len(self.patch_list) + + def __getitem__(self, idx): + path = self.patch_list[idx] + data = np.load(path) + + X, y = data["X"], data["y"] + + X_mag = np.abs(X) + y_mag = np.abs(y) + + return X_mag, y_mag + + +def make_pair(mix_dir, inst_dir): + input_exts = [".wav", ".m4a", ".mp3", ".mp4", ".flac"] + + X_list = sorted( + [ + os.path.join(mix_dir, fname) + for fname in os.listdir(mix_dir) + if os.path.splitext(fname)[1] in input_exts + ] + ) + y_list = sorted( + [ + os.path.join(inst_dir, fname) + for fname in os.listdir(inst_dir) + if os.path.splitext(fname)[1] in input_exts + ] + ) + + filelist = list(zip(X_list, y_list)) + + return filelist + + +def train_val_split(dataset_dir, split_mode, val_rate, val_filelist): + if split_mode == "random": + filelist = make_pair( + os.path.join(dataset_dir, "mixtures"), + os.path.join(dataset_dir, "instruments"), + ) + + random.shuffle(filelist) + + if len(val_filelist) == 0: + val_size = int(len(filelist) * val_rate) + train_filelist = filelist[:-val_size] + val_filelist = filelist[-val_size:] + else: + train_filelist = [ + pair for pair in filelist if list(pair) not in val_filelist + ] + elif split_mode == "subdirs": + if len(val_filelist) != 0: + raise ValueError( + "The `val_filelist` option is not available in `subdirs` mode" + ) + + train_filelist = make_pair( + os.path.join(dataset_dir, "training/mixtures"), + os.path.join(dataset_dir, "training/instruments"), + ) + + val_filelist = make_pair( + os.path.join(dataset_dir, "validation/mixtures"), + os.path.join(dataset_dir, "validation/instruments"), + ) + + return train_filelist, val_filelist + + +def augment(X, y, reduction_rate, reduction_mask, mixup_rate, mixup_alpha): + perm = np.random.permutation(len(X)) + for i, idx in enumerate(tqdm(perm)): + if np.random.uniform() < reduction_rate: + y[idx] = spec_utils.reduce_vocal_aggressively( + X[idx], y[idx], reduction_mask + ) + + if np.random.uniform() < 0.5: + # swap channel + X[idx] = X[idx, ::-1] + y[idx] = y[idx, ::-1] + if np.random.uniform() < 0.02: + # mono + X[idx] = X[idx].mean(axis=0, keepdims=True) + y[idx] = y[idx].mean(axis=0, keepdims=True) + if np.random.uniform() < 0.02: + # inst + X[idx] = y[idx] + + if np.random.uniform() < mixup_rate and i < len(perm) - 1: + lam = np.random.beta(mixup_alpha, mixup_alpha) + X[idx] = lam * X[idx] + (1 - lam) * X[perm[i + 1]] + y[idx] = lam * y[idx] + (1 - lam) * y[perm[i + 1]] + + return X, y + + +def make_padding(width, cropsize, offset): + left = offset + roi_size = cropsize - left * 2 + if roi_size == 0: + roi_size = cropsize + right = roi_size - (width % roi_size) + left + + return left, right, roi_size + + +def make_training_set(filelist, cropsize, patches, sr, hop_length, n_fft, offset): + len_dataset = patches * len(filelist) + + X_dataset = np.zeros((len_dataset, 2, n_fft // 2 + 1, cropsize), dtype=np.complex64) + y_dataset = np.zeros((len_dataset, 2, n_fft // 2 + 1, cropsize), dtype=np.complex64) + + for i, (X_path, y_path) in enumerate(tqdm(filelist)): + X, y = spec_utils.cache_or_load(X_path, y_path, sr, hop_length, n_fft) + coef = np.max([np.abs(X).max(), np.abs(y).max()]) + X, y = X / coef, y / coef + + l, r, roi_size = make_padding(X.shape[2], cropsize, offset) + X_pad = np.pad(X, ((0, 0), (0, 0), (l, r)), mode="constant") + y_pad = np.pad(y, ((0, 0), (0, 0), (l, r)), mode="constant") + + starts = np.random.randint(0, X_pad.shape[2] - cropsize, patches) + ends = starts + cropsize + for j in range(patches): + idx = i * patches + j + X_dataset[idx] = X_pad[:, :, starts[j] : ends[j]] + y_dataset[idx] = y_pad[:, :, starts[j] : ends[j]] + + return X_dataset, y_dataset + + +def make_validation_set(filelist, cropsize, sr, hop_length, n_fft, offset): + patch_list = [] + patch_dir = "cs{}_sr{}_hl{}_nf{}_of{}".format( + cropsize, sr, hop_length, n_fft, offset + ) + os.makedirs(patch_dir, exist_ok=True) + + for i, (X_path, y_path) in enumerate(tqdm(filelist)): + basename = os.path.splitext(os.path.basename(X_path))[0] + + X, y = spec_utils.cache_or_load(X_path, y_path, sr, hop_length, n_fft) + coef = np.max([np.abs(X).max(), np.abs(y).max()]) + X, y = X / coef, y / coef + + l, r, roi_size = make_padding(X.shape[2], cropsize, offset) + X_pad = np.pad(X, ((0, 0), (0, 0), (l, r)), mode="constant") + y_pad = np.pad(y, ((0, 0), (0, 0), (l, r)), mode="constant") + + len_dataset = int(np.ceil(X.shape[2] / roi_size)) + for j in range(len_dataset): + outpath = os.path.join(patch_dir, "{}_p{}.npz".format(basename, j)) + start = j * roi_size + if not os.path.exists(outpath): + np.savez( + outpath, + X=X_pad[:, :, start : start + cropsize], + y=y_pad[:, :, start : start + cropsize], + ) + patch_list.append(outpath) + + return VocalRemoverValidationSet(patch_list) diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers.py b/rvc/infer/lib/uvr5_pack/lib_v5/layers.py new file mode 100644 index 0000000..4fc1b5c --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers.py @@ -0,0 +1,118 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class SeperableConv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(SeperableConv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nin, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + groups=nin, + bias=False, + ), + nn.Conv2d(nin, nout, kernel_size=1, bias=False), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, stride, pad, activ=activ) + + def __call__(self, x): + skip = self.conv1(x) + h = self.conv2(skip) + + return h, skip + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + h = self.conv(x) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 16), activ=nn.ReLU): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ) + self.conv3 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = nn.Sequential( + Conv2DBNActiv(nin * 5, nout, 1, 1, 0, activ=activ), nn.Dropout2d(0.1) + ) + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5), dim=1) + bottle = self.bottleneck(out) + return bottle diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers_123812KB .py b/rvc/infer/lib/uvr5_pack/lib_v5/layers_123812KB .py new file mode 100644 index 0000000..4fc1b5c --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers_123812KB .py @@ -0,0 +1,118 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class SeperableConv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(SeperableConv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nin, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + groups=nin, + bias=False, + ), + nn.Conv2d(nin, nout, kernel_size=1, bias=False), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, stride, pad, activ=activ) + + def __call__(self, x): + skip = self.conv1(x) + h = self.conv2(skip) + + return h, skip + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + h = self.conv(x) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 16), activ=nn.ReLU): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ) + self.conv3 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = nn.Sequential( + Conv2DBNActiv(nin * 5, nout, 1, 1, 0, activ=activ), nn.Dropout2d(0.1) + ) + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5), dim=1) + bottle = self.bottleneck(out) + return bottle diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers_123821KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/layers_123821KB.py new file mode 100644 index 0000000..4fc1b5c --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers_123821KB.py @@ -0,0 +1,118 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class SeperableConv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(SeperableConv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nin, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + groups=nin, + bias=False, + ), + nn.Conv2d(nin, nout, kernel_size=1, bias=False), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, stride, pad, activ=activ) + + def __call__(self, x): + skip = self.conv1(x) + h = self.conv2(skip) + + return h, skip + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + h = self.conv(x) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 16), activ=nn.ReLU): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ) + self.conv3 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = nn.Sequential( + Conv2DBNActiv(nin * 5, nout, 1, 1, 0, activ=activ), nn.Dropout2d(0.1) + ) + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5), dim=1) + bottle = self.bottleneck(out) + return bottle diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers_33966KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/layers_33966KB.py new file mode 100644 index 0000000..9b127bc --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers_33966KB.py @@ -0,0 +1,126 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class SeperableConv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(SeperableConv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nin, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + groups=nin, + bias=False, + ), + nn.Conv2d(nin, nout, kernel_size=1, bias=False), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, stride, pad, activ=activ) + + def __call__(self, x): + skip = self.conv1(x) + h = self.conv2(skip) + + return h, skip + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + h = self.conv(x) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 16, 32, 64), activ=nn.ReLU): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ) + self.conv3 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.conv6 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.conv7 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = nn.Sequential( + Conv2DBNActiv(nin * 7, nout, 1, 1, 0, activ=activ), nn.Dropout2d(0.1) + ) + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + feat6 = self.conv6(x) + feat7 = self.conv7(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5, feat6, feat7), dim=1) + bottle = self.bottleneck(out) + return bottle diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers_537227KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/layers_537227KB.py new file mode 100644 index 0000000..9b127bc --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers_537227KB.py @@ -0,0 +1,126 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class SeperableConv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(SeperableConv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nin, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + groups=nin, + bias=False, + ), + nn.Conv2d(nin, nout, kernel_size=1, bias=False), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, stride, pad, activ=activ) + + def __call__(self, x): + skip = self.conv1(x) + h = self.conv2(skip) + + return h, skip + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + h = self.conv(x) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 16, 32, 64), activ=nn.ReLU): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ) + self.conv3 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.conv6 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.conv7 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = nn.Sequential( + Conv2DBNActiv(nin * 7, nout, 1, 1, 0, activ=activ), nn.Dropout2d(0.1) + ) + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + feat6 = self.conv6(x) + feat7 = self.conv7(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5, feat6, feat7), dim=1) + bottle = self.bottleneck(out) + return bottle diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers_537238KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/layers_537238KB.py new file mode 100644 index 0000000..9b127bc --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers_537238KB.py @@ -0,0 +1,126 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class SeperableConv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(SeperableConv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nin, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + groups=nin, + bias=False, + ), + nn.Conv2d(nin, nout, kernel_size=1, bias=False), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, stride, pad, activ=activ) + + def __call__(self, x): + skip = self.conv1(x) + h = self.conv2(skip) + + return h, skip + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + h = self.conv(x) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 16, 32, 64), activ=nn.ReLU): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nin, 1, 1, 0, activ=activ) + self.conv3 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.conv6 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.conv7 = SeperableConv2DBNActiv( + nin, nin, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = nn.Sequential( + Conv2DBNActiv(nin * 7, nout, 1, 1, 0, activ=activ), nn.Dropout2d(0.1) + ) + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + feat6 = self.conv6(x) + feat7 = self.conv7(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5, feat6, feat7), dim=1) + bottle = self.bottleneck(out) + return bottle diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/layers_new.py b/rvc/infer/lib/uvr5_pack/lib_v5/layers_new.py new file mode 100644 index 0000000..44153b6 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/layers_new.py @@ -0,0 +1,125 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class Conv2DBNActiv(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, dilation=1, activ=nn.ReLU): + super(Conv2DBNActiv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d( + nin, + nout, + kernel_size=ksize, + stride=stride, + padding=pad, + dilation=dilation, + bias=False, + ), + nn.BatchNorm2d(nout), + activ(), + ) + + def __call__(self, x): + return self.conv(x) + + +class Encoder(nn.Module): + def __init__(self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.LeakyReLU): + super(Encoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, stride, pad, activ=activ) + self.conv2 = Conv2DBNActiv(nout, nout, ksize, 1, pad, activ=activ) + + def __call__(self, x): + h = self.conv1(x) + h = self.conv2(h) + + return h + + +class Decoder(nn.Module): + def __init__( + self, nin, nout, ksize=3, stride=1, pad=1, activ=nn.ReLU, dropout=False + ): + super(Decoder, self).__init__() + self.conv1 = Conv2DBNActiv(nin, nout, ksize, 1, pad, activ=activ) + # self.conv2 = Conv2DBNActiv(nout, nout, ksize, 1, pad, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def __call__(self, x, skip=None): + x = F.interpolate(x, scale_factor=2, mode="bilinear", align_corners=True) + + if skip is not None: + skip = spec_utils.crop_center(skip, x) + x = torch.cat([x, skip], dim=1) + + h = self.conv1(x) + # h = self.conv2(h) + + if self.dropout is not None: + h = self.dropout(h) + + return h + + +class ASPPModule(nn.Module): + def __init__(self, nin, nout, dilations=(4, 8, 12), activ=nn.ReLU, dropout=False): + super(ASPPModule, self).__init__() + self.conv1 = nn.Sequential( + nn.AdaptiveAvgPool2d((1, None)), + Conv2DBNActiv(nin, nout, 1, 1, 0, activ=activ), + ) + self.conv2 = Conv2DBNActiv(nin, nout, 1, 1, 0, activ=activ) + self.conv3 = Conv2DBNActiv( + nin, nout, 3, 1, dilations[0], dilations[0], activ=activ + ) + self.conv4 = Conv2DBNActiv( + nin, nout, 3, 1, dilations[1], dilations[1], activ=activ + ) + self.conv5 = Conv2DBNActiv( + nin, nout, 3, 1, dilations[2], dilations[2], activ=activ + ) + self.bottleneck = Conv2DBNActiv(nout * 5, nout, 1, 1, 0, activ=activ) + self.dropout = nn.Dropout2d(0.1) if dropout else None + + def forward(self, x): + _, _, h, w = x.size() + feat1 = F.interpolate( + self.conv1(x), size=(h, w), mode="bilinear", align_corners=True + ) + feat2 = self.conv2(x) + feat3 = self.conv3(x) + feat4 = self.conv4(x) + feat5 = self.conv5(x) + out = torch.cat((feat1, feat2, feat3, feat4, feat5), dim=1) + out = self.bottleneck(out) + + if self.dropout is not None: + out = self.dropout(out) + + return out + + +class LSTMModule(nn.Module): + def __init__(self, nin_conv, nin_lstm, nout_lstm): + super(LSTMModule, self).__init__() + self.conv = Conv2DBNActiv(nin_conv, 1, 1, 1, 0) + self.lstm = nn.LSTM( + input_size=nin_lstm, hidden_size=nout_lstm // 2, bidirectional=True + ) + self.dense = nn.Sequential( + nn.Linear(nout_lstm, nin_lstm), nn.BatchNorm1d(nin_lstm), nn.ReLU() + ) + + def forward(self, x): + N, _, nbins, nframes = x.size() + h = self.conv(x)[:, 0] # N, nbins, nframes + h = h.permute(2, 0, 1) # nframes, N, nbins + h, _ = self.lstm(h) + h = self.dense(h.reshape(-1, h.size()[-1])) # nframes * N, nbins + h = h.reshape(nframes, N, 1, nbins) + h = h.permute(1, 2, 3, 0) + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/model_param_init.py b/rvc/infer/lib/uvr5_pack/lib_v5/model_param_init.py new file mode 100644 index 0000000..b995c0b --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/model_param_init.py @@ -0,0 +1,69 @@ +import json +import os +import pathlib + +default_param = {} +default_param["bins"] = 768 +default_param["unstable_bins"] = 9 # training only +default_param["reduction_bins"] = 762 # training only +default_param["sr"] = 44100 +default_param["pre_filter_start"] = 757 +default_param["pre_filter_stop"] = 768 +default_param["band"] = {} + + +default_param["band"][1] = { + "sr": 11025, + "hl": 128, + "n_fft": 960, + "crop_start": 0, + "crop_stop": 245, + "lpf_start": 61, # inference only + "res_type": "polyphase", +} + +default_param["band"][2] = { + "sr": 44100, + "hl": 512, + "n_fft": 1536, + "crop_start": 24, + "crop_stop": 547, + "hpf_start": 81, # inference only + "res_type": "sinc_best", +} + + +def int_keys(d): + r = {} + for k, v in d: + if k.isdigit(): + k = int(k) + r[k] = v + return r + + +class ModelParameters(object): + def __init__(self, config_path=""): + if ".pth" == pathlib.Path(config_path).suffix: + import zipfile + + with zipfile.ZipFile(config_path, "r") as zip: + self.param = json.loads( + zip.read("param.json"), object_pairs_hook=int_keys + ) + elif ".json" == pathlib.Path(config_path).suffix: + with open(config_path, "r") as f: + self.param = json.loads(f.read(), object_pairs_hook=int_keys) + else: + self.param = default_param + + for k in [ + "mid_side", + "mid_side_b", + "mid_side_b2", + "stereo_w", + "stereo_n", + "reverse", + ]: + if not k in self.param: + self.param[k] = False diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr16000_hl512.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr16000_hl512.json new file mode 100644 index 0000000..72cb449 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr16000_hl512.json @@ -0,0 +1,19 @@ +{ + "bins": 1024, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 16000, + "hl": 512, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 1024, + "hpf_start": -1, + "res_type": "sinc_best" + } + }, + "sr": 16000, + "pre_filter_start": 1023, + "pre_filter_stop": 1024 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json new file mode 100644 index 0000000..3c00ecf --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json @@ -0,0 +1,19 @@ +{ + "bins": 1024, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 32000, + "hl": 512, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 1024, + "hpf_start": -1, + "res_type": "kaiser_fast" + } + }, + "sr": 32000, + "pre_filter_start": 1000, + "pre_filter_stop": 1021 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json new file mode 100644 index 0000000..55666ac --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json @@ -0,0 +1,19 @@ +{ + "bins": 1024, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 33075, + "hl": 384, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 1024, + "hpf_start": -1, + "res_type": "sinc_best" + } + }, + "sr": 33075, + "pre_filter_start": 1000, + "pre_filter_stop": 1021 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl1024.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl1024.json new file mode 100644 index 0000000..665abe2 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl1024.json @@ -0,0 +1,19 @@ +{ + "bins": 1024, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 44100, + "hl": 1024, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 1024, + "hpf_start": -1, + "res_type": "sinc_best" + } + }, + "sr": 44100, + "pre_filter_start": 1023, + "pre_filter_stop": 1024 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl256.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl256.json new file mode 100644 index 0000000..0e8b16f --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl256.json @@ -0,0 +1,19 @@ +{ + "bins": 256, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 44100, + "hl": 256, + "n_fft": 512, + "crop_start": 0, + "crop_stop": 256, + "hpf_start": -1, + "res_type": "sinc_best" + } + }, + "sr": 44100, + "pre_filter_start": 256, + "pre_filter_stop": 256 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json new file mode 100644 index 0000000..3b38fca --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json @@ -0,0 +1,19 @@ +{ + "bins": 1024, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 44100, + "hl": 512, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 1024, + "hpf_start": -1, + "res_type": "sinc_best" + } + }, + "sr": 44100, + "pre_filter_start": 1023, + "pre_filter_stop": 1024 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512_cut.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512_cut.json new file mode 100644 index 0000000..630df35 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512_cut.json @@ -0,0 +1,19 @@ +{ + "bins": 1024, + "unstable_bins": 0, + "reduction_bins": 0, + "band": { + "1": { + "sr": 44100, + "hl": 512, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 700, + "hpf_start": -1, + "res_type": "sinc_best" + } + }, + "sr": 44100, + "pre_filter_start": 1023, + "pre_filter_stop": 700 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json new file mode 100644 index 0000000..ab9cf11 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json @@ -0,0 +1,30 @@ +{ + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 705, + "band": { + "1": { + "sr": 6000, + "hl": 66, + "n_fft": 512, + "crop_start": 0, + "crop_stop": 240, + "lpf_start": 60, + "lpf_stop": 118, + "res_type": "sinc_fastest" + }, + "2": { + "sr": 32000, + "hl": 352, + "n_fft": 1024, + "crop_start": 22, + "crop_stop": 505, + "hpf_start": 44, + "hpf_stop": 23, + "res_type": "sinc_medium" + } + }, + "sr": 32000, + "pre_filter_start": 710, + "pre_filter_stop": 731 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_44100_lofi.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_44100_lofi.json new file mode 100644 index 0000000..7faa216 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_44100_lofi.json @@ -0,0 +1,30 @@ +{ + "bins": 512, + "unstable_bins": 7, + "reduction_bins": 510, + "band": { + "1": { + "sr": 11025, + "hl": 160, + "n_fft": 768, + "crop_start": 0, + "crop_stop": 192, + "lpf_start": 41, + "lpf_stop": 139, + "res_type": "sinc_fastest" + }, + "2": { + "sr": 44100, + "hl": 640, + "n_fft": 1024, + "crop_start": 10, + "crop_stop": 320, + "hpf_start": 47, + "hpf_stop": 15, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 510, + "pre_filter_stop": 512 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_48000.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_48000.json new file mode 100644 index 0000000..7e78175 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/2band_48000.json @@ -0,0 +1,30 @@ +{ + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 705, + "band": { + "1": { + "sr": 6000, + "hl": 66, + "n_fft": 512, + "crop_start": 0, + "crop_stop": 240, + "lpf_start": 60, + "lpf_stop": 240, + "res_type": "sinc_fastest" + }, + "2": { + "sr": 48000, + "hl": 528, + "n_fft": 1536, + "crop_start": 22, + "crop_stop": 505, + "hpf_start": 82, + "hpf_stop": 22, + "res_type": "sinc_medium" + } + }, + "sr": 48000, + "pre_filter_start": 710, + "pre_filter_stop": 731 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100.json new file mode 100644 index 0000000..d881d76 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100.json @@ -0,0 +1,42 @@ +{ + "bins": 768, + "unstable_bins": 5, + "reduction_bins": 733, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 768, + "crop_start": 0, + "crop_stop": 278, + "lpf_start": 28, + "lpf_stop": 140, + "res_type": "polyphase" + }, + "2": { + "sr": 22050, + "hl": 256, + "n_fft": 768, + "crop_start": 14, + "crop_stop": 322, + "hpf_start": 70, + "hpf_stop": 14, + "lpf_start": 283, + "lpf_stop": 314, + "res_type": "polyphase" + }, + "3": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 131, + "crop_stop": 313, + "hpf_start": 154, + "hpf_stop": 141, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 757, + "pre_filter_stop": 768 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json new file mode 100644 index 0000000..77ec198 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json @@ -0,0 +1,43 @@ +{ + "mid_side": true, + "bins": 768, + "unstable_bins": 5, + "reduction_bins": 733, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 768, + "crop_start": 0, + "crop_stop": 278, + "lpf_start": 28, + "lpf_stop": 140, + "res_type": "polyphase" + }, + "2": { + "sr": 22050, + "hl": 256, + "n_fft": 768, + "crop_start": 14, + "crop_stop": 322, + "hpf_start": 70, + "hpf_stop": 14, + "lpf_start": 283, + "lpf_stop": 314, + "res_type": "polyphase" + }, + "3": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 131, + "crop_stop": 313, + "hpf_start": 154, + "hpf_stop": 141, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 757, + "pre_filter_stop": 768 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json new file mode 100644 index 0000000..85ee8a7 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json @@ -0,0 +1,43 @@ +{ + "mid_side_b2": true, + "bins": 640, + "unstable_bins": 7, + "reduction_bins": 565, + "band": { + "1": { + "sr": 11025, + "hl": 108, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 187, + "lpf_start": 92, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "2": { + "sr": 22050, + "hl": 216, + "n_fft": 768, + "crop_start": 0, + "crop_stop": 212, + "hpf_start": 68, + "hpf_stop": 34, + "lpf_start": 174, + "lpf_stop": 209, + "res_type": "polyphase" + }, + "3": { + "sr": 44100, + "hl": 432, + "n_fft": 640, + "crop_start": 66, + "crop_stop": 307, + "hpf_start": 86, + "hpf_stop": 72, + "res_type": "kaiser_fast" + } + }, + "sr": 44100, + "pre_filter_start": 639, + "pre_filter_stop": 640 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json new file mode 100644 index 0000000..df12375 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json @@ -0,0 +1,54 @@ +{ + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 668, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 186, + "lpf_start": 37, + "lpf_stop": 73, + "res_type": "polyphase" + }, + "2": { + "sr": 11025, + "hl": 128, + "n_fft": 512, + "crop_start": 4, + "crop_stop": 185, + "hpf_start": 36, + "hpf_stop": 18, + "lpf_start": 93, + "lpf_stop": 185, + "res_type": "polyphase" + }, + "3": { + "sr": 22050, + "hl": 256, + "n_fft": 512, + "crop_start": 46, + "crop_stop": 186, + "hpf_start": 93, + "hpf_stop": 46, + "lpf_start": 164, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 121, + "crop_stop": 382, + "hpf_start": 138, + "hpf_stop": 123, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 740, + "pre_filter_stop": 768 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_mid.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_mid.json new file mode 100644 index 0000000..e91b699 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_mid.json @@ -0,0 +1,55 @@ +{ + "bins": 768, + "unstable_bins": 7, + "mid_side": true, + "reduction_bins": 668, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 186, + "lpf_start": 37, + "lpf_stop": 73, + "res_type": "polyphase" + }, + "2": { + "sr": 11025, + "hl": 128, + "n_fft": 512, + "crop_start": 4, + "crop_stop": 185, + "hpf_start": 36, + "hpf_stop": 18, + "lpf_start": 93, + "lpf_stop": 185, + "res_type": "polyphase" + }, + "3": { + "sr": 22050, + "hl": 256, + "n_fft": 512, + "crop_start": 46, + "crop_stop": 186, + "hpf_start": 93, + "hpf_stop": 46, + "lpf_start": 164, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 121, + "crop_stop": 382, + "hpf_start": 138, + "hpf_stop": 123, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 740, + "pre_filter_stop": 768 +} diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb.json new file mode 100644 index 0000000..f852f28 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb.json @@ -0,0 +1,55 @@ +{ + "mid_side_b": true, + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 668, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 186, + "lpf_start": 37, + "lpf_stop": 73, + "res_type": "polyphase" + }, + "2": { + "sr": 11025, + "hl": 128, + "n_fft": 512, + "crop_start": 4, + "crop_stop": 185, + "hpf_start": 36, + "hpf_stop": 18, + "lpf_start": 93, + "lpf_stop": 185, + "res_type": "polyphase" + }, + "3": { + "sr": 22050, + "hl": 256, + "n_fft": 512, + "crop_start": 46, + "crop_stop": 186, + "hpf_start": 93, + "hpf_stop": 46, + "lpf_start": 164, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 121, + "crop_stop": 382, + "hpf_start": 138, + "hpf_stop": 123, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 740, + "pre_filter_stop": 768 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb2.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb2.json new file mode 100644 index 0000000..f852f28 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb2.json @@ -0,0 +1,55 @@ +{ + "mid_side_b": true, + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 668, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 186, + "lpf_start": 37, + "lpf_stop": 73, + "res_type": "polyphase" + }, + "2": { + "sr": 11025, + "hl": 128, + "n_fft": 512, + "crop_start": 4, + "crop_stop": 185, + "hpf_start": 36, + "hpf_stop": 18, + "lpf_start": 93, + "lpf_stop": 185, + "res_type": "polyphase" + }, + "3": { + "sr": 22050, + "hl": 256, + "n_fft": 512, + "crop_start": 46, + "crop_stop": 186, + "hpf_start": 93, + "hpf_stop": 46, + "lpf_start": 164, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 121, + "crop_stop": 382, + "hpf_start": 138, + "hpf_stop": 123, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 740, + "pre_filter_stop": 768 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_reverse.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_reverse.json new file mode 100644 index 0000000..7a07d55 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_reverse.json @@ -0,0 +1,55 @@ +{ + "reverse": true, + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 668, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 186, + "lpf_start": 37, + "lpf_stop": 73, + "res_type": "polyphase" + }, + "2": { + "sr": 11025, + "hl": 128, + "n_fft": 512, + "crop_start": 4, + "crop_stop": 185, + "hpf_start": 36, + "hpf_stop": 18, + "lpf_start": 93, + "lpf_stop": 185, + "res_type": "polyphase" + }, + "3": { + "sr": 22050, + "hl": 256, + "n_fft": 512, + "crop_start": 46, + "crop_stop": 186, + "hpf_start": 93, + "hpf_stop": 46, + "lpf_start": 164, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 121, + "crop_stop": 382, + "hpf_start": 138, + "hpf_stop": 123, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 740, + "pre_filter_stop": 768 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_sw.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_sw.json new file mode 100644 index 0000000..ba0cf34 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_sw.json @@ -0,0 +1,55 @@ +{ + "stereo_w": true, + "bins": 768, + "unstable_bins": 7, + "reduction_bins": 668, + "band": { + "1": { + "sr": 11025, + "hl": 128, + "n_fft": 1024, + "crop_start": 0, + "crop_stop": 186, + "lpf_start": 37, + "lpf_stop": 73, + "res_type": "polyphase" + }, + "2": { + "sr": 11025, + "hl": 128, + "n_fft": 512, + "crop_start": 4, + "crop_stop": 185, + "hpf_start": 36, + "hpf_stop": 18, + "lpf_start": 93, + "lpf_stop": 185, + "res_type": "polyphase" + }, + "3": { + "sr": 22050, + "hl": 256, + "n_fft": 512, + "crop_start": 46, + "crop_stop": 186, + "hpf_start": 93, + "hpf_stop": 46, + "lpf_start": 164, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 512, + "n_fft": 768, + "crop_start": 121, + "crop_stop": 382, + "hpf_start": 138, + "hpf_stop": 123, + "res_type": "sinc_medium" + } + }, + "sr": 44100, + "pre_filter_start": 740, + "pre_filter_stop": 768 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json new file mode 100644 index 0000000..33281a0 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json @@ -0,0 +1,54 @@ +{ + "bins": 672, + "unstable_bins": 8, + "reduction_bins": 637, + "band": { + "1": { + "sr": 7350, + "hl": 80, + "n_fft": 640, + "crop_start": 0, + "crop_stop": 85, + "lpf_start": 25, + "lpf_stop": 53, + "res_type": "polyphase" + }, + "2": { + "sr": 7350, + "hl": 80, + "n_fft": 320, + "crop_start": 4, + "crop_stop": 87, + "hpf_start": 25, + "hpf_stop": 12, + "lpf_start": 31, + "lpf_stop": 62, + "res_type": "polyphase" + }, + "3": { + "sr": 14700, + "hl": 160, + "n_fft": 512, + "crop_start": 17, + "crop_stop": 216, + "hpf_start": 48, + "hpf_stop": 24, + "lpf_start": 139, + "lpf_stop": 210, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 480, + "n_fft": 960, + "crop_start": 78, + "crop_stop": 383, + "hpf_start": 130, + "hpf_stop": 86, + "res_type": "kaiser_fast" + } + }, + "sr": 44100, + "pre_filter_start": 668, + "pre_filter_stop": 672 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json new file mode 100644 index 0000000..2e5c770 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json @@ -0,0 +1,55 @@ +{ + "bins": 672, + "unstable_bins": 8, + "reduction_bins": 637, + "band": { + "1": { + "sr": 7350, + "hl": 80, + "n_fft": 640, + "crop_start": 0, + "crop_stop": 85, + "lpf_start": 25, + "lpf_stop": 53, + "res_type": "polyphase" + }, + "2": { + "sr": 7350, + "hl": 80, + "n_fft": 320, + "crop_start": 4, + "crop_stop": 87, + "hpf_start": 25, + "hpf_stop": 12, + "lpf_start": 31, + "lpf_stop": 62, + "res_type": "polyphase" + }, + "3": { + "sr": 14700, + "hl": 160, + "n_fft": 512, + "crop_start": 17, + "crop_stop": 216, + "hpf_start": 48, + "hpf_stop": 24, + "lpf_start": 139, + "lpf_stop": 210, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 480, + "n_fft": 960, + "crop_start": 78, + "crop_stop": 383, + "hpf_start": 130, + "hpf_stop": 86, + "convert_channels": "stereo_n", + "res_type": "kaiser_fast" + } + }, + "sr": 44100, + "pre_filter_start": 668, + "pre_filter_stop": 672 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v3.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v3.json new file mode 100644 index 0000000..2a73bc9 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/4band_v3.json @@ -0,0 +1,54 @@ +{ + "bins": 672, + "unstable_bins": 8, + "reduction_bins": 530, + "band": { + "1": { + "sr": 7350, + "hl": 80, + "n_fft": 640, + "crop_start": 0, + "crop_stop": 85, + "lpf_start": 25, + "lpf_stop": 53, + "res_type": "polyphase" + }, + "2": { + "sr": 7350, + "hl": 80, + "n_fft": 320, + "crop_start": 4, + "crop_stop": 87, + "hpf_start": 25, + "hpf_stop": 12, + "lpf_start": 31, + "lpf_stop": 62, + "res_type": "polyphase" + }, + "3": { + "sr": 14700, + "hl": 160, + "n_fft": 512, + "crop_start": 17, + "crop_stop": 216, + "hpf_start": 48, + "hpf_stop": 24, + "lpf_start": 139, + "lpf_stop": 210, + "res_type": "polyphase" + }, + "4": { + "sr": 44100, + "hl": 480, + "n_fft": 960, + "crop_start": 78, + "crop_stop": 383, + "hpf_start": 130, + "hpf_stop": 86, + "res_type": "kaiser_fast" + } + }, + "sr": 44100, + "pre_filter_start": 668, + "pre_filter_stop": 672 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/ensemble.json b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/ensemble.json new file mode 100644 index 0000000..ee69beb --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/modelparams/ensemble.json @@ -0,0 +1,43 @@ +{ + "mid_side_b2": true, + "bins": 1280, + "unstable_bins": 7, + "reduction_bins": 565, + "band": { + "1": { + "sr": 11025, + "hl": 108, + "n_fft": 2048, + "crop_start": 0, + "crop_stop": 374, + "lpf_start": 92, + "lpf_stop": 186, + "res_type": "polyphase" + }, + "2": { + "sr": 22050, + "hl": 216, + "n_fft": 1536, + "crop_start": 0, + "crop_stop": 424, + "hpf_start": 68, + "hpf_stop": 34, + "lpf_start": 348, + "lpf_stop": 418, + "res_type": "polyphase" + }, + "3": { + "sr": 44100, + "hl": 432, + "n_fft": 1280, + "crop_start": 132, + "crop_stop": 614, + "hpf_start": 172, + "hpf_stop": 144, + "res_type": "polyphase" + } + }, + "sr": 44100, + "pre_filter_start": 1280, + "pre_filter_stop": 1280 +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets.py new file mode 100644 index 0000000..5da3948 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets.py @@ -0,0 +1,123 @@ +import layers +import torch +import torch.nn.functional as F +from torch import nn + +from . import spec_utils + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 16) + self.stg1_high_band_net = BaseASPPNet(2, 16) + + self.stg2_bridge = layers.Conv2DBNActiv(18, 8, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(8, 16) + + self.stg3_bridge = layers.Conv2DBNActiv(34, 16, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(16, 32) + + self.out = nn.Conv2d(32, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(16, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(16, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_123812KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_123812KB.py new file mode 100644 index 0000000..167d4cb --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_123812KB.py @@ -0,0 +1,122 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_123821KB as layers + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 32) + self.stg1_high_band_net = BaseASPPNet(2, 32) + + self.stg2_bridge = layers.Conv2DBNActiv(34, 16, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(16, 32) + + self.stg3_bridge = layers.Conv2DBNActiv(66, 32, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(32, 64) + + self.out = nn.Conv2d(64, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(32, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(32, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_123821KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_123821KB.py new file mode 100644 index 0000000..167d4cb --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_123821KB.py @@ -0,0 +1,122 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_123821KB as layers + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 32) + self.stg1_high_band_net = BaseASPPNet(2, 32) + + self.stg2_bridge = layers.Conv2DBNActiv(34, 16, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(16, 32) + + self.stg3_bridge = layers.Conv2DBNActiv(66, 32, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(32, 64) + + self.out = nn.Conv2d(64, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(32, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(32, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_33966KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_33966KB.py new file mode 100644 index 0000000..73a5b83 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_33966KB.py @@ -0,0 +1,122 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_33966KB as layers + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16, 32)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 16) + self.stg1_high_band_net = BaseASPPNet(2, 16) + + self.stg2_bridge = layers.Conv2DBNActiv(18, 8, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(8, 16) + + self.stg3_bridge = layers.Conv2DBNActiv(34, 16, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(16, 32) + + self.out = nn.Conv2d(32, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(16, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(16, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_537227KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_537227KB.py new file mode 100644 index 0000000..823b44f --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_537227KB.py @@ -0,0 +1,123 @@ +import numpy as np +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_537238KB as layers + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 64) + self.stg1_high_band_net = BaseASPPNet(2, 64) + + self.stg2_bridge = layers.Conv2DBNActiv(66, 32, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(32, 64) + + self.stg3_bridge = layers.Conv2DBNActiv(130, 64, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(64, 128) + + self.out = nn.Conv2d(128, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(64, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(64, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_537238KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_537238KB.py new file mode 100644 index 0000000..823b44f --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_537238KB.py @@ -0,0 +1,123 @@ +import numpy as np +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_537238KB as layers + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 64) + self.stg1_high_band_net = BaseASPPNet(2, 64) + + self.stg2_bridge = layers.Conv2DBNActiv(66, 32, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(32, 64) + + self.stg3_bridge = layers.Conv2DBNActiv(130, 64, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(64, 128) + + self.out = nn.Conv2d(128, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(64, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(64, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_61968KB.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_61968KB.py new file mode 100644 index 0000000..167d4cb --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_61968KB.py @@ -0,0 +1,122 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_123821KB as layers + + +class BaseASPPNet(nn.Module): + def __init__(self, nin, ch, dilations=(4, 8, 16)): + super(BaseASPPNet, self).__init__() + self.enc1 = layers.Encoder(nin, ch, 3, 2, 1) + self.enc2 = layers.Encoder(ch, ch * 2, 3, 2, 1) + self.enc3 = layers.Encoder(ch * 2, ch * 4, 3, 2, 1) + self.enc4 = layers.Encoder(ch * 4, ch * 8, 3, 2, 1) + + self.aspp = layers.ASPPModule(ch * 8, ch * 16, dilations) + + self.dec4 = layers.Decoder(ch * (8 + 16), ch * 8, 3, 1, 1) + self.dec3 = layers.Decoder(ch * (4 + 8), ch * 4, 3, 1, 1) + self.dec2 = layers.Decoder(ch * (2 + 4), ch * 2, 3, 1, 1) + self.dec1 = layers.Decoder(ch * (1 + 2), ch, 3, 1, 1) + + def __call__(self, x): + h, e1 = self.enc1(x) + h, e2 = self.enc2(h) + h, e3 = self.enc3(h) + h, e4 = self.enc4(h) + + h = self.aspp(h) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = self.dec1(h, e1) + + return h + + +class CascadedASPPNet(nn.Module): + def __init__(self, n_fft): + super(CascadedASPPNet, self).__init__() + self.stg1_low_band_net = BaseASPPNet(2, 32) + self.stg1_high_band_net = BaseASPPNet(2, 32) + + self.stg2_bridge = layers.Conv2DBNActiv(34, 16, 1, 1, 0) + self.stg2_full_band_net = BaseASPPNet(16, 32) + + self.stg3_bridge = layers.Conv2DBNActiv(66, 32, 1, 1, 0) + self.stg3_full_band_net = BaseASPPNet(32, 64) + + self.out = nn.Conv2d(64, 2, 1, bias=False) + self.aux1_out = nn.Conv2d(32, 2, 1, bias=False) + self.aux2_out = nn.Conv2d(32, 2, 1, bias=False) + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + + self.offset = 128 + + def forward(self, x, aggressiveness=None): + mix = x.detach() + x = x.clone() + + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + aux1 = torch.cat( + [ + self.stg1_low_band_net(x[:, :, :bandw]), + self.stg1_high_band_net(x[:, :, bandw:]), + ], + dim=2, + ) + + h = torch.cat([x, aux1], dim=1) + aux2 = self.stg2_full_band_net(self.stg2_bridge(h)) + + h = torch.cat([x, aux1, aux2], dim=1) + h = self.stg3_full_band_net(self.stg3_bridge(h)) + + mask = torch.sigmoid(self.out(h)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux1 = torch.sigmoid(self.aux1_out(aux1)) + aux1 = F.pad( + input=aux1, + pad=(0, 0, 0, self.output_bin - aux1.size()[2]), + mode="replicate", + ) + aux2 = torch.sigmoid(self.aux2_out(aux2)) + aux2 = F.pad( + input=aux2, + pad=(0, 0, 0, self.output_bin - aux2.size()[2]), + mode="replicate", + ) + return mask * mix, aux1 * mix, aux2 * mix + else: + if aggressiveness: + mask[:, :, : aggressiveness["split_bin"]] = torch.pow( + mask[:, :, : aggressiveness["split_bin"]], + 1 + aggressiveness["value"] / 3, + ) + mask[:, :, aggressiveness["split_bin"] :] = torch.pow( + mask[:, :, aggressiveness["split_bin"] :], + 1 + aggressiveness["value"], + ) + + return mask * mix + + def predict(self, x_mag, aggressiveness=None): + h = self.forward(x_mag, aggressiveness) + + if self.offset > 0: + h = h[:, :, :, self.offset : -self.offset] + assert h.size()[3] > 0 + + return h diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/nets_new.py b/rvc/infer/lib/uvr5_pack/lib_v5/nets_new.py new file mode 100644 index 0000000..1c0f4fa --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/nets_new.py @@ -0,0 +1,133 @@ +import torch +import torch.nn.functional as F +from torch import nn + +from . import layers_new + + +class BaseNet(nn.Module): + def __init__( + self, nin, nout, nin_lstm, nout_lstm, dilations=((4, 2), (8, 4), (12, 6)) + ): + super(BaseNet, self).__init__() + self.enc1 = layers_new.Conv2DBNActiv(nin, nout, 3, 1, 1) + self.enc2 = layers_new.Encoder(nout, nout * 2, 3, 2, 1) + self.enc3 = layers_new.Encoder(nout * 2, nout * 4, 3, 2, 1) + self.enc4 = layers_new.Encoder(nout * 4, nout * 6, 3, 2, 1) + self.enc5 = layers_new.Encoder(nout * 6, nout * 8, 3, 2, 1) + + self.aspp = layers_new.ASPPModule(nout * 8, nout * 8, dilations, dropout=True) + + self.dec4 = layers_new.Decoder(nout * (6 + 8), nout * 6, 3, 1, 1) + self.dec3 = layers_new.Decoder(nout * (4 + 6), nout * 4, 3, 1, 1) + self.dec2 = layers_new.Decoder(nout * (2 + 4), nout * 2, 3, 1, 1) + self.lstm_dec2 = layers_new.LSTMModule(nout * 2, nin_lstm, nout_lstm) + self.dec1 = layers_new.Decoder(nout * (1 + 2) + 1, nout * 1, 3, 1, 1) + + def __call__(self, x): + e1 = self.enc1(x) + e2 = self.enc2(e1) + e3 = self.enc3(e2) + e4 = self.enc4(e3) + e5 = self.enc5(e4) + + h = self.aspp(e5) + + h = self.dec4(h, e4) + h = self.dec3(h, e3) + h = self.dec2(h, e2) + h = torch.cat([h, self.lstm_dec2(h)], dim=1) + h = self.dec1(h, e1) + + return h + + +class CascadedNet(nn.Module): + def __init__(self, n_fft, nout=32, nout_lstm=128): + super(CascadedNet, self).__init__() + + self.max_bin = n_fft // 2 + self.output_bin = n_fft // 2 + 1 + self.nin_lstm = self.max_bin // 2 + self.offset = 64 + + self.stg1_low_band_net = nn.Sequential( + BaseNet(2, nout // 2, self.nin_lstm // 2, nout_lstm), + layers_new.Conv2DBNActiv(nout // 2, nout // 4, 1, 1, 0), + ) + + self.stg1_high_band_net = BaseNet( + 2, nout // 4, self.nin_lstm // 2, nout_lstm // 2 + ) + + self.stg2_low_band_net = nn.Sequential( + BaseNet(nout // 4 + 2, nout, self.nin_lstm // 2, nout_lstm), + layers_new.Conv2DBNActiv(nout, nout // 2, 1, 1, 0), + ) + self.stg2_high_band_net = BaseNet( + nout // 4 + 2, nout // 2, self.nin_lstm // 2, nout_lstm // 2 + ) + + self.stg3_full_band_net = BaseNet( + 3 * nout // 4 + 2, nout, self.nin_lstm, nout_lstm + ) + + self.out = nn.Conv2d(nout, 2, 1, bias=False) + self.aux_out = nn.Conv2d(3 * nout // 4, 2, 1, bias=False) + + def forward(self, x): + x = x[:, :, : self.max_bin] + + bandw = x.size()[2] // 2 + l1_in = x[:, :, :bandw] + h1_in = x[:, :, bandw:] + l1 = self.stg1_low_band_net(l1_in) + h1 = self.stg1_high_band_net(h1_in) + aux1 = torch.cat([l1, h1], dim=2) + + l2_in = torch.cat([l1_in, l1], dim=1) + h2_in = torch.cat([h1_in, h1], dim=1) + l2 = self.stg2_low_band_net(l2_in) + h2 = self.stg2_high_band_net(h2_in) + aux2 = torch.cat([l2, h2], dim=2) + + f3_in = torch.cat([x, aux1, aux2], dim=1) + f3 = self.stg3_full_band_net(f3_in) + + mask = torch.sigmoid(self.out(f3)) + mask = F.pad( + input=mask, + pad=(0, 0, 0, self.output_bin - mask.size()[2]), + mode="replicate", + ) + + if self.training: + aux = torch.cat([aux1, aux2], dim=1) + aux = torch.sigmoid(self.aux_out(aux)) + aux = F.pad( + input=aux, + pad=(0, 0, 0, self.output_bin - aux.size()[2]), + mode="replicate", + ) + return mask, aux + else: + return mask + + def predict_mask(self, x): + mask = self.forward(x) + + if self.offset > 0: + mask = mask[:, :, :, self.offset : -self.offset] + assert mask.size()[3] > 0 + + return mask + + def predict(self, x, aggressiveness=None): + mask = self.forward(x) + pred_mag = x * mask + + if self.offset > 0: + pred_mag = pred_mag[:, :, :, self.offset : -self.offset] + assert pred_mag.size()[3] > 0 + + return pred_mag diff --git a/rvc/infer/lib/uvr5_pack/lib_v5/spec_utils.py b/rvc/infer/lib/uvr5_pack/lib_v5/spec_utils.py new file mode 100644 index 0000000..a9634fd --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/lib_v5/spec_utils.py @@ -0,0 +1,672 @@ +import hashlib +import json +import math +import os + +import librosa +import numpy as np +import soundfile as sf +from tqdm import tqdm + + +def crop_center(h1, h2): + h1_shape = h1.size() + h2_shape = h2.size() + + if h1_shape[3] == h2_shape[3]: + return h1 + elif h1_shape[3] < h2_shape[3]: + raise ValueError("h1_shape[3] must be greater than h2_shape[3]") + + # s_freq = (h2_shape[2] - h1_shape[2]) // 2 + # e_freq = s_freq + h1_shape[2] + s_time = (h1_shape[3] - h2_shape[3]) // 2 + e_time = s_time + h2_shape[3] + h1 = h1[:, :, :, s_time:e_time] + + return h1 + + +def wave_to_spectrogram( + wave, hop_length, n_fft, mid_side=False, mid_side_b2=False, reverse=False +): + if reverse: + wave_left = np.flip(np.asfortranarray(wave[0])) + wave_right = np.flip(np.asfortranarray(wave[1])) + elif mid_side: + wave_left = np.asfortranarray(np.add(wave[0], wave[1]) / 2) + wave_right = np.asfortranarray(np.subtract(wave[0], wave[1])) + elif mid_side_b2: + wave_left = np.asfortranarray(np.add(wave[1], wave[0] * 0.5)) + wave_right = np.asfortranarray(np.subtract(wave[0], wave[1] * 0.5)) + else: + wave_left = np.asfortranarray(wave[0]) + wave_right = np.asfortranarray(wave[1]) + + spec_left = librosa.stft(wave_left, n_fft, hop_length=hop_length) + spec_right = librosa.stft(wave_right, n_fft, hop_length=hop_length) + + spec = np.asfortranarray([spec_left, spec_right]) + + return spec + + +def wave_to_spectrogram_mt( + wave, hop_length, n_fft, mid_side=False, mid_side_b2=False, reverse=False +): + import threading + + if reverse: + wave_left = np.flip(np.asfortranarray(wave[0])) + wave_right = np.flip(np.asfortranarray(wave[1])) + elif mid_side: + wave_left = np.asfortranarray(np.add(wave[0], wave[1]) / 2) + wave_right = np.asfortranarray(np.subtract(wave[0], wave[1])) + elif mid_side_b2: + wave_left = np.asfortranarray(np.add(wave[1], wave[0] * 0.5)) + wave_right = np.asfortranarray(np.subtract(wave[0], wave[1] * 0.5)) + else: + wave_left = np.asfortranarray(wave[0]) + wave_right = np.asfortranarray(wave[1]) + + def run_thread(**kwargs): + global spec_left + spec_left = librosa.stft(**kwargs) + + thread = threading.Thread( + target=run_thread, + kwargs={"y": wave_left, "n_fft": n_fft, "hop_length": hop_length}, + ) + thread.start() + spec_right = librosa.stft(wave_right, n_fft, hop_length=hop_length) + thread.join() + + spec = np.asfortranarray([spec_left, spec_right]) + + return spec + + +def combine_spectrograms(specs, mp): + l = min([specs[i].shape[2] for i in specs]) + spec_c = np.zeros(shape=(2, mp.param["bins"] + 1, l), dtype=np.complex64) + offset = 0 + bands_n = len(mp.param["band"]) + + for d in range(1, bands_n + 1): + h = mp.param["band"][d]["crop_stop"] - mp.param["band"][d]["crop_start"] + spec_c[:, offset : offset + h, :l] = specs[d][ + :, mp.param["band"][d]["crop_start"] : mp.param["band"][d]["crop_stop"], :l + ] + offset += h + + if offset > mp.param["bins"]: + raise ValueError("Too much bins") + + # lowpass fiter + if ( + mp.param["pre_filter_start"] > 0 + ): # and mp.param['band'][bands_n]['res_type'] in ['scipy', 'polyphase']: + if bands_n == 1: + spec_c = fft_lp_filter( + spec_c, mp.param["pre_filter_start"], mp.param["pre_filter_stop"] + ) + else: + gp = 1 + for b in range( + mp.param["pre_filter_start"] + 1, mp.param["pre_filter_stop"] + ): + g = math.pow( + 10, -(b - mp.param["pre_filter_start"]) * (3.5 - gp) / 20.0 + ) + gp = g + spec_c[:, b, :] *= g + + return np.asfortranarray(spec_c) + + +def spectrogram_to_image(spec, mode="magnitude"): + if mode == "magnitude": + if np.iscomplexobj(spec): + y = np.abs(spec) + else: + y = spec + y = np.log10(y**2 + 1e-8) + elif mode == "phase": + if np.iscomplexobj(spec): + y = np.angle(spec) + else: + y = spec + + y -= y.min() + y *= 255 / y.max() + img = np.uint8(y) + + if y.ndim == 3: + img = img.transpose(1, 2, 0) + img = np.concatenate([np.max(img, axis=2, keepdims=True), img], axis=2) + + return img + + +def reduce_vocal_aggressively(X, y, softmask): + v = X - y + y_mag_tmp = np.abs(y) + v_mag_tmp = np.abs(v) + + v_mask = v_mag_tmp > y_mag_tmp + y_mag = np.clip(y_mag_tmp - v_mag_tmp * v_mask * softmask, 0, np.inf) + + return y_mag * np.exp(1.0j * np.angle(y)) + + +def mask_silence(mag, ref, thres=0.2, min_range=64, fade_size=32): + if min_range < fade_size * 2: + raise ValueError("min_range must be >= fade_area * 2") + + mag = mag.copy() + + idx = np.where(ref.mean(axis=(0, 1)) < thres)[0] + starts = np.insert(idx[np.where(np.diff(idx) != 1)[0] + 1], 0, idx[0]) + ends = np.append(idx[np.where(np.diff(idx) != 1)[0]], idx[-1]) + uninformative = np.where(ends - starts > min_range)[0] + if len(uninformative) > 0: + starts = starts[uninformative] + ends = ends[uninformative] + old_e = None + for s, e in zip(starts, ends): + if old_e is not None and s - old_e < fade_size: + s = old_e - fade_size * 2 + + if s != 0: + weight = np.linspace(0, 1, fade_size) + mag[:, :, s : s + fade_size] += weight * ref[:, :, s : s + fade_size] + else: + s -= fade_size + + if e != mag.shape[2]: + weight = np.linspace(1, 0, fade_size) + mag[:, :, e - fade_size : e] += weight * ref[:, :, e - fade_size : e] + else: + e += fade_size + + mag[:, :, s + fade_size : e - fade_size] += ref[ + :, :, s + fade_size : e - fade_size + ] + old_e = e + + return mag + + +def align_wave_head_and_tail(a, b): + l = min([a[0].size, b[0].size]) + + return a[:l, :l], b[:l, :l] + + +def cache_or_load(mix_path, inst_path, mp): + mix_basename = os.path.splitext(os.path.basename(mix_path))[0] + inst_basename = os.path.splitext(os.path.basename(inst_path))[0] + + cache_dir = "mph{}".format( + hashlib.sha1(json.dumps(mp.param, sort_keys=True).encode("utf-8")).hexdigest() + ) + mix_cache_dir = os.path.join("cache", cache_dir) + inst_cache_dir = os.path.join("cache", cache_dir) + + os.makedirs(mix_cache_dir, exist_ok=True) + os.makedirs(inst_cache_dir, exist_ok=True) + + mix_cache_path = os.path.join(mix_cache_dir, mix_basename + ".npy") + inst_cache_path = os.path.join(inst_cache_dir, inst_basename + ".npy") + + if os.path.exists(mix_cache_path) and os.path.exists(inst_cache_path): + X_spec_m = np.load(mix_cache_path) + y_spec_m = np.load(inst_cache_path) + else: + X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {} + + for d in range(len(mp.param["band"]), 0, -1): + bp = mp.param["band"][d] + + if d == len(mp.param["band"]): # high-end band + X_wave[d], _ = librosa.load( + mix_path, bp["sr"], False, dtype=np.float32, res_type=bp["res_type"] + ) + y_wave[d], _ = librosa.load( + inst_path, + bp["sr"], + False, + dtype=np.float32, + res_type=bp["res_type"], + ) + else: # lower bands + X_wave[d] = librosa.resample( + X_wave[d + 1], + mp.param["band"][d + 1]["sr"], + bp["sr"], + res_type=bp["res_type"], + ) + y_wave[d] = librosa.resample( + y_wave[d + 1], + mp.param["band"][d + 1]["sr"], + bp["sr"], + res_type=bp["res_type"], + ) + + X_wave[d], y_wave[d] = align_wave_head_and_tail(X_wave[d], y_wave[d]) + + X_spec_s[d] = wave_to_spectrogram( + X_wave[d], + bp["hl"], + bp["n_fft"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ) + y_spec_s[d] = wave_to_spectrogram( + y_wave[d], + bp["hl"], + bp["n_fft"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ) + + del X_wave, y_wave + + X_spec_m = combine_spectrograms(X_spec_s, mp) + y_spec_m = combine_spectrograms(y_spec_s, mp) + + if X_spec_m.shape != y_spec_m.shape: + raise ValueError("The combined spectrograms are different: " + mix_path) + + _, ext = os.path.splitext(mix_path) + + np.save(mix_cache_path, X_spec_m) + np.save(inst_cache_path, y_spec_m) + + return X_spec_m, y_spec_m + + +def spectrogram_to_wave(spec, hop_length, mid_side, mid_side_b2, reverse): + spec_left = np.asfortranarray(spec[0]) + spec_right = np.asfortranarray(spec[1]) + + wave_left = librosa.istft(spec_left, hop_length=hop_length) + wave_right = librosa.istft(spec_right, hop_length=hop_length) + + if reverse: + return np.asfortranarray([np.flip(wave_left), np.flip(wave_right)]) + elif mid_side: + return np.asfortranarray( + [np.add(wave_left, wave_right / 2), np.subtract(wave_left, wave_right / 2)] + ) + elif mid_side_b2: + return np.asfortranarray( + [ + np.add(wave_right / 1.25, 0.4 * wave_left), + np.subtract(wave_left / 1.25, 0.4 * wave_right), + ] + ) + else: + return np.asfortranarray([wave_left, wave_right]) + + +def spectrogram_to_wave_mt(spec, hop_length, mid_side, reverse, mid_side_b2): + import threading + + spec_left = np.asfortranarray(spec[0]) + spec_right = np.asfortranarray(spec[1]) + + def run_thread(**kwargs): + global wave_left + wave_left = librosa.istft(**kwargs) + + thread = threading.Thread( + target=run_thread, kwargs={"stft_matrix": spec_left, "hop_length": hop_length} + ) + thread.start() + wave_right = librosa.istft(spec_right, hop_length=hop_length) + thread.join() + + if reverse: + return np.asfortranarray([np.flip(wave_left), np.flip(wave_right)]) + elif mid_side: + return np.asfortranarray( + [np.add(wave_left, wave_right / 2), np.subtract(wave_left, wave_right / 2)] + ) + elif mid_side_b2: + return np.asfortranarray( + [ + np.add(wave_right / 1.25, 0.4 * wave_left), + np.subtract(wave_left / 1.25, 0.4 * wave_right), + ] + ) + else: + return np.asfortranarray([wave_left, wave_right]) + + +def cmb_spectrogram_to_wave(spec_m, mp, extra_bins_h=None, extra_bins=None): + wave_band = {} + bands_n = len(mp.param["band"]) + offset = 0 + + for d in range(1, bands_n + 1): + bp = mp.param["band"][d] + spec_s = np.ndarray( + shape=(2, bp["n_fft"] // 2 + 1, spec_m.shape[2]), dtype=complex + ) + h = bp["crop_stop"] - bp["crop_start"] + spec_s[:, bp["crop_start"] : bp["crop_stop"], :] = spec_m[ + :, offset : offset + h, : + ] + + offset += h + if d == bands_n: # higher + if extra_bins_h: # if --high_end_process bypass + max_bin = bp["n_fft"] // 2 + spec_s[:, max_bin - extra_bins_h : max_bin, :] = extra_bins[ + :, :extra_bins_h, : + ] + if bp["hpf_start"] > 0: + spec_s = fft_hp_filter(spec_s, bp["hpf_start"], bp["hpf_stop"] - 1) + if bands_n == 1: + wave = spectrogram_to_wave( + spec_s, + bp["hl"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ) + else: + wave = np.add( + wave, + spectrogram_to_wave( + spec_s, + bp["hl"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ), + ) + else: + sr = mp.param["band"][d + 1]["sr"] + if d == 1: # lower + spec_s = fft_lp_filter(spec_s, bp["lpf_start"], bp["lpf_stop"]) + wave = librosa.resample( + spectrogram_to_wave( + spec_s, + bp["hl"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ), + bp["sr"], + sr, + res_type="sinc_fastest", + ) + else: # mid + spec_s = fft_hp_filter(spec_s, bp["hpf_start"], bp["hpf_stop"] - 1) + spec_s = fft_lp_filter(spec_s, bp["lpf_start"], bp["lpf_stop"]) + wave2 = np.add( + wave, + spectrogram_to_wave( + spec_s, + bp["hl"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ), + ) + # wave = librosa.core.resample(wave2, bp['sr'], sr, res_type="sinc_fastest") + wave = librosa.core.resample(wave2, bp["sr"], sr, res_type="scipy") + + return wave.T + + +def fft_lp_filter(spec, bin_start, bin_stop): + g = 1.0 + for b in range(bin_start, bin_stop): + g -= 1 / (bin_stop - bin_start) + spec[:, b, :] = g * spec[:, b, :] + + spec[:, bin_stop:, :] *= 0 + + return spec + + +def fft_hp_filter(spec, bin_start, bin_stop): + g = 1.0 + for b in range(bin_start, bin_stop, -1): + g -= 1 / (bin_start - bin_stop) + spec[:, b, :] = g * spec[:, b, :] + + spec[:, 0 : bin_stop + 1, :] *= 0 + + return spec + + +def mirroring(a, spec_m, input_high_end, mp): + if "mirroring" == a: + mirror = np.flip( + np.abs( + spec_m[ + :, + mp.param["pre_filter_start"] + - 10 + - input_high_end.shape[1] : mp.param["pre_filter_start"] + - 10, + :, + ] + ), + 1, + ) + mirror = mirror * np.exp(1.0j * np.angle(input_high_end)) + + return np.where( + np.abs(input_high_end) <= np.abs(mirror), input_high_end, mirror + ) + + if "mirroring2" == a: + mirror = np.flip( + np.abs( + spec_m[ + :, + mp.param["pre_filter_start"] + - 10 + - input_high_end.shape[1] : mp.param["pre_filter_start"] + - 10, + :, + ] + ), + 1, + ) + mi = np.multiply(mirror, input_high_end * 1.7) + + return np.where(np.abs(input_high_end) <= np.abs(mi), input_high_end, mi) + + +def ensembling(a, specs): + for i in range(1, len(specs)): + if i == 1: + spec = specs[0] + + ln = min([spec.shape[2], specs[i].shape[2]]) + spec = spec[:, :, :ln] + specs[i] = specs[i][:, :, :ln] + + if "min_mag" == a: + spec = np.where(np.abs(specs[i]) <= np.abs(spec), specs[i], spec) + if "max_mag" == a: + spec = np.where(np.abs(specs[i]) >= np.abs(spec), specs[i], spec) + + return spec + + +def stft(wave, nfft, hl): + wave_left = np.asfortranarray(wave[0]) + wave_right = np.asfortranarray(wave[1]) + spec_left = librosa.stft(wave_left, nfft, hop_length=hl) + spec_right = librosa.stft(wave_right, nfft, hop_length=hl) + spec = np.asfortranarray([spec_left, spec_right]) + + return spec + + +def istft(spec, hl): + spec_left = np.asfortranarray(spec[0]) + spec_right = np.asfortranarray(spec[1]) + + wave_left = librosa.istft(spec_left, hop_length=hl) + wave_right = librosa.istft(spec_right, hop_length=hl) + wave = np.asfortranarray([wave_left, wave_right]) + + +if __name__ == "__main__": + import argparse + import sys + import time + + import cv2 + from model_param_init import ModelParameters + + p = argparse.ArgumentParser() + p.add_argument( + "--algorithm", + "-a", + type=str, + choices=["invert", "invert_p", "min_mag", "max_mag", "deep", "align"], + default="min_mag", + ) + p.add_argument( + "--model_params", + "-m", + type=str, + default=os.path.join("modelparams", "1band_sr44100_hl512.json"), + ) + p.add_argument("--output_name", "-o", type=str, default="output") + p.add_argument("--vocals_only", "-v", action="store_true") + p.add_argument("input", nargs="+") + args = p.parse_args() + + start_time = time.time() + + if args.algorithm.startswith("invert") and len(args.input) != 2: + raise ValueError("There should be two input files.") + + if not args.algorithm.startswith("invert") and len(args.input) < 2: + raise ValueError("There must be at least two input files.") + + wave, specs = {}, {} + mp = ModelParameters(args.model_params) + + for i in range(len(args.input)): + spec = {} + + for d in range(len(mp.param["band"]), 0, -1): + bp = mp.param["band"][d] + + if d == len(mp.param["band"]): # high-end band + wave[d], _ = librosa.load( + args.input[i], + bp["sr"], + False, + dtype=np.float32, + res_type=bp["res_type"], + ) + + if len(wave[d].shape) == 1: # mono to stereo + wave[d] = np.array([wave[d], wave[d]]) + else: # lower bands + wave[d] = librosa.resample( + wave[d + 1], + mp.param["band"][d + 1]["sr"], + bp["sr"], + res_type=bp["res_type"], + ) + + spec[d] = wave_to_spectrogram( + wave[d], + bp["hl"], + bp["n_fft"], + mp.param["mid_side"], + mp.param["mid_side_b2"], + mp.param["reverse"], + ) + + specs[i] = combine_spectrograms(spec, mp) + + del wave + + if args.algorithm == "deep": + d_spec = np.where(np.abs(specs[0]) <= np.abs(spec[1]), specs[0], spec[1]) + v_spec = d_spec - specs[1] + sf.write( + os.path.join("{}.wav".format(args.output_name)), + cmb_spectrogram_to_wave(v_spec, mp), + mp.param["sr"], + ) + + if args.algorithm.startswith("invert"): + ln = min([specs[0].shape[2], specs[1].shape[2]]) + specs[0] = specs[0][:, :, :ln] + specs[1] = specs[1][:, :, :ln] + + if "invert_p" == args.algorithm: + X_mag = np.abs(specs[0]) + y_mag = np.abs(specs[1]) + max_mag = np.where(X_mag >= y_mag, X_mag, y_mag) + v_spec = specs[1] - max_mag * np.exp(1.0j * np.angle(specs[0])) + else: + specs[1] = reduce_vocal_aggressively(specs[0], specs[1], 0.2) + v_spec = specs[0] - specs[1] + + if not args.vocals_only: + X_mag = np.abs(specs[0]) + y_mag = np.abs(specs[1]) + v_mag = np.abs(v_spec) + + X_image = spectrogram_to_image(X_mag) + y_image = spectrogram_to_image(y_mag) + v_image = spectrogram_to_image(v_mag) + + cv2.imwrite("{}_X.png".format(args.output_name), X_image) + cv2.imwrite("{}_y.png".format(args.output_name), y_image) + cv2.imwrite("{}_v.png".format(args.output_name), v_image) + + sf.write( + "{}_X.wav".format(args.output_name), + cmb_spectrogram_to_wave(specs[0], mp), + mp.param["sr"], + ) + sf.write( + "{}_y.wav".format(args.output_name), + cmb_spectrogram_to_wave(specs[1], mp), + mp.param["sr"], + ) + + sf.write( + "{}_v.wav".format(args.output_name), + cmb_spectrogram_to_wave(v_spec, mp), + mp.param["sr"], + ) + else: + if not args.algorithm == "deep": + sf.write( + os.path.join("ensembled", "{}.wav".format(args.output_name)), + cmb_spectrogram_to_wave(ensembling(args.algorithm, specs), mp), + mp.param["sr"], + ) + + if args.algorithm == "align": + trackalignment = [ + { + "file1": '"{}"'.format(args.input[0]), + "file2": '"{}"'.format(args.input[1]), + } + ] + + for i, e in tqdm(enumerate(trackalignment), desc="Performing Alignment..."): + os.system(f"python lib/align_tracks.py {e['file1']} {e['file2']}") + + # print('Total time: {0:.{1}f}s'.format(time.time() - start_time, 1)) diff --git a/rvc/infer/lib/uvr5_pack/name_params.json b/rvc/infer/lib/uvr5_pack/name_params.json new file mode 100644 index 0000000..8ed51a6 --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/name_params.json @@ -0,0 +1,263 @@ +{ + "equivalent" : [ + { + "model_hash_name" : [ + { + "hash_name": "47939caf0cfe52a0e81442b85b971dfd", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100" + }, + { + "hash_name": "4e4ecb9764c50a8c414fee6e10395bbe", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json", + "param_name": "4band_v2" + }, + { + "hash_name": "ca106edd563e034bde0bdec4bb7a4b36", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json", + "param_name": "4band_v2" + }, + { + "hash_name": "e60a1e84803ce4efc0a6551206cc4b71", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100" + }, + { + "hash_name": "a82f14e75892e55e994376edbf0c8435", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100" + }, + { + "hash_name": "6dd9eaa6f0420af9f1d403aaafa4cc06", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json", + "param_name": "4band_v2_sn" + }, + { + "hash_name": "08611fb99bd59eaa79ad27c58d137727", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json", + "param_name": "4band_v2_sn" + }, + { + "hash_name": "5c7bbca45a187e81abbbd351606164e5", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json", + "param_name": "3band_44100_msb2" + }, + { + "hash_name": "d6b2cb685a058a091e5e7098192d3233", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json", + "param_name": "3band_44100_msb2" + }, + { + "hash_name": "c1b9f38170a7c90e96f027992eb7c62b", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100" + }, + { + "hash_name": "c3448ec923fa0edf3d03a19e633faa53", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100" + }, + { + "hash_name": "68aa2c8093d0080704b200d140f59e54", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100.json", + "param_name": "3band_44100" + }, + { + "hash_name": "fdc83be5b798e4bd29fe00fe6600e147", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json", + "param_name": "3band_44100_mid.json" + }, + { + "hash_name": "2ce34bc92fd57f55db16b7a4def3d745", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json", + "param_name": "3band_44100_mid.json" + }, + { + "hash_name": "52fdca89576f06cf4340b74a4730ee5f", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100.json" + }, + { + "hash_name": "41191165b05d38fc77f072fa9e8e8a30", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100.json" + }, + { + "hash_name": "89e83b511ad474592689e562d5b1f80e", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json", + "param_name": "2band_32000.json" + }, + { + "hash_name": "0b954da81d453b716b114d6d7c95177f", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json", + "param_name": "2band_32000.json" + } + + ], + "v4 Models": [ + { + "hash_name": "6a00461c51c2920fd68937d4609ed6c8", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr16000_hl512.json", + "param_name": "1band_sr16000_hl512" + }, + { + "hash_name": "0ab504864d20f1bd378fe9c81ef37140", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json", + "param_name": "1band_sr32000_hl512" + }, + { + "hash_name": "7dd21065bf91c10f7fccb57d7d83b07f", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json", + "param_name": "1band_sr32000_hl512" + }, + { + "hash_name": "80ab74d65e515caa3622728d2de07d23", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json", + "param_name": "1band_sr32000_hl512" + }, + { + "hash_name": "edc115e7fc523245062200c00caa847f", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json", + "param_name": "1band_sr33075_hl384" + }, + { + "hash_name": "28063e9f6ab5b341c5f6d3c67f2045b7", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json", + "param_name": "1band_sr33075_hl384" + }, + { + "hash_name": "b58090534c52cbc3e9b5104bad666ef2", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json", + "param_name": "1band_sr44100_hl512" + }, + { + "hash_name": "0cdab9947f1b0928705f518f3c78ea8f", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json", + "param_name": "1band_sr44100_hl512" + }, + { + "hash_name": "ae702fed0238afb5346db8356fe25f13", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl1024.json", + "param_name": "1band_sr44100_hl1024" + } + ] + } + ], + "User Models" : [ + { + "1 Band": [ + { + "hash_name": "1band_sr16000_hl512", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr16000_hl512.json", + "param_name": "1band_sr16000_hl512" + }, + { + "hash_name": "1band_sr32000_hl512", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr32000_hl512.json", + "param_name": "1band_sr16000_hl512" + }, + { + "hash_name": "1band_sr33075_hl384", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr33075_hl384.json", + "param_name": "1band_sr33075_hl384" + }, + { + "hash_name": "1band_sr44100_hl256", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl256.json", + "param_name": "1band_sr44100_hl256" + }, + { + "hash_name": "1band_sr44100_hl512", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl512.json", + "param_name": "1band_sr44100_hl512" + }, + { + "hash_name": "1band_sr44100_hl1024", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/1band_sr44100_hl1024.json", + "param_name": "1band_sr44100_hl1024" + } + ], + "2 Band": [ + { + "hash_name": "2band_44100_lofi", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/2band_44100_lofi.json", + "param_name": "2band_44100_lofi" + }, + { + "hash_name": "2band_32000", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/2band_32000.json", + "param_name": "2band_32000" + }, + { + "hash_name": "2band_48000", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/2band_48000.json", + "param_name": "2band_48000" + } + ], + "3 Band": [ + { + "hash_name": "3band_44100", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100.json", + "param_name": "3band_44100" + }, + { + "hash_name": "3band_44100_mid", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_mid.json", + "param_name": "3band_44100_mid" + }, + { + "hash_name": "3band_44100_msb2", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/3band_44100_msb2.json", + "param_name": "3band_44100_msb2" + } + ], + "4 Band": [ + { + "hash_name": "4band_44100", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100.json", + "param_name": "4band_44100" + }, + { + "hash_name": "4band_44100_mid", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_mid.json", + "param_name": "4band_44100_mid" + }, + { + "hash_name": "4band_44100_msb", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb.json", + "param_name": "4band_44100_msb" + }, + { + "hash_name": "4band_44100_msb2", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_msb2.json", + "param_name": "4band_44100_msb2" + }, + { + "hash_name": "4band_44100_reverse", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_reverse.json", + "param_name": "4band_44100_reverse" + }, + { + "hash_name": "4band_44100_sw", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_44100_sw.json", + "param_name": "4band_44100_sw" + }, + { + "hash_name": "4band_v2", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json", + "param_name": "4band_v2" + }, + { + "hash_name": "4band_v2_sn", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2_sn.json", + "param_name": "4band_v2_sn" + }, + { + "hash_name": "tmodelparam", + "model_params": "infer/lib/uvr5_pack/lib_v5/modelparams/tmodelparam.json", + "param_name": "User Model Param Set" + } + ] + } + ] +} \ No newline at end of file diff --git a/rvc/infer/lib/uvr5_pack/utils.py b/rvc/infer/lib/uvr5_pack/utils.py new file mode 100644 index 0000000..f4805cd --- /dev/null +++ b/rvc/infer/lib/uvr5_pack/utils.py @@ -0,0 +1,121 @@ +import json + +import numpy as np +import torch +from tqdm import tqdm + + +def load_data(file_name: str = "./infer/lib/uvr5_pack/name_params.json") -> dict: + with open(file_name, "r") as f: + data = json.load(f) + + return data + + +def make_padding(width, cropsize, offset): + left = offset + roi_size = cropsize - left * 2 + if roi_size == 0: + roi_size = cropsize + right = roi_size - (width % roi_size) + left + + return left, right, roi_size + + +def inference(X_spec, device, model, aggressiveness, data): + """ + data : dic configs + """ + + def _execute( + X_mag_pad, roi_size, n_window, device, model, aggressiveness, is_half=True + ): + model.eval() + with torch.no_grad(): + preds = [] + + iterations = [n_window] + + total_iterations = sum(iterations) + for i in tqdm(range(n_window)): + start = i * roi_size + X_mag_window = X_mag_pad[ + None, :, :, start : start + data["window_size"] + ] + X_mag_window = torch.from_numpy(X_mag_window) + if is_half: + X_mag_window = X_mag_window.half() + X_mag_window = X_mag_window.to(device) + + pred = model.predict(X_mag_window, aggressiveness) + + pred = pred.detach().cpu().numpy() + preds.append(pred[0]) + + pred = np.concatenate(preds, axis=2) + return pred + + def preprocess(X_spec): + X_mag = np.abs(X_spec) + X_phase = np.angle(X_spec) + + return X_mag, X_phase + + X_mag, X_phase = preprocess(X_spec) + + coef = X_mag.max() + X_mag_pre = X_mag / coef + + n_frame = X_mag_pre.shape[2] + pad_l, pad_r, roi_size = make_padding(n_frame, data["window_size"], model.offset) + n_window = int(np.ceil(n_frame / roi_size)) + + X_mag_pad = np.pad(X_mag_pre, ((0, 0), (0, 0), (pad_l, pad_r)), mode="constant") + + if list(model.state_dict().values())[0].dtype == torch.float16: + is_half = True + else: + is_half = False + pred = _execute( + X_mag_pad, roi_size, n_window, device, model, aggressiveness, is_half + ) + pred = pred[:, :, :n_frame] + + if data["tta"]: + pad_l += roi_size // 2 + pad_r += roi_size // 2 + n_window += 1 + + X_mag_pad = np.pad(X_mag_pre, ((0, 0), (0, 0), (pad_l, pad_r)), mode="constant") + + pred_tta = _execute( + X_mag_pad, roi_size, n_window, device, model, aggressiveness, is_half + ) + pred_tta = pred_tta[:, :, roi_size // 2 :] + pred_tta = pred_tta[:, :, :n_frame] + + return (pred + pred_tta) * 0.5 * coef, X_mag, np.exp(1.0j * X_phase) + else: + return pred * coef, X_mag, np.exp(1.0j * X_phase) + + +def _get_name_params(model_path, model_hash): + data = load_data() + flag = False + ModelName = model_path + for type in list(data): + for model in list(data[type][0]): + for i in range(len(data[type][0][model])): + if str(data[type][0][model][i]["hash_name"]) == model_hash: + flag = True + elif str(data[type][0][model][i]["hash_name"]) in ModelName: + flag = True + + if flag: + model_params_auto = data[type][0][model][i]["model_params"] + param_name_auto = data[type][0][model][i]["param_name"] + if type == "equivalent": + return param_name_auto, model_params_auto + else: + flag = False + return param_name_auto, model_params_auto diff --git a/rvc/infer/modules/gui/__init__.py b/rvc/infer/modules/gui/__init__.py new file mode 100644 index 0000000..8c1b549 --- /dev/null +++ b/rvc/infer/modules/gui/__init__.py @@ -0,0 +1,13 @@ +""" +TorchGating is a PyTorch-based implementation of Spectral Gating +================================================ +Author: Asaf Zorea + +Contents +-------- +torchgate imports all the functions from PyTorch, and in addition provides: + TorchGating --- A PyTorch module that applies a spectral gate to an input signal + +""" + +from .torchgate import TorchGate diff --git a/rvc/infer/modules/gui/torchgate.py b/rvc/infer/modules/gui/torchgate.py new file mode 100644 index 0000000..e4b80c4 --- /dev/null +++ b/rvc/infer/modules/gui/torchgate.py @@ -0,0 +1,280 @@ +import torch +from infer.lib.rmvpe import STFT +from torch.nn.functional import conv1d, conv2d +from typing import Union, Optional +from .utils import linspace, temperature_sigmoid, amp_to_db + + +class TorchGate(torch.nn.Module): + """ + A PyTorch module that applies a spectral gate to an input signal. + + Arguments: + sr {int} -- Sample rate of the input signal. + nonstationary {bool} -- Whether to use non-stationary or stationary masking (default: {False}). + n_std_thresh_stationary {float} -- Number of standard deviations above mean to threshold noise for + stationary masking (default: {1.5}). + n_thresh_nonstationary {float} -- Number of multiplies above smoothed magnitude spectrogram. for + non-stationary masking (default: {1.3}). + temp_coeff_nonstationary {float} -- Temperature coefficient for non-stationary masking (default: {0.1}). + n_movemean_nonstationary {int} -- Number of samples for moving average smoothing in non-stationary masking + (default: {20}). + prop_decrease {float} -- Proportion to decrease signal by where the mask is zero (default: {1.0}). + n_fft {int} -- Size of FFT for STFT (default: {1024}). + win_length {[int]} -- Window length for STFT. If None, defaults to `n_fft` (default: {None}). + hop_length {[int]} -- Hop length for STFT. If None, defaults to `win_length` // 4 (default: {None}). + freq_mask_smooth_hz {float} -- Frequency smoothing width for mask (in Hz). If None, no smoothing is applied + (default: {500}). + time_mask_smooth_ms {float} -- Time smoothing width for mask (in ms). If None, no smoothing is applied + (default: {50}). + """ + + @torch.no_grad() + def __init__( + self, + sr: int, + nonstationary: bool = False, + n_std_thresh_stationary: float = 1.5, + n_thresh_nonstationary: float = 1.3, + temp_coeff_nonstationary: float = 0.1, + n_movemean_nonstationary: int = 20, + prop_decrease: float = 1.0, + n_fft: int = 1024, + win_length: bool = None, + hop_length: int = None, + freq_mask_smooth_hz: float = 500, + time_mask_smooth_ms: float = 50, + ): + super().__init__() + + # General Params + self.sr = sr + self.nonstationary = nonstationary + assert 0.0 <= prop_decrease <= 1.0 + self.prop_decrease = prop_decrease + + # STFT Params + self.n_fft = n_fft + self.win_length = self.n_fft if win_length is None else win_length + self.hop_length = self.win_length // 4 if hop_length is None else hop_length + + # Stationary Params + self.n_std_thresh_stationary = n_std_thresh_stationary + + # Non-Stationary Params + self.temp_coeff_nonstationary = temp_coeff_nonstationary + self.n_movemean_nonstationary = n_movemean_nonstationary + self.n_thresh_nonstationary = n_thresh_nonstationary + + # Smooth Mask Params + self.freq_mask_smooth_hz = freq_mask_smooth_hz + self.time_mask_smooth_ms = time_mask_smooth_ms + self.register_buffer("smoothing_filter", self._generate_mask_smoothing_filter()) + + @torch.no_grad() + def _generate_mask_smoothing_filter(self) -> Union[torch.Tensor, None]: + """ + A PyTorch module that applies a spectral gate to an input signal using the STFT. + + Returns: + smoothing_filter (torch.Tensor): a 2D tensor representing the smoothing filter, + with shape (n_grad_freq, n_grad_time), where n_grad_freq is the number of frequency + bins to smooth and n_grad_time is the number of time frames to smooth. + If both self.freq_mask_smooth_hz and self.time_mask_smooth_ms are None, returns None. + """ + if self.freq_mask_smooth_hz is None and self.time_mask_smooth_ms is None: + return None + + n_grad_freq = ( + 1 + if self.freq_mask_smooth_hz is None + else int(self.freq_mask_smooth_hz / (self.sr / (self.n_fft / 2))) + ) + if n_grad_freq < 1: + raise ValueError( + f"freq_mask_smooth_hz needs to be at least {int((self.sr / (self._n_fft / 2)))} Hz" + ) + + n_grad_time = ( + 1 + if self.time_mask_smooth_ms is None + else int(self.time_mask_smooth_ms / ((self.hop_length / self.sr) * 1000)) + ) + if n_grad_time < 1: + raise ValueError( + f"time_mask_smooth_ms needs to be at least {int((self.hop_length / self.sr) * 1000)} ms" + ) + + if n_grad_time == 1 and n_grad_freq == 1: + return None + + v_f = torch.cat( + [ + linspace(0, 1, n_grad_freq + 1, endpoint=False), + linspace(1, 0, n_grad_freq + 2), + ] + )[1:-1] + v_t = torch.cat( + [ + linspace(0, 1, n_grad_time + 1, endpoint=False), + linspace(1, 0, n_grad_time + 2), + ] + )[1:-1] + smoothing_filter = torch.outer(v_f, v_t).unsqueeze(0).unsqueeze(0) + + return smoothing_filter / smoothing_filter.sum() + + @torch.no_grad() + def _stationary_mask( + self, X_db: torch.Tensor, xn: Optional[torch.Tensor] = None + ) -> torch.Tensor: + """ + Computes a stationary binary mask to filter out noise in a log-magnitude spectrogram. + + Arguments: + X_db (torch.Tensor): 2D tensor of shape (frames, freq_bins) containing the log-magnitude spectrogram. + xn (torch.Tensor): 1D tensor containing the audio signal corresponding to X_db. + + Returns: + sig_mask (torch.Tensor): Binary mask of the same shape as X_db, where values greater than the threshold + are set to 1, and the rest are set to 0. + """ + if xn is not None: + if "privateuseone" in str(xn.device): + if not hasattr(self, "stft"): + self.stft = STFT( + filter_length=self.n_fft, + hop_length=self.hop_length, + win_length=self.win_length, + window="hann", + ).to(xn.device) + XN = self.stft.transform(xn) + else: + XN = torch.stft( + xn, + n_fft=self.n_fft, + hop_length=self.hop_length, + win_length=self.win_length, + return_complex=True, + pad_mode="constant", + center=True, + window=torch.hann_window(self.win_length).to(xn.device), + ) + XN_db = amp_to_db(XN).to(dtype=X_db.dtype) + else: + XN_db = X_db + + # calculate mean and standard deviation along the frequency axis + std_freq_noise, mean_freq_noise = torch.std_mean(XN_db, dim=-1) + + # compute noise threshold + noise_thresh = mean_freq_noise + std_freq_noise * self.n_std_thresh_stationary + + # create binary mask by thresholding the spectrogram + sig_mask = X_db > noise_thresh.unsqueeze(2) + return sig_mask + + @torch.no_grad() + def _nonstationary_mask(self, X_abs: torch.Tensor) -> torch.Tensor: + """ + Computes a non-stationary binary mask to filter out noise in a log-magnitude spectrogram. + + Arguments: + X_abs (torch.Tensor): 2D tensor of shape (frames, freq_bins) containing the magnitude spectrogram. + + Returns: + sig_mask (torch.Tensor): Binary mask of the same shape as X_abs, where values greater than the threshold + are set to 1, and the rest are set to 0. + """ + X_smoothed = ( + conv1d( + X_abs.reshape(-1, 1, X_abs.shape[-1]), + torch.ones( + self.n_movemean_nonstationary, + dtype=X_abs.dtype, + device=X_abs.device, + ).view(1, 1, -1), + padding="same", + ).view(X_abs.shape) + / self.n_movemean_nonstationary + ) + + # Compute slowness ratio and apply temperature sigmoid + slowness_ratio = (X_abs - X_smoothed) / (X_smoothed + 1e-6) + sig_mask = temperature_sigmoid( + slowness_ratio, self.n_thresh_nonstationary, self.temp_coeff_nonstationary + ) + + return sig_mask + + def forward( + self, x: torch.Tensor, xn: Optional[torch.Tensor] = None + ) -> torch.Tensor: + """ + Apply the proposed algorithm to the input signal. + + Arguments: + x (torch.Tensor): The input audio signal, with shape (batch_size, signal_length). + xn (Optional[torch.Tensor]): The noise signal used for stationary noise reduction. If `None`, the input + signal is used as the noise signal. Default: `None`. + + Returns: + torch.Tensor: The denoised audio signal, with the same shape as the input signal. + """ + + # Compute short-time Fourier transform (STFT) + if "privateuseone" in str(x.device): + if not hasattr(self, "stft"): + self.stft = STFT( + filter_length=self.n_fft, + hop_length=self.hop_length, + win_length=self.win_length, + window="hann", + ).to(x.device) + X, phase = self.stft.transform(x, return_phase=True) + else: + X = torch.stft( + x, + n_fft=self.n_fft, + hop_length=self.hop_length, + win_length=self.win_length, + return_complex=True, + pad_mode="constant", + center=True, + window=torch.hann_window(self.win_length).to(x.device), + ) + + # Compute signal mask based on stationary or nonstationary assumptions + if self.nonstationary: + sig_mask = self._nonstationary_mask(X.abs()) + else: + sig_mask = self._stationary_mask(amp_to_db(X), xn) + + # Propagate decrease in signal power + sig_mask = self.prop_decrease * (sig_mask.float() - 1.0) + 1.0 + + # Smooth signal mask with 2D convolution + if self.smoothing_filter is not None: + sig_mask = conv2d( + sig_mask.unsqueeze(1), + self.smoothing_filter.to(sig_mask.dtype), + padding="same", + ) + + # Apply signal mask to STFT magnitude and phase components + Y = X * sig_mask.squeeze(1) + + # Inverse STFT to obtain time-domain signal + if "privateuseone" in str(Y.device): + y = self.stft.inverse(Y, phase) + else: + y = torch.istft( + Y, + n_fft=self.n_fft, + hop_length=self.hop_length, + win_length=self.win_length, + center=True, + window=torch.hann_window(self.win_length).to(Y.device), + ) + + return y.to(dtype=x.dtype) diff --git a/rvc/infer/modules/gui/utils.py b/rvc/infer/modules/gui/utils.py new file mode 100644 index 0000000..4682098 --- /dev/null +++ b/rvc/infer/modules/gui/utils.py @@ -0,0 +1,70 @@ +import torch +from torch.types import Number + + +@torch.no_grad() +def amp_to_db( + x: torch.Tensor, eps=torch.finfo(torch.float64).eps, top_db=40 +) -> torch.Tensor: + """ + Convert the input tensor from amplitude to decibel scale. + + Arguments: + x {[torch.Tensor]} -- [Input tensor.] + + Keyword Arguments: + eps {[float]} -- [Small value to avoid numerical instability.] + (default: {torch.finfo(torch.float64).eps}) + top_db {[float]} -- [threshold the output at ``top_db`` below the peak] + ` (default: {40}) + + Returns: + [torch.Tensor] -- [Output tensor in decibel scale.] + """ + x_db = 20 * torch.log10(x.abs() + eps) + return torch.max(x_db, (x_db.max(-1).values - top_db).unsqueeze(-1)) + + +@torch.no_grad() +def temperature_sigmoid(x: torch.Tensor, x0: float, temp_coeff: float) -> torch.Tensor: + """ + Apply a sigmoid function with temperature scaling. + + Arguments: + x {[torch.Tensor]} -- [Input tensor.] + x0 {[float]} -- [Parameter that controls the threshold of the sigmoid.] + temp_coeff {[float]} -- [Parameter that controls the slope of the sigmoid.] + + Returns: + [torch.Tensor] -- [Output tensor after applying the sigmoid with temperature scaling.] + """ + return torch.sigmoid((x - x0) / temp_coeff) + + +@torch.no_grad() +def linspace( + start: Number, stop: Number, num: int = 50, endpoint: bool = True, **kwargs +) -> torch.Tensor: + """ + Generate a linearly spaced 1-D tensor. + + Arguments: + start {[Number]} -- [The starting value of the sequence.] + stop {[Number]} -- [The end value of the sequence, unless `endpoint` is set to False. + In that case, the sequence consists of all but the last of ``num + 1`` + evenly spaced samples, so that `stop` is excluded. Note that the step + size changes when `endpoint` is False.] + + Keyword Arguments: + num {[int]} -- [Number of samples to generate. Default is 50. Must be non-negative.] + endpoint {[bool]} -- [If True, `stop` is the last sample. Otherwise, it is not included. + Default is True.] + **kwargs -- [Additional arguments to be passed to the underlying PyTorch `linspace` function.] + + Returns: + [torch.Tensor] -- [1-D tensor of `num` equally spaced samples from `start` to `stop`.] + """ + if endpoint: + return torch.linspace(start, stop, num, **kwargs) + else: + return torch.linspace(start, stop, num + 1, **kwargs)[:-1] diff --git a/rvc/infer/modules/ipex/__init__.py b/rvc/infer/modules/ipex/__init__.py new file mode 100644 index 0000000..cd27bc1 --- /dev/null +++ b/rvc/infer/modules/ipex/__init__.py @@ -0,0 +1,190 @@ +import os +import sys +import contextlib +import torch +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import +from .hijacks import ipex_hijacks +from .attention import attention_init + +# pylint: disable=protected-access, missing-function-docstring, line-too-long + + +def ipex_init(): # pylint: disable=too-many-statements + try: + # Replace cuda with xpu: + torch.cuda.current_device = torch.xpu.current_device + torch.cuda.current_stream = torch.xpu.current_stream + torch.cuda.device = torch.xpu.device + torch.cuda.device_count = torch.xpu.device_count + torch.cuda.device_of = torch.xpu.device_of + torch.cuda.get_device_name = torch.xpu.get_device_name + torch.cuda.get_device_properties = torch.xpu.get_device_properties + torch.cuda.init = torch.xpu.init + torch.cuda.is_available = torch.xpu.is_available + torch.cuda.is_initialized = torch.xpu.is_initialized + torch.cuda.is_current_stream_capturing = lambda: False + torch.cuda.set_device = torch.xpu.set_device + torch.cuda.stream = torch.xpu.stream + torch.cuda.synchronize = torch.xpu.synchronize + torch.cuda.Event = torch.xpu.Event + torch.cuda.Stream = torch.xpu.Stream + torch.cuda.FloatTensor = torch.xpu.FloatTensor + torch.Tensor.cuda = torch.Tensor.xpu + torch.Tensor.is_cuda = torch.Tensor.is_xpu + torch.cuda._initialization_lock = torch.xpu.lazy_init._initialization_lock + torch.cuda._initialized = torch.xpu.lazy_init._initialized + torch.cuda._lazy_seed_tracker = torch.xpu.lazy_init._lazy_seed_tracker + torch.cuda._queued_calls = torch.xpu.lazy_init._queued_calls + torch.cuda._tls = torch.xpu.lazy_init._tls + torch.cuda.threading = torch.xpu.lazy_init.threading + torch.cuda.traceback = torch.xpu.lazy_init.traceback + torch.cuda.Optional = torch.xpu.Optional + torch.cuda.__cached__ = torch.xpu.__cached__ + torch.cuda.__loader__ = torch.xpu.__loader__ + torch.cuda.ComplexFloatStorage = torch.xpu.ComplexFloatStorage + torch.cuda.Tuple = torch.xpu.Tuple + torch.cuda.streams = torch.xpu.streams + torch.cuda._lazy_new = torch.xpu._lazy_new + torch.cuda.FloatStorage = torch.xpu.FloatStorage + torch.cuda.Any = torch.xpu.Any + torch.cuda.__doc__ = torch.xpu.__doc__ + torch.cuda.default_generators = torch.xpu.default_generators + torch.cuda.HalfTensor = torch.xpu.HalfTensor + torch.cuda._get_device_index = torch.xpu._get_device_index + torch.cuda.__path__ = torch.xpu.__path__ + torch.cuda.Device = torch.xpu.Device + torch.cuda.IntTensor = torch.xpu.IntTensor + torch.cuda.ByteStorage = torch.xpu.ByteStorage + torch.cuda.set_stream = torch.xpu.set_stream + torch.cuda.BoolStorage = torch.xpu.BoolStorage + torch.cuda.os = torch.xpu.os + torch.cuda.torch = torch.xpu.torch + torch.cuda.BFloat16Storage = torch.xpu.BFloat16Storage + torch.cuda.Union = torch.xpu.Union + torch.cuda.DoubleTensor = torch.xpu.DoubleTensor + torch.cuda.ShortTensor = torch.xpu.ShortTensor + torch.cuda.LongTensor = torch.xpu.LongTensor + torch.cuda.IntStorage = torch.xpu.IntStorage + torch.cuda.LongStorage = torch.xpu.LongStorage + torch.cuda.__annotations__ = torch.xpu.__annotations__ + torch.cuda.__package__ = torch.xpu.__package__ + torch.cuda.__builtins__ = torch.xpu.__builtins__ + torch.cuda.CharTensor = torch.xpu.CharTensor + torch.cuda.List = torch.xpu.List + torch.cuda._lazy_init = torch.xpu._lazy_init + torch.cuda.BFloat16Tensor = torch.xpu.BFloat16Tensor + torch.cuda.DoubleStorage = torch.xpu.DoubleStorage + torch.cuda.ByteTensor = torch.xpu.ByteTensor + torch.cuda.StreamContext = torch.xpu.StreamContext + torch.cuda.ComplexDoubleStorage = torch.xpu.ComplexDoubleStorage + torch.cuda.ShortStorage = torch.xpu.ShortStorage + torch.cuda._lazy_call = torch.xpu._lazy_call + torch.cuda.HalfStorage = torch.xpu.HalfStorage + torch.cuda.random = torch.xpu.random + torch.cuda._device = torch.xpu._device + torch.cuda.classproperty = torch.xpu.classproperty + torch.cuda.__name__ = torch.xpu.__name__ + torch.cuda._device_t = torch.xpu._device_t + torch.cuda.warnings = torch.xpu.warnings + torch.cuda.__spec__ = torch.xpu.__spec__ + torch.cuda.BoolTensor = torch.xpu.BoolTensor + torch.cuda.CharStorage = torch.xpu.CharStorage + torch.cuda.__file__ = torch.xpu.__file__ + torch.cuda._is_in_bad_fork = torch.xpu.lazy_init._is_in_bad_fork + # torch.cuda.is_current_stream_capturing = torch.xpu.is_current_stream_capturing + + # Memory: + torch.cuda.memory = torch.xpu.memory + if "linux" in sys.platform and "WSL2" in os.popen("uname -a").read(): + torch.xpu.empty_cache = lambda: None + torch.cuda.empty_cache = torch.xpu.empty_cache + torch.cuda.memory_stats = torch.xpu.memory_stats + torch.cuda.memory_summary = torch.xpu.memory_summary + torch.cuda.memory_snapshot = torch.xpu.memory_snapshot + torch.cuda.memory_allocated = torch.xpu.memory_allocated + torch.cuda.max_memory_allocated = torch.xpu.max_memory_allocated + torch.cuda.memory_reserved = torch.xpu.memory_reserved + torch.cuda.memory_cached = torch.xpu.memory_reserved + torch.cuda.max_memory_reserved = torch.xpu.max_memory_reserved + torch.cuda.max_memory_cached = torch.xpu.max_memory_reserved + torch.cuda.reset_peak_memory_stats = torch.xpu.reset_peak_memory_stats + torch.cuda.reset_max_memory_cached = torch.xpu.reset_peak_memory_stats + torch.cuda.reset_max_memory_allocated = torch.xpu.reset_peak_memory_stats + torch.cuda.memory_stats_as_nested_dict = torch.xpu.memory_stats_as_nested_dict + torch.cuda.reset_accumulated_memory_stats = ( + torch.xpu.reset_accumulated_memory_stats + ) + + # RNG: + torch.cuda.get_rng_state = torch.xpu.get_rng_state + torch.cuda.get_rng_state_all = torch.xpu.get_rng_state_all + torch.cuda.set_rng_state = torch.xpu.set_rng_state + torch.cuda.set_rng_state_all = torch.xpu.set_rng_state_all + torch.cuda.manual_seed = torch.xpu.manual_seed + torch.cuda.manual_seed_all = torch.xpu.manual_seed_all + torch.cuda.seed = torch.xpu.seed + torch.cuda.seed_all = torch.xpu.seed_all + torch.cuda.initial_seed = torch.xpu.initial_seed + + # AMP: + torch.cuda.amp = torch.xpu.amp + if not hasattr(torch.cuda.amp, "common"): + torch.cuda.amp.common = contextlib.nullcontext() + torch.cuda.amp.common.amp_definitely_not_available = lambda: False + try: + torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler + except Exception: # pylint: disable=broad-exception-caught + try: + from .gradscaler import ( + gradscaler_init, + ) # pylint: disable=import-outside-toplevel, import-error + + gradscaler_init() + torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler + except Exception: # pylint: disable=broad-exception-caught + torch.cuda.amp.GradScaler = ipex.cpu.autocast._grad_scaler.GradScaler + + # C + torch._C._cuda_getCurrentRawStream = ipex._C._getCurrentStream + ipex._C._DeviceProperties.major = 2023 + ipex._C._DeviceProperties.minor = 2 + + # Fix functions with ipex: + torch.cuda.mem_get_info = lambda device=None: [ + ( + torch.xpu.get_device_properties(device).total_memory + - torch.xpu.memory_allocated(device) + ), + torch.xpu.get_device_properties(device).total_memory, + ] + torch._utils._get_available_device_type = lambda: "xpu" + torch.has_cuda = True + torch.cuda.has_half = True + torch.cuda.is_bf16_supported = lambda *args, **kwargs: True + torch.cuda.is_fp16_supported = lambda *args, **kwargs: True + torch.version.cuda = "11.7" + torch.cuda.get_device_capability = lambda *args, **kwargs: [11, 7] + torch.cuda.get_device_properties.major = 11 + torch.cuda.get_device_properties.minor = 7 + torch.cuda.ipc_collect = lambda *args, **kwargs: None + torch.cuda.utilization = lambda *args, **kwargs: 0 + if hasattr(torch.xpu, "getDeviceIdListForCard"): + torch.cuda.getDeviceIdListForCard = torch.xpu.getDeviceIdListForCard + torch.cuda.get_device_id_list_per_card = torch.xpu.getDeviceIdListForCard + else: + torch.cuda.getDeviceIdListForCard = torch.xpu.get_device_id_list_per_card + torch.cuda.get_device_id_list_per_card = ( + torch.xpu.get_device_id_list_per_card + ) + + ipex_hijacks() + attention_init() + try: + from .diffusers import ipex_diffusers + + ipex_diffusers() + except Exception: # pylint: disable=broad-exception-caught + pass + except Exception as e: + return False, e + return True, None diff --git a/rvc/infer/modules/ipex/attention.py b/rvc/infer/modules/ipex/attention.py new file mode 100644 index 0000000..78a4775 --- /dev/null +++ b/rvc/infer/modules/ipex/attention.py @@ -0,0 +1,218 @@ +import torch +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import + +# pylint: disable=protected-access, missing-function-docstring, line-too-long + +original_torch_bmm = torch.bmm + + +def torch_bmm(input, mat2, *, out=None): + if input.dtype != mat2.dtype: + mat2 = mat2.to(input.dtype) + + # ARC GPUs can't allocate more than 4GB to a single block, Slice it: + batch_size_attention, input_tokens, mat2_shape = ( + input.shape[0], + input.shape[1], + mat2.shape[2], + ) + block_multiply = input.element_size() + slice_block_size = input_tokens * mat2_shape / 1024 / 1024 * block_multiply + block_size = batch_size_attention * slice_block_size + + split_slice_size = batch_size_attention + if block_size > 4: + do_split = True + # Find something divisible with the input_tokens + while (split_slice_size * slice_block_size) > 4: + split_slice_size = split_slice_size // 2 + if split_slice_size <= 1: + split_slice_size = 1 + break + else: + do_split = False + + split_2_slice_size = input_tokens + if split_slice_size * slice_block_size > 4: + slice_block_size2 = split_slice_size * mat2_shape / 1024 / 1024 * block_multiply + do_split_2 = True + # Find something divisible with the input_tokens + while (split_2_slice_size * slice_block_size2) > 4: + split_2_slice_size = split_2_slice_size // 2 + if split_2_slice_size <= 1: + split_2_slice_size = 1 + break + else: + do_split_2 = False + + if do_split: + hidden_states = torch.zeros( + input.shape[0], + input.shape[1], + mat2.shape[2], + device=input.device, + dtype=input.dtype, + ) + for i in range(batch_size_attention // split_slice_size): + start_idx = i * split_slice_size + end_idx = (i + 1) * split_slice_size + if do_split_2: + for i2 in range( + input_tokens // split_2_slice_size + ): # pylint: disable=invalid-name + start_idx_2 = i2 * split_2_slice_size + end_idx_2 = (i2 + 1) * split_2_slice_size + hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = ( + original_torch_bmm( + input[start_idx:end_idx, start_idx_2:end_idx_2], + mat2[start_idx:end_idx, start_idx_2:end_idx_2], + out=out, + ) + ) + else: + hidden_states[start_idx:end_idx] = original_torch_bmm( + input[start_idx:end_idx], mat2[start_idx:end_idx], out=out + ) + else: + return original_torch_bmm(input, mat2, out=out) + return hidden_states + + +original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention + + +def scaled_dot_product_attention( + query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False +): + # ARC GPUs can't allocate more than 4GB to a single block, Slice it: + if len(query.shape) == 3: + batch_size_attention, query_tokens, shape_four = query.shape + shape_one = 1 + no_shape_one = True + else: + shape_one, batch_size_attention, query_tokens, shape_four = query.shape + no_shape_one = False + + block_multiply = query.element_size() + slice_block_size = ( + shape_one * query_tokens * shape_four / 1024 / 1024 * block_multiply + ) + block_size = batch_size_attention * slice_block_size + + split_slice_size = batch_size_attention + if block_size > 4: + do_split = True + # Find something divisible with the shape_one + while (split_slice_size * slice_block_size) > 4: + split_slice_size = split_slice_size // 2 + if split_slice_size <= 1: + split_slice_size = 1 + break + else: + do_split = False + + split_2_slice_size = query_tokens + if split_slice_size * slice_block_size > 4: + slice_block_size2 = ( + shape_one * split_slice_size * shape_four / 1024 / 1024 * block_multiply + ) + do_split_2 = True + # Find something divisible with the batch_size_attention + while (split_2_slice_size * slice_block_size2) > 4: + split_2_slice_size = split_2_slice_size // 2 + if split_2_slice_size <= 1: + split_2_slice_size = 1 + break + else: + do_split_2 = False + + if do_split: + hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype) + for i in range(batch_size_attention // split_slice_size): + start_idx = i * split_slice_size + end_idx = (i + 1) * split_slice_size + if do_split_2: + for i2 in range( + query_tokens // split_2_slice_size + ): # pylint: disable=invalid-name + start_idx_2 = i2 * split_2_slice_size + end_idx_2 = (i2 + 1) * split_2_slice_size + if no_shape_one: + hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = ( + original_scaled_dot_product_attention( + query[start_idx:end_idx, start_idx_2:end_idx_2], + key[start_idx:end_idx, start_idx_2:end_idx_2], + value[start_idx:end_idx, start_idx_2:end_idx_2], + attn_mask=( + attn_mask[start_idx:end_idx, start_idx_2:end_idx_2] + if attn_mask is not None + else attn_mask + ), + dropout_p=dropout_p, + is_causal=is_causal, + ) + ) + else: + hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = ( + original_scaled_dot_product_attention( + query[:, start_idx:end_idx, start_idx_2:end_idx_2], + key[:, start_idx:end_idx, start_idx_2:end_idx_2], + value[:, start_idx:end_idx, start_idx_2:end_idx_2], + attn_mask=( + attn_mask[ + :, start_idx:end_idx, start_idx_2:end_idx_2 + ] + if attn_mask is not None + else attn_mask + ), + dropout_p=dropout_p, + is_causal=is_causal, + ) + ) + else: + if no_shape_one: + hidden_states[start_idx:end_idx] = ( + original_scaled_dot_product_attention( + query[start_idx:end_idx], + key[start_idx:end_idx], + value[start_idx:end_idx], + attn_mask=( + attn_mask[start_idx:end_idx] + if attn_mask is not None + else attn_mask + ), + dropout_p=dropout_p, + is_causal=is_causal, + ) + ) + else: + hidden_states[:, start_idx:end_idx] = ( + original_scaled_dot_product_attention( + query[:, start_idx:end_idx], + key[:, start_idx:end_idx], + value[:, start_idx:end_idx], + attn_mask=( + attn_mask[:, start_idx:end_idx] + if attn_mask is not None + else attn_mask + ), + dropout_p=dropout_p, + is_causal=is_causal, + ) + ) + else: + return original_scaled_dot_product_attention( + query, + key, + value, + attn_mask=attn_mask, + dropout_p=dropout_p, + is_causal=is_causal, + ) + return hidden_states + + +def attention_init(): + # ARC GPUs can't allocate more than 4GB to a single block: + torch.bmm = torch_bmm + torch.nn.functional.scaled_dot_product_attention = scaled_dot_product_attention diff --git a/rvc/infer/modules/ipex/gradscaler.py b/rvc/infer/modules/ipex/gradscaler.py new file mode 100644 index 0000000..7875151 --- /dev/null +++ b/rvc/infer/modules/ipex/gradscaler.py @@ -0,0 +1,187 @@ +from collections import defaultdict +import torch +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import +import intel_extension_for_pytorch._C as core # pylint: disable=import-error, unused-import + +# pylint: disable=protected-access, missing-function-docstring, line-too-long + +OptState = ipex.cpu.autocast._grad_scaler.OptState +_MultiDeviceReplicator = ipex.cpu.autocast._grad_scaler._MultiDeviceReplicator +_refresh_per_optimizer_state = ( + ipex.cpu.autocast._grad_scaler._refresh_per_optimizer_state +) + + +def _unscale_grads_( + self, optimizer, inv_scale, found_inf, allow_fp16 +): # pylint: disable=unused-argument + per_device_inv_scale = _MultiDeviceReplicator(inv_scale) + per_device_found_inf = _MultiDeviceReplicator(found_inf) + + # To set up _amp_foreach_non_finite_check_and_unscale_, split grads by device and dtype. + # There could be hundreds of grads, so we'd like to iterate through them just once. + # However, we don't know their devices or dtypes in advance. + + # https://stackoverflow.com/questions/5029934/defaultdict-of-defaultdict + # Google says mypy struggles with defaultdicts type annotations. + per_device_and_dtype_grads = defaultdict(lambda: defaultdict(list)) # type: ignore[var-annotated] + # sync grad to master weight + if hasattr(optimizer, "sync_grad"): + optimizer.sync_grad() + with torch.no_grad(): + for group in optimizer.param_groups: + for param in group["params"]: + if param.grad is None: + continue + if (not allow_fp16) and param.grad.dtype == torch.float16: + raise ValueError("Attempting to unscale FP16 gradients.") + if param.grad.is_sparse: + # is_coalesced() == False means the sparse grad has values with duplicate indices. + # coalesce() deduplicates indices and adds all values that have the same index. + # For scaled fp16 values, there's a good chance coalescing will cause overflow, + # so we should check the coalesced _values(). + if param.grad.dtype is torch.float16: + param.grad = param.grad.coalesce() + to_unscale = param.grad._values() + else: + to_unscale = param.grad + + # -: is there a way to split by device and dtype without appending in the inner loop? + to_unscale = to_unscale.to("cpu") + per_device_and_dtype_grads[to_unscale.device][to_unscale.dtype].append( + to_unscale + ) + + for _, per_dtype_grads in per_device_and_dtype_grads.items(): + for grads in per_dtype_grads.values(): + core._amp_foreach_non_finite_check_and_unscale_( + grads, + per_device_found_inf.get("cpu"), + per_device_inv_scale.get("cpu"), + ) + + return per_device_found_inf._per_device_tensors + + +def unscale_(self, optimizer): + """ + Divides ("unscales") the optimizer's gradient tensors by the scale factor. + :meth:`unscale_` is optional, serving cases where you need to + :ref:`modify or inspect gradients` + between the backward pass(es) and :meth:`step`. + If :meth:`unscale_` is not called explicitly, gradients will be unscaled automatically during :meth:`step`. + Simple example, using :meth:`unscale_` to enable clipping of unscaled gradients:: + ... + scaler.scale(loss).backward() + scaler.unscale_(optimizer) + torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm) + scaler.step(optimizer) + scaler.update() + Args: + optimizer (torch.optim.Optimizer): Optimizer that owns the gradients to be unscaled. + .. warning:: + :meth:`unscale_` should only be called once per optimizer per :meth:`step` call, + and only after all gradients for that optimizer's assigned parameters have been accumulated. + Calling :meth:`unscale_` twice for a given optimizer between each :meth:`step` triggers a RuntimeError. + .. warning:: + :meth:`unscale_` may unscale sparse gradients out of place, replacing the ``.grad`` attribute. + """ + if not self._enabled: + return + + self._check_scale_growth_tracker("unscale_") + + optimizer_state = self._per_optimizer_states[id(optimizer)] + + if optimizer_state["stage"] is OptState.UNSCALED: # pylint: disable=no-else-raise + raise RuntimeError( + "unscale_() has already been called on this optimizer since the last update()." + ) + elif optimizer_state["stage"] is OptState.STEPPED: + raise RuntimeError("unscale_() is being called after step().") + + # FP32 division can be imprecise for certain compile options, so we carry out the reciprocal in FP64. + assert self._scale is not None + inv_scale = ( + self._scale.to("cpu").double().reciprocal().float().to(self._scale.device) + ) + found_inf = torch.full((1,), 0.0, dtype=torch.float32, device=self._scale.device) + + optimizer_state["found_inf_per_device"] = self._unscale_grads_( + optimizer, inv_scale, found_inf, False + ) + optimizer_state["stage"] = OptState.UNSCALED + + +def update(self, new_scale=None): + """ + Updates the scale factor. + If any optimizer steps were skipped the scale is multiplied by ``backoff_factor`` + to reduce it. If ``growth_interval`` unskipped iterations occurred consecutively, + the scale is multiplied by ``growth_factor`` to increase it. + Passing ``new_scale`` sets the new scale value manually. (``new_scale`` is not + used directly, it's used to fill GradScaler's internal scale tensor. So if + ``new_scale`` was a tensor, later in-place changes to that tensor will not further + affect the scale GradScaler uses internally.) + Args: + new_scale (float or :class:`torch.FloatTensor`, optional, default=None): New scale factor. + .. warning:: + :meth:`update` should only be called at the end of the iteration, after ``scaler.step(optimizer)`` has + been invoked for all optimizers used this iteration. + """ + if not self._enabled: + return + + _scale, _growth_tracker = self._check_scale_growth_tracker("update") + + if new_scale is not None: + # Accept a new user-defined scale. + if isinstance(new_scale, float): + self._scale.fill_(new_scale) # type: ignore[union-attr] + else: + reason = "new_scale should be a float or a 1-element torch.FloatTensor with requires_grad=False." + assert isinstance(new_scale, torch.FloatTensor), reason # type: ignore[attr-defined] + assert new_scale.numel() == 1, reason + assert new_scale.requires_grad is False, reason + self._scale.copy_(new_scale) # type: ignore[union-attr] + else: + # Consume shared inf/nan data collected from optimizers to update the scale. + # If all found_inf tensors are on the same device as self._scale, this operation is asynchronous. + found_infs = [ + found_inf.to(device="cpu", non_blocking=True) + for state in self._per_optimizer_states.values() + for found_inf in state["found_inf_per_device"].values() + ] + + assert len(found_infs) > 0, "No inf checks were recorded prior to update." + + found_inf_combined = found_infs[0] + if len(found_infs) > 1: + for i in range(1, len(found_infs)): + found_inf_combined += found_infs[i] + + to_device = _scale.device + _scale = _scale.to("cpu") + _growth_tracker = _growth_tracker.to("cpu") + + core._amp_update_scale_( + _scale, + _growth_tracker, + found_inf_combined, + self._growth_factor, + self._backoff_factor, + self._growth_interval, + ) + + _scale = _scale.to(to_device) + _growth_tracker = _growth_tracker.to(to_device) + # To prepare for next iteration, clear the data collected from optimizers this iteration. + self._per_optimizer_states = defaultdict(_refresh_per_optimizer_state) + + +def gradscaler_init(): + torch.xpu.amp.GradScaler = ipex.cpu.autocast._grad_scaler.GradScaler + torch.xpu.amp.GradScaler._unscale_grads_ = _unscale_grads_ + torch.xpu.amp.GradScaler.unscale_ = unscale_ + torch.xpu.amp.GradScaler.update = update + return torch.xpu.amp.GradScaler diff --git a/rvc/infer/modules/ipex/hijacks.py b/rvc/infer/modules/ipex/hijacks.py new file mode 100644 index 0000000..fc75f0c --- /dev/null +++ b/rvc/infer/modules/ipex/hijacks.py @@ -0,0 +1,365 @@ +import contextlib +import importlib +import torch +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import + +# pylint: disable=protected-access, missing-function-docstring, line-too-long, unnecessary-lambda, no-else-return + + +class CondFunc: # pylint: disable=missing-class-docstring + def __new__(cls, orig_func, sub_func, cond_func): + self = super(CondFunc, cls).__new__(cls) + if isinstance(orig_func, str): + func_path = orig_func.split(".") + for i in range(len(func_path) - 1, -1, -1): + try: + resolved_obj = importlib.import_module(".".join(func_path[:i])) + break + except ImportError: + pass + for attr_name in func_path[i:-1]: + resolved_obj = getattr(resolved_obj, attr_name) + orig_func = getattr(resolved_obj, func_path[-1]) + setattr( + resolved_obj, + func_path[-1], + lambda *args, **kwargs: self(*args, **kwargs), + ) + self.__init__(orig_func, sub_func, cond_func) + return lambda *args, **kwargs: self(*args, **kwargs) + + def __init__(self, orig_func, sub_func, cond_func): + self.__orig_func = orig_func + self.__sub_func = sub_func + self.__cond_func = cond_func + + def __call__(self, *args, **kwargs): + if not self.__cond_func or self.__cond_func(self.__orig_func, *args, **kwargs): + return self.__sub_func(self.__orig_func, *args, **kwargs) + else: + return self.__orig_func(*args, **kwargs) + + +_utils = torch.utils.data._utils + + +def _shutdown_workers(self): + if ( + torch.utils.data._utils is None + or torch.utils.data._utils.python_exit_status is True + or torch.utils.data._utils.python_exit_status is None + ): + return + if hasattr(self, "_shutdown") and not self._shutdown: + self._shutdown = True + try: + if hasattr(self, "_pin_memory_thread"): + self._pin_memory_thread_done_event.set() + self._worker_result_queue.put((None, None)) + self._pin_memory_thread.join() + self._worker_result_queue.cancel_join_thread() + self._worker_result_queue.close() + self._workers_done_event.set() + for worker_id in range(len(self._workers)): + if self._persistent_workers or self._workers_status[worker_id]: + self._mark_worker_as_unavailable(worker_id, shutdown=True) + for w in self._workers: # pylint: disable=invalid-name + w.join(timeout=torch.utils.data._utils.MP_STATUS_CHECK_INTERVAL) + for q in self._index_queues: # pylint: disable=invalid-name + q.cancel_join_thread() + q.close() + finally: + if self._worker_pids_set: + torch.utils.data._utils.signal_handling._remove_worker_pids(id(self)) + self._worker_pids_set = False + for w in self._workers: # pylint: disable=invalid-name + if w.is_alive(): + w.terminate() + + +class DummyDataParallel( + torch.nn.Module +): # pylint: disable=missing-class-docstring, unused-argument, too-few-public-methods + def __new__( + cls, module, device_ids=None, output_device=None, dim=0 + ): # pylint: disable=unused-argument + if isinstance(device_ids, list) and len(device_ids) > 1: + print("IPEX backend doesn't support DataParallel on multiple XPU devices") + return module.to("xpu") + + +def return_null_context(*args, **kwargs): # pylint: disable=unused-argument + return contextlib.nullcontext() + + +def check_device(device): + return bool( + (isinstance(device, torch.device) and device.type == "cuda") + or (isinstance(device, str) and "cuda" in device) + or isinstance(device, int) + ) + + +def return_xpu(device): + return ( + f"xpu:{device[-1]}" + if isinstance(device, str) and ":" in device + else ( + f"xpu:{device}" + if isinstance(device, int) + else torch.device("xpu") if isinstance(device, torch.device) else "xpu" + ) + ) + + +def ipex_no_cuda(orig_func, *args, **kwargs): + torch.cuda.is_available = lambda: False + orig_func(*args, **kwargs) + torch.cuda.is_available = torch.xpu.is_available + + +original_autocast = torch.autocast + + +def ipex_autocast(*args, **kwargs): + if len(args) > 0 and args[0] == "cuda": + return original_autocast("xpu", *args[1:], **kwargs) + else: + return original_autocast(*args, **kwargs) + + +original_torch_cat = torch.cat + + +def torch_cat(tensor, *args, **kwargs): + if len(tensor) == 3 and ( + tensor[0].dtype != tensor[1].dtype or tensor[2].dtype != tensor[1].dtype + ): + return original_torch_cat( + [tensor[0].to(tensor[1].dtype), tensor[1], tensor[2].to(tensor[1].dtype)], + *args, + **kwargs, + ) + else: + return original_torch_cat(tensor, *args, **kwargs) + + +original_interpolate = torch.nn.functional.interpolate + + +def interpolate( + tensor, + size=None, + scale_factor=None, + mode="nearest", + align_corners=None, + recompute_scale_factor=None, + antialias=False, +): # pylint: disable=too-many-arguments + if antialias or align_corners is not None: + return_device = tensor.device + return_dtype = tensor.dtype + return original_interpolate( + tensor.to("cpu", dtype=torch.float32), + size=size, + scale_factor=scale_factor, + mode=mode, + align_corners=align_corners, + recompute_scale_factor=recompute_scale_factor, + antialias=antialias, + ).to(return_device, dtype=return_dtype) + else: + return original_interpolate( + tensor, + size=size, + scale_factor=scale_factor, + mode=mode, + align_corners=align_corners, + recompute_scale_factor=recompute_scale_factor, + antialias=antialias, + ) + + +original_linalg_solve = torch.linalg.solve + + +def linalg_solve(A, B, *args, **kwargs): # pylint: disable=invalid-name + if A.device != torch.device("cpu") or B.device != torch.device("cpu"): + return_device = A.device + return original_linalg_solve(A.to("cpu"), B.to("cpu"), *args, **kwargs).to( + return_device + ) + else: + return original_linalg_solve(A, B, *args, **kwargs) + + +def ipex_hijacks(): + CondFunc( + "torch.Tensor.to", + lambda orig_func, self, device=None, *args, **kwargs: orig_func( + self, return_xpu(device), *args, **kwargs + ), + lambda orig_func, self, device=None, *args, **kwargs: check_device(device), + ) + CondFunc( + "torch.Tensor.cuda", + lambda orig_func, self, device=None, *args, **kwargs: orig_func( + self, return_xpu(device), *args, **kwargs + ), + lambda orig_func, self, device=None, *args, **kwargs: check_device(device), + ) + CondFunc( + "torch.empty", + lambda orig_func, *args, device=None, **kwargs: orig_func( + *args, device=return_xpu(device), **kwargs + ), + lambda orig_func, *args, device=None, **kwargs: check_device(device), + ) + CondFunc( + "torch.load", + lambda orig_func, *args, map_location=None, **kwargs: orig_func( + *args, return_xpu(map_location), **kwargs + ), + lambda orig_func, *args, map_location=None, **kwargs: map_location is None + or check_device(map_location), + ) + CondFunc( + "torch.randn", + lambda orig_func, *args, device=None, **kwargs: orig_func( + *args, device=return_xpu(device), **kwargs + ), + lambda orig_func, *args, device=None, **kwargs: check_device(device), + ) + CondFunc( + "torch.ones", + lambda orig_func, *args, device=None, **kwargs: orig_func( + *args, device=return_xpu(device), **kwargs + ), + lambda orig_func, *args, device=None, **kwargs: check_device(device), + ) + CondFunc( + "torch.zeros", + lambda orig_func, *args, device=None, **kwargs: orig_func( + *args, device=return_xpu(device), **kwargs + ), + lambda orig_func, *args, device=None, **kwargs: check_device(device), + ) + CondFunc( + "torch.tensor", + lambda orig_func, *args, device=None, **kwargs: orig_func( + *args, device=return_xpu(device), **kwargs + ), + lambda orig_func, *args, device=None, **kwargs: check_device(device), + ) + CondFunc( + "torch.linspace", + lambda orig_func, *args, device=None, **kwargs: orig_func( + *args, device=return_xpu(device), **kwargs + ), + lambda orig_func, *args, device=None, **kwargs: check_device(device), + ) + + CondFunc( + "torch.Generator", + lambda orig_func, device=None: torch.xpu.Generator(device), + lambda orig_func, device=None: device is not None + and device != torch.device("cpu") + and device != "cpu", + ) + + CondFunc( + "torch.batch_norm", + lambda orig_func, input, weight, bias, *args, **kwargs: orig_func( + input, + ( + weight + if weight is not None + else torch.ones(input.size()[1], device=input.device) + ), + ( + bias + if bias is not None + else torch.zeros(input.size()[1], device=input.device) + ), + *args, + **kwargs, + ), + lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu"), + ) + CondFunc( + "torch.instance_norm", + lambda orig_func, input, weight, bias, *args, **kwargs: orig_func( + input, + ( + weight + if weight is not None + else torch.ones(input.size()[1], device=input.device) + ), + ( + bias + if bias is not None + else torch.zeros(input.size()[1], device=input.device) + ), + *args, + **kwargs, + ), + lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu"), + ) + + # Functions with dtype errors: + CondFunc( + "torch.nn.modules.GroupNorm.forward", + lambda orig_func, self, input: orig_func( + self, input.to(self.weight.data.dtype) + ), + lambda orig_func, self, input: input.dtype != self.weight.data.dtype, + ) + CondFunc( + "torch.nn.modules.linear.Linear.forward", + lambda orig_func, self, input: orig_func( + self, input.to(self.weight.data.dtype) + ), + lambda orig_func, self, input: input.dtype != self.weight.data.dtype, + ) + CondFunc( + "torch.nn.modules.conv.Conv2d.forward", + lambda orig_func, self, input: orig_func( + self, input.to(self.weight.data.dtype) + ), + lambda orig_func, self, input: input.dtype != self.weight.data.dtype, + ) + CondFunc( + "torch.nn.functional.layer_norm", + lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs: orig_func( + input.to(weight.data.dtype), normalized_shape, weight, *args, **kwargs + ), + lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs: weight + is not None + and input.dtype != weight.data.dtype, + ) + + # Diffusers Float64 (ARC GPUs doesn't support double or Float64): + if not torch.xpu.has_fp64_dtype(): + CondFunc( + "torch.from_numpy", + lambda orig_func, ndarray: orig_func(ndarray.astype("float32")), + lambda orig_func, ndarray: ndarray.dtype == float, + ) + + # Broken functions when torch.cuda.is_available is True: + CondFunc( + "torch.utils.data.dataloader._BaseDataLoaderIter.__init__", + lambda orig_func, *args, **kwargs: ipex_no_cuda(orig_func, *args, **kwargs), + lambda orig_func, *args, **kwargs: True, + ) + + # Functions that make compile mad with CondFunc: + torch.utils.data.dataloader._MultiProcessingDataLoaderIter._shutdown_workers = ( + _shutdown_workers + ) + torch.nn.DataParallel = DummyDataParallel + torch.autocast = ipex_autocast + torch.cat = torch_cat + torch.linalg.solve = linalg_solve + torch.nn.functional.interpolate = interpolate + torch.backends.cuda.sdp_kernel = return_null_context diff --git a/rvc/infer/modules/onnx/export.py b/rvc/infer/modules/onnx/export.py new file mode 100644 index 0000000..b5ef2f5 --- /dev/null +++ b/rvc/infer/modules/onnx/export.py @@ -0,0 +1,52 @@ +import torch + +from infer.lib.infer_pack.models_onnx import SynthesizerTrnMsNSFsidM + + +def export_onnx(ModelPath, ExportedPath): + cpt = torch.load(ModelPath, map_location="cpu") + cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0] + vec_channels = 256 if cpt.get("version", "v1") == "v1" else 768 + + test_phone = torch.rand(1, 200, vec_channels) # hidden unit + test_phone_lengths = torch.tensor([200]).long() # hidden unit 长度(貌似没啥用) + test_pitch = torch.randint(size=(1, 200), low=5, high=255) # 基频(单位赫兹) + test_pitchf = torch.rand(1, 200) # nsf基频 + test_ds = torch.LongTensor([0]) # 说话人ID + test_rnd = torch.rand(1, 192, 200) # 噪声(加入随机因子) + + device = "cpu" # 导出时设备(不影响使用模型) + + net_g = SynthesizerTrnMsNSFsidM( + *cpt["config"], is_half=False, encoder_dim=vec_channels + ) # fp32导出(C++要支持fp16必须手动将内存重新排列所以暂时不用fp16) + net_g.load_state_dict(cpt["weight"], strict=False) + input_names = ["phone", "phone_lengths", "pitch", "pitchf", "ds", "rnd"] + output_names = [ + "audio", + ] + # net_g.construct_spkmixmap() #多角色混合轨道导出 + torch.onnx.export( + net_g, + ( + test_phone.to(device), + test_phone_lengths.to(device), + test_pitch.to(device), + test_pitchf.to(device), + test_ds.to(device), + test_rnd.to(device), + ), + ExportedPath, + dynamic_axes={ + "phone": [1], + "pitch": [1], + "pitchf": [1], + "rnd": [2], + }, + do_constant_folding=False, + opset_version=17, + verbose=False, + input_names=input_names, + output_names=output_names, + ) + return "Finished" diff --git a/rvc/infer/modules/train/extract/extract_f0_print.py b/rvc/infer/modules/train/extract/extract_f0_print.py new file mode 100644 index 0000000..9d231e4 --- /dev/null +++ b/rvc/infer/modules/train/extract/extract_f0_print.py @@ -0,0 +1,175 @@ +import os +import sys +import traceback + +import parselmouth + +now_dir = os.getcwd() +sys.path.append(now_dir) +import logging + +import numpy as np +import pyworld + +from infer.lib.audio import load_audio + +logging.getLogger("numba").setLevel(logging.WARNING) +from multiprocessing import Process + +exp_dir = sys.argv[1] +f = open("%s/extract_f0_feature.log" % exp_dir, "a+") + + +def printt(strr): + print(strr) + f.write("%s\n" % strr) + f.flush() + + +n_p = int(sys.argv[2]) +f0method = sys.argv[3] + + +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): + 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 == "harvest": + f0, t = pyworld.harvest( + x.astype(np.double), + fs=self.fs, + f0_ceil=self.f0_max, + f0_floor=self.f0_min, + frame_period=1000 * self.hop / self.fs, + ) + f0 = pyworld.stonemask(x.astype(np.double), f0, t, self.fs) + elif f0_method == "dio": + f0, t = pyworld.dio( + x.astype(np.double), + fs=self.fs, + f0_ceil=self.f0_max, + f0_floor=self.f0_min, + frame_period=1000 * self.hop / self.fs, + ) + f0 = pyworld.stonemask(x.astype(np.double), f0, t, self.fs) + elif f0_method == "rmvpe": + if hasattr(self, "model_rmvpe") == False: + from infer.lib.rmvpe import RMVPE + + print("Loading rmvpe model") + self.model_rmvpe = RMVPE( + "assets/rmvpe/rmvpe.pt", is_half=False, device="cpu" + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + 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): + if len(paths) == 0: + printt("no-f0-todo") + else: + printt("todo-f0-%s" % len(paths)) + n = max(len(paths) // 5, 1) # 每个进程最多打印5条 + for idx, (inp_path, opt_path1, opt_path2) in enumerate(paths): + try: + if idx % n == 0: + printt("f0ing,now-%s,all-%s,-%s" % (idx, len(paths), inp_path)) + if ( + os.path.exists(opt_path1 + ".npy") == True + and os.path.exists(opt_path2 + ".npy") == True + ): + continue + featur_pit = self.compute_f0(inp_path, f0_method) + 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 + except: + printt("f0fail-%s-%s-%s" % (idx, inp_path, traceback.format_exc())) + + +if __name__ == "__main__": + # exp_dir=r"E:\codes\py39\dataset\mi-test" + # n_p=16 + # f = open("%s/log_extract_f0.log"%exp_dir, "w") + printt(" ".join(sys.argv)) + 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) + paths.append([inp_path, opt_path1, opt_path2]) + + ps = [] + for i in range(n_p): + p = Process( + target=featureInput.go, + args=( + paths[i::n_p], + f0method, + ), + ) + ps.append(p) + p.start() + for i in range(n_p): + ps[i].join() diff --git a/rvc/infer/modules/train/extract/extract_f0_rmvpe.py b/rvc/infer/modules/train/extract/extract_f0_rmvpe.py new file mode 100644 index 0000000..90b2073 --- /dev/null +++ b/rvc/infer/modules/train/extract/extract_f0_rmvpe.py @@ -0,0 +1,141 @@ +import os +import sys +import traceback + +import parselmouth + +now_dir = os.getcwd() +sys.path.append(now_dir) +import logging + +import numpy as np +import pyworld + +from infer.lib.audio import load_audio + +logging.getLogger("numba").setLevel(logging.WARNING) + +n_part = int(sys.argv[1]) +i_part = int(sys.argv[2]) +i_gpu = sys.argv[3] +os.environ["CUDA_VISIBLE_DEVICES"] = str(i_gpu) +exp_dir = sys.argv[4] +is_half = sys.argv[5] +f = open("%s/extract_f0_feature.log" % exp_dir, "a+") + + +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): + x = load_audio(path, self.fs) + # p_len = x.shape[0] // self.hop + if f0_method == "rmvpe": + if hasattr(self, "model_rmvpe") == False: + from infer.lib.rmvpe import RMVPE + + print("Loading rmvpe model") + self.model_rmvpe = RMVPE( + "assets/rmvpe/rmvpe.pt", is_half=is_half, device="cuda" + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + 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): + if len(paths) == 0: + printt("no-f0-todo") + else: + printt("todo-f0-%s" % len(paths)) + n = max(len(paths) // 5, 1) # 每个进程最多打印5条 + for idx, (inp_path, opt_path1, opt_path2) in enumerate(paths): + try: + if idx % n == 0: + printt("f0ing,now-%s,all-%s,-%s" % (idx, len(paths), inp_path)) + if ( + os.path.exists(opt_path1 + ".npy") == True + and os.path.exists(opt_path2 + ".npy") == True + ): + continue + featur_pit = self.compute_f0(inp_path, f0_method) + 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 + except: + printt("f0fail-%s-%s-%s" % (idx, inp_path, traceback.format_exc())) + + +if __name__ == "__main__": + # exp_dir=r"E:\codes\py39\dataset\mi-test" + # n_p=16 + # f = open("%s/log_extract_f0.log"%exp_dir, "w") + printt(" ".join(sys.argv)) + 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) + paths.append([inp_path, opt_path1, opt_path2]) + try: + featureInput.go(paths[i_part::n_part], "rmvpe") + except: + printt("f0_all_fail-%s" % (traceback.format_exc())) + # ps = [] + # for i in range(n_p): + # p = Process( + # target=featureInput.go, + # args=( + # paths[i::n_p], + # f0method, + # ), + # ) + # ps.append(p) + # p.start() + # for i in range(n_p): + # ps[i].join() diff --git a/rvc/infer/modules/train/extract/extract_f0_rmvpe_dml.py b/rvc/infer/modules/train/extract/extract_f0_rmvpe_dml.py new file mode 100644 index 0000000..243e825 --- /dev/null +++ b/rvc/infer/modules/train/extract/extract_f0_rmvpe_dml.py @@ -0,0 +1,139 @@ +import os +import sys +import traceback + +import parselmouth + +now_dir = os.getcwd() +sys.path.append(now_dir) +import logging + +import numpy as np +import pyworld + +from infer.lib.audio import load_audio + +logging.getLogger("numba").setLevel(logging.WARNING) + +exp_dir = sys.argv[1] +import torch_directml + +device = torch_directml.device(torch_directml.default_device()) +f = open("%s/extract_f0_feature.log" % exp_dir, "a+") + + +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): + x = load_audio(path, self.fs) + # p_len = x.shape[0] // self.hop + if f0_method == "rmvpe": + if hasattr(self, "model_rmvpe") == False: + from infer.lib.rmvpe import RMVPE + + print("Loading rmvpe model") + self.model_rmvpe = RMVPE( + "assets/rmvpe/rmvpe.pt", is_half=False, device=device + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + 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): + if len(paths) == 0: + printt("no-f0-todo") + else: + printt("todo-f0-%s" % len(paths)) + n = max(len(paths) // 5, 1) # 每个进程最多打印5条 + for idx, (inp_path, opt_path1, opt_path2) in enumerate(paths): + try: + if idx % n == 0: + printt("f0ing,now-%s,all-%s,-%s" % (idx, len(paths), inp_path)) + if ( + os.path.exists(opt_path1 + ".npy") == True + and os.path.exists(opt_path2 + ".npy") == True + ): + continue + featur_pit = self.compute_f0(inp_path, f0_method) + 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 + except: + printt("f0fail-%s-%s-%s" % (idx, inp_path, traceback.format_exc())) + + +if __name__ == "__main__": + # exp_dir=r"E:\codes\py39\dataset\mi-test" + # n_p=16 + # f = open("%s/log_extract_f0.log"%exp_dir, "w") + printt(" ".join(sys.argv)) + 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) + paths.append([inp_path, opt_path1, opt_path2]) + try: + featureInput.go(paths, "rmvpe") + except: + printt("f0_all_fail-%s" % (traceback.format_exc())) + # ps = [] + # for i in range(n_p): + # p = Process( + # target=featureInput.go, + # args=( + # paths[i::n_p], + # f0method, + # ), + # ) + # ps.append(p) + # p.start() + # for i in range(n_p): + # ps[i].join() diff --git a/rvc/infer/modules/train/extract_feature_print.py b/rvc/infer/modules/train/extract_feature_print.py new file mode 100644 index 0000000..96a69de --- /dev/null +++ b/rvc/infer/modules/train/extract_feature_print.py @@ -0,0 +1,142 @@ +import os +import sys +import traceback + +os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" +os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.0" + +device = sys.argv[1] +n_part = int(sys.argv[2]) +i_part = int(sys.argv[3]) +if len(sys.argv) == 7: + exp_dir = sys.argv[4] + version = sys.argv[5] + is_half = sys.argv[6].lower() == "true" +else: + i_gpu = sys.argv[4] + exp_dir = sys.argv[5] + os.environ["CUDA_VISIBLE_DEVICES"] = str(i_gpu) + version = sys.argv[6] + is_half = sys.argv[7].lower() == "true" +import fairseq +import numpy as np +import soundfile as sf +import torch +import torch.nn.functional as F + +if "privateuseone" not in device: + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + elif torch.backends.mps.is_available(): + device = "mps" +else: + import torch_directml + + device = torch_directml.device(torch_directml.default_device()) + + def forward_dml(ctx, x, scale): + ctx.scale = scale + res = x.clone().detach() + return res + + fairseq.modules.grad_multiply.GradMultiply.forward = forward_dml + +f = open("%s/extract_f0_feature.log" % exp_dir, "a+") + + +def printt(strr): + print(strr) + f.write("%s\n" % strr) + f.flush() + + +printt(" ".join(sys.argv)) +model_path = "assets/hubert/hubert_base.pt" + +printt("exp_dir: " + exp_dir) +wavPath = "%s/1_16k_wavs" % exp_dir +outPath = ( + "%s/3_feature256" % exp_dir if version == "v1" else "%s/3_feature768" % exp_dir +) +os.makedirs(outPath, exist_ok=True) + + +# wave must be 16k, hop_size=320 +def readwave(wav_path, normalize=False): + wav, sr = sf.read(wav_path) + assert sr == 16000 + feats = torch.from_numpy(wav).float() + if feats.dim() == 2: # double channels + feats = feats.mean(-1) + assert feats.dim() == 1, feats.dim() + if normalize: + with torch.no_grad(): + feats = F.layer_norm(feats, feats.shape) + feats = feats.view(1, -1) + return feats + + +# HuBERT model +printt("load model(s) from {}".format(model_path)) +# if hubert model is exist +if os.access(model_path, os.F_OK) == False: + printt( + "Error: Extracting is shut down because %s does not exist, you may download it from https://huggingface.co/lj1995/VoiceConversionWebUI/tree/main" + % model_path + ) + exit(0) +models, saved_cfg, task = fairseq.checkpoint_utils.load_model_ensemble_and_task( + [model_path], + suffix="", +) +model = models[0] +model = model.to(device) +printt("move model to %s" % device) +if is_half: + if device not in ["mps", "cpu"]: + model = model.half() +model.eval() + +todo = sorted(list(os.listdir(wavPath)))[i_part::n_part] +n = max(1, len(todo) // 10) # 最多打印十条 +if len(todo) == 0: + printt("no-feature-todo") +else: + printt("all-feature-%s" % len(todo)) + for idx, file in enumerate(todo): + try: + if file.endswith(".wav"): + wav_path = "%s/%s" % (wavPath, file) + out_path = "%s/%s" % (outPath, file.replace("wav", "npy")) + + if os.path.exists(out_path): + continue + + feats = readwave(wav_path, normalize=saved_cfg.task.normalize) + padding_mask = torch.BoolTensor(feats.shape).fill_(False) + inputs = { + "source": ( + feats.half().to(device) + if is_half and device not in ["mps", "cpu"] + else feats.to(device) + ), + "padding_mask": padding_mask.to(device), + "output_layer": 9 if version == "v1" else 12, # layer 9 + } + with torch.no_grad(): + logits = model.extract_features(**inputs) + feats = ( + model.final_proj(logits[0]) if version == "v1" else logits[0] + ) + + feats = feats.squeeze(0).float().cpu().numpy() + if np.isnan(feats).sum() == 0: + np.save(out_path, feats, allow_pickle=False) + else: + printt("%s-contains nan" % file) + if idx % n == 0: + printt("now-%s,all-%s,%s,%s" % (len(todo), idx, file, feats.shape)) + except: + printt(traceback.format_exc()) + printt("all-feature-done") diff --git a/rvc/infer/modules/train/preprocess.py b/rvc/infer/modules/train/preprocess.py new file mode 100644 index 0000000..138bb4c --- /dev/null +++ b/rvc/infer/modules/train/preprocess.py @@ -0,0 +1,142 @@ +import multiprocessing +import os +import sys + +from scipy import signal + +now_dir = os.getcwd() +sys.path.append(now_dir) +print(*sys.argv[1:]) +inp_root = sys.argv[1] +sr = int(sys.argv[2]) +n_p = int(sys.argv[3]) +exp_dir = sys.argv[4] +noparallel = sys.argv[5] == "True" +per = float(sys.argv[6]) +import os +import traceback + +import librosa +import numpy as np +from scipy.io import wavfile + +from infer.lib.audio import load_audio +from infer.lib.slicer2 import Slicer + +f = open("%s/preprocess.log" % exp_dir, "a+") + + +def println(strr): + print(strr) + f.write("%s\n" % strr) + f.flush() + + +class PreProcess: + def __init__(self, sr, exp_dir, per=3.7): + self.slicer = Slicer( + sr=sr, + threshold=-42, + min_length=1500, + min_interval=400, + hop_size=15, + max_sil_kept=500, + ) + self.sr = sr + self.bh, self.ah = signal.butter(N=5, Wn=48, btype="high", fs=self.sr) + self.per = per + self.overlap = 0.3 + self.tail = self.per + self.overlap + self.max = 0.9 + self.alpha = 0.75 + self.exp_dir = exp_dir + self.gt_wavs_dir = "%s/0_gt_wavs" % exp_dir + self.wavs16k_dir = "%s/1_16k_wavs" % exp_dir + os.makedirs(self.exp_dir, exist_ok=True) + os.makedirs(self.gt_wavs_dir, exist_ok=True) + os.makedirs(self.wavs16k_dir, exist_ok=True) + + def norm_write(self, tmp_audio, idx0, idx1): + tmp_max = np.abs(tmp_audio).max() + if tmp_max > 2.5: + print("%s-%s-%s-filtered" % (idx0, idx1, tmp_max)) + return + tmp_audio = (tmp_audio / tmp_max * (self.max * self.alpha)) + ( + 1 - self.alpha + ) * tmp_audio + wavfile.write( + "%s/%s_%s.wav" % (self.gt_wavs_dir, idx0, idx1), + self.sr, + tmp_audio.astype(np.float32), + ) + tmp_audio = librosa.resample( + tmp_audio, orig_sr=self.sr, target_sr=16000 + ) # , res_type="soxr_vhq" + wavfile.write( + "%s/%s_%s.wav" % (self.wavs16k_dir, idx0, idx1), + 16000, + tmp_audio.astype(np.float32), + ) + + def pipeline(self, path, idx0): + try: + audio = load_audio(path, self.sr) + # zero phased digital filter cause pre-ringing noise... + # audio = signal.filtfilt(self.bh, self.ah, audio) + audio = signal.lfilter(self.bh, self.ah, audio) + + idx1 = 0 + for audio in self.slicer.slice(audio): + i = 0 + while 1: + start = int(self.sr * (self.per - self.overlap) * i) + i += 1 + if len(audio[start:]) > self.tail * self.sr: + tmp_audio = audio[start : start + int(self.per * self.sr)] + self.norm_write(tmp_audio, idx0, idx1) + idx1 += 1 + else: + tmp_audio = audio[start:] + idx1 += 1 + break + self.norm_write(tmp_audio, idx0, idx1) + println("%s\t-> Success" % path) + except: + println("%s\t-> %s" % (path, traceback.format_exc())) + + def pipeline_mp(self, infos): + for path, idx0 in infos: + self.pipeline(path, idx0) + + def pipeline_mp_inp_dir(self, inp_root, n_p): + try: + infos = [ + ("%s/%s" % (inp_root, name), idx) + for idx, name in enumerate(sorted(list(os.listdir(inp_root)))) + ] + if noparallel: + for i in range(n_p): + self.pipeline_mp(infos[i::n_p]) + else: + ps = [] + for i in range(n_p): + p = multiprocessing.Process( + target=self.pipeline_mp, args=(infos[i::n_p],) + ) + ps.append(p) + p.start() + for i in range(n_p): + ps[i].join() + except: + println("Fail. %s" % traceback.format_exc()) + + +def preprocess_trainset(inp_root, sr, n_p, exp_dir, per): + pp = PreProcess(sr, exp_dir, per) + println("start preprocess") + pp.pipeline_mp_inp_dir(inp_root, n_p) + println("end preprocess") + + +if __name__ == "__main__": + preprocess_trainset(inp_root, sr, n_p, exp_dir, per) diff --git a/rvc/infer/modules/train/train.py b/rvc/infer/modules/train/train.py new file mode 100644 index 0000000..38a5678 --- /dev/null +++ b/rvc/infer/modules/train/train.py @@ -0,0 +1,640 @@ +import os +import sys +import logging + +logger = logging.getLogger(__name__) + +now_dir = os.getcwd() +sys.path.append(os.path.join(now_dir)) + +import datetime + +from infer.lib.train import utils + +hps = utils.get_hparams() +os.environ["CUDA_VISIBLE_DEVICES"] = hps.gpus.replace("-", ",") +n_gpus = len(hps.gpus.split("-")) +from random import randint, shuffle + +import torch + +try: + import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import + + if torch.xpu.is_available(): + from infer.modules.ipex import ipex_init + from infer.modules.ipex.gradscaler import gradscaler_init + from torch.xpu.amp import autocast + + GradScaler = gradscaler_init() + ipex_init() + else: + from torch.cuda.amp import GradScaler, autocast +except Exception: + from torch.cuda.amp import GradScaler, autocast + +torch.backends.cudnn.deterministic = False +torch.backends.cudnn.benchmark = False +from time import sleep +from time import time as ttime + +import torch.distributed as dist +import torch.multiprocessing as mp +from torch.nn import functional as F +from torch.nn.parallel import DistributedDataParallel as DDP +from torch.utils.data import DataLoader +from torch.utils.tensorboard import SummaryWriter + +from infer.lib.infer_pack import commons +from infer.lib.train.data_utils import ( + DistributedBucketSampler, + TextAudioCollate, + TextAudioCollateMultiNSFsid, + TextAudioLoader, + TextAudioLoaderMultiNSFsid, +) + +if hps.version == "v1": + from infer.lib.infer_pack.models import MultiPeriodDiscriminator + from infer.lib.infer_pack.models import SynthesizerTrnMs256NSFsid as RVC_Model_f0 + from infer.lib.infer_pack.models import ( + SynthesizerTrnMs256NSFsid_nono as RVC_Model_nof0, + ) +else: + from infer.lib.infer_pack.models import ( + SynthesizerTrnMs768NSFsid as RVC_Model_f0, + SynthesizerTrnMs768NSFsid_nono as RVC_Model_nof0, + MultiPeriodDiscriminatorV2 as MultiPeriodDiscriminator, + ) + +from infer.lib.train.losses import ( + discriminator_loss, + feature_loss, + generator_loss, + kl_loss, +) +from infer.lib.train.mel_processing import mel_spectrogram_torch, spec_to_mel_torch +from infer.lib.train.process_ckpt import savee + +global_step = 0 + + +class EpochRecorder: + def __init__(self): + self.last_time = ttime() + + def record(self): + now_time = ttime() + elapsed_time = now_time - self.last_time + self.last_time = now_time + elapsed_time_str = str(datetime.timedelta(seconds=elapsed_time)) + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + return f"[{current_time}] | ({elapsed_time_str})" + + +def main(): + n_gpus = torch.cuda.device_count() + + if torch.cuda.is_available() == False and torch.backends.mps.is_available() == True: + n_gpus = 1 + if n_gpus < 1: + # patch to unblock people without gpus. there is probably a better way. + print("NO GPU DETECTED: falling back to CPU - this may take a while") + n_gpus = 1 + os.environ["MASTER_ADDR"] = "localhost" + os.environ["MASTER_PORT"] = str(randint(20000, 55555)) + children = [] + logger = utils.get_logger(hps.model_dir) + for i in range(n_gpus): + subproc = mp.Process( + target=run, + args=(i, n_gpus, hps, logger), + ) + children.append(subproc) + subproc.start() + + for i in range(n_gpus): + children[i].join() + + +def run(rank, n_gpus, hps, logger: logging.Logger): + global global_step + if rank == 0: + # logger = utils.get_logger(hps.model_dir) + logger.info(hps) + # utils.check_git_hash(hps.model_dir) + writer = SummaryWriter(log_dir=hps.model_dir) + writer_eval = SummaryWriter(log_dir=os.path.join(hps.model_dir, "eval")) + + dist.init_process_group( + backend="gloo", init_method="env://", world_size=n_gpus, rank=rank + ) + torch.manual_seed(hps.train.seed) + if torch.cuda.is_available(): + torch.cuda.set_device(rank) + + if hps.if_f0 == 1: + train_dataset = TextAudioLoaderMultiNSFsid(hps.data.training_files, hps.data) + else: + train_dataset = TextAudioLoader(hps.data.training_files, hps.data) + train_sampler = DistributedBucketSampler( + train_dataset, + hps.train.batch_size * n_gpus, + # [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,1400], # 16s + [100, 200, 300, 400, 500, 600, 700, 800, 900], # 16s + num_replicas=n_gpus, + rank=rank, + shuffle=True, + ) + # It is possible that dataloader's workers are out of shared memory. Please try to raise your shared memory limit. + # num_workers=8 -> num_workers=4 + if hps.if_f0 == 1: + collate_fn = TextAudioCollateMultiNSFsid() + else: + collate_fn = TextAudioCollate() + train_loader = DataLoader( + train_dataset, + num_workers=4, + shuffle=False, + pin_memory=True, + collate_fn=collate_fn, + batch_sampler=train_sampler, + persistent_workers=True, + prefetch_factor=8, + ) + if hps.if_f0 == 1: + net_g = RVC_Model_f0( + hps.data.filter_length // 2 + 1, + hps.train.segment_size // hps.data.hop_length, + **hps.model, + is_half=hps.train.fp16_run, + sr=hps.sample_rate, + ) + else: + net_g = RVC_Model_nof0( + hps.data.filter_length // 2 + 1, + hps.train.segment_size // hps.data.hop_length, + **hps.model, + is_half=hps.train.fp16_run, + ) + if torch.cuda.is_available(): + net_g = net_g.cuda(rank) + net_d = MultiPeriodDiscriminator(hps.model.use_spectral_norm) + if torch.cuda.is_available(): + net_d = net_d.cuda(rank) + optim_g = torch.optim.AdamW( + net_g.parameters(), + hps.train.learning_rate, + betas=hps.train.betas, + eps=hps.train.eps, + ) + optim_d = torch.optim.AdamW( + net_d.parameters(), + hps.train.learning_rate, + betas=hps.train.betas, + eps=hps.train.eps, + ) + # net_g = DDP(net_g, device_ids=[rank], find_unused_parameters=True) + # net_d = DDP(net_d, device_ids=[rank], find_unused_parameters=True) + if hasattr(torch, "xpu") and torch.xpu.is_available(): + pass + elif torch.cuda.is_available(): + net_g = DDP(net_g, device_ids=[rank]) + net_d = DDP(net_d, device_ids=[rank]) + else: + net_g = DDP(net_g) + net_d = DDP(net_d) + + try: # 如果能加载自动resume + _, _, _, epoch_str = utils.load_checkpoint( + utils.latest_checkpoint_path(hps.model_dir, "D_*.pth"), net_d, optim_d + ) # D多半加载没事 + if rank == 0: + logger.info("loaded D") + # _, _, _, epoch_str = utils.load_checkpoint(utils.latest_checkpoint_path(hps.model_dir, "G_*.pth"), net_g, optim_g,load_opt=0) + _, _, _, epoch_str = utils.load_checkpoint( + utils.latest_checkpoint_path(hps.model_dir, "G_*.pth"), net_g, optim_g + ) + global_step = (epoch_str - 1) * len(train_loader) + # epoch_str = 1 + # global_step = 0 + except: # 如果首次不能加载,加载pretrain + # traceback.print_exc() + epoch_str = 1 + global_step = 0 + if hps.pretrainG != "": + if rank == 0: + logger.info("loaded pretrained %s" % (hps.pretrainG)) + if hasattr(net_g, "module"): + logger.info( + net_g.module.load_state_dict( + torch.load(hps.pretrainG, map_location="cpu")["model"] + ) + ) ##测试不加载优化器 + else: + logger.info( + net_g.load_state_dict( + torch.load(hps.pretrainG, map_location="cpu")["model"] + ) + ) ##测试不加载优化器 + if hps.pretrainD != "": + if rank == 0: + logger.info("loaded pretrained %s" % (hps.pretrainD)) + if hasattr(net_d, "module"): + logger.info( + net_d.module.load_state_dict( + torch.load(hps.pretrainD, map_location="cpu")["model"] + ) + ) + else: + logger.info( + net_d.load_state_dict( + torch.load(hps.pretrainD, map_location="cpu")["model"] + ) + ) + + scheduler_g = torch.optim.lr_scheduler.ExponentialLR( + optim_g, gamma=hps.train.lr_decay, last_epoch=epoch_str - 2 + ) + scheduler_d = torch.optim.lr_scheduler.ExponentialLR( + optim_d, gamma=hps.train.lr_decay, last_epoch=epoch_str - 2 + ) + + scaler = GradScaler(enabled=hps.train.fp16_run) + + cache = [] + for epoch in range(epoch_str, hps.train.epochs + 1): + if rank == 0: + train_and_evaluate( + rank, + epoch, + hps, + [net_g, net_d], + [optim_g, optim_d], + [scheduler_g, scheduler_d], + scaler, + [train_loader, None], + logger, + [writer, writer_eval], + cache, + ) + else: + train_and_evaluate( + rank, + epoch, + hps, + [net_g, net_d], + [optim_g, optim_d], + [scheduler_g, scheduler_d], + scaler, + [train_loader, None], + None, + None, + cache, + ) + scheduler_g.step() + scheduler_d.step() + + +def train_and_evaluate( + rank, epoch, hps, nets, optims, schedulers, scaler, loaders, logger, writers, cache +): + net_g, net_d = nets + optim_g, optim_d = optims + train_loader, eval_loader = loaders + if writers is not None: + writer, writer_eval = writers + + train_loader.batch_sampler.set_epoch(epoch) + global global_step + + net_g.train() + net_d.train() + + # Prepare data iterator + if hps.if_cache_data_in_gpu == True: + # Use Cache + data_iterator = cache + if cache == []: + # Make new cache + for batch_idx, info in enumerate(train_loader): + # Unpack + if hps.if_f0 == 1: + ( + phone, + phone_lengths, + pitch, + pitchf, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ) = info + else: + ( + phone, + phone_lengths, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ) = info + # Load on CUDA + if torch.cuda.is_available(): + phone = phone.cuda(rank, non_blocking=True) + phone_lengths = phone_lengths.cuda(rank, non_blocking=True) + if hps.if_f0 == 1: + pitch = pitch.cuda(rank, non_blocking=True) + pitchf = pitchf.cuda(rank, non_blocking=True) + sid = sid.cuda(rank, non_blocking=True) + spec = spec.cuda(rank, non_blocking=True) + spec_lengths = spec_lengths.cuda(rank, non_blocking=True) + wave = wave.cuda(rank, non_blocking=True) + wave_lengths = wave_lengths.cuda(rank, non_blocking=True) + # Cache on list + if hps.if_f0 == 1: + cache.append( + ( + batch_idx, + ( + phone, + phone_lengths, + pitch, + pitchf, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ), + ) + ) + else: + cache.append( + ( + batch_idx, + ( + phone, + phone_lengths, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ), + ) + ) + else: + # Load shuffled cache + shuffle(cache) + else: + # Loader + data_iterator = enumerate(train_loader) + + # Run steps + epoch_recorder = EpochRecorder() + for batch_idx, info in data_iterator: + # Data + ## Unpack + if hps.if_f0 == 1: + ( + phone, + phone_lengths, + pitch, + pitchf, + spec, + spec_lengths, + wave, + wave_lengths, + sid, + ) = info + else: + phone, phone_lengths, spec, spec_lengths, wave, wave_lengths, sid = info + ## Load on CUDA + if (hps.if_cache_data_in_gpu == False) and torch.cuda.is_available(): + phone = phone.cuda(rank, non_blocking=True) + phone_lengths = phone_lengths.cuda(rank, non_blocking=True) + if hps.if_f0 == 1: + pitch = pitch.cuda(rank, non_blocking=True) + pitchf = pitchf.cuda(rank, non_blocking=True) + sid = sid.cuda(rank, non_blocking=True) + spec = spec.cuda(rank, non_blocking=True) + spec_lengths = spec_lengths.cuda(rank, non_blocking=True) + wave = wave.cuda(rank, non_blocking=True) + # wave_lengths = wave_lengths.cuda(rank, non_blocking=True) + + # Calculate + with autocast(enabled=hps.train.fp16_run): + if hps.if_f0 == 1: + ( + y_hat, + ids_slice, + x_mask, + z_mask, + (z, z_p, m_p, logs_p, m_q, logs_q), + ) = net_g(phone, phone_lengths, pitch, pitchf, spec, spec_lengths, sid) + else: + ( + y_hat, + ids_slice, + x_mask, + z_mask, + (z, z_p, m_p, logs_p, m_q, logs_q), + ) = net_g(phone, phone_lengths, spec, spec_lengths, sid) + mel = spec_to_mel_torch( + spec, + hps.data.filter_length, + hps.data.n_mel_channels, + hps.data.sampling_rate, + hps.data.mel_fmin, + hps.data.mel_fmax, + ) + y_mel = commons.slice_segments( + mel, ids_slice, hps.train.segment_size // hps.data.hop_length + ) + with autocast(enabled=False): + y_hat_mel = mel_spectrogram_torch( + y_hat.float().squeeze(1), + hps.data.filter_length, + hps.data.n_mel_channels, + hps.data.sampling_rate, + hps.data.hop_length, + hps.data.win_length, + hps.data.mel_fmin, + hps.data.mel_fmax, + ) + if hps.train.fp16_run == True: + y_hat_mel = y_hat_mel.half() + wave = commons.slice_segments( + wave, ids_slice * hps.data.hop_length, hps.train.segment_size + ) # slice + + # Discriminator + y_d_hat_r, y_d_hat_g, _, _ = net_d(wave, y_hat.detach()) + with autocast(enabled=False): + loss_disc, losses_disc_r, losses_disc_g = discriminator_loss( + y_d_hat_r, y_d_hat_g + ) + optim_d.zero_grad() + scaler.scale(loss_disc).backward() + scaler.unscale_(optim_d) + grad_norm_d = commons.clip_grad_value_(net_d.parameters(), None) + scaler.step(optim_d) + + with autocast(enabled=hps.train.fp16_run): + # Generator + y_d_hat_r, y_d_hat_g, fmap_r, fmap_g = net_d(wave, y_hat) + with autocast(enabled=False): + loss_mel = F.l1_loss(y_mel, y_hat_mel) * hps.train.c_mel + loss_kl = kl_loss(z_p, logs_q, m_p, logs_p, z_mask) * hps.train.c_kl + loss_fm = feature_loss(fmap_r, fmap_g) + loss_gen, losses_gen = generator_loss(y_d_hat_g) + loss_gen_all = loss_gen + loss_fm + loss_mel + loss_kl + optim_g.zero_grad() + scaler.scale(loss_gen_all).backward() + scaler.unscale_(optim_g) + grad_norm_g = commons.clip_grad_value_(net_g.parameters(), None) + scaler.step(optim_g) + scaler.update() + + if rank == 0: + if global_step % hps.train.log_interval == 0: + lr = optim_g.param_groups[0]["lr"] + logger.info( + "Train Epoch: {} [{:.0f}%]".format( + epoch, 100.0 * batch_idx / len(train_loader) + ) + ) + # Amor For Tensorboard display + if loss_mel > 75: + loss_mel = 75 + if loss_kl > 9: + loss_kl = 9 + + logger.info([global_step, lr]) + logger.info( + f"loss_disc={loss_disc:.3f}, loss_gen={loss_gen:.3f}, loss_fm={loss_fm:.3f},loss_mel={loss_mel:.3f}, loss_kl={loss_kl:.3f}" + ) + scalar_dict = { + "loss/g/total": loss_gen_all, + "loss/d/total": loss_disc, + "learning_rate": lr, + "grad_norm_d": grad_norm_d, + "grad_norm_g": grad_norm_g, + } + scalar_dict.update( + { + "loss/g/fm": loss_fm, + "loss/g/mel": loss_mel, + "loss/g/kl": loss_kl, + } + ) + + scalar_dict.update( + {"loss/g/{}".format(i): v for i, v in enumerate(losses_gen)} + ) + scalar_dict.update( + {"loss/d_r/{}".format(i): v for i, v in enumerate(losses_disc_r)} + ) + scalar_dict.update( + {"loss/d_g/{}".format(i): v for i, v in enumerate(losses_disc_g)} + ) + image_dict = { + "slice/mel_org": utils.plot_spectrogram_to_numpy( + y_mel[0].data.cpu().numpy() + ), + "slice/mel_gen": utils.plot_spectrogram_to_numpy( + y_hat_mel[0].data.cpu().numpy() + ), + "all/mel": utils.plot_spectrogram_to_numpy( + mel[0].data.cpu().numpy() + ), + } + utils.summarize( + writer=writer, + global_step=global_step, + images=image_dict, + scalars=scalar_dict, + ) + global_step += 1 + # /Run steps + + if epoch % hps.save_every_epoch == 0 and rank == 0: + if hps.if_latest == 0: + utils.save_checkpoint( + net_g, + optim_g, + hps.train.learning_rate, + epoch, + os.path.join(hps.model_dir, "G_{}.pth".format(global_step)), + ) + utils.save_checkpoint( + net_d, + optim_d, + hps.train.learning_rate, + epoch, + os.path.join(hps.model_dir, "D_{}.pth".format(global_step)), + ) + else: + utils.save_checkpoint( + net_g, + optim_g, + hps.train.learning_rate, + epoch, + os.path.join(hps.model_dir, "G_{}.pth".format(2333333)), + ) + utils.save_checkpoint( + net_d, + optim_d, + hps.train.learning_rate, + epoch, + os.path.join(hps.model_dir, "D_{}.pth".format(2333333)), + ) + if rank == 0 and hps.save_every_weights == "1": + if hasattr(net_g, "module"): + ckpt = net_g.module.state_dict() + else: + ckpt = net_g.state_dict() + logger.info( + "saving ckpt %s_e%s:%s" + % ( + hps.name, + epoch, + savee( + ckpt, + hps.sample_rate, + hps.if_f0, + hps.name + "_e%s_s%s" % (epoch, global_step), + epoch, + hps.version, + hps, + ), + ) + ) + + if rank == 0: + logger.info("====> Epoch: {} {}".format(epoch, epoch_recorder.record())) + if epoch >= hps.total_epoch and rank == 0: + logger.info("Training is done. The program is closed.") + + if hasattr(net_g, "module"): + ckpt = net_g.module.state_dict() + else: + ckpt = net_g.state_dict() + logger.info( + "saving final ckpt:%s" + % ( + savee( + ckpt, hps.sample_rate, hps.if_f0, hps.name, epoch, hps.version, hps + ) + ) + ) + sleep(1) + os._exit(2333333) + + +if __name__ == "__main__": + torch.multiprocessing.set_start_method("spawn") + main() diff --git a/rvc/infer/modules/uvr5/mdxnet.py b/rvc/infer/modules/uvr5/mdxnet.py new file mode 100644 index 0000000..2f246db --- /dev/null +++ b/rvc/infer/modules/uvr5/mdxnet.py @@ -0,0 +1,256 @@ +import os +import logging + +logger = logging.getLogger(__name__) + +import librosa +import numpy as np +import soundfile as sf +import torch +from tqdm import tqdm + +cpu = torch.device("cpu") + + +class ConvTDFNetTrim: + def __init__( + self, device, model_name, target_name, L, dim_f, dim_t, n_fft, hop=1024 + ): + super(ConvTDFNetTrim, self).__init__() + + self.dim_f = dim_f + self.dim_t = 2**dim_t + self.n_fft = n_fft + self.hop = hop + self.n_bins = self.n_fft // 2 + 1 + self.chunk_size = hop * (self.dim_t - 1) + self.window = torch.hann_window(window_length=self.n_fft, periodic=True).to( + device + ) + self.target_name = target_name + self.blender = "blender" in model_name + + self.dim_c = 4 + out_c = self.dim_c * 4 if target_name == "*" else self.dim_c + self.freq_pad = torch.zeros( + [1, out_c, self.n_bins - self.dim_f, self.dim_t] + ).to(device) + + self.n = L // 2 + + def stft(self, x): + x = x.reshape([-1, self.chunk_size]) + x = torch.stft( + x, + n_fft=self.n_fft, + hop_length=self.hop, + window=self.window, + center=True, + return_complex=True, + ) + x = torch.view_as_real(x) + x = x.permute([0, 3, 1, 2]) + x = x.reshape([-1, 2, 2, self.n_bins, self.dim_t]).reshape( + [-1, self.dim_c, self.n_bins, self.dim_t] + ) + return x[:, :, : self.dim_f] + + def istft(self, x, freq_pad=None): + freq_pad = ( + self.freq_pad.repeat([x.shape[0], 1, 1, 1]) + if freq_pad is None + else freq_pad + ) + x = torch.cat([x, freq_pad], -2) + c = 4 * 2 if self.target_name == "*" else 2 + x = x.reshape([-1, c, 2, self.n_bins, self.dim_t]).reshape( + [-1, 2, self.n_bins, self.dim_t] + ) + x = x.permute([0, 2, 3, 1]) + x = x.contiguous() + x = torch.view_as_complex(x) + x = torch.istft( + x, n_fft=self.n_fft, hop_length=self.hop, window=self.window, center=True + ) + return x.reshape([-1, c, self.chunk_size]) + + +def get_models(device, dim_f, dim_t, n_fft): + return ConvTDFNetTrim( + device=device, + model_name="Conv-TDF", + target_name="vocals", + L=11, + dim_f=dim_f, + dim_t=dim_t, + n_fft=n_fft, + ) + + +class Predictor: + def __init__(self, args): + import onnxruntime as ort + + logger.info(ort.get_available_providers()) + self.args = args + self.model_ = get_models( + device=cpu, dim_f=args.dim_f, dim_t=args.dim_t, n_fft=args.n_fft + ) + self.model = ort.InferenceSession( + os.path.join(args.onnx, self.model_.target_name + ".onnx"), + providers=[ + "CUDAExecutionProvider", + "DmlExecutionProvider", + "CPUExecutionProvider", + ], + ) + logger.info("ONNX load done") + + def demix(self, mix): + samples = mix.shape[-1] + margin = self.args.margin + chunk_size = self.args.chunks * 44100 + assert not margin == 0, "margin cannot be zero!" + if margin > chunk_size: + margin = chunk_size + + segmented_mix = {} + + if self.args.chunks == 0 or samples < chunk_size: + chunk_size = samples + + counter = -1 + for skip in range(0, samples, chunk_size): + counter += 1 + + s_margin = 0 if counter == 0 else margin + end = min(skip + chunk_size + margin, samples) + + start = skip - s_margin + + segmented_mix[skip] = mix[:, start:end].copy() + if end == samples: + break + + sources = self.demix_base(segmented_mix, margin_size=margin) + """ + mix:(2,big_sample) + segmented_mix:offset->(2,small_sample) + sources:(1,2,big_sample) + """ + return sources + + def demix_base(self, mixes, margin_size): + chunked_sources = [] + progress_bar = tqdm(total=len(mixes)) + progress_bar.set_description("Processing") + for mix in mixes: + cmix = mixes[mix] + sources = [] + n_sample = cmix.shape[1] + model = self.model_ + trim = model.n_fft // 2 + gen_size = model.chunk_size - 2 * trim + pad = gen_size - n_sample % gen_size + mix_p = np.concatenate( + (np.zeros((2, trim)), cmix, np.zeros((2, pad)), np.zeros((2, trim))), 1 + ) + mix_waves = [] + i = 0 + while i < n_sample + pad: + waves = np.array(mix_p[:, i : i + model.chunk_size]) + mix_waves.append(waves) + i += gen_size + mix_waves = torch.tensor(mix_waves, dtype=torch.float32).to(cpu) + with torch.no_grad(): + _ort = self.model + spek = model.stft(mix_waves) + if self.args.denoise: + spec_pred = ( + -_ort.run(None, {"input": -spek.cpu().numpy()})[0] * 0.5 + + _ort.run(None, {"input": spek.cpu().numpy()})[0] * 0.5 + ) + tar_waves = model.istft(torch.tensor(spec_pred)) + else: + tar_waves = model.istft( + torch.tensor(_ort.run(None, {"input": spek.cpu().numpy()})[0]) + ) + tar_signal = ( + tar_waves[:, :, trim:-trim] + .transpose(0, 1) + .reshape(2, -1) + .numpy()[:, :-pad] + ) + + start = 0 if mix == 0 else margin_size + end = None if mix == list(mixes.keys())[::-1][0] else -margin_size + if margin_size == 0: + end = None + sources.append(tar_signal[:, start:end]) + + progress_bar.update(1) + + chunked_sources.append(sources) + _sources = np.concatenate(chunked_sources, axis=-1) + # del self.model + progress_bar.close() + return _sources + + def prediction(self, m, vocal_root, others_root, format): + os.makedirs(vocal_root, exist_ok=True) + os.makedirs(others_root, exist_ok=True) + basename = os.path.basename(m) + mix, rate = librosa.load(m, mono=False, sr=44100) + if mix.ndim == 1: + mix = np.asfortranarray([mix, mix]) + mix = mix.T + sources = self.demix(mix.T) + opt = sources[0].T + if format in ["wav", "flac"]: + sf.write( + "%s/%s_main_vocal.%s" % (vocal_root, basename, format), mix - opt, rate + ) + sf.write("%s/%s_others.%s" % (others_root, basename, format), opt, rate) + else: + path_vocal = "%s/%s_main_vocal.wav" % (vocal_root, basename) + path_other = "%s/%s_others.wav" % (others_root, basename) + sf.write(path_vocal, mix - opt, rate) + sf.write(path_other, opt, rate) + opt_path_vocal = path_vocal[:-4] + ".%s" % format + opt_path_other = path_other[:-4] + ".%s" % format + if os.path.exists(path_vocal): + os.system( + "ffmpeg -i %s -vn %s -q:a 2 -y" % (path_vocal, opt_path_vocal) + ) + if os.path.exists(opt_path_vocal): + try: + os.remove(path_vocal) + except: + pass + if os.path.exists(path_other): + os.system( + "ffmpeg -i %s -vn %s -q:a 2 -y" % (path_other, opt_path_other) + ) + if os.path.exists(opt_path_other): + try: + os.remove(path_other) + except: + pass + + +class MDXNetDereverb: + def __init__(self, chunks, device): + self.onnx = "assets/uvr5_weights/onnx_dereverb_By_FoxJoy" + self.shifts = 10 # 'Predict with randomised equivariant stabilisation' + self.mixing = "min_mag" # ['default','min_mag','max_mag'] + self.chunks = chunks + self.margin = 44100 + self.dim_t = 9 + self.dim_f = 3072 + self.n_fft = 6144 + self.denoise = True + self.pred = Predictor(self) + self.device = device + + def _path_audio_(self, input, vocal_root, others_root, format, is_hp3=False): + self.pred.prediction(input, vocal_root, others_root, format) diff --git a/rvc/infer/modules/uvr5/modules.py b/rvc/infer/modules/uvr5/modules.py new file mode 100644 index 0000000..bce3cef --- /dev/null +++ b/rvc/infer/modules/uvr5/modules.py @@ -0,0 +1,108 @@ +import os +import traceback +import logging + +logger = logging.getLogger(__name__) + +import ffmpeg +import torch + +from configs.config import Config +from infer.modules.uvr5.mdxnet import MDXNetDereverb +from infer.modules.uvr5.vr import AudioPre, AudioPreDeEcho + +config = Config() + + +def uvr(model_name, inp_root, save_root_vocal, paths, save_root_ins, agg, format0): + infos = [] + try: + inp_root = inp_root.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + save_root_vocal = ( + save_root_vocal.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + ) + save_root_ins = ( + save_root_ins.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + ) + if model_name == "onnx_dereverb_By_FoxJoy": + pre_fun = MDXNetDereverb(15, config.device) + else: + func = AudioPre if "DeEcho" not in model_name else AudioPreDeEcho + pre_fun = func( + agg=int(agg), + model_path=os.path.join( + os.getenv("weight_uvr5_root"), model_name + ".pth" + ), + device=config.device, + is_half=config.is_half, + ) + is_hp3 = "HP3" in model_name + if inp_root != "": + paths = [os.path.join(inp_root, name) for name in os.listdir(inp_root)] + else: + paths = [path.name for path in paths] + for path in paths: + inp_path = os.path.join(inp_root, path) + need_reformat = 1 + done = 0 + try: + info = ffmpeg.probe(inp_path, cmd="ffprobe") + if ( + info["streams"][0]["channels"] == 2 + and info["streams"][0]["sample_rate"] == "44100" + ): + need_reformat = 0 + pre_fun._path_audio_( + inp_path, save_root_ins, save_root_vocal, format0, is_hp3=is_hp3 + ) + done = 1 + except: + need_reformat = 1 + traceback.print_exc() + if need_reformat == 1: + tmp_path = "%s/%s.reformatted.wav" % ( + os.path.join(os.environ["TEMP"]), + os.path.basename(inp_path), + ) + os.system( + "ffmpeg -i %s -vn -acodec pcm_s16le -ac 2 -ar 44100 %s -y" + % (inp_path, tmp_path) + ) + inp_path = tmp_path + try: + if done == 0: + pre_fun._path_audio_( + inp_path, save_root_ins, save_root_vocal, format0 + ) + infos.append("%s->Success" % (os.path.basename(inp_path))) + yield "\n".join(infos) + except: + try: + if done == 0: + pre_fun._path_audio_( + inp_path, save_root_ins, save_root_vocal, format0 + ) + infos.append("%s->Success" % (os.path.basename(inp_path))) + yield "\n".join(infos) + except: + infos.append( + "%s->%s" % (os.path.basename(inp_path), traceback.format_exc()) + ) + yield "\n".join(infos) + except: + infos.append(traceback.format_exc()) + yield "\n".join(infos) + finally: + try: + if model_name == "onnx_dereverb_By_FoxJoy": + del pre_fun.pred.model + del pre_fun.pred.model_ + else: + del pre_fun.model + del pre_fun + except: + traceback.print_exc() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + logger.info("Executed torch.cuda.empty_cache()") + yield "\n".join(infos) diff --git a/rvc/infer/modules/uvr5/vr.py b/rvc/infer/modules/uvr5/vr.py new file mode 100644 index 0000000..ed57784 --- /dev/null +++ b/rvc/infer/modules/uvr5/vr.py @@ -0,0 +1,368 @@ +import os +import logging + +logger = logging.getLogger(__name__) + +import librosa +import numpy as np +import soundfile as sf +import torch + +from infer.lib.uvr5_pack.lib_v5 import nets_61968KB as Nets +from infer.lib.uvr5_pack.lib_v5 import spec_utils +from infer.lib.uvr5_pack.lib_v5.model_param_init import ModelParameters +from infer.lib.uvr5_pack.lib_v5.nets_new import CascadedNet +from infer.lib.uvr5_pack.utils import inference + + +class AudioPre: + def __init__(self, agg, model_path, device, is_half, tta=False): + self.model_path = model_path + self.device = device + self.data = { + # Processing Options + "postprocess": False, + "tta": tta, + # Constants + "window_size": 512, + "agg": agg, + "high_end_process": "mirroring", + } + mp = ModelParameters("infer/lib/uvr5_pack/lib_v5/modelparams/4band_v2.json") + model = Nets.CascadedASPPNet(mp.param["bins"] * 2) + cpk = torch.load(model_path, map_location="cpu") + model.load_state_dict(cpk) + model.eval() + if is_half: + model = model.half().to(device) + else: + model = model.to(device) + + self.mp = mp + self.model = model + + def _path_audio_( + self, music_file, ins_root=None, vocal_root=None, format="flac", is_hp3=False + ): + if ins_root is None and vocal_root is None: + return "No save root." + name = os.path.basename(music_file) + if ins_root is not None: + os.makedirs(ins_root, exist_ok=True) + if vocal_root is not None: + os.makedirs(vocal_root, exist_ok=True) + X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {} + bands_n = len(self.mp.param["band"]) + # print(bands_n) + for d in range(bands_n, 0, -1): + bp = self.mp.param["band"][d] + if d == bands_n: # high-end band + ( + X_wave[d], + _, + ) = librosa.core.load( # 理论上librosa读取可能对某些音频有bug,应该上ffmpeg读取,但是太麻烦了弃坑 + music_file, + bp["sr"], + False, + dtype=np.float32, + res_type=bp["res_type"], + ) + if X_wave[d].ndim == 1: + X_wave[d] = np.asfortranarray([X_wave[d], X_wave[d]]) + else: # lower bands + X_wave[d] = librosa.core.resample( + X_wave[d + 1], + self.mp.param["band"][d + 1]["sr"], + bp["sr"], + res_type=bp["res_type"], + ) + # Stft of wave source + X_spec_s[d] = spec_utils.wave_to_spectrogram_mt( + X_wave[d], + bp["hl"], + bp["n_fft"], + self.mp.param["mid_side"], + self.mp.param["mid_side_b2"], + self.mp.param["reverse"], + ) + # pdb.set_trace() + if d == bands_n and self.data["high_end_process"] != "none": + input_high_end_h = (bp["n_fft"] // 2 - bp["crop_stop"]) + ( + self.mp.param["pre_filter_stop"] - self.mp.param["pre_filter_start"] + ) + input_high_end = X_spec_s[d][ + :, bp["n_fft"] // 2 - input_high_end_h : bp["n_fft"] // 2, : + ] + + X_spec_m = spec_utils.combine_spectrograms(X_spec_s, self.mp) + aggresive_set = float(self.data["agg"] / 100) + aggressiveness = { + "value": aggresive_set, + "split_bin": self.mp.param["band"][1]["crop_stop"], + } + with torch.no_grad(): + pred, X_mag, X_phase = inference( + X_spec_m, self.device, self.model, aggressiveness, self.data + ) + # Postprocess + if self.data["postprocess"]: + pred_inv = np.clip(X_mag - pred, 0, np.inf) + pred = spec_utils.mask_silence(pred, pred_inv) + y_spec_m = pred * X_phase + v_spec_m = X_spec_m - y_spec_m + + if ins_root is not None: + if self.data["high_end_process"].startswith("mirroring"): + input_high_end_ = spec_utils.mirroring( + self.data["high_end_process"], y_spec_m, input_high_end, self.mp + ) + wav_instrument = spec_utils.cmb_spectrogram_to_wave( + y_spec_m, self.mp, input_high_end_h, input_high_end_ + ) + else: + wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, self.mp) + logger.info("%s instruments done" % name) + if is_hp3 == True: + head = "vocal_" + else: + head = "instrument_" + if format in ["wav", "flac"]: + sf.write( + os.path.join( + ins_root, + head + "{}_{}.{}".format(name, self.data["agg"], format), + ), + (np.array(wav_instrument) * 32768).astype("int16"), + self.mp.param["sr"], + ) # + else: + path = os.path.join( + ins_root, head + "{}_{}.wav".format(name, self.data["agg"]) + ) + sf.write( + path, + (np.array(wav_instrument) * 32768).astype("int16"), + self.mp.param["sr"], + ) + if os.path.exists(path): + opt_format_path = path[:-4] + ".%s" % format + os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (path, opt_format_path)) + if os.path.exists(opt_format_path): + try: + os.remove(path) + except: + pass + if vocal_root is not None: + if is_hp3 == True: + head = "instrument_" + else: + head = "vocal_" + if self.data["high_end_process"].startswith("mirroring"): + input_high_end_ = spec_utils.mirroring( + self.data["high_end_process"], v_spec_m, input_high_end, self.mp + ) + wav_vocals = spec_utils.cmb_spectrogram_to_wave( + v_spec_m, self.mp, input_high_end_h, input_high_end_ + ) + else: + wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, self.mp) + logger.info("%s vocals done" % name) + if format in ["wav", "flac"]: + sf.write( + os.path.join( + vocal_root, + head + "{}_{}.{}".format(name, self.data["agg"], format), + ), + (np.array(wav_vocals) * 32768).astype("int16"), + self.mp.param["sr"], + ) + else: + path = os.path.join( + vocal_root, head + "{}_{}.wav".format(name, self.data["agg"]) + ) + sf.write( + path, + (np.array(wav_vocals) * 32768).astype("int16"), + self.mp.param["sr"], + ) + if os.path.exists(path): + opt_format_path = path[:-4] + ".%s" % format + os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (path, opt_format_path)) + if os.path.exists(opt_format_path): + try: + os.remove(path) + except: + pass + + +class AudioPreDeEcho: + def __init__(self, agg, model_path, device, is_half, tta=False): + self.model_path = model_path + self.device = device + self.data = { + # Processing Options + "postprocess": False, + "tta": tta, + # Constants + "window_size": 512, + "agg": agg, + "high_end_process": "mirroring", + } + mp = ModelParameters("infer/lib/uvr5_pack/lib_v5/modelparams/4band_v3.json") + nout = 64 if "DeReverb" in model_path else 48 + model = CascadedNet(mp.param["bins"] * 2, nout) + cpk = torch.load(model_path, map_location="cpu") + model.load_state_dict(cpk) + model.eval() + if is_half: + model = model.half().to(device) + else: + model = model.to(device) + + self.mp = mp + self.model = model + + def _path_audio_( + self, music_file, vocal_root=None, ins_root=None, format="flac", is_hp3=False + ): # 3个VR模型vocal和ins是反的 + if ins_root is None and vocal_root is None: + return "No save root." + name = os.path.basename(music_file) + if ins_root is not None: + os.makedirs(ins_root, exist_ok=True) + if vocal_root is not None: + os.makedirs(vocal_root, exist_ok=True) + X_wave, y_wave, X_spec_s, y_spec_s = {}, {}, {}, {} + bands_n = len(self.mp.param["band"]) + # print(bands_n) + for d in range(bands_n, 0, -1): + bp = self.mp.param["band"][d] + if d == bands_n: # high-end band + ( + X_wave[d], + _, + ) = librosa.core.load( # 理论上librosa读取可能对某些音频有bug,应该上ffmpeg读取,但是太麻烦了弃坑 + music_file, + bp["sr"], + False, + dtype=np.float32, + res_type=bp["res_type"], + ) + if X_wave[d].ndim == 1: + X_wave[d] = np.asfortranarray([X_wave[d], X_wave[d]]) + else: # lower bands + X_wave[d] = librosa.core.resample( + X_wave[d + 1], + self.mp.param["band"][d + 1]["sr"], + bp["sr"], + res_type=bp["res_type"], + ) + # Stft of wave source + X_spec_s[d] = spec_utils.wave_to_spectrogram_mt( + X_wave[d], + bp["hl"], + bp["n_fft"], + self.mp.param["mid_side"], + self.mp.param["mid_side_b2"], + self.mp.param["reverse"], + ) + # pdb.set_trace() + if d == bands_n and self.data["high_end_process"] != "none": + input_high_end_h = (bp["n_fft"] // 2 - bp["crop_stop"]) + ( + self.mp.param["pre_filter_stop"] - self.mp.param["pre_filter_start"] + ) + input_high_end = X_spec_s[d][ + :, bp["n_fft"] // 2 - input_high_end_h : bp["n_fft"] // 2, : + ] + + X_spec_m = spec_utils.combine_spectrograms(X_spec_s, self.mp) + aggresive_set = float(self.data["agg"] / 100) + aggressiveness = { + "value": aggresive_set, + "split_bin": self.mp.param["band"][1]["crop_stop"], + } + with torch.no_grad(): + pred, X_mag, X_phase = inference( + X_spec_m, self.device, self.model, aggressiveness, self.data + ) + # Postprocess + if self.data["postprocess"]: + pred_inv = np.clip(X_mag - pred, 0, np.inf) + pred = spec_utils.mask_silence(pred, pred_inv) + y_spec_m = pred * X_phase + v_spec_m = X_spec_m - y_spec_m + + if ins_root is not None: + if self.data["high_end_process"].startswith("mirroring"): + input_high_end_ = spec_utils.mirroring( + self.data["high_end_process"], y_spec_m, input_high_end, self.mp + ) + wav_instrument = spec_utils.cmb_spectrogram_to_wave( + y_spec_m, self.mp, input_high_end_h, input_high_end_ + ) + else: + wav_instrument = spec_utils.cmb_spectrogram_to_wave(y_spec_m, self.mp) + logger.info("%s instruments done" % name) + if format in ["wav", "flac"]: + sf.write( + os.path.join( + ins_root, + "vocal_{}_{}.{}".format(name, self.data["agg"], format), + ), + (np.array(wav_instrument) * 32768).astype("int16"), + self.mp.param["sr"], + ) # + else: + path = os.path.join( + ins_root, "vocal_{}_{}.wav".format(name, self.data["agg"]) + ) + sf.write( + path, + (np.array(wav_instrument) * 32768).astype("int16"), + self.mp.param["sr"], + ) + if os.path.exists(path): + opt_format_path = path[:-4] + ".%s" % format + os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (path, opt_format_path)) + if os.path.exists(opt_format_path): + try: + os.remove(path) + except: + pass + if vocal_root is not None: + if self.data["high_end_process"].startswith("mirroring"): + input_high_end_ = spec_utils.mirroring( + self.data["high_end_process"], v_spec_m, input_high_end, self.mp + ) + wav_vocals = spec_utils.cmb_spectrogram_to_wave( + v_spec_m, self.mp, input_high_end_h, input_high_end_ + ) + else: + wav_vocals = spec_utils.cmb_spectrogram_to_wave(v_spec_m, self.mp) + logger.info("%s vocals done" % name) + if format in ["wav", "flac"]: + sf.write( + os.path.join( + vocal_root, + "instrument_{}_{}.{}".format(name, self.data["agg"], format), + ), + (np.array(wav_vocals) * 32768).astype("int16"), + self.mp.param["sr"], + ) + else: + path = os.path.join( + vocal_root, "instrument_{}_{}.wav".format(name, self.data["agg"]) + ) + sf.write( + path, + (np.array(wav_vocals) * 32768).astype("int16"), + self.mp.param["sr"], + ) + if os.path.exists(path): + opt_format_path = path[:-4] + ".%s" % format + os.system("ffmpeg -i %s -vn %s -q:a 2 -y" % (path, opt_format_path)) + if os.path.exists(opt_format_path): + try: + os.remove(path) + except: + pass diff --git a/rvc/infer/modules/vc/__init__.py b/rvc/infer/modules/vc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rvc/infer/modules/vc/__pycache__/__init__.cpython-310.pyc b/rvc/infer/modules/vc/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b8f5052b1fb30615de7868fc79b018cdff35050 GIT binary patch literal 166 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 literal 0 HcmV?d00001 diff --git a/rvc/infer/modules/vc/__pycache__/utils.cpython-310.pyc b/rvc/infer/modules/vc/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f0423484bd1b29d05fb1d77fba60bb173570597 GIT binary patch literal 1272 zcma)5&1xG-5bo+}&5RT!7!yLg$t|!%u$Dq{6(-;i$YFih5Fbp!FrMy_Mjp+`-7WbC ziR_ZV?!||F1MBEV$gA`F(g%tn%j?%$49C0Iz4T{~ zvG=sNJUA{MqnZ2YB$JS{9s2Sd#(akZlb#fB*^Vb0(#Nlm@SJsm4|tkwb-=A8LzV1J z%B--_)MmL!fI=68<^dXJX67L}%YJ7mNRa$(SR-2)aSNx2zXa*a;DkS6Z-7jOZ#d>^ zZe4*S!UF9PLH>yDH#TRlZY zN$OK?j;kgA)7x+ElP>(&?^ZNiV0D}oN_K<`RUyqmW`~ZK#t9=pC>BA1%IO zxmGP3QFw~Q3~V`(EZbpZKnz 1: + audio /= audio_max + times = [0, 0, 0] + + if self.hubert_model is None: + self.hubert_model = load_hubert(self.config) + + if file_index: + file_index = ( + file_index.strip(" ") + .strip('"') + .strip("\n") + .strip('"') + .strip(" ") + .replace("trained", "added") + ) + elif file_index2: + file_index = file_index2 + else: + file_index = "" # 防止小白写错,自动帮他替换掉 + + audio_opt = self.pipeline.pipeline( + self.hubert_model, + self.net_g, + sid, + audio, + input_audio_path, + times, + f0_up_key, + f0_method, + file_index, + index_rate, + self.if_f0, + filter_radius, + self.tgt_sr, + resample_sr, + rms_mix_rate, + self.version, + protect, + f0_file, + ) + if self.tgt_sr != resample_sr >= 16000: + tgt_sr = resample_sr + else: + tgt_sr = self.tgt_sr + index_info = ( + "Index:\n%s." % file_index + if os.path.exists(file_index) + else "Index not used." + ) + return ( + "Success.\n%s\nTime:\nnpy: %.2fs, f0: %.2fs, infer: %.2fs." + % (index_info, *times), + (tgt_sr, audio_opt), + ) + except: + info = traceback.format_exc() + logger.warning(info) + return info, (None, None) + + def vc_multi( + self, + sid, + dir_path, + opt_root, + paths, + f0_up_key, + f0_method, + file_index, + file_index2, + index_rate, + filter_radius, + resample_sr, + rms_mix_rate, + protect, + format1, + ): + try: + dir_path = ( + dir_path.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + ) # 防止小白拷路径头尾带了空格和"和回车 + opt_root = opt_root.strip(" ").strip('"').strip("\n").strip('"').strip(" ") + os.makedirs(opt_root, exist_ok=True) + try: + if dir_path != "": + paths = [ + os.path.join(dir_path, name) for name in os.listdir(dir_path) + ] + else: + paths = [path.name for path in paths] + except: + traceback.print_exc() + paths = [path.name for path in paths] + infos = [] + for path in paths: + info, opt = self.vc_single( + sid, + path, + f0_up_key, + None, + f0_method, + file_index, + file_index2, + # file_big_npy, + index_rate, + filter_radius, + resample_sr, + rms_mix_rate, + protect, + ) + if "Success" in info: + try: + tgt_sr, audio_opt = opt + if format1 in ["wav", "flac"]: + sf.write( + "%s/%s.%s" + % (opt_root, os.path.basename(path), format1), + audio_opt, + tgt_sr, + ) + else: + path = "%s/%s.%s" % ( + opt_root, + os.path.basename(path), + format1, + ) + with BytesIO() as wavf: + sf.write(wavf, audio_opt, tgt_sr, format="wav") + wavf.seek(0, 0) + with open(path, "wb") as outf: + wav2(wavf, outf, format1) + except: + info += traceback.format_exc() + infos.append("%s->%s" % (os.path.basename(path), info)) + yield "\n".join(infos) + yield "\n".join(infos) + except: + yield traceback.format_exc() diff --git a/rvc/infer/modules/vc/pipeline.py b/rvc/infer/modules/vc/pipeline.py new file mode 100644 index 0000000..371836b --- /dev/null +++ b/rvc/infer/modules/vc/pipeline.py @@ -0,0 +1,475 @@ +import os +import sys +import traceback +import logging + +logger = logging.getLogger(__name__) + +from functools import lru_cache +from time import time as ttime + +import faiss +import librosa +import numpy as np +import parselmouth +import pyworld +import torch +import torch.nn.functional as F +import torchcrepe +from scipy import signal + +now_dir = os.getcwd() +sys.path.append(now_dir) + +bh, ah = signal.butter(N=5, Wn=48, btype="high", fs=16000) + +input_audio_path2wav = {} + + +@lru_cache +def cache_harvest_f0(input_audio_path, fs, f0max, f0min, frame_period): + audio = input_audio_path2wav[input_audio_path] + f0, t = pyworld.harvest( + audio, + fs=fs, + f0_ceil=f0max, + f0_floor=f0min, + frame_period=frame_period, + ) + f0 = pyworld.stonemask(audio, f0, t, fs) + return f0 + + +def change_rms(data1, sr1, data2, sr2, rate): # 1是输入音频,2是输出音频,rate是2的占比 + # print(data1.max(),data2.max()) + rms1 = librosa.feature.rms( + y=data1, frame_length=sr1 // 2 * 2, hop_length=sr1 // 2 + ) # 每半秒一个点 + rms2 = librosa.feature.rms(y=data2, frame_length=sr2 // 2 * 2, hop_length=sr2 // 2) + rms1 = torch.from_numpy(rms1) + rms1 = F.interpolate( + rms1.unsqueeze(0), size=data2.shape[0], mode="linear" + ).squeeze() + rms2 = torch.from_numpy(rms2) + rms2 = F.interpolate( + rms2.unsqueeze(0), size=data2.shape[0], mode="linear" + ).squeeze() + rms2 = torch.max(rms2, torch.zeros_like(rms2) + 1e-6) + data2 *= ( + torch.pow(rms1, torch.tensor(1 - rate)) + * torch.pow(rms2, torch.tensor(rate - 1)) + ).numpy() + return data2 + + +class Pipeline(object): + def __init__(self, tgt_sr, config): + self.x_pad, self.x_query, self.x_center, self.x_max, self.is_half = ( + config.x_pad, + config.x_query, + config.x_center, + config.x_max, + config.is_half, + ) + self.sr = 16000 # hubert输入采样率 + self.window = 160 # 每帧点数 + self.t_pad = self.sr * self.x_pad # 每条前后pad时间 + self.t_pad_tgt = tgt_sr * self.x_pad + self.t_pad2 = self.t_pad * 2 + self.t_query = self.sr * self.x_query # 查询切点前后查询时间 + self.t_center = self.sr * self.x_center # 查询切点位置 + self.t_max = self.sr * self.x_max # 免查询时长阈值 + self.device = config.device + + def get_f0( + self, + input_audio_path, + x, + p_len, + f0_up_key, + f0_method, + filter_radius, + inp_f0=None, + ): + global input_audio_path2wav + time_step = self.window / self.sr * 1000 + f0_min = 50 + f0_max = 1100 + f0_mel_min = 1127 * np.log(1 + f0_min / 700) + f0_mel_max = 1127 * np.log(1 + f0_max / 700) + if f0_method == "pm": + f0 = ( + parselmouth.Sound(x, self.sr) + .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 == "harvest": + input_audio_path2wav[input_audio_path] = x.astype(np.double) + f0 = cache_harvest_f0(input_audio_path, self.sr, f0_max, f0_min, 10) + if filter_radius > 2: + f0 = signal.medfilt(f0, 3) + elif f0_method == "crepe": + model = "full" + # Pick a batch size that doesn't cause memory errors on your gpu + batch_size = 512 + # Compute pitch using first gpu + audio = torch.tensor(np.copy(x))[None].float() + f0, pd = torchcrepe.predict( + audio, + self.sr, + self.window, + f0_min, + f0_max, + model, + batch_size=batch_size, + device=self.device, + return_periodicity=True, + ) + pd = torchcrepe.filter.median(pd, 3) + f0 = torchcrepe.filter.mean(f0, 3) + f0[pd < 0.1] = 0 + f0 = f0[0].cpu().numpy() + elif f0_method == "rmvpe": + if not hasattr(self, "model_rmvpe"): + from infer.lib.rmvpe import RMVPE + + logger.info( + "Loading rmvpe model,%s" % "%s/rmvpe.pt" % os.environ["rmvpe_root"] + ) + self.model_rmvpe = RMVPE( + "%s/rmvpe.pt" % os.environ["rmvpe_root"], + is_half=self.is_half, + device=self.device, + # use_jit=self.config.use_jit, + ) + f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03) + + if "privateuseone" in str(self.device): # clean ortruntime memory + del self.model_rmvpe.model + del self.model_rmvpe + logger.info("Cleaning ortruntime memory") + elif f0_method == "fcpe": + if not hasattr(self, "model_fcpe"): + from torchfcpe import spawn_bundled_infer_model + + logger.info("Loading fcpe model") + self.model_fcpe = spawn_bundled_infer_model(self.device) + f0 = ( + self.model_fcpe.infer( + torch.from_numpy(x).to(self.device).unsqueeze(0).float(), + sr=16000, + decoder_mode="local_argmax", + threshold=0.006, + ) + .squeeze() + .cpu() + .numpy() + ) + + f0 *= pow(2, f0_up_key / 12) + # with open("test.txt","w")as f:f.write("\n".join([str(i)for i in f0.tolist()])) + tf0 = self.sr // self.window # 每秒f0点数 + if inp_f0 is not None: + delta_t = np.round( + (inp_f0[:, 0].max() - inp_f0[:, 0].min()) * tf0 + 1 + ).astype("int16") + replace_f0 = np.interp( + list(range(delta_t)), inp_f0[:, 0] * 100, inp_f0[:, 1] + ) + shape = f0[self.x_pad * tf0 : self.x_pad * tf0 + len(replace_f0)].shape[0] + f0[self.x_pad * tf0 : self.x_pad * tf0 + len(replace_f0)] = replace_f0[ + :shape + ] + # with open("test_opt.txt","w")as f:f.write("\n".join([str(i)for i in f0.tolist()])) + f0bak = f0.copy() + f0_mel = 1127 * np.log(1 + f0 / 700) + f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - f0_mel_min) * 254 / ( + f0_mel_max - f0_mel_min + ) + 1 + f0_mel[f0_mel <= 1] = 1 + f0_mel[f0_mel > 255] = 255 + f0_coarse = np.rint(f0_mel).astype(np.int32) + return f0_coarse, f0bak # 1-0 + + def vc( + self, + model, + net_g, + sid, + audio0, + pitch, + pitchf, + times, + index, + big_npy, + index_rate, + version, + protect, + ): # ,file_index,file_big_npy + feats = torch.from_numpy(audio0) + if self.is_half: + feats = feats.half() + else: + feats = feats.float() + if feats.dim() == 2: # double channels + feats = feats.mean(-1) + assert feats.dim() == 1, feats.dim() + feats = feats.view(1, -1) + padding_mask = torch.BoolTensor(feats.shape).to(self.device).fill_(False) + + inputs = { + "source": feats.to(self.device), + "padding_mask": padding_mask, + "output_layer": 9 if version == "v1" else 12, + } + t0 = ttime() + with torch.no_grad(): + logits = model.extract_features(**inputs) + feats = model.final_proj(logits[0]) if version == "v1" else logits[0] + if protect < 0.5 and pitch is not None and pitchf is not None: + feats0 = feats.clone() + if ( + not isinstance(index, type(None)) + and not isinstance(big_npy, type(None)) + and index_rate != 0 + ): + npy = feats[0].cpu().numpy() + if self.is_half: + npy = npy.astype("float32") + + # _, I = index.search(npy, 1) + # npy = big_npy[I.squeeze()] + + score, ix = index.search(npy, k=8) + weight = np.square(1 / score) + weight /= weight.sum(axis=1, keepdims=True) + npy = np.sum(big_npy[ix] * np.expand_dims(weight, axis=2), axis=1) + + if self.is_half: + npy = npy.astype("float16") + feats = ( + torch.from_numpy(npy).unsqueeze(0).to(self.device) * index_rate + + (1 - index_rate) * feats + ) + + feats = F.interpolate(feats.permute(0, 2, 1), scale_factor=2).permute(0, 2, 1) + if protect < 0.5 and pitch is not None and pitchf is not None: + feats0 = F.interpolate(feats0.permute(0, 2, 1), scale_factor=2).permute( + 0, 2, 1 + ) + t1 = ttime() + p_len = audio0.shape[0] // self.window + if feats.shape[1] < p_len: + p_len = feats.shape[1] + if pitch is not None and pitchf is not None: + pitch = pitch[:, :p_len] + pitchf = pitchf[:, :p_len] + + if protect < 0.5 and pitch is not None and pitchf is not None: + pitchff = pitchf.clone() + pitchff[pitchf > 0] = 1 + pitchff[pitchf < 1] = protect + pitchff = pitchff.unsqueeze(-1) + feats = feats * pitchff + feats0 * (1 - pitchff) + feats = feats.to(feats0.dtype) + p_len = torch.tensor([p_len], device=self.device).long() + with torch.no_grad(): + hasp = pitch is not None and pitchf is not None + arg = (feats, p_len, pitch, pitchf, sid) if hasp else (feats, p_len, sid) + audio1 = (net_g.infer(*arg)[0][0, 0]).data.cpu().float().numpy() + del hasp, arg + del feats, p_len, padding_mask + if torch.cuda.is_available(): + torch.cuda.empty_cache() + t2 = ttime() + times[0] += t1 - t0 + times[2] += t2 - t1 + return audio1 + + def pipeline( + self, + model, + net_g, + sid, + audio, + input_audio_path, + times, + f0_up_key, + f0_method, + file_index, + index_rate, + if_f0, + filter_radius, + tgt_sr, + resample_sr, + rms_mix_rate, + version, + protect, + f0_file=None, + ): + if ( + file_index != "" + # and file_big_npy != "" + # and os.path.exists(file_big_npy) == True + and os.path.exists(file_index) + and index_rate != 0 + ): + try: + index = faiss.read_index(file_index) + # big_npy = np.load(file_big_npy) + big_npy = index.reconstruct_n(0, index.ntotal) + except: + traceback.print_exc() + index = big_npy = None + else: + index = big_npy = None + audio = signal.filtfilt(bh, ah, audio) + audio_pad = np.pad(audio, (self.window // 2, self.window // 2), mode="reflect") + opt_ts = [] + if audio_pad.shape[0] > self.t_max: + audio_sum = np.zeros_like(audio) + for i in range(self.window): + audio_sum += np.abs(audio_pad[i : i - self.window]) + for t in range(self.t_center, audio.shape[0], self.t_center): + opt_ts.append( + t + - self.t_query + + np.where( + audio_sum[t - self.t_query : t + self.t_query] + == audio_sum[t - self.t_query : t + self.t_query].min() + )[0][0] + ) + s = 0 + audio_opt = [] + t = None + t1 = ttime() + audio_pad = np.pad(audio, (self.t_pad, self.t_pad), mode="reflect") + p_len = audio_pad.shape[0] // self.window + inp_f0 = None + if hasattr(f0_file, "name"): + try: + with open(f0_file.name, "r") as f: + lines = f.read().strip("\n").split("\n") + inp_f0 = [] + for line in lines: + inp_f0.append([float(i) for i in line.split(",")]) + inp_f0 = np.array(inp_f0, dtype="float32") + except: + traceback.print_exc() + sid = torch.tensor(sid, device=self.device).unsqueeze(0).long() + pitch, pitchf = None, None + if if_f0 == 1: + pitch, pitchf = self.get_f0( + input_audio_path, + audio_pad, + p_len, + f0_up_key, + f0_method, + filter_radius, + inp_f0, + ) + pitch = pitch[:p_len] + pitchf = pitchf[:p_len] + if "mps" not in str(self.device) or "xpu" not in str(self.device): + pitchf = pitchf.astype(np.float32) + pitch = torch.tensor(pitch, device=self.device).unsqueeze(0).long() + pitchf = torch.tensor(pitchf, device=self.device).unsqueeze(0).float() + t2 = ttime() + times[1] += t2 - t1 + for t in opt_ts: + t = t // self.window * self.window + if if_f0 == 1: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[s : t + self.t_pad2 + self.window], + pitch[:, s // self.window : (t + self.t_pad2) // self.window], + pitchf[:, s // self.window : (t + self.t_pad2) // self.window], + times, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + else: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[s : t + self.t_pad2 + self.window], + None, + None, + times, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + s = t + if if_f0 == 1: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[t:], + pitch[:, t // self.window :] if t is not None else pitch, + pitchf[:, t // self.window :] if t is not None else pitchf, + times, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + else: + audio_opt.append( + self.vc( + model, + net_g, + sid, + audio_pad[t:], + None, + None, + times, + index, + big_npy, + index_rate, + version, + protect, + )[self.t_pad_tgt : -self.t_pad_tgt] + ) + audio_opt = np.concatenate(audio_opt) + if rms_mix_rate != 1: + audio_opt = change_rms(audio, 16000, audio_opt, tgt_sr, rms_mix_rate) + if tgt_sr != resample_sr >= 16000: + audio_opt = librosa.resample( + audio_opt, orig_sr=tgt_sr, target_sr=resample_sr + ) + audio_max = np.abs(audio_opt).max() / 0.99 + max_int16 = 32768 + if audio_max > 1: + max_int16 /= audio_max + audio_opt = (audio_opt * max_int16).astype(np.int16) + del pitch, pitchf, sid + if torch.cuda.is_available(): + torch.cuda.empty_cache() + return audio_opt diff --git a/rvc/infer/modules/vc/utils.py b/rvc/infer/modules/vc/utils.py new file mode 100644 index 0000000..4363cb2 --- /dev/null +++ b/rvc/infer/modules/vc/utils.py @@ -0,0 +1,33 @@ +import os + +from fairseq import checkpoint_utils + + +def get_index_path_from_model(sid): + return next( + ( + f + for f in [ + os.path.join(root, name) + for root, _, files in os.walk(os.getenv("index_root"), topdown=False) + for name in files + if name.endswith(".index") and "trained" not in name + ] + if sid.split(".")[0] in f + ), + "", + ) + + +def load_hubert(config): + models, _, _ = checkpoint_utils.load_model_ensemble_and_task( + [os.getenv('hubert_base')], + suffix="", + ) + hubert_model = models[0] + hubert_model = hubert_model.to(config.device) + if config.is_half: + hubert_model = hubert_model.half() + else: + hubert_model = hubert_model.float() + return hubert_model.eval() diff --git a/rvc/logs/mute/0_gt_wavs/mute32k.wav b/rvc/logs/mute/0_gt_wavs/mute32k.wav new file mode 100644 index 0000000000000000000000000000000000000000..b4b5029205bf72dee5856bbe0c65c34337dc8dd4 GIT binary patch literal 192078 zcmeIuF$%&!5CzaN2awcOo*+a)(9*(%g`i-9)&WUp7wo*U2e5LlVE_Ja{xnm(YfdSB zs<xfvC#=DGfL=|;(xgS>L%;$8;eHxZ!cjIf??VIhP*|cpu#(9kLMFZul<8U$f@-x1(S8-+zB~@8EKL{BsiT7a^JEcakJo zckR%YZBv|AU0=7WFp3HJ?f9mthoPy8YPGBvt1$kYXE8W@SXV_~f2{g2xd`br--*Hg ztnG%dGau)ZxT{w~xG_)jbX%icorc|cy4l2@<ZZ?%u6nnrn`JRntG?}u z^H~;Wn&!LPf-!S(T7}W|C)rWcE`~SP!7PmPy_hpQeDq}Y_~Gm@O#Zel*PD<&-~W*& zFXAk(=gILXJ>DN(#aTx2@ha?oDdRbtsvP1m`|M@6th#FXtS*Nv+Y?#1O{- literal 0 HcmV?d00001 diff --git a/rvc/logs/mute/0_gt_wavs/mute48k.spec.pt b/rvc/logs/mute/0_gt_wavs/mute48k.spec.pt new file mode 100644 index 0000000000000000000000000000000000000000..2c5414d1c6f9ea25b0e7b41d6ba9be54be47791a GIT binary patch literal 1230337 zcmeIw&1%$86ae7NOloaJ5O?leRw%`vg5X+QIMkoI$wtDM32h`ydXqZ^g+f6n>NEHN zZhb5tL2%(6yYm5DeSut(lXK5GH+&ba$ScGZ0lX4zBKDhrVEeB!tw{3m84*B!FA9?m7 z&GLGl9ZvGYy~$;oWs)8*!}gatowIA|n2tFXFNb9_G|Q)L9gA@N-ci_zp*RXR(x7ic z_D_q+Vt-S4bM?1BJ#qU73^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d| z0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb zFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?0 z00Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u< z3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs# zzyJdbFu(u<3^2d|0}L?000Rs#zyJdbFu(u<3^2d|0}L?000Rs#zyJdb{9glq02B($ AfdBvi literal 0 HcmV?d00001 diff --git a/rvc/logs/mute/0_gt_wavs/mute48k.wav b/rvc/logs/mute/0_gt_wavs/mute48k.wav new file mode 100644 index 0000000000000000000000000000000000000000..72822a01251e77d7d2a4a7da9d94805426829083 GIT binary patch literal 288078 zcmeIuF$%&^5CqVTSW9XvPY|LYXldbxKu|D2>wu=S3)*@wFXumm<(p|{n=Rg@Nhv+% zaXVa(<8xY-5mDrRzInWhm_-?F)Ah@IHm7uMy3{Z7Zim;n-Bp`?wXW;957Q9&ix40{ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF K5FkL{UjjeN{10CM literal 0 HcmV?d00001 diff --git a/rvc/logs/mute/1_16k_wavs/mute.wav b/rvc/logs/mute/1_16k_wavs/mute.wav new file mode 100644 index 0000000000000000000000000000000000000000..27a7d638558539c521aacf8c0f34bd0d4816aa9d GIT binary patch literal 96078 zcmeIuF$%&!5CzbQ2awcOo4^ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!CtM Ffgj{54+Q`K literal 0 HcmV?d00001 diff --git a/rvc/logs/mute/2a_f0/mute.wav.npy b/rvc/logs/mute/2a_f0/mute.wav.npy new file mode 100644 index 0000000000000000000000000000000000000000..a7ecfbf9295b11a58fa1316e03ac3d0e85fa3ad6 GIT binary patch literal 1332 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= oXCxM+0{I%o28KGCItsN4WCJcn1_p*vJQ@b0X<#%B43#th0Co!;s{jB1 literal 0 HcmV?d00001 diff --git a/rvc/logs/mute/2b-f0nsf/mute.wav.npy b/rvc/logs/mute/2b-f0nsf/mute.wav.npy new file mode 100644 index 0000000000000000000000000000000000000000..cf5c21bd4c9bfca9d8a39708454eee44757e608d GIT binary patch literal 2536 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= xXCxM+0{I%o28KGCItsN4WCJdSQ7{?;qaiRF0;3@?8UmvsFd71*AwZK5000fP8-D-* literal 0 HcmV?d00001 diff --git a/rvc/logs/mute/3_feature256/mute.npy b/rvc/logs/mute/3_feature256/mute.npy new file mode 100644 index 0000000000000000000000000000000000000000..ffe35e78401ba03731e2ebe48ed7a73d94198a8f GIT binary patch literal 152704 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�XTz_~yy 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͏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

cHQ^4||Dk&i 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 literal 0 HcmV?d00001 diff --git a/rvc/train.py b/rvc/train.py new file mode 100644 index 0000000..883404d --- /dev/null +++ b/rvc/train.py @@ -0,0 +1,619 @@ +import os +import torch +import json +import faiss +import platform +import traceback +import pathlib +import logging +import fairseq +import numpy as np +import threading +from random import shuffle +from time import sleep +from subprocess import Popen +from sklearn.cluster import MiniBatchKMeans +from .i18n.i18n import I18nAuto +from .configs.config import Config +from .infer.modules.vc.modules import VC + +logging.getLogger("numba").setLevel(logging.WARNING) +logging.getLogger("httpx").setLevel(logging.WARNING) + +logger = logging.getLogger(__name__) + +i18n = I18nAuto() +logger.info(i18n) + +# 判断是否有能用来训练和加速推理的N卡 +ngpu = torch.cuda.device_count() +gpu_infos = [] +mem = [] +if_gpu_ok = False + +if torch.cuda.is_available() or ngpu != 0: + for i in range(ngpu): + gpu_name = torch.cuda.get_device_name(i) + if any( + value in gpu_name.upper() + for value in [ + "10", + "16", + "20", + "30", + "40", + "A2", + "A3", + "A4", + "P4", + "A50", + "500", + "A60", + "70", + "80", + "90", + "M4", + "T4", + "TITAN", + "4060", + "L", + "6000", + ] + ): + # A10#A100#V100#A40#P40#M40#K80#A4500 + if_gpu_ok = True # 至少有一张能用的N卡 + gpu_infos.append("%s\t%s" % (i, gpu_name)) + mem.append( + int( + torch.cuda.get_device_properties(i).total_memory + / 1024 + / 1024 + / 1024 + + 0.4 + ) + ) +if if_gpu_ok and len(gpu_infos) > 0: + gpu_info = "\n".join(gpu_infos) + default_batch_size = min(mem) // 2 +else: + gpu_info = i18n("很遗憾您这没有能用的显卡来支持您训练") + default_batch_size = 1 +gpus = "-".join([i[0] for i in gpu_infos]) + + +config = Config() +vc = VC(config) + +if config.dml == True: + + def forward_dml(ctx, x, scale): + ctx.scale = scale + res = x.clone().detach() + return res + + fairseq.modules.grad_multiply.GradMultiply.forward = forward_dml + +import folder_paths +now_dir = os.path.dirname(os.path.abspath(__file__)) +out_path = folder_paths.get_output_directory() +weight_root = os.path.join(out_path,"RVC") +os.makedirs(weight_root, exist_ok=True) +outside_index_root = weight_root +sr_dict = { + "32k": 32000, + "40k": 40000, + "48k": 48000, +} + +def if_done(done, p): + while 1: + if p.poll() is None: + sleep(0.5) + else: + break + done[0] = True + +def if_done_multi(done, ps): + while 1: + # poll==None代表进程未结束 + # 只要有一个进程未结束都不停 + flag = 1 + for p in ps: + if p.poll() is None: + flag = 0 + sleep(0.5) + break + if flag == 1: + break + done[0] = True + +# but3.click(click_train,[exp_dir1,sr2,if_f0_3,save_epoch10,total_epoch11,batch_size12,if_save_latest13,pretrained_G14,pretrained_D15,gpus16]) +def click_train( + exp_dir1, + sr2, + if_f0_3, + spk_id5, + save_epoch10, + total_epoch11, + batch_size12, + if_save_latest13, + pretrained_G14, + pretrained_D15, + gpus16, + if_cache_gpu17, + if_save_every_weights18, + version19, +): + # 生成filelist + exp_dir = "%s/logs/%s" % (now_dir, exp_dir1) + os.makedirs(exp_dir, exist_ok=True) + gt_wavs_dir = "%s/0_gt_wavs" % (exp_dir) + feature_dir = ( + "%s/3_feature256" % (exp_dir) + if version19 == "v1" + else "%s/3_feature768" % (exp_dir) + ) + if if_f0_3: + f0_dir = "%s/2a_f0" % (exp_dir) + f0nsf_dir = "%s/2b-f0nsf" % (exp_dir) + names = ( + set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) + & set([name.split(".")[0] for name in os.listdir(feature_dir)]) + & set([name.split(".")[0] for name in os.listdir(f0_dir)]) + & set([name.split(".")[0] for name in os.listdir(f0nsf_dir)]) + ) + else: + names = set([name.split(".")[0] for name in os.listdir(gt_wavs_dir)]) & set( + [name.split(".")[0] for name in os.listdir(feature_dir)] + ) + opt = [] + for name in names: + if if_f0_3: + opt.append( + "%s/%s.wav|%s/%s.npy|%s/%s.wav.npy|%s/%s.wav.npy|%s" + % ( + gt_wavs_dir.replace("\\", "\\\\"), + name, + feature_dir.replace("\\", "\\\\"), + name, + f0_dir.replace("\\", "\\\\"), + name, + f0nsf_dir.replace("\\", "\\\\"), + name, + spk_id5, + ) + ) + else: + opt.append( + "%s/%s.wav|%s/%s.npy|%s" + % ( + gt_wavs_dir.replace("\\", "\\\\"), + name, + feature_dir.replace("\\", "\\\\"), + name, + spk_id5, + ) + ) + fea_dim = 256 if version19 == "v1" else 768 + if if_f0_3: + for _ in range(2): + opt.append( + "%s/logs/mute/0_gt_wavs/mute%s.wav|%s/logs/mute/3_feature%s/mute.npy|%s/logs/mute/2a_f0/mute.wav.npy|%s/logs/mute/2b-f0nsf/mute.wav.npy|%s" + % (now_dir, sr2, now_dir, fea_dim, now_dir, now_dir, spk_id5) + ) + else: + for _ in range(2): + opt.append( + "%s/logs/mute/0_gt_wavs/mute%s.wav|%s/logs/mute/3_feature%s/mute.npy|%s" + % (now_dir, sr2, now_dir, fea_dim, spk_id5) + ) + shuffle(opt) + with open("%s/filelist.txt" % exp_dir, "w") as f: + f.write("\n".join(opt)) + logger.debug("Write filelist done") + # 生成config#无需生成config + # cmd = python_cmd + " train_nsf_sim_cache_sid_load_pretrain.py -e mi-test -sr 40k -f0 1 -bs 4 -g 0 -te 10 -se 5 -pg pretrained/f0G40k.pth -pd pretrained/f0D40k.pth -l 1 -c 0" + logger.info("Use gpus: %s", str(gpus16)) + if pretrained_G14 == "": + logger.info("No pretrained Generator") + if pretrained_D15 == "": + logger.info("No pretrained Discriminator") + if version19 == "v1" or sr2 == "40k": + config_path = "v1/%s.json" % sr2 + else: + config_path = "v2/%s.json" % sr2 + config_save_path = os.path.join(exp_dir, "config.json") + if not pathlib.Path(config_save_path).exists(): + with open(config_save_path, "w", encoding="utf-8") as f: + json.dump( + config.json_config[config_path], + f, + ensure_ascii=False, + indent=4, + sort_keys=True, + ) + f.write("\n") + if gpus16: + cmd = ( + '"%s" %s/infer/modules/train/train.py -e "%s" -sr %s -f0 %s -bs %s -g %s -te %s -se %s %s %s -l %s -c %s -sw %s -v %s' + % ( + config.python_cmd, + now_dir, + exp_dir1, + sr2, + 1 if if_f0_3 else 0, + batch_size12, + gpus16, + total_epoch11, + save_epoch10, + "-pg %s" % pretrained_G14 if pretrained_G14 != "" else "", + "-pd %s" % pretrained_D15 if pretrained_D15 != "" else "", + 1 if if_save_latest13 == i18n("是") else 0, + 1 if if_cache_gpu17 == i18n("是") else 0, + 1 if if_save_every_weights18 == i18n("是") else 0, + version19, + ) + ) + else: + cmd = ( + '"%s" %s/infer/modules/train/train.py -e "%s" -sr %s -f0 %s -bs %s -te %s -se %s %s %s -l %s -c %s -sw %s -v %s' + % ( + config.python_cmd, + now_dir, + exp_dir1, + sr2, + 1 if if_f0_3 else 0, + batch_size12, + total_epoch11, + save_epoch10, + "-pg %s" % pretrained_G14 if pretrained_G14 != "" else "", + "-pd %s" % pretrained_D15 if pretrained_D15 != "" else "", + 1 if if_save_latest13 == i18n("是") else 0, + 1 if if_cache_gpu17 == i18n("是") else 0, + 1 if if_save_every_weights18 == i18n("是") else 0, + version19, + ) + ) + logger.info("Execute: " + cmd) + p = Popen(cmd, shell=True, cwd=now_dir) + p.wait() + return "训练结束, 您可查看控制台训练日志或实验文件夹下的train.log" + + +# but4.click(train_index, [exp_dir1], info3) +def train_index(exp_dir1, version19): + # exp_dir = "%s/logs/%s" % (now_dir, exp_dir1) + exp_dir = "%s/logs/%s" % (now_dir, exp_dir1) + os.makedirs(exp_dir, exist_ok=True) + feature_dir = ( + "%s/3_feature256" % (exp_dir) + if version19 == "v1" + else "%s/3_feature768" % (exp_dir) + ) + if not os.path.exists(feature_dir): + return "请先进行特征提取!" + listdir_res = list(os.listdir(feature_dir)) + if len(listdir_res) == 0: + return "请先进行特征提取!" + infos = [] + npys = [] + for name in sorted(listdir_res): + phone = np.load("%s/%s" % (feature_dir, name)) + npys.append(phone) + big_npy = np.concatenate(npys, 0) + big_npy_idx = np.arange(big_npy.shape[0]) + np.random.shuffle(big_npy_idx) + big_npy = big_npy[big_npy_idx] + if big_npy.shape[0] > 2e5: + infos.append("Trying doing kmeans %s shape to 10k centers." % big_npy.shape[0]) + logger.info("\n".join(infos)) + try: + big_npy = ( + MiniBatchKMeans( + n_clusters=10000, + verbose=True, + batch_size=256 * config.n_cpu, + compute_labels=False, + init="random", + ) + .fit(big_npy) + .cluster_centers_ + ) + except: + info = traceback.format_exc() + logger.info(info) + infos.append(info) + logger.info("\n".join(infos)) + + np.save("%s/total_fea.npy" % exp_dir, big_npy) + n_ivf = min(int(16 * np.sqrt(big_npy.shape[0])), big_npy.shape[0] // 39) + infos.append("%s,%s" % (big_npy.shape, n_ivf)) + logger.info("\n".join(infos)) + index = faiss.index_factory(256 if version19 == "v1" else 768, "IVF%s,Flat" % n_ivf) + # index = faiss.index_factory(256if version19=="v1"else 768, "IVF%s,PQ128x4fs,RFlat"%n_ivf) + infos.append("training") + logger.info("\n".join(infos)) + index_ivf = faiss.extract_index_ivf(index) # + index_ivf.nprobe = 1 + index.train(big_npy) + faiss.write_index( + index, + "%s/trained_IVF%s_Flat_nprobe_%s_%s_%s.index" + % (exp_dir, n_ivf, index_ivf.nprobe, exp_dir1, version19), + ) + infos.append("adding") + logger.info("\n".join(infos)) + batch_size_add = 8192 + for i in range(0, big_npy.shape[0], batch_size_add): + index.add(big_npy[i : i + batch_size_add]) + faiss.write_index( + index, + "%s/added_IVF%s_Flat_nprobe_%s_%s_%s.index" + % (exp_dir, n_ivf, index_ivf.nprobe, exp_dir1, version19), + ) + infos.append( + "成功构建索引 added_IVF%s_Flat_nprobe_%s_%s_%s.index" + % (n_ivf, index_ivf.nprobe, exp_dir1, version19) + ) + try: + import shutil + shutil.copy( + "%s/added_IVF%s_Flat_nprobe_%s_%s_%s.index" + % (exp_dir, n_ivf, index_ivf.nprobe, exp_dir1, version19), + "%s/%s_IVF%s_Flat_nprobe_%s_%s_%s.index" + % ( + outside_index_root, + exp_dir1, + n_ivf, + index_ivf.nprobe, + exp_dir1, + version19, + ), + ) + infos.append("链接索引到外部-%s" % (outside_index_root)) + except: + infos.append("链接索引到外部-%s失败" % (outside_index_root)) + + # faiss.write_index(index, '%s/added_IVF%s_Flat_FastScan_%s.index'%(exp_dir,n_ivf,version19)) + # infos.append("成功构建索引,added_IVF%s_Flat_FastScan_%s.index"%(n_ivf,version19)) + logger.info("\n".join(infos)) + +# but5.click(train1key, [exp_dir1, sr2, if_f0_3, trainset_dir4, spk_id5, gpus6, np7, f0method8, save_epoch10, total_epoch11, batch_size12, if_save_latest13, pretrained_G14, pretrained_D15, gpus16, if_cache_gpu17], info3) +def train1key( + exp_dir1, + sr2, + if_f0_3, + trainset_dir4, + spk_id5, + np7, + f0method8, + save_epoch10, + total_epoch11, + batch_size12, + if_save_latest13, + pretrained_G14, + pretrained_D15, + gpus16, + if_cache_gpu17, + if_save_every_weights18, + version19, + gpus_rmvpe, +): + infos = [] + + def get_info_str(strr): + infos.append(strr) + return "\n".join(infos) + + # step3a:训练模型 + logger.info(get_info_str(i18n("step3a:正在训练模型"))) + click_train( + exp_dir1, + sr2, + if_f0_3, + spk_id5, + save_epoch10, + total_epoch11, + batch_size12, + if_save_latest13, + pretrained_G14, + pretrained_D15, + gpus16, + if_cache_gpu17, + if_save_every_weights18, + version19, + ) + logger.info(get_info_str( + i18n("训练结束, 您可查看控制台训练日志或实验文件夹下的train.log") + )) + + # step3b:训练索引 + train_index(exp_dir1, version19) + logger.info(get_info_str(i18n("全流程结束!"))) + + +def extract_f0_feature(gpus, n_p, f0method, if_f0, exp_dir, version19, gpus_rmvpe): + gpus = gpus.split("-") + os.makedirs("%s/logs/%s" % (now_dir, exp_dir), exist_ok=True) + f = open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "w") + f.close() + if if_f0: + if f0method != "rmvpe_gpu": + cmd = ( + '"%s" %s/infer/modules/train/extract/extract_f0_print.py "%s/logs/%s" %s %s' + % ( + config.python_cmd, + now_dir, + now_dir, + exp_dir, + n_p, + f0method, + ) + ) + logger.info("Execute: " + cmd) + p = Popen( + cmd, shell=True, cwd=now_dir + ) # , stdin=PIPE, stdout=PIPE,stderr=PIPE + # 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读 + done = [False] + threading.Thread( + target=if_done, + args=( + done, + p, + ), + ).start() + else: + if gpus_rmvpe != "-": + gpus_rmvpe = gpus_rmvpe.split("-") + leng = len(gpus_rmvpe) + ps = [] + for idx, n_g in enumerate(gpus_rmvpe): + cmd = ( + '"%s" %s/infer/modules/train/extract/extract_f0_rmvpe.py %s %s %s "%s/logs/%s" %s ' + % ( + config.python_cmd, + now_dir, + leng, + idx, + n_g, + now_dir, + exp_dir, + config.is_half, + ) + ) + logger.info("Execute: " + cmd) + p = Popen( + cmd, shell=True, cwd=now_dir + ) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir + ps.append(p) + # 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读 + done = [False] + threading.Thread( + target=if_done_multi, # + args=( + done, + ps, + ), + ).start() + else: + cmd = ( + config.python_cmd + + ' %s/infer/modules/train/extract/extract_f0_rmvpe_dml.py "%s/logs/%s" ' + % ( + now_dir, + now_dir, + exp_dir, + ) + ) + logger.info("Execute: " + cmd) + p = Popen( + cmd, shell=True, cwd=now_dir + ) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir + p.wait() + done = [True] + while 1: + with open( + "%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r" + ) as f: + logger.info(f.read()) + sleep(1) + if done[0]: + break + with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f: + log = f.read() + logger.info(log) + # yield log + # 对不同part分别开多进程 + """ + n_part=int(sys.argv[1]) + i_part=int(sys.argv[2]) + i_gpu=sys.argv[3] + exp_dir=sys.argv[4] + os.environ["CUDA_VISIBLE_DEVICES"]=str(i_gpu) + """ + leng = len(gpus) + ps = [] + for idx, n_g in enumerate(gpus): + cmd = ( + '"%s" %s/infer/modules/train/extract_feature_print.py %s %s %s %s "%s/logs/%s" %s %s' + % ( + config.python_cmd, + now_dir, + config.device, + leng, + idx, + n_g, + now_dir, + exp_dir, + version19, + config.is_half, + ) + ) + logger.info("Execute: " + cmd) + p = Popen( + cmd, shell=True, cwd=now_dir + ) # , shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=now_dir + ps.append(p) + # 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读 + done = [False] + threading.Thread( + target=if_done_multi, + args=( + done, + ps, + ), + ).start() + while 1: + with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f: + logger.info(f.read()) + sleep(1) + if done[0]: + break + with open("%s/logs/%s/extract_f0_feature.log" % (now_dir, exp_dir), "r") as f: + log = f.read() + logger.info(log) + #yield log + + + + +def preprocess_dataset(trainset_dir, exp_dir, sr, n_p): + sr = sr_dict[sr] + os.makedirs(os.path.join(now_dir,"logs",exp_dir), exist_ok=True) + f = open(os.path.join(now_dir,"logs",exp_dir,"preprocess.log"), "w") + f.close() + cmd = '"%s" %s/infer/modules/train/preprocess.py "%s" %s %s "%s/logs/%s" %s %.1f' % ( + config.python_cmd, + now_dir, + trainset_dir, + sr, + n_p, + now_dir, + exp_dir, + config.noparallel, + config.preprocess_per, + ) + logger.info("Execute: " + cmd) + # , stdin=PIPE, stdout=PIPE,stderr=PIPE,cwd=now_dir + p = Popen(cmd, shell=True) + # 煞笔gr, popen read都非得全跑完了再一次性读取, 不用gr就正常读一句输出一句;只能额外弄出一个文本流定时读 + done = [False] + threading.Thread( + target=if_done, + args=( + done, + p, + ), + ).start() + + while 1: + with open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "r") as f: + logger.info(f.read()) + sleep(1) + if done[0]: + break + with open("%s/logs/%s/preprocess.log" % (now_dir, exp_dir), "r") as f: + log = f.read() + logger.info(log) + # yield log \ No newline at end of file diff --git a/web/js/alertMSG.js b/web/js/alertMSG.js new file mode 100644 index 0000000..23d2cab --- /dev/null +++ b/web/js/alertMSG.js @@ -0,0 +1,21 @@ +import { app } from "../../../scripts/app.js"; + +app.registerExtension({ + name: "RVC.alertMSG", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData?.name == "RVC_Train") { + nodeType.prototype.onExecuted = function (data) { + // alert("Success!you can find weights in:\n" + data.finetune[0] + "\n" + data.finetune[1] + "\n Now you can tts or inference"); + let msg = "Success! you can find weights in:\n" + data.train[0] + "\n you'd like to reboot the server to inference?" + if (confirm(msg)) { + try { + api.fetchApi("/rvc/reboot"); + } + catch(exception) { + console.log(exception); + } + } + } + } + }, +}); \ No newline at end of file diff --git a/web/js/previewAudio.js b/web/js/previewAudio.js new file mode 100644 index 0000000..b15b75d --- /dev/null +++ b/web/js/previewAudio.js @@ -0,0 +1,155 @@ +import { app } from "../../../scripts/app.js"; +import { api } from '../../../scripts/api.js' + +function fitHeight(node) { + node.setSize([node.size[0], node.computeSize([node.size[0], node.size[1]])[1]]) + node?.graph?.setDirtyCanvas(true); +} +function chainCallback(object, property, callback) { + if (object == undefined) { + //This should not happen. + console.error("Tried to add callback to non-existant object") + return; + } + if (property in object) { + const callback_orig = object[property] + object[property] = function () { + const r = callback_orig.apply(this, arguments); + callback.apply(this, arguments); + return r + }; + } else { + object[property] = callback; + } +} + +function addPreviewOptions(nodeType) { + chainCallback(nodeType.prototype, "getExtraMenuOptions", function(_, options) { + // The intended way of appending options is returning a list of extra options, + // but this isn't used in widgetInputs.js and would require + // less generalization of chainCallback + let optNew = [] + try { + const previewWidget = this.widgets.find((w) => w.name === "audiopreview"); + + let url = null + if (previewWidget.audioEl?.hidden == false && previewWidget.audioEl.src) { + //Use full quality audio + //url = api.apiURL('/view?' + new URLSearchParams(previewWidget.value.params)); + url = previewWidget.audioEl.src + } + if (url) { + optNew.push( + { + content: "Open preview", + callback: () => { + window.open(url, "_blank") + }, + }, + { + content: "Save preview", + callback: () => { + const a = document.createElement("a"); + a.href = url; + a.setAttribute("download", new URLSearchParams(previewWidget.value.params).get("filename")); + document.body.append(a); + a.click(); + requestAnimationFrame(() => a.remove()); + }, + } + ); + } + if(options.length > 0 && options[0] != null && optNew.length > 0) { + optNew.push(null); + } + options.unshift(...optNew); + + } catch (error) { + console.log(error); + } + + }); +} +function previewAudio(node,file,type){ + var element = document.createElement("div"); + const previewNode = node; + var previewWidget = node.addDOMWidget("audiopreview", "preview", element, { + serialize: false, + hideOnZoom: false, + getValue() { + return element.value; + }, + setValue(v) { + element.value = v; + }, + }); + previewWidget.computeSize = function(width) { + if (this.aspectRatio && !this.parentEl.hidden) { + let height = (previewNode.size[0]-20)/ this.aspectRatio + 10; + if (!(height > 0)) { + height = 0; + } + this.computedHeight = height + 10; + return [width, height]; + } + return [width, -4];//no loaded src, widget should not display + } + // element.style['pointer-events'] = "none" + previewWidget.value = {hidden: false, paused: false, params: {}} + previewWidget.parentEl = document.createElement("div"); + previewWidget.parentEl.className = "audio_preview"; + previewWidget.parentEl.style['width'] = "100%" + element.appendChild(previewWidget.parentEl); + previewWidget.audioEl = document.createElement("audio"); + previewWidget.audioEl.controls = true; + previewWidget.audioEl.loop = false; + previewWidget.audioEl.muted = false; + previewWidget.audioEl.style['width'] = "100%" + previewWidget.audioEl.addEventListener("loadedmetadata", () => { + + previewWidget.aspectRatio = previewWidget.audioEl.audioWidth / previewWidget.audioEl.audioHeight; + fitHeight(this); + }); + previewWidget.audioEl.addEventListener("error", () => { + //TODO: consider a way to properly notify the user why a preview isn't shown. + previewWidget.parentEl.hidden = true; + fitHeight(this); + }); + + let params = { + "filename": file, + "type": type, + } + + previewWidget.parentEl.hidden = previewWidget.value.hidden; + previewWidget.audioEl.autoplay = !previewWidget.value.paused && !previewWidget.value.hidden; + let target_width = 256 + if (element.style?.width) { + //overscale to allow scrolling. Endpoint won't return higher than native + target_width = element.style.width.slice(0,-2)*2; + } + if (!params.force_size || params.force_size.includes("?") || params.force_size == "Disabled") { + params.force_size = target_width+"x?" + } else { + let size = params.force_size.split("x") + let ar = parseInt(size[0])/parseInt(size[1]) + params.force_size = target_width+"x"+(target_width/ar) + } + + previewWidget.audioEl.src = api.apiURL('/view?' + new URLSearchParams(params)); + + previewWidget.audioEl.hidden = false; + previewWidget.parentEl.appendChild(previewWidget.audioEl) +} + +app.registerExtension({ + name: "RVC.AudioPreviewer", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData?.name == "PreViewAudio") { + nodeType.prototype.onExecuted = function (data) { + previewAudio(this, data.audio[0], data.audio[1]); + } + addPreviewOptions(nodeType) + } + } +}); \ No newline at end of file diff --git a/web/js/refreshPath.js b/web/js/refreshPath.js new file mode 100644 index 0000000..8fe2b5d --- /dev/null +++ b/web/js/refreshPath.js @@ -0,0 +1,36 @@ +import { app } from "../../../scripts/app.js"; +import { api } from '../../../scripts/api.js' +import { ComfyWidgets } from "../../../scripts/widgets.js" +function rebootAPI() { + if (confirm("Are you sure you'd like to reboot the server to refresh weights path?")) { + try { + api.fetchApi("/rvc/reboot"); + } + catch(exception) { + + } + return true; + } + + return false; +} +function pathRefresh(node, inputName, inputData, app) { + // Create the button widget for selecting the files + let refreshWidget = node.addWidget("button", "REBOOT TO REFRESH SID LIST", "refresh", () => { + rebootAPI() + }); + + refreshWidget.serialize = false; + + return { widget: refreshWidget }; +} +ComfyWidgets.PATHREFRESH = pathRefresh; + +app.registerExtension({ + name: "RVC.RefreshPath", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData?.name == "RVC_Infer") { + nodeData.input.required.upload = ["PATHREFRESH"]; + } + }, +}); \ No newline at end of file diff --git a/web/js/uploadAudio.js b/web/js/uploadAudio.js new file mode 100644 index 0000000..d16c0e1 --- /dev/null +++ b/web/js/uploadAudio.js @@ -0,0 +1,203 @@ +import { app } from "../../../scripts/app.js"; +import { api } from '../../../scripts/api.js' +import { ComfyWidgets } from "../../../scripts/widgets.js" + +function fitHeight(node) { + node.setSize([node.size[0], node.computeSize([node.size[0], node.size[1]])[1]]) + node?.graph?.setDirtyCanvas(true); +} + +function previewAudio(node,file){ + while (node.widgets.length > 2){ + node.widgets.pop(); + } + try { + var el = document.getElementById("uploadAudio"); + el.remove(); + } catch (error) { + console.log(error); + } + var element = document.createElement("div"); + element.id = "uploadAudio"; + const previewNode = node; + var previewWidget = node.addDOMWidget("audiopreview", "preview", element, { + serialize: false, + hideOnZoom: false, + getValue() { + return element.value; + }, + setValue(v) { + element.value = v; + }, + }); + previewWidget.computeSize = function(width) { + if (this.aspectRatio && !this.parentEl.hidden) { + let height = (previewNode.size[0]-20)/ this.aspectRatio + 10; + if (!(height > 0)) { + height = 0; + } + this.computedHeight = height + 10; + return [width, height]; + } + return [width, -4];//no loaded src, widget should not display + } + // element.style['pointer-events'] = "none" + previewWidget.value = {hidden: false, paused: false, params: {}} + previewWidget.parentEl = document.createElement("div"); + previewWidget.parentEl.className = "audio_preview"; + previewWidget.parentEl.style['width'] = "100%" + element.appendChild(previewWidget.parentEl); + previewWidget.audioEl = document.createElement("audio"); + previewWidget.audioEl.controls = true; + previewWidget.audioEl.loop = false; + previewWidget.audioEl.muted = false; + previewWidget.audioEl.style['width'] = "100%" + previewWidget.audioEl.addEventListener("loadedmetadata", () => { + + previewWidget.aspectRatio = previewWidget.audioEl.audioWidth / previewWidget.audioEl.audioHeight; + fitHeight(this); + }); + previewWidget.audioEl.addEventListener("error", () => { + //TODO: consider a way to properly notify the user why a preview isn't shown. + previewWidget.parentEl.hidden = true; + fitHeight(this); + }); + + let params = { + "filename": file, + "type": "input", + } + + previewWidget.parentEl.hidden = previewWidget.value.hidden; + previewWidget.audioEl.autoplay = !previewWidget.value.paused && !previewWidget.value.hidden; + let target_width = 256 + if (element.style?.width) { + //overscale to allow scrolling. Endpoint won't return higher than native + target_width = element.style.width.slice(0,-2)*2; + } + if (!params.force_size || params.force_size.includes("?") || params.force_size == "Disabled") { + params.force_size = target_width+"x?" + } else { + let size = params.force_size.split("x") + let ar = parseInt(size[0])/parseInt(size[1]) + params.force_size = target_width+"x"+(target_width/ar) + } + + previewWidget.audioEl.src = api.apiURL('/view?' + new URLSearchParams(params)); + + previewWidget.audioEl.hidden = false; + previewWidget.parentEl.appendChild(previewWidget.audioEl) +} + +function audioUpload(node, inputName, inputData, app) { + const audioWidget = node.widgets.find((w) => w.name === "audio"); + let uploadWidget; + /* + A method that returns the required style for the html + */ + var default_value = audioWidget.value; + Object.defineProperty(audioWidget, "value", { + set : function(value) { + this._real_value = value; + }, + + get : function() { + let value = ""; + if (this._real_value) { + value = this._real_value; + } else { + return default_value; + } + + if (value.filename) { + let real_value = value; + value = ""; + if (real_value.subfolder) { + value = real_value.subfolder + "/"; + } + + value += real_value.filename; + + if(real_value.type && real_value.type !== "input") + value += ` [${real_value.type}]`; + } + return value; + } + }); + async function uploadFile(file, updateNode, pasted = false) { + try { + // Wrap file in formdata so it includes filename + const body = new FormData(); + body.append("image", file); + if (pasted) body.append("subfolder", "pasted"); + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + // Add the file to the dropdown list and update the widget value + let path = data.name; + if (data.subfolder) path = data.subfolder + "/" + path; + + if (!audioWidget.options.values.includes(path)) { + audioWidget.options.values.push(path); + } + + if (updateNode) { + audioWidget.value = path; + previewAudio(node,path) + + } + } else { + alert(resp.status + " - " + resp.statusText); + } + } catch (error) { + alert(error); + } + } + + const fileInput = document.createElement("input"); + Object.assign(fileInput, { + type: "file", + accept: "audio/mp3,audio/wav,audio/flac,audio/m4a", + style: "display: none", + onchange: async () => { + if (fileInput.files.length) { + await uploadFile(fileInput.files[0], true); + } + }, + }); + document.body.append(fileInput); + + // Create the button widget for selecting the files + uploadWidget = node.addWidget("button", "choose audio file to upload", "Audio", () => { + fileInput.click(); + }); + + uploadWidget.serialize = false; + + previewAudio(node, audioWidget.value); + const cb = node.callback; + audioWidget.callback = function () { + previewAudio(node,audioWidget.value); + if (cb) { + return cb.apply(this, arguments); + } + }; + + return { widget: uploadWidget }; +} + +ComfyWidgets.AUDIOPLOAD = audioUpload; + +app.registerExtension({ + name: "RVC.UploadAudio", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData?.name == "LoadAudio") { + nodeData.input.required.upload = ["AUDIOPLOAD"]; + } + }, +}); +