refactoring, aggrigate to hooks

This commit is contained in:
wataru 2023-01-11 00:59:09 +09:00
parent eda0296ce8
commit e4b7eaebb4
11 changed files with 488 additions and 404 deletions

File diff suppressed because one or more lines are too long

View File

@ -8,41 +8,21 @@ import { useAdvancedSetting } from "./105_advanced_setting";
import { useSpeakerSetting } from "./103_speaker_setting"; import { useSpeakerSetting } from "./103_speaker_setting";
import { useClient } from "./hooks/useClient"; import { useClient } from "./hooks/useClient";
import { useServerControl } from "./106_server_control"; import { useServerControl } from "./106_server_control";
import { ServerSettingKey } from "@dannadori/voice-changer-client-js";
export const useMicrophoneOptions = () => { export const useMicrophoneOptions = () => {
const [audioContext, setAudioContext] = useState<AudioContext | null>(null) const [audioContext, setAudioContext] = useState<AudioContext | null>(null)
const [loadModelFunc, setLoadModelFunc] = useState<() => Promise<void>>()
const [uploadProgress, setUploadProgress] = useState<number>(0)
const [isUploading, setIsUploading] = useState<boolean>(false)
const clientState = useClient({ const clientState = useClient({
audioContext: audioContext, audioContext: audioContext,
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
}) })
const serverSetting = useServerSetting({ const serverSetting = useServerSetting({ clientState })
clientState, const deviceSetting = useDeviceSetting(audioContext, { clientState })
loadModelFunc, const speakerSetting = useSpeakerSetting({ clientState })
uploadProgress: uploadProgress, const convertSetting = useConvertSetting({ clientState })
isUploading: isUploading const advancedSetting = useAdvancedSetting({ clientState })
}) const serverControl = useServerControl({ clientState })
const deviceSetting = useDeviceSetting(audioContext)
const speakerSetting = useSpeakerSetting()
const convertSetting = useConvertSetting()
const advancedSetting = useAdvancedSetting()
const serverControl = useServerControl({
convertStart: async () => { await clientState.start(serverSetting.mmvcServerUrl, serverSetting.protocol) },
convertStop: async () => { clientState.stop() },
getInfo: clientState.getInfo,
volume: clientState.volume,
bufferingTime: clientState.bufferingTime,
responseTime: clientState.responseTime,
})
useEffect(() => { useEffect(() => {
const createAudioContext = () => { const createAudioContext = () => {
@ -55,100 +35,6 @@ export const useMicrophoneOptions = () => {
document.addEventListener('mousedown', createAudioContext); document.addEventListener('mousedown', createAudioContext);
}, []) }, [])
// 101 ServerSetting
//// サーバ変更
useEffect(() => {
clientState.setServerUrl(serverSetting.mmvcServerUrl)
}, [serverSetting.mmvcServerUrl])
//// プロトコル変更
useEffect(() => {
clientState.setProtocol(serverSetting.protocol)
}, [serverSetting.protocol])
//// フレームワーク変更
useEffect(() => {
clientState.updateSettings(ServerSettingKey.framework, serverSetting.framework)
}, [serverSetting.framework])
//// OnnxExecutionProvider変更
useEffect(() => {
clientState.updateSettings(ServerSettingKey.onnxExecutionProvider, serverSetting.onnxExecutionProvider)
}, [serverSetting.onnxExecutionProvider])
// 102 DeviceSetting
//// 入力情報の設定
useEffect(() => {
clientState.changeInput(deviceSetting.audioInput, convertSetting.bufferSize, advancedSetting.vfForceDisabled)
}, [deviceSetting.audioInput, convertSetting.bufferSize, advancedSetting.vfForceDisabled])
// 103 SpeakerSetting
// 音声変換元、変換先の設定
useEffect(() => {
clientState.updateSettings(ServerSettingKey.srcId, speakerSetting.srcId)
}, [speakerSetting.srcId])
useEffect(() => {
clientState.updateSettings(ServerSettingKey.dstId, speakerSetting.dstId)
}, [speakerSetting.dstId])
// 104 ConvertSetting
useEffect(() => {
clientState.setInputChunkNum(convertSetting.inputChunkNum)
}, [convertSetting.inputChunkNum])
useEffect(() => {
clientState.updateSettings(ServerSettingKey.convertChunkNum, convertSetting.convertChunkNum)
}, [convertSetting.convertChunkNum])
useEffect(() => {
clientState.updateSettings(ServerSettingKey.gpu, convertSetting.gpu)
}, [convertSetting.gpu])
useEffect(() => {
clientState.updateSettings(ServerSettingKey.crossFadeOffsetRate, convertSetting.crossFadeOffsetRate)
}, [convertSetting.crossFadeOffsetRate])
useEffect(() => {
clientState.updateSettings(ServerSettingKey.crossFadeEndRate, convertSetting.crossFadeEndRate)
}, [convertSetting.crossFadeEndRate])
// 105 AdvancedSetting
useEffect(() => {
clientState.setVoiceChangerMode(advancedSetting.voiceChangerMode)
}, [advancedSetting.voiceChangerMode])
// Model Load
useEffect(() => {
const loadModel = () => {
return async () => {
if (!serverSetting.pyTorchModel && !serverSetting.onnxModel) {
alert("PyTorchモデルとONNXモデルのどちらか一つ以上指定する必要があります。")
return
}
if (!serverSetting.configFile) {
alert("Configファイルを指定する必要があります。")
return
}
setUploadProgress(0)
setIsUploading(true)
const models = [serverSetting.pyTorchModel, serverSetting.onnxModel].filter(x => { return x != null }) as File[]
for (let i = 0; i < models.length; i++) {
const progRate = 1 / models.length
const progOffset = 100 * i * progRate
await clientState.uploadFile(models[i], (progress: number, end: boolean) => {
// console.log(progress * progRate + progOffset, end, progRate,)
setUploadProgress(progress * progRate + progOffset)
})
}
await clientState.uploadFile(serverSetting.configFile, (progress: number, end: boolean) => {
console.log(progress, end)
})
await clientState.loadModel(serverSetting.configFile, serverSetting.pyTorchModel, serverSetting.onnxModel)
setUploadProgress(0)
setIsUploading(false)
}
}
setLoadModelFunc(loadModel)
}, [serverSetting.configFile, serverSetting.pyTorchModel, serverSetting.onnxModel,
serverSetting.framework, serverSetting.onnxExecutionProvider, speakerSetting.srcId, speakerSetting.dstId, convertSetting.gpu, convertSetting.crossFadeOffsetRate, convertSetting.crossFadeEndRate
])
const voiceChangerSetting = useMemo(() => { const voiceChangerSetting = useMemo(() => {
return ( return (

View File

@ -1,53 +1,37 @@
import { DefaultVoiceChangerOptions, OnnxExecutionProvider, Protocol, Framework, fileSelector, ServerSettingKey } from "@dannadori/voice-changer-client-js" import { OnnxExecutionProvider, Protocol, Framework, fileSelector } from "@dannadori/voice-changer-client-js"
import React from "react" import React from "react"
import { useMemo, useState } from "react" import { useMemo } from "react"
import { ClientState } from "./hooks/useClient" import { ClientState } from "./hooks/useClient"
export type UseServerSettingProps = { export type UseServerSettingProps = {
clientState: ClientState clientState: ClientState
loadModelFunc: (() => Promise<void>) | undefined
uploadProgress: number,
isUploading: boolean
} }
export type ServerSettingState = { export type ServerSettingState = {
serverSetting: JSX.Element; serverSetting: JSX.Element;
mmvcServerUrl: string;
pyTorchModel: File | null;
configFile: File | null;
onnxModel: File | null;
framework: string;
onnxExecutionProvider: OnnxExecutionProvider;
protocol: Protocol;
} }
export const useServerSetting = (props: UseServerSettingProps): ServerSettingState => { export const useServerSetting = (props: UseServerSettingProps): ServerSettingState => {
const [mmvcServerUrl, setMmvcServerUrl] = useState<string>(DefaultVoiceChangerOptions.mmvcServerUrl)
const [pyTorchModel, setPyTorchModel] = useState<File | null>(null)
const [configFile, setConfigFile] = useState<File | null>(null)
const [onnxModel, setOnnxModel] = useState<File | null>(null)
const [protocol, setProtocol] = useState<Protocol>("sio")
const [onnxExecutionProvider, setOnnxExecutionProvider] = useState<OnnxExecutionProvider>("CPUExecutionProvider")
const [framework, setFramework] = useState<Framework>("PyTorch")
const mmvcServerUrlRow = useMemo(() => { const mmvcServerUrlRow = useMemo(() => {
const onSetServerClicked = async () => { const onSetServerClicked = async () => {
const input = document.getElementById("mmvc-server-url") as HTMLInputElement const input = document.getElementById("mmvc-server-url") as HTMLInputElement
setMmvcServerUrl(input.value) props.clientState.setSettingState({
...props.clientState.settingState,
mmvcServerUrl: input.value
})
} }
return ( return (
<div className="body-row split-3-3-4 left-padding-1 guided"> <div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">MMVC Server</div> <div className="body-item-title left-padding-1">MMVC Server</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="text" defaultValue={mmvcServerUrl} id="mmvc-server-url" className="body-item-input" /> <input type="text" defaultValue={props.clientState.settingState.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onSetServerClicked}>set</div> <div className="body-button" onClick={onSetServerClicked}>set</div>
</div> </div>
</div> </div>
) )
}, []) }, [props.clientState.settingState])
const uploadeModelRow = useMemo(() => { const uploadeModelRow = useMemo(() => {
const onPyTorchFileLoadClicked = async () => { const onPyTorchFileLoadClicked = async () => {
@ -56,10 +40,16 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
alert("モデルファイルの拡張子はpthである必要があります。") alert("モデルファイルの拡張子はpthである必要があります。")
return return
} }
setPyTorchModel(file) props.clientState.setSettingState({
...props.clientState.settingState,
pyTorchModel: file
})
} }
const onPyTorchFileClearClicked = () => { const onPyTorchFileClearClicked = () => {
setPyTorchModel(null) props.clientState.setSettingState({
...props.clientState.settingState,
pyTorchModel: null
})
} }
const onConfigFileLoadClicked = async () => { const onConfigFileLoadClicked = async () => {
const file = await fileSelector("") const file = await fileSelector("")
@ -67,10 +57,16 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
alert("モデルファイルの拡張子はjsonである必要があります。") alert("モデルファイルの拡張子はjsonである必要があります。")
return return
} }
setConfigFile(file) props.clientState.setSettingState({
...props.clientState.settingState,
configFile: file
})
} }
const onConfigFileClearClicked = () => { const onConfigFileClearClicked = () => {
setConfigFile(null) props.clientState.setSettingState({
...props.clientState.settingState,
configFile: null
})
} }
const onOnnxFileLoadClicked = async () => { const onOnnxFileLoadClicked = async () => {
const file = await fileSelector("") const file = await fileSelector("")
@ -78,16 +74,19 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
alert("モデルファイルの拡張子はonnxである必要があります。") alert("モデルファイルの拡張子はonnxである必要があります。")
return return
} }
setOnnxModel(file) props.clientState.setSettingState({
...props.clientState.settingState,
onnxModel: file
})
} }
const onOnnxFileClearClicked = () => { const onOnnxFileClearClicked = () => {
setOnnxModel(null) props.clientState.setSettingState({
...props.clientState.settingState,
onnxModel: null
})
} }
const onModelUploadClicked = async () => { const onModelUploadClicked = async () => {
if (!props.loadModelFunc) { props.clientState.loadModel()
return
}
props.loadModelFunc()
} }
return ( return (
@ -104,7 +103,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
<div className="body-row split-3-3-4 left-padding-1 guided"> <div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2">PyTorch(.pth)</div> <div className="body-item-title left-padding-2">PyTorch(.pth)</div>
<div className="body-item-text"> <div className="body-item-text">
<div>{pyTorchModel?.name}</div> <div>{props.clientState.settingState.pyTorchModel?.name}</div>
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onPyTorchFileLoadClicked}>select</div> <div className="body-button" onClick={onPyTorchFileLoadClicked}>select</div>
@ -114,7 +113,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
<div className="body-row split-3-3-4 left-padding-1 guided"> <div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2">Config(.json)</div> <div className="body-item-title left-padding-2">Config(.json)</div>
<div className="body-item-text"> <div className="body-item-text">
<div>{configFile?.name}</div> <div>{props.clientState.settingState.configFile?.name}</div>
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onConfigFileLoadClicked}>select</div> <div className="body-button" onClick={onConfigFileLoadClicked}>select</div>
@ -124,7 +123,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
<div className="body-row split-3-3-4 left-padding-1 guided"> <div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2">Onnx(.onnx)</div> <div className="body-item-title left-padding-2">Onnx(.onnx)</div>
<div className="body-item-text"> <div className="body-item-text">
<div>{onnxModel?.name}</div> <div>{props.clientState.settingState.onnxModel?.name}</div>
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onOnnxFileLoadClicked}>select</div> <div className="body-button" onClick={onOnnxFileLoadClicked}>select</div>
@ -134,7 +133,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
<div className="body-row split-3-3-4 left-padding-1 guided"> <div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2"></div> <div className="body-item-title left-padding-2"></div>
<div className="body-item-text"> <div className="body-item-text">
{props.isUploading ? `uploading.... ${props.uploadProgress}%` : ""} {props.clientState.isUploading ? `uploading.... ${props.clientState.uploadProgress}%` : ""}
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onModelUploadClicked}>upload</div> <div className="body-button" onClick={onModelUploadClicked}>upload</div>
@ -142,17 +141,24 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div> </div>
</> </>
) )
}, [pyTorchModel, configFile, onnxModel, props.loadModelFunc, props.isUploading, props.uploadProgress]) }, [
props.clientState.settingState,
props.clientState.loadModel,
props.clientState.isUploading,
props.clientState.uploadProgress])
const protocolRow = useMemo(() => { const protocolRow = useMemo(() => {
const onProtocolChanged = async (val: Protocol) => { const onProtocolChanged = async (val: Protocol) => {
setProtocol(val) props.clientState.setSettingState({
...props.clientState.settingState,
protocol: val
})
} }
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Protocol</div> <div className="body-item-title left-padding-1">Protocol</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={protocol} onChange={(e) => { <select className="body-select" value={props.clientState.settingState.protocol} onChange={(e) => {
onProtocolChanged(e.target.value as onProtocolChanged(e.target.value as
Protocol) Protocol)
}}> }}>
@ -165,17 +171,20 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div> </div>
</div> </div>
) )
}, [protocol]) }, [props.clientState.settingState])
const frameworkRow = useMemo(() => { const frameworkRow = useMemo(() => {
const onFrameworkChanged = async (val: Framework) => { const onFrameworkChanged = async (val: Framework) => {
setFramework(val) props.clientState.setSettingState({
...props.clientState.settingState,
framework: val
})
} }
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Framework</div> <div className="body-item-title left-padding-1">Framework</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={framework} onChange={(e) => { <select className="body-select" value={props.clientState.settingState.framework} onChange={(e) => {
onFrameworkChanged(e.target.value as onFrameworkChanged(e.target.value as
Framework) Framework)
}}> }}>
@ -188,20 +197,23 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div> </div>
</div> </div>
) )
}, [framework]) }, [props.clientState.settingState])
const onnxExecutionProviderRow = useMemo(() => { const onnxExecutionProviderRow = useMemo(() => {
if (framework != "ONNX") { if (props.clientState.settingState.framework != "ONNX") {
return return
} }
const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => { const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => {
setOnnxExecutionProvider(val) props.clientState.setSettingState({
...props.clientState.settingState,
onnxExecutionProvider: val
})
} }
return ( return (
<div className="body-row split-3-7 left-padding-1"> <div className="body-row split-3-7 left-padding-1">
<div className="body-item-title left-padding-2">OnnxExecutionProvider</div> <div className="body-item-title left-padding-2">OnnxExecutionProvider</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={onnxExecutionProvider} onChange={(e) => { <select className="body-select" value={props.clientState.settingState.onnxExecutionProvider} onChange={(e) => {
onOnnxExecutionProviderChanged(e.target.value as onOnnxExecutionProviderChanged(e.target.value as
OnnxExecutionProvider) OnnxExecutionProvider)
}}> }}>
@ -214,7 +226,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div> </div>
</div> </div>
) )
}, [onnxExecutionProvider, framework, mmvcServerUrl]) }, [props.clientState.settingState])
const serverSetting = useMemo(() => { const serverSetting = useMemo(() => {
return ( return (
@ -236,13 +248,5 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
return { return {
serverSetting, serverSetting,
mmvcServerUrl,
pyTorchModel,
configFile,
onnxModel,
framework,
onnxExecutionProvider,
protocol,
} }
} }

View File

@ -1,6 +1,7 @@
import { fileSelectorAsDataURL, createDummyMediaStream, SampleRate } from "@dannadori/voice-changer-client-js" import { fileSelectorAsDataURL, createDummyMediaStream, SampleRate } from "@dannadori/voice-changer-client-js"
import React, { useEffect, useMemo, useState } from "react" import React, { useEffect, useMemo, useState } from "react"
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_ORIGINAL } from "./const" import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_ORIGINAL } from "./const"
import { ClientState } from "./hooks/useClient";
const reloadDevices = async () => { const reloadDevices = async () => {
@ -29,22 +30,20 @@ const reloadDevices = async () => {
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" }) const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" })
return [audioInputs, audioOutputs] return [audioInputs, audioOutputs]
} }
export type UseDeviceSettingProps = {
clientState: ClientState
}
export type DeviceSettingState = { export type DeviceSettingState = {
deviceSetting: JSX.Element; deviceSetting: JSX.Element;
audioInput: string | MediaStream;
sampleRate: SampleRate;
} }
export const useDeviceSetting = (audioContext: AudioContext | null): DeviceSettingState => { export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDeviceSettingProps): DeviceSettingState => {
const [inputAudioDeviceInfo, setInputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([]) const [inputAudioDeviceInfo, setInputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([]) const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none") const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none")
const [audioInput, setAudioInput] = useState<string | MediaStream>("none")
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none") const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none")
const [sampleRate, setSampleRate] = useState<SampleRate>(48000)
useEffect(() => { useEffect(() => {
@ -88,11 +87,17 @@ export const useDeviceSetting = (audioContext: AudioContext | null): DeviceSetti
} }
if (audioInputForGUI == "none") { if (audioInputForGUI == "none") {
const ms = createDummyMediaStream(audioContext) const ms = createDummyMediaStream(audioContext)
setAudioInput(ms) props.clientState.setSettingState({
...props.clientState.settingState,
audioInput: ms
})
} else if (audioInputForGUI == "file") { } else if (audioInputForGUI == "file") {
// file selector (audioMediaInputRow) // file selector (audioMediaInputRow)
} else { } else {
setAudioInput(audioInputForGUI) props.clientState.setSettingState({
...props.clientState.settingState,
audioInput: audioInputForGUI
})
} }
}, [audioContext, audioInputForGUI]) }, [audioContext, audioInputForGUI])
@ -111,8 +116,10 @@ export const useDeviceSetting = (audioContext: AudioContext | null): DeviceSetti
const src = audioContext!.createMediaElementSource(audio); const src = audioContext!.createMediaElementSource(audio);
const dst = audioContext!.createMediaStreamDestination() const dst = audioContext!.createMediaStreamDestination()
src.connect(dst) src.connect(dst)
setAudioInput(dst.stream) props.clientState.setSettingState({
...props.clientState.settingState,
audioInput: dst.stream
})
// original stream to play. // original stream to play.
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement
audio_org.src = url audio_org.src = url
@ -179,7 +186,13 @@ export const useDeviceSetting = (audioContext: AudioContext | null): DeviceSetti
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Sample Rate</div> <div className="body-item-title left-padding-1">Sample Rate</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={sampleRate} onChange={(e) => { setSampleRate(Number(e.target.value) as SampleRate) }}> <select className="body-select" value={props.clientState.settingState.sampleRate} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
sampleRate: Number(e.target.value) as SampleRate
})
}}>
{ {
Object.values(SampleRate).map(x => { Object.values(SampleRate).map(x => {
return <option key={x} value={x}>{x}</option> return <option key={x} value={x}>{x}</option>
@ -189,7 +202,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null): DeviceSetti
</div> </div>
</div> </div>
) )
}, [sampleRate]) }, [props.clientState.settingState])
@ -211,7 +224,5 @@ export const useDeviceSetting = (audioContext: AudioContext | null): DeviceSetti
return { return {
deviceSetting, deviceSetting,
audioInput,
sampleRate,
} }
} }

