refactoring hooks

This commit is contained in:
wataru 2023-01-12 16:38:45 +09:00
parent 43fd0316ff
commit ea3bcdcb18
17 changed files with 1209 additions and 594 deletions

View File

@ -1 +1,10 @@
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8" />
<title>Voice Changer Client Demo</title>
<script defer src="index.js"></script></head>
<body style="width: 100%; height: 100%; margin: 0px">
<div id="app" style="width: 100%; height: 100%"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,31 +0,0 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@ -1,7 +1,7 @@
import * as React from "react";
import { useEffect, useMemo, useState } from "react";
import { AUDIO_ELEMENT_FOR_PLAY_RESULT } from "./const";
import { useServerSetting } from "./101_server_setting";
import { useServerSettingArea } from "./101_server_setting";
import { useDeviceSetting } from "./102_device_setting";
import { useConvertSetting } from "./104_convert_setting";
import { useAdvancedSetting } from "./105_advanced_setting";
@ -17,7 +17,7 @@ export const useMicrophoneOptions = () => {
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
})
const serverSetting = useServerSetting({ clientState })
const serverSetting = useServerSettingArea({ clientState })
const deviceSetting = useDeviceSetting(audioContext, { clientState })
const speakerSetting = useSpeakerSetting({ clientState })
const convertSetting = useConvertSetting({ clientState })

View File

