WIP: new gui

This commit is contained in:
wataru 2023-06-10 13:01:46 +09:00
parent 8c21d4e5ff
commit c273a40f44
34 changed files with 8141 additions and 3466 deletions

View File

@ -2,64 +2,24 @@
"type": "demo",
"id": "RVC",
"front": {
"title": [
{
"name": "title",
"options": {
"mainTitle": "Realtime Voice Changer Client",
"subTitle": "for RVC",
"lineNum": 1
}
},
{
"name": "clearSetting",
"options": {}
}
],
"title": [],
"serverControl": [],
"modelSetting": [],
"lab": [
{
"name": "mergeLab",
"options": {}
}
],
"lab": [],
"deviceSetting": [],
"qualityControl": [],
"speakerSetting": [],
"converterSetting": [],
"advancedSetting": [
{
"name": "protocol",
"options": {}
},
{
"name": "crossFadeOverlapSize",
"options": {}
},
{
"name": "crossFadeOffsetRate",
"options": {}
},
{
"name": "crossFadeEndRate",
"options": {}
},
{
"name": "trancateNumThreshold",
"options": {}
},
{
"name": "silenceFront",
"options": {}
},
{
"name": "protect",
"options": {}
}
],
"advancedSetting": [],
"modelSlotControl": [
{
"name": "headerArea",
"options": {
"mainTitle": "Realtime Voice Changer Client",
"subTitle": "for RVC"
}
},
{
"name": "modelSlotArea",
"options": {}

View File

@ -1,10 +1 @@
<!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>
<!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>

File diff suppressed because one or more lines are too long

31
client/demo/dist/index.js.LICENSE.txt vendored Normal file
View File

@ -0,0 +1,31 @@
/*! 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.
*/

File diff suppressed because it is too large Load Diff

View File

@ -19,27 +19,27 @@
"author": "wataru.okada@flect.co.jp",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.1",
"@babel/plugin-transform-runtime": "^7.22.4",
"@babel/preset-env": "^7.22.4",
"@babel/preset-react": "^7.22.3",
"@babel/preset-typescript": "^7.21.5",
"@types/node": "^20.2.5",
"@types/react": "^18.2.8",
"@babel/core": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@types/node": "^20.2.6",
"@types/react": "^18.2.9",
"@types/react-dom": "^18.2.4",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"eslint": "^8.41.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-webpack-plugin": "^4.0.1",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.1",
"html-webpack-plugin": "^5.5.2",
"npm-run-all": "^4.1.5",
"postcss-loader": "^7.3.2",
"postcss-loader": "^7.3.3",
"postcss-nested": "^6.0.1",
"prettier": "^2.8.8",
"rimraf": "^5.0.1",
@ -47,12 +47,12 @@
"ts-loader": "^9.4.3",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3",
"webpack": "^5.85.0",
"webpack-cli": "^5.1.1",
"webpack-dev-server": "^4.15.0"
"webpack": "^5.86.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.139",
"@dannadori/voice-changer-client-js": "^1.0.143",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",

View File

@ -2,64 +2,24 @@
"type": "demo",
"id": "RVC",
"front": {
"title": [
{
"name": "title",
"options": {
"mainTitle": "Realtime Voice Changer Client",
"subTitle": "for RVC",
"lineNum": 1
}
},
{
"name": "clearSetting",
"options": {}
}
],
"title": [],
"serverControl": [],
"modelSetting": [],
"lab": [
{
"name": "mergeLab",
"options": {}
}
],
"lab": [],
"deviceSetting": [],
"qualityControl": [],
"speakerSetting": [],
"converterSetting": [],
"advancedSetting": [
{
"name": "protocol",
"options": {}
},
{
"name": "crossFadeOverlapSize",
"options": {}
},
{
"name": "crossFadeOffsetRate",
"options": {}
},
{
"name": "crossFadeEndRate",
"options": {}
},
{
"name": "trancateNumThreshold",
"options": {}
},
{
"name": "silenceFront",
"options": {}
},
{
"name": "protect",
"options": {}
}
],
"advancedSetting": [],
"modelSlotControl": [
{
"name": "headerArea",
"options": {
"mainTitle": "Realtime Voice Changer Client",
"subTitle": "for RVC"
}
},
{
"name": "modelSlotArea",
"options": {}

View File

@ -16,6 +16,8 @@ export const OpenLicenseDialogCheckbox = "open-license-dialog-checkbox"
export const OpenWaitingDialogCheckbox = "open-waiting-dialog-checkbox"
export const OpenStartingNoticeDialogCheckbox = "open-starting-notice-dialog-checkbox"
export const OpenModelSlotManagerDialogCheckbox = "open-model-slot-manager-dialog-checkbox"
export const OpenMergeLabDialogCheckbox = "open-merge-lab-dialog-checkbox"
export const OpenAdvancedSettingDialogCheckbox = "open-advanced-setting-dialog-checkbox"
type Props = {
children: ReactNode;
@ -36,6 +38,9 @@ export type StateControls = {
showStartingNoticeCheckbox: StateControlCheckbox
showModelSlotManagerCheckbox: StateControlCheckbox
showMergeLabCheckbox: StateControlCheckbox
showAdvancedSettingCheckbox: StateControlCheckbox
}
type GuiStateAndMethod = {
@ -154,6 +159,8 @@ export const GuiStateProvider = ({ children }: Props) => {
const showWaitingCheckbox = useStateControlCheckbox(OpenWaitingDialogCheckbox);
const showStartingNoticeCheckbox = useStateControlCheckbox(OpenStartingNoticeDialogCheckbox);
const showModelSlotManagerCheckbox = useStateControlCheckbox(OpenModelSlotManagerDialogCheckbox);
const showMergeLabCheckbox = useStateControlCheckbox(OpenMergeLabDialogCheckbox);
const showAdvancedSettingCheckbox = useStateControlCheckbox(OpenAdvancedSettingDialogCheckbox);
useEffect(() => {
openServerControlCheckbox.updateState(true)
@ -172,6 +179,9 @@ export const GuiStateProvider = ({ children }: Props) => {
showStartingNoticeCheckbox.updateState(false)
showModelSlotManagerCheckbox.updateState(false)
showMergeLabCheckbox.updateState(false)
showAdvancedSettingCheckbox.updateState(false)
}, [])
useEffect(() => {
@ -209,7 +219,11 @@ export const GuiStateProvider = ({ children }: Props) => {
showLicenseCheckbox,
showWaitingCheckbox,
showStartingNoticeCheckbox,
showModelSlotManagerCheckbox
showModelSlotManagerCheckbox,
showMergeLabCheckbox,
showAdvancedSettingCheckbox
},
isConverting,
setIsConverting,

View File

@ -60,6 +60,7 @@ import { ProtectRow, ProtectRowProps } from "./components/610_ProtectRow"
import { ModelSlotArea, ModelSlotAreaProps } from "./components2/100_ModelSlotArea"
import { CharacterArea, CharacterAreaProps } from "./components2/101_CharacterArea"
import { ConfigArea, ConfigAreaProps } from "./components2/102_ConfigArea"
import { HeaderArea, HeaderAreaProps } from "./components2/001_HeaderArea"
export const catalog: { [key: string]: (props: any) => JSX.Element } = {}
@ -155,11 +156,13 @@ const initialize = () => {
addToCatalog("mergeLab", (props: MergeLabRowProps) => { return <MergeLabRow {...props} /> })
addToCatalog("headerArea", (props: HeaderAreaProps) => { return <HeaderArea {...props} /> })
addToCatalog("modelSlotArea", (props: ModelSlotAreaProps) => { return <ModelSlotArea {...props} /> })
addToCatalog("characterArea", (props: CharacterAreaProps) => { return <CharacterArea {...props} /> })
addToCatalog("configArea", (props: ConfigAreaProps) => { return <ConfigArea {...props} /> })
}
initialize()

View File

@ -4,6 +4,8 @@ import { LicenseDialog } from "./901_LicenseDialog";
import { WaitingDialog } from "./902_WaitingDialog";
import { StartingNoticeDialog } from "./903_StartingNoticeDialog";
import { ModelSlotManagerDialog } from "./904_ModelSlotManagerDialog";
import { MergeLabDialog } from "./905_MergeLabDialog";
import { AdvancedSettingDialog } from "./906_AdvancedSettingDialog";
export const Dialogs = () => {
const guiState = useGuiState()
@ -13,6 +15,8 @@ export const Dialogs = () => {
{guiState.stateControls.showWaitingCheckbox.trigger}
{guiState.stateControls.showStartingNoticeCheckbox.trigger}
{guiState.stateControls.showModelSlotManagerCheckbox.trigger}
{guiState.stateControls.showMergeLabCheckbox.trigger}
{guiState.stateControls.showAdvancedSettingCheckbox.trigger}
<div className="dialog-container" id="dialog">
{guiState.stateControls.showLicenseCheckbox.trigger}
<LicenseDialog></LicenseDialog>
@ -22,6 +26,10 @@ export const Dialogs = () => {
<StartingNoticeDialog></StartingNoticeDialog>
{guiState.stateControls.showModelSlotManagerCheckbox.trigger}
<ModelSlotManagerDialog></ModelSlotManagerDialog>
{guiState.stateControls.showMergeLabCheckbox.trigger}
<MergeLabDialog></MergeLabDialog>
{guiState.stateControls.showAdvancedSettingCheckbox.trigger}
<AdvancedSettingDialog></AdvancedSettingDialog>
</div>
</div>

View File

@ -131,7 +131,7 @@ export const ModelSlotManagerDialog = () => {
const isRegisterd = modelFileName.length > 0 ? true : false
const name = x.name && x.name.length > 0 ? x.name : isRegisterd ? modelFileName : "blank"
const termOfUseUrlLink = x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[terms of use]</a> : <></>
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[terms of use]</a> : <></>
const nameValueClass = isRegisterd ? "model-slot-detail-row-value-pointable" : "model-slot-detail-row-value"
const nameValueAction = isRegisterd ? async (index: number) => {
@ -260,7 +260,7 @@ export const ModelSlotManagerDialog = () => {
}
const options = (
serverSetting.serverSetting.sampleModels.filter(x => { return lang == "All" ? true : x.lang == lang }).map((x, index) => {
const termOfUseUrlLink = x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[terms of use]</a> : <></>
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[terms of use]</a> : <></>
return (
<div key={index} className="model-slot">

View File

@ -0,0 +1,164 @@
import React, { useEffect, useMemo, useState } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { MergeElement, ModelType } from "@dannadori/voice-changer-client-js";
export const MergeLabDialog = () => {
const guiState = useGuiState()
const { serverSetting } = useAppState()
const [currentFilter, setCurrentFilter] = useState<string>("")
const [mergeElements, setMergeElements] = useState<MergeElement[]>([])
// スロットが変更されたときの初期化処理
const newSlotChangeKey = useMemo(() => {
if (!serverSetting.serverSetting.modelSlots) {
return ""
}
return serverSetting.serverSetting.modelSlots.reduce((prev, cur) => {
return prev + "_" + cur.modelFile
}, "")
}, [serverSetting.serverSetting.modelSlots])
const filterItems = useMemo(() => {
return serverSetting.serverSetting.modelSlots.reduce((prev, cur) => {
const key = `${cur.modelType},${cur.samplingRate},${cur.embChannels}`
const val = { type: cur.modelType, samplingRate: cur.samplingRate, embChannels: cur.embChannels }
const existKeys = Object.keys(prev)
if (cur.modelFile.length == 0) {
return prev
}
if (cur.modelType == "onnxRVC" || cur.modelType == "onnxRVCNono") {
return prev
}
if (!existKeys.includes(key)) {
prev[key] = val
}
return prev
}, {} as { [key: string]: { type: ModelType, samplingRate: number, embChannels: number } })
}, [newSlotChangeKey])
const models = useMemo(() => {
return serverSetting.serverSetting.modelSlots.filter(x => {
const filterVals = filterItems[currentFilter]
if (!filterVals) {
return false
}
if (x.modelType == filterVals.type && x.samplingRate == filterVals.samplingRate && x.embChannels == filterVals.embChannels) {
return true
} else {
return false
}
})
}, [filterItems, currentFilter])
useEffect(() => {
if (Object.keys(filterItems).length > 0) {
setCurrentFilter(Object.keys(filterItems)[0])
}
}, [filterItems])
useEffect(() => {
const newMergeElements = models.map((x) => {
return { filename: x.modelFile, strength: 0 }
})
setMergeElements(newMergeElements)
}, [models])
const dialog = useMemo(() => {
const closeButtonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text">
</div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { guiState.stateControls.showMergeLabCheckbox.updateState(false) }} >close</div>
</div>
<div className="body-item-text"></div>
</div>
)
const filterOptions = Object.keys(filterItems).map(x => {
return <option key={x} value={x}>{x}</option>
}).filter(x => x != null)
const onMergeElementsChanged = (filename: string, strength: number) => {
const newMergeElements = mergeElements.map((x) => {
if (x.filename == filename) {
return { filename: x.filename, strength: strength }
} else {
return x
}
})
setMergeElements(newMergeElements)
}
const onMergeClicked = () => {
serverSetting.mergeModel({
command: "mix",
defaultTune: 0,
defaultIndexRatio: 1,
defaultProtect: 0.5,
files: mergeElements
})
}
const modelList = mergeElements.map((x, index) => {
const name = models.find(model => { return model.modelFile == x.filename })?.name || ""
return (
<div key={index} className="merge-lab-model-item">
<div>
{name}
</div>
<div>
<input type="range" className="body-item-input-slider" min="0" max="100" step="1" value={x.strength} onChange={(e) => {
onMergeElementsChanged(x.filename, Number(e.target.value))
}}></input>
<span className="body-item-input-slider-val">{x.strength}</span>
</div>
</div>
)
})
const content = (
<div className="merge-lab-container">
<div className="merge-lab-type-filter">
<div>
Type:
</div>
<div>
<select value={currentFilter} onChange={(e) => { setCurrentFilter(e.target.value) }}>
{filterOptions}
</select>
</div>
</div>
<div className="merge-lab-manipulator">
<div className="merge-lab-model-list">
{modelList}
</div>
<div className="merge-lab-merge-buttons">
<div className="merge-lab-merge-buttons-notice">
The merged model is stored in the final slot. If you assign this slot, it will be overwritten.
</div>
<div className="merge-lab-merge-button" onClick={onMergeClicked}>
merge
</div>
</div>
</div>
</div>
)
return (
<div className="dialog-frame">
<div className="dialog-title">MergeLab</div>
<div className="dialog-content">
{content}
{closeButtonRow}
</div>
</div>
);
}, [newSlotChangeKey, currentFilter, mergeElements]);
return dialog;
};

View File

@ -0,0 +1,182 @@
import React, { useMemo } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { CrossFadeOverlapSize, Protocol } from "@dannadori/voice-changer-client-js";
export const AdvancedSettingDialog = () => {
const guiState = useGuiState()
const { workletNodeSetting, workletSetting, serverSetting } = useAppState()
const dialog = useMemo(() => {
const closeButtonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text">
</div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { guiState.stateControls.showAdvancedSettingCheckbox.updateState(false) }} >close</div>
</div>
<div className="body-item-text"></div>
</div>
)
const onProtocolChanged = async (val: Protocol) => {
workletNodeSetting.updateWorkletNodeSetting({ ...workletNodeSetting.workletNodeSetting, protocol: val })
}
const protocolRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
protocol
</div>
<div className="advanced-setting-container-row-field">
<select value={workletNodeSetting.workletNodeSetting.protocol} onChange={(e) => {
onProtocolChanged(e.target.value as
Protocol)
}}>
{
Object.values(Protocol).map(x => {
return <option key={x} value={x}>{x}</option>
})
}
</select>
</div>
</div>
)
const crossfaceRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
Crossfade
</div>
<div className="advanced-setting-container-row-field">
<div className="advanced-setting-container-row-field-crossfade-container">
<div>
<div>overlap:</div>
<div>
<select className="body-select" value={serverSetting.serverSetting.crossFadeOverlapSize} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeOverlapSize: Number(e.target.value) as CrossFadeOverlapSize })
}}>
{
Object.values(CrossFadeOverlapSize).map(x => {
return <option key={x} value={x}>{x}</option>
})
}
</select>
</div>
</div>
<div>
<div>start:</div>
<div>
<input type="number" min={0} max={1} step={0.1} value={serverSetting.serverSetting.crossFadeOffsetRate} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeOffsetRate: Number(e.target.value) })
}} />
</div>
</div>
<div>
<div>end:</div>
<div>
<input type="number" min={0} max={1} step={0.1} value={serverSetting.serverSetting.crossFadeEndRate} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeEndRate: Number(e.target.value) })
}} />
</div>
</div>
</div>
</div>
</div>
)
const trancateRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
Trancate
</div>
<div className="advanced-setting-container-row-field">
<input type="number" min={5} max={300} step={1} value={workletSetting.setting.numTrancateTreshold} onChange={(e) => {
workletSetting.setSetting({
...workletSetting.setting,
numTrancateTreshold: Number(e.target.value)
})
}} />
</div>
</div>
)
const onSilenceFrontChanged = (val: number) => {
serverSetting.updateServerSettings({
...serverSetting.serverSetting,
silenceFront: val
})
}
const silenceFrontRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
SilenceFront
</div>
<div className="advanced-setting-container-row-field">
<select value={serverSetting.serverSetting.silenceFront} onChange={(e) => { onSilenceFrontChanged(Number(e.target.value)) }}>
<option value="0" >off</option>
<option value="1" >on</option>
</select>
</div>
</div>
)
const protectRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
Protect
</div>
<div className="advanced-setting-container-row-field">
<div>
<input type="range" className="body-item-input-slider" min="0" max="0.5" step="0.1" value={serverSetting.serverSetting.protect || 0} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, protect: Number(e.target.value) })
}}></input>
<span className="body-item-input-slider-val">{serverSetting.serverSetting.protect}</span>
</div>
</div>
</div>
)
const onRVCQualityChanged = (val: number) => {
serverSetting.updateServerSettings({
...serverSetting.serverSetting,
rvcQuality: val
})
}
const rvcQualityRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
RVC Quality
</div>
<div className="advanced-setting-container-row-field">
<select value={serverSetting.serverSetting.rvcQuality} onChange={(e) => { onRVCQualityChanged(Number(e.target.value)) }}>
<option value="0" >low</option>
<option value="1" >high</option>
</select>
</div>
</div>
)
const content = (
<div className="advanced-setting-container">
{protocolRow}
{crossfaceRow}
{trancateRow}
{silenceFrontRow}
{protectRow}
{rvcQualityRow}
</div>
)
return (
<div className="dialog-frame">
<div className="dialog-title">Advanced Setting</div>
<div className="dialog-content">
{content}
{closeButtonRow}
</div>
</div>
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, workletNodeSetting.workletNodeSetting, workletNodeSetting.updateWorkletNodeSetting, workletSetting.setting, workletSetting.setSetting]);
return dialog;
};

