voice-changer/client/demo/src/hooks/useClient.ts

433 lines
15 KiB
TypeScript
Raw Normal View History

2023-01-10 20:19:54 +03:00
import { ServerInfo, BufferSize, createDummyMediaStream, DefaultVoiceChangerOptions, DefaultVoiceChangerRequestParamas, Framework, OnnxExecutionProvider, Protocol, SampleRate, ServerSettingKey, Speaker, VoiceChangerMode, VoiceChnagerClient } from "@dannadori/voice-changer-client-js"
2023-01-07 14:07:39 +03:00
import { useEffect, useMemo, useRef, useState } from "react"
export type UseClientProps = {
audioContext: AudioContext | null
audioOutputElementId: string
}
2023-01-10 18:59:09 +03:00
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
2023-01-11 19:05:38 +03:00
crossFadeOverlapRate: number
2023-01-10 18:59:09 +03:00
// 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,
2023-01-11 19:05:38 +03:00
crossFadeOverlapRate: DefaultVoiceChangerRequestParamas.crossFadeOverlapRate,
2023-01-10 18:59:09 +03:00
vfForceDisabled: DefaultVoiceChangerOptions.forceVfDisable,
voiceChangerMode: DefaultVoiceChangerOptions.voiceChangerMode
}
2023-01-07 14:07:39 +03:00
export type ClientState = {
clientInitialized: boolean
bufferingTime: number;
responseTime: number;
volume: number;
2023-01-10 18:59:09 +03:00
uploadProgress: number;
isUploading: boolean
2023-01-10 16:49:16 +03:00
// Setting
2023-01-10 18:59:09 +03:00
settingState: SettingState
2023-01-10 20:19:54 +03:00
serverInfo: ServerInfo | undefined
2023-01-10 18:59:09 +03:00
setSettingState: (setting: SettingState) => void
2023-01-08 10:18:20 +03:00
// Client Control
2023-01-10 18:59:09 +03:00
loadModel: () => Promise<void>
start: () => Promise<void>;
2023-01-07 14:07:39 +03:00
stop: () => Promise<void>;
2023-01-08 10:18:20 +03:00
getInfo: () => Promise<void>
2023-01-07 14:07:39 +03:00
}
2023-01-11 17:12:29 +03:00
2023-01-07 14:07:39 +03:00
export const useClient = (props: UseClientProps): ClientState => {
2023-01-10 18:59:09 +03:00
// (1) クライアント初期化
2023-01-07 14:07:39 +03:00
const voiceChangerClientRef = useRef<VoiceChnagerClient | null>(null)
const [clientInitialized, setClientInitialized] = useState<boolean>(false)
2023-01-08 14:28:57 +03:00
const initializedResolveRef = useRef<(value: void | PromiseLike<void>) => void>()
const initializedPromise = useMemo(() => {
return new Promise<void>((resolve) => {
initializedResolveRef.current = resolve
})
}, [])
2023-01-10 16:49:16 +03:00
const [bufferingTime, setBufferingTime] = useState<number>(0)
const [responseTime, setResponseTime] = useState<number>(0)
const [volume, setVolume] = useState<number>(0)
2023-01-08 14:28:57 +03:00
2023-01-11 09:35:49 +03:00
2023-01-11 17:12:29 +03:00
// Colab対応
useEffect(() => {
const params = new URLSearchParams(location.search);
const colab = params.get("colab")
if (colab == "true") {
}
}, [])
2023-01-11 09:35:49 +03:00
2023-01-07 14:07:39 +03:00
useEffect(() => {
const initialized = async () => {
if (!props.audioContext) {
return
}
const voiceChangerClient = new VoiceChnagerClient(props.audioContext, true, {
notifySendBufferingTime: (val: number) => {
setBufferingTime(val)
},
notifyResponseTime: (val: number) => {
setResponseTime(val)
},
notifyException: (mes: string) => {
if (mes.length > 0) {
console.log(`error:${mes}`)
}
}
}, {
notifyVolume: (vol: number) => {
setVolume(vol)
}
})
2023-01-08 14:28:57 +03:00
2023-01-07 14:07:39 +03:00
await voiceChangerClient.isInitialized()
voiceChangerClientRef.current = voiceChangerClient
2023-01-08 10:18:20 +03:00
console.log("[useClient] client initialized")
2023-01-07 14:07:39 +03:00
setClientInitialized(true)
const audio = document.getElementById(props.audioOutputElementId) as HTMLAudioElement
audio.srcObject = voiceChangerClientRef.current.stream
audio.play()
2023-01-08 14:28:57 +03:00
initializedResolveRef.current!()
2023-01-07 14:07:39 +03:00
}
initialized()
}, [props.audioContext])
2023-01-08 10:18:20 +03:00
2023-01-10 18:59:09 +03:00
// (2) 設定
const [settingState, setSettingState] = useState<SettingState>(InitialSettingState)
2023-01-10 20:19:54 +03:00
const [displaySettingState, setDisplaySettingState] = useState<SettingState>(InitialSettingState)
const [serverInfo, setServerInfo] = useState<ServerInfo>()
2023-01-10 18:59:09 +03:00
const [uploadProgress, setUploadProgress] = useState<number>(0)
const [isUploading, setIsUploading] = useState<boolean>(false)
2023-01-08 10:18:20 +03:00
2023-01-10 18:59:09 +03:00
// (2-1) server setting
// (a) サーバURL設定
useEffect(() => {
(async () => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
voiceChangerClientRef.current!.setServerUrl(settingState.mmvcServerUrl, true)
2023-01-10 16:49:16 +03:00
voiceChangerClientRef.current!.stop()
2023-01-10 20:19:54 +03:00
getInfo()
2023-01-10 18:59:09 +03:00
})()
}, [settingState.mmvcServerUrl])
// (b) プロトコル設定
useEffect(() => {
(async () => {
await initializedPromise
voiceChangerClientRef.current!.setProtocol(settingState.protocol)
})()
}, [settingState.protocol])
// (c) フレームワーク設定
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.framework, "" + settingState.framework)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.framework])
// (d) OnnxExecutionProvider設定
useEffect(() => {
(async () => {
await initializedPromise
2023-01-10 20:19:54 +03:00
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.onnxExecutionProvider, settingState.onnxExecutionProvider)
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.onnxExecutionProvider])
2023-01-08 10:18:20 +03:00
2023-01-10 18:59:09 +03:00
// (e) モデルアップロード
const uploadFile = useMemo(() => {
return async (file: File, onprogress: (progress: number, end: boolean) => void) => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
const num = await voiceChangerClientRef.current!.uploadFile(file, onprogress)
const res = await voiceChangerClientRef.current!.concatUploadedFile(file, num)
console.log("uploaded", num, res)
2023-01-07 14:07:39 +03:00
}
}, [])
2023-01-10 18:59:09 +03:00
const loadModel = useMemo(() => {
2023-01-07 14:07:39 +03:00
return async () => {
2023-01-10 18:59:09 +03:00
if (!settingState.pyTorchModel && !settingState.onnxModel) {
alert("PyTorchモデルとONNXモデルのどちらか一つ以上指定する必要があります。")
return
}
if (!settingState.configFile) {
alert("Configファイルを指定する必要があります。")
return
}
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
setUploadProgress(0)
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)
})
}
2023-01-07 14:07:39 +03:00
2023-01-10 18:59:09 +03:00
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])
2023-01-08 10:18:20 +03:00
2023-01-10 18:59:09 +03:00
// (2-2) device setting
// (a) インプット設定。audio nodes の設定の都合上、バッファサイズの変更も併せて反映させる。
useEffect(() => {
(async () => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
if (!settingState.audioInput || settingState.audioInput == "none") {
console.log("[useClient] setup!(1)", settingState.audioInput)
2023-01-10 16:49:16 +03:00
const ms = createDummyMediaStream(props.audioContext!)
2023-01-10 18:59:09 +03:00
await voiceChangerClientRef.current!.setup(ms, settingState.bufferSize, settingState.vfForceDisabled)
2023-01-07 14:07:39 +03:00
} else {
2023-01-10 18:59:09 +03:00
console.log("[useClient] setup!(2)", settingState.audioInput)
await voiceChangerClientRef.current!.setup(settingState.audioInput, settingState.bufferSize, settingState.vfForceDisabled)
2023-01-07 14:07:39 +03:00
}
2023-01-10 18:59:09 +03:00
})()
}, [settingState.audioInput, settingState.bufferSize, settingState.vfForceDisabled])
2023-01-07 14:07:39 +03:00
2023-01-10 18:59:09 +03:00
// (2-3) speaker setting
// (a) srcId設定。
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.srcId, "" + settingState.srcId)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.srcId])
2023-01-08 10:18:20 +03:00
2023-01-10 18:59:09 +03:00
// (b) dstId設定。
useEffect(() => {
(async () => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.dstId, "" + settingState.dstId)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.dstId])
2023-01-07 14:07:39 +03:00
2023-01-10 18:59:09 +03:00
// (2-4) convert setting
// (a) input chunk num設定
useEffect(() => {
(async () => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
voiceChangerClientRef.current!.setInputChunkNum(settingState.inputChunkNum)
})()
}, [settingState.inputChunkNum])
2023-01-11 09:35:49 +03:00
// (b) convert chunk num設定
2023-01-10 18:59:09 +03:00
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.convertChunkNum, "" + settingState.convertChunkNum)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.convertChunkNum])
2023-01-07 14:07:39 +03:00
2023-01-10 18:59:09 +03:00
// (c) gpu設定
useEffect(() => {
(async () => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 18:59:09 +03:00
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.gpu, "" + settingState.gpu)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.gpu])
// (d) crossfade設定1
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeOffsetRate, "" + settingState.crossFadeOffsetRate)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.crossFadeOffsetRate])
// (e) crossfade設定2
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeEndRate, "" + settingState.crossFadeEndRate)
2023-01-10 20:19:54 +03:00
setServerInfo(info)
2023-01-10 18:59:09 +03:00
})()
}, [settingState.crossFadeEndRate])
2023-01-11 19:05:38 +03:00
// (f) crossfade設定3
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeOverlapRate, "" + settingState.crossFadeOverlapRate)
setServerInfo(info)
})()
}, [settingState.crossFadeOverlapRate])
2023-01-10 18:59:09 +03:00
// (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()
2023-01-07 14:07:39 +03:00
}
}, [])
2023-01-10 18:59:09 +03:00
// (3) get info
2023-01-08 10:18:20 +03:00
const getInfo = useMemo(() => {
return async () => {
2023-01-08 14:28:57 +03:00
await initializedPromise
2023-01-10 16:49:16 +03:00
const serverSettings = await voiceChangerClientRef.current!.getServerSettings()
const clientSettings = await voiceChangerClientRef.current!.getClientSettings()
2023-01-10 20:19:54 +03:00
setServerInfo(serverSettings)
2023-01-08 10:18:20 +03:00
console.log(serverSettings, clientSettings)
2023-01-07 18:25:21 +03:00
}
}, [])
2023-01-07 14:07:39 +03:00
2023-01-10 20:19:54 +03:00
// (x)
useEffect(() => {
if (serverInfo && serverInfo.status == "OK") {
setDisplaySettingState({
...settingState,
convertChunkNum: serverInfo.convertChunkNum,
crossFadeOffsetRate: serverInfo.crossFadeOffsetRate,
crossFadeEndRate: serverInfo.crossFadeEndRate,
gpu: serverInfo.gpu,
srcId: serverInfo.srcId,
dstId: serverInfo.dstId,
framework: serverInfo.framework,
2023-01-11 21:49:22 +03:00
onnxExecutionProvider: !!serverInfo.providers && serverInfo.providers.length > 0 ? serverInfo.providers[0] as OnnxExecutionProvider : "CPUExecutionProvider"
2023-01-10 20:19:54 +03:00
})
} else {
setDisplaySettingState({
...settingState,
})
}
}, [settingState, serverInfo])
2023-01-07 14:07:39 +03:00
2023-01-11 17:12:29 +03:00
// Colab対応
useEffect(() => {
const params = new URLSearchParams(location.search);
const colab = params.get("colab")
if (colab == "true") {
setSettingState({
...settingState,
2023-01-11 19:05:38 +03:00
protocol: "rest",
inputChunkNum: 64
2023-01-11 17:12:29 +03:00
})
}
}, [])
2023-01-07 14:07:39 +03:00
return {
clientInitialized,
bufferingTime,
responseTime,
volume,
2023-01-10 18:59:09 +03:00
uploadProgress,
isUploading,
2023-01-07 14:07:39 +03:00
2023-01-10 20:19:54 +03:00
settingState: displaySettingState,
serverInfo,
2023-01-10 18:59:09 +03:00
setSettingState,
loadModel,
2023-01-07 14:07:39 +03:00
start,
stop,
2023-01-08 10:18:20 +03:00
getInfo,
2023-01-07 14:07:39 +03:00
}
}