voice-changer/server/voice_changer/DiffusionSVC/DiffusionSVC.py

308 lines
11 KiB
Python
Raw Normal View History

2023-07-12 18:59:48 +03:00
from dataclasses import asdict
import numpy as np
from data.ModelSlot import DiffusionSVCModelSlot
from mods.log_control import VoiceChangaerLogger
2023-07-12 18:59:48 +03:00
from voice_changer.DiffusionSVC.DiffusionSVCSettings import DiffusionSVCSettings
2023-07-19 04:20:30 +03:00
from voice_changer.DiffusionSVC.inferencer.InferencerManager import InferencerManager
2023-07-14 07:54:08 +03:00
from voice_changer.DiffusionSVC.pipeline.Pipeline import Pipeline
2023-07-12 18:59:48 +03:00
from voice_changer.DiffusionSVC.pipeline.PipelineGenerator import createPipeline
2023-11-08 13:54:13 +03:00
from voice_changer.DiffusionSVC.pitchExtractor.PitchExtractorManager import (
PitchExtractorManager,
)
from voice_changer.ModelSlotManager import ModelSlotManager
2023-07-12 18:59:48 +03:00
2023-11-08 13:54:13 +03:00
from voice_changer.utils.VoiceChangerModel import (
AudioInOut,
PitchfInOut,
FeatureInOut,
VoiceChangerModel,
)
2023-07-12 18:59:48 +03:00
from voice_changer.utils.VoiceChangerParams import VoiceChangerParams
2023-07-14 07:54:08 +03:00
from voice_changer.RVC.embedder.EmbedderManager import EmbedderManager
2023-11-08 13:54:13 +03:00
2023-07-14 22:45:27 +03:00
# from voice_changer.RVC.onnxExporter.export2onnx import export2onnx
2023-07-12 18:59:48 +03:00
from voice_changer.RVC.deviceManager.DeviceManager import DeviceManager
2023-11-08 13:54:13 +03:00
from Exceptions import (
DeviceCannotSupportHalfPrecisionException,
PipelineCreateException,
PipelineNotInitializedException,
)
logger = VoiceChangaerLogger.get_instance().getLogger()
2023-07-12 18:59:48 +03:00
class DiffusionSVC(VoiceChangerModel):
def __init__(self, params: VoiceChangerParams, slotInfo: DiffusionSVCModelSlot):
logger.info("[Voice Changer] [DiffusionSVC] Creating instance ")
2023-11-08 13:54:13 +03:00
self.voiceChangerType = "Diffusion-SVC"
2023-07-12 18:59:48 +03:00
self.deviceManager = DeviceManager.get_instance()
EmbedderManager.initialize(params)
PitchExtractorManager.initialize(params)
2023-07-19 04:20:30 +03:00
InferencerManager.initialize(params)
2023-07-12 18:59:48 +03:00
self.settings = DiffusionSVCSettings()
self.params = params
self.pipeline: Pipeline | None = None
self.audio_buffer: AudioInOut | None = None
self.pitchf_buffer: PitchfInOut | None = None
self.feature_buffer: FeatureInOut | None = None
self.prevVol = 0.0
self.slotInfo = slotInfo
2023-07-15 12:35:11 +03:00
self.modelSlotManager = ModelSlotManager.get_instance(self.params.model_dir)
2023-07-12 18:59:48 +03:00
def initialize(self):
logger.info("[Voice Changer] [DiffusionSVC] Initializing... ")
self.slotInfo = self.modelSlotManager.get_slot_info(self.slotInfo.slotIndex)
2023-07-12 18:59:48 +03:00
# pipelineの生成
try:
2023-11-08 13:54:13 +03:00
self.pipeline = createPipeline(
self.slotInfo,
self.settings.gpu,
self.settings.f0Detector,
self.inputSampleRate,
self.outputSampleRate,
)
except PipelineCreateException as e: # NOQA
2023-11-08 13:54:13 +03:00
logger.error(
"[Voice Changer] pipeline create failed. check your model is valid."
)
return
2023-07-12 18:59:48 +03:00
# その他の設定
self.settings.tran = self.slotInfo.defaultTune
self.settings.dstId = self.slotInfo.dstId
2023-07-17 16:21:58 +03:00
self.settings.kStep = self.slotInfo.defaultKstep
2023-07-21 12:25:28 +03:00
self.settings.speedUp = self.slotInfo.defaultSpeedup
2023-07-12 18:59:48 +03:00
logger.info("[Voice Changer] [DiffusionSVC] Initializing... done")
2023-07-12 18:59:48 +03:00
2023-07-14 22:45:27 +03:00
def setSamplingRate(self, inputSampleRate, outputSampleRate):
self.inputSampleRate = inputSampleRate
self.outputSampleRate = outputSampleRate
self.initialize()
2023-07-12 18:59:48 +03:00
def update_settings(self, key: str, val: int | float | str):
logger.info(f"[Voice Changer][DiffusionSVC]: update_settings {key}:{val}")
2023-07-12 18:59:48 +03:00
if key in self.settings.intData:
setattr(self.settings, key, int(val))
if key == "gpu":
self.deviceManager.setForceTensor(False)
self.initialize()
elif key in self.settings.floatData:
setattr(self.settings, key, float(val))
elif key in self.settings.strData:
setattr(self.settings, key, str(val))
if key == "f0Detector" and self.pipeline is not None:
2023-11-08 13:54:13 +03:00
pitchExtractor = PitchExtractorManager.getPitchExtractor(
self.settings.f0Detector, self.settings.gpu
)
2023-07-12 18:59:48 +03:00
self.pipeline.setPitchExtractor(pitchExtractor)
else:
return False
return True
def get_info(self):
data = asdict(self.settings)
if self.pipeline is not None:
pipelineInfo = self.pipeline.getPipelineInfo()
data["pipelineInfo"] = pipelineInfo
else:
data["pipelineInfo"] = "None"
2023-07-12 18:59:48 +03:00
return data
def get_processing_sampling_rate(self):
return self.slotInfo.samplingRate
def generate_input(
self,
newData: AudioInOut,
crossfadeSize: int,
solaSearchFrame: int = 0,
):
2023-11-08 13:54:13 +03:00
newData = (
newData.astype(np.float32) / 32768.0
) # DiffusionSVCのモデルのサンプリングレートで入ってきている。extraDataLength, Crossfade等も同じSRで処理(★1)
new_feature_length = int(
((newData.shape[0] / self.inputSampleRate) * self.slotInfo.samplingRate)
/ 512
) # 100 は hubertのhosizeから (16000 / 160).
2023-07-17 16:21:58 +03:00
# ↑newData.shape[0]//sampleRate でデータ秒数。これに16000かけてhubertの世界でのデータ長。これにhop数(160)でわるとfeatsのデータサイズになる。
2023-07-12 18:59:48 +03:00
if self.audio_buffer is not None:
# 過去のデータに連結
self.audio_buffer = np.concatenate([self.audio_buffer, newData], 0)
2023-11-08 13:54:13 +03:00
self.pitchf_buffer = np.concatenate(
[self.pitchf_buffer, np.zeros(new_feature_length)], 0
)
self.feature_buffer = np.concatenate(
[
self.feature_buffer,
np.zeros([new_feature_length, self.slotInfo.embChannels]),
],
0,
)
2023-07-12 18:59:48 +03:00
else:
self.audio_buffer = newData
2023-07-13 21:33:04 +03:00
self.pitchf_buffer = np.zeros(new_feature_length)
2023-11-08 13:54:13 +03:00
self.feature_buffer = np.zeros(
[new_feature_length, self.slotInfo.embChannels]
)
2023-07-12 18:59:48 +03:00
2023-11-08 13:54:13 +03:00
convertSize = (
newData.shape[0]
+ crossfadeSize
+ solaSearchFrame
+ self.settings.extraConvertSize
)
2023-07-12 18:59:48 +03:00
if convertSize % 128 != 0: # モデルの出力のホップサイズで切り捨てが発生するので補う。
convertSize = convertSize + (128 - (convertSize % 128))
# バッファがたまっていない場合はzeroで補う
2023-11-08 13:54:13 +03:00
generateFeatureLength = (
int(
((convertSize / self.inputSampleRate) * self.slotInfo.samplingRate)
/ 512
)
+ 1
)
2023-07-12 18:59:48 +03:00
if self.audio_buffer.shape[0] < convertSize:
2023-11-08 13:54:13 +03:00
self.audio_buffer = np.concatenate(
[np.zeros([convertSize]), self.audio_buffer]
)
self.pitchf_buffer = np.concatenate(
[np.zeros(generateFeatureLength), self.pitchf_buffer]
)
self.feature_buffer = np.concatenate(
[
np.zeros([generateFeatureLength, self.slotInfo.embChannels]),
self.feature_buffer,
]
)
2023-07-12 18:59:48 +03:00
convertOffset = -1 * convertSize
2023-07-17 16:21:58 +03:00
featureOffset = -1 * generateFeatureLength
2023-07-12 18:59:48 +03:00
self.audio_buffer = self.audio_buffer[convertOffset:] # 変換対象の部分だけ抽出
2023-07-13 21:33:04 +03:00
self.pitchf_buffer = self.pitchf_buffer[featureOffset:]
2023-07-12 18:59:48 +03:00
self.feature_buffer = self.feature_buffer[featureOffset:]
# 出力部分だけ切り出して音量を確認。(TODO:段階的消音にする)
2023-07-14 22:45:27 +03:00
cropOffset = -1 * (newData.shape[0] + crossfadeSize)
2023-07-12 18:59:48 +03:00
cropEnd = -1 * (crossfadeSize)
crop = self.audio_buffer[cropOffset:cropEnd]
vol = np.sqrt(np.square(crop).mean())
2023-07-14 22:45:27 +03:00
vol = float(max(vol, self.prevVol * 0.0))
2023-07-12 18:59:48 +03:00
self.prevVol = vol
2023-11-08 13:54:13 +03:00
return (
self.audio_buffer,
self.pitchf_buffer,
self.feature_buffer,
convertSize,
vol,
)
2023-07-12 18:59:48 +03:00
2023-11-08 13:54:13 +03:00
def inference(
self, receivedData: AudioInOut, crossfade_frame: int, sola_search_frame: int
):
if self.pipeline is None:
logger.info("[Voice Changer] Pipeline is not initialized.")
raise PipelineNotInitializedException()
2023-07-14 22:45:27 +03:00
data = self.generate_input(receivedData, crossfade_frame, sola_search_frame)
audio: AudioInOut = data[0]
pitchf: PitchfInOut = data[1]
feature: FeatureInOut = data[2]
convertSize: int = data[3]
vol: float = data[4]
2023-07-12 18:59:48 +03:00
if vol < self.settings.silentThreshold:
return np.zeros(convertSize).astype(np.int16) * np.sqrt(vol)
2023-07-14 22:45:27 +03:00
if self.pipeline is None:
return np.zeros(convertSize).astype(np.int16) * np.sqrt(vol)
2023-07-15 12:35:11 +03:00
2023-07-14 22:45:27 +03:00
# device = self.pipeline.device
# audio = torch.from_numpy(audio).to(device=device, dtype=torch.float32)
# audio = self.resampler16K(audio)
2023-07-12 18:59:48 +03:00
sid = self.settings.dstId
f0_up_key = self.settings.tran
2023-07-13 21:33:04 +03:00
protect = 0
2023-07-12 18:59:48 +03:00
2023-07-15 12:35:11 +03:00
kStep = self.settings.kStep
speedUp = self.settings.speedUp
2023-07-13 21:33:04 +03:00
embOutputLayer = 12
useFinalProj = False
2023-11-08 13:54:13 +03:00
silenceFrontSec = (
self.settings.extraConvertSize / self.inputSampleRate
if self.settings.silenceFront
else 0.0
) # extaraConvertSize(既にモデルのサンプリングレートにリサンプリング済み)の秒数。モデルのサンプリングレートで処理(★1)。
2023-07-12 18:59:48 +03:00
try:
audio_out, self.pitchf_buffer, self.feature_buffer = self.pipeline.exec(
sid,
audio,
self.inputSampleRate,
2023-07-12 18:59:48 +03:00
pitchf,
feature,
f0_up_key,
2023-07-15 12:35:11 +03:00
kStep,
speedUp,
2023-07-14 22:45:27 +03:00
silenceFrontSec,
2023-07-12 18:59:48 +03:00
embOutputLayer,
useFinalProj,
2023-08-05 22:50:42 +03:00
protect,
skip_diffusion=self.settings.skipDiffusion,
2023-07-12 18:59:48 +03:00
)
2023-07-13 21:33:04 +03:00
result = audio_out.detach().cpu().numpy()
2023-07-12 18:59:48 +03:00
return result
except DeviceCannotSupportHalfPrecisionException as e: # NOQA
2023-11-08 13:54:13 +03:00
logger.warn(
"[Device Manager] Device cannot support half precision. Fallback to float...."
)
2023-07-12 18:59:48 +03:00
self.deviceManager.setForceTensor(True)
self.initialize()
# raise e
return
2023-07-15 12:35:11 +03:00
2023-07-12 18:59:48 +03:00
def __del__(self):
del self.pipeline
2023-07-14 22:45:27 +03:00
# def export2onnx(self):
# modelSlot = self.slotInfo
# if modelSlot.isONNX:
# print("[Voice Changer] export2onnx, No pyTorch filepath.")
# return {"status": "ng", "path": ""}
# output_file_simple = export2onnx(self.settings.gpu, modelSlot)
# return {
# "status": "ok",
# "path": f"/tmp/{output_file_simple}",
# "filename": output_file_simple,
# }
2023-07-15 04:01:42 +03:00
def get_model_current(self):
return [
{
"key": "defaultTune",
"val": self.settings.tran,
},
2023-08-05 07:24:11 +03:00
{
"key": "dstId",
"val": self.settings.dstId,
},
2023-07-15 04:01:42 +03:00
{
"key": "defaultKstep",
2023-07-17 01:21:06 +03:00
"val": self.settings.kStep,
2023-07-15 04:01:42 +03:00
},
{
"key": "defaultSpeedup",
2023-07-17 01:21:06 +03:00
"val": self.settings.speedUp,
2023-07-15 04:01:42 +03:00
},
]