View File

@ -21,6 +21,9 @@ export const Lab = () => {
}, []);
const deviceSetting = useMemo(() => {
if (componentSettings.length == 0) {
return <></>
}
const components = componentSettings.map((x, index) => {
const c = generateComponent(x.name, x.options)
return <div key={`${x.name}_${index}`}>{c}</div>

View File

@ -41,7 +41,11 @@ export const AudioInputMediaRow = () => {
const dst = appState.audioContext.createMediaStreamDestination()
audioSrcNode.current.connect(dst)
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: dst.stream })
try {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: dst.stream })
} catch (e) {
console.error(e)
}
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement
audio_echo.srcObject = dst.stream

View File

@ -36,7 +36,11 @@ export const AudioInputRow = (_props: AudioInputRowProps) => {
<select className="body-select" value={guiState.audioInputForGUI} onChange={(e) => {
guiState.setAudioInputForGUI(e.target.value)
if (guiState.audioInputForGUI != "file") {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: e.target.value })
try {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: e.target.value })
} catch (e) {
console.error(e)
}
}
}}>
{

View File

@ -14,17 +14,29 @@ export const NoiseControlRow = (_props: NoiseControlRowProps) => {
<div className="body-item-title left-padding-1 ">Noise Suppression</div>
<div>
<input type="checkbox" checked={appState.clientSetting.clientSetting.echoCancel} onChange={(e) => {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, echoCancel: e.target.checked })
try {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, echoCancel: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> echo cancel
</div>
<div>
<input type="checkbox" checked={appState.clientSetting.clientSetting.noiseSuppression} onChange={(e) => {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression: e.target.checked })
try {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> suppression1
</div>
<div>
<input type="checkbox" checked={appState.clientSetting.clientSetting.noiseSuppression2} onChange={(e) => {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression2: e.target.checked })
try {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression2: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> suppression2
</div>
<div className="body-button-container">

View File

@ -13,7 +13,11 @@ export const SampleRateRow = (_props: SampleRateRowProps) => {
<div className="body-item-title left-padding-1">Sample Rate</div>
<div className="body-select-container">
<select className="body-select" value={appState.clientSetting.clientSetting.sampleRate} onChange={(e) => {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, sampleRate: Number(e.target.value) as SampleRate })
try {
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, sampleRate: Number(e.target.value) as SampleRate })
} catch (e) {
console.error(e)
}
}}>
{
Object.values(SampleRate).map(x => {

View File

@ -0,0 +1,162 @@
import React, { useMemo } from "react";
import { INDEXEDDB_KEY_AUDIO_OUTPUT, INDEXEDDB_KEY_DEFAULT_MODEL_TYPE, isDesktopApp } from "../../../const";
import { useGuiState } from "../001_GuiStateProvider";
import { useAppRoot } from "../../../001_provider/001_AppRootProvider";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useIndexedDB } from "@dannadori/voice-changer-client-js";
export type HeaderAreaProps = {
mainTitle: string
subTitle: string
}
export const HeaderArea = (props: HeaderAreaProps) => {
const { appGuiSettingState, setClientType } = useAppRoot()
const { clientSetting, clearSetting } = useAppState()
const { setIsConverting, isConverting } = useGuiState()
const clientType = appGuiSettingState.appGuiSetting.id
const { removeItem } = useIndexedDB({ clientType: clientType })
const { setItem } = useIndexedDB({ clientType: null })
const githubLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer") }}>
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">github</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">github</div>
</a>
)
}, [])
const manualLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md") }}>
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text">manual</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text">manual</div>
</a>
)
}, [])
const toolLink = useMemo(() => {
return isDesktopApp() ?
(
<div className="link tooltip">
<img src="./assets/icons/tool.svg" />
<div className="tooltip-text tooltip-text-100px">
<p onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://w-okada.github.io/screen-recorder-ts/")
}}>
screen capture
</p>
</div>
</div>
)
:
(
<div className="link tooltip">
<img src="./assets/icons/tool.svg" />
<div className="tooltip-text tooltip-text-100px">
<p onClick={() => {
window.open("https://w-okada.github.io/screen-recorder-ts/", '_blank', "noreferrer")
}}>
screen capture
</p>
</div>
</div>
)
}, [])
const coffeeLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">donate()</div>
</span>
)
:
(
<a className="link tooltip" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">
donate()
</div>
</a>
)
}, [])
const headerArea = useMemo(() => {
const onClearSettingClicked = async () => {
await clearSetting()
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
location.reload()
}
const onReloadClicked = async () => {
location.reload()
}
const onReselectVCClicked = async () => {
setIsConverting(false)
if (isConverting) {
await clientSetting.stop()
setIsConverting(false)
}
setItem(INDEXEDDB_KEY_DEFAULT_MODEL_TYPE, "null")
setClientType(null)
appGuiSettingState.clearAppGuiSetting()
}
return (
<div className="headerArea">
<div className="title1">
<span className="title">{props.mainTitle}</span>
<span className="title-version">{props.subTitle}</span>
<span className="title-version-number">{appGuiSettingState.version}</span>
<span className="title-version-number">{appGuiSettingState.edition}</span>
</div>
<div className="icons">
<span className="belongings">
{githubLink}
{manualLink}
{toolLink}
{coffeeLink}
{/* {licenseButton} */}
</span>
<span className="belongings">
<div className="belongings-button" onClick={onClearSettingClicked}>clear setting</div>
<div className="belongings-button" onClick={onReloadClicked}>reload</div>
<div className="belongings-button" onClick={onReselectVCClicked}>select vc</div>
</span>
</div>
</div>
)
}, [props.subTitle, props.mainTitle, appGuiSettingState.version, appGuiSettingState.edition])
return headerArea
};