@ -11,27 +11,24 @@ export type ServerSettingState = {
serverSetting: JSX.Element;
}
export const useServerSetting = (props: UseServerSettingProps): ServerSettingState => {
export const useServerSettingArea = (props: UseServerSettingProps): ServerSettingState => {
const mmvcServerUrlRow = useMemo(() => {
const onSetServerClicked = async () => {
const input = document.getElementById("mmvc-server-url") as HTMLInputElement
props.clientState.setSettingState({
...props.clientState.settingState,
mmvcServerUrl: input.value
})
props.clientState.clientSetting.setServerUrl(input.value)
}
return (
<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-input-container">
<input type="text" defaultValue={props.clientState.settingState.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
<input type="text" defaultValue={props.clientState.clientSetting.setting.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
</div>
<div className="body-button-container">
<div className="body-button" onClick={onSetServerClicked}>set</div>
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.mmvcServerUrl, props.clientState.clientSetting.setServerUrl])
const uploadeModelRow = useMemo(() => {
const onPyTorchFileLoadClicked = async () => {
@ -40,14 +37,14 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
alert("モデルファイルの拡張子はpthである必要があります。")
return
}
props.clientState.setSettingState({
...props.clientState.settingState,
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
pyTorchModel: file
})
}
const onPyTorchFileClearClicked = () => {
props.clientState.setSettingState({
...props.clientState.settingState,
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
pyTorchModel: null
})
}
@ -57,14 +54,14 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
alert("モデルファイルの拡張子はjsonである必要があります。")
return
}
props.clientState.setSettingState({
...props.clientState.settingState,
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
configFile: file
})
}
const onConfigFileClearClicked = () => {
props.clientState.setSettingState({
...props.clientState.settingState,
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
configFile: null
})
}
@ -74,19 +71,19 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
alert("モデルファイルの拡張子はonnxである必要があります。")
return
}
props.clientState.setSettingState({
...props.clientState.settingState,
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
onnxModel: file
})
}
const onOnnxFileClearClicked = () => {
props.clientState.setSettingState({
...props.clientState.settingState,
props.clientState.serverSetting.setFileUploadSetting({
...props.clientState.serverSetting.fileUploadSetting,
onnxModel: null
})
}
const onModelUploadClicked = async () => {
props.clientState.loadModel()
props.clientState.serverSetting.loadModel()
}
return (
@ -100,30 +97,30 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
<div></div>
</div>
</div>
<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-text">
<div>{props.clientState.settingState.pyTorchModel?.name}</div>
</div>
<div className="body-button-container">
<div className="body-button" onClick={onPyTorchFileLoadClicked}>select</div>
<div className="body-button left-margin-1" onClick={onPyTorchFileClearClicked}>clear</div>
</div>
</div>
<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-text">
<div>{props.clientState.settingState.configFile?.name}</div>
<div>{props.clientState.serverSetting.fileUploadSetting.configFile?.name}</div>
</div>
<div className="body-button-container">
<div className="body-button" onClick={onConfigFileLoadClicked}>select</div>
<div className="body-button left-margin-1" onClick={onConfigFileClearClicked}>clear</div>
</div>
</div>
<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-text">
<div>{props.clientState.serverSetting.fileUploadSetting.pyTorchModel?.name}</div>
</div>
<div className="body-button-container">
<div className="body-button" onClick={onPyTorchFileLoadClicked}>select</div>
<div className="body-button left-margin-1" onClick={onPyTorchFileClearClicked}>clear</div>
</div>
</div>
<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-text">
<div>{props.clientState.settingState.onnxModel?.name}</div>
<div>{props.clientState.serverSetting.fileUploadSetting.onnxModel?.name}</div>
</div>
<div className="body-button-container">
<div className="body-button" onClick={onOnnxFileLoadClicked}>select</div>
@ -133,7 +130,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
<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-text">
{props.clientState.isUploading ? `uploading.... ${props.clientState.uploadProgress}%` : ""}
{props.clientState.serverSetting.isUploading ? `uploading.... ${props.clientState.serverSetting.uploadProgress}%` : ""}
</div>
<div className="body-button-container">
<div className="body-button" onClick={onModelUploadClicked}>upload</div>
@ -142,23 +139,20 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</>
)
}, [
props.clientState.settingState,
props.clientState.loadModel,
props.clientState.isUploading,
props.clientState.uploadProgress])
props.clientState.serverSetting.fileUploadSetting,
props.clientState.serverSetting.loadModel,
props.clientState.serverSetting.isUploading,
props.clientState.serverSetting.uploadProgress])
const protocolRow = useMemo(() => {
const onProtocolChanged = async (val: Protocol) => {
props.clientState.setSettingState({
...props.clientState.settingState,
protocol: val
})
props.clientState.clientSetting.setProtocol(val)
}
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Protocol</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.settingState.protocol} onChange={(e) => {
<select className="body-select" value={props.clientState.clientSetting.setting.protocol} onChange={(e) => {
onProtocolChanged(e.target.value as
Protocol)
}}>
@ -171,20 +165,17 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.protocol, props.clientState.clientSetting.setProtocol])
const frameworkRow = useMemo(() => {
const onFrameworkChanged = async (val: Framework) => {
props.clientState.setSettingState({
...props.clientState.settingState,
framework: val
})
props.clientState.serverSetting.setFramework(val)
}
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Framework</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.settingState.framework} onChange={(e) => {
<select className="body-select" value={props.clientState.serverSetting.setting.framework} onChange={(e) => {
onFrameworkChanged(e.target.value as
Framework)
}}>
@ -197,23 +188,20 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.framework, props.clientState.serverSetting.setFramework])
const onnxExecutionProviderRow = useMemo(() => {
if (props.clientState.settingState.framework != "ONNX") {
if (props.clientState.serverSetting.setting.framework != "ONNX") {
return
}
const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => {
props.clientState.setSettingState({
...props.clientState.settingState,
onnxExecutionProvider: val
})
props.clientState.serverSetting.setOnnxExecutionProvider(val)
}
return (
<div className="body-row split-3-7 left-padding-1">
<div className="body-item-title left-padding-2">OnnxExecutionProvider</div>
<div className="body-select-container">
<select className="body-select" value={props.clientState.settingState.onnxExecutionProvider} onChange={(e) => {
<select className="body-select" value={props.clientState.serverSetting.setting.onnxExecutionProvider} onChange={(e) => {
onOnnxExecutionProviderChanged(e.target.value as
OnnxExecutionProvider)
}}>
@ -226,7 +214,7 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.onnxExecutionProvider, props.clientState.serverSetting.setOnnxExecutionProvider])
const serverSetting = useMemo(() => {
return (

View File

@ -68,7 +68,9 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">AudioInput</div>
<div className="body-select-container">
<select className="body-select" value={audioInputForGUI} onChange={(e) => { setAudioInputForGUI(e.target.value) }}>
<select className="body-select" value={audioInputForGUI} onChange={(e) => {
setAudioInputForGUI(e.target.value)
}}>
{
inputAudioDeviceInfo.map(x => {
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
@ -85,21 +87,16 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
if (!audioContext) {
return
}
if (audioInputForGUI == "none") {
const ms = createDummyMediaStream(audioContext)
props.clientState.setSettingState({
...props.clientState.settingState,
audioInput: ms
})
props.clientState.clientSetting.setAudioInput(ms)
} else if (audioInputForGUI == "file") {
// file selector (audioMediaInputRow)
} else {
props.clientState.setSettingState({
...props.clientState.settingState,
audioInput: audioInputForGUI
})
props.clientState.clientSetting.setAudioInput(audioInputForGUI)
}
}, [audioContext, audioInputForGUI])
}, [audioContext, audioInputForGUI, props.clientState.clientSetting.setAudioInput])
const audioMediaInputRow = useMemo(() => {
if (audioInputForGUI != "file") {
@ -116,10 +113,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
const src = audioContext!.createMediaElementSource(audio);
const dst = audioContext!.createMediaStreamDestination()
src.connect(dst)
props.clientState.setSettingState({
...props.clientState.settingState,
audioInput: dst.stream
})
props.clientState.clientSetting.setAudioInput(dst.stream)
// original stream to play.
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement
audio_org.src = url
@ -148,7 +142,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
</div>
</div>
)
}, [audioInputForGUI])
}, [audioInputForGUI, props.clientState.clientSetting.setAudioInput])
@ -186,12 +180,8 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
<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-select-container">
<select className="body-select" value={props.clientState.settingState.sampleRate} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
sampleRate: Number(e.target.value) as SampleRate
})
<select className="body-select" value={props.clientState.clientSetting.setting.sampleRate} onChange={(e) => {
props.clientState.clientSetting.setSampleRate(Number(e.target.value) as SampleRate)
}}>
{
Object.values(SampleRate).map(x => {
@ -202,9 +192,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.sampleRate, props.clientState.clientSetting.setSampleRate])
const deviceSetting = useMemo(() => {
return (

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react"
import React, { useMemo, useState } from "react"
import { ClientState } from "./hooks/useClient"
export type UseSpeakerSettingProps = {
@ -6,20 +6,19 @@ export type UseSpeakerSettingProps = {
}
export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
const [editSpeakerTargetId, setEditSpeakerTargetId] = useState<number>(0)
const [editSpeakerTargetName, setEditSpeakerTargetName] = useState<string>("")
const srcIdRow = useMemo(() => {
return (
<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-select-container">
<select className="body-select" value={props.clientState.settingState.srcId} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
srcId: Number(e.target.value)
})
<select className="body-select" value={props.clientState.serverSetting.setting.srcId} onChange={(e) => {
props.clientState.serverSetting.setSrcId(Number(e.target.value))
}}>
{
props.clientState.settingState.speakers.map(x => {
props.clientState.clientSetting.setting.speakers.map(x => {
return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
})
}
@ -27,21 +26,18 @@ export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.speakers, props.clientState.serverSetting.setting.srcId, props.clientState.serverSetting.setSrcId])
const dstIdRow = useMemo(() => {
return (
<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-select-container">
<select className="body-select" value={props.clientState.settingState.dstId} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
dstId: Number(e.target.value)
})
<select className="body-select" value={props.clientState.serverSetting.setting.dstId} onChange={(e) => {
props.clientState.serverSetting.setDstId(Number(e.target.value))
}}>
{
props.clientState.settingState.speakers.map(x => {
props.clientState.clientSetting.setting.speakers.map(x => {
return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
})
}
@ -49,38 +45,29 @@ export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.speakers, props.clientState.serverSetting.setting.dstId, props.clientState.serverSetting.setDstId])
const editSpeakerIdMappingRow = useMemo(() => {
const onSetSpeakerMappingClicked = async () => {
const targetId = props.clientState.settingState.editSpeakerTargetId
const targetName = props.clientState.settingState.editSpeakerTargetName
const targetSpeaker = props.clientState.settingState.speakers.find(x => { return x.id == targetId })
const targetId = editSpeakerTargetId
const targetName = editSpeakerTargetName
const targetSpeaker = props.clientState.clientSetting.setting.speakers.find(x => { return x.id == targetId })
if (targetSpeaker) {
if (targetName.length == 0) { // Delete
const newSpeakers = props.clientState.settingState.speakers.filter(x => { return x.id != targetId })
props.clientState.setSettingState({
...props.clientState.settingState,
speakers: newSpeakers
})
const newSpeakers = props.clientState.clientSetting.setting.speakers.filter(x => { return x.id != targetId })
props.clientState.clientSetting.setSpeakers(newSpeakers)
} else { // Update
targetSpeaker.name = targetName
props.clientState.setSettingState({
...props.clientState.settingState,
speakers: props.clientState.settingState.speakers
})
props.clientState.clientSetting.setSpeakers([...props.clientState.clientSetting.setting.speakers])
}
} else {
if (targetName.length == 0) { // Noop
} else {// add
props.clientState.settingState.speakers.push({
props.clientState.clientSetting.setting.speakers.push({
id: targetId,
name: targetName
})
props.clientState.setSettingState({
...props.clientState.settingState,
speakers: props.clientState.settingState.speakers
})
props.clientState.clientSetting.setSpeakers([...props.clientState.clientSetting.setting.speakers])
}
}
}
@ -88,21 +75,15 @@ export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
<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-input-container">
<input type="number" min={1} max={256} step={1} value={props.clientState.settingState.editSpeakerTargetId} onChange={(e) => {
<input type="number" min={1} max={256} step={1} value={editSpeakerTargetId} onChange={(e) => {
const id = Number(e.target.value)
props.clientState.setSettingState({
...props.clientState.settingState,
editSpeakerTargetId: id,
editSpeakerTargetName: props.clientState.settingState.speakers.find(x => { return x.id == id })?.name || ""
})
setEditSpeakerTargetId(id)
setEditSpeakerTargetName(props.clientState.clientSetting.setting.speakers.find(x => { return x.id == id })?.name || "")
}} />
</div>
<div className="body-input-container">
<input type="text" value={props.clientState.settingState.editSpeakerTargetName} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
editSpeakerTargetName: e.target.value
})
<input type="text" value={editSpeakerTargetName} onChange={(e) => {
setEditSpeakerTargetName(e.target.value)
}} />
</div>
<div className="body-button-container">
@ -110,7 +91,7 @@ export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.speakers, editSpeakerTargetId, editSpeakerTargetName])
const speakerSetting = useMemo(() => {

View File

@ -1,4 +1,4 @@
import { DefaultVoiceChangerRequestParamas, DefaultVoiceChangerOptions, BufferSize } from "@dannadori/voice-changer-client-js"
import { BufferSize } from "@dannadori/voice-changer-client-js"
import React, { useMemo, useState } from "react"
import { ClientState } from "./hooks/useClient"
@ -18,11 +18,8 @@ export const useConvertSetting = (props: UseConvertSettingProps): ConvertSetting
<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-select-container">
<select className="body-select" value={props.clientState.settingState.bufferSize} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
bufferSize: Number(e.target.value) as BufferSize
})
<select className="body-select" value={props.clientState.clientSetting.setting.bufferSize} onChange={(e) => {
props.clientState.clientSetting.setBufferSize(Number(e.target.value) as BufferSize)
}}>
{
Object.values(BufferSize).map(x => {
@ -33,23 +30,20 @@ export const useConvertSetting = (props: UseConvertSettingProps): ConvertSetting
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.bufferSize, props.clientState.clientSetting.setBufferSize])
const inputChunkNumRow = useMemo(() => {
return (
<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-input-container">
<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)
})
<input type="number" min={1} max={256} step={1} value={props.clientState.clientSetting.setting.inputChunkNum} onChange={(e) => {
props.clientState.clientSetting.setInputChunkNum(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.inputChunkNum, props.clientState.clientSetting.setInputChunkNum])
const convertChunkNumRow = useMemo(() => {
return (
@ -57,80 +51,65 @@ export const useConvertSetting = (props: UseConvertSettingProps): ConvertSetting
<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-input-container">
<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)
})
<input type="number" min={1} max={256} step={1} value={props.clientState.serverSetting.setting.convertChunkNum} onChange={(e) => {
props.clientState.serverSetting.setConvertChunkNum(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.convertChunkNum, props.clientState.serverSetting.setConvertChunkNum])
const gpuRow = useMemo(() => {
return (
<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={props.clientState.settingState.gpu} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
gpu: Number(e.target.value)
})
<input type="number" min={-2} max={5} step={1} value={props.clientState.serverSetting.setting.gpu} onChange={(e) => {
props.clientState.serverSetting.setGpu(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.gpu, props.clientState.serverSetting.setGpu])
const crossFadeOverlapRateRow = useMemo(() => {
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-item-title left-padding-1">Cross Fade Overlap Rate</div>
<div className="body-input-container">
<input type="number" min={0.1} max={1} step={0.1} value={props.clientState.settingState.crossFadeOverlapRate} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
crossFadeOverlapRate: Number(e.target.value)
})
<input type="number" min={0.1} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeOverlapRate} onChange={(e) => {
props.clientState.serverSetting.setCrossFadeOverlapRate(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.crossFadeOverlapRate, props.clientState.serverSetting.setCrossFadeOverlapRate])
const crossFadeOffsetRateRow = useMemo(() => {
return (
<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-input-container">
<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)
})
<input type="number" min={0} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeOffsetRate} onChange={(e) => {
props.clientState.serverSetting.setCrossFadeOffsetRate(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.crossFadeOffsetRate, props.clientState.serverSetting.setCrossFadeOffsetRate])
const crossFadeEndRateRow = useMemo(() => {
return (
<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-input-container">
<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)
})
<input type="number" min={0} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeEndRate} onChange={(e) => {
props.clientState.serverSetting.setCrossFadeEndRate(Number(e.target.value))
}} />
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.serverSetting.setting.crossFadeEndRate, props.clientState.serverSetting.setCrossFadeEndRate])
const convertSetting = useMemo(() => {
return (

View File

@ -18,29 +18,23 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1 ">VF Disabled</div>
<div>
<input type="checkbox" checked={props.clientState.settingState.vfForceDisabled} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
vfForceDisabled: e.target.checked
})
<input type="checkbox" checked={props.clientState.clientSetting.setting.forceVfDisable} onChange={(e) => {
props.clientState.clientSetting.setVfForceDisabled(e.target.checked)
}} />
</div>
<div className="body-button-container">
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.forceVfDisable, props.clientState.clientSetting.setVfForceDisabled])
const voiceChangeModeRow = useMemo(() => {
return (
<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-select-container">
<select className="body-select" value={props.clientState.settingState.voiceChangerMode} onChange={(e) => {
props.clientState.setSettingState({
...props.clientState.settingState,
voiceChangerMode: e.target.value as VoiceChangerMode
})
<select className="body-select" value={props.clientState.clientSetting.setting.voiceChangerMode} onChange={(e) => {
props.clientState.clientSetting.setVoiceChangerMode(e.target.value as VoiceChangerMode)
}}>
{
Object.values(VoiceChangerMode).map(x => {
@ -51,7 +45,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
</div>
</div>
)
}, [props.clientState.settingState])
}, [props.clientState.clientSetting.setting.voiceChangerMode, props.clientState.clientSetting.setVoiceChangerMode])

View File

@ -11,11 +11,11 @@ export const useServerControl = (props: UseServerControlProps) => {
const startButtonRow = useMemo(() => {
const onStartClicked = async () => {
setIsStarted(true)
await props.clientState.start()
await props.clientState.clientSetting.start()
}
const onStopClicked = async () => {
setIsStarted(false)
await props.clientState.stop()
await props.clientState.clientSetting.stop()
}
const startClassName = isStarted ? "body-button-active" : "body-button-stanby"
const stopClassName = isStarted ? "body-button-stanby" : "body-button-active"
@ -32,7 +32,7 @@ export const useServerControl = (props: UseServerControlProps) => {
</div>
)
}, [isStarted, props.clientState.start, props.clientState.stop])
}, [isStarted, props.clientState.clientSetting.start, props.clientState.clientSetting.stop])
const performanceRow = useMemo(() => {
return (
@ -60,9 +60,9 @@ export const useServerControl = (props: UseServerControlProps) => {
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-1">Model Info:</div>
<div className="body-item-text">
<span className="body-item-text-item">{props.clientState.serverInfo?.configFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverInfo?.pyTorchModelFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverInfo?.onnxModelFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.configFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.pyTorchModelFile || ""}</span>
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.onnxModelFile || ""}</span>
</div>
@ -72,7 +72,7 @@ export const useServerControl = (props: UseServerControlProps) => {
</div>
</>
)
}, [props.clientState.getInfo, props.clientState.serverInfo])
}, [props.clientState.getInfo, props.clientState.serverSetting.serverInfo])

View File

@ -1,6 +1,5 @@
export const CHROME_EXTENSION = false
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result"
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original"
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"

View File

@ -1,5 +1,7 @@
import { ServerInfo, BufferSize, createDummyMediaStream, DefaultVoiceChangerOptions, DefaultVoiceChangerRequestParamas, Framework, OnnxExecutionProvider, Protocol, SampleRate, ServerSettingKey, Speaker, VoiceChangerMode, VoiceChangerClient } from "@dannadori/voice-changer-client-js"
import { VoiceChangerClient } from "@dannadori/voice-changer-client-js"
import { useEffect, useMemo, useRef, useState } from "react"
import { ClientSettingState, useClientSetting } from "./useClientSetting"
import { ServerSettingState, useServerSetting } from "./useServerSetting"
import { useWorkletSetting, WorkletSettingState } from "./useWorkletSetting"
export type UseClientProps = {
@ -7,123 +9,46 @@ export type UseClientProps = {
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
crossFadeOverlapRate: 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,
crossFadeOverlapRate: DefaultVoiceChangerRequestParamas.crossFadeOverlapRate,
vfForceDisabled: DefaultVoiceChangerOptions.forceVfDisable,
voiceChangerMode: DefaultVoiceChangerOptions.voiceChangerMode
}
export type ClientState = {
clientInitialized: boolean
workletSetting: WorkletSettingState
clientSetting: ClientSettingState
serverSetting: ServerSettingState
bufferingTime: number;
responseTime: number;
volume: number;
uploadProgress: number;
isUploading: boolean
// Setting
settingState: SettingState
serverInfo: ServerInfo | undefined
setSettingState: (setting: SettingState) => void
// Client Control
loadModel: () => Promise<void>
start: () => Promise<void>;
stop: () => Promise<void>;
getInfo: () => Promise<void>
workletSetting: WorkletSettingState
}
export const useClient = (props: UseClientProps): ClientState => {
// (1) クライアント初期化
// (1-1) クライアント
const voiceChangerClientRef = useRef<VoiceChangerClient | null>(null)
const [voiceChangerClient, setVoiceChangerClient] = useState<VoiceChangerClient | null>(voiceChangerClientRef.current)
const workletSetting = useWorkletSetting({ voiceChangerClient })
const [clientInitialized, setClientInitialized] = useState<boolean>(false)
//// クライアント初期化待ち用フラグ
const initializedResolveRef = useRef<(value: void | PromiseLike<void>) => void>()
const initializedPromise = useMemo(() => {
return new Promise<void>((resolve) => {
initializedResolveRef.current = resolve
})
}, [])
// (1-2) 各種設定
const clientSetting = useClientSetting({ voiceChangerClient, audioContext: props.audioContext })
const workletSetting = useWorkletSetting({ voiceChangerClient })
const serverSetting = useServerSetting({ voiceChangerClient })
// (1-3) ステータス
const [bufferingTime, setBufferingTime] = useState<number>(0)
const [responseTime, setResponseTime] = useState<number>(0)
const [volume, setVolume] = useState<number>(0)
// Colab対応
useEffect(() => {
const params = new URLSearchParams(location.search);
const colab = params.get("colab")
if (colab == "true") {
}
}, [])
// (2-1) 初期化処理
useEffect(() => {
const initialized = async () => {
if (!props.audioContext) {
@ -151,7 +76,6 @@ export const useClient = (props: UseClientProps): ClientState => {
voiceChangerClientRef.current = voiceChangerClient
setVoiceChangerClient(voiceChangerClientRef.current)
console.log("[useClient] client initialized")
setClientInitialized(true)
const audio = document.getElementById(props.audioOutputElementId) as HTMLAudioElement
audio.srcObject = voiceChangerClientRef.current.stream
@ -162,282 +86,26 @@ export const useClient = (props: UseClientProps): ClientState => {
}, [props.audioContext])
// (2) 設定
const [settingState, setSettingState] = useState<SettingState>(InitialSettingState)
const [displaySettingState, setDisplaySettingState] = useState<SettingState>(InitialSettingState)
const [serverInfo, setServerInfo] = useState<ServerInfo>()
const [uploadProgress, setUploadProgress] = useState<number>(0)
const [isUploading, setIsUploading] = useState<boolean>(false)
// (2-1) server setting
// (a) サーバURL設定
useEffect(() => {
(async () => {
await initializedPromise
voiceChangerClientRef.current!.setServerUrl(settingState.mmvcServerUrl, true)
voiceChangerClientRef.current!.stop()
getInfo()
})()
}, [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)
setServerInfo(info)
})()
}, [settingState.framework])
// (d) OnnxExecutionProvider設定
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.onnxExecutionProvider, settingState.onnxExecutionProvider)
setServerInfo(info)
})()
}, [settingState.onnxExecutionProvider])
// (e) モデルアップロード
const uploadFile = useMemo(() => {
return async (file: File, onprogress: (progress: number, end: boolean) => void) => {
await initializedPromise
const num = await voiceChangerClientRef.current!.uploadFile(file, onprogress)
const res = await voiceChangerClientRef.current!.concatUploadedFile(file, num)
console.log("uploaded", num, res)
}
}, [])
const loadModel = useMemo(() => {
return async () => {
if (!settingState.pyTorchModel && !settingState.onnxModel) {
alert("PyTorchモデルとONNXモデルのどちらか一つ以上指定する必要があります。")
return
}
if (!settingState.configFile) {
alert("Configファイルを指定する必要があります。")
return
}
await initializedPromise
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)
})
}
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)
setServerInfo(info)
})()
}, [settingState.srcId])
// (b) dstId設定。
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.dstId, "" + settingState.dstId)
setServerInfo(info)
})()
}, [settingState.dstId])
// (2-4) convert setting
// (a) input chunk num設定
useEffect(() => {
(async () => {
await initializedPromise
voiceChangerClientRef.current!.setInputChunkNum(settingState.inputChunkNum)
})()
}, [settingState.inputChunkNum])
// (b) convert chunk num設定
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.convertChunkNum, "" + settingState.convertChunkNum)
setServerInfo(info)
})()
}, [settingState.convertChunkNum])
// (c) gpu設定
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.gpu, "" + settingState.gpu)
setServerInfo(info)
})()
}, [settingState.gpu])
// (d) crossfade設定1
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeOffsetRate, "" + settingState.crossFadeOffsetRate)
setServerInfo(info)
})()
}, [settingState.crossFadeOffsetRate])
// (e) crossfade設定2
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeEndRate, "" + settingState.crossFadeEndRate)
setServerInfo(info)
})()
}, [settingState.crossFadeEndRate])
// (f) crossfade設定3
useEffect(() => {
(async () => {
await initializedPromise
const info = await voiceChangerClientRef.current!.updateServerSettings(ServerSettingKey.crossFadeOverlapRate, "" + settingState.crossFadeOverlapRate)
setServerInfo(info)
})()
}, [settingState.crossFadeOverlapRate])
// (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()
}
}, [])
// (3) get info
// (2-2) 情報リロード
const getInfo = useMemo(() => {
return async () => {
await initializedPromise
const serverSettings = await voiceChangerClientRef.current!.getServerSettings()
const clientSettings = await voiceChangerClientRef.current!.getClientSettings()
setServerInfo(serverSettings)
console.log(serverSettings, clientSettings)
await clientSetting.reloadClientSetting()
await serverSetting.reloadServerInfo()
}
}, [])
// (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,
onnxExecutionProvider: !!serverInfo.providers && serverInfo.providers.length > 0 ? serverInfo.providers[0] as OnnxExecutionProvider : "CPUExecutionProvider"
})
} else {
setDisplaySettingState({
...settingState,
})
}
}, [settingState, serverInfo])
// Colab対応
useEffect(() => {
const params = new URLSearchParams(location.search);
const colab = params.get("colab")
if (colab == "true") {
setSettingState({
...settingState,
protocol: "rest",
inputChunkNum: 64
})
}
}, [])
}, [clientSetting, serverSetting])
return {
clientInitialized,
bufferingTime,
responseTime,
volume,
uploadProgress,
isUploading,
settingState: displaySettingState,
serverInfo,
setSettingState,
loadModel,
start,
stop,
getInfo,
workletSetting
clientSetting,
workletSetting,
serverSetting,
}
}

