mirror of
https://github.com/w-okada/voice-changer.git
synced 2025-01-23 13:35:12 +03:00
WIP: new GUI
This commit is contained in:
parent
bc5fd76cc2
commit
8c21d4e5ff
153
client/demo/dist/assets/gui_settings/RVC.json
vendored
153
client/demo/dist/assets/gui_settings/RVC.json
vendored
@ -16,70 +16,8 @@
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"serverControl": [
|
||||
{
|
||||
"name": "startButton",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "performance",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "modelSwitch",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "serverOperation",
|
||||
"options": {
|
||||
"showDownload": true,
|
||||
"showExportOnnx": true,
|
||||
"showReload": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"modelSetting": [
|
||||
{
|
||||
"name": "modelUploaderv2",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "modelSlotRow2",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "commonFileSelect",
|
||||
"options": {
|
||||
"title": "Model(.onnx, .pt, pth)",
|
||||
"acceptExtentions": ["onnx", "pth", "pt"],
|
||||
"fileKind": "rvcModel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commonFileSelect",
|
||||
"options": {
|
||||
"title": "index(.index)",
|
||||
"acceptExtentions": ["index"],
|
||||
"fileKind": "rvcIndex"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sampleModelSelect",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "sampleDownloadControlRow",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "defaultTuneRow2",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "modelUploadButtonRow2",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"serverControl": [],
|
||||
"modelSetting": [],
|
||||
"lab": [
|
||||
{
|
||||
"name": "mergeLab",
|
||||
@ -87,72 +25,10 @@
|
||||
}
|
||||
],
|
||||
|
||||
"deviceSetting": [
|
||||
{
|
||||
"name": "audioDeviceMode",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "audioInput",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "audioOutput",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"qualityControl": [
|
||||
{
|
||||
"name": "noiseControl",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "gainControl",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "f0Detector",
|
||||
"options": {
|
||||
"detectors": ["dio", "harvest", "crepe"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "divider",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "analyzer",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"speakerSetting": [
|
||||
{
|
||||
"name": "tune",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "indexRatio",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "silentThreshold",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"converterSetting": [
|
||||
{
|
||||
"name": "inputChunkNum",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "extraDataLength",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "gpu",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"deviceSetting": [],
|
||||
"qualityControl": [],
|
||||
"speakerSetting": [],
|
||||
"converterSetting": [],
|
||||
"advancedSetting": [
|
||||
{
|
||||
"name": "protocol",
|
||||
@ -182,6 +58,23 @@
|
||||
"name": "protect",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"modelSlotControl": [
|
||||
{
|
||||
"name": "modelSlotArea",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "characterArea",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "configArea",
|
||||
"options": {
|
||||
"detectors": ["dio", "harvest", "crepe"],
|
||||
"inputChunkNums": [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
1
client/demo/dist/assets/icons/folder.svg
vendored
Normal file
1
client/demo/dist/assets/icons/folder.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
After Width: | Height: | Size: 311 B |
BIN
client/demo/dist/assets/icons/human.png
vendored
Normal file
BIN
client/demo/dist/assets/icons/human.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
114
client/demo/dist/index.js
vendored
114
client/demo/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@ -16,70 +16,8 @@
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"serverControl": [
|
||||
{
|
||||
"name": "startButton",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "performance",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "modelSwitch",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "serverOperation",
|
||||
"options": {
|
||||
"showDownload": true,
|
||||
"showExportOnnx": true,
|
||||
"showReload": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"modelSetting": [
|
||||
{
|
||||
"name": "modelUploaderv2",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "modelSlotRow2",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "commonFileSelect",
|
||||
"options": {
|
||||
"title": "Model(.onnx, .pt, pth)",
|
||||
"acceptExtentions": ["onnx", "pth", "pt"],
|
||||
"fileKind": "rvcModel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "commonFileSelect",
|
||||
"options": {
|
||||
"title": "index(.index)",
|
||||
"acceptExtentions": ["index"],
|
||||
"fileKind": "rvcIndex"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sampleModelSelect",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "sampleDownloadControlRow",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "defaultTuneRow2",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "modelUploadButtonRow2",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"serverControl": [],
|
||||
"modelSetting": [],
|
||||
"lab": [
|
||||
{
|
||||
"name": "mergeLab",
|
||||
@ -87,72 +25,10 @@
|
||||
}
|
||||
],
|
||||
|
||||
"deviceSetting": [
|
||||
{
|
||||
"name": "audioDeviceMode",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "audioInput",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "audioOutput",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"qualityControl": [
|
||||
{
|
||||
"name": "noiseControl",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "gainControl",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "f0Detector",
|
||||
"options": {
|
||||
"detectors": ["dio", "harvest", "crepe"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "divider",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "analyzer",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"speakerSetting": [
|
||||
{
|
||||
"name": "tune",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "indexRatio",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "silentThreshold",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"converterSetting": [
|
||||
{
|
||||
"name": "inputChunkNum",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "extraDataLength",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "gpu",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"deviceSetting": [],
|
||||
"qualityControl": [],
|
||||
"speakerSetting": [],
|
||||
"converterSetting": [],
|
||||
"advancedSetting": [
|
||||
{
|
||||
"name": "protocol",
|
||||
@ -182,6 +58,23 @@
|
||||
"name": "protect",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"modelSlotControl": [
|
||||
{
|
||||
"name": "modelSlotArea",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "characterArea",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"name": "configArea",
|
||||
"options": {
|
||||
"detectors": ["dio", "harvest", "crepe"],
|
||||
"inputChunkNums": [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
1
client/demo/public/assets/icons/folder.svg
Normal file
1
client/demo/public/assets/icons/folder.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
After Width: | Height: | Size: 311 B |
BIN
client/demo/public/assets/icons/human.png
Normal file
BIN
client/demo/public/assets/icons/human.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@ -16,6 +16,7 @@ export type AppGuiDemoSetting = {
|
||||
"converterSetting": GuiComponentSetting[],
|
||||
"advancedSetting": GuiComponentSetting[],
|
||||
"lab": GuiComponentSetting[],
|
||||
"modelSlotControl": GuiComponentSetting[],
|
||||
},
|
||||
dialogs: {
|
||||
"license": { title: string, auther: string, contact: string, url: string, license: string }[]
|
||||
@ -54,7 +55,8 @@ const InitialAppGuiDemoSetting: AppGuiDemoSetting = {
|
||||
"speakerSetting": [],
|
||||
"converterSetting": [],
|
||||
"advancedSetting": [],
|
||||
"lab": []
|
||||
"lab": [],
|
||||
"modelSlotControl": []
|
||||
},
|
||||
dialogs: {
|
||||
"license": [{ title: "", auther: "", contact: "", url: "", license: "MIT" }]
|
||||
|
@ -57,6 +57,9 @@ import { SampleModelSelectRow, SampleModelSelectRowProps } from "./components/30
|
||||
import { SampleDownloadControlRow, SampleDownloadControlRowProps } from "./components/301-k_SampleDownloadControl"
|
||||
import { IndexRatioRow, IndexRatioRowProps } from "./components/609_IndexRatioRow copy"
|
||||
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"
|
||||
|
||||
export const catalog: { [key: string]: (props: any) => JSX.Element } = {}
|
||||
|
||||
@ -152,6 +155,10 @@ const initialize = () => {
|
||||
|
||||
|
||||
addToCatalog("mergeLab", (props: MergeLabRowProps) => { return <MergeLabRow {...props} /> })
|
||||
addToCatalog("modelSlotArea", (props: ModelSlotAreaProps) => { return <ModelSlotArea {...props} /> })
|
||||
addToCatalog("characterArea", (props: CharacterAreaProps) => { return <CharacterArea {...props} /> })
|
||||
addToCatalog("configArea", (props: ConfigAreaProps) => { return <ConfigArea {...props} /> })
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react"
|
||||
import React from "react"
|
||||
import { GuiStateProvider } from "./001_GuiStateProvider";
|
||||
import { Dialogs } from "./900_Dialogs";
|
||||
import { TitleArea } from "./100_TitleArea";
|
||||
@ -10,6 +10,7 @@ import { SpeakerSetting } from "./600_SpeakerSetting";
|
||||
import { ConverterSetting } from "./700_ConverterSetting";
|
||||
import { AdvancedSetting } from "./800_AdvancedSetting";
|
||||
import { Lab } from "./a00_Lab";
|
||||
import { ModelSlotControl } from "./b00_ModelSlotControl";
|
||||
|
||||
export const Demo = () => {
|
||||
return (
|
||||
@ -17,6 +18,7 @@ export const Demo = () => {
|
||||
<div className="main-body">
|
||||
<Dialogs />
|
||||
<TitleArea />
|
||||
<ModelSlotControl></ModelSlotControl>
|
||||
<ServerControl />
|
||||
<ModelSetting />
|
||||
<SpeakerSetting />
|
||||
|
@ -23,6 +23,9 @@ export const ServerControl = () => {
|
||||
|
||||
|
||||
const serverControl = 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>
|
||||
|
@ -22,6 +22,9 @@ export const ModelSetting = () => {
|
||||
}, []);
|
||||
|
||||
const modelSetting = 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>
|
||||
|
@ -22,6 +22,9 @@ export const DeviceSetting = () => {
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
@ -22,6 +22,9 @@ export const QualityControl = () => {
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
@ -22,6 +22,9 @@ export const SpeakerSetting = () => {
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
@ -21,7 +21,12 @@ export const ConverterSetting = () => {
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
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>
|
||||
|
@ -21,6 +21,9 @@ export const AdvancedSetting = () => {
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
@ -1,8 +1,5 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useGuiState } from "./001_GuiStateProvider";
|
||||
import { getMessage } from "./messages/MessageBuilder";
|
||||
import { isDesktopApp } from "../../const";
|
||||
import { useAppRoot } from "../../001_provider/001_AppRootProvider";
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider";
|
||||
import { InitialFileUploadSetting, fileSelector } from "@dannadori/voice-changer-client-js";
|
||||
|
||||
@ -26,7 +23,6 @@ export const ModelSlotManagerDialog = () => {
|
||||
const [mode, setMode] = useState<Mode>("localFile")
|
||||
const [fromNetTargetIndex, setFromNetTargetIndex] = useState<number>(0)
|
||||
const [lang, setLang] = useState<string>("All")
|
||||
const [sampleId, setSampleId] = useState<string>("")
|
||||
|
||||
|
||||
/////////////////////////////////////////
|
||||
@ -36,6 +32,9 @@ export const ModelSlotManagerDialog = () => {
|
||||
if (mode != "localFile") {
|
||||
return <></>
|
||||
}
|
||||
if (!serverSetting.serverSetting.modelSlots) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const checkExtention = (filename: string, acceptExtentions: string[]) => {
|
||||
const ext = filename.split('.').pop();
|
||||
@ -154,13 +153,13 @@ export const ModelSlotManagerDialog = () => {
|
||||
} : async (_index: number) => { }
|
||||
|
||||
const fileValueClass = (uploadData?.slot == index) ? "model-slot-detail-row-value-edit" : isRegisterd ? "model-slot-detail-row-value-download" : "model-slot-detail-row-value"
|
||||
const fileValueAction = (uploadData?.slot == index) ? (url: string) => {
|
||||
} : (url: string) => {
|
||||
const fileValueAction = (uploadData?.slot == index) ? (_url: string) => {
|
||||
} : isRegisterd ? (url: string) => {
|
||||
const link = document.createElement("a")
|
||||
link.href = url
|
||||
link.download = url.replace(/^.*[\\\/]/, '')
|
||||
link.click()
|
||||
}
|
||||
} : (_url: string) => { }
|
||||
|
||||
const iconUrl = x.modelFile && x.modelFile.length > 0 ? (x.iconFile && x.iconFile.length > 0 ? x.iconFile : "/assets/icons/noimage.png") : "/assets/icons/blank.png"
|
||||
|
||||
|
29
client/demo/src/components/demo/b00_ModelSlotControl.tsx
Normal file
29
client/demo/src/components/demo/b00_ModelSlotControl.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppRoot } from "../../001_provider/001_AppRootProvider"
|
||||
import { generateComponent } from "./002_ComponentGenerator"
|
||||
|
||||
export const ModelSlotControl = () => {
|
||||
const { appGuiSettingState } = useAppRoot()
|
||||
const componentSettings = appGuiSettingState.appGuiSetting.front.modelSlotControl
|
||||
|
||||
const deviceSetting = useMemo(() => {
|
||||
if (!componentSettings || 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>
|
||||
})
|
||||
return (
|
||||
<>
|
||||
<div className="partition">
|
||||
<div className="partition-content">
|
||||
{components}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return deviceSetting
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "../001_GuiStateProvider"
|
||||
|
||||
export type ModelSlotAreaProps = {
|
||||
}
|
||||
|
||||
|
||||
export const ModelSlotArea = (_props: ModelSlotAreaProps) => {
|
||||
const { serverSetting, getInfo } = useAppState()
|
||||
const guiState = useGuiState()
|
||||
const modelTiles = useMemo(() => {
|
||||
if (!serverSetting.serverSetting.modelSlots) {
|
||||
return []
|
||||
}
|
||||
return serverSetting.serverSetting.modelSlots.map((x, index) => {
|
||||
if (x.modelFile.length == 0) {
|
||||
return null
|
||||
}
|
||||
const tileContainerClass = index == serverSetting.serverSetting.modelSlotIndex ? "model-slot-tile-container-selected" : "model-slot-tile-container"
|
||||
const name = x.name.length > 8 ? x.name.substring(0, 7) + "..." : x.name
|
||||
const iconElem = x.iconFile.length > 0 ?
|
||||
<img className="model-slot-tile-icon" src={x.iconFile} alt={x.name} /> :
|
||||
<div className="model-slot-tile-icon-no-entry">no entry.</div>
|
||||
|
||||
const clickAction = async () => {
|
||||
const dummyModelSlotIndex = (Math.floor(Date.now() / 1000)) * 1000 + index
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, modelSlotIndex: dummyModelSlotIndex })
|
||||
setTimeout(() => { // quick hack
|
||||
getInfo()
|
||||
}, 1000 * 2)
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={index} className={tileContainerClass} onClick={clickAction}>
|
||||
<div className="model-slot-tile-icon-div">
|
||||
{iconElem}
|
||||
</div>
|
||||
<div className="model-slot-tile-dscription">
|
||||
{name}
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}).filter(x => x != null)
|
||||
}, [serverSetting.serverSetting.modelSlots, serverSetting.serverSetting.modelSlotIndex])
|
||||
|
||||
|
||||
const modelSlotArea = useMemo(() => {
|
||||
const onModelSlotEditClicked = () => {
|
||||
guiState.stateControls.showModelSlotManagerCheckbox.updateState(true)
|
||||
}
|
||||
return (
|
||||
<div className="model-slot-area">
|
||||
<div className="model-slot-panel">
|
||||
<div className="model-slot-tiles-container">{modelTiles}</div>
|
||||
<div className="model-slot-buttons">
|
||||
<div className="model-slot-button" onClick={onModelSlotEditClicked}>
|
||||
edit
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [modelTiles])
|
||||
|
||||
return modelSlotArea
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { useAppState } from "../../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "../001_GuiStateProvider"
|
||||
import { OnnxExporterInfo } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
export type CharacterAreaProps = {
|
||||
}
|
||||
|
||||
|
||||
export const CharacterArea = (_props: CharacterAreaProps) => {
|
||||
const { serverSetting, clientSetting, initializedRef, volume, bufferingTime, performance } = useAppState()
|
||||
const guiState = useGuiState()
|
||||
|
||||
const selected = useMemo(() => {
|
||||
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
|
||||
return
|
||||
}
|
||||
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
|
||||
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const vol = document.getElementById("status-vol") as HTMLSpanElement
|
||||
const buf = document.getElementById("status-buf") as HTMLSpanElement
|
||||
const res = document.getElementById("status-res") as HTMLSpanElement
|
||||
if (!vol || !buf || !res) {
|
||||
return
|
||||
}
|
||||
vol.innerText = volume.toFixed(4)
|
||||
buf.innerText = bufferingTime.toString()
|
||||
res.innerText = performance.responseTime.toString()
|
||||
|
||||
}, [volume, bufferingTime, performance])
|
||||
|
||||
const portrait = useMemo(() => {
|
||||
if (!selected) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const icon = selected.iconFile.length > 0 ? selected.iconFile : "./assets/icons/human.png"
|
||||
const selectedTermOfUseUrlLink = selected.termsOfUseUrl ? <a href={selected.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="portrait-area-terms-of-use-link">[terms of use]</a> : <></>
|
||||
|
||||
|
||||
return (
|
||||
<div className="portrait-area">
|
||||
<div className="portrait-container">
|
||||
<img className="portrait" src={icon} alt={selected.name} />
|
||||
<div className="portrait-area-status">
|
||||
<p>vol: <span id="status-vol">0</span></p>
|
||||
<p>buf: <span id="status-buf">0</span> ms</p>
|
||||
<p>res: <span id="status-res">0</span> ms</p>
|
||||
</div>
|
||||
<div className="portrait-area-terms-of-use">
|
||||
{selectedTermOfUseUrlLink}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [selected])
|
||||
|
||||
|
||||
const [startWithAudioContextCreate, setStartWithAudioContextCreate] = useState<boolean>(false)
|
||||
useEffect(() => {
|
||||
if (!startWithAudioContextCreate) {
|
||||
return
|
||||
}
|
||||
guiState.setIsConverting(true)
|
||||
clientSetting.start()
|
||||
}, [startWithAudioContextCreate])
|
||||
|
||||
const startControl = useMemo(() => {
|
||||
const onStartClicked = async () => {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 0) {
|
||||
if (!initializedRef.current) {
|
||||
while (true) {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, 500)
|
||||
})
|
||||
if (initializedRef.current) {
|
||||
break
|
||||
}
|
||||
}
|
||||
setStartWithAudioContextCreate(true)
|
||||
} else {
|
||||
guiState.setIsConverting(true)
|
||||
await clientSetting.start()
|
||||
}
|
||||
} else {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioStated: 1 })
|
||||
guiState.setIsConverting(true)
|
||||
}
|
||||
}
|
||||
const onStopClicked = async () => {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 0) {
|
||||
guiState.setIsConverting(false)
|
||||
await clientSetting.stop()
|
||||
} else {
|
||||
guiState.setIsConverting(false)
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioStated: 0 })
|
||||
}
|
||||
}
|
||||
const startClassName = guiState.isConverting ? "character-area-control-button-active" : "character-area-control-button-stanby"
|
||||
const stopClassName = guiState.isConverting ? "character-area-control-button-stanby" : "character-area-control-button-active"
|
||||
|
||||
return (
|
||||
<div className="character-area-control">
|
||||
<div className="character-area-control-buttons">
|
||||
<div onClick={onStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
guiState.isConverting,
|
||||
clientSetting.start,
|
||||
clientSetting.stop,
|
||||
serverSetting.serverSetting,
|
||||
serverSetting.updateServerSettings
|
||||
])
|
||||
|
||||
const gainControl = useMemo(() => {
|
||||
const currentInputGain = serverSetting.serverSetting.enableServerAudio == 0 ? clientSetting.clientSetting.inputGain : serverSetting.serverSetting.serverInputAudioGain
|
||||
const inputValueUpdatedAction = serverSetting.serverSetting.enableServerAudio == 0 ?
|
||||
async (val: number) => {
|
||||
await clientSetting.updateClientSetting({ ...clientSetting.clientSetting, inputGain: val })
|
||||
} :
|
||||
async (val: number) => {
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputAudioGain: val })
|
||||
}
|
||||
|
||||
const currentOutputGain = serverSetting.serverSetting.enableServerAudio == 0 ? clientSetting.clientSetting.outputGain : serverSetting.serverSetting.serverOutputAudioGain
|
||||
const outputValueUpdatedAction = serverSetting.serverSetting.enableServerAudio == 0 ?
|
||||
async (val: number) => {
|
||||
await clientSetting.updateClientSetting({ ...clientSetting.clientSetting, outputGain: val })
|
||||
} :
|
||||
async (val: number) => {
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputAudioGain: val })
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="character-area-control">
|
||||
<div className="character-area-control-title">
|
||||
GAIN:
|
||||
</div>
|
||||
<div className="character-area-control-field">
|
||||
<div className="character-area-slider-control">
|
||||
<span className="character-area-slider-control-kind">in</span>
|
||||
<span className="character-area-slider-control-slider">
|
||||
<input type="range" min="0.1" max="10.0" step="0.1" value={currentInputGain} onChange={(e) => {
|
||||
inputValueUpdatedAction(Number(e.target.value))
|
||||
}}></input>
|
||||
</span>
|
||||
<span className="character-area-slider-control-val">{currentInputGain}</span>
|
||||
</div>
|
||||
|
||||
<div className="character-area-slider-control">
|
||||
<span className="character-area-slider-control-kind">out</span>
|
||||
<span className="character-area-slider-control-slider">
|
||||
<input type="range" min="0.1" max="10.0" step="0.1" value={currentOutputGain} onChange={(e) => {
|
||||
outputValueUpdatedAction(Number(e.target.value))
|
||||
}}></input>
|
||||
</span>
|
||||
<span className="character-area-slider-control-val">{currentOutputGain}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting, clientSetting.clientSetting, clientSetting.updateClientSetting, serverSetting.updateServerSettings])
|
||||
|
||||
const tuningCotrol = useMemo(() => {
|
||||
const currentTuning = serverSetting.serverSetting.tran
|
||||
const tranValueUpdatedAction = async (val: number) => {
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, tran: val })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="character-area-control">
|
||||
<div className="character-area-control-title">
|
||||
TUNE:
|
||||
</div>
|
||||
<div className="character-area-control-field">
|
||||
<div className="character-area-slider-control">
|
||||
<span className="character-area-slider-control-kind"></span>
|
||||
<span className="character-area-slider-control-slider">
|
||||
<input type="range" min="-50" max="50" step="1" value={currentTuning} onChange={(e) => {
|
||||
tranValueUpdatedAction(Number(e.target.value))
|
||||
}}></input>
|
||||
</span>
|
||||
<span className="character-area-slider-control-val">{currentTuning}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting, clientSetting.updateClientSetting])
|
||||
|
||||
|
||||
const indexCotrol = useMemo(() => {
|
||||
const currentIndexRatio = serverSetting.serverSetting.indexRatio
|
||||
const indexRatioValueUpdatedAction = async (val: number) => {
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, indexRatio: val })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="character-area-control">
|
||||
<div className="character-area-control-title">
|
||||
INDEX:
|
||||
</div>
|
||||
<div className="character-area-control-field">
|
||||
<div className="character-area-slider-control">
|
||||
<span className="character-area-slider-control-kind"></span>
|
||||
<span className="character-area-slider-control-slider">
|
||||
<input type="range" min="0" max="1" step="0.1" value={currentIndexRatio} onChange={(e) => {
|
||||
indexRatioValueUpdatedAction(Number(e.target.value))
|
||||
}}></input>
|
||||
</span>
|
||||
<span className="character-area-slider-control-val">{currentIndexRatio}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting, clientSetting.updateClientSetting])
|
||||
|
||||
|
||||
const modelSlotControl = useMemo(() => {
|
||||
if (!selected) {
|
||||
return <></>
|
||||
}
|
||||
const onUpdateDefaultClicked = async () => {
|
||||
await serverSetting.updateModelDefault()
|
||||
}
|
||||
|
||||
const onnxExportButtonAction = async () => {
|
||||
if (guiState.isConverting) {
|
||||
alert("cannot export onnx when voice conversion is enabled")
|
||||
return
|
||||
}
|
||||
|
||||
document.getElementById("dialog")?.classList.add("dialog-container-show")
|
||||
guiState.stateControls.showWaitingCheckbox.updateState(true)
|
||||
const res = await serverSetting.getOnnx() as OnnxExporterInfo
|
||||
const a = document.createElement("a")
|
||||
a.href = res.path
|
||||
a.download = res.filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
guiState.stateControls.showWaitingCheckbox.updateState(false)
|
||||
|
||||
}
|
||||
|
||||
const exportOnnx = selected.modelFile.endsWith("pth") ? (
|
||||
<div className="character-area-buutton" onClick={onnxExportButtonAction}>export onnx</div>
|
||||
) : <></>
|
||||
return (
|
||||
<div className="character-area-control">
|
||||
<div className="character-area-control-title">
|
||||
|
||||
</div>
|
||||
<div className="character-area-control-field">
|
||||
<div className="character-area-buuttons">
|
||||
<div className="character-area-buutton" onClick={onUpdateDefaultClicked}>save default</div>
|
||||
{exportOnnx}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [selected, serverSetting.getOnnx, serverSetting.updateModelDefault])
|
||||
|
||||
const characterArea = useMemo(() => {
|
||||
return (
|
||||
<div className="character-area">
|
||||
{portrait}
|
||||
<div className="character-area-control-area">
|
||||
{startControl}
|
||||
{gainControl}
|
||||
{tuningCotrol}
|
||||
{indexCotrol}
|
||||
{modelSlotControl}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [portrait, startControl, gainControl, tuningCotrol, modelSlotControl])
|
||||
|
||||
return characterArea
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../../001_provider/001_AppStateProvider"
|
||||
import { F0Detector, } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
export type QualityAreaProps = {
|
||||
detectors: string[]
|
||||
}
|
||||
|
||||
export const QualityArea = (props: QualityAreaProps) => {
|
||||
const { clientSetting, serverSetting } = useAppState()
|
||||
const qualityArea = useMemo(() => {
|
||||
if (!serverSetting.updateServerSettings || !clientSetting.updateClientSetting || !serverSetting.serverSetting || !clientSetting.clientSetting) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<div className="config-sub-area">
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">NOISE:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<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 })
|
||||
}} /> <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 })
|
||||
}} /> <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 })
|
||||
}} /> <span>Sup2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">F0 Det.:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<select className="body-select" value={serverSetting.serverSetting.f0Detector} onChange={(e) => {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, f0Detector: e.target.value as F0Detector })
|
||||
}}>
|
||||
{
|
||||
Object.values(props.detectors).map(x => {
|
||||
//@ts-ignore
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">S.Thresh.:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-slider-control">
|
||||
<span className="config-sub-area-slider-control-kind"></span>
|
||||
<span className="config-sub-area-slider-control-slider">
|
||||
<input type="range" className="config-sub-area-slider-control-slider" min="0.00000" max="0.001" step="0.00001" value={serverSetting.serverSetting.silentThreshold || 0} onChange={(e) => {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, silentThreshold: Number(e.target.value) })
|
||||
}}></input>
|
||||
</span>
|
||||
<span className="config-sub-area-slider-control-val">{serverSetting.serverSetting.silentThreshold}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting, clientSetting.clientSetting, serverSetting.updateServerSettings, clientSetting.updateClientSetting])
|
||||
|
||||
|
||||
return qualityArea
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../../001_provider/001_AppStateProvider"
|
||||
|
||||
export type ConvertProps = {
|
||||
inputChunkNums: number[]
|
||||
}
|
||||
|
||||
|
||||
export const ConvertArea = (props: ConvertProps) => {
|
||||
const { clientSetting, serverSetting, workletNodeSetting } = useAppState()
|
||||
|
||||
const convertArea = useMemo(() => {
|
||||
let nums: number[]
|
||||
if (!props.inputChunkNums) {
|
||||
nums = [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048]
|
||||
} else {
|
||||
nums = props.inputChunkNums
|
||||
}
|
||||
|
||||
const gpusEntry = [...serverSetting.serverSetting.gpus]
|
||||
gpusEntry.push({
|
||||
id: -1,
|
||||
name: "cpu",
|
||||
memory: 0
|
||||
})
|
||||
return (
|
||||
<div className="config-sub-area">
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">CHUNK:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<select className="body-select" value={workletNodeSetting.workletNodeSetting.inputChunkNum} onChange={(e) => {
|
||||
workletNodeSetting.updateWorkletNodeSetting({ ...workletNodeSetting.workletNodeSetting, inputChunkNum: Number(e.target.value) })
|
||||
workletNodeSetting.trancateBuffer()
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverReadChunkSize: Number(e.target.value) })
|
||||
}}>
|
||||
{
|
||||
nums.map(x => {
|
||||
return <option key={x} value={x}>{x} ({(x * 128 * 1000 / 48000).toFixed(1)} ms, {x * 128})</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">EXTRA:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<select className="body-select" value={serverSetting.serverSetting.extraConvertSize} onChange={(e) => {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, extraConvertSize: Number(e.target.value) })
|
||||
workletNodeSetting.trancateBuffer()
|
||||
}}>
|
||||
{
|
||||
[1024 * 4, 1024 * 8, 1024 * 16, 1024 * 32, 1024 * 64, 1024 * 128].map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">GPU:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<select className="body-select" value={serverSetting.serverSetting.gpu} onChange={(e) => {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, gpu: Number(e.target.value) })
|
||||
}}>
|
||||
{
|
||||
gpusEntry.map(x => {
|
||||
return <option key={x.id} value={x.id}>{x.name}({(x.memory / 1024 / 1024 / 1024).toFixed(0)}GB) </option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting, clientSetting.clientSetting, workletNodeSetting.workletNodeSetting, serverSetting.updateServerSettings, clientSetting.updateClientSetting, workletNodeSetting.updateWorkletNodeSetting])
|
||||
|
||||
|
||||
return convertArea
|
||||
}
|
416
client/demo/src/components/demo/components2/102-3_DeviceArea.tsx
Normal file
416
client/demo/src/components/demo/components2/102-3_DeviceArea.tsx
Normal file
@ -0,0 +1,416 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useAppState } from "../../../001_provider/001_AppStateProvider"
|
||||
import { fileSelectorAsDataURL, useIndexedDB, } from "@dannadori/voice-changer-client-js"
|
||||
import { useGuiState } from "../001_GuiStateProvider"
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const"
|
||||
import { useAppRoot } from "../../../001_provider/001_AppRootProvider"
|
||||
|
||||
export type DeviceAreaProps = {
|
||||
}
|
||||
|
||||
export const DeviceArea = (_props: DeviceAreaProps) => {
|
||||
const { clientSetting, serverSetting, workletNodeSetting, audioContext, setAudioOutputElementId, initializedRef } = useAppState()
|
||||
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo } = useGuiState()
|
||||
const [hostApi, setHostApi] = useState<string>("ALL")
|
||||
const audioSrcNode = useRef<MediaElementAudioSourceNode>()
|
||||
const { appGuiSettingState } = useAppRoot()
|
||||
const clientType = appGuiSettingState.appGuiSetting.id
|
||||
const { getItem, setItem } = useIndexedDB({ clientType: clientType })
|
||||
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false)
|
||||
|
||||
// (1) Audio Mode
|
||||
const deviceModeRow = useMemo(() => {
|
||||
const enableServerAudio = serverSetting.serverSetting.enableServerAudio
|
||||
const clientChecked = enableServerAudio == 1 ? false : true
|
||||
const serverChecked = enableServerAudio == 1 ? true : false
|
||||
|
||||
const onDeviceModeChanged = (val: number) => {
|
||||
if (isConverting) {
|
||||
alert("cannot change mode when voice conversion is enabled")
|
||||
return
|
||||
}
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, enableServerAudio: val })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title">AUDIO:</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-noise-container">
|
||||
<div className="config-sub-area-noise-checkbox-container">
|
||||
<input type="radio" id="client-device" name="device-mode" checked={clientChecked} onChange={() => { onDeviceModeChanged(0) }} /> <label htmlFor="client-device">client</label>
|
||||
</div>
|
||||
<div className="config-sub-area-noise-checkbox-container">
|
||||
<input className="left-padding-1" type="radio" id="server-device" name="device-mode" checked={serverChecked} onChange={() => { onDeviceModeChanged(1) }} />
|
||||
<label htmlFor="server-device">server</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting, serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
|
||||
// (2) Audio Input
|
||||
// キャッシュの設定は反映(たぶん、設定操作の時も起動していしまう。が問題は起こらないはず)
|
||||
useEffect(() => {
|
||||
if (typeof clientSetting.clientSetting.audioInput == "string") {
|
||||
if (inputAudioDeviceInfo.find(x => {
|
||||
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
|
||||
return x.deviceId == clientSetting.clientSetting.audioInput
|
||||
})) {
|
||||
setAudioInputForGUI(clientSetting.clientSetting.audioInput)
|
||||
}
|
||||
}
|
||||
}, [inputAudioDeviceInfo, clientSetting.clientSetting.audioInput])
|
||||
|
||||
// (2-1) クライアント
|
||||
const clientAudioInputRow = useMemo(() => {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 1) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<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) => {
|
||||
setAudioInputForGUI(e.target.value)
|
||||
if (audioInputForGUI != "file") {
|
||||
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: e.target.value })
|
||||
}
|
||||
}}>
|
||||
{
|
||||
inputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [clientSetting.updateClientSetting, clientSetting.clientSetting, inputAudioDeviceInfo, audioInputForGUI, serverSetting.serverSetting.enableServerAudio])
|
||||
|
||||
|
||||
// (2-2) サーバ
|
||||
const serverAudioInputRow = useMemo(() => {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 0) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const devices = serverSetting.serverSetting.serverAudioInputDevices
|
||||
const hostAPIs = new Set(devices.map(x => { return x.hostAPI }))
|
||||
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => { return <option value={x} key={index} >{x}</option> })
|
||||
|
||||
const filteredDevice = devices.map((x, index) => {
|
||||
if (hostApi != "ALL" && x.hostAPI != hostApi) {
|
||||
return null
|
||||
}
|
||||
return <option value={x.index} key={index}>[{x.hostAPI}]{x.name}</option>
|
||||
}).filter(x => x != null)
|
||||
|
||||
return (
|
||||
<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">
|
||||
<div className="config-sub-area-control-field-auido-io">
|
||||
<select className="config-sub-area-control-field-auido-io-filter" name="kinds" id="kinds" value={hostApi} onChange={(e) => { setHostApi(e.target.value) }}>
|
||||
{hostAPIOptions}
|
||||
</select>
|
||||
<select className="config-sub-area-control-field-auido-io-select" value={serverSetting.serverSetting.serverInputDeviceId} onChange={(e) => {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputDeviceId: Number(e.target.value) })
|
||||
|
||||
}}>
|
||||
{filteredDevice}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}, [clientSetting.updateClientSetting, clientSetting.clientSetting, inputAudioDeviceInfo, audioInputForGUI, serverSetting.serverSetting.enableServerAudio])
|
||||
|
||||
// (2-3) File
|
||||
useEffect(() => {
|
||||
[AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
|
||||
const audio = document.getElementById(x) as HTMLAudioElement
|
||||
if (audio) {
|
||||
audio.volume = fileInputEchoback ? 1 : 0
|
||||
}
|
||||
})
|
||||
}, [fileInputEchoback])
|
||||
|
||||
const audioInputMediaRow = useMemo(() => {
|
||||
if (audioInputForGUI != "file" || serverSetting.serverSetting.enableServerAudio == 1) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const onFileLoadClicked = async () => {
|
||||
const url = await fileSelectorAsDataURL("")
|
||||
|
||||
// input stream for client.
|
||||
const audio = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED) as HTMLAudioElement
|
||||
audio.pause()
|
||||
audio.srcObject = null
|
||||
audio.src = url
|
||||
await audio.play()
|
||||
if (!audioSrcNode.current) {
|
||||
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
if (audioSrcNode.current.mediaElement != audio) {
|
||||
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
|
||||
const dst = audioContext.createMediaStreamDestination()
|
||||
audioSrcNode.current.connect(dst)
|
||||
clientSetting.updateClientSetting({ ...clientSetting.clientSetting, audioInput: dst.stream })
|
||||
|
||||
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement
|
||||
audio_echo.srcObject = dst.stream
|
||||
audio_echo.play()
|
||||
audio_echo.volume = 0
|
||||
setFileInputEchoback(false)
|
||||
|
||||
// original stream to play.
|
||||
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement
|
||||
audio_org.src = url
|
||||
audio_org.pause()
|
||||
}
|
||||
|
||||
const echobackClass = fileInputEchoback ? "config-sub-area-control-field-wav-file-echoback-button-active" : "config-sub-area-control-field-wav-file-echoback-button"
|
||||
return (
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-control-field-wav-file left-padding-1">
|
||||
<div className="config-sub-area-control-field-wav-file-audio-container">
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio>
|
||||
<audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls controlsList="nodownload noplaybackrate"></audio>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
|
||||
</div>
|
||||
<div>
|
||||
<img className="config-sub-area-control-field-wav-file-folder" src="./assets/icons/folder.svg" onClick={onFileLoadClicked} />
|
||||
</div>
|
||||
<div className={echobackClass} onClick={() => { setFileInputEchoback(!fileInputEchoback) }}>
|
||||
echo{fileInputEchoback}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [audioInputForGUI, fileInputEchoback, serverSetting.serverSetting])
|
||||
|
||||
|
||||
|
||||
|
||||
// (3) Audio Output
|
||||
useEffect(() => {
|
||||
const loadCache = async () => {
|
||||
const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
|
||||
if (key) {
|
||||
setAudioOutputForGUI(key as string)
|
||||
}
|
||||
}
|
||||
loadCache()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const setAudioOutput = async () => {
|
||||
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
||||
|
||||
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
|
||||
const audio = document.getElementById(x) as HTMLAudioElement
|
||||
if (audio) {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 1) {
|
||||
|
||||
// Server Audio を使う場合はElementから音は出さない。
|
||||
audio.volume = 0
|
||||
} else if (audioOutputForGUI == "none") {
|
||||
// @ts-ignore
|
||||
audio.setSinkId("")
|
||||
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
|
||||
audio.volume = 0
|
||||
} else {
|
||||
audio.volume = 0
|
||||
}
|
||||
} else {
|
||||
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" })
|
||||
const found = audioOutputs.some(x => { return x.deviceId == audioOutputForGUI })
|
||||
if (found) {
|
||||
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。!?
|
||||
audio.setSinkId(audioOutputForGUI)
|
||||
} else {
|
||||
console.warn("No audio output device. use default")
|
||||
}
|
||||
|
||||
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
|
||||
audio.volume = fileInputEchoback ? 1 : 0
|
||||
} else {
|
||||
audio.volume = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
setAudioOutput()
|
||||
}, [audioOutputForGUI, fileInputEchoback, serverSetting.serverSetting.enableServerAudio])
|
||||
|
||||
|
||||
// (3-1) クライアント
|
||||
const clientAudioOutputRow = useMemo(() => {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 1) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title left-padding-1">output</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<select className="body-select" value={audioOutputForGUI} onChange={(e) => {
|
||||
setAudioOutputForGUI(e.target.value)
|
||||
setItem(INDEXEDDB_KEY_AUDIO_OUTPUT, e.target.value)
|
||||
}}>
|
||||
{
|
||||
outputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioOutputForGUI])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("initializedRef.current", initializedRef.current)
|
||||
setAudioOutputElementId(AUDIO_ELEMENT_FOR_PLAY_RESULT)
|
||||
}, [initializedRef.current])
|
||||
|
||||
// (3-2) サーバ
|
||||
const serverAudioOutputRow = useMemo(() => {
|
||||
if (serverSetting.serverSetting.enableServerAudio == 0) {
|
||||
return <></>
|
||||
}
|
||||
const devices = serverSetting.serverSetting.serverAudioOutputDevices
|
||||
const hostAPIs = new Set(devices.map(x => { return x.hostAPI }))
|
||||
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => { return <option value={x} key={index} >{x}</option> })
|
||||
|
||||
// const filteredDevice = devices.filter(x => { return x.hostAPI == hostApi || hostApi == "" }).map((x, index) => { return <option value={x.index} key={index}>{x.name}</option> })
|
||||
const filteredDevice = devices.map((x, index) => {
|
||||
const className = (x.hostAPI == hostApi || hostApi == "") ? "select-option-red" : ""
|
||||
return <option className={className} value={x.index} key={index}>[{x.hostAPI}]{x.name}</option>
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title left-padding-1">output</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-control-field-auido-io">
|
||||
<select className="config-sub-area-control-field-auido-io-filter" name="kinds" id="kinds" value={hostApi} onChange={(e) => { setHostApi(e.target.value) }}>
|
||||
{hostAPIOptions}
|
||||
</select>
|
||||
<select className="config-sub-area-control-field-auido-io-select" value={serverSetting.serverSetting.serverOutputDeviceId} onChange={(e) => {
|
||||
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputDeviceId: Number(e.target.value) })
|
||||
|
||||
}}>
|
||||
{filteredDevice}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [hostApi, serverSetting.serverSetting, serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
|
||||
// (4) レコーダー
|
||||
const outputRecorderRow = useMemo(() => {
|
||||
const onOutputRecordStartClicked = async () => {
|
||||
setOutputRecordingStarted(true)
|
||||
await workletNodeSetting.startOutputRecording()
|
||||
}
|
||||
const onOutputRecordStopClicked = async () => {
|
||||
setOutputRecordingStarted(false)
|
||||
const record = await workletNodeSetting.stopOutputRecording()
|
||||
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"
|
||||
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 onClick={onOutputRecordStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onOutputRecordStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}, [outputRecordingStarted, workletNodeSetting])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="config-sub-area">
|
||||
{deviceModeRow}
|
||||
{clientAudioInputRow}
|
||||
{serverAudioInputRow}
|
||||
{audioInputMediaRow}
|
||||
{clientAudioOutputRow}
|
||||
{serverAudioOutputRow}
|
||||
|
||||
{outputRecorderRow}
|
||||
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const downloadRecord = (data: Float32Array) => {
|
||||
|
||||
const writeString = (view: DataView, offset: number, string: string) => {
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
};
|
||||
|
||||
const floatTo16BitPCM = (output: DataView, offset: number, input: Float32Array) => {
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]));
|
||||
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
||||
}
|
||||
};
|
||||
|
||||
const buffer = new ArrayBuffer(44 + data.length * 2);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
// https://www.youfit.co.jp/archives/1418
|
||||
writeString(view, 0, 'RIFF'); // RIFFヘッダ
|
||||
view.setUint32(4, 32 + data.length * 2, true); // これ以降のファイルサイズ
|
||||
writeString(view, 8, 'WAVE'); // WAVEヘッダ
|
||||
writeString(view, 12, 'fmt '); // fmtチャンク
|
||||
view.setUint32(16, 16, true); // fmtチャンクのバイト数
|
||||
view.setUint16(20, 1, true); // フォーマットID
|
||||
view.setUint16(22, 1, true); // チャンネル数
|
||||
view.setUint32(24, 48000, true); // サンプリングレート
|
||||
view.setUint32(28, 48000 * 2, true); // データ速度
|
||||
view.setUint16(32, 2, true); // ブロックサイズ
|
||||
view.setUint16(34, 16, true); // サンプルあたりのビット数
|
||||
writeString(view, 36, 'data'); // dataチャンク
|
||||
view.setUint32(40, data.length * 2, true); // 波形データのバイト数
|
||||
floatTo16BitPCM(view, 44, data); // 波形データ
|
||||
const audioBlob = new Blob([view], { type: 'audio/wav' });
|
||||
|
||||
const url = URL.createObjectURL(audioBlob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `output.wav`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { useAppState } from "../../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "../001_GuiStateProvider"
|
||||
import { AUDIO_ELEMENT_FOR_SAMPLING_INPUT, AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT } from "../../../const"
|
||||
|
||||
export type RecorderAreaProps = {
|
||||
}
|
||||
|
||||
export const RecorderArea = (_props: RecorderAreaProps) => {
|
||||
const { serverSetting, workletNodeSetting } = useAppState()
|
||||
const { audioOutputForAnalyzer, setAudioOutputForAnalyzer, outputAudioDeviceInfo } = useGuiState()
|
||||
|
||||
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 () => {
|
||||
setServerIORecording(true)
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, recordIO: 1 })
|
||||
}
|
||||
const onServerIORecordStopClicked = async () => {
|
||||
setServerIORecording(false)
|
||||
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, recordIO: 0 })
|
||||
|
||||
// set wav (input)
|
||||
const wavInput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_INPUT) as HTMLAudioElement
|
||||
wavInput.src = "/tmp/in.wav?" + new Date().getTime()
|
||||
wavInput.controls = true
|
||||
// @ts-ignore
|
||||
wavInput.setSinkId(audioOutputForAnalyzer)
|
||||
|
||||
// set wav (output)
|
||||
const wavOutput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT) as HTMLAudioElement
|
||||
wavOutput.src = "/tmp/out.wav?" + new Date().getTime()
|
||||
wavOutput.controls = true
|
||||
// @ts-ignore
|
||||
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"
|
||||
return (
|
||||
<>
|
||||
<div className="config-sub-area-control">
|
||||
<div className="config-sub-area-control-title-long">ServerIO Analyzer</div>
|
||||
</div>
|
||||
|
||||
<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 onClick={onServerIORecordStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onServerIORecordStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="config-sub-area-control left-padding-1">
|
||||
<div className="config-sub-area-control-title">dev</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-control-field-auido-io">
|
||||
<select className="body-select" value={audioOutputForAnalyzer} onChange={(e) => {
|
||||
setAudioOutputForAnalyzer(e.target.value)
|
||||
const wavInput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_INPUT) as HTMLAudioElement
|
||||
const wavOutput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT) as HTMLAudioElement
|
||||
//@ts-ignore
|
||||
wavInput.setSinkId(e.target.value)
|
||||
//@ts-ignore
|
||||
wavOutput.setSinkId(e.target.value)
|
||||
}}>
|
||||
{
|
||||
outputAudioDeviceInfo.map(x => {
|
||||
if (x.deviceId == "none") {
|
||||
return null
|
||||
}
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
}).filter(x => { return x != null })
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="config-sub-area-control left-padding-1">
|
||||
<div className="config-sub-area-control-title">in</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-control-field-wav-file">
|
||||
<div className="config-sub-area-control-field-wav-file-audio-container">
|
||||
<audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_SAMPLING_INPUT} controls></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="config-sub-area-control left-padding-1">
|
||||
<div className="config-sub-area-control-title">out</div>
|
||||
<div className="config-sub-area-control-field">
|
||||
<div className="config-sub-area-control-field-wav-file">
|
||||
<div className="config-sub-area-control-field-wav-file-audio-container">
|
||||
<audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT} controls></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
}, [serverIORecording, workletNodeSetting])
|
||||
|
||||
return (
|
||||
<div className="config-sub-area">
|
||||
{serverIORecorderRow}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { QualityArea } from "./102-1_QualityArea"
|
||||
import { ConvertArea } from "./102-2_ConvertArea"
|
||||
import { DeviceArea } from "./102-3_DeviceArea"
|
||||
import { RecorderArea } from "./102-4_RecorderArea"
|
||||
|
||||
export type ConfigAreaProps = {
|
||||
detectors: string[]
|
||||
inputChunkNums: number[]
|
||||
}
|
||||
|
||||
|
||||
export const ConfigArea = (props: ConfigAreaProps) => {
|
||||
const configArea = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="config-area">
|
||||
<QualityArea detectors={props.detectors}></QualityArea>
|
||||
<ConvertArea inputChunkNums={props.inputChunkNums}></ConvertArea>
|
||||
</div>
|
||||
<div className="config-area">
|
||||
<DeviceArea></DeviceArea>
|
||||
<RecorderArea></RecorderArea>
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}, [])
|
||||
|
||||
return configArea
|
||||
}
|
@ -960,3 +960,341 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
.model-slot-area {
|
||||
display: inline-block;
|
||||
background: var(--company-color2);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
.model-slot-panel {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
.model-slot-tiles-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap;
|
||||
/* width: calc(30rem + 40px + 10px); */
|
||||
}
|
||||
|
||||
.model-slot-buttons {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
.model-slot-button {
|
||||
border: solid 2px #999;
|
||||
color: white;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 2px;
|
||||
background: #333;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
.model-slot-button:hover {
|
||||
border: solid 2px #faa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.model-slot-tile-container,
|
||||
.model-slot-tile-container-selected {
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.model-slot-tile-container-selected {
|
||||
background: #43030c;
|
||||
}
|
||||
.model-slot-tile-container:hover {
|
||||
background: #43030c;
|
||||
}
|
||||
|
||||
.model-slot-tile-icon-div {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.model-slot-tile-icon {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
object-fit: contain;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.model-slot-tile-icon-no-entry {
|
||||
color: gray;
|
||||
}
|
||||
.model-slot-tile-dscription {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: navajowhite;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.character-area {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
padding: 20px;
|
||||
.portrait-area {
|
||||
width: 20rem;
|
||||
height: 20rem;
|
||||
.portrait-container {
|
||||
position: relative;
|
||||
width: 20rem;
|
||||
height: 20rem;
|
||||
.portrait {
|
||||
width: 20rem;
|
||||
height: 20rem;
|
||||
object-fit: contain;
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
.portrait-area-status {
|
||||
width: 5rem;
|
||||
background: rgba(100, 100, 100, 0.5);
|
||||
color: white;
|
||||
position: absolute;
|
||||
paddig: 2px;
|
||||
font-size: 0.7rem;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.portrait-area-terms-of-use {
|
||||
width: 5rem;
|
||||
background: rgba(100, 100, 100, 0.5);
|
||||
color: white;
|
||||
position: absolute;
|
||||
paddig: 2px;
|
||||
font-size: 0.7rem;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
.portrait-area-terms-of-use-link {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.character-area-control-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
.character-area-control {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
.character-area-control-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
.character-area-control-button-active {
|
||||
width: 5rem;
|
||||
border: solid 1px #333;
|
||||
border-radius: 2px;
|
||||
background: #ada;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
.character-area-control-button-stanby {
|
||||
width: 5rem;
|
||||
border: solid 1px #999;
|
||||
border-radius: 2px;
|
||||
background: #aba;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
&:hover {
|
||||
border: solid 1px #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
.character-area-control-title {
|
||||
width: 3rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.character-area-control-field {
|
||||
/* width: 20rem; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.character-area-slider-control {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.character-area-slider-control-kind {
|
||||
width: 2rem;
|
||||
}
|
||||
.character-area-slider-control-slider {
|
||||
width: 10rem;
|
||||
}
|
||||
.character-area-slider-control-val {
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
.character-area-buuttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
.character-area-buutton {
|
||||
border: solid 2px #999;
|
||||
color: white;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 2px;
|
||||
background: #666;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
.character-area-buutton:hover {
|
||||
border: solid 2px #faa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* audio::-webkit-media-controls-play-button,
|
||||
audio::-webkit-media-controls-panel {
|
||||
background-color: #ff0;
|
||||
height: 1rem;
|
||||
}
|
||||
audio::-webkit-media-controls-enclosure {
|
||||
max-height: 1rem;
|
||||
}
|
||||
audio::-webkit-media-controls {
|
||||
justify-content: start;
|
||||
}
|
||||
audio::-webkit-media-controls-overlay-enclosure{
|
||||
height: 1rem;
|
||||
} */
|
||||
|
||||
.config-area {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
padding: 20px;
|
||||
.config-sub-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
.config-sub-area-control {
|
||||
display: flex;
|
||||
.config-sub-area-control-title {
|
||||
width: 5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.config-sub-area-control-title-long {
|
||||
width: 20rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.config-sub-area-control-field {
|
||||
width: 15rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
.config-sub-area-noise-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
.config-sub-area-noise-checkbox-container {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-sub-area-slider-control {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.config-sub-area-slider-control-kind {
|
||||
width: 1rem;
|
||||
}
|
||||
.config-sub-area-slider-control-slider {
|
||||
width: 10rem;
|
||||
}
|
||||
.config-sub-area-slider-control-val {
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
.config-sub-area-buuttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
.config-sub-area-buutton {
|
||||
border: solid 2px #999;
|
||||
color: white;
|
||||
background: #666;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 0.8rem;
|
||||
border-radius: 5px;
|
||||
height: 1.2rem;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
.config-sub-area-buutton:hover {
|
||||
border: solid 2px #faa;
|
||||
}
|
||||
.config-sub-area-buutton-active {
|
||||
border: solid 2px #999;
|
||||
color: white;
|
||||
background: #844;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 0.8rem;
|
||||
border-radius: 5px;
|
||||
height: 1.2rem;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
}
|
||||
.config-sub-area-control-field-auido-io {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.config-sub-area-control-field-auido-io-filter {
|
||||
max-width: 30%;
|
||||
}
|
||||
.config-sub-area-control-field-auido-io-select {
|
||||
max-width: 70%;
|
||||
}
|
||||
}
|
||||
.config-sub-area-control-field-wav-file {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
.config-sub-area-control-field-wav-file-audio-container {
|
||||
height: 1rem;
|
||||
.config-sub-area-control-field-wav-file-audio {
|
||||
height: 1rem;
|
||||
width: 15rem;
|
||||
}
|
||||
}
|
||||
.config-sub-area-control-field-wav-file-folder {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.config-sub-area-control-field-wav-file-echoback-button {
|
||||
border: solid 1px #333;
|
||||
background: #fff;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 5px;
|
||||
height: 1.2rem;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.config-sub-area-control-field-wav-file-echoback-button-active {
|
||||
font-size: 0.8rem;
|
||||
border: solid 1px #333;
|
||||
border-radius: 5px;
|
||||
background: #ada;
|
||||
height: 1.2rem;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +237,11 @@ export type ServerInfo = VoiceChangerServerSetting & {
|
||||
serverAudioInputDevices: ServerAudioDevice[]
|
||||
serverAudioOutputDevices: ServerAudioDevice[]
|
||||
sampleModels: RVCSampleModel[]
|
||||
gpus: {
|
||||
id: number,
|
||||
name: string,
|
||||
memory: number,
|
||||
}[]
|
||||
|
||||
}
|
||||
|
||||
@ -308,6 +313,7 @@ export const DefaultServerSetting: ServerInfo = {
|
||||
silenceFront: 1,
|
||||
modelSlotIndex: 0,
|
||||
sampleModels: [],
|
||||
gpus: [],
|
||||
|
||||
useEnhancer: 0,
|
||||
useDiff: 1,
|
||||
|
@ -159,8 +159,10 @@ export const useClient = (props: UseClientProps): ClientState => {
|
||||
return
|
||||
}
|
||||
const audio = document.getElementById(elemId) as HTMLAudioElement
|
||||
audio.srcObject = voiceChangerClientRef.current.stream
|
||||
audio.play()
|
||||
if (audio.paused) {
|
||||
audio.srcObject = voiceChangerClientRef.current.stream
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
|
||||
// (2-2) 情報リロード
|
||||
|
@ -76,6 +76,7 @@ class MMVC_Rest_Fileuploader:
|
||||
self, key: str = Form(...), val: Union[int, str, float] = Form(...)
|
||||
):
|
||||
# print("[Voice Changer] update configuration:", key, val)
|
||||
print("post_update_settings", key, type(val))
|
||||
info = self.voiceChangerManager.update_settings(key, val)
|
||||
json_compatible_item_data = jsonable_encoder(info)
|
||||
return JSONResponse(content=json_compatible_item_data)
|
||||
|
@ -38,7 +38,7 @@
|
||||
"modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/tokina_shigure_v2_40k_e100_simple.onnx",
|
||||
"indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/added_IVF2736_Flat_nprobe_1_v2.index.bin",
|
||||
"termsOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc_v2_alpha/tokina_shigure/terms_of_use.txt",
|
||||
"icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/dummy.png",
|
||||
"icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/tokina_shigure.png",
|
||||
"credit": "刻鳴時雨",
|
||||
"description": "",
|
||||
"sampleRate": 40000,
|
||||
|
@ -28,6 +28,10 @@ class MMVC_SocketIOApp:
|
||||
"filename": f"{getFrontendPath()}/assets/icons/tool.svg",
|
||||
"content_type": "image/svg+xml",
|
||||
},
|
||||
"/assets/icons/folder.svg": {
|
||||
"filename": f"{getFrontendPath()}/assets/icons/folder.svg",
|
||||
"content_type": "image/svg+xml",
|
||||
},
|
||||
"/buymeacoffee.png": {
|
||||
"filename": f"{getFrontendPath()}/assets/buymeacoffee.png",
|
||||
"content_type": "image/png",
|
||||
|
@ -197,9 +197,13 @@ 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
|
||||
@ -213,7 +217,9 @@ 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)
|
||||
@ -222,6 +228,7 @@ 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(
|
||||
@ -513,7 +520,7 @@ class RVC:
|
||||
try:
|
||||
shutil.move(uploadPath, storePath)
|
||||
params = json.load(open(storeJson, "r", encoding="utf-8"))
|
||||
params[paramsDict["name"]] = storePath
|
||||
params[paramsDict["name"]] = storePath # type:ignore
|
||||
json.dump(params, open(storeJson, "w"))
|
||||
except Exception as e:
|
||||
print("Exception::::", e)
|
||||
|
@ -341,6 +341,15 @@ class VoiceChanger:
|
||||
data = asdict(self.settings)
|
||||
if self.voiceChanger is not None:
|
||||
data.update(self.voiceChanger.get_info())
|
||||
|
||||
devCount = torch.cuda.device_count()
|
||||
gpus = []
|
||||
for id in range(devCount):
|
||||
name = torch.cuda.get_device_name(id)
|
||||
memory = torch.cuda.get_device_properties(id).total_memory
|
||||
gpu = {"id": id, "name": name, "memory": memory}
|
||||
gpus.append(gpu)
|
||||
data["gpus"] = gpus
|
||||
return data
|
||||
|
||||
def get_performance(self):
|
||||
|
Loading…
Reference in New Issue
Block a user