View File

@ -253,7 +253,7 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
}
const exportOnnx = selected.modelFile.endsWith("pth") ? (
<div className="character-area-buutton" onClick={onnxExportButtonAction}>export onnx</div>
<div className="character-area-button" onClick={onnxExportButtonAction}>export onnx</div>
) : <></>
return (
<div className="character-area-control">
@ -261,8 +261,8 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
</div>
<div className="character-area-control-field">
<div className="character-area-buuttons">
<div className="character-area-buutton" onClick={onUpdateDefaultClicked}>save default</div>
<div className="character-area-buttons">
<div className="character-area-button" onClick={onUpdateDefaultClicked}>save default</div>
{exportOnnx}
</div>
</div>

View File

@ -20,19 +20,31 @@ export const QualityArea = (props: QualityAreaProps) => {
<div className="config-sub-area-noise-container">
<div className="config-sub-area-noise-checkbox-container">
<input type="checkbox" disabled={serverSetting.serverSetting.enableServerAudio != 0} checked={clientSetting.clientSetting.echoCancel} onChange={(e) => {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, echoCancel: e.target.checked })
try {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, echoCancel: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> <span>Echo</span>
</div>
<div className="config-sub-area-noise-checkbox-container">
<input type="checkbox" disabled={serverSetting.serverSetting.enableServerAudio != 0} checked={clientSetting.clientSetting.noiseSuppression} onChange={(e) => {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, noiseSuppression: e.target.checked })
try {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, noiseSuppression: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> <span>Sup1</span>
</div>
<div className="config-sub-area-noise-checkbox-container">
<input type="checkbox" disabled={serverSetting.serverSetting.enableServerAudio != 0} checked={clientSetting.clientSetting.noiseSuppression2} onChange={(e) => {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, noiseSuppression2: e.target.checked })
try {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, noiseSuppression2: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> <span>Sup2</span>
</div>
</div>

View File

@ -65,7 +65,7 @@ export const ConvertArea = (props: ConvertProps) => {
}}>
{
gpusEntry.map(x => {
return <option key={x.id} value={x.id}>{x.name}({(x.memory / 1024 / 1024 / 1024).toFixed(0)}GB) </option>
return <option key={x.id} value={x.id}>{x.name}{x.name == "cpu" ? "" : `(${(x.memory / 1024 / 1024 / 1024).toFixed(0)})GB`} </option>
})
}
</select>

View File

@ -75,10 +75,17 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">input</div>
<div className="config-sub-area-control-field">
<select className="body-select" value={audioInputForGUI} onChange={(e) => {
<select className="body-select" value={audioInputForGUI} onChange={async (e) => {
setAudioInputForGUI(e.target.value)
if (audioInputForGUI != "file") {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: e.target.value })
try {
await clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: e.target.value })
} catch (e) {
alert(e)
console.error(e)
setAudioInputForGUI("none")
await clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: null })
}
}
}}>
{
@ -164,7 +171,11 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const dst = audioContext.createMediaStreamDestination()
audioSrcNode.current.connect(dst)
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: dst.stream })
try {
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: dst.stream })
} catch (e) {
console.error(e)
}
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement
audio_echo.srcObject = dst.stream
@ -325,6 +336,9 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
// (4) レコーダー
const outputRecorderRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>
}
const onOutputRecordStartClicked = async () => {
setOutputRecordingStarted(true)
await workletNodeSetting.startOutputRecording()
@ -335,13 +349,13 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
downloadRecord(record)
}
const startClassName = outputRecordingStarted ? "config-sub-area-buutton-active" : "config-sub-area-buutton"
const stopClassName = outputRecordingStarted ? "config-sub-area-buutton" : "config-sub-area-buutton-active"
const startClassName = outputRecordingStarted ? "config-sub-area-button-active" : "config-sub-area-button"
const stopClassName = outputRecordingStarted ? "config-sub-area-button" : "config-sub-area-button-active"
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">REC.</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buuttons">
<div className="config-sub-area-buttons">
<div onClick={onOutputRecordStartClicked} className={startClassName}>start</div>
<div onClick={onOutputRecordStopClicked} className={stopClassName}>stop</div>
</div>