View File

@ -0,0 +1,184 @@
import { BufferSize, createDummyMediaStream, DefaultVoiceChangerClientSetting, Protocol, SampleRate, Speaker, VoiceChangerClient, VoiceChangerClientSetting, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
import { useState, useMemo, useRef, useEffect } from "react"
export type UseClientSettingProps = {
voiceChangerClient: VoiceChangerClient | null
audioContext: AudioContext | null
}
export type ClientSettingState = {
setting: VoiceChangerClientSetting;
setServerUrl: (url: string) => void;
setProtocol: (proto: Protocol) => void;
setAudioInput: (audioInput: string | MediaStream | null) => Promise<void>
setBufferSize: (bufferSize: BufferSize) => Promise<void>
setVfForceDisabled: (vfForceDisabled: boolean) => Promise<void>
setInputChunkNum: (num: number) => void;
setVoiceChangerMode: (mode: VoiceChangerMode) => void
setSampleRate: (num: SampleRate) => void
setSpeakers: (speakers: Speaker[]) => void
start: () => Promise<void>
stop: () => Promise<void>
reloadClientSetting: () => Promise<void>
}
export const useClientSetting = (props: UseClientSettingProps): ClientSettingState => {
const settingRef = useRef<VoiceChangerClientSetting>(DefaultVoiceChangerClientSetting)
const [setting, _setSetting] = useState<VoiceChangerClientSetting>(settingRef.current)
//////////////
// 設定
/////////////
const setServerUrl = useMemo(() => {
return (url: string) => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.setServerUrl(url)
settingRef.current.mmvcServerUrl = url
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setProtocol = useMemo(() => {
return (proto: Protocol) => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.setProtocol(proto)
settingRef.current.protocol = proto
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const _setInput = async () => {
if (!props.voiceChangerClient) return
if (!settingRef.current.audioInput || settingRef.current.audioInput == "none") {
console.log("[useClient] setup!(1)", settingRef.current.audioInput)
const ms = createDummyMediaStream(props.audioContext!)
await props.voiceChangerClient.setup(ms, settingRef.current.bufferSize, settingRef.current.forceVfDisable)
} else {
console.log("[useClient] setup!(2)", settingRef.current.audioInput)
await props.voiceChangerClient.setup(settingRef.current.audioInput, settingRef.current.bufferSize, settingRef.current.forceVfDisable)
}
}
const setAudioInput = useMemo(() => {
return async (audioInput: string | MediaStream | null) => {
if (!props.voiceChangerClient) return
settingRef.current.audioInput = audioInput
await _setInput()
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setBufferSize = useMemo(() => {
return async (bufferSize: BufferSize) => {
if (!props.voiceChangerClient) return
settingRef.current.bufferSize = bufferSize
await _setInput()
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setVfForceDisabled = useMemo(() => {
return async (vfForceDisabled: boolean) => {
if (!props.voiceChangerClient) return
settingRef.current.forceVfDisable = vfForceDisabled
await _setInput()
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setInputChunkNum = useMemo(() => {
return (num: number) => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.setInputChunkNum(num)
settingRef.current.inputChunkNum = num
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setVoiceChangerMode = useMemo(() => {
return (mode: VoiceChangerMode) => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.setVoiceChangerMode(mode)
settingRef.current.voiceChangerMode = mode
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setSampleRate = useMemo(() => {
return (num: SampleRate) => {
if (!props.voiceChangerClient) return
//props.voiceChangerClient.setSampleRate(num) // Not Implemented
settingRef.current.sampleRate = num
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
const setSpeakers = useMemo(() => {
return (speakers: Speaker[]) => {
if (!props.voiceChangerClient) return
settingRef.current.speakers = speakers
_setSetting({ ...settingRef.current })
}
}, [props.voiceChangerClient])
//////////////
// 操作
/////////////
// (1) start
const start = useMemo(() => {
return async () => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.setServerUrl(setting.mmvcServerUrl, true)
props.voiceChangerClient.start()
}
}, [setting.mmvcServerUrl, props.voiceChangerClient])
// (2) stop
const stop = useMemo(() => {
return async () => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.stop()
}
}, [props.voiceChangerClient])
const reloadClientSetting = useMemo(() => {
return async () => {
if (!props.voiceChangerClient) return
await props.voiceChangerClient.getClientSettings()
}
}, [props.voiceChangerClient])
//////////////
// Colab対応
/////////////
useEffect(() => {
const params = new URLSearchParams(location.search);
const colab = params.get("colab")
if (colab == "true") {
settingRef.current.protocol = "rest"
settingRef.current.inputChunkNum = 64
_setSetting({ ...settingRef.current })
}
}, [])
return {
setting,
setServerUrl,
setProtocol,
setAudioInput,
setBufferSize,
setVfForceDisabled,
setInputChunkNum,
setVoiceChangerMode,
setSampleRate,
setSpeakers,
start,
stop,
reloadClientSetting
}
}

View File

@ -0,0 +1,217 @@
import { DefaultVoiceChangerServerSetting, Framework, OnnxExecutionProvider, ServerInfo, ServerSettingKey, VoiceChangerClient, VoiceChangerServerSetting, } from "@dannadori/voice-changer-client-js"
import { useState, useMemo, useRef, } from "react"
export type FileUploadSetting = {
pyTorchModel: File | null
configFile: File | null
onnxModel: File | null
}
const InitialFileUploadSetting: FileUploadSetting = {
pyTorchModel: null,
configFile: null,
onnxModel: null,
}
export type UseServerSettingProps = {
voiceChangerClient: VoiceChangerClient | null
}
export type ServerSettingState = {
setting: VoiceChangerServerSetting;
serverInfo: ServerInfo | undefined;
fileUploadSetting: FileUploadSetting
setFramework: (framework: Framework) => Promise<boolean>;
setOnnxExecutionProvider: (provider: OnnxExecutionProvider) => Promise<boolean>;
setSrcId: (num: number) => Promise<boolean>;
setDstId: (num: number) => Promise<boolean>;
setConvertChunkNum: (num: number) => Promise<boolean>;
setGpu: (num: number) => Promise<boolean>;
setCrossFadeOffsetRate: (num: number) => Promise<boolean>;
setCrossFadeEndRate: (num: number) => Promise<boolean>;
setCrossFadeOverlapRate: (num: number) => Promise<boolean>;
reloadServerInfo: () => Promise<void>;
setFileUploadSetting: (val: FileUploadSetting) => void
loadModel: () => Promise<void>
uploadProgress: number
isUploading: boolean
}
export const useServerSetting = (props: UseServerSettingProps): ServerSettingState => {
const settingRef = useRef<VoiceChangerServerSetting>(DefaultVoiceChangerServerSetting)
const [setting, _setSetting] = useState<VoiceChangerServerSetting>(settingRef.current)
const [serverInfo, _setServerInfo] = useState<ServerInfo>()
const [fileUploadSetting, setFileUploadSetting] = useState<FileUploadSetting>(InitialFileUploadSetting)
//////////////
// 設定
/////////////
//// サーバに設定後、反映された情報と照合して値が一致していることを確認。一致していない場合はalert
const _set_and_store = async (key: ServerSettingKey, newVal: string) => {
if (!props.voiceChangerClient) return false
const res = await props.voiceChangerClient.updateServerSettings(key, "" + newVal)
_setServerInfo(res)
if (newVal == res[key]) {
_setSetting({
...settingRef.current,
convertChunkNum: res.convertChunkNum,
srcId: res.srcId,
dstId: res.dstId,
gpu: res.gpu,
crossFadeOffsetRate: res.crossFadeOffsetRate,
crossFadeEndRate: res.crossFadeEndRate,
crossFadeOverlapRate: res.crossFadeOverlapRate,
framework: res.framework,
onnxExecutionProvider: (!!res.onnxExecutionProvider && res.onnxExecutionProvider.length > 0) ? res.onnxExecutionProvider[0] as OnnxExecutionProvider : DefaultVoiceChangerServerSetting.onnxExecutionProvider
})
return true
} else {
alert(`[ServerSetting] setting failed. [key:${key}, new:${newVal}, res:${res[key]}]`)
return false
}
}
const setFramework = useMemo(() => {
return async (framework: Framework) => {
return await _set_and_store(ServerSettingKey.framework, "" + framework)
}
}, [props.voiceChangerClient])
const setOnnxExecutionProvider = useMemo(() => {
return async (provider: OnnxExecutionProvider) => {
return await _set_and_store(ServerSettingKey.onnxExecutionProvider, "" + provider)
}
}, [props.voiceChangerClient])
const setSrcId = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.srcId, "" + num)
}
}, [props.voiceChangerClient])
const setDstId = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.dstId, "" + num)
}
}, [props.voiceChangerClient])
const setConvertChunkNum = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.convertChunkNum, "" + num)
}
}, [props.voiceChangerClient])
const setGpu = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.gpu, "" + num)
}
}, [props.voiceChangerClient])
const setCrossFadeOffsetRate = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.crossFadeOffsetRate, "" + num)
}
}, [props.voiceChangerClient])
const setCrossFadeEndRate = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.crossFadeEndRate, "" + num)
}
}, [props.voiceChangerClient])
const setCrossFadeOverlapRate = useMemo(() => {
return async (num: number) => {
return await _set_and_store(ServerSettingKey.crossFadeOverlapRate, "" + num)
}
}, [props.voiceChangerClient])
//////////////
// 操作
/////////////
const [uploadProgress, setUploadProgress] = useState<number>(0)
const [isUploading, setIsUploading] = useState<boolean>(false)
// (e) モデルアップロード
const _uploadFile = useMemo(() => {
return async (file: File, onprogress: (progress: number, end: boolean) => void) => {
if (!props.voiceChangerClient) return
const num = await props.voiceChangerClient.uploadFile(file, onprogress)
const res = await props.voiceChangerClient.concatUploadedFile(file, num)
console.log("uploaded", num, res)
}
}, [props.voiceChangerClient])
const loadModel = useMemo(() => {
return async () => {
if (!fileUploadSetting.pyTorchModel && !fileUploadSetting.onnxModel) {
alert("PyTorchモデルとONNXモデルのどちらか一つ以上指定する必要があります。")
return
}
if (!fileUploadSetting.configFile) {
alert("Configファイルを指定する必要があります。")
return
}
if (!props.voiceChangerClient) return
setUploadProgress(0)
setIsUploading(true)
const models = [fileUploadSetting.pyTorchModel, fileUploadSetting.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(fileUploadSetting.configFile, (progress: number, end: boolean) => {
console.log(progress, end)
})
const serverInfo = await props.voiceChangerClient.loadModel(fileUploadSetting.configFile, fileUploadSetting.pyTorchModel, fileUploadSetting.onnxModel)
console.log(serverInfo)
setUploadProgress(0)
setIsUploading(false)
}
}, [fileUploadSetting, props.voiceChangerClient])
const reloadServerInfo = useMemo(() => {
return async () => {
if (!props.voiceChangerClient) return
const res = await props.voiceChangerClient.getServerSettings()
_setServerInfo(res)
_setSetting({
...settingRef.current,
convertChunkNum: res.convertChunkNum,
srcId: res.srcId,
dstId: res.dstId,
gpu: res.gpu,
crossFadeOffsetRate: res.crossFadeOffsetRate,
crossFadeEndRate: res.crossFadeEndRate,
crossFadeOverlapRate: res.crossFadeOverlapRate,
framework: res.framework,
onnxExecutionProvider: (!!res.onnxExecutionProvider && res.onnxExecutionProvider.length > 0) ? res.onnxExecutionProvider[0] as OnnxExecutionProvider : DefaultVoiceChangerServerSetting.onnxExecutionProvider
})
}
}, [props.voiceChangerClient])
return {
setting,
serverInfo,
fileUploadSetting,
setFramework,
setOnnxExecutionProvider,
setSrcId,
setDstId,
setConvertChunkNum,
setGpu,
setCrossFadeOffsetRate,
setCrossFadeEndRate,
setCrossFadeOverlapRate,
reloadServerInfo,
setFileUploadSetting,
loadModel,
uploadProgress,
isUploading,
}
}

