WIP:VC select

This commit is contained in:
wataru 2023-04-11 00:21:17 +09:00
parent bd6080c52e
commit 4cc02540ea
34 changed files with 389 additions and 118 deletions

View File

@ -105,10 +105,6 @@
"name": "indexRatio",
"options": {}
},
{
"name": "noiseScale",
"options": {}
},
{
"name": "silentThreshold",
"options": {}

File diff suppressed because one or more lines are too long

View File

@ -10,9 +10,10 @@ import { far } from "@fortawesome/free-regular-svg-icons";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { AppRootProvider, useAppRoot } from "./001_provider/001_AppRootProvider";
import ErrorBoundary from "./001_provider/900_ErrorBoundary";
import { INDEXEDDB_KEY_CLIENT, INDEXEDDB_KEY_MODEL_DATA, INDEXEDDB_KEY_SERVER, INDEXEDDB_KEY_WORKLET, INDEXEDDB_KEY_WORKLETNODE, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { ClientType, INDEXEDDB_KEY_CLIENT, INDEXEDDB_KEY_MODEL_DATA, INDEXEDDB_KEY_SERVER, INDEXEDDB_KEY_WORKLET, INDEXEDDB_KEY_WORKLETNODE, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { CLIENT_TYPE, INDEXEDDB_KEY_AUDIO_OUTPUT } from "./const";
import { Demo } from "./components/demo/010_Demo";
import { ClientSelector } from "./001_ClientSelector";
library.add(fas, far, fab);
@ -21,7 +22,7 @@ const container = document.getElementById("app")!;
const root = createRoot(container);
const App = () => {
const { appGuiSettingState } = useAppRoot()
const { appGuiSettingState, clientType } = useAppRoot()
const front = useMemo(() => {
if (appGuiSettingState.appGuiSetting.type == "demo") {
return <Demo></Demo>
@ -38,7 +39,7 @@ const App = () => {
}
const AppStateWrapper = () => {
const { appGuiSettingState } = useAppRoot()
const { appGuiSettingState, clientType } = useAppRoot()
// エラーバウンダリー設定
const [error, setError] = useState<{ error: Error, errorInfo: ErrorInfo }>()
const { removeItem } = useIndexedDB({ clientType: CLIENT_TYPE })
@ -96,7 +97,11 @@ const AppStateWrapper = () => {
setError({ error, errorInfo })
}
if (!appGuiSettingState.guiSettingLoaded) {
if (!clientType) {
return <ClientSelector></ClientSelector>
} else if (!appGuiSettingState.guiSettingLoaded) {
return <></>
} else {
return (

View File

@ -0,0 +1,16 @@
import React from "react"
import { useAppRoot } from "./001_provider/001_AppRootProvider"
export const ClientSelector = () => {
const { setClientType } = useAppRoot()
return (
<div>
<div onClick={() => { setClientType("MMVCv13") }}>MMVCv13</div>
<div onClick={() => { setClientType("MMVCv15") }}>MMVCv15</div>
<div onClick={() => { setClientType("so-vits-svc-40") }}>so-vits-svc-40</div>
<div onClick={() => { setClientType("so-vits-svc-40v2") }}>so-vits-svc-40v2</div>
<div onClick={() => { setClientType("RVC") }}>RVC</div>
</div>
)
}

View File

@ -51,13 +51,14 @@ export type AppGuiSettingState = {
}
export type AppGuiSettingStateAndMethod = AppGuiSettingState & {
getAppSetting: (url: string) => Promise<void>
getAppGuiSetting: (url: string) => Promise<void>
clearAppGuiSetting: () => void
}
export const userAppGuiSetting = (): AppGuiSettingStateAndMethod => {
const [guiSettingLoaded, setGuiSettingLoaded] = useState<boolean>(false)
const [appGuiSetting, setAppGuiSetting] = useState<AppGuiSetting>(InitialAppGuiDemoSetting)
const getAppSetting = async (url: string) => {
const getAppGuiSetting = async (url: string) => {
const res = await fetch(`${url}`, {
method: "GET",
})
@ -65,9 +66,15 @@ export const userAppGuiSetting = (): AppGuiSettingStateAndMethod => {
setAppGuiSetting(appSetting)
setGuiSettingLoaded(true)
}
const clearAppGuiSetting = () => {
setAppGuiSetting(InitialAppGuiDemoSetting)
setGuiSettingLoaded(false)
}
return {
appGuiSetting,
guiSettingLoaded,
getAppSetting,
getAppGuiSetting,
clearAppGuiSetting,
}
}

View File

@ -2,23 +2,30 @@ import { ClientState, useClient, ClientType } from "@dannadori/voice-changer-cli
export type UseVCClientProps = {
audioContext: AudioContext | null
clientType: ClientType
clientType: ClientType | null
}
export type VCClientState = {
clientState: ClientState
}
export const useVCClient = (props: UseVCClientProps) => {
export const useVCClient = (props: UseVCClientProps): VCClientState => {
const clientState = useClient({
clientType: props.clientType,
audioContext: props.audioContext,
clientType: props.clientType,
})
// const setClientType = (clientType: ClientType) => {
// console.log("SET CLIENT TYPE", clientType)
// clientState.setClientType(clientType)
// }
const ret: VCClientState = {
clientState
}
return ret
}

View File

@ -1,4 +1,5 @@
import React, { useContext, useEffect } from "react";
import { ClientType } from "@dannadori/voice-changer-client-js";
import React, { useContext, useEffect, useState } from "react";
import { ReactNode } from "react";
import { AppGuiSettingStateAndMethod, userAppGuiSetting } from "../001_globalHooks/001_useAppGuiSetting";
import { AudioConfigState, useAudioConfig } from "../001_globalHooks/001_useAudioConfig";
@ -10,6 +11,8 @@ type Props = {
type AppRootValue = {
audioContextState: AudioConfigState
appGuiSettingState: AppGuiSettingStateAndMethod
clientType: ClientType | null
setClientType: (val: ClientType | null) => void
}
const AppRootContext = React.createContext<AppRootValue | null>(null);
@ -24,16 +27,20 @@ export const useAppRoot = (): AppRootValue => {
export const AppRootProvider = ({ children }: Props) => {
const audioContextState = useAudioConfig()
const appGuiSettingState = userAppGuiSetting()
const [clientType, setClientType] = useState<ClientType | null>(null)
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const modelType = params.get("modelType") || ""
appGuiSettingState.getAppSetting(`/assets/gui_settings/${modelType}.json`)
}, [])
if (!clientType) {
return
}
appGuiSettingState.getAppGuiSetting(`/assets/gui_settings/${clientType}.json`)
}, [clientType])
const providerValue: AppRootValue = {
audioContextState,
appGuiSettingState
appGuiSettingState,
clientType,
setClientType
};
return <AppRootContext.Provider value={providerValue}>{children}</AppRootContext.Provider>;
};

View File

@ -24,7 +24,7 @@ export const useAppState = (): AppStateValue => {
export const AppStateProvider = ({ children }: Props) => {
const appRoot = useAppRoot()
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext, clientType: appRoot.appGuiSettingState.appGuiSetting.id })
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext, clientType: appRoot.clientType })
const initializedRef = useRef<boolean>(false)

View File

@ -3,14 +3,15 @@ import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useIndexedDB } from "@dannadori/voice-changer-client-js";
import { INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const";
import { useAppRoot } from "../../../001_provider/001_AppRootProvider";
import { useGuiState } from "../001_GuiStateProvider"
export type ClearSettingRowProps = {
}
export const ClearSettingRow = (_props: ClearSettingRowProps) => {
const appState = useAppState()
const { appGuiSettingState } = useAppRoot()
const { appGuiSettingState, setClientType } = useAppRoot()
const guiState = useGuiState()
const clientType = appGuiSettingState.appGuiSetting.id
const { removeItem } = useIndexedDB({ clientType: clientType })
@ -21,12 +22,21 @@ export const ClearSettingRow = (_props: ClearSettingRowProps) => {
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
location.reload()
}
const onReselectVCClicked = async () => {
guiState.setIsConverting(false)
await appState.clientSetting.stop()
setClientType(null)
appGuiSettingState.clearAppGuiSetting()
}
return (
<div className="body-row split-3-3-4 left-padding-1">
<div className="body-button-container">
<div className="body-button" onClick={onClearSettingClicked}>clear setting</div>
</div>
<div className="body-item-text"></div>
<div className="body-button-container">
<div className="body-button" onClick={onReselectVCClicked}>re-select vc</div>
</div>
<div className="body-item-text"></div>
</div>
)

View File

@ -11,7 +11,7 @@ export const TuneRow = (_props: TuneRowProps) => {
<div className="body-row split-3-2-2-3 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Tuning</div>
<div>
<input type="range" className="body-item-input-slider" min="-50" max="50" step="1" value={appState.serverSetting.serverSetting.tran} onChange={(e) => {
<input type="range" className="body-item-input-slider" min="-50" max="50" step="1" value={appState.serverSetting.serverSetting.tran || 0} onChange={(e) => {
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, tran: Number(e.target.value) })
}}></input>
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.tran}</span>

View File

@ -12,7 +12,7 @@ export const ClusterInferRatioRow = (_props: ClusterInferRatioRowProps) => {
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Cluster infer ratio</div>
<div>
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.clusterInferRatio} onChange={(e) => {
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.clusterInferRatio || 0} onChange={(e) => {
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, clusterInferRatio: Number(e.target.value) })
}}></input>
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.clusterInferRatio}</span>

View File

@ -12,7 +12,7 @@ export const NoiseScaleRow = (_props: NoiseScaleRowProps) => {
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Noise Scale</div>
<div>
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.noiceScale} onChange={(e) => {
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.noiceScale || 0} onChange={(e) => {
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, noiceScale: Number(e.target.value) })
}}></input>
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.noiceScale}</span>

View File

@ -12,7 +12,7 @@ export const SilentThresholdRow = (_props: SilentThresholdRowProps) => {
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Silent Threshold</div>
<div>
<input type="range" className="body-item-input-slider" min="0.00000" max="0.001" step="0.00001" value={appState.serverSetting.serverSetting.silentThreshold} onChange={(e) => {
<input type="range" className="body-item-input-slider" min="0.00000" max="0.001" step="0.00001" value={appState.serverSetting.serverSetting.silentThreshold || 0} onChange={(e) => {
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, silentThreshold: Number(e.target.value) })
}}></input>
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.silentThreshold}</span>

View File

@ -12,7 +12,7 @@ export const IndexRatioRow = (_props: IndexRatioRowProps) => {
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">index ratio</div>
<div>
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.indexRatio} onChange={(e) => {
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.indexRatio || 0} onChange={(e) => {
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, indexRatio: Number(e.target.value) })
}}></input>
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.indexRatio}</span>

View File

@ -10,7 +10,7 @@ export const GPURow = (_props: GPURowProps) => {
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">GPU</div>
<div className="body-input-container">
<input type="number" min={-2} max={5} step={1} value={appState.serverSetting.serverSetting.gpu} onChange={(e) => {
<input type="number" min={-2} max={5} step={1} value={appState.serverSetting.serverSetting.gpu || 0} onChange={(e) => {
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, gpu: Number(e.target.value) })
}} />
</div>

View File

@ -21,7 +21,7 @@ export const TrancateNumTresholdRow = (_props: TrancateNumTresholdRowProps) => {
</div>
</div>
)
}, [appState.workletNodeSetting.workletNodeSetting, appState.workletNodeSetting.updateWorkletNodeSetting])
}, [appState.workletSetting.setting, appState.workletSetting.setSetting])
return trancateNumTresholdRow
}

View File

@ -1,4 +1,4 @@
import { ServerInfo, ServerSettingKey } from "./const";
import { ClientType, ServerInfo, ServerSettingKey } from "./const";
type FileChunk = {
@ -132,4 +132,32 @@ export class ServerConfigurator {
return await info
}
switchModelType = async (clinetType: ClientType) => {
const url = this.serverUrl + "/model_type"
const info = new Promise<ServerInfo>(async (resolve) => {
const formData = new FormData();
formData.append("modelType", clinetType);
const request = new Request(url, {
method: 'POST',
body: formData,
});
const res = await (await fetch(request)).json() as ServerInfo
resolve(res)
})
return await info
}
getModelType = async () => {
const url = this.serverUrl + "/model_type"
const info = new Promise<ServerInfo>(async (resolve) => {
const request = new Request(url, {
method: 'GET',
});
const res = await (await fetch(request)).json() as ServerInfo
resolve(res)
})
return await info
}
}

View File

@ -3,7 +3,7 @@ import { VoiceChangerWorkletNode, VoiceChangerWorkletListener } from "./VoiceCha
import workerjs from "raw-loader!../worklet/dist/index.js";
import { VoiceFocusDeviceTransformer, VoiceFocusTransformDevice } from "amazon-chime-sdk-js";
import { createDummyMediaStream, validateUrl } from "./util";
import { DefaultVoiceChangerClientSetting, ServerSettingKey, VoiceChangerClientSetting, WorkletNodeSetting, WorkletSetting } from "./const";
import { ClientType, DefaultVoiceChangerClientSetting, ServerSettingKey, VoiceChangerClientSetting, WorkletNodeSetting, WorkletSetting } from "./const";
import { ServerConfigurator } from "./ServerConfigurator";
// オーディオデータの流れ
@ -44,15 +44,28 @@ export class VoiceChangerClient {
this.vfEnable = vfEnable
this.promiseForInitialize = new Promise<void>(async (resolve) => {
const scriptUrl = URL.createObjectURL(new Blob([workerjs], { type: "text/javascript" }));
await this.ctx.audioWorklet.addModule(scriptUrl)
// await this.ctx.audioWorklet.addModule(scriptUrl)
// this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
try {
this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
} catch (err) {
await this.ctx.audioWorklet.addModule(scriptUrl)
this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
}
// const ctx44k = new AudioContext({ sampleRate: 44100 }) // これでもプチプチが残る
const ctx44k = new AudioContext({ sampleRate: 48000 }) // 結局これが一番まし。
console.log("audio out:", ctx44k)
try {
this.vcOutNode = new VoiceChangerWorkletNode(ctx44k, voiceChangerWorkletListener); // vc node
} catch (err) {
await ctx44k.audioWorklet.addModule(scriptUrl)
this.vcOutNode = new VoiceChangerWorkletNode(ctx44k, voiceChangerWorkletListener); // vc node
}
this.currentMediaStreamAudioDestinationNode = ctx44k.createMediaStreamDestination() // output node
this.outputGainNode = ctx44k.createGain()
this.outputGainNode.gain.value = this.setting.outputGain
@ -248,6 +261,14 @@ export class VoiceChangerClient {
// コンポーネント設定、操作
/////////////////////////////////////////////////////
//## Server ##//
switchModelType = (clientType: ClientType) => {
return this.configurator.switchModelType(clientType)
}
getModelType = () => {
return this.configurator.getModelType()
}
updateServerSettings = (key: ServerSettingKey, val: string) => {
return this.configurator.updateSettings(key, val)
}

View File

@ -9,7 +9,7 @@ import { useWorkletSetting, WorkletSettingState } from "./useWorkletSetting"
export type UseClientProps = {
audioContext: AudioContext | null
clientType: ClientType
clientType: ClientType | null
}
export type ClientState = {
@ -26,6 +26,8 @@ export type ClientState = {
volume: number;
performance: PerformanceData
// setClientType: (val: ClientType) => void
// 情報取得
getInfo: () => Promise<void>
// 設定クリア
@ -50,6 +52,7 @@ const InitialPerformanceData: PerformanceData = {
export const useClient = (props: UseClientProps): ClientState => {
const [initialized, setInitialized] = useState<boolean>(false)
// const [clientType, setClientType] = useState<ClientType | null>(null)
// (1-1) クライアント
const voiceChangerClientRef = useRef<VoiceChangerClient | null>(null)
const [voiceChangerClient, setVoiceChangerClient] = useState<VoiceChangerClient | null>(voiceChangerClientRef.current)
@ -165,6 +168,8 @@ export const useClient = (props: UseClientProps): ClientState => {
volume,
performance,
// setClientType,
// 情報取得
getInfo,

View File

@ -5,7 +5,7 @@ import { VoiceChangerClient } from "../VoiceChangerClient"
import { useIndexedDB } from "./useIndexedDB"
export type UseClientSettingProps = {
clientType: ClientType
clientType: ClientType | null
voiceChangerClient: VoiceChangerClient | null
audioContext: AudioContext | null
}

View File

@ -3,7 +3,7 @@ import { useMemo } from "react";
import { ClientType, INDEXEDDB_DB_APP_NAME, INDEXEDDB_DB_NAME } from "../const";
export type UseIndexedDBProps = {
clientType: ClientType
clientType: ClientType | null
}
export type IndexedDBState = {
dummy: string
@ -16,31 +16,36 @@ export type IndexedDBStateAndMethod = IndexedDBState & {
}
export const useIndexedDB = (props: UseIndexedDBProps): IndexedDBStateAndMethod => {
const clientType = props.clientType || "default"
localForage.config({
driver: localForage.INDEXEDDB,
name: INDEXEDDB_DB_APP_NAME,
version: 1.0,
storeName: `${INDEXEDDB_DB_NAME}_${props.clientType}`,
storeName: `${INDEXEDDB_DB_NAME}`,
description: 'appStorage'
})
const setItem = useMemo(() => {
return async (key: string, value: unknown) => {
await localForage.setItem(key, value)
const clientKey = `${clientType}_${key}`
await localForage.setItem(clientKey, value)
}
}, [])
}, [props.clientType])
const getItem = useMemo(() => {
return async (key: string) => {
return await localForage.getItem(key)
const clientKey = `${clientType}_${key}`
return await localForage.getItem(clientKey)
}
}, [])
}, [props.clientType])
const removeItem = useMemo(() => {
return async (key: string) => {
return await localForage.removeItem(key)
const clientKey = `${clientType}_${key}`
return await localForage.removeItem(clientKey)
}
}, [])
}, [props.clientType])
return {

View File

@ -38,7 +38,7 @@ const InitialFileUploadSetting: FileUploadSetting = {
}
export type UseServerSettingProps = {
clientType: ClientType
clientType: ClientType | null
voiceChangerClient: VoiceChangerClient | null
}
@ -58,7 +58,7 @@ export type ServerSettingState = {
export const useServerSetting = (props: UseServerSettingProps): ServerSettingState => {
// const settingRef = useRef<VoiceChangerServerSetting>(DefaultVoiceChangerServerSetting)
const defaultServerSetting = useMemo(() => {
const getDefaultServerSetting = () => {
if (props.clientType == "MMVCv13") {
return DefaultServerSetting_MMVCv13
} else if (props.clientType == "MMVCv15") {
@ -75,43 +75,57 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
} else {
return DefaultServerSetting_MMVCv15
}
}, [])
const [serverSetting, setServerSetting] = useState<ServerInfo>(defaultServerSetting)
}
const [serverSetting, setServerSetting] = useState<ServerInfo>(getDefaultServerSetting())
const [fileUploadSetting, setFileUploadSetting] = useState<FileUploadSetting>(InitialFileUploadSetting)
const { setItem, getItem, removeItem } = useIndexedDB({ clientType: props.clientType })
// DBから設定取得キャッシュによる初期化
// clientTypeが新しく設定されたときに、serverのmodelType動作を変更設定反映
useEffect(() => {
const loadCache = async () => {
const setting = await getItem(INDEXEDDB_KEY_SERVER)
if (!setting) {
if (!props.voiceChangerClient) return
if (!props.clientType) return
const setInitialSetting = async () => {
// Set Model Type
await props.voiceChangerClient!.switchModelType(props.clientType!)
// Load Default (and Cache) and set
const defaultServerSetting = getDefaultServerSetting()
const cachedServerSetting = await getItem(INDEXEDDB_KEY_SERVER)
let initialSetting: ServerInfo
if (cachedServerSetting) {
initialSetting = { ...defaultServerSetting, ...cachedServerSetting as ServerInfo }
console.log("Initial Setting1:", initialSetting)
} else {
setServerSetting(setting as ServerInfo)
initialSetting = { ...defaultServerSetting }
console.log("Initial Setting2:", initialSetting)
}
setServerSetting(initialSetting)
// upload setting
for (let i = 0; i < Object.values(ServerSettingKey).length; i++) {
const k = Object.values(ServerSettingKey)[i] as keyof VoiceChangerServerSetting
const v = initialSetting[k]
if (v) {
props.voiceChangerClient!.updateServerSettings(k, "" + v)
}
}
// Load file upload cache
const fileuploadSetting = await getItem(INDEXEDDB_KEY_MODEL_DATA)
if (!fileuploadSetting) {
} else {
setFileUploadSetting(fileuploadSetting as FileUploadSetting)
}
}
loadCache()
}, [])
// サーバへキャッシュの内容を反映 (クライアント初期化した時の一回)
useEffect(() => {
if (!props.voiceChangerClient) return
for (let i = 0; i < Object.values(ServerSettingKey).length; i++) {
const k = Object.values(ServerSettingKey)[i] as keyof VoiceChangerServerSetting
const v = serverSetting[k]
if (v) {
props.voiceChangerClient.updateServerSettings(k, "" + v)
}
}
reloadServerInfo()
}, [props.voiceChangerClient])
}
setInitialSetting()
}, [props.voiceChangerClient, props.clientType])
//////////////
// 設定
@ -125,7 +139,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
const new_v = setting[k]
if (cur_v != new_v) {
const res = await props.voiceChangerClient.updateServerSettings(k, "" + new_v)
if (res.onnxExecutionProviders.length > 0) {
if (res.onnxExecutionProviders && res.onnxExecutionProviders.length > 0) {
res.onnxExecutionProvider = res.onnxExecutionProviders[0]
} else {
res.onnxExecutionProvider = "CPUExecutionProvider"

View File

@ -5,7 +5,7 @@ import { VoiceChangerClient } from "../VoiceChangerClient"
import { useIndexedDB } from "./useIndexedDB"
export type UseWorkletNodeSettingProps = {
clientType: ClientType
clientType: ClientType | null
voiceChangerClient: VoiceChangerClient | null
}

View File

@ -4,7 +4,7 @@ import { VoiceChangerClient } from "../VoiceChangerClient";
import { useIndexedDB } from "./useIndexedDB";
export type UseWorkletSettingProps = {
clientType: ClientType
clientType: ClientType | null
voiceChangerClient: VoiceChangerClient | null
}

View File

@ -8,8 +8,8 @@ class UvicornSuppressFilter(logging.Filter):
return False
# logger = logging.getLogger("uvicorn.error")
# logger.addFilter(UvicornSuppressFilter())
logger = logging.getLogger("uvicorn.error")
logger.addFilter(UvicornSuppressFilter())
logger = logging.getLogger("fairseq.tasks.hubert_pretraining")
logger.addFilter(UvicornSuppressFilter())

View File

@ -9,7 +9,7 @@ from fastapi import HTTPException, FastAPI, UploadFile, File, Form
from restapi.mods.FileUploader import upload_file, concat_file_chunks
from voice_changer.VoiceChangerManager import VoiceChangerManager
from const import MODEL_DIR, UPLOAD_DIR
from const import MODEL_DIR, UPLOAD_DIR, ModelType
os.makedirs(UPLOAD_DIR, exist_ok=True)
os.makedirs(MODEL_DIR, exist_ok=True)
@ -25,8 +25,8 @@ class MMVC_Rest_Fileuploader:
self.router.add_api_route("/load_model", self.post_load_model, methods=["POST"])
self.router.add_api_route("/load_model_for_train", self.post_load_model_for_train, methods=["POST"])
self.router.add_api_route("/extract_voices", self.post_extract_voices, methods=["POST"])
self.onnx_provider = ""
self.router.add_api_route("/model_type", self.post_model_type, methods=["POST"])
self.router.add_api_route("/model_type", self.get_model_type, methods=["GET"])
def post_upload_file(self, file: UploadFile = File(...), filename: str = Form(...)):
res = upload_file(UPLOAD_DIR, file, filename)
@ -95,3 +95,18 @@ class MMVC_Rest_Fileuploader:
UPLOAD_DIR, zipFilename, zipFileChunkNum, UPLOAD_DIR)
shutil.unpack_archive(zipFilePath, "MMVC_Trainer/dataset/textful/")
return {"Zip file unpacked": f"{zipFilePath}"}
def post_model_type(
self,
modelType: ModelType = Form(...),
):
info = self.voiceChangerManager.switchModelType(modelType)
json_compatible_item_data = jsonable_encoder(info)
return JSONResponse(content=json_compatible_item_data)
def get_model_type(
self,
):
info = self.voiceChangerManager.getModelType(modelType)
json_compatible_item_data = jsonable_encoder(info)
return JSONResponse(content=json_compatible_item_data)

View File

@ -273,6 +273,10 @@ class DDSP_SVC:
del self.net_g
del self.onnx_session
def __del__(self):
del self.net_g
del self.onnx_session
def cross_fade(a: np.ndarray, b: np.ndarray, idx: int):
result = np.zeros(idx + b.shape[0])

View File

@ -10,6 +10,7 @@ if sys.platform.startswith('darwin'):
else:
sys.path.append("MMVC_Client_v13/python")
from dataclasses import dataclass, asdict
import numpy as np
import torch
@ -199,6 +200,18 @@ class MMVCv13:
audio = self._pyTorch_inference(data)
return audio
def destroy(self):
def __del__(self):
del self.net_g
del self.onnx_session
remove_path = os.path.join("MMVC_Client_v13", "python")
sys.path = [x for x in sys.path if x.endswith(remove_path) == False]
for key in list(sys.modules):
val = sys.modules.get(key)
try:
file_path = val.__file__
if file_path.find("MMVC_Client_v13/python") >= 0:
print("remove", key, file_path)
sys.modules.pop(key)
except Exception as e:
pass

View File

@ -247,6 +247,19 @@ class MMVCv15:
audio = self._pyTorch_inference(data)
return audio
def destroy(self):
def __del__(self):
del self.net_g
del self.onnx_session
remove_path = os.path.join("MMVC_Client_v15", "python")
sys.path = [x for x in sys.path if x.endswith(remove_path) == False]
for key in list(sys.modules):
val = sys.modules.get(key)
try:
file_path = val.__file__
if file_path.find("MMVC_Client_v15/python") >= 0:
print("remove", key, file_path)
sys.modules.pop(key)
except Exception as e:
pass

View File

@ -277,6 +277,19 @@ class RVC:
audio = self._pyTorch_inference(data)
return audio
def destroy(self):
def __del__(self):
del self.net_g
del self.onnx_session
remove_path = os.path.join("RVC")
sys.path = [x for x in sys.path if x.endswith(remove_path) == False]
for key in list(sys.modules):
val = sys.modules.get(key)
try:
file_path = val.__file__
if file_path.find("RVC/") >= 0:
print("remove", key, file_path)
sys.modules.pop(key)
except Exception as e:
pass

View File

@ -351,9 +351,21 @@ class SoVitsSvc40:
audio = self._pyTorch_inference(data)
return audio
def destroy(self):
def __del__(self):
del self.net_g
del self.onnx_session
remove_path = os.path.join("so-vits-svc-40")
sys.path = [x for x in sys.path if x.endswith(remove_path) == False]
for key in list(sys.modules):
val = sys.modules.get(key)
try:
file_path = val.__file__
if file_path.find("so-vits-svc-40/") >= 0:
print("remove", key, file_path)
sys.modules.pop(key)
except Exception as e:
pass
def resize_f0(x, target_len):

View File

@ -314,10 +314,23 @@ class SoVitsSvc40v2:
audio = self._pyTorch_inference(data)
return audio
def destroy(self):
def __del__(self):
del self.net_g
del self.onnx_session
remove_path = os.path.join("so-vits-svc-40v2")
sys.path = [x for x in sys.path if x.endswith(remove_path) == False]
for key in list(sys.modules):
val = sys.modules.get(key)
try:
file_path = val.__file__
if file_path.find("so-vits-svc-40v2/") >= 0:
print("remove", key, file_path)
sys.modules.pop(key)
except Exception as e:
pass
def resize_f0(x, target_len):
source = np.array(x)

View File

@ -1,10 +1,10 @@
from typing import Any, Callable, Optional, Protocol, TypeAlias, Union, cast
from const import TMP_DIR, getModelType
from const import TMP_DIR, ModelType
import torch
import os
import traceback
import numpy as np
from dataclasses import dataclass, asdict
from dataclasses import dataclass, asdict, field
import resampy
@ -46,9 +46,15 @@ class VoiceChangerSettings():
recordIO: int = 0 # 0:off, 1:on
# ↓mutableな物だけ列挙
intData: list[str] = ["inputSampleRate", "crossFadeOverlapSize", "recordIO"]
floatData: list[str] = ["crossFadeOffsetRate", "crossFadeEndRate"]
strData: list[str] = []
intData: list[str] = field(
default_factory=lambda: ["inputSampleRate", "crossFadeOverlapSize", "recordIO"]
)
floatData: list[str] = field(
default_factory=lambda: ["crossFadeOffsetRate", "crossFadeEndRate"]
)
strData: list[str] = field(
default_factory=lambda: []
)
class VoiceChanger():
@ -64,8 +70,46 @@ class VoiceChanger():
self.currentCrossFadeOverlapSize = 0 # setting
self.crossfadeSize = 0 # calculated
self.modelType = getModelType()
print("[VoiceChanger] activate model type:", self.modelType)
# self.modelType = getModelType()
# print("[VoiceChanger] activate model type:", self.modelType)
# if self.modelType == "MMVCv15":
# from voice_changer.MMVCv15.MMVCv15 import MMVCv15
# self.voiceChanger = MMVCv15() # type: ignore
# elif self.modelType == "MMVCv13":
# from voice_changer.MMVCv13.MMVCv13 import MMVCv13
# self.voiceChanger = MMVCv13()
# elif self.modelType == "so-vits-svc-40v2":
# from voice_changer.SoVitsSvc40v2.SoVitsSvc40v2 import SoVitsSvc40v2
# self.voiceChanger = SoVitsSvc40v2(params)
# elif self.modelType == "so-vits-svc-40" or self.modelType == "so-vits-svc-40_c":
# from voice_changer.SoVitsSvc40.SoVitsSvc40 import SoVitsSvc40
# self.voiceChanger = SoVitsSvc40(params)
# elif self.modelType == "DDSP-SVC":
# from voice_changer.DDSP_SVC.DDSP_SVC import DDSP_SVC
# self.voiceChanger = DDSP_SVC(params)
# elif self.modelType == "RVC":
# from voice_changer.RVC.RVC import RVC
# self.voiceChanger = RVC(params)
# else:
# from voice_changer.MMVCv13.MMVCv13 import MMVCv13
# self.voiceChanger = MMVCv13()
self.voiceChanger = None
self.modelType = None
self.params = params
self.gpu_num = torch.cuda.device_count()
self.prev_audio = np.zeros(4096)
self.mps_enabled: bool = getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available()
print(f"VoiceChanger Initialized (GPU_NUM:{self.gpu_num}, mps_enabled:{self.mps_enabled})")
def switchModelType(self, modelType: ModelType):
if hasattr(self, "voiceChanger") and self.voiceChanger != None:
# return {"status": "ERROR", "msg": "vc is already selected. currently re-select is not implemented"}
del self.voiceChanger
self.voiceChanger = None
self.modelType = modelType
if self.modelType == "MMVCv15":
from voice_changer.MMVCv15.MMVCv15 import MMVCv15
self.voiceChanger = MMVCv15() # type: ignore
@ -74,25 +118,27 @@ class VoiceChanger():
self.voiceChanger = MMVCv13()
elif self.modelType == "so-vits-svc-40v2":
from voice_changer.SoVitsSvc40v2.SoVitsSvc40v2 import SoVitsSvc40v2
self.voiceChanger = SoVitsSvc40v2(params)
self.voiceChanger = SoVitsSvc40v2(self.params)
elif self.modelType == "so-vits-svc-40" or self.modelType == "so-vits-svc-40_c":
from voice_changer.SoVitsSvc40.SoVitsSvc40 import SoVitsSvc40
self.voiceChanger = SoVitsSvc40(params)
self.voiceChanger = SoVitsSvc40(self.params)
elif self.modelType == "DDSP-SVC":
from voice_changer.DDSP_SVC.DDSP_SVC import DDSP_SVC
self.voiceChanger = DDSP_SVC(params)
self.voiceChanger = DDSP_SVC(self.params)
elif self.modelType == "RVC":
from voice_changer.RVC.RVC import RVC
self.voiceChanger = RVC(params)
self.voiceChanger = RVC(self.params)
else:
from voice_changer.MMVCv13.MMVCv13 import MMVCv13
self.voiceChanger = MMVCv13()
self.gpu_num = torch.cuda.device_count()
self.prev_audio = np.zeros(4096)
self.mps_enabled: bool = getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available()
return {"status": "OK", "msg": "vc is switched."}
print(f"VoiceChanger Initialized (GPU_NUM:{self.gpu_num}, mps_enabled:{self.mps_enabled})")
def getModelType(self):
if self.modelType != None:
return {"status": "OK", "vc": self.modelType}
else:
return {"status": "OK", "vc": "none"}
def loadModel(
self,
@ -115,6 +161,7 @@ class VoiceChanger():
def get_info(self):
data = asdict(self.settings)
if hasattr(self, "voiceChanger"):
data.update(self.voiceChanger.get_info())
return data
@ -148,10 +195,12 @@ class VoiceChanger():
elif key in self.settings.strData:
setattr(self.settings, key, str(val))
else:
if hasattr(self, "voiceChanger"):
ret = self.voiceChanger.update_settings(key, val)
if ret == False:
print(f"{key} is not mutable variable or unknown variable!")
else:
print(f"voice changer is not initialized!")
return self.get_info()
def _generate_strength(self, crossfadeSize: int):

View File

@ -1,5 +1,6 @@
import numpy as np
from voice_changer.VoiceChanger import VoiceChanger
from const import ModelType
class VoiceChangerManager():
@ -37,3 +38,9 @@ class VoiceChangerManager():
else:
print("Voice Change is not loaded. Did you load a correct model?")
return np.zeros(1).astype(np.int16), []
def switchModelType(self, modelType: ModelType):
return self.voiceChanger.switchModelType(modelType)
def getModelType(self):
return self.voiceChanger.getModelType()