WIP: improve gui: add accordion
This commit is contained in:
parent
27c5d54cc5
commit
eaf310c00b
131
client/demo/package-lock.json
generated
131
client/demo/package-lock.json
generated
@ -10,6 +10,11 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@dannadori/voice-changer-client-js": "^1.0.69",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.3.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.3.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.3.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
@ -3272,6 +3277,75 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
|
||||
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
|
||||
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-brands-svg-icons": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
|
||||
"integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-regular-svg-icons": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
|
||||
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
|
||||
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/react-fontawesome": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
||||
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
|
||||
"react": ">=16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
@ -8160,7 +8234,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -8878,7 +8951,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@ -9044,8 +9116,7 @@
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "3.0.0",
|
||||
@ -13591,6 +13662,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
|
||||
"integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
|
||||
},
|
||||
"@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
|
||||
"integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-brands-svg-icons": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
|
||||
"integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-regular-svg-icons": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
|
||||
"integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
|
||||
"integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.3.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/react-fontawesome": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
||||
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
|
||||
"requires": {
|
||||
"prop-types": "^15.8.1"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
@ -17304,8 +17420,7 @@
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.12.3",
|
||||
@ -17812,7 +17927,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@ -17933,8 +18047,7 @@
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "3.0.0",
|
||||
|
@ -52,6 +52,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@dannadori/voice-changer-client-js": "^1.0.69",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.3.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.3.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.3.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
|
@ -3,20 +3,52 @@ import { createRoot } from "react-dom/client";
|
||||
import "./css/App.css"
|
||||
import { useMemo, } from "react";
|
||||
import { useMicrophoneOptions } from "./100_options_microphone";
|
||||
import { AppStateProvider, useAppState } from "./001_provider/001_AppStateProvider";
|
||||
|
||||
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons";
|
||||
import { far } from "@fortawesome/free-regular-svg-icons";
|
||||
import { fab } from "@fortawesome/free-brands-svg-icons";
|
||||
import { AppRootProvider, useAppRoot } from "./001_provider/001_AppRootProvider";
|
||||
|
||||
library.add(fas, far, fab);
|
||||
|
||||
|
||||
const container = document.getElementById("app")!;
|
||||
const root = createRoot(container);
|
||||
|
||||
const App = () => {
|
||||
const appState = useAppState()
|
||||
const { voiceChangerSetting } = useMicrophoneOptions()
|
||||
|
||||
const { voiceChangerSetting, clearSetting } = useMicrophoneOptions()
|
||||
const titleRow = useMemo(() => {
|
||||
return (
|
||||
<div className="top-title">
|
||||
<span className="title">Voice Changer Setting</span>
|
||||
<span className="top-title-version">for v.1.5.x</span>
|
||||
<span className="belongings">
|
||||
<a className="link" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
|
||||
<img src="./assets/icons/github.svg" />
|
||||
<span>github</span>
|
||||
</a>
|
||||
<a className="link" href="https://zenn.dev/wok/books/0003_vc-helper-v_1_5" target="_blank" rel="noopener noreferrer">
|
||||
<img src="./assets/icons/help-circle.svg" />
|
||||
<span>manual</span>
|
||||
</a>
|
||||
</span>
|
||||
<span className="belongings">
|
||||
|
||||
const onClearSettingClicked = async () => {
|
||||
clearSetting()
|
||||
location.reload()
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
const clearRow = useMemo(() => {
|
||||
const onClearSettingClicked = async () => {
|
||||
await appState.clearSetting()
|
||||
location.reload()
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-3-4 left-padding-1">
|
||||
@ -30,39 +62,41 @@ const App = () => {
|
||||
)
|
||||
}, [])
|
||||
|
||||
const mainSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="main-body">
|
||||
{titleRow}
|
||||
{clearRow}
|
||||
{voiceChangerSetting}
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}, [voiceChangerSetting])
|
||||
return (
|
||||
<>
|
||||
{mainSetting}
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
const AppStateWrapper = () => {
|
||||
const appRoot = useAppRoot()
|
||||
if (!appRoot.audioContextState.audioContext) {
|
||||
return <>please click window</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-body">
|
||||
<div className="body-row split-6-4">
|
||||
<div className="body-top-title">
|
||||
Voice Changer Setting
|
||||
<span className="body-top-title-version">for v.1.5.x</span>
|
||||
</div>
|
||||
<div className="body-top-title-belongings">
|
||||
<div className="belonging-item">
|
||||
<a className="link" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
|
||||
<img src="./assets/icons/github.svg" />
|
||||
<span>github</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="belonging-item">
|
||||
<a className="link" href="https://zenn.dev/wok/articles/s01_vc001_top" target="_blank" rel="noopener noreferrer">
|
||||
<img src="./assets/icons/help-circle.svg" />
|
||||
<span>manual</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{clearRow}
|
||||
{voiceChangerSetting}
|
||||
<div>
|
||||
<audio id="audio-output"></audio>
|
||||
</div>
|
||||
</div>
|
||||
<AppStateProvider>
|
||||
<App></App>
|
||||
</AppStateProvider>
|
||||
)
|
||||
}
|
||||
|
||||
root.render(
|
||||
<App></App>
|
||||
<AppRootProvider>
|
||||
<AppStateWrapper></AppStateWrapper>
|
||||
</AppRootProvider>
|
||||
);
|
||||
|
37
client/demo/src/001_globalHooks/001_useAudioConfig.ts
Normal file
37
client/demo/src/001_globalHooks/001_useAudioConfig.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export type AudioConfigState = {
|
||||
audioContext: AudioContext | null
|
||||
}
|
||||
|
||||
export const useAudioConfig = (): AudioConfigState => {
|
||||
const [audioContext, setAudioContext] = useState<AudioContext | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const ctx = new AudioContext()
|
||||
setAudioContext(ctx)
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
// If crate ctx failed, wait for user jesture.
|
||||
const createAudioContext = () => {
|
||||
console.log("click window")
|
||||
const ctx = new AudioContext()
|
||||
document.removeEventListener('touchstart', createAudioContext);
|
||||
document.removeEventListener('mousedown', createAudioContext);
|
||||
setAudioContext(ctx)
|
||||
}
|
||||
document.addEventListener('touchstart', createAudioContext);
|
||||
document.addEventListener('mousedown', createAudioContext);
|
||||
}
|
||||
}, [])
|
||||
|
||||
console.log("AUDIO CONTEXT", audioContext)
|
||||
|
||||
const ret: AudioConfigState = {
|
||||
audioContext
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
}
|
28
client/demo/src/001_globalHooks/001_useVCClient.ts
Normal file
28
client/demo/src/001_globalHooks/001_useVCClient.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { ClientState, useClient } from "@dannadori/voice-changer-client-js"
|
||||
import { useEffect, useState } from "react"
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT } from "../const"
|
||||
|
||||
export type UseVCClientProps = {
|
||||
audioContext: AudioContext
|
||||
}
|
||||
|
||||
export type VCClientState = {
|
||||
audioContext: AudioContext
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export const useVCClient = (props: UseVCClientProps) => {
|
||||
|
||||
const clientState = useClient({
|
||||
audioContext: props.audioContext,
|
||||
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
|
||||
})
|
||||
|
||||
const ret: VCClientState = {
|
||||
audioContext: props.audioContext,
|
||||
clientState
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
}
|
47
client/demo/src/001_globalHooks/010_useFrontendManager.ts
Normal file
47
client/demo/src/001_globalHooks/010_useFrontendManager.ts
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
import { 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
|
||||
}
|
||||
|
||||
type FrontendManagerState = {
|
||||
stateControls: StateControls
|
||||
};
|
||||
|
||||
export type FrontendManagerStateAndMethod = FrontendManagerState & {
|
||||
}
|
||||
|
||||
export const useFrontendManager = (): FrontendManagerStateAndMethod => {
|
||||
|
||||
// (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 returnValue = {
|
||||
stateControls: {
|
||||
openServerControlCheckbox,
|
||||
openModelSettingCheckbox,
|
||||
openDeviceSettingCheckbox,
|
||||
openQualityControlCheckbox,
|
||||
openSpeakerSettingCheckbox,
|
||||
openConverterSettingCheckbox,
|
||||
openAdvancedSettingCheckbox
|
||||
}
|
||||
};
|
||||
return returnValue;
|
||||
};
|
30
client/demo/src/001_provider/001_AppRootProvider.tsx
Normal file
30
client/demo/src/001_provider/001_AppRootProvider.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { useContext } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { AudioConfigState, useAudioConfig } from "../001_globalHooks/001_useAudioConfig";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type AppRootValue = {
|
||||
audioContextState: AudioConfigState
|
||||
}
|
||||
|
||||
const AppRootContext = React.createContext<AppRootValue | null>(null);
|
||||
export const useAppRoot = (): AppRootValue => {
|
||||
const state = useContext(AppRootContext);
|
||||
if (!state) {
|
||||
throw new Error("useAppState must be used within AppStateProvider");
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const AppRootProvider = ({ children }: Props) => {
|
||||
const audioContextState = useAudioConfig()
|
||||
|
||||
const providerValue: AppRootValue = {
|
||||
audioContextState
|
||||
};
|
||||
|
||||
return <AppRootContext.Provider value={providerValue}>{children}</AppRootContext.Provider>;
|
||||
};
|
38
client/demo/src/001_provider/001_AppStateProvider.tsx
Normal file
38
client/demo/src/001_provider/001_AppStateProvider.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { useVCClient, VCClientState } from "../001_globalHooks/001_useVCClient";
|
||||
import { FrontendManagerStateAndMethod, useFrontendManager } from "../001_globalHooks/010_useFrontendManager";
|
||||
import { useAppRoot } from "./001_AppRootProvider";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type AppStateValue = ClientState & {
|
||||
audioContext: AudioContext
|
||||
frontendManagerState: FrontendManagerStateAndMethod;
|
||||
}
|
||||
|
||||
const AppStateContext = React.createContext<AppStateValue | null>(null);
|
||||
export const useAppState = (): AppStateValue => {
|
||||
const state = useContext(AppStateContext);
|
||||
if (!state) {
|
||||
throw new Error("useAppState must be used within AppStateProvider");
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const AppStateProvider = ({ children }: Props) => {
|
||||
const appRoot = useAppRoot()
|
||||
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext! })
|
||||
const frontendManagerState = useFrontendManager();
|
||||
|
||||
const providerValue: AppStateValue = {
|
||||
audioContext: appRoot.audioContextState.audioContext!,
|
||||
...clientState.clientState,
|
||||
frontendManagerState
|
||||
};
|
||||
|
||||
return <AppStateContext.Provider value={providerValue}>{children}</AppStateContext.Provider>;
|
||||
};
|
@ -1,54 +1,28 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT } from "./const";
|
||||
import { useServerSettingArea } from "./101_server_setting";
|
||||
import { useDeviceSetting } from "./102_device_setting";
|
||||
import { useConvertSetting } from "./104_convert_setting";
|
||||
import { useAdvancedSetting } from "./105_advanced_setting";
|
||||
import { useSpeakerSetting } from "./103_speaker_setting";
|
||||
import { useServerControl } from "./106_server_control";
|
||||
import { useClient } from "@dannadori/voice-changer-client-js";
|
||||
import { useQualityControl } from "./107_qulity_control";
|
||||
import { useMemo } from "react";
|
||||
import { useModelSettingArea } from "./102_model_setting";
|
||||
import { useDeviceSetting } from "./103_device_setting";
|
||||
import { useConvertSetting } from "./106_convert_setting";
|
||||
import { useAdvancedSetting } from "./107_advanced_setting";
|
||||
import { useSpeakerSetting } from "./105_speaker_setting";
|
||||
import { useServerControl } from "./101_server_control";
|
||||
import { useQualityControl } from "./104_qulity_control";
|
||||
|
||||
export const useMicrophoneOptions = () => {
|
||||
const [audioContext, setAudioContext] = useState<AudioContext | null>(null)
|
||||
|
||||
const clientState = useClient({
|
||||
audioContext: audioContext,
|
||||
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
|
||||
})
|
||||
|
||||
const serverSetting = useServerSettingArea({ clientState })
|
||||
const deviceSetting = useDeviceSetting(audioContext, { clientState })
|
||||
const speakerSetting = useSpeakerSetting({ clientState })
|
||||
const convertSetting = useConvertSetting({ clientState })
|
||||
const advancedSetting = useAdvancedSetting({ clientState })
|
||||
const serverControl = useServerControl({ clientState })
|
||||
const qualityControl = useQualityControl({ clientState })
|
||||
|
||||
const clearSetting = async () => {
|
||||
await clientState.clearSetting()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const createAudioContext = () => {
|
||||
const ctx = new AudioContext({
|
||||
sampleRate: 48000,
|
||||
})
|
||||
setAudioContext(ctx)
|
||||
document.removeEventListener('touchstart', createAudioContext);
|
||||
document.removeEventListener('mousedown', createAudioContext);
|
||||
}
|
||||
document.addEventListener('touchstart', createAudioContext);
|
||||
document.addEventListener('mousedown', createAudioContext);
|
||||
}, [])
|
||||
const serverControl = useServerControl()
|
||||
const modelSetting = useModelSettingArea()
|
||||
const deviceSetting = useDeviceSetting()
|
||||
const speakerSetting = useSpeakerSetting()
|
||||
const convertSetting = useConvertSetting()
|
||||
const advancedSetting = useAdvancedSetting()
|
||||
const qualityControl = useQualityControl()
|
||||
|
||||
|
||||
const voiceChangerSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{serverControl.serverControl}
|
||||
{serverSetting.serverSetting}
|
||||
{modelSetting.modelSetting}
|
||||
{deviceSetting.deviceSetting}
|
||||
{qualityControl.qualityControl}
|
||||
{speakerSetting.speakerSetting}
|
||||
@ -57,16 +31,16 @@ export const useMicrophoneOptions = () => {
|
||||
</>
|
||||
)
|
||||
}, [serverControl.serverControl,
|
||||
serverSetting.serverSetting,
|
||||
modelSetting.modelSetting,
|
||||
deviceSetting.deviceSetting,
|
||||
speakerSetting.speakerSetting,
|
||||
convertSetting.convertSetting,
|
||||
advancedSetting.advancedSetting,
|
||||
qualityControl.qualityControl])
|
||||
|
||||
|
||||
return {
|
||||
voiceChangerSetting,
|
||||
clearSetting
|
||||
voiceChangerSetting
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,34 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export type UseServerControlProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export const useServerControl = (props: UseServerControlProps) => {
|
||||
export const useServerControl = () => {
|
||||
const appState = useAppState()
|
||||
const [isStarted, setIsStarted] = useState<boolean>(false)
|
||||
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openServerControlCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const startButtonRow = useMemo(() => {
|
||||
const onStartClicked = async () => {
|
||||
setIsStarted(true)
|
||||
await props.clientState.clientSetting.start()
|
||||
await appState.clientSetting.start()
|
||||
}
|
||||
const onStopClicked = async () => {
|
||||
setIsStarted(false)
|
||||
console.log("stop click1")
|
||||
await props.clientState.clientSetting.stop()
|
||||
await appState.clientSetting.stop()
|
||||
console.log("stop click2")
|
||||
}
|
||||
const startClassName = isStarted ? "body-button-active" : "body-button-stanby"
|
||||
@ -34,9 +46,8 @@ export const useServerControl = (props: UseServerControlProps) => {
|
||||
<div className="body-input-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}, [isStarted, props.clientState.clientSetting.start, props.clientState.clientSetting.stop])
|
||||
}, [isStarted, appState.clientSetting.start, appState.clientSetting.stop])
|
||||
|
||||
const performanceRow = useMemo(() => {
|
||||
return (
|
||||
@ -50,20 +61,20 @@ export const useServerControl = (props: UseServerControlProps) => {
|
||||
</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">{props.clientState.volume.toFixed(4)}</div>
|
||||
<div className="body-item-text">{props.clientState.bufferingTime}</div>
|
||||
<div className="body-item-text">{props.clientState.responseTime}</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.responseTime}</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [props.clientState.volume, props.clientState.bufferingTime, props.clientState.responseTime])
|
||||
}, [appState.volume, appState.bufferingTime, appState.responseTime])
|
||||
|
||||
|
||||
|
||||
const infoRow = useMemo(() => {
|
||||
const onReloadClicked = async () => {
|
||||
const info = await props.clientState.getInfo()
|
||||
const info = await appState.getInfo()
|
||||
console.log("info", info)
|
||||
}
|
||||
return (
|
||||
@ -71,9 +82,9 @@ export const useServerControl = (props: UseServerControlProps) => {
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Model Info:</div>
|
||||
<div className="body-item-text">
|
||||
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.configFile || ""}</span>
|
||||
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.pyTorchModelFile || ""}</span>
|
||||
<span className="body-item-text-item">{props.clientState.serverSetting.serverInfo?.onnxModelFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverInfo?.configFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverInfo?.pyTorchModelFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverInfo?.onnxModelFile || ""}</span>
|
||||
|
||||
|
||||
</div>
|
||||
@ -83,21 +94,28 @@ export const useServerControl = (props: UseServerControlProps) => {
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [props.clientState.getInfo, props.clientState.serverSetting.serverInfo])
|
||||
|
||||
|
||||
}, [appState.getInfo, appState.serverSetting.serverInfo])
|
||||
|
||||
const serverControl = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Server Control</div>
|
||||
<div className="body-select-container">
|
||||
{appState.frontendManagerState.stateControls.openServerControlCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openServerControlCheckbox.updateState(!appState.frontendManagerState.stateControls.openServerControlCheckbox.checked()) }}>
|
||||
Server Control
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{startButtonRow}
|
||||
{performanceRow}
|
||||
{infoRow}
|
||||
</div>
|
||||
</div>
|
||||
{startButtonRow}
|
||||
{performanceRow}
|
||||
{infoRow}
|
||||
</>
|
||||
)
|
||||
}, [startButtonRow, performanceRow, infoRow])
|
||||
@ -105,7 +123,6 @@ export const useServerControl = (props: UseServerControlProps) => {
|
||||
return {
|
||||
serverControl,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,56 @@
|
||||
import { OnnxExecutionProvider, Framework, fileSelector } from "@dannadori/voice-changer-client-js"
|
||||
import React, { useState } from "react"
|
||||
import { useMemo } from "react"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
|
||||
export type UseServerSettingProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export type ServerSettingState = {
|
||||
serverSetting: JSX.Element;
|
||||
modelSetting: JSX.Element;
|
||||
}
|
||||
|
||||
export const useServerSettingArea = (props: UseServerSettingProps): ServerSettingState => {
|
||||
export const useModelSettingArea = (): ServerSettingState => {
|
||||
const appState = useAppState()
|
||||
const [showPyTorch, setShowPyTorch] = useState<boolean>(true)
|
||||
const [showDetail, setShowDetail] = useState<boolean>(true)
|
||||
|
||||
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openModelSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
|
||||
// If already model is set, show summarry.
|
||||
const settingDone = useMemo(() => {
|
||||
if (
|
||||
appState.serverSetting.fileUploadSetting.configFile?.filename && // Config file
|
||||
(
|
||||
appState.serverSetting.fileUploadSetting.onnxModel?.filename || // Model file
|
||||
appState.serverSetting.fileUploadSetting.pyTorchModel?.filename
|
||||
) &&
|
||||
appState.clientSetting.setting.correspondences.length > 0 // Corresopondence file
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}, [
|
||||
appState.serverSetting.fileUploadSetting,
|
||||
appState.clientSetting.setting.correspondences
|
||||
])
|
||||
|
||||
const uploadeModelSummaryRow = useMemo(() => {
|
||||
|
||||
|
||||
}, [settingDone, showDetail])
|
||||
|
||||
const uploadeModelRow = useMemo(() => {
|
||||
const onPyTorchFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
@ -20,16 +58,16 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
alert("モデルファイルの拡張子はpthである必要があります。")
|
||||
return
|
||||
}
|
||||
props.clientState.serverSetting.setFileUploadSetting({
|
||||
...props.clientState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
pyTorchModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onPyTorchFileClearClicked = () => {
|
||||
props.clientState.serverSetting.setFileUploadSetting({
|
||||
...props.clientState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
pyTorchModel: null
|
||||
})
|
||||
}
|
||||
@ -39,16 +77,16 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
alert("モデルファイルの拡張子はjsonである必要があります。")
|
||||
return
|
||||
}
|
||||
props.clientState.serverSetting.setFileUploadSetting({
|
||||
...props.clientState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
configFile: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onConfigFileClearClicked = () => {
|
||||
props.clientState.serverSetting.setFileUploadSetting({
|
||||
...props.clientState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
configFile: null
|
||||
})
|
||||
}
|
||||
@ -58,39 +96,39 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
alert("モデルファイルの拡張子はonnxである必要があります。")
|
||||
return
|
||||
}
|
||||
props.clientState.serverSetting.setFileUploadSetting({
|
||||
...props.clientState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
onnxModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onOnnxFileClearClicked = () => {
|
||||
props.clientState.serverSetting.setFileUploadSetting({
|
||||
...props.clientState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
onnxModel: null
|
||||
})
|
||||
}
|
||||
const onCorrespondenceFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
props.clientState.clientSetting.setCorrespondences(file)
|
||||
appState.clientSetting.setCorrespondences(file)
|
||||
}
|
||||
const onCorrespondenceFileClearClicked = () => {
|
||||
props.clientState.clientSetting.setCorrespondences(null)
|
||||
appState.clientSetting.setCorrespondences(null)
|
||||
}
|
||||
|
||||
const onModelUploadClicked = async () => {
|
||||
props.clientState.serverSetting.loadModel()
|
||||
appState.serverSetting.loadModel()
|
||||
}
|
||||
|
||||
const uploadButtonClassName = props.clientState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
|
||||
const uploadButtonAction = props.clientState.serverSetting.isUploading ? () => { } : onModelUploadClicked
|
||||
const uploadButtonLabel = props.clientState.serverSetting.isUploading ? "wait..." : "upload"
|
||||
const uploadButtonClassName = appState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
|
||||
const uploadButtonAction = appState.serverSetting.isUploading ? () => { } : onModelUploadClicked
|
||||
const uploadButtonLabel = appState.serverSetting.isUploading ? "wait..." : "upload"
|
||||
|
||||
const configFilenameText = props.clientState.serverSetting.fileUploadSetting.configFile?.filename || props.clientState.serverSetting.fileUploadSetting.configFile?.file?.name || ""
|
||||
const onnxModelFilenameText = props.clientState.serverSetting.fileUploadSetting.onnxModel?.filename || props.clientState.serverSetting.fileUploadSetting.onnxModel?.file?.name || ""
|
||||
const pyTorchFilenameText = props.clientState.serverSetting.fileUploadSetting.pyTorchModel?.filename || props.clientState.serverSetting.fileUploadSetting.pyTorchModel?.file?.name || ""
|
||||
const correspondenceFileText = props.clientState.clientSetting.setting.correspondences ? JSON.stringify(props.clientState.clientSetting.setting.correspondences.map(x => { return x.dirname })) : ""
|
||||
const configFilenameText = appState.serverSetting.fileUploadSetting.configFile?.filename || appState.serverSetting.fileUploadSetting.configFile?.file?.name || ""
|
||||
const onnxModelFilenameText = appState.serverSetting.fileUploadSetting.onnxModel?.filename || appState.serverSetting.fileUploadSetting.onnxModel?.file?.name || ""
|
||||
const pyTorchFilenameText = appState.serverSetting.fileUploadSetting.pyTorchModel?.filename || appState.serverSetting.fileUploadSetting.pyTorchModel?.file?.name || ""
|
||||
const correspondenceFileText = appState.clientSetting.setting.correspondences ? JSON.stringify(appState.clientSetting.setting.correspondences.map(x => { return x.dirname })) : ""
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -161,7 +199,7 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2"></div>
|
||||
<div className="body-item-text">
|
||||
{props.clientState.serverSetting.isUploading ? `uploading.... ${props.clientState.serverSetting.uploadProgress}%` : ""}
|
||||
{appState.serverSetting.isUploading ? `uploading.... ${appState.serverSetting.uploadProgress}%` : ""}
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className={uploadButtonClassName} onClick={uploadButtonAction}>{uploadButtonLabel}</div>
|
||||
@ -170,22 +208,22 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
</>
|
||||
)
|
||||
}, [
|
||||
props.clientState.serverSetting.fileUploadSetting,
|
||||
props.clientState.serverSetting.loadModel,
|
||||
props.clientState.serverSetting.isUploading,
|
||||
props.clientState.serverSetting.uploadProgress,
|
||||
props.clientState.clientSetting.setting.correspondences,
|
||||
appState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.loadModel,
|
||||
appState.serverSetting.isUploading,
|
||||
appState.serverSetting.uploadProgress,
|
||||
appState.clientSetting.setting.correspondences,
|
||||
showPyTorch])
|
||||
|
||||
const frameworkRow = useMemo(() => {
|
||||
const onFrameworkChanged = async (val: Framework) => {
|
||||
props.clientState.serverSetting.setFramework(val)
|
||||
appState.serverSetting.setFramework(val)
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Framework</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.serverSetting.setting.framework} onChange={(e) => {
|
||||
<select className="body-select" value={appState.serverSetting.setting.framework} onChange={(e) => {
|
||||
onFrameworkChanged(e.target.value as
|
||||
Framework)
|
||||
}}>
|
||||
@ -198,20 +236,20 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.framework, props.clientState.serverSetting.setFramework])
|
||||
}, [appState.serverSetting.setting.framework, appState.serverSetting.setFramework])
|
||||
|
||||
const onnxExecutionProviderRow = useMemo(() => {
|
||||
if (props.clientState.serverSetting.setting.framework != "ONNX") {
|
||||
if (appState.serverSetting.setting.framework != "ONNX") {
|
||||
return
|
||||
}
|
||||
const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => {
|
||||
props.clientState.serverSetting.setOnnxExecutionProvider(val)
|
||||
appState.serverSetting.setOnnxExecutionProvider(val)
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-item-title left-padding-2">OnnxExecutionProvider</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.serverSetting.setting.onnxExecutionProvider} onChange={(e) => {
|
||||
<select className="body-select" value={appState.serverSetting.setting.onnxExecutionProvider} onChange={(e) => {
|
||||
onOnnxExecutionProviderChanged(e.target.value as
|
||||
OnnxExecutionProvider)
|
||||
}}>
|
||||
@ -224,25 +262,34 @@ export const useServerSettingArea = (props: UseServerSettingProps): ServerSettin
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.framework, props.clientState.serverSetting.setting.onnxExecutionProvider, props.clientState.serverSetting.setOnnxExecutionProvider])
|
||||
}, [appState.serverSetting.setting.framework, appState.serverSetting.setting.onnxExecutionProvider, appState.serverSetting.setOnnxExecutionProvider])
|
||||
|
||||
const serverSetting = useMemo(() => {
|
||||
const modelSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Server Setting</div>
|
||||
<div className="body-select-container">
|
||||
{appState.frontendManagerState.stateControls.openModelSettingCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openModelSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openModelSettingCheckbox.checked()) }}>
|
||||
Model Setting
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{uploadeModelRow}
|
||||
{frameworkRow}
|
||||
{onnxExecutionProviderRow}
|
||||
</div>
|
||||
</div>
|
||||
{uploadeModelRow}
|
||||
{frameworkRow}
|
||||
{onnxExecutionProviderRow}
|
||||
</>
|
||||
)
|
||||
}, [uploadeModelRow, frameworkRow, onnxExecutionProviderRow])
|
||||
|
||||
|
||||
return {
|
||||
serverSetting,
|
||||
modelSetting,
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js"
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "./const"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
|
||||
const reloadDevices = async () => {
|
||||
@ -38,15 +39,25 @@ const reloadDevices = async () => {
|
||||
// })
|
||||
return [audioInputs, audioOutputs]
|
||||
}
|
||||
export type UseDeviceSettingProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export type DeviceSettingState = {
|
||||
deviceSetting: JSX.Element;
|
||||
}
|
||||
|
||||
export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDeviceSettingProps): DeviceSettingState => {
|
||||
export const useDeviceSetting = (): DeviceSettingState => {
|
||||
const appState = useAppState()
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openDeviceSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const [inputAudioDeviceInfo, setInputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
|
||||
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
|
||||
|
||||
@ -69,16 +80,16 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof props.clientState.clientSetting.setting.audioInput == "string") {
|
||||
if (typeof appState.clientSetting.setting.audioInput == "string") {
|
||||
if (inputAudioDeviceInfo.find(x => {
|
||||
// console.log("COMPARE:", x.deviceId, props.clientState.clientSetting.setting.audioInput)
|
||||
return x.deviceId == props.clientState.clientSetting.setting.audioInput
|
||||
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
|
||||
return x.deviceId == appState.clientSetting.setting.audioInput
|
||||
})) {
|
||||
setAudioInputForGUI(props.clientState.clientSetting.setting.audioInput)
|
||||
setAudioInputForGUI(appState.clientSetting.setting.audioInput)
|
||||
|
||||
}
|
||||
}
|
||||
}, [inputAudioDeviceInfo, props.clientState.clientSetting.setting.audioInput])
|
||||
}, [inputAudioDeviceInfo, appState.clientSetting.setting.audioInput])
|
||||
|
||||
const audioInputRow = useMemo(() => {
|
||||
return (
|
||||
@ -97,20 +108,16 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [inputAudioDeviceInfo, audioInputForGUI, props.clientState.clientSetting.setting.audioInput])
|
||||
}, [inputAudioDeviceInfo, audioInputForGUI, appState.clientSetting.setting.audioInput])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if (audioInputForGUI == "file") {
|
||||
// file selector (audioMediaInputRow)
|
||||
} else {
|
||||
props.clientState.clientSetting.setAudioInput(audioInputForGUI)
|
||||
appState.clientSetting.setAudioInput(audioInputForGUI)
|
||||
}
|
||||
}, [audioContext, audioInputForGUI, props.clientState.clientSetting.setAudioInput])
|
||||
}, [appState.audioContext, audioInputForGUI, appState.clientSetting.setAudioInput])
|
||||
|
||||
const audioMediaInputRow = useMemo(() => {
|
||||
if (audioInputForGUI != "file") {
|
||||
@ -127,15 +134,15 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
audio.src = url
|
||||
await audio.play()
|
||||
if (!audioSrcNode.current) {
|
||||
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
|
||||
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
if (audioSrcNode.current.mediaElement != audio) {
|
||||
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
|
||||
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
|
||||
const dst = audioContext!.createMediaStreamDestination()
|
||||
const dst = appState.audioContext.createMediaStreamDestination()
|
||||
audioSrcNode.current.connect(dst)
|
||||
props.clientState.clientSetting.setAudioInput(dst.stream)
|
||||
appState.clientSetting.setAudioInput(dst.stream)
|
||||
|
||||
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement
|
||||
audio_echo.srcObject = dst.stream
|
||||
@ -173,7 +180,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [audioInputForGUI, props.clientState.clientSetting.setAudioInput, fileInputEchoback])
|
||||
}, [audioInputForGUI, appState.clientSetting.setAudioInput, fileInputEchoback])
|
||||
|
||||
|
||||
|
||||
@ -204,11 +211,11 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
// }
|
||||
const onOutputRecordStartClicked = async () => {
|
||||
setOutputRecordingStarted(true)
|
||||
await props.clientState.workletSetting.startOutputRecording()
|
||||
await appState.workletSetting.startOutputRecording()
|
||||
}
|
||||
const onOutputRecordStopClicked = async () => {
|
||||
setOutputRecordingStarted(false)
|
||||
await props.clientState.workletSetting.stopOutputRecording()
|
||||
await appState.workletSetting.stopOutputRecording()
|
||||
}
|
||||
|
||||
const startClassName = outputRecordingStarted ? "body-button-active" : "body-button-stanby"
|
||||
@ -226,7 +233,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
</div>
|
||||
)
|
||||
|
||||
}, [audioOutputForGUI, outputRecordingStarted, props.clientState.workletSetting.startOutputRecording, props.clientState.workletSetting.stopOutputRecording])
|
||||
}, [audioOutputForGUI, outputRecordingStarted, appState.workletSetting.startOutputRecording, appState.workletSetting.stopOutputRecording])
|
||||
|
||||
useEffect(() => {
|
||||
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
|
||||
@ -277,15 +284,25 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
const deviceSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Device Setting</div>
|
||||
<div className="body-select-container">
|
||||
{appState.frontendManagerState.stateControls.openDeviceSettingCheckbox.trigger}
|
||||
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openDeviceSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openDeviceSettingCheckbox.checked()) }}>
|
||||
Device Setting
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{audioInputRow}
|
||||
{audioMediaInputRow}
|
||||
{audioOutputRow}
|
||||
{audioOutputRecordingRow}
|
||||
</div>
|
||||
</div>
|
||||
{audioInputRow}
|
||||
{audioMediaInputRow}
|
||||
{audioOutputRow}
|
||||
{audioOutputRecordingRow}
|
||||
</>
|
||||
)
|
||||
}, [audioInputRow, audioMediaInputRow, audioOutputRow, audioOutputRecordingRow])
|
||||
@ -293,10 +310,10 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
|
||||
// 出力の録音データ(from worklet)がストアされたら実行
|
||||
useEffect(() => {
|
||||
if (!props.clientState.outputRecordData || props.clientState.outputRecordData?.length == 0) {
|
||||
if (!appState.outputRecordData || appState.outputRecordData?.length == 0) {
|
||||
return
|
||||
}
|
||||
const f32Datas = props.clientState.outputRecordData
|
||||
const f32Datas = appState.outputRecordData
|
||||
const sampleSize = f32Datas.reduce((prev, cur) => {
|
||||
return prev + cur.length
|
||||
}, 0)
|
||||
@ -353,7 +370,7 @@ export const useDeviceSetting = (audioContext: AudioContext | null, props: UseDe
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
}, [props.clientState.outputRecordData])
|
||||
}, [appState.outputRecordData])
|
||||
|
||||
return {
|
||||
deviceSetting,
|
@ -1,177 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
|
||||
export type UseSpeakerSettingProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
|
||||
const [editSpeakerTargetId, setEditSpeakerTargetId] = useState<number>(0)
|
||||
const [editSpeakerTargetName, setEditSpeakerTargetName] = useState<string>("")
|
||||
|
||||
useEffect(() => {
|
||||
const src = props.clientState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == props.clientState.serverSetting.setting.srcId
|
||||
})
|
||||
const dst = props.clientState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == props.clientState.serverSetting.setting.dstId
|
||||
})
|
||||
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
|
||||
props.clientState.serverSetting.setF0Factor(recommendedF0Factor)
|
||||
|
||||
}, [props.clientState.serverSetting.setting.srcId, props.clientState.serverSetting.setting.dstId])
|
||||
|
||||
const srcIdRow = useMemo(() => {
|
||||
const selected = props.clientState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == props.clientState.serverSetting.setting.srcId
|
||||
})
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Source Speaker Id</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.serverSetting.setting.srcId} onChange={(e) => {
|
||||
props.clientState.serverSetting.setSrcId(Number(e.target.value))
|
||||
}}>
|
||||
{
|
||||
// props.clientState.clientSetting.setting.speakers.map(x => {
|
||||
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
|
||||
// })
|
||||
props.clientState.clientSetting.setting.correspondences?.map(x => {
|
||||
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
|
||||
})
|
||||
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.speakers, props.clientState.serverSetting.setting.srcId, props.clientState.clientSetting.setting.correspondences, props.clientState.serverSetting.setSrcId])
|
||||
|
||||
const dstIdRow = useMemo(() => {
|
||||
const selected = props.clientState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == props.clientState.serverSetting.setting.dstId
|
||||
})
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Destination Speaker Id</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.serverSetting.setting.dstId} onChange={(e) => {
|
||||
props.clientState.serverSetting.setDstId(Number(e.target.value))
|
||||
}}>
|
||||
{
|
||||
// props.clientState.clientSetting.setting.speakers.map(x => {
|
||||
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
|
||||
// })
|
||||
props.clientState.clientSetting.setting.correspondences?.map(x => {
|
||||
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.speakers, props.clientState.serverSetting.setting.dstId, props.clientState.clientSetting.setting.correspondences, props.clientState.serverSetting.setDstId])
|
||||
|
||||
const editSpeakerIdMappingRow = useMemo(() => {
|
||||
const onSetSpeakerMappingClicked = async () => {
|
||||
const targetId = editSpeakerTargetId
|
||||
const targetName = editSpeakerTargetName
|
||||
const targetSpeaker = props.clientState.clientSetting.setting.speakers.find(x => { return x.id == targetId })
|
||||
if (targetSpeaker) {
|
||||
if (targetName.length == 0) { // Delete
|
||||
const newSpeakers = props.clientState.clientSetting.setting.speakers.filter(x => { return x.id != targetId })
|
||||
props.clientState.clientSetting.setSpeakers(newSpeakers)
|
||||
} else { // Update
|
||||
targetSpeaker.name = targetName
|
||||
props.clientState.clientSetting.setSpeakers([...props.clientState.clientSetting.setting.speakers])
|
||||
}
|
||||
} else {
|
||||
if (targetName.length == 0) { // Noop
|
||||
} else {// add
|
||||
props.clientState.clientSetting.setting.speakers.push({
|
||||
id: targetId,
|
||||
name: targetName
|
||||
})
|
||||
props.clientState.clientSetting.setSpeakers([...props.clientState.clientSetting.setting.speakers])
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-1-2-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Edit Speaker Mapping</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={1} max={256} step={1} value={editSpeakerTargetId} onChange={(e) => {
|
||||
const id = Number(e.target.value)
|
||||
setEditSpeakerTargetId(id)
|
||||
setEditSpeakerTargetName(props.clientState.clientSetting.setting.speakers.find(x => { return x.id == id })?.name || "")
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-input-container">
|
||||
<input type="text" value={editSpeakerTargetName} onChange={(e) => {
|
||||
setEditSpeakerTargetName(e.target.value)
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onSetSpeakerMappingClicked}>set</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.speakers, editSpeakerTargetId, editSpeakerTargetName])
|
||||
|
||||
|
||||
const f0FactorRow = useMemo(() => {
|
||||
const src = props.clientState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == props.clientState.serverSetting.setting.srcId
|
||||
})
|
||||
const dst = props.clientState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == props.clientState.serverSetting.setting.dstId
|
||||
})
|
||||
|
||||
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">F0 Factor</div>
|
||||
<div className="body-input-container">
|
||||
<input type="range" className="body-item-input-slider" min="0.1" max="5.0" step="0.1" value={props.clientState.serverSetting.setting.f0Factor} onChange={(e) => {
|
||||
props.clientState.serverSetting.setF0Factor(Number(e.target.value))
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{props.clientState.serverSetting.setting.f0Factor.toFixed(1)}</span>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
<div className="body-item-text">recommend: {recommendedF0Factor.toFixed(1)}</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.f0Factor, props.clientState.serverSetting.setting.srcId, props.clientState.serverSetting.setting.dstId, props.clientState.clientSetting.setting.correspondences, props.clientState.serverSetting.setF0Factor])
|
||||
|
||||
const speakerSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Speaker Setting</div>
|
||||
<div className="body-select-container">
|
||||
</div>
|
||||
</div>
|
||||
{srcIdRow}
|
||||
{dstIdRow}
|
||||
{/* {editSpeakerIdMappingRow} */}
|
||||
{f0FactorRow}
|
||||
</>
|
||||
)
|
||||
}, [srcIdRow, dstIdRow, editSpeakerIdMappingRow, f0FactorRow])
|
||||
|
||||
return {
|
||||
speakerSetting,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,66 +0,0 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
|
||||
export type UseConvertSettingProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export type ConvertSettingState = {
|
||||
convertSetting: JSX.Element;
|
||||
}
|
||||
|
||||
export const useConvertSetting = (props: UseConvertSettingProps): ConvertSettingState => {
|
||||
const inputChunkNumRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Input Chunk Num(128sample/chunk)</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={1} max={256} step={1} value={props.clientState.clientSetting.setting.inputChunkNum} onChange={(e) => {
|
||||
props.clientState.clientSetting.setInputChunkNum(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>buff: {(props.clientState.clientSetting.setting.inputChunkNum * 128 * 1000 / 24000).toFixed(1)}ms</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.inputChunkNum, props.clientState.clientSetting.setInputChunkNum])
|
||||
|
||||
const gpuRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">GPU</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={-2} max={5} step={1} value={props.clientState.serverSetting.setting.gpu} onChange={(e) => {
|
||||
props.clientState.serverSetting.setGpu(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.gpu, props.clientState.serverSetting.setGpu])
|
||||
|
||||
|
||||
const convertSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Converter Setting</div>
|
||||
<div className="body-select-container">
|
||||
</div>
|
||||
</div>
|
||||
{inputChunkNumRow}
|
||||
{gpuRow}
|
||||
|
||||
</>
|
||||
)
|
||||
}, [inputChunkNumRow, gpuRow])
|
||||
|
||||
return {
|
||||
convertSetting,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { BufferSize, DownSamplingMode, F0Detector, Protocol, SampleRate, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
|
||||
import { F0Detector } from "@dannadori/voice-changer-client-js"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
|
||||
export type UseQualityControlProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export type QualityControlState = {
|
||||
qualityControl: JSX.Element;
|
||||
@ -24,8 +22,20 @@ const reloadDevices = async () => {
|
||||
}
|
||||
|
||||
|
||||
export const useQualityControl = (props: UseQualityControlProps): QualityControlState => {
|
||||
const [showQualityControl, setShowQualityControl] = useState<boolean>(false)
|
||||
export const useQualityControl = (): QualityControlState => {
|
||||
const appState = useAppState()
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openQualityControlCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
|
||||
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none")
|
||||
useEffect(() => {
|
||||
@ -42,18 +52,18 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
<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={props.clientState.clientSetting.setting.echoCancel} onChange={(e) => {
|
||||
props.clientState.clientSetting.setEchoCancel(e.target.checked)
|
||||
<input type="checkbox" checked={appState.clientSetting.setting.echoCancel} onChange={(e) => {
|
||||
appState.clientSetting.setEchoCancel(e.target.checked)
|
||||
}} /> echo cancel
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={props.clientState.clientSetting.setting.noiseSuppression} onChange={(e) => {
|
||||
props.clientState.clientSetting.setNoiseSuppression(e.target.checked)
|
||||
<input type="checkbox" checked={appState.clientSetting.setting.noiseSuppression} onChange={(e) => {
|
||||
appState.clientSetting.setNoiseSuppression(e.target.checked)
|
||||
}} /> suppression1
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={props.clientState.clientSetting.setting.noiseSuppression2} onChange={(e) => {
|
||||
props.clientState.clientSetting.setNoiseSuppression2(e.target.checked)
|
||||
<input type="checkbox" checked={appState.clientSetting.setting.noiseSuppression2} onChange={(e) => {
|
||||
appState.clientSetting.setNoiseSuppression2(e.target.checked)
|
||||
}} /> suppression2
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
@ -61,9 +71,9 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
props.clientState.clientSetting.setting.echoCancel, props.clientState.clientSetting.setEchoCancel,
|
||||
props.clientState.clientSetting.setting.noiseSuppression, props.clientState.clientSetting.setNoiseSuppression,
|
||||
props.clientState.clientSetting.setting.noiseSuppression2, props.clientState.clientSetting.setNoiseSuppression2,
|
||||
appState.clientSetting.setting.echoCancel, appState.clientSetting.setEchoCancel,
|
||||
appState.clientSetting.setting.noiseSuppression, appState.clientSetting.setNoiseSuppression,
|
||||
appState.clientSetting.setting.noiseSuppression2, appState.clientSetting.setNoiseSuppression2,
|
||||
])
|
||||
|
||||
const gainControlRow = useMemo(() => {
|
||||
@ -72,25 +82,25 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
<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="1.0" step="0.1" value={props.clientState.clientSetting.setting.inputGain} onChange={(e) => {
|
||||
props.clientState.clientSetting.setInputGain(Number(e.target.value))
|
||||
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={appState.clientSetting.setting.inputGain} onChange={(e) => {
|
||||
appState.clientSetting.setInputGain(Number(e.target.value))
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{props.clientState.clientSetting.setting.inputGain}</span>
|
||||
<span className="body-item-input-slider-val">{appState.clientSetting.setting.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="1.0" step="0.1" value={props.clientState.clientSetting.setting.outputGain} onChange={(e) => {
|
||||
props.clientState.clientSetting.setOutputGain(Number(e.target.value))
|
||||
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={appState.clientSetting.setting.outputGain} onChange={(e) => {
|
||||
appState.clientSetting.setOutputGain(Number(e.target.value))
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{props.clientState.clientSetting.setting.outputGain}</span>
|
||||
<span className="body-item-input-slider-val">{appState.clientSetting.setting.outputGain}</span>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
props.clientState.clientSetting.setting.inputGain, props.clientState.clientSetting.setting.inputGain,
|
||||
props.clientState.clientSetting.setting.outputGain, props.clientState.clientSetting.setOutputGain,
|
||||
appState.clientSetting.setting.inputGain, appState.clientSetting.setting.inputGain,
|
||||
appState.clientSetting.setting.outputGain, appState.clientSetting.setOutputGain,
|
||||
])
|
||||
|
||||
const f0DetectorRow = useMemo(() => {
|
||||
@ -99,8 +109,8 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
<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={props.clientState.serverSetting.setting.f0Detector} onChange={(e) => {
|
||||
props.clientState.serverSetting.setF0Detector(e.target.value as F0Detector)
|
||||
<select className="body-select" value={appState.serverSetting.setting.f0Detector} onChange={(e) => {
|
||||
appState.serverSetting.setF0Detector(e.target.value as F0Detector)
|
||||
}}>
|
||||
{
|
||||
Object.values(F0Detector).map(x => {
|
||||
@ -112,12 +122,12 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.f0Detector, props.clientState.serverSetting.setF0Detector])
|
||||
}, [appState.serverSetting.setting.f0Detector, appState.serverSetting.setF0Detector])
|
||||
|
||||
|
||||
const recordIORow = useMemo(() => {
|
||||
const setReocrdIO = async (val: number) => {
|
||||
await props.clientState.serverSetting.setRecordIO(val)
|
||||
await appState.serverSetting.setRecordIO(val)
|
||||
if (val == 0) {
|
||||
const imageContainer = document.getElementById("quality-control-analyze-image-container") as HTMLDivElement
|
||||
imageContainer.innerHTML = ""
|
||||
@ -149,7 +159,7 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">recordIO</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.serverSetting.setting.recordIO} onChange={(e) => {
|
||||
<select className="body-select" value={appState.serverSetting.setting.recordIO} onChange={(e) => {
|
||||
setReocrdIO(Number(e.target.value))
|
||||
}}>
|
||||
{
|
||||
@ -199,10 +209,9 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.recordIO, props.clientState.serverSetting.setRecordIO, outputAudioDeviceInfo, audioOutputForGUI])
|
||||
}, [appState.serverSetting.setting.recordIO, appState.serverSetting.setRecordIO, outputAudioDeviceInfo, audioOutputForGUI])
|
||||
|
||||
const QualityControlContent = useMemo(() => {
|
||||
if (!showQualityControl) return <></>
|
||||
return (
|
||||
<>
|
||||
{noiseControlRow}
|
||||
@ -211,24 +220,30 @@ export const useQualityControl = (props: UseQualityControlProps): QualityControl
|
||||
{recordIORow}
|
||||
</>
|
||||
)
|
||||
}, [showQualityControl, gainControlRow, noiseControlRow, f0DetectorRow, recordIORow,])
|
||||
}, [gainControlRow, noiseControlRow, f0DetectorRow, recordIORow])
|
||||
|
||||
|
||||
const qualityControl = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Quality Control</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={showQualityControl} onChange={(e) => {
|
||||
setShowQualityControl(e.target.checked)
|
||||
}} /> show
|
||||
{appState.frontendManagerState.stateControls.openQualityControlCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openQualityControlCheckbox.updateState(!appState.frontendManagerState.stateControls.openQualityControlCheckbox.checked()) }}>
|
||||
Quality Control
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{QualityControlContent}
|
||||
</div>
|
||||
</div>
|
||||
{QualityControlContent}
|
||||
</>
|
||||
)
|
||||
}, [showQualityControl, QualityControlContent])
|
||||
}, [QualityControlContent])
|
||||
|
||||
return {
|
||||
qualityControl,
|
195
client/demo/src/105_speaker_setting.tsx
Normal file
195
client/demo/src/105_speaker_setting.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export const useSpeakerSetting = () => {
|
||||
const appState = useAppState()
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const [editSpeakerTargetId, setEditSpeakerTargetId] = useState<number>(0)
|
||||
const [editSpeakerTargetName, setEditSpeakerTargetName] = useState<string>("")
|
||||
|
||||
useEffect(() => {
|
||||
const src = appState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == appState.serverSetting.setting.srcId
|
||||
})
|
||||
const dst = appState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == appState.serverSetting.setting.dstId
|
||||
})
|
||||
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
|
||||
appState.serverSetting.setF0Factor(recommendedF0Factor)
|
||||
|
||||
}, [appState.serverSetting.setting.srcId, appState.serverSetting.setting.dstId])
|
||||
|
||||
const srcIdRow = useMemo(() => {
|
||||
const selected = appState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == appState.serverSetting.setting.srcId
|
||||
})
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Source Speaker Id</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.setting.srcId} onChange={(e) => {
|
||||
appState.serverSetting.setSrcId(Number(e.target.value))
|
||||
}}>
|
||||
{
|
||||
// appState.clientSetting.setting.speakers.map(x => {
|
||||
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
|
||||
// })
|
||||
appState.clientSetting.setting.correspondences?.map(x => {
|
||||
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
|
||||
})
|
||||
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.clientSetting.setting.speakers, appState.serverSetting.setting.srcId, appState.clientSetting.setting.correspondences, appState.serverSetting.setSrcId])
|
||||
|
||||
const dstIdRow = useMemo(() => {
|
||||
const selected = appState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == appState.serverSetting.setting.dstId
|
||||
})
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Destination Speaker Id</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.setting.dstId} onChange={(e) => {
|
||||
appState.serverSetting.setDstId(Number(e.target.value))
|
||||
}}>
|
||||
{
|
||||
// appState.clientSetting.setting.speakers.map(x => {
|
||||
// return <option key={x.id} value={x.id}>{x.name}({x.id})</option>
|
||||
// })
|
||||
appState.clientSetting.setting.correspondences?.map(x => {
|
||||
return <option key={x.sid} value={x.sid}>{x.dirname}({x.sid})</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>F0: {selected?.correspondence.toFixed(1) || ""}</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.clientSetting.setting.speakers, appState.serverSetting.setting.dstId, appState.clientSetting.setting.correspondences, appState.serverSetting.setDstId])
|
||||
|
||||
const editSpeakerIdMappingRow = useMemo(() => {
|
||||
const onSetSpeakerMappingClicked = async () => {
|
||||
const targetId = editSpeakerTargetId
|
||||
const targetName = editSpeakerTargetName
|
||||
const targetSpeaker = appState.clientSetting.setting.speakers.find(x => { return x.id == targetId })
|
||||
if (targetSpeaker) {
|
||||
if (targetName.length == 0) { // Delete
|
||||
const newSpeakers = appState.clientSetting.setting.speakers.filter(x => { return x.id != targetId })
|
||||
appState.clientSetting.setSpeakers(newSpeakers)
|
||||
} else { // Update
|
||||
targetSpeaker.name = targetName
|
||||
appState.clientSetting.setSpeakers([...appState.clientSetting.setting.speakers])
|
||||
}
|
||||
} else {
|
||||
if (targetName.length == 0) { // Noop
|
||||
} else {// add
|
||||
appState.clientSetting.setting.speakers.push({
|
||||
id: targetId,
|
||||
name: targetName
|
||||
})
|
||||
appState.clientSetting.setSpeakers([...appState.clientSetting.setting.speakers])
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-1-2-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Edit Speaker Mapping</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={1} max={256} step={1} value={editSpeakerTargetId} onChange={(e) => {
|
||||
const id = Number(e.target.value)
|
||||
setEditSpeakerTargetId(id)
|
||||
setEditSpeakerTargetName(appState.clientSetting.setting.speakers.find(x => { return x.id == id })?.name || "")
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-input-container">
|
||||
<input type="text" value={editSpeakerTargetName} onChange={(e) => {
|
||||
setEditSpeakerTargetName(e.target.value)
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onSetSpeakerMappingClicked}>set</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.clientSetting.setting.speakers, editSpeakerTargetId, editSpeakerTargetName])
|
||||
|
||||
|
||||
const f0FactorRow = useMemo(() => {
|
||||
const src = appState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == appState.serverSetting.setting.srcId
|
||||
})
|
||||
const dst = appState.clientSetting.setting.correspondences?.find(x => {
|
||||
return x.sid == appState.serverSetting.setting.dstId
|
||||
})
|
||||
|
||||
const recommendedF0Factor = dst && src ? dst.correspondence / src.correspondence : 0
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">F0 Factor</div>
|
||||
<div className="body-input-container">
|
||||
<input type="range" className="body-item-input-slider" min="0.1" max="5.0" step="0.1" value={appState.serverSetting.setting.f0Factor} onChange={(e) => {
|
||||
appState.serverSetting.setF0Factor(Number(e.target.value))
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.serverSetting.setting.f0Factor.toFixed(1)}</span>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
<div className="body-item-text">recommend: {recommendedF0Factor.toFixed(1)}</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.setting.f0Factor, appState.serverSetting.setting.srcId, appState.serverSetting.setting.dstId, appState.clientSetting.setting.correspondences, appState.serverSetting.setF0Factor])
|
||||
|
||||
const speakerSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openSpeakerSettingCheckbox.checked()) }}>
|
||||
Speaker Setting
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{srcIdRow}
|
||||
{dstIdRow}
|
||||
{f0FactorRow}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [srcIdRow, dstIdRow, editSpeakerIdMappingRow, f0FactorRow])
|
||||
|
||||
return {
|
||||
speakerSetting,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
84
client/demo/src/106_convert_setting.tsx
Normal file
84
client/demo/src/106_convert_setting.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
|
||||
export type ConvertSettingState = {
|
||||
convertSetting: JSX.Element;
|
||||
}
|
||||
|
||||
export const useConvertSetting = (): ConvertSettingState => {
|
||||
const appState = useAppState()
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openConverterSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
const inputChunkNumRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Input Chunk Num(128sample/chunk)</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={1} max={256} step={1} value={appState.clientSetting.setting.inputChunkNum} onChange={(e) => {
|
||||
appState.clientSetting.setInputChunkNum(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>buff: {(appState.clientSetting.setting.inputChunkNum * 128 * 1000 / 24000).toFixed(1)}ms</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}, [appState.clientSetting.setting.inputChunkNum, appState.clientSetting.setInputChunkNum])
|
||||
|
||||
const gpuRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">GPU</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={-2} max={5} step={1} value={appState.serverSetting.setting.gpu} onChange={(e) => {
|
||||
appState.serverSetting.setGpu(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.setting.gpu, appState.serverSetting.setGpu])
|
||||
|
||||
|
||||
const convertSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{appState.frontendManagerState.stateControls.openConverterSettingCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openConverterSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openConverterSettingCheckbox.checked()) }}>
|
||||
Converter Setting
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{inputChunkNumRow}
|
||||
{gpuRow}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [inputChunkNumRow, gpuRow])
|
||||
|
||||
return {
|
||||
convertSetting,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,45 +1,54 @@
|
||||
import { BufferSize, DownSamplingMode, F0Detector, Protocol, SampleRate, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
|
||||
import { BufferSize, DownSamplingMode, Protocol, SampleRate, VoiceChangerMode } from "@dannadori/voice-changer-client-js"
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
|
||||
|
||||
export type UseAdvancedSettingProps = {
|
||||
clientState: ClientState
|
||||
}
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export type AdvancedSettingState = {
|
||||
advancedSetting: JSX.Element;
|
||||
}
|
||||
|
||||
export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSettingState => {
|
||||
const [showAdvancedSetting, setShowAdvancedSetting] = useState<boolean>(false)
|
||||
export const useAdvancedSetting = (): AdvancedSettingState => {
|
||||
const appState = useAppState()
|
||||
const accodionButton = useMemo(() => {
|
||||
const accodionButtonProps: HeaderButtonProps = {
|
||||
stateControlCheckbox: appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox,
|
||||
tooltip: "Open/Close",
|
||||
onIcon: ["fas", "caret-up"],
|
||||
offIcon: ["fas", "caret-up"],
|
||||
animation: AnimationTypes.spinner,
|
||||
tooltipClass: "tooltip-right",
|
||||
};
|
||||
return <HeaderButton {...accodionButtonProps}></HeaderButton>;
|
||||
}, []);
|
||||
|
||||
const mmvcServerUrlRow = useMemo(() => {
|
||||
const onSetServerClicked = async () => {
|
||||
const input = document.getElementById("mmvc-server-url") as HTMLInputElement
|
||||
props.clientState.clientSetting.setServerUrl(input.value)
|
||||
appState.clientSetting.setServerUrl(input.value)
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">MMVC Server</div>
|
||||
<div className="body-input-container">
|
||||
<input type="text" defaultValue={props.clientState.clientSetting.setting.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
|
||||
<input type="text" defaultValue={appState.clientSetting.setting.mmvcServerUrl} id="mmvc-server-url" className="body-item-input" />
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onSetServerClicked}>set</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.mmvcServerUrl, props.clientState.clientSetting.setServerUrl])
|
||||
}, [appState.clientSetting.setting.mmvcServerUrl, appState.clientSetting.setServerUrl])
|
||||
|
||||
const protocolRow = useMemo(() => {
|
||||
const onProtocolChanged = async (val: Protocol) => {
|
||||
props.clientState.clientSetting.setProtocol(val)
|
||||
appState.clientSetting.setProtocol(val)
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Protocol</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.clientSetting.setting.protocol} onChange={(e) => {
|
||||
<select className="body-select" value={appState.clientSetting.setting.protocol} onChange={(e) => {
|
||||
onProtocolChanged(e.target.value as
|
||||
Protocol)
|
||||
}}>
|
||||
@ -52,7 +61,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.protocol, props.clientState.clientSetting.setProtocol])
|
||||
}, [appState.clientSetting.setting.protocol, appState.clientSetting.setProtocol])
|
||||
|
||||
|
||||
const sampleRateRow = useMemo(() => {
|
||||
@ -60,8 +69,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Sample Rate</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.clientSetting.setting.sampleRate} onChange={(e) => {
|
||||
props.clientState.clientSetting.setSampleRate(Number(e.target.value) as SampleRate)
|
||||
<select className="body-select" value={appState.clientSetting.setting.sampleRate} onChange={(e) => {
|
||||
appState.clientSetting.setSampleRate(Number(e.target.value) as SampleRate)
|
||||
}}>
|
||||
{
|
||||
Object.values(SampleRate).map(x => {
|
||||
@ -72,7 +81,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.sampleRate, props.clientState.clientSetting.setSampleRate])
|
||||
}, [appState.clientSetting.setting.sampleRate, appState.clientSetting.setSampleRate])
|
||||
|
||||
const bufferSizeRow = useMemo(() => {
|
||||
return (
|
||||
@ -80,8 +89,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Buffer Size</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.clientSetting.setting.bufferSize} onChange={(e) => {
|
||||
props.clientState.clientSetting.setBufferSize(Number(e.target.value) as BufferSize)
|
||||
<select className="body-select" value={appState.clientSetting.setting.bufferSize} onChange={(e) => {
|
||||
appState.clientSetting.setBufferSize(Number(e.target.value) as BufferSize)
|
||||
}}>
|
||||
{
|
||||
Object.values(BufferSize).map(x => {
|
||||
@ -92,7 +101,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.bufferSize, props.clientState.clientSetting.setBufferSize])
|
||||
}, [appState.clientSetting.setting.bufferSize, appState.clientSetting.setBufferSize])
|
||||
|
||||
const convertChunkNumRow = useMemo(() => {
|
||||
return (
|
||||
@ -100,13 +109,13 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Convert Chunk Num(128sample/chunk)</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={1} max={256} step={1} value={props.clientState.serverSetting.setting.convertChunkNum} onChange={(e) => {
|
||||
props.clientState.serverSetting.setConvertChunkNum(Number(e.target.value))
|
||||
<input type="number" min={1} max={256} step={1} value={appState.serverSetting.setting.convertChunkNum} onChange={(e) => {
|
||||
appState.serverSetting.setConvertChunkNum(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.convertChunkNum, props.clientState.serverSetting.setConvertChunkNum])
|
||||
}, [appState.serverSetting.setting.convertChunkNum, appState.serverSetting.setConvertChunkNum])
|
||||
|
||||
const minConvertSizeRow = useMemo(() => {
|
||||
return (
|
||||
@ -114,52 +123,52 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Min Convert Size(byte)</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={0} max={8196} step={8196} value={props.clientState.serverSetting.setting.minConvertSize} onChange={(e) => {
|
||||
props.clientState.serverSetting.setMinConvertSize(Number(e.target.value))
|
||||
<input type="number" min={0} max={8196} step={8196} value={appState.serverSetting.setting.minConvertSize} onChange={(e) => {
|
||||
appState.serverSetting.setMinConvertSize(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.minConvertSize, props.clientState.serverSetting.setMinConvertSize])
|
||||
}, [appState.serverSetting.setting.minConvertSize, appState.serverSetting.setMinConvertSize])
|
||||
|
||||
const crossFadeOverlapRateRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Cross Fade Overlap Rate</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={0.1} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeOverlapRate} onChange={(e) => {
|
||||
props.clientState.serverSetting.setCrossFadeOverlapRate(Number(e.target.value))
|
||||
<input type="number" min={0.1} max={1} step={0.1} value={appState.serverSetting.setting.crossFadeOverlapRate} onChange={(e) => {
|
||||
appState.serverSetting.setCrossFadeOverlapRate(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.crossFadeOverlapRate, props.clientState.serverSetting.setCrossFadeOverlapRate])
|
||||
}, [appState.serverSetting.setting.crossFadeOverlapRate, appState.serverSetting.setCrossFadeOverlapRate])
|
||||
|
||||
const crossFadeOffsetRateRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Cross Fade Offset Rate</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={0} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeOffsetRate} onChange={(e) => {
|
||||
props.clientState.serverSetting.setCrossFadeOffsetRate(Number(e.target.value))
|
||||
<input type="number" min={0} max={1} step={0.1} value={appState.serverSetting.setting.crossFadeOffsetRate} onChange={(e) => {
|
||||
appState.serverSetting.setCrossFadeOffsetRate(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.crossFadeOffsetRate, props.clientState.serverSetting.setCrossFadeOffsetRate])
|
||||
}, [appState.serverSetting.setting.crossFadeOffsetRate, appState.serverSetting.setCrossFadeOffsetRate])
|
||||
|
||||
const crossFadeEndRateRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Cross Fade End Rate</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={0} max={1} step={0.1} value={props.clientState.serverSetting.setting.crossFadeEndRate} onChange={(e) => {
|
||||
props.clientState.serverSetting.setCrossFadeEndRate(Number(e.target.value))
|
||||
<input type="number" min={0} max={1} step={0.1} value={appState.serverSetting.setting.crossFadeEndRate} onChange={(e) => {
|
||||
appState.serverSetting.setCrossFadeEndRate(Number(e.target.value))
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.serverSetting.setting.crossFadeEndRate, props.clientState.serverSetting.setCrossFadeEndRate])
|
||||
}, [appState.serverSetting.setting.crossFadeEndRate, appState.serverSetting.setCrossFadeEndRate])
|
||||
|
||||
|
||||
const voiceChangeModeRow = useMemo(() => {
|
||||
@ -167,8 +176,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Voice Change Mode</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.clientSetting.setting.voiceChangerMode} onChange={(e) => {
|
||||
props.clientState.clientSetting.setVoiceChangerMode(e.target.value as VoiceChangerMode)
|
||||
<select className="body-select" value={appState.clientSetting.setting.voiceChangerMode} onChange={(e) => {
|
||||
appState.clientSetting.setVoiceChangerMode(e.target.value as VoiceChangerMode)
|
||||
}}>
|
||||
{
|
||||
Object.values(VoiceChangerMode).map(x => {
|
||||
@ -179,7 +188,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.voiceChangerMode, props.clientState.clientSetting.setVoiceChangerMode])
|
||||
}, [appState.clientSetting.setting.voiceChangerMode, appState.clientSetting.setVoiceChangerMode])
|
||||
|
||||
|
||||
const downSamplingModeRow = useMemo(() => {
|
||||
@ -187,8 +196,8 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">DownSamplingMode</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={props.clientState.clientSetting.setting.downSamplingMode} onChange={(e) => {
|
||||
props.clientState.clientSetting.setDownSamplingMode(e.target.value as DownSamplingMode)
|
||||
<select className="body-select" value={appState.clientSetting.setting.downSamplingMode} onChange={(e) => {
|
||||
appState.clientSetting.setDownSamplingMode(e.target.value as DownSamplingMode)
|
||||
}}>
|
||||
{
|
||||
Object.values(DownSamplingMode).map(x => {
|
||||
@ -199,7 +208,7 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [props.clientState.clientSetting.setting.downSamplingMode, props.clientState.clientSetting.setDownSamplingMode])
|
||||
}, [appState.clientSetting.setting.downSamplingMode, appState.clientSetting.setDownSamplingMode])
|
||||
|
||||
|
||||
const workletSettingRow = useMemo(() => {
|
||||
@ -209,9 +218,9 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Trancate Num</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={5} max={300} step={1} value={props.clientState.workletSetting.setting.numTrancateTreshold} onChange={(e) => {
|
||||
props.clientState.workletSetting.setSetting({
|
||||
...props.clientState.workletSetting.setting,
|
||||
<input type="number" min={5} max={300} step={1} value={appState.workletSetting.setting.numTrancateTreshold} onChange={(e) => {
|
||||
appState.workletSetting.setSetting({
|
||||
...appState.workletSetting.setting,
|
||||
numTrancateTreshold: Number(e.target.value)
|
||||
})
|
||||
}} />
|
||||
@ -222,9 +231,9 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
{/* <div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Trancate Vol</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={0.0001} max={0.0009} step={0.0001} value={props.clientState.workletSetting.setting.volTrancateThreshold} onChange={(e) => {
|
||||
props.clientState.workletSetting.setSetting({
|
||||
...props.clientState.workletSetting.setting,
|
||||
<input type="number" min={0.0001} max={0.0009} step={0.0001} value={appState.workletSetting.setting.volTrancateThreshold} onChange={(e) => {
|
||||
appState.workletSetting.setSetting({
|
||||
...appState.workletSetting.setting,
|
||||
volTrancateThreshold: Number(e.target.value)
|
||||
})
|
||||
}} />
|
||||
@ -233,9 +242,9 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Trancate Vol Length</div>
|
||||
<div className="body-input-container">
|
||||
<input type="number" min={16} max={128} step={1} value={props.clientState.workletSetting.setting.volTrancateLength} onChange={(e) => {
|
||||
props.clientState.workletSetting.setSetting({
|
||||
...props.clientState.workletSetting.setting,
|
||||
<input type="number" min={16} max={128} step={1} value={appState.workletSetting.setting.volTrancateLength} onChange={(e) => {
|
||||
appState.workletSetting.setSetting({
|
||||
...appState.workletSetting.setting,
|
||||
volTrancateLength: Number(e.target.value)
|
||||
})
|
||||
}} />
|
||||
@ -243,11 +252,10 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
</div> */}
|
||||
</>
|
||||
)
|
||||
}, [props.clientState.workletSetting.setting, props.clientState.workletSetting.setSetting])
|
||||
}, [appState.workletSetting.setting, appState.workletSetting.setSetting])
|
||||
|
||||
|
||||
const advanceSettingContent = useMemo(() => {
|
||||
if (!showAdvancedSetting) return <></>
|
||||
return (
|
||||
<>
|
||||
<div className="body-row divider"></div>
|
||||
@ -272,24 +280,30 @@ export const useAdvancedSetting = (props: UseAdvancedSettingProps): AdvancedSett
|
||||
|
||||
</>
|
||||
)
|
||||
}, [showAdvancedSetting, mmvcServerUrlRow, protocolRow, sampleRateRow, bufferSizeRow, convertChunkNumRow, minConvertSizeRow, crossFadeOverlapRateRow, crossFadeOffsetRateRow, crossFadeEndRateRow, voiceChangeModeRow, workletSettingRow, downSamplingModeRow])
|
||||
}, [mmvcServerUrlRow, protocolRow, sampleRateRow, bufferSizeRow, convertChunkNumRow, minConvertSizeRow, crossFadeOverlapRateRow, crossFadeOffsetRateRow, crossFadeEndRateRow, voiceChangeModeRow, workletSettingRow, downSamplingModeRow])
|
||||
|
||||
|
||||
const advancedSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-sub-section-title">Advanced Setting</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={showAdvancedSetting} onChange={(e) => {
|
||||
setShowAdvancedSetting(e.target.checked)
|
||||
}} /> show
|
||||
{appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox.trigger}
|
||||
<div className="partition">
|
||||
<div className="partition-header">
|
||||
<span className="caret">
|
||||
{accodionButton}
|
||||
</span>
|
||||
<span className="title" onClick={() => { appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox.updateState(!appState.frontendManagerState.stateControls.openAdvancedSettingCheckbox.checked()) }}>
|
||||
Advanced Setting
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{advanceSettingContent}
|
||||
</div>
|
||||
</div>
|
||||
{advanceSettingContent}
|
||||
</>
|
||||
)
|
||||
}, [showAdvancedSetting, advanceSettingContent])
|
||||
}, [advanceSettingContent])
|
||||
|
||||
return {
|
||||
advancedSetting,
|
37
client/demo/src/components/101_HeaderButton.tsx
Normal file
37
client/demo/src/components/101_HeaderButton.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { IconName, IconPrefix } from "@fortawesome/free-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React, { useMemo } from "react";
|
||||
import { StateControlCheckbox } from "../hooks/useStateControlCheckbox";
|
||||
|
||||
export const AnimationTypes = {
|
||||
colored: "colored",
|
||||
spinner: "spinner",
|
||||
} as const;
|
||||
export type AnimationTypes = typeof AnimationTypes[keyof typeof AnimationTypes];
|
||||
|
||||
export type HeaderButtonProps = {
|
||||
stateControlCheckbox: StateControlCheckbox;
|
||||
tooltip: string;
|
||||
onIcon: [IconPrefix, IconName];
|
||||
offIcon: [IconPrefix, IconName];
|
||||
animation: AnimationTypes;
|
||||
tooltipClass?: string;
|
||||
};
|
||||
|
||||
export const HeaderButton = (props: HeaderButtonProps) => {
|
||||
const headerButton = useMemo(() => {
|
||||
const tooltipClass = props.tooltipClass || "tooltip-bottom";
|
||||
return (
|
||||
<div className={`rotate-button-container ${tooltipClass}`} data-tooltip={props.tooltip}>
|
||||
{props.stateControlCheckbox.trigger}
|
||||
<label htmlFor={props.stateControlCheckbox.className} className="rotate-lable">
|
||||
<div className={props.animation}>
|
||||
<FontAwesomeIcon icon={props.onIcon} className="spin-on" />
|
||||
<FontAwesomeIcon icon={props.offIcon} className="spin-off" />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
return headerButton;
|
||||
};
|
@ -6,4 +6,19 @@ export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"
|
||||
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback"
|
||||
|
||||
|
||||
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_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"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
70
client/demo/src/css/101_RotatedButton.css
Normal file
70
client/demo/src/css/101_RotatedButton.css
Normal file
@ -0,0 +1,70 @@
|
||||
/* 前提条件 */
|
||||
|
||||
.rotate-button-container {
|
||||
height: var(--header-height);
|
||||
width: var(--header-height);
|
||||
position: relative;
|
||||
}
|
||||
.rotate-button {
|
||||
display: none;
|
||||
}
|
||||
.rotate-button ~ .rotate-lable {
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
height: var(--header-height);
|
||||
width: var(--header-height);
|
||||
}
|
||||
.rotate-button ~ .rotate-lable > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
float: left;
|
||||
transition: all 0.3s;
|
||||
.spin-on {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
.spin-off {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: blcok;
|
||||
}
|
||||
}
|
||||
.rotate-button ~ .rotate-lable > .colored {
|
||||
color: rgba(200, 200, 200, 0.8);
|
||||
background: rgba(0, 0, 0, 1);
|
||||
transition: all 0.3s;
|
||||
.spin-on {
|
||||
display: none;
|
||||
}
|
||||
.spin-off {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.rotate-button:checked ~ .rotate-lable > .colored {
|
||||
color: rgba(50, 240, 50, 0.8);
|
||||
background: rgba(60, 60, 60, 1);
|
||||
transition: all 0.3s;
|
||||
.spin-on {
|
||||
display: block;
|
||||
}
|
||||
.spin-off {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rotate-button:checked ~ .rotate-lable > .spinner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(180deg);
|
||||
transition: all 0.3s;
|
||||
box-sizing: border-box;
|
||||
.spin-on {
|
||||
display: block;
|
||||
}
|
||||
.spin-off {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Chicle&family=Poppins:ital,wght@0,200;0,400;0,600;1,200;1,400;1,600&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Yusei+Magic&display=swap");
|
||||
|
||||
@import "./101_RotatedButton.css";
|
||||
|
||||
:root {
|
||||
--text-color: #333;
|
||||
--company-color1: rgba(64, 119, 187, 1);
|
||||
@ -11,7 +13,7 @@
|
||||
--company-color3-alpha: rgba(255, 255, 255, 0.3);
|
||||
--global-shadow-color: rgba(0, 0, 0, 0.4);
|
||||
|
||||
--sidebar-transition-time: 0.3s;
|
||||
--sidebar-transition-time: 0.2s;
|
||||
--sidebar-transition-time-quick: 0.1s;
|
||||
--sidebar-transition-animation: ease-in-out;
|
||||
|
||||
@ -52,6 +54,8 @@ body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Main + Section Partition*/
|
||||
.main-body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@ -60,9 +64,61 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
user-select: none;
|
||||
/* Title */
|
||||
.top-title {
|
||||
.title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.top-title-version {
|
||||
margin-left: 2rem;
|
||||
font-size: 1.2rem;
|
||||
background: linear-gradient(transparent 60%, yellow 30%);
|
||||
}
|
||||
.belongings {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
.link {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Partition */
|
||||
.partition {
|
||||
width: 100%;
|
||||
.partition-header {
|
||||
font-weight: 700;
|
||||
color: rgb(71, 69, 69);
|
||||
display: flex;
|
||||
.caret {
|
||||
width: 2rem;
|
||||
}
|
||||
.title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
.partition-content {
|
||||
position: static;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.row-split {
|
||||
}
|
||||
}
|
||||
}
|
||||
.body-row {
|
||||
.state-control-checkbox:checked + .partition .partition-content {
|
||||
max-height: 700px;
|
||||
background: #fff;
|
||||
transition: all var(--sidebar-transition-time) var(--sidebar-transition-animation);
|
||||
}
|
||||
.state-control-checkbox + .partition .partition-content {
|
||||
max-height: 0px;
|
||||
background: #eef;
|
||||
transition: all var(--sidebar-transition-time) var(--sidebar-transition-animation);
|
||||
}
|
||||
|
||||
/* ROW */
|
||||
|
||||
.split-6-4 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -332,32 +388,6 @@ body {
|
||||
background-color: rgba(31, 42, 36, 0.1);
|
||||
}
|
||||
|
||||
.body-top-title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.body-top-title-version {
|
||||
margin-left: 2rem;
|
||||
font-size: 1.2rem;
|
||||
background: linear-gradient(transparent 60%, yellow 30%);
|
||||
}
|
||||
.body-top-title-belongings {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
& > div {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
& > .belonging-item {
|
||||
& > .link {
|
||||
text-decoration: none;
|
||||
& > span {
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.body-section-title {
|
||||
font-size: 1.5rem;
|
||||
color: rgb(51, 49, 49);
|
||||
|
100
client/demo/src/hooks/useStateControlCheckbox.tsx
Normal file
100
client/demo/src/hooks/useStateControlCheckbox.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import React, { useMemo, useRef } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export type StateControlCheckbox = {
|
||||
trigger: JSX.Element;
|
||||
updateState: (newVal: boolean) => void;
|
||||
checked: () => boolean
|
||||
className: string;
|
||||
};
|
||||
|
||||
export const useStateControlCheckbox = (className: string, changeCallback?: (newVal: boolean) => void): StateControlCheckbox => {
|
||||
const currentValForTriggerCallbackRef = useRef<boolean>(false);
|
||||
// (4) トリガチェックボックス
|
||||
const callback = useMemo(() => {
|
||||
console.log("generate callback function", className);
|
||||
return (newVal: boolean) => {
|
||||
if (!changeCallback) {
|
||||
return;
|
||||
}
|
||||
// 値が同じときはスルー (== 初期値(undefined)か、値が違ったのみ発火)
|
||||
if (currentValForTriggerCallbackRef.current === newVal) {
|
||||
return;
|
||||
}
|
||||
// 初期値(undefined)か、値が違ったのみ発火
|
||||
currentValForTriggerCallbackRef.current = newVal;
|
||||
changeCallback(currentValForTriggerCallbackRef.current);
|
||||
};
|
||||
}, []);
|
||||
const trigger = useMemo(() => {
|
||||
if (changeCallback) {
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
className={`${className} state-control-checkbox rotate-button`}
|
||||
id={`${className}`}
|
||||
onChange={(e) => {
|
||||
callback(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <input type="checkbox" className={`${className} state-control-checkbox rotate-button`} id={`${className}`} />;
|
||||
}
|
||||
}, []);
|
||||
const checked = useMemo(() => {
|
||||
return () => {
|
||||
const checkboxes = document.querySelectorAll(`.${className}`);
|
||||
if (checkboxes.length == 0) {
|
||||
return false
|
||||
}
|
||||
const box = checkboxes[0] as HTMLInputElement
|
||||
return box.checked
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const checkboxes = document.querySelectorAll(`.${className}`);
|
||||
// (1) On/Off同期
|
||||
checkboxes.forEach((x) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
x.onchange = (ev) => {
|
||||
updateState(ev.target.checked);
|
||||
};
|
||||
});
|
||||
// (2) 全エレメントoff
|
||||
const removers = document.querySelectorAll(`.${className}-remover`);
|
||||
removers.forEach((x) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
x.onclick = (ev) => {
|
||||
if (ev.target.className.indexOf(`${className}-remover`) > 0) {
|
||||
updateState(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
// (3) ステート変更
|
||||
const updateState = useMemo(() => {
|
||||
return (newVal: boolean) => {
|
||||
const currentCheckboxes = document.querySelectorAll(`.${className}`);
|
||||
currentCheckboxes.forEach((y) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
y.checked = newVal;
|
||||
});
|
||||
if (changeCallback) {
|
||||
callback(newVal);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
trigger,
|
||||
updateState,
|
||||
checked,
|
||||
className,
|
||||
};
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user