View File

@ -3,7 +3,7 @@ import { VoiceChangerWorkletNode, VolumeListener } from "./VoiceChangerWorkletNo
import workerjs from "raw-loader!../worklet/dist/index.js";
import { VoiceFocusDeviceTransformer, VoiceFocusTransformDevice } from "amazon-chime-sdk-js";
import { createDummyMediaStream, validateUrl } from "./util";
import { BufferSize, DefaultVoiceChangerOptions, Protocol, ServerSettingKey, VoiceChangerMode, VOICE_CHANGER_CLIENT_EXCEPTION, WorkletSetting } from "./const";
import { BufferSize, DefaultVoiceChangerClientSetting, Protocol, ServerSettingKey, VoiceChangerMode, VOICE_CHANGER_CLIENT_EXCEPTION, WorkletSetting } from "./const";
import MicrophoneStream from "microphone-stream";
import { AudioStreamer, Callbacks, AudioStreamerListeners } from "./AudioStreamer";
import { ServerConfigurator } from "./ServerConfigurator";
@ -85,8 +85,8 @@ export class VoiceChangerClient {
this.vcNode.connect(this.currentMediaStreamAudioDestinationNode) // vc node -> output node
// (vc nodeにはaudio streamerのcallbackでデータが投げ込まれる)
this.audioStreamer = new AudioStreamer(this.callbacks, audioStreamerListeners, { objectMode: true, })
this.audioStreamer.setInputChunkNum(DefaultVoiceChangerOptions.inputChunkNum)
this.audioStreamer.setVoiceChangerMode(DefaultVoiceChangerOptions.voiceChangerMode)
this.audioStreamer.setInputChunkNum(DefaultVoiceChangerClientSetting.inputChunkNum)
this.audioStreamer.setVoiceChangerMode(DefaultVoiceChangerClientSetting.voiceChangerMode)
if (this.vfEnable) {
this.vf = await VoiceFocusDeviceTransformer.create({ variant: 'c20' })

View File

@ -5,7 +5,7 @@
// 187.5chunk -> 1sec
// types
export type VoiceChangerRequestParamas = {
export type VoiceChangerServerSetting = {
convertChunkNum: number, // VITSに入力する変換サイズ。(入力データの2倍以上の大きさで指定。それより小さいものが指定された場合は、サーバ側で自動的に入力の2倍のサイズが設定される。)
srcId: number,
dstId: number,
@ -16,9 +16,11 @@ export type VoiceChangerRequestParamas = {
crossFadeEndRate: number,
crossFadeOverlapRate: number,
framework: Framework
onnxExecutionProvider: OnnxExecutionProvider,
}
export type VoiceChangerOptions = {
export type VoiceChangerClientSetting = {
audioInput: string | MediaStream | null,
mmvcServerUrl: string,
protocol: Protocol,
@ -28,8 +30,6 @@ export type VoiceChangerOptions = {
speakers: Speaker[],
forceVfDisable: boolean,
voiceChangerMode: VoiceChangerMode,
onnxExecutionProvider: OnnxExecutionProvider,
framework: Framework
}
export type WorkletSetting = {
@ -52,11 +52,12 @@ export type ServerInfo = {
convertChunkNum: number,
crossFadeOffsetRate: number,
crossFadeEndRate: number,
crossFadeOverlapRate: number,
gpu: number,
srcId: number,
dstId: number,
framework: Framework,
providers: string[]
onnxExecutionProvider: string[]
}
@ -120,7 +121,7 @@ export const ServerSettingKey = {
export type ServerSettingKey = typeof ServerSettingKey[keyof typeof ServerSettingKey]
// Defaults
export const DefaultVoiceChangerRequestParamas: VoiceChangerRequestParamas = {
export const DefaultVoiceChangerServerSetting: VoiceChangerServerSetting = {
convertChunkNum: 32, //(★1)
srcId: 107,
dstId: 100,
@ -128,10 +129,13 @@ export const DefaultVoiceChangerRequestParamas: VoiceChangerRequestParamas = {
crossFadeLowerValue: 0.1,
crossFadeOffsetRate: 0.1,
crossFadeEndRate: 0.9,
crossFadeOverlapRate: 0.5
crossFadeOverlapRate: 0.5,
framework: "PyTorch",
onnxExecutionProvider: "CPUExecutionProvider"
}
export const DefaultVoiceChangerOptions: VoiceChangerOptions = {
export const DefaultVoiceChangerClientSetting: VoiceChangerClientSetting = {
audioInput: null,
mmvcServerUrl: "",
protocol: "sio",
@ -162,8 +166,6 @@ export const DefaultVoiceChangerOptions: VoiceChangerOptions = {
],
forceVfDisable: false,
voiceChangerMode: "realtime",
framework: "PyTorch",
onnxExecutionProvider: "CPUExecutionProvider"
}
export const DefaultWorkletSetting: WorkletSetting = {

View File

@ -95,7 +95,7 @@ class VoiceChanger():
def get_info(self):
data = asdict(self.settings)
data["providers"] = self.onnx_session.get_providers() if self.onnx_session != None else []
data["onnxExecutionProvider"] = self.onnx_session.get_providers() if self.onnx_session != None else []
files = ["configFile", "pyTorchModelFile", "onnxModelFile"]
for f in files:
if data[f]!=None and os.path.exists(data[f]):