mirror of
https://github.com/w-okada/voice-changer.git
synced 2025-02-02 16:23:58 +03:00
WIP: gui commonize
This commit is contained in:
parent
6d53d4ccf4
commit
2adfb7c910
37
client/demo_v13/dist/assets/gui_settings/MMVCv13.json
vendored
Normal file
37
client/demo_v13/dist/assets/gui_settings/MMVCv13.json
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"type": "demo",
|
||||
"id": "MMVCv13",
|
||||
"front": {
|
||||
"title": {
|
||||
"mainTitle": "VC Client",
|
||||
"subTitle": "MMVC for v.1.3",
|
||||
"lineNum": 1
|
||||
},
|
||||
"serverControl": {},
|
||||
"modelSetting": {
|
||||
"ONNXEnable": true,
|
||||
"pyTorchEnable": true,
|
||||
"MMVCCorrespondense": false,
|
||||
"pyTorchClusterEnable": false
|
||||
},
|
||||
"deviceSetting": {},
|
||||
"qualityControl": {
|
||||
"F0DetectorEnable": false
|
||||
},
|
||||
"speakerSetting": [],
|
||||
"converterSetting": [],
|
||||
"advancedSetting": []
|
||||
},
|
||||
|
||||
"dialogs": {
|
||||
"license": [
|
||||
{
|
||||
"title": "c",
|
||||
"auther": "c",
|
||||
"contact": "b",
|
||||
"url": "a",
|
||||
"license": "MIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
11
client/demo_v13/dist/index.html
vendored
11
client/demo_v13/dist/index.html
vendored
@ -1 +1,10 @@
|
||||
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Voice Changer Client Demo</title>
|
||||
<script defer src="index.js"></script></head>
|
||||
<body style="width: 100%; height: 100%; margin: 0px">
|
||||
<div id="app" style="width: 100%; height: 100%"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
1167
client/demo_v13/dist/index.js
vendored
1167
client/demo_v13/dist/index.js
vendored
File diff suppressed because one or more lines are too long
31
client/demo_v13/dist/index.js.LICENSE.txt
vendored
31
client/demo_v13/dist/index.js.LICENSE.txt
vendored
@ -1,31 +0,0 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
37
client/demo_v13/public/assets/gui_settings/MMVCv13.json
Normal file
37
client/demo_v13/public/assets/gui_settings/MMVCv13.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"type": "demo",
|
||||
"id": "MMVCv13",
|
||||
"front": {
|
||||
"title": {
|
||||
"mainTitle": "VC Client",
|
||||
"subTitle": "MMVC for v.1.3",
|
||||
"lineNum": 1
|
||||
},
|
||||
"serverControl": {},
|
||||
"modelSetting": {
|
||||
"ONNXEnable": true,
|
||||
"pyTorchEnable": true,
|
||||
"MMVCCorrespondense": false,
|
||||
"pyTorchClusterEnable": false
|
||||
},
|
||||
"deviceSetting": {},
|
||||
"qualityControl": {
|
||||
"F0DetectorEnable": false
|
||||
},
|
||||
"speakerSetting": [],
|
||||
"converterSetting": [],
|
||||
"advancedSetting": []
|
||||
},
|
||||
|
||||
"dialogs": {
|
||||
"license": [
|
||||
{
|
||||
"title": "c",
|
||||
"auther": "c",
|
||||
"contact": "b",
|
||||
"url": "a",
|
||||
"license": "MIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./css/App.css"
|
||||
import { ErrorInfo, useMemo, useState, } from "react";
|
||||
import { useMicrophoneOptions } from "./100_options_microphone";
|
||||
import { AppStateProvider, useAppState } from "./001_provider/001_AppStateProvider";
|
||||
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
@ -12,8 +11,8 @@ import { fab } from "@fortawesome/free-brands-svg-icons";
|
||||
import { AppRootProvider } from "./001_provider/001_AppRootProvider";
|
||||
import ErrorBoundary from "./001_provider/900_ErrorBoundary";
|
||||
import { INDEXEDDB_KEY_CLIENT, INDEXEDDB_KEY_MODEL_DATA, INDEXEDDB_KEY_SERVER, INDEXEDDB_KEY_WORKLET, INDEXEDDB_KEY_WORKLETNODE, useIndexedDB } from "@dannadori/voice-changer-client-js";
|
||||
import { CLIENT_TYPE, INDEXEDDB_KEY_AUDIO_OUTPUT, isDesktopApp } from "./const";
|
||||
import { Dialog } from "./components/201_Dialog";
|
||||
import { CLIENT_TYPE, INDEXEDDB_KEY_AUDIO_OUTPUT } from "./const";
|
||||
import { Demo } from "./components/demo/010_Demo";
|
||||
|
||||
library.add(fas, far, fab);
|
||||
|
||||
@ -23,149 +22,17 @@ const root = createRoot(container);
|
||||
|
||||
const App = () => {
|
||||
const appState = useAppState()
|
||||
const { removeItem } = useIndexedDB({ clientType: CLIENT_TYPE })
|
||||
const { voiceChangerSetting } = useMicrophoneOptions()
|
||||
const titleRow = useMemo(() => {
|
||||
const githubLink = 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 = isDesktopApp() ?
|
||||
(
|
||||
// @ts-ignore
|
||||
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://zenn.dev/wok/books/0003_vc-helper-v_1_5") }}>
|
||||
<img src="./assets/icons/help-circle.svg" />
|
||||
<div className="tooltip-text">manual</div>
|
||||
</span>
|
||||
)
|
||||
:
|
||||
(
|
||||
<a className="link tooltip" href="https://zenn.dev/wok/books/0003_vc-helper-v_1_5" target="_blank" rel="noopener noreferrer">
|
||||
<img src="./assets/icons/help-circle.svg" />
|
||||
<div className="tooltip-text">manual</div>
|
||||
</a>
|
||||
)
|
||||
|
||||
const toolLink = 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 = 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 licenseButton = (
|
||||
<span className="link" onClick={() => {
|
||||
document.getElementById("dialog")?.classList.add("dialog-container-show")
|
||||
appState.frontendManagerState.stateControls.showLicenseCheckbox.updateState(true)
|
||||
}}>
|
||||
<span>License</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="top-title">
|
||||
<span className="title">Voice Changer Setting</span>
|
||||
<span className="top-title-version">for MMVC v.1.3.x</span>
|
||||
<span className="belongings">
|
||||
{githubLink}
|
||||
{manualLink}
|
||||
{toolLink}
|
||||
{coffeeLink}
|
||||
{licenseButton}
|
||||
</span>
|
||||
<span className="belongings">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
const clearRow = useMemo(() => {
|
||||
const onClearSettingClicked = async () => {
|
||||
await appState.clearSetting()
|
||||
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
|
||||
location.reload()
|
||||
const front = useMemo(() => {
|
||||
if (appState.appGuiSettingState.appGuiSetting.type == "demo") {
|
||||
return <Demo></Demo>
|
||||
} else {
|
||||
return <>unknown gui type. {appState.appGuiSettingState.appGuiSetting.type}</>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-3-4 left-padding-1">
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onClearSettingClicked}>clear setting</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
}, [appState.appGuiSettingState.appGuiSetting.type])
|
||||
|
||||
const mainSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="main-body">
|
||||
<Dialog />
|
||||
{titleRow}
|
||||
{clearRow}
|
||||
{voiceChangerSetting}
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}, [voiceChangerSetting])
|
||||
return (
|
||||
<>
|
||||
{mainSetting}
|
||||
{front}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
96
client/demo_v13/src/001_globalHooks/001_useAppGuiSetting.ts
Normal file
96
client/demo_v13/src/001_globalHooks/001_useAppGuiSetting.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { useState } from "react"
|
||||
import { AppGuiDemoClearSetting } from "../components/demo/102_ClearSettingRow"
|
||||
import { AppGuiDemoDialogLicenseSetting } from "../components/demo/901_LicenseDialog"
|
||||
import { ClientType } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
export type AppGuiSetting = AppGuiDemoSetting
|
||||
|
||||
export type AppGuiDemoSetting = {
|
||||
type: "demo",
|
||||
id: ClientType,
|
||||
front: {
|
||||
"title": {
|
||||
"mainTitle": string,
|
||||
"subTitle": string,
|
||||
"lineNum": number
|
||||
},
|
||||
"serverControl": {
|
||||
},
|
||||
"modelSetting": {
|
||||
"ONNXEnable": boolean,
|
||||
"pyTorchEnable": boolean,
|
||||
"MMVCCorrespondense": boolean,
|
||||
"pyTorchClusterEnable": boolean,
|
||||
},
|
||||
"deviceSetting": {},
|
||||
"qualityControl": {
|
||||
"F0DetectorEnable": boolean
|
||||
},
|
||||
"speakerSetting": AppGuiDemoComponents[],
|
||||
"converterSetting": AppGuiDemoComponents[],
|
||||
"advancedSetting": AppGuiDemoComponents[]
|
||||
},
|
||||
dialogs: {
|
||||
"license": { title: string, auther: string, contact: string, url: string, license: string }[]
|
||||
}
|
||||
}
|
||||
|
||||
export type AppGuiDemoComponents = AppGuiDemoClearSetting
|
||||
export type AppGuiDemoDialogComponents = AppGuiDemoDialogLicenseSetting
|
||||
|
||||
|
||||
|
||||
const InitialAppGuiDemoSetting: AppGuiDemoSetting = {
|
||||
type: "demo",
|
||||
id: ClientType.MMVCv13,
|
||||
front: {
|
||||
"title": {
|
||||
"mainTitle": "",
|
||||
"subTitle": "",
|
||||
"lineNum": 1
|
||||
},
|
||||
"serverControl": {
|
||||
|
||||
},
|
||||
"modelSetting": {
|
||||
"ONNXEnable": false,
|
||||
"pyTorchEnable": false,
|
||||
"MMVCCorrespondense": false,
|
||||
"pyTorchClusterEnable": false,
|
||||
},
|
||||
"deviceSetting": {},
|
||||
"qualityControl": {
|
||||
"F0DetectorEnable": false
|
||||
},
|
||||
"speakerSetting": [],
|
||||
"converterSetting": [],
|
||||
"advancedSetting": []
|
||||
},
|
||||
dialogs: {
|
||||
"license": [{ title: "", auther: "", contact: "", url: "", license: "MIT" }]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export type AppGuiSettingState = {
|
||||
appGuiSetting: AppGuiSetting
|
||||
}
|
||||
|
||||
export type AppGuiSettingStateAndMethod = AppGuiSettingState & {
|
||||
getAppSetting: (url: string) => Promise<void>
|
||||
}
|
||||
|
||||
export const userAppGuiSetting = (): AppGuiSettingStateAndMethod => {
|
||||
const [appGuiSetting, setAppGuiSetting] = useState<AppGuiSetting>(InitialAppGuiDemoSetting)
|
||||
const getAppSetting = async (url: string) => {
|
||||
const res = await fetch(`${url}`, {
|
||||
method: "GET",
|
||||
})
|
||||
const appSetting = await res.json() as AppGuiSetting
|
||||
setAppGuiSetting(appSetting)
|
||||
}
|
||||
return {
|
||||
appGuiSetting,
|
||||
getAppSetting,
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { StateControlCheckbox, useStateControlCheckbox } from "../hooks/useStateControlCheckbox";
|
||||
import { OpenAdvancedSettingCheckbox, OpenConverterSettingCheckbox, OpenDeviceSettingCheckbox, OpenModelSettingCheckbox, OpenQualityControlCheckbox, OpenServerControlCheckbox, OpenSpeakerSettingCheckbox } from "../const"
|
||||
export type StateControls = {
|
||||
openServerControlCheckbox: StateControlCheckbox
|
||||
openModelSettingCheckbox: StateControlCheckbox
|
||||
openDeviceSettingCheckbox: StateControlCheckbox
|
||||
openQualityControlCheckbox: StateControlCheckbox
|
||||
openSpeakerSettingCheckbox: StateControlCheckbox
|
||||
openConverterSettingCheckbox: StateControlCheckbox
|
||||
openAdvancedSettingCheckbox: StateControlCheckbox
|
||||
|
||||
showLicenseCheckbox: StateControlCheckbox
|
||||
}
|
||||
|
||||
type FrontendManagerState = {
|
||||
stateControls: StateControls
|
||||
isConverting: boolean,
|
||||
isAnalyzing: boolean
|
||||
};
|
||||
|
||||
export type FrontendManagerStateAndMethod = FrontendManagerState & {
|
||||
setIsConverting: (val: boolean) => void
|
||||
setIsAnalyzing: (val: boolean) => void
|
||||
}
|
||||
|
||||
export const useFrontendManager = (): FrontendManagerStateAndMethod => {
|
||||
const [isConverting, setIsConverting] = useState<boolean>(false)
|
||||
const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false)
|
||||
|
||||
// (1) Controller Switch
|
||||
const openServerControlCheckbox = useStateControlCheckbox(OpenServerControlCheckbox);
|
||||
const openModelSettingCheckbox = useStateControlCheckbox(OpenModelSettingCheckbox);
|
||||
const openDeviceSettingCheckbox = useStateControlCheckbox(OpenDeviceSettingCheckbox);
|
||||
const openQualityControlCheckbox = useStateControlCheckbox(OpenQualityControlCheckbox);
|
||||
const openSpeakerSettingCheckbox = useStateControlCheckbox(OpenSpeakerSettingCheckbox);
|
||||
const openConverterSettingCheckbox = useStateControlCheckbox(OpenConverterSettingCheckbox);
|
||||
const openAdvancedSettingCheckbox = useStateControlCheckbox(OpenAdvancedSettingCheckbox);
|
||||
|
||||
const showLicenseCheckbox = useStateControlCheckbox("leave-checkbox");
|
||||
|
||||
useEffect(() => {
|
||||
openServerControlCheckbox.updateState(true)
|
||||
openModelSettingCheckbox.updateState(true)
|
||||
openDeviceSettingCheckbox.updateState(true)
|
||||
openSpeakerSettingCheckbox.updateState(true)
|
||||
openConverterSettingCheckbox.updateState(true)
|
||||
|
||||
// openQualityControlCheckbox.updateState(true)
|
||||
|
||||
}, [])
|
||||
|
||||
|
||||
const returnValue = {
|
||||
stateControls: {
|
||||
openServerControlCheckbox,
|
||||
openModelSettingCheckbox,
|
||||
openDeviceSettingCheckbox,
|
||||
openQualityControlCheckbox,
|
||||
openSpeakerSettingCheckbox,
|
||||
openConverterSettingCheckbox,
|
||||
openAdvancedSettingCheckbox,
|
||||
|
||||
showLicenseCheckbox
|
||||
},
|
||||
isConverting,
|
||||
setIsConverting,
|
||||
isAnalyzing,
|
||||
setIsAnalyzing
|
||||
};
|
||||
return returnValue;
|
||||
};
|
@ -1,8 +1,8 @@
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { AppGuiSettingStateAndMethod, userAppGuiSetting } from "../001_globalHooks/001_useAppGuiSetting";
|
||||
import { useVCClient } from "../001_globalHooks/001_useVCClient";
|
||||
import { FrontendManagerStateAndMethod, useFrontendManager } from "../001_globalHooks/010_useFrontendManager";
|
||||
import { useAppRoot } from "./001_AppRootProvider";
|
||||
|
||||
type Props = {
|
||||
@ -11,8 +11,8 @@ type Props = {
|
||||
|
||||
type AppStateValue = ClientState & {
|
||||
audioContext: AudioContext
|
||||
frontendManagerState: FrontendManagerStateAndMethod;
|
||||
initializedRef: React.MutableRefObject<boolean>
|
||||
appGuiSettingState: AppGuiSettingStateAndMethod
|
||||
}
|
||||
|
||||
const AppStateContext = React.createContext<AppStateValue | null>(null);
|
||||
@ -27,7 +27,7 @@ export const useAppState = (): AppStateValue => {
|
||||
export const AppStateProvider = ({ children }: Props) => {
|
||||
const appRoot = useAppRoot()
|
||||
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext })
|
||||
const frontendManagerState = useFrontendManager();
|
||||
const appGuiSettingState = userAppGuiSetting()
|
||||
|
||||
const initializedRef = useRef<boolean>(false)
|
||||
useEffect(() => {
|
||||
@ -61,14 +61,16 @@ export const AppStateProvider = ({ children }: Props) => {
|
||||
}
|
||||
}, [clientState.clientState.initialized])
|
||||
|
||||
useEffect(() => {
|
||||
appGuiSettingState.getAppSetting("/assets/gui_settings/MMVCv13.json")
|
||||
}, [])
|
||||
|
||||
console.log("appSettingState", appGuiSettingState)
|
||||
const providerValue: AppStateValue = {
|
||||
audioContext: appRoot.audioContextState.audioContext!,
|
||||
...clientState.clientState,
|
||||
frontendManagerState,
|
||||
initializedRef
|
||||
|
||||
|
||||
initializedRef,
|
||||
appGuiSettingState
|
||||
};
|
||||
|
||||
return <AppStateContext.Provider value={providerValue}>{children}</AppStateContext.Provider>;
|
||||
|
@ -1,17 +0,0 @@
|
||||
import React from "react";
|
||||
import { useAppState } from "../001_provider/001_AppStateProvider";
|
||||
import { LicenseDialog } from "./202_LicenseDialog";
|
||||
|
||||
export const Dialog = () => {
|
||||
const { frontendManagerState } = useAppState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{frontendManagerState.stateControls.showLicenseCheckbox.trigger}
|
||||
<div className="dialog-container" id="dialog">
|
||||
{frontendManagerState.stateControls.showLicenseCheckbox.trigger}
|
||||
<LicenseDialog></LicenseDialog>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
191
client/demo_v13/src/components/demo/001_GuiStateProvider.tsx
Normal file
191
client/demo_v13/src/components/demo/001_GuiStateProvider.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { StateControlCheckbox, useStateControlCheckbox } from "../../hooks/useStateControlCheckbox";
|
||||
|
||||
export const OpenServerControlCheckbox = "open-server-control-checkbox"
|
||||
export const OpenModelSettingCheckbox = "open-model-setting-checkbox"
|
||||
export const OpenDeviceSettingCheckbox = "open-device-setting-checkbox"
|
||||
export const OpenQualityControlCheckbox = "open-quality-control-checkbox"
|
||||
export const OpenSpeakerSettingCheckbox = "open-speaker-setting-checkbox"
|
||||
export const OpenConverterSettingCheckbox = "open-converter-setting-checkbox"
|
||||
export const OpenAdvancedSettingCheckbox = "open-advanced-setting-checkbox"
|
||||
|
||||
export const OpenLicenseDialogCheckbox = "open-license-dialog-checkbox"
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export type StateControls = {
|
||||
openServerControlCheckbox: StateControlCheckbox
|
||||
openModelSettingCheckbox: StateControlCheckbox
|
||||
openDeviceSettingCheckbox: StateControlCheckbox
|
||||
openQualityControlCheckbox: StateControlCheckbox
|
||||
openSpeakerSettingCheckbox: StateControlCheckbox
|
||||
openConverterSettingCheckbox: StateControlCheckbox
|
||||
openAdvancedSettingCheckbox: StateControlCheckbox
|
||||
|
||||
showLicenseCheckbox: StateControlCheckbox
|
||||
}
|
||||
|
||||
type GuiStateAndMethod = {
|
||||
stateControls: StateControls
|
||||
isConverting: boolean,
|
||||
isAnalyzing: boolean,
|
||||
showPyTorchModelUpload: boolean
|
||||
setIsConverting: (val: boolean) => void
|
||||
setIsAnalyzing: (val: boolean) => void
|
||||
setShowPyTorchModelUpload: (val: boolean) => void
|
||||
|
||||
inputAudioDeviceInfo: MediaDeviceInfo[]
|
||||
outputAudioDeviceInfo: MediaDeviceInfo[]
|
||||
audioInputForGUI: string
|
||||
audioOutputForGUI: string
|
||||
fileInputEchoback: boolean | undefined
|
||||
audioOutputForAnalyzer: string
|
||||
setInputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void
|
||||
setOutputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void
|
||||
setAudioInputForGUI: (val: string) => void
|
||||
setAudioOutputForGUI: (val: string) => void
|
||||
setFileInputEchoback: (val: boolean) => void
|
||||
setAudioOutputForAnalyzer: (val: string) => void
|
||||
}
|
||||
|
||||
const GuiStateContext = React.createContext<GuiStateAndMethod | null>(null);
|
||||
export const useGuiState = (): GuiStateAndMethod => {
|
||||
const state = useContext(GuiStateContext);
|
||||
if (!state) {
|
||||
throw new Error("useGuiState must be used within GuiStateProvider");
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const GuiStateProvider = ({ children }: Props) => {
|
||||
const [isConverting, setIsConverting] = useState<boolean>(false)
|
||||
const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false)
|
||||
|
||||
const [showPyTorchModelUpload, setShowPyTorchModelUpload] = useState<boolean>(false)
|
||||
|
||||
|
||||
const [inputAudioDeviceInfo, setInputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
|
||||
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
|
||||
const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none")
|
||||
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none")
|
||||
const [fileInputEchoback, setFileInputEchoback] = useState<boolean>(false)//最初のmuteが有効になるように。undefined <-- ??? falseしておけばよさそう。undefinedだとwarningがでる。
|
||||
const [audioOutputForAnalyzer, setAudioOutputForAnalyzer] = useState<string>("default")
|
||||
|
||||
|
||||
const reloadDeviceInfo = async () => {
|
||||
try {
|
||||
const ms = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
|
||||
ms.getTracks().forEach(x => { x.stop() })
|
||||
} catch (e) {
|
||||
console.warn("Enumerate device error::", e)
|
||||
}
|
||||
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
||||
|
||||
const audioInputs = mediaDeviceInfos.filter(x => { return x.kind == "audioinput" })
|
||||
audioInputs.push({
|
||||
deviceId: "none",
|
||||
groupId: "none",
|
||||
kind: "audioinput",
|
||||
label: "none",
|
||||
toJSON: () => { }
|
||||
})
|
||||
audioInputs.push({
|
||||
deviceId: "file",
|
||||
groupId: "file",
|
||||
kind: "audioinput",
|
||||
label: "file",
|
||||
toJSON: () => { }
|
||||
})
|
||||
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" })
|
||||
audioOutputs.push({
|
||||
deviceId: "none",
|
||||
groupId: "none",
|
||||
kind: "audiooutput",
|
||||
label: "none",
|
||||
toJSON: () => { }
|
||||
})
|
||||
// audioOutputs.push({
|
||||
// deviceId: "record",
|
||||
// groupId: "record",
|
||||
// kind: "audiooutput",
|
||||
// label: "record",
|
||||
// toJSON: () => { }
|
||||
// })
|
||||
return [audioInputs, audioOutputs]
|
||||
}
|
||||
useEffect(() => {
|
||||
const audioInitialize = async () => {
|
||||
const audioInfo = await reloadDeviceInfo()
|
||||
console.log("AUDIO", audioInfo)
|
||||
setInputAudioDeviceInfo(audioInfo[0])
|
||||
setOutputAudioDeviceInfo(audioInfo[1])
|
||||
}
|
||||
audioInitialize()
|
||||
}, [])
|
||||
|
||||
// (1) Controller Switch
|
||||
const openServerControlCheckbox = useStateControlCheckbox(OpenServerControlCheckbox);
|
||||
const openModelSettingCheckbox = useStateControlCheckbox(OpenModelSettingCheckbox);
|
||||
const openDeviceSettingCheckbox = useStateControlCheckbox(OpenDeviceSettingCheckbox);
|
||||
const openQualityControlCheckbox = useStateControlCheckbox(OpenQualityControlCheckbox);
|
||||
const openSpeakerSettingCheckbox = useStateControlCheckbox(OpenSpeakerSettingCheckbox);
|
||||
const openConverterSettingCheckbox = useStateControlCheckbox(OpenConverterSettingCheckbox);
|
||||
const openAdvancedSettingCheckbox = useStateControlCheckbox(OpenAdvancedSettingCheckbox);
|
||||
|
||||
const showLicenseCheckbox = useStateControlCheckbox(OpenLicenseDialogCheckbox);
|
||||
|
||||
useEffect(() => {
|
||||
openServerControlCheckbox.updateState(true)
|
||||
openModelSettingCheckbox.updateState(true)
|
||||
openDeviceSettingCheckbox.updateState(true)
|
||||
openSpeakerSettingCheckbox.updateState(true)
|
||||
openConverterSettingCheckbox.updateState(true)
|
||||
openQualityControlCheckbox.updateState(true)
|
||||
|
||||
showLicenseCheckbox.updateState(true)
|
||||
|
||||
}, [])
|
||||
|
||||
|
||||
const providerValue = {
|
||||
stateControls: {
|
||||
openServerControlCheckbox,
|
||||
openModelSettingCheckbox,
|
||||
openDeviceSettingCheckbox,
|
||||
openQualityControlCheckbox,
|
||||
openSpeakerSettingCheckbox,
|
||||
openConverterSettingCheckbox,
|
||||
openAdvancedSettingCheckbox,
|
||||
|
||||
showLicenseCheckbox
|
||||
},
|
||||
isConverting,
|
||||
setIsConverting,
|
||||
isAnalyzing,
|
||||
setIsAnalyzing,
|
||||
showPyTorchModelUpload,
|
||||
setShowPyTorchModelUpload,
|
||||
|
||||
|
||||
reloadDeviceInfo,
|
||||
inputAudioDeviceInfo,
|
||||
outputAudioDeviceInfo,
|
||||
audioInputForGUI,
|
||||
audioOutputForGUI,
|
||||
fileInputEchoback,
|
||||
audioOutputForAnalyzer,
|
||||
setInputAudioDeviceInfo,
|
||||
setOutputAudioDeviceInfo,
|
||||
setAudioInputForGUI,
|
||||
setAudioOutputForGUI,
|
||||
setFileInputEchoback,
|
||||
setAudioOutputForAnalyzer,
|
||||
};
|
||||
return <GuiStateContext.Provider value={providerValue}>{children}</GuiStateContext.Provider>;
|
||||
};
|
||||
|
||||
|
||||
|
36
client/demo_v13/src/components/demo/010_Demo.tsx
Normal file
36
client/demo_v13/src/components/demo/010_Demo.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React from "react"
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL } from "../../const";
|
||||
import { GuiStateProvider } from "./001_GuiStateProvider";
|
||||
import { Dialogs } from "./900_Dialogs";
|
||||
import { TitleArea } from "./100_TitleArea";
|
||||
import { ServerControl } from "./200_ServerControl";
|
||||
import { ModelSetting } from "./300_ModelSetting";
|
||||
import { DeviceSetting } from "./400_DeviceSetting";
|
||||
import { QualityControl } from "./500_QualityControl";
|
||||
|
||||
|
||||
|
||||
export const Demo = () => {
|
||||
return (
|
||||
<GuiStateProvider>
|
||||
<div className="main-body">
|
||||
<Dialogs />
|
||||
|
||||
<TitleArea />
|
||||
<ServerControl />
|
||||
<ModelSetting />
|
||||
<DeviceSetting />
|
||||
<QualityControl />
|
||||
{/* {guiState.stateControls.openServerControlCheckbox.trigger} */}
|
||||
|
||||
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
|
||||
|
||||
org:<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls></audio>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls></audio>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
|
||||
</div>
|
||||
</GuiStateProvider>
|
||||
|
||||
)
|
||||
|
||||
}
|
16
client/demo_v13/src/components/demo/100_TitleArea.tsx
Normal file
16
client/demo_v13/src/components/demo/100_TitleArea.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { Title } from "./101_Title"
|
||||
import { ClearSettingRow } from "./102_ClearSettingRow"
|
||||
|
||||
export const TitleArea = () => {
|
||||
const titleArea = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<Title />
|
||||
<ClearSettingRow />
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return titleArea
|
||||
}
|
154
client/demo_v13/src/components/demo/101_Title.tsx
Normal file
154
client/demo_v13/src/components/demo/101_Title.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider";
|
||||
import { isDesktopApp } from "../../const";
|
||||
import { useGuiState } from "./001_GuiStateProvider";
|
||||
|
||||
|
||||
export const Title = () => {
|
||||
const { appGuiSettingState } = useAppState()
|
||||
const guiState = useGuiState()
|
||||
const titleSetting = appGuiSettingState.appGuiSetting.front.title
|
||||
|
||||
|
||||
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://zenn.dev/wok/books/0003_vc-helper-v_1_5") }}>
|
||||
<img src="./assets/icons/help-circle.svg" />
|
||||
<div className="tooltip-text">manual</div>
|
||||
</span>
|
||||
)
|
||||
:
|
||||
(
|
||||
<a className="link tooltip" href="https://zenn.dev/wok/books/0003_vc-helper-v_1_5" 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 licenseButton = useMemo(() => {
|
||||
return (
|
||||
<span className="link" onClick={() => {
|
||||
document.getElementById("dialog")?.classList.add("dialog-container-show")
|
||||
guiState.stateControls.showLicenseCheckbox.updateState(true)
|
||||
}}>
|
||||
<span>License</span>
|
||||
</span>
|
||||
)
|
||||
}, [])
|
||||
|
||||
const titleRow = useMemo(() => {
|
||||
if (titleSetting.lineNum == 2) {
|
||||
return (
|
||||
<>
|
||||
<div className="top-title">
|
||||
<span className="title">{titleSetting.mainTitle}</span>
|
||||
</div>
|
||||
<div className="top-title">
|
||||
<span className="top-title-version">{titleSetting.subTitle}</span>
|
||||
<span className="belongings">
|
||||
{githubLink}
|
||||
{manualLink}
|
||||
{toolLink}
|
||||
{coffeeLink}
|
||||
{licenseButton}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
} else {
|
||||
return (
|
||||
<div className="top-title">
|
||||
<span className="title">{titleSetting.mainTitle}</span>
|
||||
<span className="top-title-version">{titleSetting.subTitle}</span>
|
||||
<span className="belongings">
|
||||
{githubLink}
|
||||
{manualLink}
|
||||
{toolLink}
|
||||
{coffeeLink}
|
||||
{licenseButton}
|
||||
</span>
|
||||
<span className="belongings">
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}, [titleSetting])
|
||||
|
||||
return titleRow
|
||||
};
|
29
client/demo_v13/src/components/demo/102_ClearSettingRow.tsx
Normal file
29
client/demo_v13/src/components/demo/102_ClearSettingRow.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider";
|
||||
import { useIndexedDB } from "@dannadori/voice-changer-client-js";
|
||||
import { INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../const";
|
||||
|
||||
export const ClearSettingRow = () => {
|
||||
const appState = useAppState()
|
||||
const clientType = appState.appGuiSettingState.appGuiSetting.id
|
||||
const { removeItem } = useIndexedDB({ clientType: clientType })
|
||||
|
||||
|
||||
const clearSettingRow = useMemo(() => {
|
||||
const onClearSettingClicked = async () => {
|
||||
await appState.clearSetting()
|
||||
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
|
||||
location.reload()
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1">
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onClearSettingClicked}>clear setting</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
return clearSettingRow
|
||||
};
|
48
client/demo_v13/src/components/demo/200_ServerControl.tsx
Normal file
48
client/demo_v13/src/components/demo/200_ServerControl.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "../101_HeaderButton"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
import { StartButtonRow } from "./201_StartButtonRow"
|
||||
import { PerformanceRow } from "./202_PerformanceRow"
|
||||
import { ServerInfoRow } from "./203_ServerInfoRow"
|
||||
|
||||
export const ServerControl = () => {
|
||||
const guiState = useGuiState()
|
||||
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: guiState.stateControls.openServerControlCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const serverControl = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{guiState.stateControls.openServerControlCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { guiState.stateControls.openServerControlCheckbox.updateState(!guiState.stateControls.openServerControlCheckbox.checked()) }}>
|
||||
Server Control
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
<StartButtonRow />
|
||||
<PerformanceRow />
|
||||
<ServerInfoRow />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return serverControl
|
||||
}
|
61
client/demo_v13/src/components/demo/201_StartButtonRow.tsx
Normal file
61
client/demo_v13/src/components/demo/201_StartButtonRow.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useMemo, useState, useEffect } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const StartButtonRow = () => {
|
||||
const appState = useAppState()
|
||||
const guiState = useGuiState()
|
||||
const [startWithAudioContextCreate, setStartWithAudioContextCreate] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!startWithAudioContextCreate) {
|
||||
return
|
||||
}
|
||||
guiState.setIsConverting(true)
|
||||
appState.clientSetting.start()
|
||||
}, [startWithAudioContextCreate])
|
||||
|
||||
|
||||
const startButtonRow = useMemo(() => {
|
||||
const onStartClicked = async () => {
|
||||
if (!appState.initializedRef.current) {
|
||||
while (true) {
|
||||
// console.log("wait 500ms")
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, 500)
|
||||
})
|
||||
// console.log("initiliazed", appState.initializedRef.current)
|
||||
if (appState.initializedRef.current) {
|
||||
break
|
||||
}
|
||||
}
|
||||
setStartWithAudioContextCreate(true)
|
||||
} else {
|
||||
guiState.setIsConverting(true)
|
||||
await appState.clientSetting.start()
|
||||
}
|
||||
}
|
||||
const onStopClicked = async () => {
|
||||
guiState.setIsConverting(false)
|
||||
await appState.clientSetting.stop()
|
||||
}
|
||||
const startClassName = guiState.isConverting ? "body-button-active" : "body-button-stanby"
|
||||
const stopClassName = guiState.isConverting ? "body-button-stanby" : "body-button-active"
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Start</div>
|
||||
<div className="body-button-container">
|
||||
<div onClick={onStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<div className="body-input-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.isConverting, appState.clientSetting.start, appState.clientSetting.stop])
|
||||
|
||||
return startButtonRow
|
||||
}
|
34
client/demo_v13/src/components/demo/202_PerformanceRow.tsx
Normal file
34
client/demo_v13/src/components/demo/202_PerformanceRow.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const PerformanceRow = () => {
|
||||
const appState = useAppState()
|
||||
const [showPerformanceDetail, setShowPerformanceDetail] = useState<boolean>(false)
|
||||
|
||||
const performanceRow = useMemo(() => {
|
||||
const performanceDetailLabel = showPerformanceDetail ? "[pre, main, post] <<" : "more >>"
|
||||
const performanceData = showPerformanceDetail ? `[${appState.performance.preprocessTime}, ${appState.performance.mainprocessTime},${appState.performance.postprocessTime}]` : ""
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-1-1-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">monitor:</div>
|
||||
<div className="body-item-text">vol<span className="body-item-text-small">(rms)</span></div>
|
||||
<div className="body-item-text">buf<span className="body-item-text-small">(ms)</span></div>
|
||||
<div className="body-item-text">res<span className="body-item-text-small">(ms)</span></div>
|
||||
<div className="body-item-text">
|
||||
<span onClick={() => { setShowPerformanceDetail(!showPerformanceDetail) }} >{performanceDetailLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-row split-3-1-1-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1"></div>
|
||||
<div className="body-item-text">{appState.volume.toFixed(4)}</div>
|
||||
<div className="body-item-text">{appState.bufferingTime}</div>
|
||||
<div className="body-item-text">{appState.performance.responseTime}</div>
|
||||
<div className="body-item-text">{performanceData}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [appState.volume, appState.bufferingTime, appState.performance, showPerformanceDetail])
|
||||
|
||||
return performanceRow
|
||||
}
|
32
client/demo_v13/src/components/demo/203_ServerInfoRow.tsx
Normal file
32
client/demo_v13/src/components/demo/203_ServerInfoRow.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const ServerInfoRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
const serverInfoRow = useMemo(() => {
|
||||
const onReloadClicked = async () => {
|
||||
const info = await appState.getInfo()
|
||||
console.log("info", info)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Model Info:</div>
|
||||
<div className="body-item-text">
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverSetting.configFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverSetting.pyTorchModelFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverSetting.onnxModelFile || ""}</span>
|
||||
|
||||
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onReloadClicked}>reload</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [appState.getInfo, appState.serverSetting.serverSetting])
|
||||
|
||||
return serverInfoRow
|
||||
}
|
63
client/demo_v13/src/components/demo/300_ModelSetting.tsx
Normal file
63
client/demo_v13/src/components/demo/300_ModelSetting.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "../101_HeaderButton"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
import { ModelUploaderRow } from "./301_ModelUploaderRow"
|
||||
import { ConfigSelectRow } from "./302_ConfigSelectRow"
|
||||
import { ONNXSelectRow } from "./303_ONNXSelectRow"
|
||||
import { PyTorchSelectRow } from "./304_PyTorchSelectRow"
|
||||
import { CorrespondenceSelectRow } from "./305_CorrespondenceSelectRow"
|
||||
import { PyTorchClusterSelectRow } from "./306_PyTorchClusterSelectRow"
|
||||
import { ModelUploadButtonRow } from "./310_ModelUploadButtonRow"
|
||||
import { FrameworkRow } from "./320_FrameworkRow"
|
||||
import { ONNXExecutorRow } from "./321_ONNXExecutorRow"
|
||||
|
||||
|
||||
export const ModelSetting = () => {
|
||||
const guiState = useGuiState()
|
||||
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: guiState.stateControls.openModelSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const modelSetting = useMemo(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{guiState.stateControls.openModelSettingCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { guiState.stateControls.openModelSettingCheckbox.updateState(!guiState.stateControls.openModelSettingCheckbox.checked()) }}>
|
||||
Model Setting
|
||||
</span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
<ModelUploaderRow />
|
||||
<ConfigSelectRow />
|
||||
<ONNXSelectRow />
|
||||
<PyTorchSelectRow />
|
||||
<CorrespondenceSelectRow />
|
||||
<PyTorchClusterSelectRow />
|
||||
<ModelUploadButtonRow />
|
||||
<FrameworkRow />
|
||||
<ONNXExecutorRow />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return modelSetting
|
||||
}
|
26
client/demo_v13/src/components/demo/301_ModelUploaderRow.tsx
Normal file
26
client/demo_v13/src/components/demo/301_ModelUploaderRow.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const ModelUploaderRow = () => {
|
||||
const guiState = useGuiState()
|
||||
|
||||
const modelUploaderRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Model Uploader</div>
|
||||
<div className="body-item-text">
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>
|
||||
<input type="checkbox" checked={guiState.showPyTorchModelUpload} onChange={(e) => {
|
||||
guiState.setShowPyTorchModelUpload(e.target.checked)
|
||||
}} /> enable PyTorch
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.showPyTorchModelUpload])
|
||||
|
||||
return modelUploaderRow
|
||||
}
|
46
client/demo_v13/src/components/demo/302_ConfigSelectRow.tsx
Normal file
46
client/demo_v13/src/components/demo/302_ConfigSelectRow.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { fileSelector } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const ConfigSelectRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
const configSelectRow = useMemo(() => {
|
||||
const configFilenameText = appState.serverSetting.fileUploadSetting.configFile?.filename || appState.serverSetting.fileUploadSetting.configFile?.file?.name || ""
|
||||
const onConfigFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".json") == false) {
|
||||
alert("モデルファイルの拡張子はjsonである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
configFile: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onConfigFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
configFile: null
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">Config(.json)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{configFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onConfigFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onConfigFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.fileUploadSetting, appState.serverSetting.setFileUploadSetting])
|
||||
|
||||
return configSelectRow
|
||||
}
|
51
client/demo_v13/src/components/demo/303_ONNXSelectRow.tsx
Normal file
51
client/demo_v13/src/components/demo/303_ONNXSelectRow.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { fileSelector } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const ONNXSelectRow = () => {
|
||||
const appState = useAppState()
|
||||
const modelSetting = appState.appGuiSettingState.appGuiSetting.front.modelSetting
|
||||
|
||||
const onnxSelectRow = useMemo(() => {
|
||||
if (!modelSetting.ONNXEnable) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const onnxModelFilenameText = appState.serverSetting.fileUploadSetting.onnxModel?.filename || appState.serverSetting.fileUploadSetting.onnxModel?.file?.name || ""
|
||||
const onOnnxFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".onnx") == false) {
|
||||
alert("モデルファイルの拡張子はonnxである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
onnxModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onOnnxFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
onnxModel: null
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">Onnx(.onnx)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{onnxModelFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onOnnxFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onOnnxFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [modelSetting.ONNXEnable, appState.serverSetting.fileUploadSetting, appState.serverSetting.setFileUploadSetting])
|
||||
|
||||
return onnxSelectRow
|
||||
}
|
56
client/demo_v13/src/components/demo/304_PyTorchSelectRow.tsx
Normal file
56
client/demo_v13/src/components/demo/304_PyTorchSelectRow.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { fileSelector } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const PyTorchSelectRow = () => {
|
||||
const appState = useAppState()
|
||||
const modelSetting = appState.appGuiSettingState.appGuiSetting.front.modelSetting
|
||||
const guiState = useGuiState()
|
||||
|
||||
const pyTorchSelectRow = useMemo(() => {
|
||||
if (!modelSetting.pyTorchEnable) {
|
||||
return <></>
|
||||
}
|
||||
if (!guiState.showPyTorchModelUpload) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const pyTorchFilenameText = appState.serverSetting.fileUploadSetting.pyTorchModel?.filename || appState.serverSetting.fileUploadSetting.pyTorchModel?.file?.name || ""
|
||||
const onPyTorchFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".pth") == false) {
|
||||
alert("モデルファイルの拡張子はpthである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
pyTorchModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onPyTorchFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
pyTorchModel: null
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">PyTorch(.pth)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{pyTorchFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onPyTorchFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onPyTorchFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [modelSetting.pyTorchEnable, guiState.showPyTorchModelUpload, appState.serverSetting.fileUploadSetting, appState.serverSetting.setFileUploadSetting])
|
||||
|
||||
return pyTorchSelectRow
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { fileSelector, Correspondence } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const CorrespondenceSelectRow = () => {
|
||||
const appState = useAppState()
|
||||
const modelSetting = appState.appGuiSettingState.appGuiSetting.front.modelSetting
|
||||
|
||||
const CorrespondenceSelectRow = useMemo(() => {
|
||||
if (!modelSetting.MMVCCorrespondense) {
|
||||
return <></>
|
||||
}
|
||||
const correspondenceFileText = appState.clientSetting.clientSetting.correspondences ? JSON.stringify(appState.clientSetting.clientSetting.correspondences.map(x => { return x.dirname })) : ""
|
||||
const onCorrespondenceFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
|
||||
const correspondenceText = await file.text()
|
||||
const cors = correspondenceText.split("\n").map(line => {
|
||||
const items = line.split("|")
|
||||
if (items.length != 3) {
|
||||
console.warn("Invalid Correspondence Line:", line)
|
||||
return null
|
||||
} else {
|
||||
const cor: Correspondence = {
|
||||
sid: Number(items[0]),
|
||||
correspondence: Number(items[1]),
|
||||
dirname: items[2]
|
||||
}
|
||||
return cor
|
||||
}
|
||||
}).filter(x => { return x != null }) as Correspondence[]
|
||||
console.log(cors)
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, correspondences: cors })
|
||||
|
||||
}
|
||||
|
||||
const onCorrespondenceFileClearClicked = () => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, correspondences: [] })
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">Correspondence</div>
|
||||
<div className="body-item-text">
|
||||
<div>{correspondenceFileText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onCorrespondenceFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onCorrespondenceFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [modelSetting.MMVCCorrespondense, appState.clientSetting.clientSetting.correspondences])
|
||||
|
||||
return CorrespondenceSelectRow
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { fileSelector } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const PyTorchClusterSelectRow = () => {
|
||||
const appState = useAppState()
|
||||
const modelSetting = appState.appGuiSettingState.appGuiSetting.front.modelSetting
|
||||
|
||||
const pyTorchSelectRow = useMemo(() => {
|
||||
if (!modelSetting.pyTorchClusterEnable) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const clusterModelFilenameText = appState.serverSetting.fileUploadSetting.clusterTorchModel?.filename || appState.serverSetting.fileUploadSetting.clusterTorchModel?.file?.name || ""
|
||||
const onClusterFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".pt") == false) {
|
||||
alert("モデルファイルの拡張子はptである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
clusterTorchModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onClusterFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
clusterTorchModel: null
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">cluster(.pt)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{clusterModelFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onClusterFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onClusterFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [modelSetting.pyTorchClusterEnable, appState.serverSetting.fileUploadSetting, appState.serverSetting.setFileUploadSetting])
|
||||
|
||||
return pyTorchSelectRow
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import React, { useMemo } from "react"
|
||||
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const ModelUploadButtonRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
const modelUploadButtonRow = useMemo(() => {
|
||||
const onModelUploadClicked = async () => {
|
||||
appState.serverSetting.loadModel()
|
||||
}
|
||||
|
||||
const uploadButtonClassName = appState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
|
||||
const uploadButtonAction = appState.serverSetting.isUploading ? () => { } : onModelUploadClicked
|
||||
const uploadButtonLabel = appState.serverSetting.isUploading ? "wait..." : "upload"
|
||||
const uploadingStatus = appState.serverSetting.isUploading ?
|
||||
appState.serverSetting.uploadProgress == 0 ? `loading model...(wait about 20sec)` : `uploading.... ${appState.serverSetting.uploadProgress}%` : ""
|
||||
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2"></div>
|
||||
<div className="body-item-text">
|
||||
{uploadingStatus}
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className={uploadButtonClassName} onClick={uploadButtonAction}>{uploadButtonLabel}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}, [appState.serverSetting.isUploading, appState.serverSetting.uploadProgress, appState.serverSetting.loadModel])
|
||||
|
||||
return modelUploadButtonRow
|
||||
}
|
34
client/demo_v13/src/components/demo/320_FrameworkRow.tsx
Normal file
34
client/demo_v13/src/components/demo/320_FrameworkRow.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { Framework } from "@dannadori/voice-changer-client-js"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const FrameworkRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
const frameworkRow = useMemo(() => {
|
||||
const onFrameworkChanged = async (val: Framework) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, framework: val })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Framework</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.serverSetting.framework} onChange={(e) => {
|
||||
onFrameworkChanged(e.target.value as
|
||||
Framework)
|
||||
}}>
|
||||
{
|
||||
Object.values(Framework).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.framework, appState.serverSetting.updateServerSettings])
|
||||
|
||||
return frameworkRow
|
||||
}
|
38
client/demo_v13/src/components/demo/321_ONNXExecutorRow.tsx
Normal file
38
client/demo_v13/src/components/demo/321_ONNXExecutorRow.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { OnnxExecutionProvider } from "@dannadori/voice-changer-client-js"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const ONNXExecutorRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
const onnxExecutorRow = useMemo(() => {
|
||||
if (appState.serverSetting.serverSetting.framework != "ONNX") {
|
||||
return <></>
|
||||
|
||||
}
|
||||
const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, onnxExecutionProvider: val })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-item-title left-padding-2">OnnxExecutionProvider</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.serverSetting.onnxExecutionProvider} onChange={(e) => {
|
||||
onOnnxExecutionProviderChanged(e.target.value as
|
||||
OnnxExecutionProvider)
|
||||
}}>
|
||||
{
|
||||
Object.values(OnnxExecutionProvider).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.framework, appState.serverSetting.updateServerSettings])
|
||||
|
||||
return onnxExecutorRow
|
||||
}
|
53
client/demo_v13/src/components/demo/400_DeviceSetting.tsx
Normal file
53
client/demo_v13/src/components/demo/400_DeviceSetting.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "../101_HeaderButton"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
import { AudioInputRow } from "./401_AudioInputRow"
|
||||
import { AudioInputMediaRow } from "./402_AudioInputMediaRow"
|
||||
import { AudioOutputRow } from "./403_AudioOutputRow"
|
||||
import { AudioOutputRecordRow } from "./404_AudioOutputRecordRow"
|
||||
|
||||
|
||||
export const DeviceSetting = () => {
|
||||
const guiState = useGuiState()
|
||||
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: guiState.stateControls.openDeviceSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const deviceSetting = useMemo(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{guiState.stateControls.openDeviceSettingCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { guiState.stateControls.openDeviceSettingCheckbox.updateState(!guiState.stateControls.openDeviceSettingCheckbox.checked()) }}>
|
||||
Device Setting
|
||||
</span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
<AudioInputRow />
|
||||
<AudioInputMediaRow />
|
||||
<AudioOutputRow />
|
||||
<AudioOutputRecordRow />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return deviceSetting
|
||||
}
|
48
client/demo_v13/src/components/demo/401_AudioInputRow.tsx
Normal file
48
client/demo_v13/src/components/demo/401_AudioInputRow.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useMemo, useEffect } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const AudioInputRow = () => {
|
||||
const appState = useAppState()
|
||||
const guiState = useGuiState()
|
||||
|
||||
// キャッシュの設定は反映(たぶん、設定操作の時も起動していしまう。が問題は起こらないはず)
|
||||
useEffect(() => {
|
||||
if (typeof appState.clientSetting.clientSetting.audioInput == "string") {
|
||||
if (guiState.inputAudioDeviceInfo.find(x => {
|
||||
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
|
||||
return x.deviceId == appState.clientSetting.clientSetting.audioInput
|
||||
})) {
|
||||
guiState.setAudioInputForGUI(appState.clientSetting.clientSetting.audioInput)
|
||||
}
|
||||
}
|
||||
}, [guiState.inputAudioDeviceInfo, appState.clientSetting.clientSetting.audioInput])
|
||||
|
||||
useEffect(() => {
|
||||
if (guiState.audioInputForGUI == "file") {
|
||||
} else {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: guiState.audioInputForGUI })
|
||||
}
|
||||
}, [guiState.audioInputForGUI, appState.clientSetting.updateClientSetting])
|
||||
|
||||
const audioInputRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">AudioInput</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={guiState.audioInputForGUI} onChange={(e) => {
|
||||
guiState.setAudioInputForGUI(e.target.value)
|
||||
}}>
|
||||
{
|
||||
guiState.inputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.inputAudioDeviceInfo, guiState.audioInputForGUI])
|
||||
|
||||
return audioInputRow
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import React, { useMemo, useEffect, useRef } from "react"
|
||||
import { fileSelectorAsDataURL } from "@dannadori/voice-changer-client-js"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL } from "../../const"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const AudioInputMediaRow = () => {
|
||||
const appState = useAppState()
|
||||
const guiState = useGuiState()
|
||||
const audioSrcNode = useRef<MediaElementAudioSourceNode>()
|
||||
|
||||
useEffect(() => {
|
||||
[AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
|
||||
const audio = document.getElementById(x) as HTMLAudioElement
|
||||
if (audio) {
|
||||
audio.volume = guiState.fileInputEchoback ? 1 : 0
|
||||
}
|
||||
})
|
||||
}, [guiState.fileInputEchoback])
|
||||
|
||||
const audioInputRow = useMemo(() => {
|
||||
if (guiState.audioInputForGUI != "file") {
|
||||
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 = appState.audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
if (audioSrcNode.current.mediaElement != audio) {
|
||||
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
|
||||
const dst = appState.audioContext.createMediaStreamDestination()
|
||||
audioSrcNode.current.connect(dst)
|
||||
appState.clientSetting.updateClientSetting({ ...appState.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
|
||||
guiState.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()
|
||||
|
||||
// audio_org.onplay = () => {
|
||||
// console.log(audioOutputRef.current)
|
||||
// // @ts-ignore
|
||||
// audio_org.setSinkId(audioOutputRef.current)
|
||||
// }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title"></div>
|
||||
<div className="body-item-text">
|
||||
<div style={{ display: "none" }}>
|
||||
org:<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls></audio>
|
||||
</div>
|
||||
<div>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls></audio>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onFileLoadClicked}>load</div>
|
||||
<input type="checkbox" checked={guiState.fileInputEchoback} onChange={(e) => { guiState.setFileInputEchoback(e.target.checked) }} /> echoback
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.audioInputForGUI, guiState.fileInputEchoback])
|
||||
|
||||
return audioInputRow
|
||||
}
|
84
client/demo_v13/src/components/demo/403_AudioOutputRow.tsx
Normal file
84
client/demo_v13/src/components/demo/403_AudioOutputRow.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import React, { useMemo, useEffect } from "react"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
import { useIndexedDB } from "@dannadori/voice-changer-client-js"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../const"
|
||||
|
||||
export const AudioOutputRow = () => {
|
||||
const guiState = useGuiState()
|
||||
const appState = useAppState()
|
||||
const clientType = appState.appGuiSettingState.appGuiSetting.id
|
||||
const { getItem, setItem } = useIndexedDB({ clientType: clientType })
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const loadCache = async () => {
|
||||
const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
|
||||
if (key) {
|
||||
guiState.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 (guiState.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 == guiState.audioOutputForGUI })
|
||||
if (found) {
|
||||
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。!?
|
||||
audio.setSinkId(guiState.audioOutputForGUI)
|
||||
} else {
|
||||
console.warn("No audio output device. use default")
|
||||
}
|
||||
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
|
||||
audio.volume = guiState.fileInputEchoback ? 1 : 0
|
||||
} else {
|
||||
audio.volume = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
setAudioOutput()
|
||||
}, [guiState.audioOutputForGUI, guiState.fileInputEchoback])
|
||||
|
||||
|
||||
|
||||
const audioOutputRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">AudioOutput</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={guiState.audioOutputForGUI} onChange={(e) => {
|
||||
guiState.setAudioOutputForGUI(e.target.value)
|
||||
setItem(INDEXEDDB_KEY_AUDIO_OUTPUT, e.target.value)
|
||||
}}>
|
||||
{
|
||||
guiState.outputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.outputAudioDeviceInfo, guiState.audioOutputForGUI])
|
||||
|
||||
return audioOutputRow
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const AudioOutputRecordRow = () => {
|
||||
const appState = useAppState()
|
||||
const guiState = useGuiState()
|
||||
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false)
|
||||
|
||||
|
||||
const audioOutputRecordRow = useMemo(() => {
|
||||
const onOutputRecordStartClicked = async () => {
|
||||
setOutputRecordingStarted(true)
|
||||
await appState.workletNodeSetting.startOutputRecording()
|
||||
}
|
||||
const onOutputRecordStopClicked = async () => {
|
||||
setOutputRecordingStarted(false)
|
||||
const record = await appState.workletNodeSetting.stopOutputRecording()
|
||||
downloadRecord(record)
|
||||
}
|
||||
|
||||
const startClassName = outputRecordingStarted ? "body-button-active" : "body-button-stanby"
|
||||
const stopClassName = outputRecordingStarted ? "body-button-stanby" : "body-button-active"
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">output record</div>
|
||||
<div className="body-button-container">
|
||||
<div onClick={onOutputRecordStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onOutputRecordStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
<div className="body-input-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.audioOutputForGUI, outputRecordingStarted, appState.workletNodeSetting.startOutputRecording, appState.workletNodeSetting.stopOutputRecording])
|
||||
|
||||
return audioOutputRecordRow
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
58
client/demo_v13/src/components/demo/500_QualityControl.tsx
Normal file
58
client/demo_v13/src/components/demo/500_QualityControl.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "../101_HeaderButton"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
import { NoiseControlRow } from "./501_NoiseControlRow"
|
||||
import { GainControlRow } from "./502_GainControlRow"
|
||||
import { F0DetectorRow } from "./503_F0DetectorRow"
|
||||
import { AnalyzerRow } from "./510_AnalyzerRow"
|
||||
import { SamplingRow } from "./511_SamplingRow"
|
||||
import { SamplingPlayRow } from "./512_SamplingPlayRow"
|
||||
|
||||
|
||||
export const QualityControl = () => {
|
||||
const guiState = useGuiState()
|
||||
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: guiState.stateControls.openQualityControlCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const deviceSetting = useMemo(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{guiState.stateControls.openQualityControlCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { guiState.stateControls.openQualityControlCheckbox.updateState(!guiState.stateControls.openQualityControlCheckbox.checked()) }}>
|
||||
Quality Control
|
||||
</span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
<NoiseControlRow />
|
||||
<GainControlRow />
|
||||
<F0DetectorRow />
|
||||
<div className="body-row divider"></div>
|
||||
<AnalyzerRow />
|
||||
<SamplingRow />
|
||||
<SamplingPlayRow />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return deviceSetting
|
||||
}
|
39
client/demo_v13/src/components/demo/501_NoiseControlRow.tsx
Normal file
39
client/demo_v13/src/components/demo/501_NoiseControlRow.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const NoiseControlRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
|
||||
const noiseControlRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-2-1 left-padding-1 guided">
|
||||
<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 })
|
||||
}} /> echo cancel
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={appState.clientSetting.clientSetting.noiseSuppression} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression: e.target.checked })
|
||||
}} /> suppression1
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={appState.clientSetting.clientSetting.noiseSuppression2} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression2: e.target.checked })
|
||||
}} /> suppression2
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
appState.clientSetting.clientSetting.echoCancel,
|
||||
appState.clientSetting.clientSetting.noiseSuppression,
|
||||
appState.clientSetting.clientSetting.noiseSuppression2,
|
||||
appState.clientSetting.updateClientSetting
|
||||
])
|
||||
|
||||
return noiseControlRow
|
||||
}
|
37
client/demo_v13/src/components/demo/502_GainControlRow.tsx
Normal file
37
client/demo_v13/src/components/demo/502_GainControlRow.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
|
||||
export const GainControlRow = () => {
|
||||
const appState = useAppState()
|
||||
|
||||
|
||||
const gainControlRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Gain Control</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">in</span>
|
||||
<input type="range" className="body-item-input-slider" min="0.0" max="10.0" step="0.1" value={appState.clientSetting.clientSetting.inputGain} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, inputGain: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.clientSetting.clientSetting.inputGain}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">out</span>
|
||||
<input type="range" className="body-item-input-slider" min="0.0" max="10.0" step="0.1" value={appState.clientSetting.clientSetting.outputGain} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, outputGain: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.clientSetting.clientSetting.outputGain}</span>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
appState.clientSetting.clientSetting.inputGain,
|
||||
appState.clientSetting.clientSetting.outputGain,
|
||||
appState.clientSetting.updateClientSetting
|
||||
])
|
||||
|
||||
return gainControlRow
|
||||
}
|
35
client/demo_v13/src/components/demo/503_F0DetectorRow.tsx
Normal file
35
client/demo_v13/src/components/demo/503_F0DetectorRow.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { F0Detector } from "@dannadori/voice-changer-client-js";
|
||||
|
||||
export const F0DetectorRow = () => {
|
||||
const appState = useAppState()
|
||||
const qualityControlSetting = appState.appGuiSettingState.appGuiSetting.front.qualityControl
|
||||
const f0DetectorRow = useMemo(() => {
|
||||
if (!qualityControlSetting.F0DetectorEnable) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const desc = { "harvest": "High Quality", "dio": "Light Weight" }
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">F0 Detector</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.serverSetting.f0Detector} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, f0Detector: e.target.value as F0Detector })
|
||||
}}>
|
||||
{
|
||||
Object.values(F0Detector).map(x => {
|
||||
//@ts-ignore
|
||||
return <option key={x} value={x}>{x}({desc[x]})</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.f0Detector, appState.serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
return f0DetectorRow
|
||||
}
|
15
client/demo_v13/src/components/demo/510_AnalyzerRow.tsx
Normal file
15
client/demo_v13/src/components/demo/510_AnalyzerRow.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { useMemo } from "react"
|
||||
|
||||
export const AnalyzerRow = () => {
|
||||
const analyzerRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Analyzer(Experimental)</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return analyzerRow
|
||||
}
|
53
client/demo_v13/src/components/demo/511_SamplingRow.tsx
Normal file
53
client/demo_v13/src/components/demo/511_SamplingRow.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider"
|
||||
import { AUDIO_ELEMENT_FOR_SAMPLING_INPUT, AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT } from "../../const"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const SamplingRow = () => {
|
||||
const [recording, setRecording] = useState<boolean>(false)
|
||||
const appState = useAppState()
|
||||
const guiState = useGuiState()
|
||||
|
||||
|
||||
const samplingRow = useMemo(() => {
|
||||
const onRecordStartClicked = async () => {
|
||||
setRecording(true)
|
||||
await appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, recordIO: 1 })
|
||||
}
|
||||
const onRecordStopClicked = async () => {
|
||||
setRecording(false)
|
||||
await appState.serverSetting.updateServerSettings({ ...appState.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(guiState.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(guiState.audioOutputForAnalyzer)
|
||||
}
|
||||
|
||||
const startClassName = recording ? "body-button-active" : "body-button-stanby"
|
||||
const stopClassName = recording ? "body-button-stanby" : "body-button-active"
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2 ">
|
||||
Sampling
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div onClick={onRecordStartClicked} className={startClassName}>Start</div>
|
||||
<div onClick={onRecordStopClicked} className={stopClassName}>Stop</div>
|
||||
{/* <div onClick={onRecordAnalizeClicked} className={analyzeClassName}>{analyzeLabel}</div> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [recording, appState.serverSetting.serverSetting, appState.serverSetting.updateServerSettings, guiState.audioOutputForAnalyzer])
|
||||
|
||||
return samplingRow
|
||||
}
|
49
client/demo_v13/src/components/demo/512_SamplingPlayRow.tsx
Normal file
49
client/demo_v13/src/components/demo/512_SamplingPlayRow.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { AUDIO_ELEMENT_FOR_SAMPLING_INPUT, AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT } from "../../const"
|
||||
import { useGuiState } from "./001_GuiStateProvider"
|
||||
|
||||
export const SamplingPlayRow = () => {
|
||||
const guiState = useGuiState()
|
||||
|
||||
const samplingPlayRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2 ">
|
||||
<div>
|
||||
Play
|
||||
</div>
|
||||
<select className="body-select-50 left-margin-2" value={guiState.audioOutputForAnalyzer} onChange={(e) => {
|
||||
guiState.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)
|
||||
}}>
|
||||
{
|
||||
guiState.outputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<div className="body-wav-container-title">Input</div>
|
||||
<div className="body-wav-container-wav">
|
||||
<audio src="" id={AUDIO_ELEMENT_FOR_SAMPLING_INPUT}></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div >
|
||||
<div className="body-wav-container-title">Output</div>
|
||||
<div className="body-wav-container-wav" >
|
||||
<audio src="" id={AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT}></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
)
|
||||
}, [guiState.audioOutputForAnalyzer, guiState.outputAudioDeviceInfo])
|
||||
|
||||
return samplingPlayRow
|
||||
}
|
18
client/demo_v13/src/components/demo/900_Dialogs.tsx
Normal file
18
client/demo_v13/src/components/demo/900_Dialogs.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import { useGuiState } from "./001_GuiStateProvider";
|
||||
import { LicenseDialog } from "./901_LicenseDialog";
|
||||
|
||||
export const Dialogs = () => {
|
||||
const guiState = useGuiState()
|
||||
const dialogs = (
|
||||
<div>
|
||||
{guiState.stateControls.showLicenseCheckbox.trigger}
|
||||
<div className="dialog-container" id="dialog">
|
||||
{guiState.stateControls.showLicenseCheckbox.trigger}
|
||||
<LicenseDialog></LicenseDialog>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return dialogs
|
||||
}
|
@ -1,31 +1,34 @@
|
||||
import { getLicenceInfo } from "@dannadori/voice-changer-client-js";
|
||||
import React, { useMemo } from "react";
|
||||
import { useAppState } from "../001_provider/001_AppStateProvider";
|
||||
import { useAppState } from "../../001_provider/001_AppStateProvider";
|
||||
import { useGuiState } from "./001_GuiStateProvider";
|
||||
|
||||
|
||||
export const LicenseDialog = () => {
|
||||
const { frontendManagerState } = useAppState();
|
||||
const { appGuiSettingState } = useAppState()
|
||||
const guiState = useGuiState()
|
||||
const licenses = appGuiSettingState.appGuiSetting.dialogs.license
|
||||
|
||||
const form = useMemo(() => {
|
||||
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={() => { frontendManagerState.stateControls.showLicenseCheckbox.updateState(false) }} >close</div>
|
||||
<div className="body-button" onClick={() => { guiState.stateControls.showLicenseCheckbox.updateState(false) }} >close</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
const records = getLicenceInfo().map(x => {
|
||||
const records = licenses.map(x => {
|
||||
return (
|
||||
<div key={x.url} className="body-row split-3-4-3 left-padding-1">
|
||||
<div className="body-item-text">
|
||||
<a href={x.url} target="_blank" rel="noopener noreferrer">{x.name}</a>
|
||||
<a href={x.url} target="_blank" rel="noopener noreferrer">{x.title}</a>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<a href={x.licenseUrl} target="_blank" rel="noopener noreferrer">{x.license}</a>
|
||||
<a href={x.url} target="_blank" rel="noopener noreferrer">{x.auther}({x.contact})</a>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
<div className="body-item-text">{x.license}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@ -39,6 +42,8 @@ export const LicenseDialog = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
return form;
|
||||
}, [licenses]);
|
||||
return dialog;
|
||||
|
||||
return <></>
|
||||
};
|
@ -7,19 +7,12 @@ export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original"
|
||||
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"
|
||||
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback"
|
||||
|
||||
export const AUDIO_ELEMENT_FOR_SAMPLING_INPUT = "body-wav-container-wav-input"
|
||||
export const AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT = "body-wav-container-wav-output"
|
||||
|
||||
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT"
|
||||
|
||||
|
||||
// State Control Checkbox
|
||||
export const OpenServerControlCheckbox = "open-server-control-checkbox"
|
||||
export const OpenModelSettingCheckbox = "open-model-setting-checkbox"
|
||||
export const OpenDeviceSettingCheckbox = "open-device-setting-checkbox"
|
||||
export const OpenQualityControlCheckbox = "open-quality-control-checkbox"
|
||||
export const OpenSpeakerSettingCheckbox = "open-speaker-setting-checkbox"
|
||||
export const OpenConverterSettingCheckbox = "open-converter-setting-checkbox"
|
||||
export const OpenAdvancedSettingCheckbox = "open-advanced-setting-checkbox"
|
||||
|
||||
export const isDesktopApp = () => {
|
||||
if (navigator.userAgent.indexOf('Electron') >= 0) {
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user