View File

@ -1,24 +1,25 @@
import { DefaultVoiceChangerRequestParamas, DefaultVoiceChangerOptions, Speaker } from "@dannadori/voice-changer-client-js" import React, { useMemo } from "react"
import React, { useMemo, useState } from "react" import { ClientState } from "./hooks/useClient"
export type UseSpeakerSettingProps = {
clientState: ClientState
}
export const useSpeakerSetting = () => { export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
const [speakers, setSpeakers] = useState<Speaker[]>(DefaultVoiceChangerOptions.speakers)
const [editSpeakerTargetId, setEditSpeakerTargetId] = useState<number>(0)
const [editSpeakerTargetName, setEditSpeakerTargetName] = useState<string>("")
const [srcId, setSrcId] = useState<number>(DefaultVoiceChangerRequestParamas.srcId)
const [dstId, setDstId] = useState<number>(DefaultVoiceChangerRequestParamas.dstId)
const srcIdRow = useMemo(() => { const srcIdRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Source Speaker Id</div> <div className="body-item-title left-padding-1">Source Speaker Id</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={srcId} onChange={(e) => { setSrcId(Number(e.target.value)) }}> <select className="body-select" value={props.clientState.settingState.srcId} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
srcId: Number(e.target.value)
})
}}>
{ {
speakers.map(x => { props.clientState.settingState.speakers.map(x => {
return <option key={x.id} value={x.id}>{x.name}({x.id})</option> return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
}) })
} }
@ -26,16 +27,21 @@ export const useSpeakerSetting = () => {
</div> </div>
</div> </div>
) )
}, [srcId, speakers]) }, [props.clientState.settingState])
const dstIdRow = useMemo(() => { const dstIdRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Destination Speaker Id</div> <div className="body-item-title left-padding-1">Destination Speaker Id</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={dstId} onChange={(e) => { setDstId(Number(e.target.value)) }}> <select className="body-select" value={props.clientState.settingState.dstId} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
dstId: Number(e.target.value)
})
}}>
{ {
speakers.map(x => { props.clientState.settingState.speakers.map(x => {
return <option key={x.id} value={x.id}>{x.name}({x.id})</option> return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
}) })
} }
@ -43,29 +49,38 @@ export const useSpeakerSetting = () => {
</div> </div>
</div> </div>
) )
}, [dstId, speakers]) }, [props.clientState.settingState])
const editSpeakerIdMappingRow = useMemo(() => { const editSpeakerIdMappingRow = useMemo(() => {
const onSetSpeakerMappingClicked = async () => { const onSetSpeakerMappingClicked = async () => {
const targetId = editSpeakerTargetId const targetId = props.clientState.settingState.editSpeakerTargetId
const targetName = editSpeakerTargetName const targetName = props.clientState.settingState.editSpeakerTargetName
const targetSpeaker = speakers.find(x => { return x.id == targetId }) const targetSpeaker = props.clientState.settingState.speakers.find(x => { return x.id == targetId })
if (targetSpeaker) { if (targetSpeaker) {
if (targetName.length == 0) { // Delete if (targetName.length == 0) { // Delete
const newSpeakers = speakers.filter(x => { return x.id != targetId }) const newSpeakers = props.clientState.settingState.speakers.filter(x => { return x.id != targetId })
setSpeakers(newSpeakers) props.clientState.setSettingState({
...props.clientState.settingState,
speakers: newSpeakers
})
} else { // Update } else { // Update
targetSpeaker.name = targetName targetSpeaker.name = targetName
setSpeakers([...speakers]) props.clientState.setSettingState({
...props.clientState.settingState,
speakers: props.clientState.settingState.speakers
})
} }
} else { } else {
if (targetName.length == 0) { // Noop if (targetName.length == 0) { // Noop
} else {// add } else {// add
speakers.push({ props.clientState.settingState.speakers.push({
id: targetId, id: targetId,
name: targetName name: targetName
}) })
setSpeakers([...speakers]) props.clientState.setSettingState({
...props.clientState.settingState,
speakers: props.clientState.settingState.speakers
})
} }
} }
} }
@ -73,21 +88,29 @@ export const useSpeakerSetting = () => {
<div className="body-row split-3-1-2-4 left-padding-1 guided"> <div className="body-row split-3-1-2-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Edit Speaker Mapping</div> <div className="body-item-title left-padding-1">Edit Speaker Mapping</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={editSpeakerTargetId} onChange={(e) => { <input type="number" min={1} max={256} step={1} value={props.clientState.settingState.editSpeakerTargetId} onChange={(e) => {
const id = Number(e.target.value) const id = Number(e.target.value)
setEditSpeakerTargetId(id) props.clientState.setSettingState({
setEditSpeakerTargetName(speakers.find(x => { return x.id == id })?.name || "") ...props.clientState.settingState,
editSpeakerTargetId: id,
editSpeakerTargetName: props.clientState.settingState.speakers.find(x => { return x.id == id })?.name || ""
})
}} /> }} />
</div> </div>
<div className="body-input-container"> <div className="body-input-container">
<input type="text" value={editSpeakerTargetName} onChange={(e) => { setEditSpeakerTargetName(e.target.value) }} /> <input type="text" value={props.clientState.settingState.editSpeakerTargetName} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
editSpeakerTargetName: e.target.value
})
}} />
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onSetSpeakerMappingClicked}>set</div> <div className="body-button" onClick={onSetSpeakerMappingClicked}>set</div>
</div> </div>
</div> </div>
) )
}, [speakers, editSpeakerTargetId, editSpeakerTargetName]) }, [props.clientState.settingState])
const speakerSetting = useMemo(() => { const speakerSetting = useMemo(() => {
@ -107,8 +130,6 @@ export const useSpeakerSetting = () => {
return { return {
speakerSetting, speakerSetting,
srcId,
dstId,
} }
} }

View File

@ -1,25 +1,16 @@
import { DefaultVoiceChangerRequestParamas, DefaultVoiceChangerOptions, BufferSize } from "@dannadori/voice-changer-client-js" import { DefaultVoiceChangerRequestParamas, DefaultVoiceChangerOptions, BufferSize } from "@dannadori/voice-changer-client-js"
import React, { useMemo, useState } from "react" import React, { useMemo, useState } from "react"
import { ClientState } from "./hooks/useClient"
export type SpeakerSettingState = { export type UseConvertSettingProps = {
convertSetting: JSX.Element; clientState: ClientState
bufferSize: BufferSize;
inputChunkNum: number;
convertChunkNum: number;
gpu: number;
crossFadeOffsetRate: number;
crossFadeEndRate: number;
} }
export const useConvertSetting = (): SpeakerSettingState => { export type ConvertSettingState = {
convertSetting: JSX.Element;
}
const [bufferSize, setBufferSize] = useState<BufferSize>(1024) export const useConvertSetting = (props: UseConvertSettingProps): ConvertSettingState => {
const [inputChunkNum, setInputChunkNum] = useState<number>(DefaultVoiceChangerOptions.inputChunkNum)
const [convertChunkNum, setConvertChunkNum] = useState<number>(DefaultVoiceChangerRequestParamas.convertChunkNum)
const [gpu, setGpu] = useState<number>(DefaultVoiceChangerRequestParamas.gpu)
const [crossFadeOffsetRate, setCrossFadeOffsetRate] = useState<number>(DefaultVoiceChangerRequestParamas.crossFadeOffsetRate)
const [crossFadeEndRate, setCrossFadeEndRate] = useState<number>(DefaultVoiceChangerRequestParamas.crossFadeEndRate)
const bufferSizeRow = useMemo(() => { const bufferSizeRow = useMemo(() => {
return ( return (
@ -27,7 +18,12 @@ export const useConvertSetting = (): SpeakerSettingState => {
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Buffer Size</div> <div className="body-item-title left-padding-1">Buffer Size</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={bufferSize} onChange={(e) => { setBufferSize(Number(e.target.value) as BufferSize) }}> <select className="body-select" value={props.clientState.settingState.bufferSize} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
bufferSize: Number(e.target.value) as BufferSize
})
}}>
{ {
Object.values(BufferSize).map(x => { Object.values(BufferSize).map(x => {
return <option key={x} value={x}>{x}</option> return <option key={x} value={x}>{x}</option>
@ -37,18 +33,23 @@ export const useConvertSetting = (): SpeakerSettingState => {
</div> </div>
</div> </div>
) )
}, [bufferSize]) }, [props.clientState.settingState])
const inputChunkNumRow = useMemo(() => { const inputChunkNumRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Input Chunk Num(128sample/chunk)</div> <div className="body-item-title left-padding-1">Input Chunk Num(128sample/chunk)</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={inputChunkNum} onChange={(e) => { setInputChunkNum(Number(e.target.value)) }} /> <input type="number" min={1} max={256} step={1} value={props.clientState.settingState.inputChunkNum} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
inputChunkNum: Number(e.target.value)
})
}} />
</div> </div>
</div> </div>
) )
}, [inputChunkNum]) }, [props.clientState.settingState])
const convertChunkNumRow = useMemo(() => { const convertChunkNumRow = useMemo(() => {
return ( return (
@ -56,44 +57,64 @@ export const useConvertSetting = (): SpeakerSettingState => {
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Convert Chunk Num(128sample/chunk)</div> <div className="body-item-title left-padding-1">Convert Chunk Num(128sample/chunk)</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="number" min={1} max={256} step={1} value={convertChunkNum} onChange={(e) => { setConvertChunkNum(Number(e.target.value)) }} /> <input type="number" min={1} max={256} step={1} value={props.clientState.settingState.convertChunkNum} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
convertChunkNum: Number(e.target.value)
})
}} />
</div> </div>
</div> </div>
) )
}, [convertChunkNum]) }, [props.clientState.settingState])
const gpuRow = useMemo(() => { const gpuRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">GPU</div> <div className="body-item-title left-padding-1">GPU</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="number" min={-2} max={5} step={1} value={gpu} onChange={(e) => { setGpu(Number(e.target.value)) }} /> <input type="number" min={-2} max={5} step={1} value={props.clientState.settingState.gpu} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
gpu: Number(e.target.value)
})
}} />
</div> </div>
</div> </div>
) )
}, [gpu]) }, [props.clientState.settingState])
const crossFadeOffsetRateRow = useMemo(() => { const crossFadeOffsetRateRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Cross Fade Offset Rate</div> <div className="body-item-title left-padding-1">Cross Fade Offset Rate</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="number" min={0} max={1} step={0.1} value={crossFadeOffsetRate} onChange={(e) => { setCrossFadeOffsetRate(Number(e.target.value)) }} /> <input type="number" min={0} max={1} step={0.1} value={props.clientState.settingState.crossFadeOffsetRate} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
crossFadeOffsetRate: Number(e.target.value)
})
}} />
</div> </div>
</div> </div>
) )
}, [crossFadeOffsetRate]) }, [props.clientState.settingState])
const crossFadeEndRateRow = useMemo(() => { const crossFadeEndRateRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Cross Fade End Rate</div> <div className="body-item-title left-padding-1">Cross Fade End Rate</div>
<div className="body-input-container"> <div className="body-input-container">
<input type="number" min={0} max={1} step={0.1} value={crossFadeEndRate} onChange={(e) => { setCrossFadeEndRate(Number(e.target.value)) }} /> <input type="number" min={0} max={1} step={0.1} value={props.clientState.settingState.crossFadeEndRate} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
crossFadeEndRate: Number(e.target.value)
})
}} />
</div> </div>
</div> </div>
) )
}, [crossFadeEndRate]) }, [props.clientState.settingState])
const convertSetting = useMemo(() => { const convertSetting = useMemo(() => {
return ( return (
@ -115,12 +136,6 @@ export const useConvertSetting = (): SpeakerSettingState => {
return { return {
convertSetting, convertSetting,
bufferSize,
inputChunkNum,
convertChunkNum,
gpu,
crossFadeOffsetRate,
crossFadeEndRate,
} }
} }

View File

@ -1,37 +1,47 @@
import { VoiceChangerMode } from "@dannadori/voice-changer-client-js" import { VoiceChangerMode } from "@dannadori/voice-changer-client-js"
import React, { useMemo, useState } from "react" import React, { useMemo, useState } from "react"
import { ClientState } from "./hooks/useClient"
export type UseAdvancedSettingProps = {
clientState: ClientState
}
export type AdvancedSettingState = { export type AdvancedSettingState = {
advancedSetting: JSX.Element; advancedSetting: JSX.Element;
vfForceDisabled: boolean;
voiceChangerMode: VoiceChangerMode;
} }
export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSettingState => {
export const useAdvancedSetting = (): AdvancedSettingState => {
const [vfForceDisabled, setVfForceDisabled] = useState<boolean>(false)
const [voiceChangerMode, setVoiceChangerMode] = useState<VoiceChangerMode>("realtime")
const vfForceDisableRow = useMemo(() => { const vfForceDisableRow = useMemo(() => {
return ( return (
<div className="body-row split-3-3-4 left-padding-1 guided"> <div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">VF Disabled</div> <div className="body-item-title left-padding-1 ">VF Disabled</div>
<div> <div>
<input type="checkbox" checked={vfForceDisabled} onChange={(e) => setVfForceDisabled(e.target.checked)} /> <input type="checkbox" checked={props.clientState.settingState.vfForceDisabled} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
vfForceDisabled: e.target.checked
})
}} />
</div> </div>
<div className="body-button-container"> <div className="body-button-container">
</div> </div>
</div> </div>
) )
}, [vfForceDisabled]) }, [props.clientState.settingState])
const voiceChangeModeRow = useMemo(() => { const voiceChangeModeRow = useMemo(() => {
return ( return (
<div className="body-row split-3-7 left-padding-1 guided"> <div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">Voice Change Mode</div> <div className="body-item-title left-padding-1 ">Voice Change Mode</div>
<div className="body-select-container"> <div className="body-select-container">
<select className="body-select" value={voiceChangerMode} onChange={(e) => { setVoiceChangerMode(e.target.value as VoiceChangerMode) }}> <select className="body-select" value={props.clientState.settingState.voiceChangerMode} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
voiceChangerMode: e.target.value as VoiceChangerMode
})
}}>
{ {
Object.values(VoiceChangerMode).map(x => { Object.values(VoiceChangerMode).map(x => {
return <option key={x} value={x}>{x}</option> return <option key={x} value={x}>{x}</option>
@ -41,7 +51,7 @@ export const useAdvancedSetting = (): AdvancedSettingState => {
</div> </div>
</div> </div>
) )
}, []) }, [props.clientState.settingState])
const advancedSetting = useMemo(() => { const advancedSetting = useMemo(() => {
return ( return (
@ -59,8 +69,6 @@ export const useAdvancedSetting = (): AdvancedSettingState => {
return { return {
advancedSetting, advancedSetting,
vfForceDisabled,
voiceChangerMode,
} }
} }

View File

@ -1,14 +1,8 @@
import { ServerInfo } from "@dannadori/voice-changer-client-js"
import React, { useMemo, useState } from "react" import React, { useMemo, useState } from "react"
import { ClientState } from "./hooks/useClient"
export type UseServerControlProps = { export type UseServerControlProps = {
convertStart: () => Promise<void> clientState: ClientState
convertStop: () => Promise<void>
getInfo: () => Promise<void>
volume: number,
bufferingTime: number,
responseTime: number
} }
export const useServerControl = (props: UseServerControlProps) => { export const useServerControl = (props: UseServerControlProps) => {
@ -17,11 +11,11 @@ export const useServerControl = (props: UseServerControlProps) => {
const startButtonRow = useMemo(() => { const startButtonRow = useMemo(() => {
const onStartClicked = async () => { const onStartClicked = async () => {
setIsStarted(true) setIsStarted(true)
await props.convertStart() await props.clientState.start()
} }
const onStopClicked = async () => { const onStopClicked = async () => {
setIsStarted(false) setIsStarted(false)
await props.convertStop() await props.clientState.stop()
} }
const startClassName = isStarted ? "body-button-active" : "body-button-stanby" const startClassName = isStarted ? "body-button-active" : "body-button-stanby"
const stopClassName = isStarted ? "body-button-stanby" : "body-button-active" const stopClassName = isStarted ? "body-button-stanby" : "body-button-active"
@ -38,43 +32,43 @@ export const useServerControl = (props: UseServerControlProps) => {
</div> </div>
) )
}, [isStarted, props.convertStart, props.convertStop]) }, [isStarted, props.clientState.start, props.clientState.stop])
const performanceRow = useMemo(() => { const performanceRow = useMemo(() => {
return ( return (
<> <>
<div className="body-row split-3-1-1-1-4 left-padding-1 guided"> <div className="body-row split-3-1-1-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">monitor:</div> <div className="body-item-title left-padding-1">monitor:</div>
<div className="body-item-text">vol(rms):{props.volume.toFixed(4)}</div> <div className="body-item-text">vol(rms):{props.clientState.volume.toFixed(4)}</div>
<div className="body-item-text">buf(ms):{props.bufferingTime}</div> <div className="body-item-text">buf(ms):{props.clientState.bufferingTime}</div>
<div className="body-item-text">res(ms):{props.responseTime}</div> <div className="body-item-text">res(ms):{props.clientState.responseTime}</div>
<div className="body-item-text"></div> <div className="body-item-text"></div>
</div> </div>
</> </>
) )
}, [props.volume, props.bufferingTime, props.responseTime]) }, [props.clientState.volume, props.clientState.bufferingTime, props.clientState.responseTime])
const infoRow = useMemo(() => { const infoRow = useMemo(() => {
const onReloadClicked = async () => { const onReloadClicked = async () => {
const info = await props.getInfo() const info = await props.clientState.getInfo()
console.log("info", info) console.log("info", info)
} }
return ( return (
<> <>
<div className="body-row split-3-1-1-1-4 left-padding-1 guided"> <div className="body-row split-3-1-1-1-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Info:</div> <div className="body-item-title left-padding-1">Info:</div>
<div className="body-item-text">vol(rms):{props.volume.toFixed(4)}</div> <div className="body-item-text">a</div>
<div className="body-item-text">buf(ms):{props.bufferingTime}</div> <div className="body-item-text">b</div>
<div className="body-item-text">res(ms):{props.responseTime}</div> <div className="body-item-text">c</div>
<div className="body-button-container"> <div className="body-button-container">
<div className="body-button" onClick={onReloadClicked}>reload</div> <div className="body-button" onClick={onReloadClicked}>reload</div>
</div> </div>
</div> </div>
</> </>
) )
}, [props.getInfo]) }, [props.clientState.getInfo])

View File

@ -1,4 +1,4 @@
import { BufferSize, createDummyMediaStream, Protocol, ServerSettingKey, VoiceChangerMode, VoiceChnagerClient } from "@dannadori/voice-changer-client-js" import { BufferSize, createDummyMediaStream, DefaultVoiceChangerOptions, DefaultVoiceChangerRequestParamas, Framework, OnnxExecutionProvider, Protocol, SampleRate, ServerSettingKey, Speaker, VoiceChangerMode, VoiceChnagerClient } from "@dannadori/voice-changer-client-js"
import { useEffect, useMemo, useRef, useState } from "react" import { useEffect, useMemo, useRef, useState } from "react"
export type UseClientProps = { export type UseClientProps = {
@ -6,40 +6,91 @@ export type UseClientProps = {
audioOutputElementId: string audioOutputElementId: string
} }
export type SettingState = {
// server setting
mmvcServerUrl: string
pyTorchModel: File | null
configFile: File | null
onnxModel: File | null
protocol: Protocol
framework: Framework
onnxExecutionProvider: OnnxExecutionProvider
// device setting
audioInput: string | MediaStream | null;
sampleRate: SampleRate;
// speaker setting
speakers: Speaker[]
editSpeakerTargetId: number
editSpeakerTargetName: string
srcId: number
dstId: number
// convert setting
bufferSize: BufferSize
inputChunkNum: number
convertChunkNum: number
gpu: number
crossFadeOffsetRate: number
crossFadeEndRate: number
// advanced setting
vfForceDisabled: boolean
voiceChangerMode: VoiceChangerMode
}
const InitialSettingState: SettingState = {
mmvcServerUrl: DefaultVoiceChangerOptions.mmvcServerUrl,
pyTorchModel: null,
configFile: null,
onnxModel: null,
protocol: DefaultVoiceChangerOptions.protocol,
framework: DefaultVoiceChangerOptions.framework,
onnxExecutionProvider: DefaultVoiceChangerOptions.onnxExecutionProvider,
audioInput: "none",
sampleRate: DefaultVoiceChangerOptions.sampleRate,
speakers: DefaultVoiceChangerOptions.speakers,
editSpeakerTargetId: 0,
editSpeakerTargetName: "",
srcId: DefaultVoiceChangerRequestParamas.srcId,
dstId: DefaultVoiceChangerRequestParamas.dstId,
bufferSize: DefaultVoiceChangerOptions.bufferSize,
inputChunkNum: DefaultVoiceChangerOptions.inputChunkNum,
convertChunkNum: DefaultVoiceChangerRequestParamas.convertChunkNum,
gpu: DefaultVoiceChangerRequestParamas.gpu,
crossFadeOffsetRate: DefaultVoiceChangerRequestParamas.crossFadeOffsetRate,
crossFadeEndRate: DefaultVoiceChangerRequestParamas.crossFadeEndRate,
vfForceDisabled: DefaultVoiceChangerOptions.forceVfDisable,
voiceChangerMode: DefaultVoiceChangerOptions.voiceChangerMode
}
export type ClientState = { export type ClientState = {
clientInitialized: boolean clientInitialized: boolean
bufferingTime: number; bufferingTime: number;
responseTime: number; responseTime: number;
volume: number; volume: number;
uploadProgress: number;
isUploading: boolean
// Setting // Setting
settingState: SettingState
setSettingState: (setting: SettingState) => void
// Client Setting
setServerUrl: (mmvcServerUrl: string) => Promise<void>
setProtocol: (protocol: Protocol) => Promise<void>
setInputChunkNum: (num: number) => Promise<void>
setVoiceChangerMode: (val: VoiceChangerMode) => Promise<void>
// Client Control // Client Control
start: (mmvcServerUrl: string, protocol: Protocol) => Promise<void>; loadModel: () => Promise<void>
start: () => Promise<void>;
stop: () => Promise<void>; stop: () => Promise<void>;
// Device Setting
changeInput: (audioInput: MediaStream | string, bufferSize: BufferSize, vfForceDisable: boolean) => Promise<void>
// Server Setting
uploadFile: (file: File, onprogress: (progress: number, end: boolean) => void) => Promise<void>
loadModel: (configFile: File, pyTorchModelFile: File | null, onnxModelFile: File | null) => Promise<void>
updateSettings: (key: ServerSettingKey, val: string | number) => Promise<any>
// Information
getInfo: () => Promise<void> getInfo: () => Promise<void>
} }
export const useClient = (props: UseClientProps): ClientState => { export const useClient = (props: UseClientProps): ClientState => {
// (1) クライアント初期化
const voiceChangerClientRef = useRef<VoiceChnagerClient | null>(null) const voiceChangerClientRef = useRef<VoiceChnagerClient | null>(null)
const [clientInitialized, setClientInitialized] = useState<boolean>(false) const [clientInitialized, setClientInitialized] = useState<boolean>(false)
const initializedResolveRef = useRef<(value: void | PromiseLike<void>) => void>() const initializedResolveRef = useRef<(value: void | PromiseLike<void>) => void>()
@ -88,73 +139,45 @@ export const useClient = (props: UseClientProps): ClientState => {
initialized() initialized()
}, [props.audioContext]) }, [props.audioContext])
// Client Setting
const setServerUrl = useMemo(() => {
return async (mmvcServerUrl: string) => { // (2) 設定
const [settingState, setSettingState] = useState<SettingState>(InitialSettingState)
const [uploadProgress, setUploadProgress] = useState<number>(0)
const [isUploading, setIsUploading] = useState<boolean>(false)
// (2-1) server setting
// (a) サーバURL設定
useEffect(() => {
(async () => {
await initializedPromise await initializedPromise
voiceChangerClientRef.current!.setServerUrl(mmvcServerUrl, true) voiceChangerClientRef.current!.setServerUrl(settingState.mmvcServerUrl, true)
voiceChangerClientRef.current!.stop() voiceChangerClientRef.current!.stop()
} })()
}, []) }, [settingState.mmvcServerUrl])
// (b) プロトコル設定
const setProtocol = useMemo(() => { useEffect(() => {
return async (protocol: Protocol) => { (async () => {
await initializedPromise await initializedPromise
voiceChangerClientRef.current!.setProtocol(protocol) voiceChangerClientRef.current!.setProtocol(settingState.protocol)
} })()
}, []) }, [settingState.protocol])
// (c) フレームワーク設定
const setInputChunkNum = useMemo(() => { useEffect(() => {
return async (num: number) => { (async () => {
await initializedPromise await initializedPromise
voiceChangerClientRef.current!.setInputChunkNum(num) const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.framework, "" + settingState.framework)
} })()
}, []) }, [settingState.framework])
// (d) OnnxExecutionProvider設定
const setVoiceChangerMode = useMemo(() => { useEffect(() => {
return async (val: VoiceChangerMode) => { (async () => {
await initializedPromise await initializedPromise
voiceChangerClientRef.current!.setVoiceChangerMode(val) const info = voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.onnxExecutionProvider, settingState.onnxExecutionProvider)
voiceChangerClientRef.current!.stop() })()
} }, [settingState.onnxExecutionProvider])
}, [])
// (e) モデルアップロード
// Client Control
const start = useMemo(() => {
return async (mmvcServerUrl: string) => {
await initializedPromise
voiceChangerClientRef.current!.setServerUrl(mmvcServerUrl, true)
voiceChangerClientRef.current!.start()
}
}, [])
const stop = useMemo(() => {
return async () => {
await initializedPromise
voiceChangerClientRef.current!.stop()
}
}, [])
// Device Setting
const changeInput = useMemo(() => {
return async (audioInput: MediaStream | string, bufferSize: BufferSize, vfForceDisable: boolean) => {
await initializedPromise
if (!audioInput || audioInput == "none") {
console.log("[useClient] setup!(1)", audioInput)
const ms = createDummyMediaStream(props.audioContext!)
await voiceChangerClientRef.current!.setup(ms, bufferSize, vfForceDisable)
} else {
console.log("[useClient] setup!(2)", audioInput)
await voiceChangerClientRef.current!.setup(audioInput, bufferSize, vfForceDisable)
}
}
}, [props.audioContext])
// Server Setting
const uploadFile = useMemo(() => { const uploadFile = useMemo(() => {
return async (file: File, onprogress: (progress: number, end: boolean) => void) => { return async (file: File, onprogress: (progress: number, end: boolean) => void) => {
await initializedPromise await initializedPromise
@ -163,23 +186,146 @@ export const useClient = (props: UseClientProps): ClientState => {
console.log("uploaded", num, res) console.log("uploaded", num, res)
} }
}, []) }, [])
const loadModel = useMemo(() => { const loadModel = useMemo(() => {
return async (configFile: File, pyTorchModelFile: File | null, onnxModelFile: File | null) => { return async () => {
if (!settingState.pyTorchModel && !settingState.onnxModel) {
alert("PyTorchモデルとONNXモデルのどちらか一つ以上指定する必要があります。")
return
}
if (!settingState.configFile) {
alert("Configファイルを指定する必要があります。")
return
}
await initializedPromise await initializedPromise
await voiceChangerClientRef.current!.loadModel(configFile, pyTorchModelFile, onnxModelFile) setUploadProgress(0)
console.log("loaded model") setIsUploading(true)
const models = [settingState.pyTorchModel, settingState.onnxModel].filter(x => { return x != null }) as File[]
for (let i = 0; i < models.length; i++) {
const progRate = 1 / models.length
const progOffset = 100 * i * progRate
await uploadFile(models[i], (progress: number, end: boolean) => {
// console.log(progress * progRate + progOffset, end, progRate,)
setUploadProgress(progress * progRate + progOffset)
})
}
await uploadFile(settingState.configFile, (progress: number, end: boolean) => {
console.log(progress, end)
})
const serverInfo = await voiceChangerClientRef.current!.loadModel(settingState.configFile, settingState.pyTorchModel, settingState.onnxModel)
console.log(serverInfo)
setUploadProgress(0)
setIsUploading(false)
}
}, [settingState.pyTorchModel, settingState.onnxModel, settingState.configFile])
// (2-2) device setting
// (a) インプット設定。audio nodes の設定の都合上、バッファサイズの変更も併せて反映させる。
useEffect(() => {
(async () => {
await initializedPromise
if (!settingState.audioInput || settingState.audioInput == "none") {
console.log("[useClient] setup!(1)", settingState.audioInput)
const ms = createDummyMediaStream(props.audioContext!)
await voiceChangerClientRef.current!.setup(ms, settingState.bufferSize, settingState.vfForceDisabled)
} else {
console.log("[useClient] setup!(2)", settingState.audioInput)
await voiceChangerClientRef.current!.setup(settingState.audioInput, settingState.bufferSize, settingState.vfForceDisabled)
}
})()
}, [settingState.audioInput, settingState.bufferSize, settingState.vfForceDisabled])
// (2-3) speaker setting
// (a) srcId設定。
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.srcId, "" + settingState.srcId)
})()
}, [settingState.srcId])
// (b) dstId設定。
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.dstId, "" + settingState.dstId)
})()
}, [settingState.dstId])
// (2-4) convert setting
// (a) input chunk num設定
useEffect(() => {
(async () => {
await initializedPromise
voiceChangerClientRef.current!.setInputChunkNum(settingState.inputChunkNum)
})()
}, [settingState.inputChunkNum])
// (b) input chunk num設定
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.convertChunkNum, "" + settingState.convertChunkNum)
})()
}, [settingState.convertChunkNum])
// (c) gpu設定
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.gpu, "" + settingState.gpu)
})()
}, [settingState.gpu])
// (d) crossfade設定1
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeOffsetRate, "" + settingState.crossFadeOffsetRate)
})()
}, [settingState.crossFadeOffsetRate])
// (e) crossfade設定2
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeEndRate, "" + settingState.crossFadeEndRate)
})()
}, [settingState.crossFadeEndRate])
// (2-5) advanced setting
//// VFDisableはinput設定で合わせて設定。
// (a) voice changer mode
useEffect(() => {
(async () => {
await initializedPromise
voiceChangerClientRef.current!.setVoiceChangerMode(settingState.voiceChangerMode)
voiceChangerClientRef.current!.stop()
})()
}, [settingState.voiceChangerMode])
// (2-6) server control
// (1) start
const start = useMemo(() => {
return async () => {
await initializedPromise
voiceChangerClientRef.current!.setServerUrl(settingState.mmvcServerUrl, true)
voiceChangerClientRef.current!.start()
}
}, [settingState.mmvcServerUrl])
// (2) stop
const stop = useMemo(() => {
return async () => {
await initializedPromise
voiceChangerClientRef.current!.stop()
} }
}, []) }, [])
const updateSettings = useMemo(() => { // (3) get info
return async (key: ServerSettingKey, val: string | number) => {
await initializedPromise
return await voiceChangerClientRef.current!.updateServerSettings(key, "" + val)
}
}, [])
// Information
const getInfo = useMemo(() => { const getInfo = useMemo(() => {
return async () => { return async () => {
await initializedPromise await initializedPromise
@ -195,21 +341,14 @@ export const useClient = (props: UseClientProps): ClientState => {
bufferingTime, bufferingTime,
responseTime, responseTime,
volume, volume,
uploadProgress,
isUploading,
setServerUrl, settingState,
setProtocol, setSettingState,
setInputChunkNum, loadModel,
setVoiceChangerMode,
start, start,
stop, stop,
changeInput,
uploadFile,
loadModel,
updateSettings,
getInfo, getInfo,
} }
} }

View File

@ -26,8 +26,8 @@ export type VoiceChangerOptions = {
speakers: Speaker[], speakers: Speaker[],
forceVfDisable: boolean, forceVfDisable: boolean,
voiceChangerMode: VoiceChangerMode, voiceChangerMode: VoiceChangerMode,
OnnxExecutionProvider: OnnxExecutionProvider, onnxExecutionProvider: OnnxExecutionProvider,
Framework: Framework framework: Framework
} }
@ -141,8 +141,8 @@ export const DefaultVoiceChangerOptions: VoiceChangerOptions = {
], ],
forceVfDisable: false, forceVfDisable: false,
voiceChangerMode: "realtime", voiceChangerMode: "realtime",
Framework: "PyTorch", framework: "PyTorch",
OnnxExecutionProvider: "CPUExecutionProvider" onnxExecutionProvider: "CPUExecutionProvider"
} }

