Replace UVR5 separation backend with pymss

Add the five-model MSST backend, CUDA precision reuse, fast MP3/M4A encoding, PyAV compatibility fixes, remote dependencies, model configs, Hugging Face download guidance, and multilingual pymss credits. Remove the obsolete tools/uvr5 implementation.
This commit is contained in:
RVC-Boss
2026-07-23 01:40:12 +08:00
parent dbf85f721c
commit 132126af72
36 changed files with 943 additions and 3767 deletions

View File

@@ -48,7 +48,7 @@
+ 使用少量数据进行训练也能得到较好结果(推荐至少收集10分钟低底噪语音数据)
+ 可以通过模型融合来改变音色(借助ckpt处理选项卡中的ckpt-merge)
+ 简单易用的网页界面
+ 可调用UVR5模型来快速分离人声和伴奏
+ 可调用pymss/MSST模型来快速分离人声和伴奏
+ 使用最先进的[人声音高提取算法InterSpeech2023-RMVPE](#参考项目)根绝哑音问题,速度快、资源占用小
+ A卡/I卡使用 CPU 依赖方案Windows 可使用 DirectMLLinux 使用 CPU
@@ -142,7 +142,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -155,7 +155,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -179,9 +179,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
仅 Windows AMD/Intel DirectML 环境还需要:
@@ -222,6 +222,7 @@ python webui.py --noautoopen
+ [Gradio](https://github.com/gradio-app/gradio)
+ [FFmpeg](https://github.com/FFmpeg/FFmpeg)
+ [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
+ [pymss-project/pymss](https://github.com/pymss-project/pymss)
+ [audio-slicer](https://github.com/openvpi/audio-slicer)
+ [Vocal pitch extraction:RMVPE](https://github.com/Dream-High/RMVPE)
+ The pretrained model is trained and tested by [yxlllc](https://github.com/yxlllc/RMVPE) and [RVC-Boss](https://github.com/RVC-Boss).

View File

@@ -0,0 +1,71 @@
audio:
chunk_size: 352800
dim_f: 1024
dim_t: 256
hop_length: 441
n_fft: 2048
num_channels: 2
sample_rate: 44100
min_mean_abs: 000
model:
dim: 384
depth: 6
stereo: true
num_stems: 1
time_transformer_depth: 1
freq_transformer_depth: 1
num_bands: 60
dim_head: 64
heads: 8
attn_dropout: 0
ff_dropout: 0
flash_attn: True
dim_freqs_in: 1025
sample_rate: 44100 # needed for mel filter bank from librosa
stft_n_fft: 2048
stft_hop_length: 441
stft_win_length: 2048
stft_normalized: False
mask_estimator_depth: 2
multi_stft_resolution_loss_weight: 1.0
multi_stft_resolutions_window_sizes: !!python/tuple
- 4096
- 2048
- 1024
- 512
- 256
multi_stft_hop_size: 147
multi_stft_normalized: False
training:
batch_size: 4
gradient_accumulation_steps: 1
grad_clip: 0
instruments:
- karaoke
- other
lr: 1.0e-05
patience: 2
reduce_factor: 0.95
target_instrument: karaoke
num_epochs: 1000
num_steps: 2000
augmentation: false # enable augmentations by audiomentations and pedalboard
augmentation_type: null
use_mp3_compress: false # Deprecated
augmentation_mix: false # Mix several stems of the same type with some probability
augmentation_loudness: false # randomly change loudness of each stem
augmentation_loudness_type: 1 # Type 1 or 2
augmentation_loudness_min: 0
augmentation_loudness_max: 0
q: 0.95
coarse_loss_clip: false
ema_momentum: 0.999
optimizer: adam
other_fix: false # it's needed for checking on multisong dataset if other is actually instrumental
inference:
batch_size: 1
dim_t: 256
num_overlap: 4

View File

@@ -0,0 +1,76 @@
audio:
chunk_size: 352800
dim_f: 1024
dim_t: 256
hop_length: 441
n_fft: 2048
num_channels: 2
sample_rate: 44100
min_mean_abs: 0.000
model:
dim: 384
depth: 6
stereo: true
num_stems: 1
time_transformer_depth: 1
freq_transformer_depth: 1
num_bands: 60
dim_head: 64
heads: 8
attn_dropout: 0
ff_dropout: 0
flash_attn: True
dim_freqs_in: 1025
sample_rate: 44100 # needed for mel filter bank from librosa
stft_n_fft: 2048
stft_hop_length: 441
stft_win_length: 2048
stft_normalized: False
mask_estimator_depth: 2
multi_stft_resolution_loss_weight: 1.0
multi_stft_resolutions_window_sizes: !!python/tuple
- 4096
- 2048
- 1024
- 512
- 256
multi_stft_hop_size: 147
multi_stft_normalized: False
training:
batch_size: 3
gradient_accumulation_steps: 1
grad_clip: 0
instruments:
- noreverb
- reverb
lr: 5.0e-05
patience: 2
reduce_factor: 0.95
target_instrument: noreverb
num_epochs: 1000
num_steps: 4000
q: 0.95
coarse_loss_clip: false
ema_momentum: 0.999
optimizer: adamw
other_fix: true # it's needed for checking on multisong dataset if other is actually instrumental
use_amp: true # enable or disable usage of mixed precision (float16) - usually it must be true
augmentations:
enable: true # enable or disable all augmentations (to fast disable if needed)
loudness: true # randomly change loudness of each stem on the range (loudness_min; loudness_max)
loudness_min: 0.1
loudness_max: 1.0
mixup: false # mix several stems of same type with some probability (only works for dataset types: 1, 2, 3)
mixup_probs: !!python/tuple # 2 additional stems of the same type (1st with prob 0.2, 2nd with prob 0.02)
- 0.2
- 0.02
mixup_loudness_min: 0.5
mixup_loudness_max: 1.5
inference:
batch_size: 1
dim_t: 801
num_overlap: 2

View File

@@ -0,0 +1,123 @@
audio:
chunk_size: 352800
dim_f: 1024
dim_t: 801
hop_length: 441
min_mean_abs: 0.0
n_fft: 2048
num_channels: 2
sample_rate: 44100
inference:
batch_size: 4
dim_t: 801
num_overlap: 2
model:
attn_dropout: 0.1
depth: 12
dim: 512
dim_freqs_in: 1025
dim_head: 64
ff_dropout: 0.1
flash_attn: true
freq_transformer_depth: 1
freqs_per_bands: !!python/tuple
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 12
- 12
- 12
- 12
- 12
- 12
- 12
- 12
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 48
- 48
- 48
- 48
- 48
- 48
- 48
- 48
- 128
- 129
heads: 8
linear_transformer_depth: 0
mask_estimator_depth: 2
multi_stft_hop_size: 147
multi_stft_normalized: false
multi_stft_resolution_loss_weight: 1.0
multi_stft_resolutions_window_sizes: !!python/tuple
- 4096
- 2048
- 1024
- 512
- 256
num_stems: 1
stereo: true
stft_hop_length: 441
stft_n_fft: 2048
stft_normalized: false
stft_win_length: 2048
time_transformer_depth: 1
training:
batch_size: 2
coarse_loss_clip: true
ema_momentum: 0.999
grad_clip: 0
gradient_accumulation_steps: 1
instruments:
- vocals
- other
lr: 1.0e-05
num_epochs: 1000
num_steps: 1000
optimizer: adam
other_fix: true
patience: 2
q: 0.95
reduce_factor: 0.95
target_instrument: vocals
use_amp: true

View File

@@ -0,0 +1,133 @@
audio:
chunk_size: 352800
dim_f: 1024
dim_t: 801 # don't work (use in model)
hop_length: 441 # don't work (use in model)
n_fft: 2048
num_channels: 2
sample_rate: 44100
min_mean_abs: 0.001
model:
dim: 512
depth: 12
stereo: true
num_stems: 1
time_transformer_depth: 1
freq_transformer_depth: 1
freqs_per_bands: !!python/tuple
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 2
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 4
- 12
- 12
- 12
- 12
- 12
- 12
- 12
- 12
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 48
- 48
- 48
- 48
- 48
- 48
- 48
- 48
- 128
- 129
dim_head: 64
heads: 8
attn_dropout: 0.1
ff_dropout: 0.1
flash_attn: true
dim_freqs_in: 1025
stft_n_fft: 2048
stft_hop_length: 441
stft_win_length: 2048
stft_normalized: false
mask_estimator_depth: 2
multi_stft_resolution_loss_weight: 1.0
multi_stft_resolutions_window_sizes: !!python/tuple
- 4096
- 2048
- 1024
- 512
- 256
multi_stft_hop_size: 147
multi_stft_normalized: False
training:
batch_size: 16
gradient_accumulation_steps: 1
grad_clip: 0
instruments:
- vocals
- instrumental
lr: 5.0e-05
patience: 2
reduce_factor: 0.95
target_instrument: vocals
num_epochs: 1000
num_steps: 1000
augmentation: false # enable augmentations by audiomentations and pedalboard
augmentation_type: simple1
use_mp3_compress: false # Deprecated
augmentation_mix: true # Mix several stems of the same type with some probability
augmentation_loudness: true # randomly change loudness of each stem
augmentation_loudness_type: 1 # Type 1 or 2
augmentation_loudness_min: 0.5
augmentation_loudness_max: 1.5
q: 0.95
coarse_loss_clip: true
ema_momentum: 0.999
optimizer: adam
other_fix: false # it's needed for checking on multisong dataset if other is actually instrumental
use_amp: true # enable or disable usage of mixed precision (float16) - usually it must be true
inference:
batch_size: 1
dim_t: 901
num_overlap: 4

View File

@@ -51,7 +51,7 @@ A simple, easy-to-use voice timbre conversion / voice changer framework.<br><br>
+ Training with a small amounts of data (>=10min low noise speech recommended);
+ Model fusion to change timbres (using ckpt processing tab->ckpt merge);
+ Easy-to-use WebUI;
+ UVR5 model to quickly separate vocals and instruments;
+ pymss/MSST model to quickly separate vocals and instruments;
+ High-pitch Voice Extraction Algorithm [InterSpeech2023-RMVPE](#Credits) to prevent a muted sound problem. Provides the best results (significantly) and is faster with lower resource consumption than Crepe_full;
+ AMD/Intel systems use the CPU dependency set; Windows may use DirectML and Linux uses CPU;
@@ -143,7 +143,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -156,7 +156,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -180,9 +180,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Windows AMD/Intel DirectML environments additionally need:
@@ -221,6 +221,7 @@ The default port is `7865`. Put personal `.pth` models in `assets/weights/` and
+ [Gradio](https://github.com/gradio-app/gradio)
+ [FFmpeg](https://github.com/FFmpeg/FFmpeg)
+ [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
+ [pymss-project/pymss](https://github.com/pymss-project/pymss)
+ [audio-slicer](https://github.com/openvpi/audio-slicer)
+ [Vocal pitch extraction:RMVPE](https://github.com/Dream-High/RMVPE)
+ The pretrained model is trained and tested by [yxlllc](https://github.com/yxlllc/RMVPE) and [RVC-Boss](https://github.com/RVC-Boss).

View File

@@ -35,7 +35,7 @@ Ce dépôt a les caractéristiques suivantes :
+ Obtient de bons résultats même avec peu de données pour la formation (il est recommandé de collecter au moins 10 minutes de données vocales avec un faible bruit de fond).
+ Peut changer le timbre vocal en fusionnant des modèles (avec l'aide de l'onglet ckpt-merge).
+ Interface web simple et facile à utiliser.
+ Peut appeler le modèle UVR5 pour séparer rapidement la voix et l'accompagnement.
+ Peut appeler le modèle pymss/MSST pour séparer rapidement la voix et l'accompagnement.
+ Utilise l'algorithme de pitch vocal le plus avancé [InterSpeech2023-RMVPE](#projets-référencés) pour éliminer les problèmes de voix muette. Meilleurs résultats, plus rapide que crepe_full, et moins gourmand en ressources.
+ Les systèmes AMD/Intel utilisent les dépendances CPU ; Windows peut utiliser DirectML et Linux utilise le CPU.
@@ -127,7 +127,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -140,7 +140,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -164,9 +164,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Les environnements Windows AMD/Intel DirectML nécessitent aussi :
@@ -205,6 +205,7 @@ Le port par défaut est `7865`. Placez les modèles `.pth` dans `assets/weights/
+ [Gradio](https://github.com/gradio-app/gradio)
+ [FFmpeg](https://github.com/FFmpeg/FFmpeg)
+ [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
+ [pymss-project/pymss](https://github.com/pymss-project/pymss)
+ [audio-slicer](https://github.com/openvpi/audio-slicer)
+ [Extraction de la hauteur vocale : RMVPE](https://github.com/Dream-High/RMVPE)
+ Le modèle pré-entraîné a été formé et testé par [yxlllc](https://github.com/yxlllc/RMVPE) et [RVC-Boss](https://github.com/RVC-Boss).

View File

@@ -53,7 +53,7 @@
- 少量のデータセットからでも、比較的良い結果を得ることができます。10 分以上のノイズの少ない音声を推奨します。)
- モデルを融合することで、音声を混ぜることができます。ckpt processing タブの、ckpt merge を使用します。)
- 使いやすい WebUI。
- UVR5 Model も含んでいるため、人の声と BGM を素早く分離できます。
- pymss/MSST Model も含んでいるため、人の声と BGM を素早く分離できます。
- 最先端の[人間の声のピッチ抽出アルゴリズム InterSpeech2023-RMVPE](#参照プロジェクト)を使用して無声音問題を解決します。効果は最高著しくで、crepe_full よりも速く、リソース使用が少ないです。
- A カードと I カードの加速サポート
@@ -147,7 +147,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -160,7 +160,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -184,9 +184,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Windows の AMD/Intel DirectML 環境では、さらに次のファイルが必要です。
@@ -226,6 +226,7 @@ python webui.py --noautoopen
- [Gradio](https://github.com/gradio-app/gradio)
- [FFmpeg](https://github.com/FFmpeg/FFmpeg)
- [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
- [pymss-project/pymss](https://github.com/pymss-project/pymss)
- [audio-slicer](https://github.com/openvpi/audio-slicer)
- [Vocal pitch extraction:RMVPE](https://github.com/Dream-High/RMVPE)
- 事前訓練されたモデルは[yxlllc](https://github.com/yxlllc/RMVPE)と[RVC-Boss](https://github.com/RVC-Boss)によって訓練され、テストされました。

View File

@@ -34,7 +34,7 @@
+ 적은量의 데이터로 訓練해도 좋은 結果를 얻을 수 있음 (最小10分以上의 低雜음音聲데이터를 使用하는 것을 勸獎);
+ 모델融合을通한 音色의 變調可能 (ckpt處理탭->ckpt混合選擇);
+ 使用하기 쉬운 WebUI (웹 使用者인터페이스);
+ UVR5 모델을 利用하여 목소리와 背景音樂의 빠른 分離;
+ pymss/MSST 모델을 利用하여 목소리와 背景音樂의 빠른 分離;
## 環境의 準備
@@ -117,7 +117,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -130,7 +130,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -154,9 +154,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Windows AMD/Intel DirectML 환경에는 다음 파일도 필요합니다.
@@ -195,6 +195,7 @@ python webui.py --noautoopen
+ [Gradio](https://github.com/gradio-app/gradio)
+ [FFmpeg](https://github.com/FFmpeg/FFmpeg)
+ [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
+ [pymss-project/pymss](https://github.com/pymss-project/pymss)
+ [audio-slicer](https://github.com/openvpi/audio-slicer)
## 모든寄與者분들의勞力에感謝드립니다

View File

@@ -53,7 +53,7 @@
- 적은 양의 데이터로 훈련해도 좋은 결과를 얻을 수 있음 (최소 10분 이상의 저잡음 음성 데이터를 사용하는 것을 권장)
- 모델 융합을 통한 음색의 변조 가능 (ckpt 처리 탭->ckpt 병합 선택)
- 사용하기 쉬운 WebUI (웹 인터페이스)
- UVR5 모델을 이용하여 목소리와 배경음악의 빠른 분리;
- pymss/MSST 모델을 이용하여 목소리와 배경음악의 빠른 분리;
- 최첨단 [음성 피치 추출 알고리즘 InterSpeech2023-RMVPE](#参考项目)을 사용하여 무성음 문제를 해결합니다. 효과는 최고(압도적)이며 crepe_full보다 더 빠르고 리소스 사용이 적음
- A카드와 I카드 가속을 지원
@@ -147,7 +147,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -160,7 +160,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -184,9 +184,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Windows AMD/Intel DirectML 환경에는 다음 파일도 필요합니다.
@@ -226,6 +226,7 @@ python webui.py --noautoopen
- [Gradio](https://github.com/gradio-app/gradio)
- [FFmpeg](https://github.com/FFmpeg/FFmpeg)
- [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
- [pymss-project/pymss](https://github.com/pymss-project/pymss)
- [audio-slicer](https://github.com/openvpi/audio-slicer)
- [Vocal pitch extraction:RMVPE](https://github.com/Dream-High/RMVPE)
- 사전 훈련된 모델은 [yxlllc](https://github.com/yxlllc/RMVPE)와 [RVC-Boss](https://github.com/RVC-Boss)에 의해 훈련되고 테스트되었습니다.

View File

@@ -42,7 +42,7 @@ Este repositório possui os seguintes recursos:
+ Treinar com uma pequena quantidade de dados também obtém resultados relativamente bons (>=10min de áudio com baixo ruído recomendado);
+ Suporta fusão de modelos para alterar timbres (usando guia de processamento ckpt-> mesclagem ckpt);
+ Interface Webui fácil de usar;
+ Use o modelo UVR5 para separar rapidamente vocais e instrumentos.
+ Use o modelo pymss/MSST para separar rapidamente vocais e instrumentos.
+ Use o mais poderoso algoritmo de extração de voz de alta frequência [InterSpeech2023-RMVPE](#Credits) para evitar o problema de som mudo. Fornece os melhores resultados (significativamente) e é mais rápido, com consumo de recursos ainda menor que o Crepe_full.
+ Sistemas AMD/Intel usam as dependências de CPU; Windows pode usar DirectML e Linux usa CPU.
@@ -134,7 +134,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -147,7 +147,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -171,9 +171,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Ambientes Windows AMD/Intel DirectML também precisam de:
@@ -212,6 +212,7 @@ A porta padrão é `7865`. Coloque modelos `.pth` em `assets/weights/` e arquivo
+ [Gradio](https://github.com/gradio-app/gradio)
+ [FFmpeg](https://github.com/FFmpeg/FFmpeg)
+ [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
+ [pymss-project/pymss](https://github.com/pymss-project/pymss)
+ [audio-slicer](https://github.com/openvpi/audio-slicer)
+ [Vocal pitch extraction:RMVPE](https://github.com/Dream-High/RMVPE)
+ The pretrained model is trained and tested by [yxlllc](https://github.com/yxlllc/RMVPE) and [RVC-Boss](https://github.com/RVC-Boss).

View File

@@ -37,7 +37,7 @@ Bu depo aşağıdaki özelliklere sahiptir:
+ Az miktarda veriyle bile nispeten iyi sonuçlar alın (>=10 dakika düşük gürültülü konuşma önerilir);
+ Timbraları değiştirmek için model birleştirmeyi destekleme (ckpt işleme sekmesi-> ckpt birleştir);
+ Kullanımı kolay Web arayüzü;
+ UVR5 modelini kullanarak hızla vokalleri ve enstrümanları ayırma.
+ pymss/MSST modelini kullanarak hızla vokalleri ve enstrümanları ayırma.
+ En güçlü Yüksek tiz Ses Çıkarma Algoritması [InterSpeech2023-RMVPE](#Krediler) sessiz ses sorununu önlemek için kullanılır. En iyi sonuçları (önemli ölçüde) sağlar ve Crepe_full'den daha hızlı çalışır, hatta daha düşük kaynak tüketimi sağlar.
+ AMD/Intel sistemleri CPU bağımlılıklarını kullanır; Windows DirectML, Linux CPU kullanabilir.
@@ -129,7 +129,7 @@ assets/
├── rmvpe/rmvpe.pt
├── pretrained/
├── pretrained_v2/
├── uvr5_weights/
├── pymss_weights/
├── weights/ # user RVC .pth models
└── indices/ # user .index files
logs/
@@ -142,7 +142,7 @@ assets/hubert_base/pytorch_model.bin
assets/rmvpe/rmvpe.pt
assets/pretrained/*.pth
assets/pretrained_v2/*.pth
assets/uvr5_weights/*
assets/pymss_weights/*
assets/weights/*.pth
assets/indices/*.index
logs/mute/*
@@ -166,9 +166,9 @@ hf download lj1995/VoiceConversionWebUI mute.zip --revision main \
--local-dir .model-downloads
python -m zipfile -e .model-downloads/mute.zip logs
# Required only for UVR5 vocal separation
# Required only for pymss/MSST vocal separation
hf download lj1995/VoiceConversionWebUI --revision main \
--include "uvr5_weights/*" --local-dir assets
--include "pymss_weights/*" --local-dir assets
```
Windows AMD/Intel DirectML ortamlarında ayrıca şu dosya gerekir:
@@ -207,6 +207,7 @@ Varsayılan bağlantı noktası `7865`'tir. `.pth` modellerini `assets/weights/`
+ [Gradio](https://github.com/gradio-app/gradio)
+ [FFmpeg](https://github.com/FFmpeg/FFmpeg)
+ [Ultimate Vocal Remover](https://github.com/Anjok07/ultimatevocalremovergui)
+ [pymss-project/pymss](https://github.com/pymss-project/pymss)
+ [audio-slicer](https://github.com/openvpi/audio-slicer)
+ [Vokal ton çıkarma:RMVPE](https://github.com/Dream-High/RMVPE)
+ Ön eğitimli model [yxlllc](https://github.com/yxlllc/RMVPE) ve [RVC-Boss](https://github.com/RVC-Boss) tarafından eğitilip test edilmiştir.

View File

@@ -44,27 +44,58 @@ AUDIO_DTYPE = _AUDIO_DTYPE
def wav2(i, o, format):
inp = av.open(i, "r")
try:
if format == "m4a":
format = "mp4"
out = av.open(o, "w", format=format)
try:
if format == "ogg":
format = "libvorbis"
if format == "mp4":
format = "aac"
ostream = out.add_stream(format)
if not inp.streams.audio:
raise ValueError("Input contains no audio stream")
input_stream = inp.streams.audio[0]
source_rate = input_stream.codec_context.sample_rate
ostream = (
out.add_stream(format, rate=source_rate)
if source_rate
else out.add_stream(format)
)
source_channels = input_stream.codec_context.channels
if source_channels == 1:
ostream.layout = "mono"
elif source_channels == 2:
ostream.layout = "stereo"
for frame in inp.decode(audio=0):
for frame in inp.decode(input_stream):
for p in ostream.encode(frame):
out.mux(p)
for p in ostream.encode(None):
out.mux(p)
finally:
out.close()
finally:
inp.close()
def transcode_audio_file(input_path, output_path, format):
"""Transcode a WAV path and remove partial compressed output on failure."""
output_path = os.fspath(output_path)
if os.path.exists(output_path):
os.remove(output_path)
try:
wav2(input_path, output_path, format)
if not os.path.isfile(output_path) or os.path.getsize(output_path) == 0:
raise RuntimeError("Audio transcoding produced no output: %s" % output_path)
except Exception:
if os.path.exists(output_path):
os.remove(output_path)
raise
def _probe_audio(file):
info = ffmpeg.probe(file, cmd="ffprobe")
stream = next(

View File

@@ -31,6 +31,11 @@ matplotlib>=3.8.2,<4
networkx>=3.2.0,<4
numpy>=1.26.4,<2
# Five-model MSST inference backend. Install both packages from the index;
# local wheel paths are intentionally not used by this requirements file.
pymss==2.0.14
pymss-core==0.1.4
# ONNX Runtime 1.18.x is the CUDA 11 / cuDNN 8 generation with Python 3.12
# Windows wheels. The CUDA DLL packages are pinned because later cuDNN/CUDA
# majors are ABI-incompatible with this provider build.
@@ -43,7 +48,7 @@ nvidia-cufft-cu11==10.9.0.58
opencv-python-headless>=4.10.0,<5
praat-parselmouth>=0.4.5,<1
PyYAML>=6.0
PyYAML>=6.0.1
scikit-learn>=1.6.0,<2
scipy>=1.13.1,<2
sounddevice>=0.5.0,<1

View File

@@ -31,16 +31,19 @@ matplotlib>=3.8.2,<4
networkx>=3.2.0,<4
numpy>=1.26.4,<2
# Five-model MSST inference backend. Install both packages from the index;
# local wheel paths are intentionally not used by this requirements file.
pymss==2.0.14
pymss-core==0.1.4
# ONNX Runtime 1.19.x uses the CUDA 12 / cuDNN 9 provider ABI. Later current
# releases have moved to CUDA 13, so this compatibility window is intentional.
# The Torch cu128 library directory is added to the Windows DLL search path by
# tools/uvr5/mdxnet.py before the FoxJoy ONNX model is loaded.
onnxruntime-gpu>=1.19.2,<1.20
coloredlogs>=15.0,<16
opencv-python-headless>=4.10.0,<5
praat-parselmouth>=0.4.5,<1
PyYAML>=6.0
PyYAML>=6.0.1
scikit-learn>=1.6.0,<2
scipy>=1.13.1,<2
sounddevice>=0.5.0,<1

406
tools/pymss_webui.py Normal file
View File

@@ -0,0 +1,406 @@
import gc
import logging
import os
import subprocess
import threading
import time
import traceback
import uuid
from concurrent.futures import ThreadPoolExecutor, wait
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import soundfile as sf
import torch
from configs.config import Config
logger = logging.getLogger(__name__)
config = Config()
weight_pymss_root = Path(os.getenv("weight_pymss_root", "assets/pymss_weights"))
MODEL_SAMPLE_RATE = 44100
FFMPEG_PATH = Path(__file__).resolve().parents[2] / "ffmpeg.exe"
AUDIO_PARAMS = {
"wav_bit_depth": "FLOAT",
"flac_bit_depth": "PCM_24",
"mp3_bit_rate": "320k",
"m4a_bit_rate": "320k",
"m4a_codec": "aac",
"m4a_aac_at_quality": 2,
}
@dataclass(frozen=True)
class ModelSpec:
label: str
model_id: str
model_type: str
model_file: str
config_file: str
desired_stem: str
secondary_stem: str
desired_suffix: str
secondary_suffix: str
batch_size: int
overlap_size: int
MODEL_SPECS = (
ModelSpec(
label="去混响",
model_id="dereverb-less-aggressive-18.8050",
model_type="mel_band_roformer",
model_file="dereverb_mel_band_roformer_less_aggressive_anvuew_sdr_18.8050.ckpt",
config_file="dereverb_mel_band_roformer_anvuew.yaml",
desired_stem="noreverb",
secondary_stem="reverb",
desired_suffix="noreverb",
secondary_suffix="reverb",
batch_size=1,
overlap_size=176400,
),
ModelSpec(
label="去混响(激进)",
model_id="dereverb-anvuew-19.1729",
model_type="mel_band_roformer",
model_file="dereverb_mel_band_roformer_anvuew_sdr_19.1729.ckpt",
config_file="dereverb_mel_band_roformer_anvuew.yaml",
desired_stem="noreverb",
secondary_stem="reverb",
desired_suffix="noreverb",
secondary_suffix="reverb",
batch_size=1,
overlap_size=176400,
),
ModelSpec(
label="去伴奏",
model_id="vocals-bs-roformer-368",
model_type="bs_roformer",
model_file="model_bs_roformer_ep_368_sdr_12.9628.ckpt",
config_file="model_bs_roformer_ep_368_sdr_12.9628.yaml",
desired_stem="vocals",
secondary_stem="instrumental",
desired_suffix="vocals",
secondary_suffix="instrumental",
batch_size=1,
overlap_size=264600,
),
ModelSpec(
label="去伴奏(激进)",
model_id="vocals-bs-roformer-317",
model_type="bs_roformer",
model_file="model_bs_roformer_ep_317_sdr_12.9755.ckpt",
config_file="model_bs_roformer_ep_317_sdr_12.9755.yaml",
desired_stem="vocals",
secondary_stem="other",
desired_suffix="vocals",
secondary_suffix="instrumental",
batch_size=4,
overlap_size=176400,
),
ModelSpec(
label="提主旋律",
model_id="karaoke-mel-roformer-10.1956",
model_type="mel_band_roformer",
model_file="model_mel_band_roformer_karaoke_aufr33_viperx_sdr_10.1956.ckpt",
config_file="config_mel_band_roformer_karaoke.yaml",
desired_stem="karaoke",
secondary_stem="other",
desired_suffix="main_vocal",
secondary_suffix="off_vocal",
batch_size=1,
overlap_size=264600,
),
)
MODEL_BY_LABEL = {spec.label: spec for spec in MODEL_SPECS}
MODEL_BY_ID = {spec.model_id: spec for spec in MODEL_SPECS}
PYMSS_MODEL_CHOICES = [spec.label for spec in MODEL_SPECS]
UVR_INFERENCE_LOCK = threading.Lock()
def resolve_model(model_name):
if not model_name:
return MODEL_SPECS[0]
spec = MODEL_BY_LABEL.get(model_name) or MODEL_BY_ID.get(model_name)
if spec is None:
raise ValueError("Unknown separation model: %s" % model_name)
return spec
def get_model_info(model_name):
spec = resolve_model(model_name)
return "%s | %s" % (spec.model_type, spec.model_id)
def clean_path(path):
path = path or ""
if path.endswith(("\\", "/")):
path = path[:-1]
return path.replace("/", os.sep).replace("\\", os.sep).strip(" '\n\"\u202a")
def _uploaded_path(item):
if isinstance(item, (str, os.PathLike)):
return os.fspath(item)
if isinstance(item, dict):
return item.get("name") or item.get("path")
return getattr(item, "name", None)
def collect_input_paths(inp_root, paths):
inp_root = clean_path(inp_root)
if inp_root:
if os.path.isfile(inp_root):
candidates = [inp_root]
elif os.path.isdir(inp_root):
candidates = [os.path.join(inp_root, name) for name in sorted(os.listdir(inp_root))]
else:
raise FileNotFoundError(inp_root)
else:
candidates = [_uploaded_path(item) for item in (paths or [])]
return [os.path.abspath(path) for path in candidates if path and os.path.isfile(path)]
def _write_audio(path, audio, sample_rate, output_format):
audio = np.ascontiguousarray(audio, dtype=np.float32)
if audio.ndim == 1:
channels = 1
elif audio.ndim == 2 and audio.shape[1] in (1, 2):
channels = audio.shape[1]
else:
raise ValueError("Unsupported audio shape: %s" % (audio.shape,))
if output_format == "wav":
sf.write(path, audio, sample_rate, format="WAV", subtype="FLOAT")
return
if output_format == "flac":
sf.write(path, audio, sample_rate, format="FLAC", subtype="PCM_24")
return
ffmpeg = str(FFMPEG_PATH) if FFMPEG_PATH.is_file() else "ffmpeg"
command = [
ffmpeg,
"-hide_banner",
"-loglevel",
"error",
"-y",
"-f",
"f32le",
"-ar",
str(sample_rate),
"-ac",
str(channels),
"-i",
"pipe:0",
"-vn",
]
if output_format == "mp3":
command.extend(("-c:a", "libmp3lame", "-b:a", "320k"))
elif output_format == "m4a":
command.extend(("-c:a", "aac", "-aac_coder", "fast", "-b:a", "320k"))
else:
raise ValueError("Unsupported output format: %s" % output_format)
command.append(path)
completed = subprocess.run(
command,
input=audio.tobytes(),
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
)
if completed.returncode != 0:
detail = completed.stderr.decode("utf-8", errors="replace").strip()
raise RuntimeError("FFmpeg audio encoding failed: %s" % detail)
class MSSTBatchSeparator:
def __init__(self, spec, output_format, desired_root, secondary_root):
try:
from pymss import MSSeparator, load_audio
except ImportError as error:
raise RuntimeError(
"缺少 pymss 运行库,请安装对应的 CUDA 版 Python 3.12 requirements"
) from error
self.spec = spec
self.output_format = output_format.lower()
if self.output_format not in {"wav", "flac", "mp3", "m4a"}:
raise ValueError("Unsupported output format: %s" % output_format)
desired_root = clean_path(desired_root)
secondary_root = clean_path(secondary_root)
if not desired_root or not secondary_root:
raise ValueError("输出文件夹不能为空")
self.desired_root = os.path.abspath(desired_root)
self.secondary_root = os.path.abspath(secondary_root)
os.makedirs(self.desired_root, exist_ok=True)
os.makedirs(self.secondary_root, exist_ok=True)
model_path = weight_pymss_root / spec.model_file
config_path = weight_pymss_root / spec.config_file
if not model_path.is_file():
raise FileNotFoundError(model_path)
if not config_path.is_file():
raise FileNotFoundError(config_path)
parsed_device = torch.device(config.device)
use_cuda = parsed_device.type == "cuda"
device_id = parsed_device.index if use_cuda and parsed_device.index is not None else 0
self._load_audio = load_audio
self.model_load_count = 0
self.separator = MSSeparator(
model_type=spec.model_type,
model_path=str(model_path),
config_path=str(config_path),
device="cuda" if use_cuda else "cpu",
device_ids=[device_id],
output_format=self.output_format,
use_tta=False,
store_dirs={},
audio_params=AUDIO_PARAMS,
debug=False,
inference_params={
"batch_size": spec.batch_size,
"chunk_size": 352800,
"overlap_size": spec.overlap_size,
"standardize": False,
"normalize": False,
"use_amp": bool(config.is_half and use_cuda),
"cuda_attention_backend": "default",
},
)
self.separator.config.training.use_amp = bool(config.is_half and use_cuda)
self._save_pool = ThreadPoolExecutor(max_workers=2, thread_name_prefix="rvc-msst-save")
self.model_load_count = 1
logger.info(
"Loaded MSST model once for batch: %s, device=%s, half=%s",
spec.model_id,
self.separator.device,
bool(config.is_half and use_cuda),
)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback_value):
self.close()
def _save_output(self, audio, sample_rate, output_root, file_stem, suffix):
output_path = os.path.join(
output_root,
"%s_%s.%s" % (file_stem, suffix, self.output_format),
)
temp_path = os.path.join(
output_root,
".%s_%s.%s.tmp.%s"
% (file_stem, suffix, uuid.uuid4().hex, self.output_format),
)
started = time.perf_counter()
try:
_write_audio(temp_path, audio, sample_rate, self.output_format)
if not os.path.isfile(temp_path) or os.path.getsize(temp_path) == 0:
raise RuntimeError("音频编码没有生成有效文件: %s" % output_path)
os.replace(temp_path, output_path)
except Exception:
if os.path.exists(temp_path):
os.remove(temp_path)
raise
return output_path, time.perf_counter() - started
def separate_file(self, input_path):
mix, sample_rate = self._load_audio(input_path, sr=MODEL_SAMPLE_RATE, mono=False)
inference_started = time.perf_counter()
results = self.separator.separate(mix, pbar=True)
inference_seconds = time.perf_counter() - inference_started
missing = {
self.spec.desired_stem,
self.spec.secondary_stem,
}.difference(results)
if missing:
raise RuntimeError("模型缺少输出 stem: %s" % ", ".join(sorted(missing)))
file_stem = Path(input_path).stem
encode_started = time.perf_counter()
futures = (
self._save_pool.submit(
self._save_output,
results[self.spec.desired_stem],
sample_rate,
self.desired_root,
file_stem,
self.spec.desired_suffix,
),
self._save_pool.submit(
self._save_output,
results[self.spec.secondary_stem],
sample_rate,
self.secondary_root,
file_stem,
self.spec.secondary_suffix,
),
)
wait(futures)
outputs = [future.result() for future in futures]
encode_seconds = time.perf_counter() - encode_started
del results, mix
return {
"outputs": [path for path, _ in outputs],
"inference_seconds": inference_seconds,
"encode_seconds": encode_seconds,
}
def close(self):
save_pool = getattr(self, "_save_pool", None)
if save_pool is not None:
save_pool.shutdown(wait=True)
self._save_pool = None
separator = getattr(self, "separator", None)
try:
if separator is not None:
separator.close()
self.separator = None
finally:
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
def pymss_separate(model_name, inp_root, save_root_vocal, paths, save_root_ins, format0):
infos = []
spec = resolve_model(model_name)
try:
input_paths = collect_input_paths(inp_root, paths)
if not input_paths:
raise ValueError("没有找到可处理的音频文件")
infos.append("%s | %s | 正在加载模型" % (spec.label, spec.model_id))
yield "\n".join(infos)
with UVR_INFERENCE_LOCK:
with MSSTBatchSeparator(
spec,
format0,
save_root_vocal,
save_root_ins,
) as batch:
for input_path in input_paths:
try:
result = batch.separate_file(input_path)
infos.append(
"%s -> 成功 | 推理 %.2fs | 编码 %.2fs"
% (
os.path.basename(input_path),
result["inference_seconds"],
result["encode_seconds"],
)
)
except Exception:
infos.append(
"%s -> 失败\n%s"
% (os.path.basename(input_path), traceback.format_exc())
)
yield "\n".join(infos)
except Exception:
infos.append("失败\n%s" % traceback.format_exc())
yield "\n".join(infos)

View File

@@ -1,70 +0,0 @@
from packaging import version
import torch
from torch import nn, einsum
import torch.nn.functional as F
def exists(val):
return val is not None
def default(v, d):
return v if exists(v) else d
class Attend(nn.Module):
def __init__(self, dropout=0.0, flash=False, scale=None):
super().__init__()
self.scale = scale
self.dropout = dropout
self.attn_dropout = nn.Dropout(dropout)
self.flash = flash
assert not (flash and version.parse(torch.__version__) < version.parse("2.0.0")), (
"in order to use flash attention, you must be using pytorch 2.0 or above"
)
def flash_attn(self, q, k, v):
# _, heads, q_len, _, k_len, is_cuda, device = *q.shape, k.shape[-2], q.is_cuda, q.device
if exists(self.scale):
default_scale = q.shape[-1] ** -0.5
q = q * (self.scale / default_scale)
# pytorch 2.0 flash attn: q, k, v, mask, dropout, softmax_scale
# with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=True):
return F.scaled_dot_product_attention(q, k, v, dropout_p=self.dropout if self.training else 0.0)
def forward(self, q, k, v):
"""
einstein notation
b - batch
h - heads
n, i, j - sequence length (base sequence length, source, target)
d - feature dimension
"""
# q_len, k_len, device = q.shape[-2], k.shape[-2], q.device
scale = default(self.scale, q.shape[-1] ** -0.5)
# DirectML does not expose PyTorch's SDPA kernels. Keep the existing
# SDPA path for CUDA/CPU/MPS and use the mathematically equivalent
# einsum implementation below for PrivateUse1 tensors.
if self.flash and q.device.type != "privateuseone":
return self.flash_attn(q, k, v)
# similarity
sim = einsum("b h i d, b h j d -> b h i j", q, k) * scale
# attention
attn = sim.softmax(dim=-1)
attn = self.attn_dropout(attn)
# aggregate values
out = einsum("b h i j, b h j d -> b h i d", attn, v)
return out

View File

@@ -1,356 +0,0 @@
from functools import partial
import torch
from torch import nn
from torch.nn import Module, ModuleList
import torch.nn.functional as F
from tools.uvr5.bs_roformer.attend import Attend
from torch.utils.checkpoint import checkpoint
from typing import Tuple, Optional, Callable
from tools.uvr5.rotary_embedding_torch import RotaryEmbedding
from einops import rearrange, pack, unpack
from einops.layers.torch import Rearrange
def exists(val):
return val is not None
def default(v, d):
return v if exists(v) else d
def pack_one(t, pattern):
return pack([t], pattern)
def unpack_one(t, ps, pattern):
return unpack(t, ps, pattern)[0]
def l2norm(t):
return F.normalize(t, dim=-1, p=2)
class RMSNorm(Module):
def __init__(self, dim):
super().__init__()
self.scale = dim ** 0.5
self.gamma = nn.Parameter(torch.ones(dim))
def forward(self, x):
return F.normalize(x, dim=-1) * self.scale * self.gamma
class FeedForward(Module):
def __init__(self, dim, mult=4, dropout=0.0):
super().__init__()
dim_inner = int(dim * mult)
self.net = nn.Sequential(RMSNorm(dim), nn.Linear(dim, dim_inner), nn.GELU(), nn.Dropout(dropout), nn.Linear(dim_inner, dim), nn.Dropout(dropout))
def forward(self, x):
return self.net(x)
class Attention(Module):
def __init__(self, dim, heads=8, dim_head=64, dropout=0.0, rotary_embed=None, flash=True):
super().__init__()
self.heads = heads
self.scale = dim_head ** (-0.5)
dim_inner = heads * dim_head
self.rotary_embed = rotary_embed
self.attend = Attend(flash=flash, dropout=dropout)
self.norm = RMSNorm(dim)
self.to_qkv = nn.Linear(dim, dim_inner * 3, bias=False)
self.to_gates = nn.Linear(dim, heads)
self.to_out = nn.Sequential(nn.Linear(dim_inner, dim, bias=False), nn.Dropout(dropout))
def forward(self, x):
x = self.norm(x)
(q, k, v) = rearrange(self.to_qkv(x), 'b n (qkv h d) -> qkv b h n d', qkv=3, h=self.heads)
if exists(self.rotary_embed):
q = self.rotary_embed.rotate_queries_or_keys(q)
k = self.rotary_embed.rotate_queries_or_keys(k)
out = self.attend(q, k, v)
gates = self.to_gates(x)
out = out * rearrange(gates, 'b n h -> b h n 1').sigmoid()
out = rearrange(out, 'b h n d -> b n (h d)')
return self.to_out(out)
class LinearAttention(Module):
"""
this flavor of linear attention proposed in https://arxiv.org/abs/2106.09681 by El-Nouby et al.
"""
def __init__(self, *, dim, dim_head=32, heads=8, scale=8, flash=False, dropout=0.0):
super().__init__()
dim_inner = dim_head * heads
self.norm = RMSNorm(dim)
self.to_qkv = nn.Sequential(nn.Linear(dim, dim_inner * 3, bias=False), Rearrange('b n (qkv h d) -> qkv b h d n', qkv=3, h=heads))
self.temperature = nn.Parameter(torch.ones(heads, 1, 1))
self.attend = Attend(scale=scale, dropout=dropout, flash=flash)
self.to_out = nn.Sequential(Rearrange('b h d n -> b n (h d)'), nn.Linear(dim_inner, dim, bias=False))
def forward(self, x):
x = self.norm(x)
(q, k, v) = self.to_qkv(x)
(q, k) = map(l2norm, (q, k))
q = q * self.temperature.exp()
out = self.attend(q, k, v)
return self.to_out(out)
class Transformer(Module):
def __init__(self, *, dim, depth, dim_head=64, heads=8, attn_dropout=0.0, ff_dropout=0.0, ff_mult=4, norm_output=True, rotary_embed=None, flash_attn=True, linear_attn=False):
super().__init__()
self.layers = ModuleList([])
for _ in range(depth):
if linear_attn:
attn = LinearAttention(dim=dim, dim_head=dim_head, heads=heads, dropout=attn_dropout, flash=flash_attn)
else:
attn = Attention(dim=dim, dim_head=dim_head, heads=heads, dropout=attn_dropout, rotary_embed=rotary_embed, flash=flash_attn)
self.layers.append(ModuleList([attn, FeedForward(dim=dim, mult=ff_mult, dropout=ff_dropout)]))
self.norm = RMSNorm(dim) if norm_output else nn.Identity()
def forward(self, x):
for (attn, ff) in self.layers:
x = attn(x) + x
x = ff(x) + x
return self.norm(x)
class BandSplit(Module):
def __init__(self, dim, dim_inputs):
super().__init__()
self.dim_inputs = dim_inputs
self.to_features = ModuleList([])
for dim_in in dim_inputs:
net = nn.Sequential(RMSNorm(dim_in), nn.Linear(dim_in, dim))
self.to_features.append(net)
def forward(self, x):
x = x.split(self.dim_inputs, dim=-1)
outs = []
for (split_input, to_feature) in zip(x, self.to_features):
split_output = to_feature(split_input)
outs.append(split_output)
return torch.stack(outs, dim=-2)
def MLP(dim_in, dim_out, dim_hidden=None, depth=1, activation=nn.Tanh):
dim_hidden = default(dim_hidden, dim_in)
net = []
dims = (dim_in, *(dim_hidden,) * (depth - 1), dim_out)
for (ind, (layer_dim_in, layer_dim_out)) in enumerate(zip(dims[:-1], dims[1:])):
is_last = ind == len(dims) - 2
net.append(nn.Linear(layer_dim_in, layer_dim_out))
if is_last:
continue
net.append(activation())
return nn.Sequential(*net)
class MaskEstimator(Module):
def __init__(self, dim, dim_inputs, depth, mlp_expansion_factor=4):
super().__init__()
self.dim_inputs = dim_inputs
self.to_freqs = ModuleList([])
dim_hidden = dim * mlp_expansion_factor
for dim_in in dim_inputs:
net = []
mlp = nn.Sequential(MLP(dim, dim_in * 2, dim_hidden=dim_hidden, depth=depth), nn.GLU(dim=-1))
self.to_freqs.append(mlp)
def forward(self, x):
x = x.unbind(dim=-2)
outs = []
for (band_features, mlp) in zip(x, self.to_freqs):
freq_out = mlp(band_features)
outs.append(freq_out)
return torch.cat(outs, dim=-1)
DEFAULT_FREQS_PER_BANDS = (2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 12, 12, 12, 12, 12, 12, 12, 24, 24, 24, 24, 24, 24, 24, 24, 48, 48, 48, 48, 48, 48, 48, 48, 128, 129)
class BSRoformer(Module):
def __init__(self, dim, *, depth, stereo=False, num_stems=1, time_transformer_depth=2, freq_transformer_depth=2, linear_transformer_depth=0, freqs_per_bands=DEFAULT_FREQS_PER_BANDS, dim_head=64, heads=8, attn_dropout=0.0, ff_dropout=0.0, flash_attn=True, dim_freqs_in=1025, stft_n_fft=2048, stft_hop_length=512, stft_win_length=2048, stft_normalized=False, stft_window_fn=None, mask_estimator_depth=2, multi_stft_resolution_loss_weight=1.0, multi_stft_resolutions_window_sizes=(4096, 2048, 1024, 512, 256), multi_stft_hop_size=147, multi_stft_normalized=False, multi_stft_window_fn=torch.hann_window, mlp_expansion_factor=4, use_torch_checkpoint=False, skip_connection=False):
super().__init__()
self.stereo = stereo
self.audio_channels = 2 if stereo else 1
self.num_stems = num_stems
self.use_torch_checkpoint = use_torch_checkpoint
self.skip_connection = skip_connection
self.layers = ModuleList([])
transformer_kwargs = dict(dim=dim, heads=heads, dim_head=dim_head, attn_dropout=attn_dropout, ff_dropout=ff_dropout, flash_attn=flash_attn, norm_output=False)
time_rotary_embed = RotaryEmbedding(dim=dim_head)
freq_rotary_embed = RotaryEmbedding(dim=dim_head)
for _ in range(depth):
tran_modules = []
if linear_transformer_depth > 0:
tran_modules.append(Transformer(depth=linear_transformer_depth, linear_attn=True, **transformer_kwargs))
tran_modules.append(Transformer(depth=time_transformer_depth, rotary_embed=time_rotary_embed, **transformer_kwargs))
tran_modules.append(Transformer(depth=freq_transformer_depth, rotary_embed=freq_rotary_embed, **transformer_kwargs))
self.layers.append(nn.ModuleList(tran_modules))
self.final_norm = RMSNorm(dim)
self.stft_kwargs = dict(n_fft=stft_n_fft, hop_length=stft_hop_length, win_length=stft_win_length, normalized=stft_normalized)
self.stft_window_fn = partial(default(stft_window_fn, torch.hann_window), stft_win_length)
self._stft_windows = {}
freqs = torch.stft(torch.randn(1, 4096), **self.stft_kwargs, window=torch.ones(stft_win_length), return_complex=True).shape[1]
assert len(freqs_per_bands) > 1
assert sum(freqs_per_bands) == freqs, f'the number of freqs in the bands must equal {freqs} based on the STFT settings, but got {sum(freqs_per_bands)}'
freqs_per_bands_with_complex = tuple((2 * f * self.audio_channels for f in freqs_per_bands))
self.band_split = BandSplit(dim=dim, dim_inputs=freqs_per_bands_with_complex)
self.mask_estimators = nn.ModuleList([])
for _ in range(num_stems):
mask_estimator = MaskEstimator(dim=dim, dim_inputs=freqs_per_bands_with_complex, depth=mask_estimator_depth, mlp_expansion_factor=mlp_expansion_factor)
self.mask_estimators.append(mask_estimator)
self.multi_stft_resolution_loss_weight = multi_stft_resolution_loss_weight
self.multi_stft_resolutions_window_sizes = multi_stft_resolutions_window_sizes
self.multi_stft_n_fft = stft_n_fft
self.multi_stft_window_fn = multi_stft_window_fn
self.multi_stft_kwargs = dict(hop_length=multi_stft_hop_size, normalized=multi_stft_normalized)
def _get_stft_window(self, device):
key = str(device)
window = self._stft_windows.get(key)
if window is None:
window = self.stft_window_fn(device=device, dtype=torch.float32)
self._stft_windows[key] = window
return window
def forward(self, raw_audio, target=None, return_loss_breakdown=False):
"""
einops
b - batch
f - freq
t - time
s - audio channel (1 for mono, 2 for stereo)
n - number of 'stems'
c - complex (2)
d - feature dimension
"""
device = raw_audio.device
x_is_dml = device.type == 'privateuseone'
x_is_mps = True if device.type == 'mps' else False
if raw_audio.ndim == 2:
raw_audio = rearrange(raw_audio, 'b t -> b 1 t')
channels = raw_audio.shape[1]
assert not self.stereo and channels == 1 or (self.stereo and channels == 2), 'stereo needs to be set to True if passing in audio signal that is stereo (channel dimension of 2). also need to be False if mono (channel dimension of 1)'
(raw_audio, batch_audio_channel_packed_shape) = pack_one(raw_audio, '* t')
if x_is_dml:
# DirectML has no complex/STFT kernels. Keep only the spectral
# boundary on CPU and move its real representation to DirectML.
stft_window = self._get_stft_window('cpu')
stft_complex = torch.stft(
raw_audio.cpu(),
**self.stft_kwargs,
window=stft_window,
return_complex=True,
)
stft_repr_cpu = torch.view_as_real(stft_complex)
stft_repr_cpu = unpack_one(
stft_repr_cpu, batch_audio_channel_packed_shape, '* f t c'
)
stft_repr_cpu = rearrange(
stft_repr_cpu, 'b s f t c -> b (f s) t c'
)
stft_repr = stft_repr_cpu.to(device)
else:
stft_window = self._get_stft_window(device)
try:
stft_repr = torch.stft(raw_audio, **self.stft_kwargs, window=stft_window, return_complex=True)
except:
stft_repr = torch.stft(raw_audio.cpu() if x_is_mps else raw_audio, **self.stft_kwargs, window=stft_window.cpu() if x_is_mps else stft_window, return_complex=True).to(device)
stft_repr = torch.view_as_real(stft_repr)
stft_repr = unpack_one(stft_repr, batch_audio_channel_packed_shape, '* f t c')
stft_repr = rearrange(stft_repr, 'b s f t c -> b (f s) t c')
x = rearrange(stft_repr, 'b f t c -> b t (f c)')
if self.use_torch_checkpoint:
x = checkpoint(self.band_split, x, use_reentrant=False)
else:
x = self.band_split(x)
store = [None] * len(self.layers)
for (i, transformer_block) in enumerate(self.layers):
if len(transformer_block) == 3:
(linear_transformer, time_transformer, freq_transformer) = transformer_block
(x, ft_ps) = pack([x], 'b * d')
if self.use_torch_checkpoint:
x = checkpoint(linear_transformer, x, use_reentrant=False)
else:
x = linear_transformer(x)
(x,) = unpack(x, ft_ps, 'b * d')
else:
(time_transformer, freq_transformer) = transformer_block
if self.skip_connection:
for j in range(i):
x = x + store[j]
x = rearrange(x, 'b t f d -> b f t d')
(x, ps) = pack([x], '* t d')
if self.use_torch_checkpoint:
x = checkpoint(time_transformer, x, use_reentrant=False)
else:
x = time_transformer(x)
(x,) = unpack(x, ps, '* t d')
x = rearrange(x, 'b f t d -> b t f d')
(x, ps) = pack([x], '* f d')
if self.use_torch_checkpoint:
x = checkpoint(freq_transformer, x, use_reentrant=False)
else:
x = freq_transformer(x)
(x,) = unpack(x, ps, '* f d')
if self.skip_connection:
store[i] = x
x = self.final_norm(x)
num_stems = len(self.mask_estimators)
if self.use_torch_checkpoint:
mask = torch.stack([checkpoint(fn, x, use_reentrant=False) for fn in self.mask_estimators], dim=1)
else:
mask = torch.stack([fn(x) for fn in self.mask_estimators], dim=1)
mask = rearrange(mask, 'b n t (f c) -> b n f t c', c=2)
if x_is_dml:
# Complex masking and ISTFT stay on CPU; all learned real-valued
# layers above remain on DirectML.
stft_repr = rearrange(stft_repr_cpu, 'b f t c -> b 1 f t c')
stft_repr = torch.view_as_complex(stft_repr.contiguous())
mask = torch.view_as_complex(mask.float().cpu().contiguous())
stft_repr = stft_repr * mask
stft_repr = rearrange(
stft_repr,
'b n (f s) t -> (b n s) f t',
s=self.audio_channels,
)
recon_audio = torch.istft(
stft_repr,
**self.stft_kwargs,
window=stft_window,
return_complex=False,
length=raw_audio.shape[-1],
).to(device)
else:
stft_repr = rearrange(stft_repr, 'b f t c -> b 1 f t c')
stft_repr = torch.view_as_complex(stft_repr)
mask = torch.view_as_complex(mask)
stft_repr = stft_repr * mask
stft_repr = rearrange(stft_repr, 'b n (f s) t -> (b n s) f t', s=self.audio_channels)
try:
recon_audio = torch.istft(stft_repr, **self.stft_kwargs, window=stft_window, return_complex=False, length=raw_audio.shape[-1])
except:
recon_audio = torch.istft(stft_repr.cpu() if x_is_mps else stft_repr, **self.stft_kwargs, window=stft_window.cpu() if x_is_mps else stft_window, return_complex=False, length=raw_audio.shape[-1]).to(device)
recon_audio = rearrange(recon_audio, '(b n s) t -> b n s t', s=self.audio_channels, n=num_stems)
if num_stems == 1:
recon_audio = rearrange(recon_audio, 'b 1 s t -> b s t')
if not exists(target):
return recon_audio
if self.num_stems > 1:
assert target.ndim == 4 and target.shape[1] == self.num_stems
if target.ndim == 2:
target = rearrange(target, '... t -> ... 1 t')
target = target[..., :recon_audio.shape[-1]]
loss_audio = recon_audio.cpu() if x_is_dml else recon_audio
loss_target = target.cpu() if x_is_dml else target
loss = F.l1_loss(loss_audio, loss_target)
multi_stft_resolution_loss = 0.0
for window_size in self.multi_stft_resolutions_window_sizes:
spectral_device = 'cpu' if x_is_dml else device
res_stft_kwargs = dict(n_fft=max(window_size, self.multi_stft_n_fft), win_length=window_size, return_complex=True, window=self.multi_stft_window_fn(window_size, device=spectral_device), **self.multi_stft_kwargs)
recon_Y = torch.stft(rearrange(loss_audio, '... s t -> (... s) t'), **res_stft_kwargs)
target_Y = torch.stft(rearrange(loss_target, '... s t -> (... s) t'), **res_stft_kwargs)
multi_stft_resolution_loss = multi_stft_resolution_loss + F.l1_loss(recon_Y, target_Y)
weighted_multi_resolution_loss = multi_stft_resolution_loss * self.multi_stft_resolution_loss_weight
total_loss = loss + weighted_multi_resolution_loss
if not return_loss_breakdown:
return total_loss
return (total_loss, (loss, multi_stft_resolution_loss))

View File

@@ -1,361 +0,0 @@
from functools import partial
import torch
from torch import nn
from torch.nn import Module, ModuleList
import torch.nn.functional as F
from tools.uvr5.bs_roformer.attend import Attend
from torch.utils.checkpoint import checkpoint
from typing import Tuple, Optional, Callable
from tools.uvr5.rotary_embedding_torch import RotaryEmbedding
from einops import rearrange, pack, unpack, reduce, repeat
from einops.layers.torch import Rearrange
from librosa import filters
def exists(val):
return val is not None
def default(v, d):
return v if exists(v) else d
def pack_one(t, pattern):
return pack([t], pattern)
def unpack_one(t, ps, pattern):
return unpack(t, ps, pattern)[0]
def pad_at_dim(t, pad, dim=-1, value=0.0):
dims_from_right = -dim - 1 if dim < 0 else t.ndim - dim - 1
zeros = (0, 0) * dims_from_right
return F.pad(t, (*zeros, *pad), value=value)
def l2norm(t):
return F.normalize(t, dim=-1, p=2)
class RMSNorm(Module):
def __init__(self, dim):
super().__init__()
self.scale = dim ** 0.5
self.gamma = nn.Parameter(torch.ones(dim))
def forward(self, x):
return F.normalize(x, dim=-1) * self.scale * self.gamma
class FeedForward(Module):
def __init__(self, dim, mult=4, dropout=0.0):
super().__init__()
dim_inner = int(dim * mult)
self.net = nn.Sequential(RMSNorm(dim), nn.Linear(dim, dim_inner), nn.GELU(), nn.Dropout(dropout), nn.Linear(dim_inner, dim), nn.Dropout(dropout))
def forward(self, x):
return self.net(x)
class Attention(Module):
def __init__(self, dim, heads=8, dim_head=64, dropout=0.0, rotary_embed=None, flash=True):
super().__init__()
self.heads = heads
self.scale = dim_head ** (-0.5)
dim_inner = heads * dim_head
self.rotary_embed = rotary_embed
self.attend = Attend(flash=flash, dropout=dropout)
self.norm = RMSNorm(dim)
self.to_qkv = nn.Linear(dim, dim_inner * 3, bias=False)
self.to_gates = nn.Linear(dim, heads)
self.to_out = nn.Sequential(nn.Linear(dim_inner, dim, bias=False), nn.Dropout(dropout))
def forward(self, x):
x = self.norm(x)
(q, k, v) = rearrange(self.to_qkv(x), 'b n (qkv h d) -> qkv b h n d', qkv=3, h=self.heads)
if exists(self.rotary_embed):
q = self.rotary_embed.rotate_queries_or_keys(q)
k = self.rotary_embed.rotate_queries_or_keys(k)
out = self.attend(q, k, v)
gates = self.to_gates(x)
out = out * rearrange(gates, 'b n h -> b h n 1').sigmoid()
out = rearrange(out, 'b h n d -> b n (h d)')
return self.to_out(out)
class LinearAttention(Module):
"""
this flavor of linear attention proposed in https://arxiv.org/abs/2106.09681 by El-Nouby et al.
"""
def __init__(self, *, dim, dim_head=32, heads=8, scale=8, flash=False, dropout=0.0):
super().__init__()
dim_inner = dim_head * heads
self.norm = RMSNorm(dim)
self.to_qkv = nn.Sequential(nn.Linear(dim, dim_inner * 3, bias=False), Rearrange('b n (qkv h d) -> qkv b h d n', qkv=3, h=heads))
self.temperature = nn.Parameter(torch.ones(heads, 1, 1))
self.attend = Attend(scale=scale, dropout=dropout, flash=flash)
self.to_out = nn.Sequential(Rearrange('b h d n -> b n (h d)'), nn.Linear(dim_inner, dim, bias=False))
def forward(self, x):
x = self.norm(x)
(q, k, v) = self.to_qkv(x)
(q, k) = map(l2norm, (q, k))
q = q * self.temperature.exp()
out = self.attend(q, k, v)
return self.to_out(out)
class Transformer(Module):
def __init__(self, *, dim, depth, dim_head=64, heads=8, attn_dropout=0.0, ff_dropout=0.0, ff_mult=4, norm_output=True, rotary_embed=None, flash_attn=True, linear_attn=False):
super().__init__()
self.layers = ModuleList([])
for _ in range(depth):
if linear_attn:
attn = LinearAttention(dim=dim, dim_head=dim_head, heads=heads, dropout=attn_dropout, flash=flash_attn)
else:
attn = Attention(dim=dim, dim_head=dim_head, heads=heads, dropout=attn_dropout, rotary_embed=rotary_embed, flash=flash_attn)
self.layers.append(ModuleList([attn, FeedForward(dim=dim, mult=ff_mult, dropout=ff_dropout)]))
self.norm = RMSNorm(dim) if norm_output else nn.Identity()
def forward(self, x):
for (attn, ff) in self.layers:
x = attn(x) + x
x = ff(x) + x
return self.norm(x)
class BandSplit(Module):
def __init__(self, dim, dim_inputs):
super().__init__()
self.dim_inputs = dim_inputs
self.to_features = ModuleList([])
for dim_in in dim_inputs:
net = nn.Sequential(RMSNorm(dim_in), nn.Linear(dim_in, dim))
self.to_features.append(net)
def forward(self, x):
x = x.split(self.dim_inputs, dim=-1)
outs = []
for (split_input, to_feature) in zip(x, self.to_features):
split_output = to_feature(split_input)
outs.append(split_output)
return torch.stack(outs, dim=-2)
def MLP(dim_in, dim_out, dim_hidden=None, depth=1, activation=nn.Tanh):
dim_hidden = default(dim_hidden, dim_in)
net = []
dims = (dim_in, *(dim_hidden,) * depth, dim_out)
for (ind, (layer_dim_in, layer_dim_out)) in enumerate(zip(dims[:-1], dims[1:])):
is_last = ind == len(dims) - 2
net.append(nn.Linear(layer_dim_in, layer_dim_out))
if is_last:
continue
net.append(activation())
return nn.Sequential(*net)
class MaskEstimator(Module):
def __init__(self, dim, dim_inputs, depth, mlp_expansion_factor=4):
super().__init__()
self.dim_inputs = dim_inputs
self.to_freqs = ModuleList([])
dim_hidden = dim * mlp_expansion_factor
for dim_in in dim_inputs:
net = []
mlp = nn.Sequential(MLP(dim, dim_in * 2, dim_hidden=dim_hidden, depth=depth), nn.GLU(dim=-1))
self.to_freqs.append(mlp)
def forward(self, x):
x = x.unbind(dim=-2)
outs = []
for (band_features, mlp) in zip(x, self.to_freqs):
freq_out = mlp(band_features)
outs.append(freq_out)
return torch.cat(outs, dim=-1)
class MelBandRoformer(Module):
def __init__(self, dim, *, depth, stereo=False, num_stems=1, time_transformer_depth=2, freq_transformer_depth=2, linear_transformer_depth=0, num_bands=60, dim_head=64, heads=8, attn_dropout=0.1, ff_dropout=0.1, flash_attn=True, dim_freqs_in=1025, sample_rate=44100, stft_n_fft=2048, stft_hop_length=512, stft_win_length=2048, stft_normalized=False, stft_window_fn=None, mask_estimator_depth=1, multi_stft_resolution_loss_weight=1.0, multi_stft_resolutions_window_sizes=(4096, 2048, 1024, 512, 256), multi_stft_hop_size=147, multi_stft_normalized=False, multi_stft_window_fn=torch.hann_window, match_input_audio_length=False, mlp_expansion_factor=4, use_torch_checkpoint=False, skip_connection=False):
super().__init__()
self.stereo = stereo
self.audio_channels = 2 if stereo else 1
self.num_stems = num_stems
self.use_torch_checkpoint = use_torch_checkpoint
self.skip_connection = skip_connection
self.layers = ModuleList([])
transformer_kwargs = dict(dim=dim, heads=heads, dim_head=dim_head, attn_dropout=attn_dropout, ff_dropout=ff_dropout, flash_attn=flash_attn)
time_rotary_embed = RotaryEmbedding(dim=dim_head)
freq_rotary_embed = RotaryEmbedding(dim=dim_head)
for _ in range(depth):
tran_modules = []
if linear_transformer_depth > 0:
tran_modules.append(Transformer(depth=linear_transformer_depth, linear_attn=True, **transformer_kwargs))
tran_modules.append(Transformer(depth=time_transformer_depth, rotary_embed=time_rotary_embed, **transformer_kwargs))
tran_modules.append(Transformer(depth=freq_transformer_depth, rotary_embed=freq_rotary_embed, **transformer_kwargs))
self.layers.append(nn.ModuleList(tran_modules))
self.stft_window_fn = partial(default(stft_window_fn, torch.hann_window), stft_win_length)
self._stft_windows = {}
self.stft_kwargs = dict(n_fft=stft_n_fft, hop_length=stft_hop_length, win_length=stft_win_length, normalized=stft_normalized)
freqs = torch.stft(torch.randn(1, 4096), **self.stft_kwargs, window=torch.ones(stft_n_fft), return_complex=True).shape[1]
mel_filter_bank_numpy = filters.mel(sr=sample_rate, n_fft=stft_n_fft, n_mels=num_bands)
mel_filter_bank = torch.from_numpy(mel_filter_bank_numpy)
mel_filter_bank[0][0] = 1.0
mel_filter_bank[-1, -1] = 1.0
freqs_per_band = mel_filter_bank > 0
assert freqs_per_band.any(dim=0).all(), 'all frequencies need to be covered by all bands for now'
repeated_freq_indices = repeat(torch.arange(freqs), 'f -> b f', b=num_bands)
freq_indices = repeated_freq_indices[freqs_per_band]
if stereo:
freq_indices = repeat(freq_indices, 'f -> f s', s=2)
freq_indices = freq_indices * 2 + torch.arange(2)
freq_indices = rearrange(freq_indices, 'f s -> (f s)')
self.register_buffer('freq_indices', freq_indices, persistent=False)
self.register_buffer('freqs_per_band', freqs_per_band, persistent=False)
num_freqs_per_band = reduce(freqs_per_band, 'b f -> b', 'sum')
num_bands_per_freq = reduce(freqs_per_band, 'b f -> f', 'sum')
self.register_buffer('num_freqs_per_band', num_freqs_per_band, persistent=False)
self.register_buffer('num_bands_per_freq', num_bands_per_freq, persistent=False)
freqs_per_bands_with_complex = tuple((2 * f * self.audio_channels for f in num_freqs_per_band.tolist()))
self.band_split = BandSplit(dim=dim, dim_inputs=freqs_per_bands_with_complex)
self.mask_estimators = nn.ModuleList([])
for _ in range(num_stems):
mask_estimator = MaskEstimator(dim=dim, dim_inputs=freqs_per_bands_with_complex, depth=mask_estimator_depth, mlp_expansion_factor=mlp_expansion_factor)
self.mask_estimators.append(mask_estimator)
self.multi_stft_resolution_loss_weight = multi_stft_resolution_loss_weight
self.multi_stft_resolutions_window_sizes = multi_stft_resolutions_window_sizes
self.multi_stft_n_fft = stft_n_fft
self.multi_stft_window_fn = multi_stft_window_fn
self.multi_stft_kwargs = dict(hop_length=multi_stft_hop_size, normalized=multi_stft_normalized)
self.match_input_audio_length = match_input_audio_length
def _get_stft_window(self, device):
key = str(device)
window = self._stft_windows.get(key)
if window is None:
window = self.stft_window_fn(device=device, dtype=torch.float32)
self._stft_windows[key] = window
return window
def forward(self, raw_audio, target=None, return_loss_breakdown=False):
"""
einops
b - batch
f - freq
t - time
s - audio channel (1 for mono, 2 for stereo)
n - number of 'stems'
c - complex (2)
d - feature dimension
"""
device = raw_audio.device
x_is_dml = device.type == 'privateuseone'
if raw_audio.ndim == 2:
raw_audio = rearrange(raw_audio, 'b t -> b 1 t')
(batch, channels, raw_audio_length) = raw_audio.shape
istft_length = raw_audio_length if self.match_input_audio_length else None
assert not self.stereo and channels == 1 or (self.stereo and channels == 2), 'stereo needs to be set to True if passing in audio signal that is stereo (channel dimension of 2). also need to be False if mono (channel dimension of 1)'
(raw_audio, batch_audio_channel_packed_shape) = pack_one(raw_audio, '* t')
if x_is_dml:
# DirectML has no STFT or complex tensor support. Build the real
# spectral features on CPU, then run the learned network on DML.
stft_window = self._get_stft_window('cpu')
stft_complex = torch.stft(
raw_audio.cpu(),
**self.stft_kwargs,
window=stft_window,
return_complex=True,
)
stft_repr = torch.view_as_real(stft_complex)
stft_repr = unpack_one(
stft_repr, batch_audio_channel_packed_shape, '* f t c'
)
stft_repr = rearrange(stft_repr, 'b s f t c -> b (f s) t c')
x = stft_repr[:, self.freq_indices.cpu()].to(device)
else:
stft_window = self._get_stft_window(device)
stft_repr = torch.stft(raw_audio, **self.stft_kwargs, window=stft_window, return_complex=True)
stft_repr = torch.view_as_real(stft_repr)
stft_repr = unpack_one(stft_repr, batch_audio_channel_packed_shape, '* f t c')
stft_repr = rearrange(stft_repr, 'b s f t c -> b (f s) t c')
x = stft_repr[:, self.freq_indices]
x = rearrange(x, 'b f t c -> b t (f c)')
if self.use_torch_checkpoint:
x = checkpoint(self.band_split, x, use_reentrant=False)
else:
x = self.band_split(x)
store = [None] * len(self.layers)
for (i, transformer_block) in enumerate(self.layers):
if len(transformer_block) == 3:
(linear_transformer, time_transformer, freq_transformer) = transformer_block
(x, ft_ps) = pack([x], 'b * d')
if self.use_torch_checkpoint:
x = checkpoint(linear_transformer, x, use_reentrant=False)
else:
x = linear_transformer(x)
(x,) = unpack(x, ft_ps, 'b * d')
else:
(time_transformer, freq_transformer) = transformer_block
if self.skip_connection:
for j in range(i):
x = x + store[j]
x = rearrange(x, 'b t f d -> b f t d')
(x, ps) = pack([x], '* t d')
if self.use_torch_checkpoint:
x = checkpoint(time_transformer, x, use_reentrant=False)
else:
x = time_transformer(x)
(x,) = unpack(x, ps, '* t d')
x = rearrange(x, 'b f t d -> b t f d')
(x, ps) = pack([x], '* f d')
if self.use_torch_checkpoint:
x = checkpoint(freq_transformer, x, use_reentrant=False)
else:
x = freq_transformer(x)
(x,) = unpack(x, ps, '* f d')
if self.skip_connection:
store[i] = x
num_stems = len(self.mask_estimators)
if self.use_torch_checkpoint:
masks = torch.stack([checkpoint(fn, x, use_reentrant=False) for fn in self.mask_estimators], dim=1)
else:
masks = torch.stack([fn(x) for fn in self.mask_estimators], dim=1)
masks = rearrange(masks, 'b n t (f c) -> b n f t c', c=2)
if x_is_dml:
masks = masks.float().cpu()
stft_repr = rearrange(stft_repr, 'b f t c -> b 1 f t c')
stft_repr = torch.view_as_complex(stft_repr.contiguous())
masks = torch.view_as_complex(masks.contiguous())
masks = masks.type(stft_repr.dtype)
freq_indices = self.freq_indices.cpu() if x_is_dml else self.freq_indices
stft_repr_expanded_stems = repeat(stft_repr, 'b 1 ... -> b n ...', n=num_stems)
masks_summed = torch.zeros_like(stft_repr_expanded_stems)
masks_summed.index_add_(2, freq_indices, masks)
num_bands_per_freq = self.num_bands_per_freq.cpu() if x_is_dml else self.num_bands_per_freq
denom = repeat(num_bands_per_freq, 'f -> (f r) 1', r=channels)
masks_averaged = masks_summed / denom.clamp(min=1e-08)
stft_repr = stft_repr * masks_averaged
stft_repr = rearrange(stft_repr, 'b n (f s) t -> (b n s) f t', s=self.audio_channels)
recon_audio = torch.istft(stft_repr, **self.stft_kwargs, window=stft_window, return_complex=False, length=istft_length)
if x_is_dml:
recon_audio = recon_audio.to(device)
recon_audio = rearrange(recon_audio, '(b n s) t -> b n s t', b=batch, s=self.audio_channels, n=num_stems)
if num_stems == 1:
recon_audio = rearrange(recon_audio, 'b 1 s t -> b s t')
if not exists(target):
return recon_audio
if self.num_stems > 1:
assert target.ndim == 4 and target.shape[1] == self.num_stems
if target.ndim == 2:
target = rearrange(target, '... t -> ... 1 t')
target = target[..., :recon_audio.shape[-1]]
loss_audio = recon_audio.cpu() if x_is_dml else recon_audio
loss_target = target.cpu() if x_is_dml else target
loss = F.l1_loss(loss_audio, loss_target)
multi_stft_resolution_loss = 0.0
for window_size in self.multi_stft_resolutions_window_sizes:
spectral_device = 'cpu' if x_is_dml else device
res_stft_kwargs = dict(n_fft=max(window_size, self.multi_stft_n_fft), win_length=window_size, return_complex=True, window=self.multi_stft_window_fn(window_size, device=spectral_device), **self.multi_stft_kwargs)
recon_Y = torch.stft(rearrange(loss_audio, '... s t -> (... s) t'), **res_stft_kwargs)
target_Y = torch.stft(rearrange(loss_target, '... s t -> (... s) t'), **res_stft_kwargs)
multi_stft_resolution_loss = multi_stft_resolution_loss + F.l1_loss(recon_Y, target_Y)
weighted_multi_resolution_loss = multi_stft_resolution_loss * self.multi_stft_resolution_loss_weight
total_loss = loss + weighted_multi_resolution_loss
if not return_loss_breakdown:
return total_loss
return (total_loss, (loss, multi_stft_resolution_loss))

View File

@@ -1,405 +0,0 @@
# This code is modified from https://github.com/ZFTurbo/
import os
import warnings
from contextlib import nullcontext
import numpy as np
import soundfile as sf
import torch
import torch.nn as nn
import yaml
from infer.audio import TORCHAUDIO_GPU_ENABLED, load_audio, load_audio_tensor
from tqdm import tqdm
from tools.file_io import read_text
from i18n.i18n import I18nAuto
warnings.filterwarnings("ignore")
i18n = I18nAuto()
class Roformer_Loader:
def get_config(self, config_path):
return yaml.load(read_text(config_path), Loader=yaml.FullLoader)
def get_default_config(self):
default_config = None
if self.model_type == "bs_roformer":
# Use model_bs_roformer_ep_368_sdr_12.9628.yaml and model_bs_roformer_ep_317_sdr_12.9755.yaml as default configuration files
# Other BS_Roformer models may not be compatible
# fmt: off
default_config = {
"audio": {"chunk_size": 352800, "sample_rate": 44100},
"model": {
"dim": 512,
"depth": 12,
"stereo": True,
"num_stems": 1,
"time_transformer_depth": 1,
"freq_transformer_depth": 1,
"linear_transformer_depth": 0,
"freqs_per_bands": (2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 12, 12, 12, 12, 12, 12, 12, 24, 24, 24, 24, 24, 24, 24, 24, 48, 48, 48, 48, 48, 48, 48, 48, 128, 129),
"dim_head": 64,
"heads": 8,
"attn_dropout": 0.1,
"ff_dropout": 0.1,
"flash_attn": True,
"dim_freqs_in": 1025,
"stft_n_fft": 2048,
"stft_hop_length": 441,
"stft_win_length": 2048,
"stft_normalized": False,
"mask_estimator_depth": 2,
"multi_stft_resolution_loss_weight": 1.0,
"multi_stft_resolutions_window_sizes": (4096, 2048, 1024, 512, 256),
"multi_stft_hop_size": 147,
"multi_stft_normalized": False,
},
"training": {"instruments": ["vocals", "other"], "target_instrument": "vocals"},
"inference": {"batch_size": 2, "num_overlap": 2},
}
# fmt: on
elif self.model_type == "mel_band_roformer":
# Use model_mel_band_roformer_ep_3005_sdr_11.4360.yaml as default configuration files
# Other Mel_Band_Roformer models may not be compatible
default_config = {
"audio": {"chunk_size": 352800, "sample_rate": 44100},
"model": {
"dim": 384,
"depth": 12,
"stereo": True,
"num_stems": 1,
"time_transformer_depth": 1,
"freq_transformer_depth": 1,
"linear_transformer_depth": 0,
"num_bands": 60,
"dim_head": 64,
"heads": 8,
"attn_dropout": 0.1,
"ff_dropout": 0.1,
"flash_attn": True,
"dim_freqs_in": 1025,
"sample_rate": 44100,
"stft_n_fft": 2048,
"stft_hop_length": 441,
"stft_win_length": 2048,
"stft_normalized": False,
"mask_estimator_depth": 2,
"multi_stft_resolution_loss_weight": 1.0,
"multi_stft_resolutions_window_sizes": (4096, 2048, 1024, 512, 256),
"multi_stft_hop_size": 147,
"multi_stft_normalized": False,
},
"training": {"instruments": ["vocals", "other"], "target_instrument": "vocals"},
"inference": {"batch_size": 2, "num_overlap": 2},
}
return default_config
def get_model_from_config(self):
if self.model_type == "bs_roformer":
from tools.uvr5.bs_roformer.bs_roformer import BSRoformer
model = BSRoformer(**dict(self.config["model"]))
elif self.model_type == "mel_band_roformer":
from tools.uvr5.bs_roformer.mel_band_roformer import MelBandRoformer
model = MelBandRoformer(**dict(self.config["model"]))
else:
print(i18n("错误:未知模型:%s") % self.model_type)
model = None
return model
def demix_track(self, model, mix, device):
C = self.config["audio"]["chunk_size"] # chunk_size
N = self.config["inference"]["num_overlap"]
fade_size = C // 10
step = int(C // N)
border = C - step
batch_size = self.config["inference"]["batch_size"]
length_init = mix.shape[-1]
# Do pad from the beginning and end to account floating window results better
if length_init > 2 * border and (border > 0):
mix = nn.functional.pad(mix, (border, border), mode="reflect")
total_windows = (mix.shape[-1] + step - 1) // step
progress_bar = tqdm(total=total_windows, desc="Processing", leave=False)
parsed_device = device if isinstance(device, torch.device) else torch.device(device)
device_type = parsed_device.type
if self.config["training"]["target_instrument"] is None:
source_count = len(self.config["training"]["instruments"])
else:
source_count = 1
req_shape = (source_count,) + tuple(mix.shape)
accumulation_device = torch.device("cpu")
if device_type == "cuda":
required_bytes = int(np.prod(req_shape)) * 4 + mix.shape[-1] * 4
free_bytes, _ = torch.cuda.mem_get_info(parsed_device)
limit = min(1024**3, int(free_bytes * 0.22))
if required_bytes <= limit:
accumulation_device = parsed_device
try:
result = torch.zeros(
req_shape,
dtype=torch.float32,
device=accumulation_device,
)
counter = torch.zeros(
mix.shape[-1],
dtype=torch.float32,
device=accumulation_device,
)
except torch.cuda.OutOfMemoryError:
torch.cuda.empty_cache()
accumulation_device = torch.device("cpu")
result = torch.zeros(req_shape, dtype=torch.float32)
counter = torch.zeros(mix.shape[-1], dtype=torch.float32)
# The overlap-add window lives beside the accumulator. A short file
# with one window uses the all-ones window to avoid a zero denominator.
fadein = torch.linspace(
0, 1, fade_size, device=accumulation_device, dtype=torch.float32
)
fadeout = torch.linspace(
1, 0, fade_size, device=accumulation_device, dtype=torch.float32
)
window_full = torch.ones(C, device=accumulation_device)
window_start = window_full.clone()
window_middle = window_full.clone()
window_finish = window_full.clone()
window_start[-fade_size:] *= fadeout
window_finish[:fade_size] *= fadein
window_middle[-fade_size:] *= fadeout
window_middle[:fade_size] *= fadein
amp_context = (
torch.amp.autocast("cuda", enabled=self.is_half)
if device_type == "cuda"
else nullcontext()
)
grad_context = (
torch.no_grad()
if device_type == "privateuseone"
else torch.inference_mode()
)
with amp_context:
# DirectML updates version counters in several linear kernels and
# therefore needs no_grad rather than inference_mode. CUDA and CPU
# retain the existing inference-mode path.
with grad_context:
model_dtype = next(model.parameters()).dtype
i = 0
batch_data = []
batch_locations = []
while i < mix.shape[1]:
part = mix[:, i : i + C]
length = part.shape[-1]
if length < C:
if length > C // 2 + 1:
part = nn.functional.pad(input=part, pad=(0, C - length), mode="reflect")
else:
part = nn.functional.pad(input=part, pad=(0, C - length, 0, 0), mode="constant", value=0)
batch_data.append(part)
batch_locations.append((i, length))
i += step
progress_bar.update(1)
if len(batch_data) >= batch_size or (i >= mix.shape[1]):
arr = torch.stack(batch_data, dim=0).to(
device=parsed_device,
dtype=model_dtype,
)
# Torch STFT/ISTFT cannot be captured reliably by a
# CUDA Graph on the supported runtime, so keep this
# model call eager while all tensors remain on CUDA.
x = model(arr)
x_for_accumulation = (
x.float()
if accumulation_device.type == "cuda"
else x.float().cpu()
)
for j in range(len(batch_locations)):
start, l = batch_locations[j]
is_first = start == 0
is_last = start + l >= mix.shape[1]
if is_first and is_last:
window = window_full
elif is_first:
window = window_start
elif is_last:
window = window_finish
else:
window = window_middle
result[..., start : start + l].add_(
x_for_accumulation[j][..., :l] * window[:l]
)
counter[start : start + l].add_(window[:l])
batch_data = []
batch_locations = []
result.div_(counter.clamp_min(1e-8))
torch.nan_to_num_(result)
if length_init > 2 * border and (border > 0):
result = result[..., border:-border]
estimated_sources = result.cpu().numpy()
progress_bar.close()
if self.config["training"]["target_instrument"] is None:
return {k: v for k, v in zip(self.config["training"]["instruments"], estimated_sources)}
else:
return {k: v for k, v in zip([self.config["training"]["target_instrument"]], estimated_sources)}
def run_folder(self, input, vocal_root, others_root, format):
self.model.eval()
path = input
os.makedirs(vocal_root, exist_ok=True)
os.makedirs(others_root, exist_ok=True)
file_base_name = os.path.splitext(os.path.basename(path))[0]
sample_rate = 44100
if "sample_rate" in self.config["audio"]:
sample_rate = self.config["audio"]["sample_rate"]
isstereo = self.config["model"].get("stereo", True)
device_type = (
self.device.type
if isinstance(self.device, torch.device)
else torch.device(self.device).type
)
try:
if device_type == "cuda" and TORCHAUDIO_GPU_ENABLED:
mix = load_audio_tensor(
path, sample_rate, force_mono=not isstereo
)
else:
mix = load_audio(path, sample_rate, force_mono=not isstereo)
sr = sample_rate
except Exception as e:
print(i18n("无法读取音频:%s") % path)
print(i18n("错误信息:%s") % str(e))
return
if isstereo:
if mix.ndim == 1:
mix = mix.unsqueeze(0) if torch.is_tensor(mix) else mix[np.newaxis, :]
if mix.shape[0] == 1:
mix = mix.repeat(2, 1) if torch.is_tensor(mix) else np.repeat(mix, 2, axis=0)
elif mix.shape[0] > 2:
mix = mix[:2].contiguous() if torch.is_tensor(mix) else np.ascontiguousarray(mix[:2])
else:
if mix.ndim == 1:
mix = mix.unsqueeze(0) if torch.is_tensor(mix) else mix[np.newaxis, :]
elif mix.shape[0] > 1:
mix = (
mix.mean(dim=0, keepdim=True)
if torch.is_tensor(mix)
else np.mean(mix, axis=0, keepdims=True)
)
print(i18n("音频包含多个声道,但模型仅支持单声道,将对所有声道取平均值"))
if torch.is_tensor(mix):
keep_on_gpu = mix.device.type == "cuda"
if keep_on_gpu:
free_bytes, _ = torch.cuda.mem_get_info(mix.device)
input_bytes = mix.numel() * mix.element_size()
keep_on_gpu = input_bytes <= min(
512 * 1024 * 1024,
int(free_bytes * 0.10),
)
if keep_on_gpu:
mixture = mix
mix_orig = mix.detach().float().cpu().numpy()
else:
mixture = mix.detach().float().cpu()
mix_orig = mixture.numpy()
del mix
else:
mix = np.ascontiguousarray(mix, dtype=np.float32)
mix_orig = mix
mixture = torch.from_numpy(mix)
res = self.demix_track(self.model, mixture, self.device)
if self.config["training"]["target_instrument"] is not None:
# if target instrument is specified, save target instrument as vocal and other instruments as others
# other instruments are caculated by subtracting target instrument from mixture
target_instrument = self.config["training"]["target_instrument"]
other_instruments = [i for i in self.config["training"]["instruments"] if i != target_instrument]
np.subtract(mix_orig, res[target_instrument], out=mix_orig)
other = mix_orig
path_vocal = "{}/{}_{}.wav".format(vocal_root, file_base_name, target_instrument)
path_other = "{}/{}_{}.wav".format(others_root, file_base_name, other_instruments[0])
self.save_audio(path_vocal, res[target_instrument].T, sr, format)
self.save_audio(path_other, other.T, sr, format)
else:
# if target instrument is not specified, save the first instrument as vocal and the rest as others
vocal_inst = self.config["training"]["instruments"][0]
path_vocal = "{}/{}_{}.wav".format(vocal_root, file_base_name, vocal_inst)
self.save_audio(path_vocal, res[vocal_inst].T, sr, format)
for other in self.config["training"]["instruments"][1:]: # save other instruments
path_other = "{}/{}_{}.wav".format(others_root, file_base_name, other)
self.save_audio(path_other, res[other].T, sr, format)
def save_audio(self, path, data, sr, format):
# input path should be endwith '.wav'
if format in ["wav", "flac"]:
if format == "flac":
path = path[:-3] + "flac"
sf.write(path, data, sr)
else:
sf.write(path, data, sr)
os.system('ffmpeg -i "{}" -vn "{}" -q:a 2 -y'.format(path, path[:-3] + format))
try:
os.remove(path)
except:
pass
def __init__(self, model_path, config_path, device, is_half):
self.device = device
self.is_half = is_half
self.model_type = None
self.config = None
# get model_type, first try:
if "bs_roformer" in model_path.lower() or "bsroformer" in model_path.lower():
self.model_type = "bs_roformer"
elif "mel_band_roformer" in model_path.lower() or "melbandroformer" in model_path.lower():
self.model_type = "mel_band_roformer"
if not os.path.exists(config_path):
if self.model_type is None:
# if model_type is still None, raise an error
raise ValueError(
"Error: Unknown model type. If you are using a model without a configuration file, Ensure that your model name includes 'bs_roformer', 'bsroformer', 'mel_band_roformer', or 'melbandroformer'. Otherwise, you can manually place the model configuration file into 'tools/uvr5/uvr5w_weights' and ensure that the configuration file is named as '<model_name>.yaml' then try it again."
)
self.config = self.get_default_config()
else:
# if there is a configuration file
self.config = self.get_config(config_path)
if self.model_type is None:
# if model_type is still None, second try, get model_type from the configuration file
if "freqs_per_bands" in self.config["model"]:
# if freqs_per_bands in config, it's a bs_roformer model
self.model_type = "bs_roformer"
else:
# else it's a mel_band_roformer model
self.model_type = "mel_band_roformer"
print(i18n("检测到模型类型:%s") % self.model_type)
model = self.get_model_from_config()
state_dict = torch.load(model_path, map_location="cpu")
model.load_state_dict(state_dict)
if is_half == False:
self.model = model.to(device)
else:
self.model = model.half().to(device)
def _path_audio_(self, input, others_root, vocal_root, format, is_hp3=False):
self.run_folder(input, vocal_root, others_root, format)

View File

@@ -1,106 +0,0 @@
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

View File

@@ -1,111 +0,0 @@
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

View File

@@ -1,68 +0,0 @@
import json
import pathlib
from tools.file_io import read_text
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:
self.param = json.loads(
read_text(config_path), 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 k not in self.param:
self.param[k] = False

View File

@@ -1,54 +0,0 @@
{
"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
}

View File

@@ -1,54 +0,0 @@
{
"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
}

View File

@@ -1,122 +0,0 @@
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

View File

@@ -1,125 +0,0 @@
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

View File

@@ -1,445 +0,0 @@
import math
import librosa
import numpy as np
import torch
from infer.audio import resample_audio, resample_audio_tensor
_STFT_WINDOWS = {}
def _stft_window(n_fft, device):
key = (n_fft, str(device))
window = _STFT_WINDOWS.get(key)
if window is None:
window = torch.hann_window(
n_fft,
periodic=True,
device=device,
dtype=torch.float32,
)
_STFT_WINDOWS[key] = window
return window
def _wave_to_spectrogram_torch(
wave, hop_length, n_fft, mid_side=False, mid_side_b2=False, reverse=False
):
wave = wave.to(dtype=torch.float32)
if reverse:
transformed = torch.flip(wave[:2], dims=(-1,))
elif mid_side:
transformed = torch.stack(
((wave[0] + wave[1]) / 2, wave[0] - wave[1])
)
elif mid_side_b2:
transformed = torch.stack(
(wave[1] + wave[0] * 0.5, wave[0] - wave[1] * 0.5)
)
else:
transformed = wave[:2]
return torch.stft(
transformed,
n_fft=n_fft,
hop_length=hop_length,
window=_stft_window(n_fft, transformed.device),
center=True,
pad_mode="constant",
normalized=False,
onesided=True,
return_complex=True,
)
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_mt(wave, hop_length, n_fft, mid_side=False, mid_side_b2=False, reverse=False):
if torch.is_tensor(wave):
return _wave_to_spectrogram_torch(
wave, hop_length, n_fft, mid_side, mid_side_b2, reverse
)
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=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])
first = specs[next(iter(specs))]
if torch.is_tensor(first):
spec_c = torch.zeros(
(2, mp.param["bins"] + 1, l),
dtype=torch.complex64,
device=first.device,
)
else:
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
if torch.is_tensor(spec_c):
return spec_c.contiguous()
return np.asfortranarray(spec_c)
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")
if torch.is_tensor(mag):
mag = mag.clone()
idx = torch.where(ref.mean(dim=(0, 1)) < thres)[0]
if idx.numel() == 0:
return mag
breaks = torch.where(torch.diff(idx) != 1)[0]
starts = torch.cat((idx[:1], idx[breaks + 1]))
ends = torch.cat((idx[breaks], idx[-1:]))
informative = torch.where(ends - starts > min_range)[0]
old_e = None
for position in informative.tolist():
s = int(starts[position].item())
e = int(ends[position].item())
if old_e is not None and s - old_e < fade_size:
s = old_e - fade_size * 2
if s != 0:
weight = torch.linspace(
0,
1,
fade_size,
device=mag.device,
dtype=mag.dtype,
)
mag[:, :, s : s + fade_size] += (
weight * ref[:, :, s : s + fade_size]
)
else:
s -= fade_size
if e != mag.shape[2]:
weight = torch.linspace(
1,
0,
fade_size,
device=mag.device,
dtype=mag.dtype,
)
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
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 spectrogram_to_wave(spec, hop_length, mid_side, mid_side_b2, reverse):
if torch.is_tensor(spec):
n_fft = (spec.shape[1] - 1) * 2
wave = torch.istft(
spec.to(dtype=torch.complex64),
n_fft=n_fft,
hop_length=hop_length,
window=_stft_window(n_fft, spec.device),
center=True,
normalized=False,
onesided=True,
return_complex=False,
)
wave_left, wave_right = wave[0], wave[1]
if reverse:
return torch.stack(
(torch.flip(wave_left, dims=(-1,)), torch.flip(wave_right, dims=(-1,)))
)
if mid_side:
return torch.stack(
(wave_left + wave_right / 2, wave_left - wave_right / 2)
)
if mid_side_b2:
return torch.stack(
(wave_right / 1.25 + 0.4 * wave_left, wave_left / 1.25 - 0.4 * wave_right)
)
return wave
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 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]
shape = (2, bp["n_fft"] // 2 + 1, spec_m.shape[2])
if torch.is_tensor(spec_m):
spec_s = torch.zeros(shape, dtype=spec_m.dtype, device=spec_m.device)
else:
spec_s = np.ndarray(shape=shape, 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 = 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"])
band_wave = spectrogram_to_wave(
spec_s,
bp["hl"],
mp.param["mid_side"],
mp.param["mid_side_b2"],
mp.param["reverse"],
)
if torch.is_tensor(band_wave):
wave = resample_audio_tensor(
band_wave, bp["sr"], sr, force_mono=False
)
else:
wave = resample_audio(
band_wave,
bp["sr"],
sr,
force_mono=False,
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 = wave + spectrogram_to_wave(
spec_s,
bp["hl"],
mp.param["mid_side"],
mp.param["mid_side_b2"],
mp.param["reverse"],
)
if torch.is_tensor(wave2):
wave = resample_audio_tensor(
wave2, bp["sr"], sr, force_mono=False
)
else:
wave = resample_audio(
wave2,
bp["sr"],
sr,
force_mono=False,
res_type="scipy",
)
return wave.transpose(0, 1) if torch.is_tensor(wave) else 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 torch.is_tensor(spec_m):
source = spec_m[
:,
mp.param["pre_filter_start"]
- 10
- input_high_end.shape[1] : mp.param["pre_filter_start"]
- 10,
:,
]
mirror = torch.flip(torch.abs(source), dims=(1,))
if "mirroring" == a:
mirror = torch.polar(mirror, torch.angle(input_high_end))
return torch.where(
torch.abs(input_high_end) <= torch.abs(mirror),
input_high_end,
mirror,
)
if "mirroring2" == a:
mirror = mirror * input_high_end * 1.7
return torch.where(
torch.abs(input_high_end) <= torch.abs(mirror),
input_high_end,
mirror,
)
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)

View File

@@ -1,198 +0,0 @@
import numpy as np
import torch
import torch.nn.functional as F
from tools.cuda_graph import clear_cuda_graph_cache, run_cuda_graph
from tqdm import tqdm
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 _execute_torch_windows(
X_mag_pad,
roi_size,
n_window,
device,
model,
aggressiveness,
data,
batch_size,
):
windows = X_mag_pad.unfold(
2,
data["window_size"],
roi_size,
)[:, :, :n_window, :]
model_dtype = next(model.parameters()).dtype
predictions = None
write_offset = 0
with torch.inference_mode():
for start in tqdm(range(0, n_window, batch_size)):
end = min(start + batch_size, n_window)
batch = (
windows[:, :, start:end, :]
.permute(2, 0, 1, 3)
.contiguous()
.to(device=device, dtype=model_dtype)
)
prediction = run_cuda_graph(
model,
"uvr-vr-%s" % repr(aggressiveness),
lambda window: model.predict(window, aggressiveness),
batch,
)
prediction = prediction.float().permute(1, 2, 0, 3).reshape(
prediction.shape[1], prediction.shape[2], -1
)
if predictions is None:
predictions = torch.empty(
prediction.shape[0],
prediction.shape[1],
n_window * roi_size,
device=prediction.device,
dtype=torch.float32,
)
end_offset = write_offset + prediction.shape[2]
predictions[:, :, write_offset:end_offset].copy_(prediction)
write_offset = end_offset
return predictions[:, :, :write_offset]
def _torch_batch_size(device):
free_bytes, _ = torch.cuda.mem_get_info(device)
free_gb = free_bytes / (1024**3)
if free_gb > 20:
return 8
if free_gb > 12:
return 4
if free_gb > 8:
return 2
return 1
def _inference_torch(X_spec, device, model, aggressiveness, data):
X_spec = X_spec.to(device)
X_mag = torch.abs(X_spec)
coef = X_mag.max().clamp_min(1e-8)
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))
def execute(pad_left, pad_right, windows_count):
padded = F.pad(X_mag_pre, (pad_left, pad_right))
batch_size = _torch_batch_size(device)
while True:
try:
return _execute_torch_windows(
padded,
roi_size,
windows_count,
device,
model,
aggressiveness,
data,
batch_size,
)
except torch.cuda.OutOfMemoryError:
clear_cuda_graph_cache(model)
torch.cuda.empty_cache()
if batch_size == 1:
raise
batch_size = max(1, batch_size // 2)
pred = execute(pad_l, pad_r, n_window)[:, :, :n_frame]
if data["tta"]:
pad_l += roi_size // 2
pad_r += roi_size // 2
n_window += 1
pred_tta = execute(pad_l, pad_r, n_window)
pred_tta = pred_tta[:, :, roi_size // 2 :][:, :, :n_frame]
pred = (pred + pred_tta) * 0.5
return pred * coef, X_mag, None
def inference(X_spec, device, model, aggressiveness, data):
"""
data : dic configs
"""
if torch.is_tensor(X_spec) and X_spec.device.type == "cuda":
return _inference_torch(X_spec, device, model, aggressiveness, data)
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 = run_cuda_graph(
model,
"uvr-vr-%s" % repr(aggressiveness),
lambda window: model.predict(window, aggressiveness),
X_mag_window,
)
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)

View File

@@ -1,446 +0,0 @@
import os
import logging
import sysconfig
logger = logging.getLogger(__name__)
import numpy as np
import soundfile as sf
import torch
from tqdm import tqdm
from infer.audio import load_audio, load_audio_tensor
_ORT_CUDA_DLL_HANDLES = []
def _configure_ort_cuda_dll_paths():
"""Expose pip-installed CUDA 11/cuDNN 8 DLLs to ONNX Runtime on Windows."""
if os.name != "nt":
return
site_packages = os.path.normpath(sysconfig.get_paths()["purelib"])
nvidia_root = os.path.join(site_packages, "nvidia")
dll_dirs = [
os.path.join(nvidia_root, "cuda_runtime", "bin"),
os.path.join(nvidia_root, "cublas", "bin"),
os.path.join(nvidia_root, "cufft", "bin"),
os.path.join(nvidia_root, "cudnn", "bin"),
os.path.join(nvidia_root, "cuda_nvrtc", "bin"),
os.path.join(os.path.dirname(torch.__file__), "lib"),
]
dll_dirs = [path for path in dll_dirs if os.path.isdir(path)]
if not dll_dirs:
return
current_path = os.environ.get("PATH", "")
current_dirs = [path for path in current_path.split(os.pathsep) if path]
known_dirs = {os.path.normcase(os.path.normpath(path)) for path in current_dirs}
prepend_dirs = []
for path in dll_dirs:
normalized = os.path.normcase(os.path.normpath(path))
if normalized not in known_dirs:
prepend_dirs.append(path)
known_dirs.add(normalized)
if prepend_dirs:
os.environ["PATH"] = os.pathsep.join(prepend_dirs + current_dirs)
# Python 3.8+ restricts DLL lookup for extension modules. Keep the handles
# alive for the process lifetime in addition to updating PATH.
if hasattr(os, "add_dll_directory"):
for path in dll_dirs:
try:
_ORT_CUDA_DLL_HANDLES.append(os.add_dll_directory(path))
except OSError:
logger.warning("Unable to add ONNX Runtime DLL directory: %s", path)
_configure_ort_cuda_dll_paths()
cpu = torch.device("cpu")
class ConvTDFNetTrim:
def __init__(self, device, dim_f, dim_t, n_fft, hop=1024):
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.dim_c = 4
self.freq_pad = torch.zeros(
[1, self.dim_c, self.n_bins - self.dim_f, self.dim_t],
device=device,
)
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 = self.freq_pad.expand(x.shape[0], -1, -1, -1)
x = torch.cat([x, freq_pad], -2)
c = 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,
dim_f=dim_f,
dim_t=dim_t,
n_fft=n_fft,
)
class Predictor:
def __init__(self, args):
import onnxruntime as ort
available_providers = ort.get_available_providers()
requested_providers = [
provider[0] if isinstance(provider, (tuple, list)) else provider
for provider in args.providers
]
logger.info("ONNX Runtime available providers: %s", available_providers)
if (
"CUDAExecutionProvider" in requested_providers
and "CUDAExecutionProvider" not in available_providers
):
raise RuntimeError(
"CUDAExecutionProvider is required for the FoxJoy ONNX model, "
"but the installed ONNX Runtime does not provide it. Install "
"the matching CUDA ONNX Runtime dependencies with this "
"project's runtime Python."
)
if (
"DmlExecutionProvider" in requested_providers
and "DmlExecutionProvider" not in available_providers
):
raise RuntimeError(
"DmlExecutionProvider is required for the FoxJoy ONNX model, "
"but the installed ONNX Runtime does not provide it. Install "
"requirments_cpu_py312.txt with this project's runtime Python."
)
self.args = args
try:
requested_torch_device = torch.device(args.device)
except Exception:
requested_torch_device = cpu
if requested_torch_device.type == "cuda" and requested_torch_device.index is None:
requested_torch_device = torch.device("cuda:0")
# DirectML and CPU keep the established NumPy/CPU STFT path. The
# Torch CUDA path is enabled only after the ORT session confirms that
# its CUDA provider really became the primary provider.
model_device = requested_torch_device if requested_torch_device.type == "cuda" else cpu
self.model_ = get_models(
device=model_device,
dim_f=args.dim_f,
dim_t=args.dim_t,
n_fft=args.n_fft,
)
self.model = ort.InferenceSession(
os.path.join(args.onnx, "vocals.onnx"),
providers=args.providers,
)
active_providers = self.model.get_providers()
logger.info("ONNX Runtime active providers: %s", active_providers)
if (
"CUDAExecutionProvider" in requested_providers
and (
not active_providers
or active_providers[0] != "CUDAExecutionProvider"
)
):
raise RuntimeError(
"The FoxJoy ONNX model did not activate CUDAExecutionProvider; "
"check the CUDA 11/cuDNN 8 DLL installation."
)
if (
"DmlExecutionProvider" in requested_providers
and (
not active_providers
or active_providers[0] != "DmlExecutionProvider"
)
):
raise RuntimeError(
"The FoxJoy ONNX model did not activate DmlExecutionProvider; "
"check the ONNX Runtime DirectML installation."
)
self.cuda_pipeline = bool(
requested_torch_device.type == "cuda"
and active_providers
and active_providers[0] == "CUDAExecutionProvider"
)
self.torch_device = requested_torch_device if self.cuda_pipeline else cpu
logger.info(
"ONNX load done; FoxJoy tensor pipeline=%s, torch device=%s",
"cuda" if self.cuda_pipeline else "cpu-compatible",
self.torch_device,
)
def _run_ort_cuda(self, input_tensor, output_tensor):
input_tensor = input_tensor.contiguous()
if input_tensor.dtype != torch.float32:
input_tensor = input_tensor.float()
if not output_tensor.is_contiguous() or output_tensor.dtype != torch.float32:
raise RuntimeError("FoxJoy CUDA output buffer must be contiguous float32")
device_id = self.torch_device.index
io_binding = self.model.io_binding()
io_binding.bind_input(
name=self.model.get_inputs()[0].name,
device_type="cuda",
device_id=device_id,
element_type=np.float32,
shape=tuple(input_tensor.shape),
buffer_ptr=input_tensor.data_ptr(),
)
io_binding.bind_output(
name=self.model.get_outputs()[0].name,
device_type="cuda",
device_id=device_id,
element_type=np.float32,
shape=tuple(output_tensor.shape),
buffer_ptr=output_tensor.data_ptr(),
)
# ORT owns a separate CUDA stream by default. Explicit boundaries
# guarantee that it sees the completed Torch STFT and that Torch sees
# the completed output without staging either tensor through NumPy.
torch.cuda.synchronize(self.torch_device)
self.model.run_with_iobinding(io_binding)
torch.cuda.synchronize(self.torch_device)
return input_tensor
def _infer_cuda(self, spek):
spek = spek.contiguous().float()
output = torch.empty_like(spek)
if self.args.denoise:
# Reuse both the ORT output allocation and the input allocation
# for the negative/positive passes. Only the accumulator is
# separate because the second ORT run overwrites its output.
spek.neg_()
spek = self._run_ort_cuda(spek, output)
prediction = output * -0.5
spek.neg_()
spek = self._run_ort_cuda(spek, output)
prediction.add_(output, alpha=0.5)
return prediction
self._run_ort_cuda(spek, output)
return output
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
segment = mix[:, start:end]
# CUDA segments are views of the already resident decoded audio;
# copying every segment would almost double long-file VRAM use.
segmented_mix[skip] = segment if torch.is_tensor(segment) else segment.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
if self.cuda_pipeline and torch.is_tensor(cmix):
cmix = cmix.to(self.torch_device, dtype=torch.float32)
mix_p = torch.cat(
(
cmix.new_zeros((2, trim)),
cmix,
cmix.new_zeros((2, pad)),
cmix.new_zeros((2, trim)),
),
1,
)
else:
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 = mix_p[:, i : i + model.chunk_size]
if not torch.is_tensor(waves):
waves = np.array(waves)
mix_waves.append(waves)
i += gen_size
if torch.is_tensor(mix_waves[0]):
mix_waves = torch.stack(mix_waves).float()
else:
mix_waves = torch.from_numpy(np.asarray(mix_waves, dtype=np.float32))
with torch.no_grad():
_ort = self.model
if self.cuda_pipeline:
# One H2D for all windows in this outer segment. STFT,
# both denoise passes and ISTFT remain on the selected
# CUDA device; only the finished waveform returns to CPU.
if mix_waves.device != self.torch_device:
mix_waves = mix_waves.to(self.torch_device, non_blocking=True)
spek = model.stft(mix_waves)
spec_pred = self._infer_cuda(spek)
tar_waves = model.istft(spec_pred)
tar_signal = (
tar_waves[:, :, trim:-trim]
.transpose(0, 1)
.reshape(2, -1)[:, :-pad]
.cpu()
.numpy()
)
else:
spek = model.stft(mix_waves)
if self.args.denoise:
spek_numpy = spek.numpy()
spec_pred = (
-_ort.run(None, {"input": -spek_numpy})[0] * 0.5
+ _ort.run(None, {"input": spek_numpy})[0] * 0.5
)
tar_waves = model.istft(torch.from_numpy(spec_pred))
else:
spec_pred = _ort.run(None, {"input": spek.numpy()})[0]
tar_waves = model.istft(torch.from_numpy(spec_pred))
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
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)
if self.cuda_pipeline:
mix = load_audio_tensor(m, 44100, force_mono=False)
mix = mix.to(self.torch_device)
else:
mix = load_audio(m, 44100, force_mono=False)
rate = 44100
if mix.ndim == 1:
mix = mix.unsqueeze(0) if torch.is_tensor(mix) else mix[np.newaxis, :]
if mix.shape[0] == 1:
mix = mix.repeat(2, 1) if torch.is_tensor(mix) else np.repeat(mix, 2, axis=0)
elif mix.shape[0] > 2:
mix = mix[:2].contiguous() if torch.is_tensor(mix) else np.ascontiguousarray(mix[:2])
sources = self.demix(mix)
opt = sources[0].T
if torch.is_tensor(mix):
mix = mix.transpose(0, 1).float().cpu().numpy()
else:
mix = mix.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, providers, device="cpu"):
self.onnx = os.path.join(
os.getenv("weight_uvr5_root", "assets/uvr5_weights"),
"onnx_dereverb_By_FoxJoy",
)
self.chunks = chunks
self.providers = providers
self.device = device
self.margin = 44100
self.dim_t = 9
self.dim_f = 3072
self.n_fft = 6144
self.denoise = True
self.pred = Predictor(self)
def _path_audio_(self, input, others_root, vocal_root, format, is_hp3=False):
self.pred.prediction(input, vocal_root, others_root, format)

View File

@@ -1,6 +0,0 @@
from .rotary_embedding_torch import (
apply_rotary_emb,
RotaryEmbedding,
apply_learned_rotations,
broadcat
)

View File

@@ -1,186 +0,0 @@
from __future__ import annotations
from math import pi, log
import warnings
warnings.filterwarnings(
"ignore",
message="`torch.cuda.amp.autocast.*is deprecated.*",
category=FutureWarning,
)
import torch
from torch.nn import Module, ModuleList
from torch.cuda.amp import autocast
from torch import nn, einsum, broadcast_tensors, Tensor
from einops import rearrange, repeat
from typing import Literal
def exists(val):
return val is not None
def default(val, d):
return val if exists(val) else d
def broadcat(tensors, dim=-1):
broadcasted_tensors = broadcast_tensors(*tensors)
return torch.cat(broadcasted_tensors, dim=dim)
def rotate_half(x):
x = rearrange(x, '... (d r) -> ... d r', r=2)
(x1, x2) = x.unbind(dim=-1)
x = torch.stack((-x2, x1), dim=-1)
return rearrange(x, '... d r -> ... (d r)')
@autocast(enabled=False)
def apply_rotary_emb(freqs, t, start_index=0, scale=1.0, seq_dim=-2):
dtype = t.dtype
if t.ndim == 3:
seq_len = t.shape[seq_dim]
freqs = freqs[-seq_len:]
rot_dim = freqs.shape[-1]
end_index = start_index + rot_dim
assert rot_dim <= t.shape[-1], f'feature dimension {t.shape[-1]} is not of sufficient size to rotate in all the positions {rot_dim}'
(t_left, t, t_right) = (t[..., :start_index], t[..., start_index:end_index], t[..., end_index:])
t = t * freqs.cos() * scale + rotate_half(t) * freqs.sin() * scale
if t.device.type == 'privateuseone':
# DirectML rejects concatenation when one of the slices has a zero
# length. Rotary embeddings normally cover the complete head, so both
# edge slices are empty; omitting them is mathematically identical.
parts = tuple(part for part in (t_left, t, t_right) if part.shape[-1] > 0)
out = parts[0] if len(parts) == 1 else torch.cat(parts, dim=-1)
else:
out = torch.cat((t_left, t, t_right), dim=-1)
return out.type(dtype)
def apply_learned_rotations(rotations, t, start_index=0, freq_ranges=None):
if exists(freq_ranges):
rotations = einsum('..., f -> ... f', rotations, freq_ranges)
rotations = rearrange(rotations, '... r f -> ... (r f)')
rotations = repeat(rotations, '... n -> ... (n r)', r=2)
return apply_rotary_emb(rotations, t, start_index=start_index)
class RotaryEmbedding(Module):
def __init__(self, dim, custom_freqs=None, freqs_for='lang', theta=10000, max_freq=10, num_freqs=1, learned_freq=False, use_xpos=False, xpos_scale_base=512, interpolate_factor=1.0, theta_rescale_factor=1.0, seq_before_head_dim=False, cache_if_possible=True):
super().__init__()
theta *= theta_rescale_factor ** (dim / (dim - 2))
self.freqs_for = freqs_for
if exists(custom_freqs):
freqs = custom_freqs
elif freqs_for == 'lang':
freqs = 1.0 / theta ** (torch.arange(0, dim, 2)[:dim // 2].float() / dim)
elif freqs_for == 'pixel':
freqs = torch.linspace(1.0, max_freq / 2, dim // 2) * pi
elif freqs_for == 'constant':
freqs = torch.ones(num_freqs).float()
self.cache_if_possible = cache_if_possible
self.tmp_store('cached_freqs', None)
self.tmp_store('cached_scales', None)
self.freqs = nn.Parameter(freqs, requires_grad=learned_freq)
self.learned_freq = learned_freq
self.tmp_store('dummy', torch.tensor(0))
self.seq_before_head_dim = seq_before_head_dim
self.default_seq_dim = -3 if seq_before_head_dim else -2
assert interpolate_factor >= 1.0
self.interpolate_factor = interpolate_factor
self.use_xpos = use_xpos
if not use_xpos:
self.tmp_store('scale', None)
return
scale = (torch.arange(0, dim, 2) + 0.4 * dim) / (1.4 * dim)
self.scale_base = xpos_scale_base
self.tmp_store('scale', scale)
self.apply_rotary_emb = staticmethod(apply_rotary_emb)
@property
def device(self):
return self.dummy.device
def tmp_store(self, key, value):
self.register_buffer(key, value, persistent=False)
def get_seq_pos(self, seq_len, device, dtype, offset=0):
return (torch.arange(seq_len, device=device, dtype=dtype) + offset) / self.interpolate_factor
def rotate_queries_or_keys(self, t, seq_dim=None, offset=0, scale=None):
seq_dim = default(seq_dim, self.default_seq_dim)
assert not self.use_xpos or exists(scale), 'you must use `.rotate_queries_and_keys` method instead and pass in both queries and keys, for length extrapolatable rotary embeddings'
(device, dtype, seq_len) = (t.device, t.dtype, t.shape[seq_dim])
seq = self.get_seq_pos(seq_len, device=device, dtype=dtype, offset=offset)
freqs = self.forward(seq, seq_len=seq_len, offset=offset)
if seq_dim == -3:
freqs = rearrange(freqs, 'n d -> n 1 d')
return apply_rotary_emb(freqs, t, scale=default(scale, 1.0), seq_dim=seq_dim)
def rotate_queries_with_cached_keys(self, q, k, seq_dim=None, offset=0):
(dtype, device, seq_dim) = (q.dtype, q.device, default(seq_dim, self.default_seq_dim))
(q_len, k_len) = (q.shape[seq_dim], k.shape[seq_dim])
assert q_len <= k_len
q_scale = k_scale = 1.0
if self.use_xpos:
seq = self.get_seq_pos(k_len, dtype=dtype, device=device)
q_scale = self.get_scale(seq[-q_len:]).type(dtype)
k_scale = self.get_scale(seq).type(dtype)
rotated_q = self.rotate_queries_or_keys(q, seq_dim=seq_dim, scale=q_scale, offset=k_len - q_len + offset)
rotated_k = self.rotate_queries_or_keys(k, seq_dim=seq_dim, scale=k_scale ** (-1))
rotated_q = rotated_q.type(q.dtype)
rotated_k = rotated_k.type(k.dtype)
return (rotated_q, rotated_k)
def rotate_queries_and_keys(self, q, k, seq_dim=None):
seq_dim = default(seq_dim, self.default_seq_dim)
assert self.use_xpos
(device, dtype, seq_len) = (q.device, q.dtype, q.shape[seq_dim])
seq = self.get_seq_pos(seq_len, dtype=dtype, device=device)
freqs = self.forward(seq, seq_len=seq_len)
scale = self.get_scale(seq, seq_len=seq_len).to(dtype)
if seq_dim == -3:
freqs = rearrange(freqs, 'n d -> n 1 d')
scale = rearrange(scale, 'n d -> n 1 d')
rotated_q = apply_rotary_emb(freqs, q, scale=scale, seq_dim=seq_dim)
rotated_k = apply_rotary_emb(freqs, k, scale=scale ** (-1), seq_dim=seq_dim)
rotated_q = rotated_q.type(q.dtype)
rotated_k = rotated_k.type(k.dtype)
return (rotated_q, rotated_k)
def get_scale(self, t, seq_len=None, offset=0):
assert self.use_xpos
should_cache = self.cache_if_possible and exists(seq_len)
if should_cache and exists(self.cached_scales) and (seq_len + offset <= self.cached_scales.shape[0]):
return self.cached_scales[offset:offset + seq_len]
scale = 1.0
if self.use_xpos:
power = (t - len(t) // 2) / self.scale_base
scale = self.scale ** rearrange(power, 'n -> n 1')
scale = torch.cat((scale, scale), dim=-1)
if should_cache:
self.tmp_store('cached_scales', scale)
return scale
def get_axial_freqs(self, *dims):
Colon = slice(None)
all_freqs = []
for (ind, dim) in enumerate(dims):
if self.freqs_for == 'pixel':
pos = torch.linspace(-1, 1, steps=dim, device=self.device)
else:
pos = torch.arange(dim, device=self.device)
freqs = self.forward(pos, seq_len=dim)
all_axis = [None] * len(dims)
all_axis[ind] = Colon
new_axis_slice = (Ellipsis, *all_axis, Colon)
all_freqs.append(freqs[new_axis_slice])
all_freqs = broadcast_tensors(*all_freqs)
return torch.cat(all_freqs, dim=-1)
@autocast(enabled=False)
def forward(self, t, seq_len=None, offset=0):
should_cache = self.cache_if_possible and (not self.learned_freq) and exists(seq_len) and (self.freqs_for != 'pixel')
if should_cache and exists(self.cached_freqs) and (offset + seq_len <= self.cached_freqs.shape[0]):
return self.cached_freqs[offset:offset + seq_len].detach()
freqs = self.freqs
freqs = einsum('..., f -> ... f', t.type(freqs.dtype), freqs)
freqs = repeat(freqs, '... n -> ... (n r)', r=2)
if should_cache:
self.tmp_store('cached_freqs', freqs.detach())
return freqs

View File

@@ -1,456 +0,0 @@
import os
parent_directory = os.path.dirname(os.path.abspath(__file__))
import logging
logger = logging.getLogger(__name__)
import numpy as np
import soundfile as sf
import torch
from infer.audio import (
TORCHAUDIO_GPU_ENABLED,
load_audio,
load_audio_tensor,
resample_audio,
resample_audio_tensor,
)
from tools.uvr5.lib.lib_v5 import nets_61968KB as Nets
from tools.uvr5.lib.lib_v5 import spec_utils
from tools.uvr5.lib.lib_v5.model_param_init import ModelParameters
from tools.uvr5.lib.lib_v5.nets_new import CascadedNet
from tools.uvr5.lib.utils import inference
def _ensure_stereo(audio):
audio = np.asarray(audio, dtype=np.float32)
if audio.ndim == 1:
audio = audio[np.newaxis, :]
if audio.shape[0] == 1:
return np.repeat(audio, 2, axis=0)
if audio.shape[0] > 2:
return np.ascontiguousarray(audio[:2])
return audio
def _ensure_stereo_tensor(audio, device):
if audio.ndim == 1:
audio = audio.unsqueeze(0)
if audio.shape[0] == 1:
audio = audio.repeat(2, 1)
elif audio.shape[0] > 2:
audio = audio[:2]
return audio.to(device=device)
def _cuda_device(device):
parsed = device if isinstance(device, torch.device) else torch.device(device)
return parsed if parsed.type == "cuda" else None
def _vr_gpu_memory_fits(audio, mp, device):
highest_band = len(mp.param["band"])
frames = max(
1,
int(audio.shape[-1] // mp.param["band"][highest_band]["hl"] + 1),
)
band_bins = sum(
mp.param["band"][band]["n_fft"] // 2 + 1
for band in mp.param["band"]
)
combined_bins = mp.param["bins"] + 1
# Complex band spectra + combined/target spectra + magnitude/prediction.
estimated = frames * 2 * (
band_bins * 8 + combined_bins * (8 * 3 + 4 * 3)
)
free_bytes, _ = torch.cuda.mem_get_info(device)
return estimated <= int(free_bytes * 0.42)
def _prepare_spectrogram(music_file, mp, data, device, allow_gpu=True):
cuda_device = _cuda_device(device)
use_gpu = bool(
allow_gpu and cuda_device is not None and TORCHAUDIO_GPU_ENABLED
)
if use_gpu:
try:
high_sr = mp.param["band"][len(mp.param["band"])]["sr"]
high_wave = _ensure_stereo_tensor(
load_audio_tensor(music_file, high_sr, force_mono=False),
cuda_device,
)
if not _vr_gpu_memory_fits(high_wave, mp, cuda_device):
use_gpu = False
high_wave = high_wave.float().cpu().numpy()
except torch.cuda.OutOfMemoryError:
torch.cuda.empty_cache()
use_gpu = False
high_wave = None
else:
high_wave = None
input_high_end_h = None
input_high_end = None
X_spec_s = {}
bands_n = len(mp.param["band"])
previous_wave = None
for d in range(bands_n, 0, -1):
bp = mp.param["band"][d]
if d == bands_n:
if high_wave is None:
current_wave = _ensure_stereo(
load_audio(music_file, bp["sr"], force_mono=False)
)
else:
current_wave = high_wave
elif use_gpu:
current_wave = resample_audio_tensor(
previous_wave,
mp.param["band"][d + 1]["sr"],
bp["sr"],
force_mono=False,
)
else:
current_wave = resample_audio(
previous_wave,
mp.param["band"][d + 1]["sr"],
bp["sr"],
force_mono=False,
res_type=bp["res_type"],
)
X_spec_s[d] = spec_utils.wave_to_spectrogram_mt(
current_wave,
bp["hl"],
bp["n_fft"],
mp.param["mid_side"],
mp.param["mid_side_b2"],
mp.param["reverse"],
)
if d == bands_n and data["high_end_process"] != "none":
input_high_end_h = (bp["n_fft"] // 2 - bp["crop_stop"]) + (
mp.param["pre_filter_stop"] - mp.param["pre_filter_start"]
)
input_high_end = X_spec_s[d][
:, bp["n_fft"] // 2 - input_high_end_h : bp["n_fft"] // 2, :
]
if torch.is_tensor(input_high_end):
input_high_end = input_high_end.clone()
previous_wave = current_wave
X_spec_m = spec_utils.combine_spectrograms(X_spec_s, mp)
del previous_wave, X_spec_s
return X_spec_m, input_high_end_h, input_high_end
def _wave_for_write(wave):
if torch.is_tensor(wave):
return wave.detach().to(device="cpu", dtype=torch.float32).numpy()
return np.asarray(wave)
def _separate_spectrogram(X_spec_m, device, model, aggressiveness, data):
with torch.no_grad():
pred, X_mag, X_phase = inference(
X_spec_m, device, model, aggressiveness, data
)
if data["postprocess"]:
if torch.is_tensor(pred):
pred_inv = torch.clamp(X_mag - pred, min=0)
else:
pred_inv = np.clip(X_mag - pred, 0, np.inf)
pred = spec_utils.mask_silence(pred, pred_inv)
if torch.is_tensor(X_spec_m):
ratio = pred.float() / X_mag.clamp_min(1e-8)
ratio = torch.nan_to_num(ratio)
y_spec_m = X_spec_m * ratio
else:
y_spec_m = pred * X_phase
return y_spec_m
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("%s/lib/lib_v5/modelparams/4band_v2.json" % parent_directory)
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)
aggresive_set = float(self.data["agg"] / 100)
aggressiveness = {
"value": aggresive_set,
"split_bin": self.mp.param["band"][1]["crop_stop"],
}
gpu_oom = False
try:
X_spec_m, input_high_end_h, input_high_end = _prepare_spectrogram(
music_file, self.mp, self.data, self.device
)
y_spec_m = _separate_spectrogram(
X_spec_m, self.device, self.model, aggressiveness, self.data
)
except torch.cuda.OutOfMemoryError:
X_spec_m = None
input_high_end = None
y_spec_m = None
gpu_oom = True
if gpu_oom:
torch.cuda.empty_cache()
X_spec_m, input_high_end_h, input_high_end = _prepare_spectrogram(
music_file, self.mp, self.data, self.device, allow_gpu=False
)
y_spec_m = _separate_spectrogram(
X_spec_m, self.device, self.model, aggressiveness, self.data
)
if is_hp3 == True:
ins_root, vocal_root = vocal_root, ins_root
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),
),
(_wave_for_write(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,
(_wave_for_write(wav_instrument) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(path):
opt_format_path = path[:-4] + ".%s" % format
cmd = 'ffmpeg -i "%s" -vn "%s" -q:a 2 -y' % (path, opt_format_path)
print(cmd)
os.system(cmd)
if os.path.exists(opt_format_path):
try:
os.remove(path)
except:
pass
if vocal_root is not None:
if torch.is_tensor(y_spec_m):
y_spec_m.neg_().add_(X_spec_m)
v_spec_m = y_spec_m
else:
np.subtract(X_spec_m, y_spec_m, out=y_spec_m)
v_spec_m = y_spec_m
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),
),
(_wave_for_write(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,
(_wave_for_write(wav_vocals) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(path):
opt_format_path = path[:-4] + ".%s" % format
cmd = 'ffmpeg -i "%s" -vn "%s" -q:a 2 -y' % (path, opt_format_path)
print(cmd)
os.system(cmd)
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("%s/lib/lib_v5/modelparams/4band_v3.json" % parent_directory)
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)
aggresive_set = float(self.data["agg"] / 100)
aggressiveness = {
"value": aggresive_set,
"split_bin": self.mp.param["band"][1]["crop_stop"],
}
gpu_oom = False
try:
X_spec_m, input_high_end_h, input_high_end = _prepare_spectrogram(
music_file, self.mp, self.data, self.device
)
y_spec_m = _separate_spectrogram(
X_spec_m, self.device, self.model, aggressiveness, self.data
)
except torch.cuda.OutOfMemoryError:
X_spec_m = None
input_high_end = None
y_spec_m = None
gpu_oom = True
if gpu_oom:
torch.cuda.empty_cache()
X_spec_m, input_high_end_h, input_high_end = _prepare_spectrogram(
music_file, self.mp, self.data, self.device, allow_gpu=False
)
y_spec_m = _separate_spectrogram(
X_spec_m, self.device, self.model, aggressiveness, self.data
)
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),
),
(_wave_for_write(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,
(_wave_for_write(wav_instrument) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(path):
opt_format_path = path[:-4] + ".%s" % format
cmd = 'ffmpeg -i "%s" -vn "%s" -q:a 2 -y' % (path, opt_format_path)
print(cmd)
os.system(cmd)
if os.path.exists(opt_format_path):
try:
os.remove(path)
except:
pass
if vocal_root is not None:
if torch.is_tensor(y_spec_m):
y_spec_m.neg_().add_(X_spec_m)
v_spec_m = y_spec_m
else:
np.subtract(X_spec_m, y_spec_m, out=y_spec_m)
v_spec_m = y_spec_m
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),
),
(_wave_for_write(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,
(_wave_for_write(wav_vocals) * 32768).astype("int16"),
self.mp.param["sr"],
)
if os.path.exists(path):
opt_format_path = path[:-4] + ".%s" % format
cmd = 'ffmpeg -i "%s" -vn "%s" -q:a 2 -y' % (path, opt_format_path)
print(cmd)
os.system(cmd)
if os.path.exists(opt_format_path):
try:
os.remove(path)
except:
pass

View File

@@ -1,111 +0,0 @@
import logging
import os
import traceback
import torch
from configs.config import Config
from tools.uvr5.bsroformer import Roformer_Loader
from tools.uvr5.mdxnet import MDXNetDereverb
from tools.uvr5.vr import AudioPre, AudioPreDeEcho
from i18n.i18n import I18nAuto
logger = logging.getLogger(__name__)
i18n = I18nAuto()
config = Config()
weight_uvr5_root = os.getenv("weight_uvr5_root", "assets/uvr5_weights")
def clean_path(path):
path = path or ""
if path.endswith(("\\", "/")):
path = path[:-1]
return path.replace("/", os.sep).replace("\\", os.sep).strip(" '\n\"\u202a")
def uvr(model_name, inp_root, save_root_vocal, paths, save_root_ins, agg, format0):
infos = []
try:
inp_root = clean_path(inp_root)
save_root_vocal = clean_path(save_root_vocal)
save_root_ins = clean_path(save_root_ins)
is_hp3 = "HP3" in model_name
if model_name == "onnx_dereverb_By_FoxJoy":
if config.dml:
providers = ["DmlExecutionProvider", "CPUExecutionProvider"]
elif torch.device(config.device).type == "cuda":
cuda_device = torch.device(config.device)
device_id = cuda_device.index if cuda_device.index is not None else 0
providers = [
("CUDAExecutionProvider", {"device_id": str(device_id)}),
"CPUExecutionProvider",
]
else:
providers = ["CPUExecutionProvider"]
pre_fun = MDXNetDereverb(15, providers, config.device)
elif "roformer" in model_name.lower():
pre_fun = Roformer_Loader(
model_path=os.path.join(weight_uvr5_root, model_name + ".ckpt"),
config_path=os.path.join(weight_uvr5_root, model_name + ".yaml"),
device=config.device,
is_half=config.is_half,
)
if not os.path.exists(
os.path.join(weight_uvr5_root, model_name + ".yaml")
):
infos.append(i18n("未找到Roformer模型配置文件正在使用内置默认配置"))
yield "\n".join(infos)
else:
func = AudioPre if "DeEcho" not in model_name else AudioPreDeEcho
pre_fun = func(
agg=int(agg),
model_path=os.path.join(weight_uvr5_root, model_name + ".pth"),
device=config.device,
is_half=config.is_half,
)
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 or [])]
for path in paths:
inp_path = os.path.join(inp_root, path)
if not os.path.isfile(inp_path):
continue
try:
# Let each model loader decode the original file. Its
# torchaudio path can then perform any required 44.1 kHz
# conversion on the selected CUDA device instead of hiding it
# behind a CPU FFmpeg pre-conversion.
pre_fun._path_audio_(
inp_path,
save_root_ins,
save_root_vocal,
format0,
is_hp3,
)
infos.append(i18n("%s → 成功") % os.path.basename(inp_path))
yield "\n".join(infos)
except Exception:
infos.append(
"%s%s\n%s"
% (os.path.basename(inp_path), i18n("失败"), traceback.format_exc())
)
yield "\n".join(infos)
except Exception:
infos.append("%s\n%s" % (i18n("失败"), 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)

View File

@@ -10,7 +10,7 @@ os.environ["RVC_CUDA_GRAPH"] = "1" if _offline_cuda_graph else "0"
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
os.environ.setdefault("no_proxy", "localhost, 127.0.0.1, ::1")
os.environ.setdefault("weight_root", "assets/weights")
os.environ.setdefault("weight_uvr5_root", "assets/uvr5_weights")
os.environ.setdefault("weight_pymss_root", "assets/pymss_weights")
os.environ.setdefault("index_root", "logs")
os.environ.setdefault("outside_index_root", "assets/indices")
os.environ.setdefault("rmvpe_root", "assets/rmvpe")
@@ -33,7 +33,7 @@ for name in os.listdir(tmp):
from configs.config import Config, GPU_INDEX, GPU_INFOS, GPU_MEMORY, IS_GPU
from infer.vc.modules import VC
from tools.uvr5.webui import uvr
from tools.pymss_webui import PYMSS_MODEL_CHOICES, get_model_info, pymss_separate
from tools.file_io import read_text
from train.process_ckpt import (
change_info,
@@ -125,7 +125,7 @@ def launch_webui_with_port_fallback(app, config):
runtime_dirs = (
os.path.join(now_dir, "logs"),
os.environ["weight_root"],
os.environ["weight_uvr5_root"],
os.environ["weight_pymss_root"],
os.environ["index_root"],
os.environ["outside_index_root"],
os.environ["rmvpe_root"],
@@ -173,7 +173,7 @@ class ToolButton(gr.Button, gr.components.FormComponent):
weight_root = os.getenv("weight_root")
weight_uvr5_root = os.getenv("weight_uvr5_root")
weight_pymss_root = os.getenv("weight_pymss_root")
outside_index_root = os.getenv("outside_index_root")
def weight_names():
@@ -190,11 +190,7 @@ def refresh_weight_choices(previous_names=None, force=False):
names = weight_names()
uvr5_names = []
for name in os.listdir(weight_uvr5_root):
if name.endswith((".pth", ".ckpt")) or "onnx" in name:
uvr5_names.append(name.replace(".pth", "").replace(".ckpt", ""))
uvr5_names.sort()
pymss_names = PYMSS_MODEL_CHOICES
def change_choices():
@@ -1514,11 +1510,11 @@ with gr.Blocks(title="RVC WebUI") as app:
outputs=[spk_item, protect0, protect1, file_index1, file_index3],
api_name="infer_change_voice",
)
with gr.TabItem(i18n("伴奏人声分离&去混响&去回声")):
with gr.TabItem(i18n("人声伴奏分离&去混响")):
with gr.Group():
gr.Markdown(
value=i18n(
"人声伴奏分离批量处理,使用UVR5模型。<br>可选择保留人声模型或使用DeEcho、DeReverb模型去除延迟和混响"
"人声伴奏与混响批量处理,使用pymss/MSST模型"
)
)
with gr.Row():
@@ -1533,22 +1529,27 @@ with gr.Blocks(title="RVC WebUI") as app:
)
with gr.Column():
model_choose = gr.Dropdown(
label=i18n("模型"), choices=uvr5_names
)
agg = gr.Slider(
minimum=0,
maximum=20,
step=1,
label=i18n("人声提取激进程度"),
value=10,
label=i18n("处理方式"),
choices=pymss_names,
value=pymss_names[0],
interactive=True,
visible=False, # 先不开放调整
)
model_info = gr.Textbox(
label=i18n("底层模型"),
value=get_model_info(pymss_names[0]),
interactive=False,
)
model_choose.change(
get_model_info,
[model_choose],
[model_info],
queue=False,
)
opt_vocal_root = gr.Textbox(
label=i18n("指定输出主人声文件夹"), value="opt"
label=i18n("主结果文件夹"), value="opt"
)
opt_ins_root = gr.Textbox(
label=i18n("指定输出非主人声文件夹"), value="opt"
label=i18n("分离残余文件夹"), value="opt"
)
format0 = gr.Radio(
label=i18n("导出文件格式"),
@@ -1559,14 +1560,13 @@ with gr.Blocks(title="RVC WebUI") as app:
but2 = gr.Button(i18n("转换"), variant="primary")
vc_output4 = gr.Textbox(label=i18n("输出信息"))
but2.click(
uvr,
pymss_separate,
[
model_choose,
dir_wav_input,
opt_vocal_root,
wav_inputs,
opt_ins_root,
agg,
format0,
],
[vc_output4],