View File

@ -12,17 +12,6 @@ export const RecorderArea = (_props: RecorderAreaProps) => {
const [serverIORecording, setServerIORecording] = useState<boolean>(false)
// const recorderRow = useMemo(() => {
// return (
// <div className="config-sub-area-control">
// <div className="config-sub-area-control-title">RECORD:</div>
// <div className="config-sub-area-control-field">
// </div>
// </div>
// )
// }, [serverSetting.serverSetting, serverSetting.updateServerSettings])
const serverIORecorderRow = useMemo(() => {
const onServerIORecordStartClicked = async () => {
@ -48,8 +37,8 @@ export const RecorderArea = (_props: RecorderAreaProps) => {
wavOutput.setSinkId(audioOutputForAnalyzer)
}
const startClassName = serverIORecording ? "config-sub-area-buutton-active" : "config-sub-area-buutton"
const stopClassName = serverIORecording ? "config-sub-area-buutton" : "config-sub-area-buutton-active"
const startClassName = serverIORecording ? "config-sub-area-button-active" : "config-sub-area-button"
const stopClassName = serverIORecording ? "config-sub-area-button" : "config-sub-area-button-active"
return (
<>
<div className="config-sub-area-control">
@ -59,7 +48,7 @@ export const RecorderArea = (_props: RecorderAreaProps) => {
<div className="config-sub-area-control left-padding-1">
<div className="config-sub-area-control-title">SIO rec.</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buuttons">
<div className="config-sub-area-buttons">
<div onClick={onServerIORecordStartClicked} className={startClassName}>start</div>
<div onClick={onServerIORecordStopClicked} className={stopClassName}>stop</div>
</div>

View File

@ -0,0 +1,41 @@
import React, { useMemo, } from "react"
import { useGuiState } from "../001_GuiStateProvider"
export type MoreActionAreaProps = {
}
export const MoreActionArea = (_props: MoreActionAreaProps) => {
const { stateControls } = useGuiState()
const serverIORecorderRow = useMemo(() => {
const onOpenMergeLabClicked = () => {
stateControls.showMergeLabCheckbox.updateState(true)
}
const onOpenAdvancedSettingClicked = () => {
stateControls.showAdvancedSettingCheckbox.updateState(true)
}
return (
<>
<div className="config-sub-area-control left-padding-1">
<div className="config-sub-area-control-title">more...</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buttons">
<div onClick={onOpenMergeLabClicked} className="config-sub-area-button">Merge Lab</div>
<div onClick={onOpenAdvancedSettingClicked} className="config-sub-area-button">Advanced Setting</div>
</div>
</div>
</div>
</>
)
}, [stateControls])
return (
<div className="config-sub-area">
{serverIORecorderRow}
</div>
)
}

View File

@ -3,6 +3,7 @@ import { QualityArea } from "./102-1_QualityArea"
import { ConvertArea } from "./102-2_ConvertArea"
import { DeviceArea } from "./102-3_DeviceArea"
import { RecorderArea } from "./102-4_RecorderArea"
import { MoreActionArea } from "./102-5_MoreActionArea"
export type ConfigAreaProps = {
detectors: string[]
@ -22,6 +23,9 @@ export const ConfigArea = (props: ConfigAreaProps) => {
<DeviceArea></DeviceArea>
<RecorderArea></RecorderArea>
</div>
<div className="config-area">
<MoreActionArea></MoreActionArea>
</div>
</>
)

View File

@ -1130,11 +1130,11 @@ body {
width: 3rem;
}
}
.character-area-buuttons {
.character-area-buttons {
display: flex;
flex-direction: row;
gap: 5px;
.character-area-buutton {
.character-area-button {
border: solid 2px #999;
color: white;
font-size: 0.8rem;
@ -1143,7 +1143,7 @@ body {
cursor: pointer;
padding: 5px;
}
.character-area-buutton:hover {
.character-area-button:hover {
border: solid 2px #faa;
}
}
@ -1212,12 +1212,12 @@ audio::-webkit-media-controls-overlay-enclosure{
width: 3rem;
}
}
.config-sub-area-buuttons {
.config-sub-area-buttons {
display: flex;
flex-direction: row;
gap: 5px;
align-items: center;
.config-sub-area-buutton {
.config-sub-area-button {
border: solid 2px #999;
color: white;
background: #666;
@ -1230,10 +1230,10 @@ audio::-webkit-media-controls-overlay-enclosure{
padding-left: 2px;
padding-right: 2px;
}
.config-sub-area-buutton:hover {
.config-sub-area-button:hover {
border: solid 2px #faa;
}
.config-sub-area-buutton-active {
.config-sub-area-button-active {
border: solid 2px #999;
color: white;
background: #844;
@ -1298,3 +1298,146 @@ audio::-webkit-media-controls-overlay-enclosure{
}
}
}
.headerArea {
display: flex;
flex-direction: column;
.title1 {
display: flex;
flex-direction: row;
gap: 5px;
align-items: flex-end;
.title {
font-size: 1.8rem;
font-weight: 700;
color: #333;
text-shadow: 0 0 2px #333;
}
.title-version {
font-size: 0.9rem;
}
.title-version-number {
font-size: 0.7rem;
}
}
.icons {
display: flex;
flex-direction: row;
gap: 20px;
.belongings {
display: flex;
flex-direction: row;
gap: 3px;
.belongings-button {
border: solid 2px #999;
color: white;
font-size: 0.8rem;
border-radius: 5px;
background: #666;
cursor: pointer;
padding: 5px;
height: 1.7rem;
top: -2px;
}
.belongings-button:hover {
border: solid 2px #cc6;
}
}
}
}
.advanced-setting-container {
display: flex;
flex-direction: column;
gap: 5px;
margin: 10px;
.advanced-setting-container-row {
display: flex;
flex-direction: row;
gap: 5px;
.advanced-setting-container-row-title {
width: 7rem;
font-weight: 700;
font-size: 0.9rem;
}
.advanced-setting-container-row-field {
width: 15rem;
font-size: 0.9rem;
.advanced-setting-container-row-field-crossfade-container {
display: flex;
flex-direction: row;
gap: 5px;
width: 10rem;
& > div {
display: flex;
flex-direction: row;
gap: 3px;
& > div:nth-child(1) {
color: #333;
}
& > div:nth-child(2) {
}
}
}
}
}
}
.merge-lab-container {
display: flex;
flex-direction: column;
margin: 10px;
gap: 10px;
.merge-lab-type-filter {
display: flex;
flex-direction: row;
& > div:nth-child(1) {
width: 50%;
}
& > div:nth-child(2) {
width: 50%;
}
}
.merge-lab-manipulator {
display: flex;
flex-direction: row;
.merge-lab-model-list {
width: 70%;
.merge-lab-model-item {
display: flex;
flex-direction: row;
& > div:nth-child(1) {
width: 50%;
}
& > div:nth-child(2) {
width: 50%;
}
}
}
.merge-lab-merge-buttons {
display: flex;
flex-direction: column-reverse;
width: 30%;
.merge-lab-merge-button {
border: solid 2px #ddd;
color: black;
font-size: 0.8rem;
border-radius: 5px;
background: #eee;
cursor: pointer;
padding: 5px;
height: 1.7rem;
text-align: center;
}
.merge-lab-merge-button:hover {
border: solid 2px #aaa;
}
.merge-lab-merge-buttons-notice {
font-size: 0.7rem;
font-weight: 700;
color: #333;
text-align: center;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.140",
"version": "1.0.143",
"description": "",
"main": "dist/index.js",
"directories": {
@ -28,9 +28,9 @@
"devDependencies": {
"@types/audioworklet": "^0.0.46",
"@types/node": "^20.2.5",
"@types/react": "18.2.7",
"@types/react": "18.2.9",
"@types/react-dom": "18.2.4",
"eslint": "^8.41.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
@ -40,14 +40,14 @@
"raw-loader": "^4.0.2",
"rimraf": "^5.0.1",
"ts-loader": "^9.4.3",
"typescript": "^5.0.4",
"webpack": "^5.85.0",
"webpack-cli": "^5.1.1",
"webpack-dev-server": "^4.15.0"
"typescript": "^5.1.3",
"webpack": "^5.86.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^2.7.0",
"amazon-chime-sdk-js": "^3.14.1",
"buffer": "^6.0.3",
"localforage": "^1.10.0",
"react": "^18.2.0",

View File

@ -144,6 +144,8 @@ export class VoiceChangerClient {
}
} catch (e) {
console.warn(e)
this.vcInNode.stop()
await this.unlock(lockNum)
throw e
}
// this.currentMediaStream.getAudioTracks().forEach((x) => {
@ -219,8 +221,7 @@ export class VoiceChangerClient {
this.configurator.setServerUrl(url)
}
updateClientSetting = (setting: VoiceChangerClientSetting) => {
console.log(`[VoiceChangerClient] Updating Client Setting,`, this.setting, setting)
updateClientSetting = async (setting: VoiceChangerClientSetting) => {
let reconstructInputRequired = false
if (
this.setting.audioInput != setting.audioInput ||
@ -241,7 +242,7 @@ export class VoiceChangerClient {
this.setting = setting
if (reconstructInputRequired) {
this.setup()
await this.setup()
}
}

View File

@ -45,8 +45,15 @@ export const useClientSetting = (props: UseClientSettingProps): ClientSettingSta
}, [])
// 初期化 その2 クライアントに設定
useEffect(() => {
if (!props.voiceChangerClient) return
props.voiceChangerClient.updateClientSetting(clientSetting)
const initialSetup = async () => {
if (!props.voiceChangerClient) return
try {
await props.voiceChangerClient.updateClientSetting(clientSetting)
} catch (e) {
console.error(e)
}
}
initialSetup()
}, [props.voiceChangerClient])
@ -67,14 +74,14 @@ export const useClientSetting = (props: UseClientSettingProps): ClientSettingSta
// 設定
/////////////
const updateClientSetting = useMemo(() => {
return (_clientSetting: VoiceChangerClientSetting) => {
return async (_clientSetting: VoiceChangerClientSetting) => {
if (!props.voiceChangerClient) return
for (let k in _clientSetting) {
const cur_v = clientSetting[k as keyof VoiceChangerClientSetting]
const new_v = _clientSetting[k as keyof VoiceChangerClientSetting]
if (cur_v != new_v) {
storeSetting(_clientSetting)
props.voiceChangerClient.updateClientSetting(_clientSetting)
await props.voiceChangerClient.updateClientSetting(_clientSetting)
break
}
}

View File

@ -75,8 +75,7 @@ class MMVC_Rest_Fileuploader:
def post_update_settings(
self, key: str = Form(...), val: Union[int, str, float] = Form(...)
):
# print("[Voice Changer] update configuration:", key, val)
print("post_update_settings", key, type(val))
print("[Voice Changer] update configuration:", key, val)
info = self.voiceChangerManager.update_settings(key, val)
json_compatible_item_data = jsonable_encoder(info)
return JSONResponse(content=json_compatible_item_data)

View File

@ -125,16 +125,19 @@ class RVC:
if sampleInfo is None:
print("[Voice Changer] sampleInfo is None")
return
modelPath, indexPath = downloadModelFiles(sampleInfo, useIndex)
modelPath, indexPath, iconPath = downloadModelFiles(sampleInfo, useIndex)
slotInfo.modelFile = modelPath
if indexPath is not None:
slotInfo.indexFile = indexPath
if iconPath is not None:
slotInfo.iconFile = iconPath
slotInfo.sampleId = sampleInfo.id
slotInfo.credit = sampleInfo.credit
slotInfo.description = sampleInfo.description
slotInfo.name = sampleInfo.name
slotInfo.termsOfUseUrl = sampleInfo.termsOfUseUrl
# slotInfo.samplingRate = sampleInfo.sampleRate
# slotInfo.modelType = sampleInfo.modelType
# slotInfo.f0 = sampleInfo.f0
@ -163,6 +166,8 @@ class RVC:
slotInfo.modelFile = self.moveToModelDir(slotInfo.modelFile, slotDir)
if slotInfo.indexFile is not None and len(slotInfo.indexFile) > 0:
slotInfo.indexFile = self.moveToModelDir(slotInfo.indexFile, slotDir)
if slotInfo.iconFile is not None and len(slotInfo.iconFile) > 0:
slotInfo.iconFile = self.moveToModelDir(slotInfo.iconFile, slotDir)
json.dump(asdict(slotInfo), open(os.path.join(slotDir, "params.json"), "w"))
self.loadSlots()
@ -197,13 +202,9 @@ class RVC:
self.settings.modelSlots = modelSlots
def update_settings(self, key: str, val: int | float | str):
print("update", key, val)
if key in self.settings.intData:
# 設定前処理
print("update(int)1", type(val), type(self.settings.tran))
val = cast(int, val)
print("update(int)2", type(val), type(self.settings.tran))
print("update(int)3", key, val)
if key == "modelSlotIndex":
if val < 0:
return True
@ -217,9 +218,7 @@ class RVC:
self.prepareModel(val)
# 設定
print("update(int)4", key, val)
setattr(self.settings, key, val)
print("update(int)5", type(val), type(self.settings.tran))
if key == "gpu":
self.deviceManager.setForceTensor(False)
@ -228,7 +227,6 @@ class RVC:
elif key in self.settings.floatData:
setattr(self.settings, key, float(val))
elif key in self.settings.strData:
print("update(str)", key, val)
setattr(self.settings, key, str(val))
if key == "f0Detector" and self.pipeline is not None:
pitchExtractor = PitchExtractorManager.getPitchExtractor(

View File

@ -156,8 +156,19 @@ def downloadModelFiles(sampleInfo: RVCModelSample, useIndex: bool = True):
}
)
iconPath = None
if hasattr(sampleInfo, "icon") and sampleInfo.icon != "":
iconPath = os.path.join(TMP_DIR, os.path.basename(sampleInfo.icon))
downloadParams.append(
{
"url": sampleInfo.icon,
"saveTo": iconPath,
"position": 2,
}
)
print("[Voice Changer] Downloading model files...", end="")
with ThreadPoolExecutor() as pool:
pool.map(download_no_tqdm, downloadParams)
print("")
return modelPath, indexPath
return modelPath, indexPath, iconPath