View File

@ -42,6 +42,8 @@ class VoiceChanger():
self.unpackedData_length=0 self.unpackedData_length=0
self.net_g = None self.net_g = None
self.onnx_session = None self.onnx_session = None
self.currentCrossFadeOffsetRate=0
self.currentCrossFadeEndRate=0
# 共通で使用する情報を収集 # 共通で使用する情報を収集
self.hps = utils.get_hparams_from_file(config) self.hps = utils.get_hparams_from_file(config)
self.gpu_num = torch.cuda.device_count() self.gpu_num = torch.cuda.device_count()
@ -109,7 +111,7 @@ class VoiceChanger():
setattr(self.settings, key, int(val)) setattr(self.settings, key, int(val))
if key == "gpu" and val >= 0 and val < self.gpu_num and self.onnx_session != None: if key == "gpu" and val >= 0 and val < self.gpu_num and self.onnx_session != None:
providers = self.onnx_session.get_providers() providers = self.onnx_session.get_providers()
print("Providers::::", providers) print("Providers:", providers)
if "CUDAExecutionProvider" in providers: if "CUDAExecutionProvider" in providers:
provider_options=[{'device_id': self.settings.gpu}] provider_options=[{'device_id': self.settings.gpu}]
self.onnx_session.set_providers(providers=["CUDAExecutionProvider"], provider_options=provider_options) self.onnx_session.set_providers(providers=["CUDAExecutionProvider"], provider_options=provider_options)
@ -121,14 +123,18 @@ class VoiceChanger():
setattr(self.settings, key, str(val)) setattr(self.settings, key, str(val))
else: else:
print(f"{key} is not mutalbe variable!") print(f"{key} is not mutalbe variable!")
return self.get_info() return self.get_info()
self.currentCrossFadeOffsetRate=0
self.currentCrossFadeEndRate=0
def _generate_strength(self, unpackedData): def _generate_strength(self, unpackedData):
if self.unpackedData_length != unpackedData.shape[0]: if self.unpackedData_length != unpackedData.shape[0] or self.currentCrossFadeOffsetRate != self.settings.crossFadeOffsetRate or self.currentCrossFadeEndRate != self.settings.crossFadeEndRate :
self.unpackedData_length = unpackedData.shape[0] self.unpackedData_length = unpackedData.shape[0]
self.currentCrossFadeOffsetRate = self.settings.crossFadeOffsetRate
self.currentCrossFadeEndRate = self.settings.crossFadeEndRate
cf_offset = int(unpackedData.shape[0] * self.settings.crossFadeOffsetRate) cf_offset = int(unpackedData.shape[0] * self.settings.crossFadeOffsetRate)
cf_end = int(unpackedData.shape[0] * self.settings.crossFadeEndRate) cf_end = int(unpackedData.shape[0] * self.settings.crossFadeEndRate)
cf_range = cf_end - cf_offset cf_range = cf_end - cf_offset