WIP: support so-vits-svc, client
@ -66,6 +66,11 @@ export const ServerSettingKey = {
|
||||
"f0Detector": "f0Detector",
|
||||
"recordIO": "recordIO",
|
||||
|
||||
"tran": "tran",
|
||||
"noiceScale": "noiceScale",
|
||||
"predictF0": "predictF0",
|
||||
"silentThreshold": "silentThreshold",
|
||||
|
||||
"inputSampleRate": "inputSampleRate",
|
||||
} as const
|
||||
export type ServerSettingKey = typeof ServerSettingKey[keyof typeof ServerSettingKey]
|
||||
@ -87,6 +92,11 @@ export type VoiceChangerServerSetting = {
|
||||
f0Detector: F0Detector // dio or harvest
|
||||
recordIO: number // 0:off, 1:on
|
||||
|
||||
tran: number // so-vits-svc
|
||||
noiceScale: number // so-vits-svc
|
||||
predictF0: number // so-vits-svc
|
||||
silentThreshold: number // so-vits-svc
|
||||
|
||||
inputSampleRate: InputSampleRate
|
||||
}
|
||||
|
||||
@ -113,6 +123,11 @@ export const DefaultServerSetting_MMVCv15: ServerInfo = {
|
||||
f0Detector: F0Detector.dio,
|
||||
recordIO: 0,
|
||||
|
||||
tran: 0,
|
||||
noiceScale: 0,
|
||||
predictF0: 0,
|
||||
silentThreshold: 0,
|
||||
|
||||
inputSampleRate: 24000,
|
||||
|
||||
//
|
||||
@ -138,6 +153,11 @@ export const DefaultServerSetting_MMVCv13: ServerInfo = {
|
||||
f0Detector: F0Detector.dio,
|
||||
recordIO: 0,
|
||||
|
||||
tran: 0,
|
||||
noiceScale: 0,
|
||||
predictF0: 0,
|
||||
silentThreshold: 0,
|
||||
|
||||
inputSampleRate: 24000,
|
||||
|
||||
//
|
||||
@ -163,6 +183,11 @@ export const DefaultServerSetting_so_vits_svc: ServerInfo = {
|
||||
f0Detector: F0Detector.dio,
|
||||
recordIO: 0,
|
||||
|
||||
tran: 10,
|
||||
noiceScale: 0.3,
|
||||
predictF0: 0,
|
||||
silentThreshold: 0.00001,
|
||||
|
||||
inputSampleRate: 24000,
|
||||
|
||||
//
|
||||
|
18
client/so-vits-svc_40v2/.eslintrc.js
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 13,
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["react", "@typescript-eslint"],
|
||||
rules: {},
|
||||
};
|
6
client/so-vits-svc_40v2/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"printWidth": 360
|
||||
}
|
8
client/so-vits-svc_40v2/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "postcss"
|
||||
},
|
||||
"workbench.colorCustomizations": {
|
||||
"tab.activeBackground": "#65952acc"
|
||||
}
|
||||
}
|
BIN
client/so-vits-svc_40v2/dist/assets/buymeacoffee.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
client/so-vits-svc_40v2/dist/assets/buymeacoffee.png_
vendored
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
client/so-vits-svc_40v2/dist/assets/buymeacoffee__.png
vendored
Normal file
After Width: | Height: | Size: 122 KiB |
1
client/so-vits-svc_40v2/dist/assets/icons/file-text.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
After Width: | Height: | Size: 473 B |
BIN
client/so-vits-svc_40v2/dist/assets/icons/flect.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
1
client/so-vits-svc_40v2/dist/assets/icons/github.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
After Width: | Height: | Size: 522 B |
1
client/so-vits-svc_40v2/dist/assets/icons/help-circle.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
After Width: | Height: | Size: 365 B |
1
client/so-vits-svc_40v2/dist/assets/icons/home.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
After Width: | Height: | Size: 327 B |
1
client/so-vits-svc_40v2/dist/assets/icons/linkedin.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
After Width: | Height: | Size: 395 B |
1
client/so-vits-svc_40v2/dist/assets/icons/twitter.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitter"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>
|
After Width: | Height: | Size: 403 B |
BIN
client/so-vits-svc_40v2/dist/assets/icons/zun.png
vendored
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
client/so-vits-svc_40v2/dist/favicon.ico
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
10
client/so-vits-svc_40v2/dist/index.html
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Voice Changer Client Demo</title>
|
||||
<script defer src="index.js"></script></head>
|
||||
<body style="width: 100%; height: 100%; margin: 0px">
|
||||
<div id="app" style="width: 100%; height: 100%"></div>
|
||||
</body>
|
||||
</html>
|
890
client/so-vits-svc_40v2/dist/index.js
vendored
Normal file
19452
client/so-vits-svc_40v2/package-lock.json
generated
Normal file
63
client/so-vits-svc_40v2/package.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": ".eslintrc.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist/*",
|
||||
"webpack:prod": "npx webpack --config webpack.prod.js",
|
||||
"webpack:dev": "npx webpack --config webpack.dev.js",
|
||||
"build:prod": "npm-run-all clean webpack:prod",
|
||||
"build:dev": "npm-run-all clean webpack:dev",
|
||||
"start": "webpack-dev-server --config webpack.dev.js",
|
||||
"build:mod": "cd ../lib && npm run build:dev && cd - && cp -r ../lib/dist/* node_modules/@dannadori/voice-changer-client-js/dist/",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"voice conversion"
|
||||
],
|
||||
"author": "wataru.okada@flect.co.jp",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.21.0",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.0",
|
||||
"@types/node": "^18.15.0",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "^9.1.2",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-webpack-plugin": "^4.0.0",
|
||||
"html-loader": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-loader": "^7.0.2",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^2.8.4",
|
||||
"rimraf": "^4.4.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dannadori/voice-changer-client-js": "^1.0.87",
|
||||
"@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"
|
||||
}
|
||||
}
|
6
client/so-vits-svc_40v2/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
"postcss-nested": {},
|
||||
},
|
||||
};
|
BIN
client/so-vits-svc_40v2/public/assets/buymeacoffee.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
client/so-vits-svc_40v2/public/assets/buymeacoffee.png_
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
client/so-vits-svc_40v2/public/assets/buymeacoffee__.png
Normal file
After Width: | Height: | Size: 122 KiB |
1
client/so-vits-svc_40v2/public/assets/icons/file-text.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
After Width: | Height: | Size: 473 B |
BIN
client/so-vits-svc_40v2/public/assets/icons/flect.png
Executable file
After Width: | Height: | Size: 1.3 KiB |
1
client/so-vits-svc_40v2/public/assets/icons/github.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
After Width: | Height: | Size: 522 B |
1
client/so-vits-svc_40v2/public/assets/icons/help-circle.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
After Width: | Height: | Size: 365 B |
1
client/so-vits-svc_40v2/public/assets/icons/home.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
After Width: | Height: | Size: 327 B |
1
client/so-vits-svc_40v2/public/assets/icons/linkedin.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
After Width: | Height: | Size: 395 B |
1
client/so-vits-svc_40v2/public/assets/icons/twitter.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitter"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>
|
After Width: | Height: | Size: 403 B |
BIN
client/so-vits-svc_40v2/public/assets/icons/zun.png
Executable file
After Width: | Height: | Size: 5.3 KiB |
BIN
client/so-vits-svc_40v2/public/favicon.ico
Executable file
After Width: | Height: | Size: 17 KiB |
10
client/so-vits-svc_40v2/public/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Voice Changer Client Demo</title>
|
||||
</head>
|
||||
<body style="width: 100%; height: 100%; margin: 0px">
|
||||
<div id="app" style="width: 100%; height: 100%"></div>
|
||||
</body>
|
||||
</html>
|
BIN
client/so-vits-svc_40v2/public/vc_128.ico
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
client/so-vits-svc_40v2/public/vc_32.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
client/so-vits-svc_40v2/public/vc_48.ico
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
client/so-vits-svc_40v2/public/vc_64.ico
Normal file
After Width: | Height: | Size: 17 KiB |
164
client/so-vits-svc_40v2/src/000_index.tsx
Normal file
@ -0,0 +1,164 @@
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./css/App.css"
|
||||
import { ErrorInfo, useMemo, useState, } from "react";
|
||||
import { useMicrophoneOptions } from "./100_options_microphone";
|
||||
import { AppStateProvider, useAppState } from "./001_provider/001_AppStateProvider";
|
||||
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
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 } from "./001_provider/001_AppRootProvider";
|
||||
import ErrorBoundary from "./001_provider/900_ErrorBoundary";
|
||||
import { INDEXEDDB_KEY_CLIENT, INDEXEDDB_KEY_MODEL_DATA, INDEXEDDB_KEY_SERVER, INDEXEDDB_KEY_WORKLET, INDEXEDDB_KEY_WORKLETNODE, useIndexedDB } from "@dannadori/voice-changer-client-js";
|
||||
import { CLIENT_TYPE, INDEXEDDB_KEY_AUDIO_OUTPUT } from "./const";
|
||||
|
||||
library.add(fas, far, fab);
|
||||
|
||||
|
||||
const container = document.getElementById("app")!;
|
||||
const root = createRoot(container);
|
||||
|
||||
const App = () => {
|
||||
const appState = useAppState()
|
||||
const { removeItem } = useIndexedDB({ clientType: CLIENT_TYPE })
|
||||
const { voiceChangerSetting } = useMicrophoneOptions()
|
||||
const titleRow = useMemo(() => {
|
||||
return (
|
||||
<div className="top-title">
|
||||
<span className="title">Voice Changer Setting</span>
|
||||
<span className="top-title-version">for so-vits-svc 40v2</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/0002_vc-helper-v_1_3" target="_blank" rel="noopener noreferrer">
|
||||
<img src="./assets/icons/help-circle.svg" />
|
||||
<span>manual</span>
|
||||
</a>
|
||||
<a className="link" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
|
||||
<img className="donate-img" src="./assets/buymeacoffee.png" />
|
||||
<span>Donate Coffee(寄付)</span>
|
||||
</a>
|
||||
|
||||
</span>
|
||||
<span className="belongings">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
const clearRow = useMemo(() => {
|
||||
const onClearSettingClicked = async () => {
|
||||
await appState.clearSetting()
|
||||
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
|
||||
location.reload()
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-3-4 left-padding-1">
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onClearSettingClicked}>clear setting</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
|
||||
const mainSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="main-body">
|
||||
{titleRow}
|
||||
{clearRow}
|
||||
{voiceChangerSetting}
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}, [voiceChangerSetting])
|
||||
return (
|
||||
<>
|
||||
{mainSetting}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const AppStateWrapper = () => {
|
||||
// エラーバウンダリー設定
|
||||
const [error, setError] = useState<{ error: Error, errorInfo: ErrorInfo }>()
|
||||
const { removeItem } = useIndexedDB({ clientType: CLIENT_TYPE })
|
||||
|
||||
const errorComponent = useMemo(() => {
|
||||
const errorName = error?.error.name || "no error name"
|
||||
const errorMessage = error?.error.message || "no error message"
|
||||
const errorInfos = (error?.errorInfo.componentStack || "no error stack").split("\n")
|
||||
|
||||
const onClearCacheClicked = async () => {
|
||||
|
||||
const indexedDBKeys = [
|
||||
INDEXEDDB_KEY_CLIENT,
|
||||
INDEXEDDB_KEY_SERVER,
|
||||
INDEXEDDB_KEY_WORKLETNODE,
|
||||
INDEXEDDB_KEY_MODEL_DATA,
|
||||
INDEXEDDB_KEY_WORKLET,
|
||||
INDEXEDDB_KEY_AUDIO_OUTPUT
|
||||
]
|
||||
for (const k of indexedDBKeys) {
|
||||
await removeItem(k)
|
||||
}
|
||||
location.reload();
|
||||
}
|
||||
return (
|
||||
<div className="error-container">
|
||||
<div className="top-error-message">
|
||||
ちょっと問題が起きたみたいです。
|
||||
</div>
|
||||
<div className="top-error-description">
|
||||
<p>このアプリで管理している情報をクリアすると回復する場合があります。</p>
|
||||
<p>下記のボタンを押して情報をクリアします。</p>
|
||||
<p><button onClick={onClearCacheClicked}>アプリを初期化</button></p>
|
||||
</div>
|
||||
<div className="error-detail">
|
||||
<div className="error-name">
|
||||
{errorName}
|
||||
</div>
|
||||
<div className="error-message">
|
||||
{errorMessage}
|
||||
</div>
|
||||
<div className="error-info-container">
|
||||
{errorInfos.map(x => {
|
||||
return <div className="error-info-line" key={x}>{x}</div>
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [error])
|
||||
|
||||
const updateError = (error: Error, errorInfo: React.ErrorInfo) => {
|
||||
console.log("error compo", error, errorInfo)
|
||||
setError({ error, errorInfo })
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallback={errorComponent} onError={updateError}>
|
||||
<AppStateProvider>
|
||||
<App></App>
|
||||
</AppStateProvider>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
root.render(
|
||||
<AppRootProvider>
|
||||
<AppStateWrapper></AppStateWrapper>
|
||||
</AppRootProvider>
|
||||
);
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export type AudioConfigState = {
|
||||
audioContext: AudioContext | null
|
||||
}
|
||||
|
||||
export const useAudioConfig = (): AudioConfigState => {
|
||||
const [audioContext, setAudioContext] = useState<AudioContext | null>(null)
|
||||
useEffect(() => {
|
||||
const createAudioContext = () => {
|
||||
const ctx = new AudioContext()
|
||||
document.removeEventListener('touchstart', createAudioContext);
|
||||
document.removeEventListener('mousedown', createAudioContext);
|
||||
setAudioContext(ctx)
|
||||
}
|
||||
document.addEventListener('touchstart', createAudioContext, false);
|
||||
document.addEventListener('mousedown', createAudioContext, false);
|
||||
}, [])
|
||||
|
||||
const ret: AudioConfigState = {
|
||||
audioContext
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { ClientState, useClient } from "@dannadori/voice-changer-client-js"
|
||||
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, CLIENT_TYPE } from "../const"
|
||||
|
||||
export type UseVCClientProps = {
|
||||
audioContext: AudioContext | null
|
||||
}
|
||||
|
||||
export type VCClientState = {
|
||||
clientState: ClientState
|
||||
}
|
||||
|
||||
export const useVCClient = (props: UseVCClientProps) => {
|
||||
|
||||
const clientState = useClient({
|
||||
clientType: CLIENT_TYPE,
|
||||
audioContext: props.audioContext,
|
||||
audioOutputElementId: AUDIO_ELEMENT_FOR_PLAY_RESULT
|
||||
})
|
||||
|
||||
const ret: VCClientState = {
|
||||
clientState
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { StateControlCheckbox, useStateControlCheckbox } from "../hooks/useStateControlCheckbox";
|
||||
import { OpenAdvancedSettingCheckbox, OpenConverterSettingCheckbox, OpenDeviceSettingCheckbox, OpenModelSettingCheckbox, OpenQualityControlCheckbox, OpenServerControlCheckbox, OpenSpeakerSettingCheckbox } from "../const"
|
||||
export type StateControls = {
|
||||
openServerControlCheckbox: StateControlCheckbox
|
||||
openModelSettingCheckbox: StateControlCheckbox
|
||||
openDeviceSettingCheckbox: StateControlCheckbox
|
||||
openQualityControlCheckbox: StateControlCheckbox
|
||||
openSpeakerSettingCheckbox: StateControlCheckbox
|
||||
openConverterSettingCheckbox: StateControlCheckbox
|
||||
openAdvancedSettingCheckbox: StateControlCheckbox
|
||||
}
|
||||
|
||||
type FrontendManagerState = {
|
||||
stateControls: StateControls
|
||||
isConverting: boolean,
|
||||
isAnalyzing: boolean
|
||||
};
|
||||
|
||||
export type FrontendManagerStateAndMethod = FrontendManagerState & {
|
||||
setIsConverting: (val: boolean) => void
|
||||
setIsAnalyzing: (val: boolean) => void
|
||||
}
|
||||
|
||||
export const useFrontendManager = (): FrontendManagerStateAndMethod => {
|
||||
const [isConverting, setIsConverting] = useState<boolean>(false)
|
||||
const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false)
|
||||
|
||||
// (1) Controller Switch
|
||||
const openServerControlCheckbox = useStateControlCheckbox(OpenServerControlCheckbox);
|
||||
const openModelSettingCheckbox = useStateControlCheckbox(OpenModelSettingCheckbox);
|
||||
const openDeviceSettingCheckbox = useStateControlCheckbox(OpenDeviceSettingCheckbox);
|
||||
const openQualityControlCheckbox = useStateControlCheckbox(OpenQualityControlCheckbox);
|
||||
const openSpeakerSettingCheckbox = useStateControlCheckbox(OpenSpeakerSettingCheckbox);
|
||||
const openConverterSettingCheckbox = useStateControlCheckbox(OpenConverterSettingCheckbox);
|
||||
const openAdvancedSettingCheckbox = useStateControlCheckbox(OpenAdvancedSettingCheckbox);
|
||||
|
||||
useEffect(() => {
|
||||
openServerControlCheckbox.updateState(true)
|
||||
openModelSettingCheckbox.updateState(true)
|
||||
openDeviceSettingCheckbox.updateState(true)
|
||||
openSpeakerSettingCheckbox.updateState(true)
|
||||
openConverterSettingCheckbox.updateState(true)
|
||||
|
||||
// openQualityControlCheckbox.updateState(true)
|
||||
|
||||
}, [])
|
||||
|
||||
|
||||
const returnValue = {
|
||||
stateControls: {
|
||||
openServerControlCheckbox,
|
||||
openModelSettingCheckbox,
|
||||
openDeviceSettingCheckbox,
|
||||
openQualityControlCheckbox,
|
||||
openSpeakerSettingCheckbox,
|
||||
openConverterSettingCheckbox,
|
||||
openAdvancedSettingCheckbox
|
||||
},
|
||||
isConverting,
|
||||
setIsConverting,
|
||||
isAnalyzing,
|
||||
setIsAnalyzing
|
||||
};
|
||||
return returnValue;
|
||||
};
|
@ -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>;
|
||||
};
|
@ -0,0 +1,75 @@
|
||||
import { ClientState } from "@dannadori/voice-changer-client-js";
|
||||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { useVCClient } 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;
|
||||
initializedRef: React.MutableRefObject<boolean>
|
||||
}
|
||||
|
||||
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 initializedRef = useRef<boolean>(false)
|
||||
useEffect(() => {
|
||||
if (clientState.clientState.initialized) {
|
||||
initializedRef.current = true
|
||||
|
||||
clientState.clientState.clientSetting.updateClientSetting({
|
||||
...clientState.clientState.clientSetting.clientSetting, speakers: [
|
||||
{
|
||||
"id": 107,
|
||||
"name": "user"
|
||||
},
|
||||
{
|
||||
"id": 100,
|
||||
"name": "ずんだもん"
|
||||
},
|
||||
{
|
||||
"id": 101,
|
||||
"name": "そら"
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"name": "めたん"
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"name": "つむぎ"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}, [clientState.clientState.initialized])
|
||||
|
||||
|
||||
const providerValue: AppStateValue = {
|
||||
audioContext: appRoot.audioContextState.audioContext!,
|
||||
...clientState.clientState,
|
||||
frontendManagerState,
|
||||
initializedRef
|
||||
|
||||
|
||||
};
|
||||
|
||||
return <AppStateContext.Provider value={providerValue}>{children}</AppStateContext.Provider>;
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
import React, { ErrorInfo } from 'react';
|
||||
|
||||
type ErrorBoundaryProps = {
|
||||
children: React.ReactNode;
|
||||
fallback: React.ReactNode;
|
||||
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
||||
}
|
||||
|
||||
type ErrorBoundaryState = {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
private eventHandler: () => void
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
this.eventHandler = this.updateError.bind(this);
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(_error: Error) {
|
||||
// console.warn("React Error Boundary Catch", error)
|
||||
return { hasError: true };
|
||||
}
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// For logging
|
||||
console.warn("React Error Boundary Catch", error, errorInfo)
|
||||
const { onError } = this.props;
|
||||
if (onError) {
|
||||
onError(error, errorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 非同期例外対応
|
||||
updateError() {
|
||||
this.setState({ hasError: true });
|
||||
}
|
||||
componentDidMount() {
|
||||
window.addEventListener('unhandledrejection', this.eventHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('unhandledrejection', this.eventHandler)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default ErrorBoundary;
|
46
client/so-vits-svc_40v2/src/100_options_microphone.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import * as React from "react";
|
||||
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 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}
|
||||
{modelSetting.modelSetting}
|
||||
{deviceSetting.deviceSetting}
|
||||
{qualityControl.qualityControl}
|
||||
{speakerSetting.speakerSetting}
|
||||
{convertSetting.convertSetting}
|
||||
{advancedSetting.advancedSetting}
|
||||
</>
|
||||
)
|
||||
}, [serverControl.serverControl,
|
||||
modelSetting.modelSetting,
|
||||
deviceSetting.deviceSetting,
|
||||
speakerSetting.speakerSetting,
|
||||
convertSetting.convertSetting,
|
||||
advancedSetting.advancedSetting,
|
||||
qualityControl.qualityControl])
|
||||
|
||||
|
||||
return {
|
||||
voiceChangerSetting
|
||||
}
|
||||
}
|
||||
|
149
client/so-vits-svc_40v2/src/101_server_control.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export const useServerControl = () => {
|
||||
const appState = useAppState()
|
||||
const [startWithAudioContextCreate, setStartWithAudioContextCreate] = useState<boolean>(false)
|
||||
const [showPerformanceDetail, setShowPerformanceDetail] = 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>;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!startWithAudioContextCreate) {
|
||||
return
|
||||
}
|
||||
appState.frontendManagerState.setIsConverting(true)
|
||||
appState.clientSetting.start()
|
||||
}, [startWithAudioContextCreate])
|
||||
|
||||
const startButtonRow = useMemo(() => {
|
||||
const onStartClicked = async () => {
|
||||
if (!appState.initializedRef.current) {
|
||||
while (true) {
|
||||
// console.log("wait 500ms")
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, 500)
|
||||
})
|
||||
// console.log("initiliazed", appState.initializedRef.current)
|
||||
if (appState.initializedRef.current) {
|
||||
break
|
||||
}
|
||||
}
|
||||
setStartWithAudioContextCreate(true)
|
||||
} else {
|
||||
appState.frontendManagerState.setIsConverting(true)
|
||||
await appState.clientSetting.start()
|
||||
}
|
||||
}
|
||||
const onStopClicked = async () => {
|
||||
appState.frontendManagerState.setIsConverting(false)
|
||||
await appState.clientSetting.stop()
|
||||
}
|
||||
const startClassName = appState.frontendManagerState.isConverting ? "body-button-active" : "body-button-stanby"
|
||||
const stopClassName = appState.frontendManagerState.isConverting ? "body-button-stanby" : "body-button-active"
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Start</div>
|
||||
<div className="body-button-container">
|
||||
<div onClick={onStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<div className="body-input-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.frontendManagerState.isConverting, appState.clientSetting.start, appState.clientSetting.stop])
|
||||
|
||||
const performanceRow = useMemo(() => {
|
||||
const performanceDetailLabel = showPerformanceDetail ? "[pre, main, post] <<" : "more >>"
|
||||
const performanceData = showPerformanceDetail ? `[${appState.performance.preprocessTime}, ${appState.performance.mainprocessTime},${appState.performance.postprocessTime}]` : ""
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-1-1-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">monitor:</div>
|
||||
<div className="body-item-text">vol<span className="body-item-text-small">(rms)</span></div>
|
||||
<div className="body-item-text">buf<span className="body-item-text-small">(ms)</span></div>
|
||||
<div className="body-item-text">res<span className="body-item-text-small">(ms)</span></div>
|
||||
<div className="body-item-text">
|
||||
<span onClick={() => { setShowPerformanceDetail(!showPerformanceDetail) }} >{performanceDetailLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-row split-3-1-1-1-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1"></div>
|
||||
<div className="body-item-text">{appState.volume.toFixed(4)}</div>
|
||||
<div className="body-item-text">{appState.bufferingTime}</div>
|
||||
<div className="body-item-text">{appState.performance.responseTime}</div>
|
||||
<div className="body-item-text">{performanceData}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [appState.volume, appState.bufferingTime, appState.performance, showPerformanceDetail])
|
||||
|
||||
const infoRow = useMemo(() => {
|
||||
const onReloadClicked = async () => {
|
||||
const info = await appState.getInfo()
|
||||
console.log("info", info)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Model Info:</div>
|
||||
<div className="body-item-text">
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverSetting.configFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverSetting.pyTorchModelFile || ""}</span>
|
||||
<span className="body-item-text-item">{appState.serverSetting.serverSetting.onnxModelFile || ""}</span>
|
||||
|
||||
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onReloadClicked}>reload</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [appState.getInfo, appState.serverSetting.serverSetting])
|
||||
|
||||
const serverControl = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{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])
|
||||
|
||||
return {
|
||||
serverControl,
|
||||
}
|
||||
}
|
||||
|
||||
|
256
client/so-vits-svc_40v2/src/102_model_setting.tsx
Normal file
@ -0,0 +1,256 @@
|
||||
import { OnnxExecutionProvider, Framework, fileSelector, Correspondence } from "@dannadori/voice-changer-client-js"
|
||||
import React, { useState } from "react"
|
||||
import { useMemo } from "react"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export type ServerSettingState = {
|
||||
modelSetting: JSX.Element;
|
||||
}
|
||||
|
||||
export const useModelSettingArea = (): ServerSettingState => {
|
||||
const appState = useAppState()
|
||||
const [showPyTorch, setShowPyTorch] = useState<boolean>(false)
|
||||
|
||||
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>;
|
||||
}, []);
|
||||
|
||||
const uploadeModelRow = useMemo(() => {
|
||||
const onPyTorchFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".pth") == false) {
|
||||
alert("モデルファイルの拡張子はpthである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
pyTorchModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onPyTorchFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
pyTorchModel: null
|
||||
})
|
||||
}
|
||||
const onConfigFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".json") == false) {
|
||||
alert("モデルファイルの拡張子はjsonである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
configFile: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onConfigFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
configFile: null
|
||||
})
|
||||
}
|
||||
const onOnnxFileLoadClicked = async () => {
|
||||
const file = await fileSelector("")
|
||||
if (file.name.endsWith(".onnx") == false) {
|
||||
alert("モデルファイルの拡張子はonnxである必要があります。")
|
||||
return
|
||||
}
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
onnxModel: {
|
||||
file: file
|
||||
}
|
||||
})
|
||||
}
|
||||
const onOnnxFileClearClicked = () => {
|
||||
appState.serverSetting.setFileUploadSetting({
|
||||
...appState.serverSetting.fileUploadSetting,
|
||||
onnxModel: null
|
||||
})
|
||||
}
|
||||
|
||||
const onModelUploadClicked = async () => {
|
||||
appState.serverSetting.loadModel()
|
||||
}
|
||||
|
||||
const uploadButtonClassName = appState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
|
||||
const uploadButtonAction = appState.serverSetting.isUploading ? () => { } : onModelUploadClicked
|
||||
const uploadButtonLabel = appState.serverSetting.isUploading ? "wait..." : "upload"
|
||||
|
||||
const 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 uploadingStatus = appState.serverSetting.isUploading ?
|
||||
appState.serverSetting.uploadProgress == 0 ? `loading model...(wait about 20sec)` : `uploading.... ${appState.serverSetting.uploadProgress}%` : ""
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Model Uploader</div>
|
||||
<div className="body-item-text">
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>
|
||||
<input type="checkbox" checked={showPyTorch} onChange={(e) => {
|
||||
setShowPyTorch(e.target.checked)
|
||||
}} /> enable PyTorch
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">Config(.json)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{configFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onConfigFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onConfigFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">Onnx(.onnx)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{onnxModelFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onOnnxFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onOnnxFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
{showPyTorch ?
|
||||
(
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">PyTorch(.pth)</div>
|
||||
<div className="body-item-text">
|
||||
<div>{pyTorchFilenameText}</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onPyTorchFileLoadClicked}>select</div>
|
||||
<div className="body-button left-margin-1" onClick={onPyTorchFileClearClicked}>clear</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
:
|
||||
(
|
||||
<></>
|
||||
)
|
||||
}
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2"></div>
|
||||
<div className="body-item-text">
|
||||
{uploadingStatus}
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className={uploadButtonClassName} onClick={uploadButtonAction}>{uploadButtonLabel}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [
|
||||
appState.serverSetting.fileUploadSetting,
|
||||
appState.serverSetting.loadModel,
|
||||
appState.serverSetting.isUploading,
|
||||
appState.serverSetting.uploadProgress,
|
||||
appState.clientSetting.clientSetting.correspondences,
|
||||
appState.serverSetting.updateServerSettings,
|
||||
appState.serverSetting.setFileUploadSetting,
|
||||
showPyTorch])
|
||||
|
||||
const frameworkRow = useMemo(() => {
|
||||
const onFrameworkChanged = async (val: Framework) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, framework: val })
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Framework</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.serverSetting.framework} onChange={(e) => {
|
||||
onFrameworkChanged(e.target.value as
|
||||
Framework)
|
||||
}}>
|
||||
{
|
||||
Object.values(Framework).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.framework, appState.serverSetting.updateServerSettings])
|
||||
|
||||
const onnxExecutionProviderRow = useMemo(() => {
|
||||
if (appState.serverSetting.serverSetting.framework != "ONNX") {
|
||||
return
|
||||
}
|
||||
const onOnnxExecutionProviderChanged = async (val: OnnxExecutionProvider) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, onnxExecutionProvider: val })
|
||||
}
|
||||
console.log("setting", appState.serverSetting.serverSetting)
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1">
|
||||
<div className="body-item-title left-padding-2">OnnxExecutionProvider</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.serverSetting.onnxExecutionProvider} onChange={(e) => {
|
||||
onOnnxExecutionProviderChanged(e.target.value as
|
||||
OnnxExecutionProvider)
|
||||
}}>
|
||||
{
|
||||
Object.values(OnnxExecutionProvider).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.framework, appState.serverSetting.serverSetting.onnxExecutionProvider, appState.serverSetting.updateServerSettings])
|
||||
|
||||
const modelSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{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>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{uploadeModelRow}
|
||||
{frameworkRow}
|
||||
{onnxExecutionProviderRow}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [uploadeModelRow, frameworkRow, onnxExecutionProviderRow])
|
||||
|
||||
|
||||
return {
|
||||
modelSetting,
|
||||
}
|
||||
}
|
390
client/so-vits-svc_40v2/src/103_device_setting.tsx
Normal file
@ -0,0 +1,390 @@
|
||||
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, CLIENT_TYPE, INDEXEDDB_KEY_AUDIO_OUTPUT } from "./const"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
|
||||
const reloadDevices = async () => {
|
||||
try {
|
||||
const ms = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
|
||||
ms.getTracks().forEach(x => { x.stop() })
|
||||
} catch (e) {
|
||||
console.warn("Enumerate device error::", e)
|
||||
}
|
||||
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
||||
|
||||
const audioInputs = mediaDeviceInfos.filter(x => { return x.kind == "audioinput" })
|
||||
audioInputs.push({
|
||||
deviceId: "none",
|
||||
groupId: "none",
|
||||
kind: "audioinput",
|
||||
label: "none",
|
||||
toJSON: () => { }
|
||||
})
|
||||
audioInputs.push({
|
||||
deviceId: "file",
|
||||
groupId: "file",
|
||||
kind: "audioinput",
|
||||
label: "file",
|
||||
toJSON: () => { }
|
||||
})
|
||||
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" })
|
||||
audioOutputs.push({
|
||||
deviceId: "none",
|
||||
groupId: "none",
|
||||
kind: "audiooutput",
|
||||
label: "none",
|
||||
toJSON: () => { }
|
||||
})
|
||||
// audioOutputs.push({
|
||||
// deviceId: "record",
|
||||
// groupId: "record",
|
||||
// kind: "audiooutput",
|
||||
// label: "record",
|
||||
// toJSON: () => { }
|
||||
// })
|
||||
return [audioInputs, audioOutputs]
|
||||
}
|
||||
|
||||
export type DeviceSettingState = {
|
||||
deviceSetting: JSX.Element;
|
||||
}
|
||||
|
||||
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[]>([])
|
||||
|
||||
const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none")
|
||||
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none")
|
||||
const [fileInputEchoback, setFileInputEchoback] = useState<boolean>()//最初のmuteが有効になるように。undefined
|
||||
const { getItem, setItem } = useIndexedDB({ clientType: CLIENT_TYPE })
|
||||
|
||||
const audioSrcNode = useRef<MediaElementAudioSourceNode>()
|
||||
|
||||
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false)
|
||||
|
||||
const [useServerMicrophone, setUseServerMicrophone] = useState<boolean>(false)
|
||||
|
||||
// リスト内の
|
||||
useEffect(() => {
|
||||
const initialize = async () => {
|
||||
const audioInfo = await reloadDevices()
|
||||
setInputAudioDeviceInfo(audioInfo[0])
|
||||
setOutputAudioDeviceInfo(audioInfo[1])
|
||||
// if (useServerMicrophone) {
|
||||
// try {
|
||||
// const serverDevices = await appState.serverSetting.getServerDevices()
|
||||
// setServerInputAudioDeviceInfo(serverDevices.audio_input_devices)
|
||||
// } catch (e) {
|
||||
// console.warn(e)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
initialize()
|
||||
}, [useServerMicrophone])
|
||||
|
||||
// キャッシュの設定は反映(たぶん、設定操作の時も起動していしまう。が問題は起こらないはず)
|
||||
useEffect(() => {
|
||||
if (typeof appState.clientSetting.clientSetting.audioInput == "string") {
|
||||
if (inputAudioDeviceInfo.find(x => {
|
||||
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
|
||||
return x.deviceId == appState.clientSetting.clientSetting.audioInput
|
||||
})) {
|
||||
setAudioInputForGUI(appState.clientSetting.clientSetting.audioInput)
|
||||
}
|
||||
}
|
||||
}, [inputAudioDeviceInfo, appState.clientSetting.clientSetting.audioInput])
|
||||
|
||||
const audioInputRow = useMemo(() => {
|
||||
if (useServerMicrophone) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">AudioInput</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={audioInputForGUI} onChange={(e) => {
|
||||
setAudioInputForGUI(e.target.value)
|
||||
}}>
|
||||
{
|
||||
inputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [inputAudioDeviceInfo, audioInputForGUI, useServerMicrophone])
|
||||
|
||||
useEffect(() => {
|
||||
if (audioInputForGUI == "file") {
|
||||
// file selector (audioMediaInputRow)
|
||||
} else {
|
||||
if (!useServerMicrophone) {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: audioInputForGUI })
|
||||
} else {
|
||||
console.log("server mic")
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: null })
|
||||
}
|
||||
}
|
||||
}, [appState.audioContext, audioInputForGUI, appState.clientSetting.updateClientSetting])
|
||||
|
||||
const audioMediaInputRow = useMemo(() => {
|
||||
if (audioInputForGUI != "file") {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const onFileLoadClicked = async () => {
|
||||
const url = await fileSelectorAsDataURL("")
|
||||
|
||||
// input stream for client.
|
||||
const audio = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED) as HTMLAudioElement
|
||||
audio.pause()
|
||||
audio.srcObject = null
|
||||
audio.src = url
|
||||
await audio.play()
|
||||
if (!audioSrcNode.current) {
|
||||
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
if (audioSrcNode.current.mediaElement != audio) {
|
||||
audioSrcNode.current = appState.audioContext!.createMediaElementSource(audio);
|
||||
}
|
||||
|
||||
const dst = appState.audioContext.createMediaStreamDestination()
|
||||
audioSrcNode.current.connect(dst)
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, audioInput: dst.stream })
|
||||
|
||||
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement
|
||||
audio_echo.srcObject = dst.stream
|
||||
audio_echo.play()
|
||||
audio_echo.volume = 0
|
||||
setFileInputEchoback(false)
|
||||
|
||||
// original stream to play.
|
||||
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement
|
||||
audio_org.src = url
|
||||
audio_org.pause()
|
||||
|
||||
// audio_org.onplay = () => {
|
||||
// console.log(audioOutputRef.current)
|
||||
// // @ts-ignore
|
||||
// audio_org.setSinkId(audioOutputRef.current)
|
||||
// }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title"></div>
|
||||
<div className="body-item-text">
|
||||
<div style={{ display: "none" }}>
|
||||
org:<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls></audio>
|
||||
</div>
|
||||
<div>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls></audio>
|
||||
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onFileLoadClicked}>load</div>
|
||||
<input type="checkbox" checked={fileInputEchoback} onChange={(e) => { setFileInputEchoback(e.target.checked) }} /> echoback
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [audioInputForGUI, appState.clientSetting.updateClientSetting, fileInputEchoback])
|
||||
|
||||
|
||||
|
||||
const audioOutputRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">AudioOutput</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={audioOutputForGUI} onChange={(e) => {
|
||||
setAudioOutputForGUI(e.target.value)
|
||||
setItem(INDEXEDDB_KEY_AUDIO_OUTPUT, e.target.value)
|
||||
}}>
|
||||
{
|
||||
outputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [outputAudioDeviceInfo, audioOutputForGUI])
|
||||
|
||||
const audioOutputRecordingRow = useMemo(() => {
|
||||
// if (audioOutputForGUI != "record") {
|
||||
// return <></>
|
||||
// }
|
||||
const onOutputRecordStartClicked = async () => {
|
||||
setOutputRecordingStarted(true)
|
||||
await appState.workletNodeSetting.startOutputRecording()
|
||||
}
|
||||
const onOutputRecordStopClicked = async () => {
|
||||
setOutputRecordingStarted(false)
|
||||
const record = await appState.workletNodeSetting.stopOutputRecording()
|
||||
downloadRecord(record)
|
||||
}
|
||||
|
||||
const startClassName = outputRecordingStarted ? "body-button-active" : "body-button-stanby"
|
||||
const stopClassName = outputRecordingStarted ? "body-button-stanby" : "body-button-active"
|
||||
|
||||
return (
|
||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2">output record</div>
|
||||
<div className="body-button-container">
|
||||
<div onClick={onOutputRecordStartClicked} className={startClassName}>start</div>
|
||||
<div onClick={onOutputRecordStopClicked} className={stopClassName}>stop</div>
|
||||
</div>
|
||||
<div className="body-input-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}, [audioOutputForGUI, outputRecordingStarted, appState.workletNodeSetting.startOutputRecording, appState.workletNodeSetting.stopOutputRecording])
|
||||
|
||||
useEffect(() => {
|
||||
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
|
||||
const audio = document.getElementById(x) as HTMLAudioElement
|
||||
if (audio) {
|
||||
if (audioOutputForGUI == "none") {
|
||||
// @ts-ignore
|
||||
audio.setSinkId("")
|
||||
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
|
||||
audio.volume = 0
|
||||
} else {
|
||||
audio.volume = 0
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore
|
||||
audio.setSinkId(audioOutputForGUI)
|
||||
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
|
||||
audio.volume = fileInputEchoback ? 1 : 0
|
||||
} else {
|
||||
audio.volume = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [audioOutputForGUI])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const loadCache = async () => {
|
||||
const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
|
||||
if (key) {
|
||||
setAudioOutputForGUI(key as string)
|
||||
}
|
||||
}
|
||||
loadCache()
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
[AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
|
||||
const audio = document.getElementById(x) as HTMLAudioElement
|
||||
if (audio) {
|
||||
audio.volume = fileInputEchoback ? 1 : 0
|
||||
}
|
||||
})
|
||||
}, [fileInputEchoback])
|
||||
|
||||
const deviceSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{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>
|
||||
<span className="belongings">
|
||||
{/* <input className="belongings-checkbox" type="checkbox" checked={useServerMicrophone} onChange={(e) => {
|
||||
setUseServerMicrophone(e.target.checked)
|
||||
}} /> use server mic (Experimental) */}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="partition-content">
|
||||
{audioInputRow}
|
||||
{audioMediaInputRow}
|
||||
{audioOutputRow}
|
||||
{audioOutputRecordingRow}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [audioInputRow, audioMediaInputRow, audioOutputRow, audioOutputRecordingRow, useServerMicrophone])
|
||||
|
||||
|
||||
const downloadRecord = (data: Float32Array) => {
|
||||
|
||||
const writeString = (view: DataView, offset: number, string: string) => {
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
};
|
||||
|
||||
const floatTo16BitPCM = (output: DataView, offset: number, input: Float32Array) => {
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]));
|
||||
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
||||
}
|
||||
};
|
||||
|
||||
const buffer = new ArrayBuffer(44 + data.length * 2);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
// https://www.youfit.co.jp/archives/1418
|
||||
writeString(view, 0, 'RIFF'); // RIFFヘッダ
|
||||
view.setUint32(4, 32 + data.length * 2, true); // これ以降のファイルサイズ
|
||||
writeString(view, 8, 'WAVE'); // WAVEヘッダ
|
||||
writeString(view, 12, 'fmt '); // fmtチャンク
|
||||
view.setUint32(16, 16, true); // fmtチャンクのバイト数
|
||||
view.setUint16(20, 1, true); // フォーマットID
|
||||
view.setUint16(22, 1, true); // チャンネル数
|
||||
view.setUint32(24, 48000, true); // サンプリングレート
|
||||
view.setUint32(28, 48000 * 2, true); // データ速度
|
||||
view.setUint16(32, 2, true); // ブロックサイズ
|
||||
view.setUint16(34, 16, true); // サンプルあたりのビット数
|
||||
writeString(view, 36, 'data'); // dataチャンク
|
||||
view.setUint32(40, data.length * 2, true); // 波形データのバイト数
|
||||
floatTo16BitPCM(view, 44, data); // 波形データ
|
||||
const audioBlob = new Blob([view], { type: 'audio/wav' });
|
||||
|
||||
const url = URL.createObjectURL(audioBlob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `output.wav`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
return {
|
||||
deviceSetting,
|
||||
}
|
||||
}
|
311
client/so-vits-svc_40v2/src/104_qulity_control.tsx
Normal file
@ -0,0 +1,311 @@
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
|
||||
|
||||
export type QualityControlState = {
|
||||
qualityControl: JSX.Element;
|
||||
}
|
||||
const reloadDevices = async () => {
|
||||
try {
|
||||
const ms = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
|
||||
ms.getTracks().forEach(x => { x.stop() })
|
||||
} catch (e) {
|
||||
console.warn("Enumerate device error::", e)
|
||||
}
|
||||
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
||||
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" })
|
||||
|
||||
return audioOutputs
|
||||
}
|
||||
|
||||
|
||||
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 [recording, setRecording] = useState<boolean>(false)
|
||||
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([])
|
||||
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("default")
|
||||
useEffect(() => {
|
||||
const initialize = async () => {
|
||||
const audioInfo = await reloadDevices()
|
||||
setOutputAudioDeviceInfo(audioInfo)
|
||||
}
|
||||
initialize()
|
||||
}, [])
|
||||
|
||||
|
||||
const noiseControlRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-2-1 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Noise Suppression</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={appState.clientSetting.clientSetting.echoCancel} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, echoCancel: e.target.checked })
|
||||
}} /> echo cancel
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={appState.clientSetting.clientSetting.noiseSuppression} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression: e.target.checked })
|
||||
}} /> suppression1
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={appState.clientSetting.clientSetting.noiseSuppression2} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, noiseSuppression2: e.target.checked })
|
||||
}} /> suppression2
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
appState.clientSetting.clientSetting.echoCancel,
|
||||
appState.clientSetting.clientSetting.noiseSuppression,
|
||||
appState.clientSetting.clientSetting.noiseSuppression2,
|
||||
appState.clientSetting.updateClientSetting
|
||||
])
|
||||
|
||||
const gainControlRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Gain Control</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">in</span>
|
||||
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={appState.clientSetting.clientSetting.inputGain} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, inputGain: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.clientSetting.clientSetting.inputGain}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">out</span>
|
||||
<input type="range" className="body-item-input-slider" min="0.0" max="1.0" step="0.1" value={appState.clientSetting.clientSetting.outputGain} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, outputGain: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.clientSetting.clientSetting.outputGain}</span>
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
appState.clientSetting.clientSetting.inputGain,
|
||||
appState.clientSetting.clientSetting.outputGain,
|
||||
appState.clientSetting.updateClientSetting
|
||||
])
|
||||
|
||||
// const f0DetectorRow = useMemo(() => {
|
||||
// const desc = { "harvest": "High Quality", "dio": "Light Weight" }
|
||||
// return (
|
||||
// <div className="body-row split-3-7 left-padding-1 guided">
|
||||
// <div className="body-item-title left-padding-1 ">F0 Detector</div>
|
||||
// <div className="body-select-container">
|
||||
// <select className="body-select" value={appState.serverSetting.serverSetting.f0Detector} onChange={(e) => {
|
||||
// appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, f0Detector: e.target.value as F0Detector })
|
||||
// }}>
|
||||
// {
|
||||
// Object.values(F0Detector).map(x => {
|
||||
// //@ts-ignore
|
||||
// return <option key={x} value={x}>{x}({desc[x]})</option>
|
||||
// })
|
||||
// }
|
||||
// </select>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }, [appState.serverSetting.serverSetting.f0Detector, appState.serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
const recordIORow = useMemo(() => {
|
||||
const onRecordStartClicked = async () => {
|
||||
setRecording(true)
|
||||
await appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, recordIO: 1 })
|
||||
}
|
||||
const onRecordStopClicked = async () => {
|
||||
setRecording(false)
|
||||
await appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, recordIO: 0 })
|
||||
|
||||
// set wav (input)
|
||||
const wavInput = document.getElementById("body-wav-container-wav-input") as HTMLAudioElement
|
||||
wavInput.src = "/tmp/in.wav?" + new Date().getTime()
|
||||
wavInput.controls = true
|
||||
// @ts-ignore
|
||||
wavInput.setSinkId(audioOutputForGUI)
|
||||
|
||||
// set wav (output)
|
||||
const wavOutput = document.getElementById("body-wav-container-wav-output") as HTMLAudioElement
|
||||
wavOutput.src = "/tmp/out.wav?" + new Date().getTime()
|
||||
wavOutput.controls = true
|
||||
// @ts-ignore
|
||||
wavOutput.setSinkId(audioOutputForGUI)
|
||||
}
|
||||
const onRecordAnalizeClicked = async () => {
|
||||
if (appState.frontendManagerState.isConverting) {
|
||||
alert("please stop voice conversion. 解析処理と音声変換を同時に行うことはできません。音声変化をストップしてください。")
|
||||
return
|
||||
}
|
||||
appState.frontendManagerState.setIsAnalyzing(true)
|
||||
await appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, recordIO: 2 })
|
||||
// set spectrogram (dio)
|
||||
const imageDio = document.getElementById("body-image-container-img-dio") as HTMLImageElement
|
||||
imageDio.src = "/tmp/analyze-dio.png?" + new Date().getTime()
|
||||
imageDio.style.width = "100%"
|
||||
|
||||
// set spectrogram (harvest)
|
||||
const imageHarvest = document.getElementById("body-image-container-img-harvest") as HTMLImageElement
|
||||
imageHarvest.src = "/tmp/analyze-harvest.png?" + new Date().getTime()
|
||||
imageHarvest.style.width = "100%"
|
||||
|
||||
appState.frontendManagerState.setIsAnalyzing(false)
|
||||
}
|
||||
|
||||
const startClassName = recording ? "body-button-active" : "body-button-stanby"
|
||||
const stopClassName = recording ? "body-button-stanby" : "body-button-active"
|
||||
const analyzeClassName = appState.frontendManagerState.isAnalyzing ? "body-button-active" : "body-button-stanby"
|
||||
const analyzeLabel = appState.frontendManagerState.isAnalyzing ? "wait..." : "Analyze"
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Analyzer(Experimental)</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2 ">
|
||||
Sampling
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div onClick={onRecordStartClicked} className={startClassName}>Start</div>
|
||||
<div onClick={onRecordStopClicked} className={stopClassName}>Stop</div>
|
||||
<div onClick={onRecordAnalizeClicked} className={analyzeClassName}>{analyzeLabel}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2 ">
|
||||
<div>
|
||||
Play
|
||||
</div>
|
||||
<select className="body-select-50 left-margin-2" value={audioOutputForGUI} onChange={(e) => {
|
||||
setAudioOutputForGUI(e.target.value)
|
||||
const wavInput = document.getElementById("body-wav-container-wav-input") as HTMLAudioElement
|
||||
const wavOutput = document.getElementById("body-wav-container-wav-output") as HTMLAudioElement
|
||||
//@ts-ignore
|
||||
wavInput.setSinkId(e.target.value)
|
||||
//@ts-ignore
|
||||
wavOutput.setSinkId(e.target.value)
|
||||
}}>
|
||||
{
|
||||
outputAudioDeviceInfo.map(x => {
|
||||
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
{/* <div>
|
||||
<div className="body-wav-container">
|
||||
<div className="body-wav-container-title">Input</div>
|
||||
<div className="body-wav-container-title">Output</div>
|
||||
</div>
|
||||
<div className="body-wav-container">
|
||||
<div className="body-wav-container-wav">
|
||||
<audio src="" id="body-wav-container-wav-input"></audio>
|
||||
</div>
|
||||
<div className="body-wav-container-wav" >
|
||||
<audio src="" id="body-wav-container-wav-output"></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<div>
|
||||
<div className="body-wav-container-title">Input</div>
|
||||
<div className="body-wav-container-wav">
|
||||
<audio src="" id="body-wav-container-wav-input"></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div >
|
||||
<div className="body-wav-container-title">Output</div>
|
||||
<div className="body-wav-container-wav" >
|
||||
<audio src="" id="body-wav-container-wav-output"></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-2 ">
|
||||
Spectrogram
|
||||
</div>
|
||||
<div>
|
||||
<div className="body-image-container">
|
||||
<div className="body-image-container-title">PyWorld Dio</div>
|
||||
<div className="body-image-container-title">PyWorld Harvest</div>
|
||||
</div>
|
||||
<div className="body-image-container">
|
||||
<div className="body-image-container-img" >
|
||||
<img src="" alt="" id="body-image-container-img-dio" />
|
||||
</div>
|
||||
<div className="body-image-container-img">
|
||||
<img src="" alt="" id="body-image-container-img-harvest" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.recordIO, appState.serverSetting.updateServerSettings, outputAudioDeviceInfo, audioOutputForGUI, appState.frontendManagerState.isAnalyzing, appState.frontendManagerState.isConverting])
|
||||
|
||||
const QualityControlContent = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{noiseControlRow}
|
||||
{gainControlRow}
|
||||
<div className="body-row divider"></div>
|
||||
{recordIORow}
|
||||
</>
|
||||
)
|
||||
}, [gainControlRow, noiseControlRow, recordIORow])
|
||||
|
||||
|
||||
const qualityControl = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{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])
|
||||
|
||||
return {
|
||||
qualityControl,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
129
client/so-vits-svc_40v2/src/105_speaker_setting.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import React, { 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 dstIdRow = useMemo(() => {
|
||||
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.serverSetting.dstId} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, dstId: Number(e.target.value) })
|
||||
|
||||
}}>
|
||||
{
|
||||
[0, 1, 2, 3, 4].map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.srcId, appState.serverSetting.serverSetting.dstId, appState.clientSetting.clientSetting.speakers, appState.serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
const tranRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Tuning</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">tran</span>
|
||||
<input type="range" className="body-item-input-slider" min="-20" max="20" step="1" value={appState.serverSetting.serverSetting.tran} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, tran: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.tran}</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" checked={appState.serverSetting.serverSetting.predictF0 == 1} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, predictF0: e.target.checked ? 1 : 0 })
|
||||
}} /> predict f0
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
appState.serverSetting.serverSetting,
|
||||
appState.serverSetting.updateServerSettings
|
||||
])
|
||||
|
||||
|
||||
const noiseControlRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1 ">Noise Control</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">n-scale</span>
|
||||
<input type="range" className="body-item-input-slider" min="0" max="1" step="0.1" value={appState.serverSetting.serverSetting.noiceScale} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, noiceScale: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.noiceScale}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="body-item-input-slider-label">silent thr</span>
|
||||
<input type="range" className="body-item-input-slider" min="0.00000" max="0.00009" step="0.00001" value={appState.serverSetting.serverSetting.silentThreshold} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, silentThreshold: Number(e.target.value) })
|
||||
}}></input>
|
||||
<span className="body-item-input-slider-val">{appState.serverSetting.serverSetting.silentThreshold}</span>
|
||||
</div>
|
||||
|
||||
<div className="body-button-container">
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [
|
||||
appState.serverSetting.serverSetting,
|
||||
appState.serverSetting.updateServerSettings
|
||||
])
|
||||
|
||||
|
||||
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">
|
||||
{dstIdRow}
|
||||
{tranRow}
|
||||
{noiseControlRow}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [dstIdRow, tranRow, noiseControlRow])
|
||||
|
||||
return {
|
||||
speakerSetting,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
85
client/so-vits-svc_40v2/src/106_convert_setting.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
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.workletNodeSetting.workletNodeSetting.inputChunkNum} onChange={(e) => {
|
||||
appState.workletNodeSetting.updateWorkletNodeSetting({ ...appState.workletNodeSetting.workletNodeSetting, inputChunkNum: Number(e.target.value) })
|
||||
appState.workletNodeSetting.trancateBuffer()
|
||||
}} />
|
||||
</div>
|
||||
<div className="body-item-text">
|
||||
<div>buff: {(appState.workletNodeSetting.workletNodeSetting.inputChunkNum * 128 * 1000 / 48000).toFixed(1)}ms</div>
|
||||
</div>
|
||||
<div className="body-item-text"></div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}, [appState.workletNodeSetting.workletNodeSetting.inputChunkNum, appState.workletNodeSetting.updateWorkletNodeSetting])
|
||||
|
||||
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.serverSetting.gpu} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, gpu: Number(e.target.value) })
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.gpu, appState.serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
266
client/so-vits-svc_40v2/src/107_advanced_setting.tsx
Normal file
@ -0,0 +1,266 @@
|
||||
import { CrossFadeOverlapSize, DownSamplingMode, InputSampleRate, Protocol, SampleRate } from "@dannadori/voice-changer-client-js"
|
||||
import React, { useMemo } from "react"
|
||||
import { useAppState } from "./001_provider/001_AppStateProvider";
|
||||
import { AnimationTypes, HeaderButton, HeaderButtonProps } from "./components/101_HeaderButton";
|
||||
|
||||
export type AdvancedSettingState = {
|
||||
advancedSetting: JSX.Element;
|
||||
}
|
||||
|
||||
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
|
||||
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={appState.workletNodeSetting.workletNodeSetting.serverUrl} id="mmvc-server-url" className="body-item-input" />
|
||||
</div>
|
||||
<div className="body-button-container">
|
||||
<div className="body-button" onClick={onSetServerClicked}>set</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.workletNodeSetting.workletNodeSetting.serverUrl, appState.clientSetting.setServerUrl])
|
||||
|
||||
const protocolRow = useMemo(() => {
|
||||
const onProtocolChanged = async (val: Protocol) => {
|
||||
appState.workletNodeSetting.updateWorkletNodeSetting({ ...appState.workletNodeSetting.workletNodeSetting, protocol: 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={appState.workletNodeSetting.workletNodeSetting.protocol} onChange={(e) => {
|
||||
onProtocolChanged(e.target.value as
|
||||
Protocol)
|
||||
}}>
|
||||
{
|
||||
Object.values(Protocol).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.workletNodeSetting.workletNodeSetting.protocol, appState.workletNodeSetting.updateWorkletNodeSetting])
|
||||
|
||||
|
||||
const sampleRateRow = useMemo(() => {
|
||||
return (
|
||||
<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={appState.clientSetting.clientSetting.sampleRate} onChange={(e) => {
|
||||
appState.clientSetting.updateClientSetting({ ...appState.clientSetting.clientSetting, sampleRate: Number(e.target.value) as SampleRate })
|
||||
}}>
|
||||
{
|
||||
Object.values(SampleRate).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.clientSetting.clientSetting.sampleRate, appState.clientSetting.updateClientSetting])
|
||||
|
||||
const sendingSampleRateRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Sending Sample Rate</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.workletNodeSetting.workletNodeSetting.sendingSampleRate} onChange={(e) => {
|
||||
appState.workletNodeSetting.updateWorkletNodeSetting({ ...appState.workletNodeSetting.workletNodeSetting, sendingSampleRate: Number(e.target.value) as InputSampleRate })
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, inputSampleRate: Number(e.target.value) as InputSampleRate })
|
||||
}}>
|
||||
{
|
||||
Object.values(InputSampleRate).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.workletNodeSetting.workletNodeSetting.sendingSampleRate, appState.workletNodeSetting.updateWorkletNodeSetting, appState.serverSetting.updateServerSettings])
|
||||
|
||||
const crossFadeOverlapSizeRow = useMemo(() => {
|
||||
return (
|
||||
<div className="body-row split-3-7 left-padding-1 guided">
|
||||
<div className="body-item-title left-padding-1">Cross Fade Overlap Size</div>
|
||||
<div className="body-select-container">
|
||||
<select className="body-select" value={appState.serverSetting.serverSetting.crossFadeOverlapSize} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, crossFadeOverlapSize: Number(e.target.value) as CrossFadeOverlapSize })
|
||||
}}>
|
||||
{
|
||||
Object.values(CrossFadeOverlapSize).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.crossFadeOverlapSize, appState.serverSetting.updateServerSettings])
|
||||
|
||||
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={appState.serverSetting.serverSetting.crossFadeOffsetRate} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, crossFadeOffsetRate: Number(e.target.value) })
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.crossFadeOffsetRate, appState.serverSetting.updateServerSettings])
|
||||
|
||||
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={appState.serverSetting.serverSetting.crossFadeEndRate} onChange={(e) => {
|
||||
appState.serverSetting.updateServerSettings({ ...appState.serverSetting.serverSetting, crossFadeEndRate: Number(e.target.value) })
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.serverSetting.serverSetting.crossFadeEndRate, appState.serverSetting.updateServerSettings])
|
||||
|
||||
|
||||
const downSamplingModeRow = useMemo(() => {
|
||||
return (
|
||||
<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={appState.workletNodeSetting.workletNodeSetting.downSamplingMode} onChange={(e) => {
|
||||
appState.workletNodeSetting.updateWorkletNodeSetting({ ...appState.workletNodeSetting.workletNodeSetting, downSamplingMode: e.target.value as DownSamplingMode })
|
||||
}}>
|
||||
{
|
||||
Object.values(DownSamplingMode).map(x => {
|
||||
return <option key={x} value={x}>{x}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [appState.workletNodeSetting.workletNodeSetting.downSamplingMode, appState.workletNodeSetting.updateWorkletNodeSetting])
|
||||
|
||||
|
||||
const workletSettingRow = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
|
||||
<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={appState.workletSetting.setting.numTrancateTreshold} onChange={(e) => {
|
||||
appState.workletSetting.setSetting({
|
||||
...appState.workletSetting.setting,
|
||||
numTrancateTreshold: Number(e.target.value)
|
||||
})
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* v.1.5.xより Silent skipは廃止 */}
|
||||
{/* <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={appState.workletSetting.setting.volTrancateThreshold} onChange={(e) => {
|
||||
appState.workletSetting.setSetting({
|
||||
...appState.workletSetting.setting,
|
||||
volTrancateThreshold: Number(e.target.value)
|
||||
})
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
<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={appState.workletSetting.setting.volTrancateLength} onChange={(e) => {
|
||||
appState.workletSetting.setSetting({
|
||||
...appState.workletSetting.setting,
|
||||
volTrancateLength: Number(e.target.value)
|
||||
})
|
||||
}} />
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
)
|
||||
}, [appState.workletSetting.setting, appState.workletSetting.setSetting])
|
||||
|
||||
|
||||
const advanceSettingContent = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="body-row divider"></div>
|
||||
{mmvcServerUrlRow}
|
||||
{protocolRow}
|
||||
<div className="body-row divider"></div>
|
||||
{sampleRateRow}
|
||||
{sendingSampleRateRow}
|
||||
<div className="body-row divider"></div>
|
||||
{crossFadeOverlapSizeRow}
|
||||
{crossFadeOffsetRateRow}
|
||||
{crossFadeEndRateRow}
|
||||
<div className="body-row divider"></div>
|
||||
{workletSettingRow}
|
||||
<div className="body-row divider"></div>
|
||||
{downSamplingModeRow}
|
||||
|
||||
</>
|
||||
)
|
||||
}, [mmvcServerUrlRow, protocolRow, sampleRateRow, sendingSampleRateRow, crossFadeOverlapSizeRow, crossFadeOffsetRateRow, crossFadeEndRateRow, workletSettingRow, downSamplingModeRow])
|
||||
|
||||
|
||||
const advancedSetting = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{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])
|
||||
|
||||
return {
|
||||
advancedSetting,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
37
client/so-vits-svc_40v2/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;
|
||||
};
|
26
client/so-vits-svc_40v2/src/const.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ClientType } from "@dannadori/voice-changer-client-js"
|
||||
|
||||
export const CLIENT_TYPE = ClientType.MMVCv13
|
||||
|
||||
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result"
|
||||
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original"
|
||||
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"
|
||||
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback"
|
||||
|
||||
|
||||
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/so-vits-svc_40v2/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;
|
||||
}
|
||||
}
|
538
client/so-vits-svc_40v2/src/css/App.css
Normal file
@ -0,0 +1,538 @@
|
||||
@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";
|
||||
@import "./Error.css";
|
||||
:root {
|
||||
--text-color: #333;
|
||||
--company-color1: rgba(64, 119, 187, 1);
|
||||
--company-color2: rgba(29, 47, 78, 1);
|
||||
--company-color3: rgba(255, 255, 255, 1);
|
||||
--company-color1-alpha: rgba(64, 119, 187, 0.3);
|
||||
--company-color2-alpha: rgba(29, 47, 78, 0.3);
|
||||
--company-color3-alpha: rgba(255, 255, 255, 0.3);
|
||||
--global-shadow-color: rgba(0, 0, 0, 0.4);
|
||||
|
||||
--sidebar-transition-time: 0.2s;
|
||||
--sidebar-transition-time-quick: 0.1s;
|
||||
--sidebar-transition-animation: ease-in-out;
|
||||
|
||||
--header-height: 1.5rem;
|
||||
--right-sidebar-width: 320px;
|
||||
|
||||
--dialog-border-color: rgba(100, 100, 100, 1);
|
||||
--dialog-shadow-color: rgba(0, 0, 0, 0.3);
|
||||
--dialog-background-color: rgba(255, 255, 255, 1);
|
||||
--dialog-primary-color: rgba(19, 70, 209, 1);
|
||||
--dialog-active-color: rgba(40, 70, 209, 1);
|
||||
--dialog-input-border-color: rgba(200, 200, 200, 1);
|
||||
--dialog-submit-button-color: rgba(180, 190, 230, 1);
|
||||
--dialog-cancel-button-color: rgba(235, 80, 80, 1);
|
||||
|
||||
--body-video-seeker-height: 3rem;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "Poppins", sans-serif;
|
||||
}
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
color: var(--text-color);
|
||||
/* background: linear-gradient(45deg, var(--company-color1) 0, 5%, var(--company-color2) 5% 10%, var(--company-color3) 10% 80%, var(--company-color1) 80% 85%, var(--company-color2) 85% 100%); */
|
||||
background: linear-gradient(45deg, var(--company-color1) 0, 1%, var(--company-color2) 1% 5%, var(--company-color3) 5% 80%, var(--company-color1) 80% 85%, var(--company-color2) 85% 100%);
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.first-gesture {
|
||||
background: rgba(200, 0, 0, 0.2);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Main + Section Partition*/
|
||||
.main-body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
font-family: "Yusei Magic", sans-serif;
|
||||
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;
|
||||
}
|
||||
.belongings {
|
||||
font-weight: 400;
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
margin-left: 10px;
|
||||
.belongings-checkbox {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.partition-content {
|
||||
position: static;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.row-split {
|
||||
}
|
||||
}
|
||||
}
|
||||
.state-control-checkbox:checked + .partition .partition-content {
|
||||
max-height: 700px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transition: all var(--sidebar-transition-time) var(--sidebar-transition-animation);
|
||||
}
|
||||
.state-control-checkbox + .partition .partition-content {
|
||||
max-height: 0px;
|
||||
background: rgba(233, 233, 255, 0.3);
|
||||
transition: all var(--sidebar-transition-time) var(--sidebar-transition-animation);
|
||||
}
|
||||
|
||||
/* ROW */
|
||||
|
||||
.split-6-4 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 60%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 60%;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
.split-4-6 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 40%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 40%;
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
.split-3-7 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.split-2-8 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 20%;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.split-3-3-4 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 60%;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
.split-2-5-3 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 20%;
|
||||
width: 50%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 70%;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
.split-4-4-2 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 40%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 40%;
|
||||
width: 40%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 80%;
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
.split-3-2-2-3 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 50%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(4) {
|
||||
left: 70%;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
.split-3-2-3-2 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 50%;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(4) {
|
||||
left: 80%;
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
.split-3-1-2-4 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 10%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 40%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(4) {
|
||||
left: 60%;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.split-3-2-1-4 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 50%;
|
||||
width: 10%;
|
||||
}
|
||||
& > div:nth-child(4) {
|
||||
left: 60%;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.split-3-2-2-2-1 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 50%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(4) {
|
||||
left: 70%;
|
||||
width: 20%;
|
||||
}
|
||||
& > div:nth-child(5) {
|
||||
left: 90%;
|
||||
width: 10%;
|
||||
}
|
||||
}
|
||||
.split-3-1-1-1-4 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 1px 0px 1px 0px;
|
||||
& > div:nth-child(1) {
|
||||
left: 0px;
|
||||
width: 30%;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
left: 30%;
|
||||
width: 10%;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
left: 40%;
|
||||
width: 10%;
|
||||
}
|
||||
& > div:nth-child(4) {
|
||||
left: 50%;
|
||||
width: 10%;
|
||||
}
|
||||
& > div:nth-child(5) {
|
||||
left: 60%;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.underline {
|
||||
border-bottom: 3px solid #333;
|
||||
}
|
||||
.left-padding-1 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.left-padding-2 {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
.left-margin-1 {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.left-margin-2 {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.highlight {
|
||||
background-color: rgba(200, 200, 255, 0.3);
|
||||
}
|
||||
.guided {
|
||||
/* background-color: rgba(9, 133, 67, 0.3); */
|
||||
background-color: rgba(159, 165, 162, 0.1);
|
||||
/* border-bottom: 1px solid rgba(9, 133, 67, 0.3); */
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 0.8rem;
|
||||
/* background-color: rgba(16, 210, 113, 0.1); */
|
||||
background-color: rgba(31, 42, 36, 0.1);
|
||||
}
|
||||
|
||||
.body-section-title {
|
||||
font-size: 1.5rem;
|
||||
color: rgb(51, 49, 49);
|
||||
}
|
||||
.body-sub-section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: rgb(3, 53, 12);
|
||||
}
|
||||
|
||||
.body-item-title {
|
||||
color: rgb(51, 99, 49);
|
||||
display: flex;
|
||||
}
|
||||
.body-item-text {
|
||||
color: rgb(30, 30, 30);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
.body-item-text-item {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
.body-item-text-small {
|
||||
color: rgb(30, 30, 30);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.body-input-container {
|
||||
display: flex;
|
||||
}
|
||||
.body-item-input {
|
||||
width: 60%;
|
||||
}
|
||||
.body-item-input-slider {
|
||||
width: 60%;
|
||||
}
|
||||
.body-item-input-slider-label {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.body-item-input-slider-val {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.body-button-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
& > div {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.body-button {
|
||||
user-select: none;
|
||||
border: solid 1px #999;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
&:hover {
|
||||
border: solid 1px #000;
|
||||
}
|
||||
}
|
||||
.body-button-disabled {
|
||||
user-select: none;
|
||||
border: solid 1px #999;
|
||||
border-radius: 2px;
|
||||
vertical-align: middle;
|
||||
background: #ddd;
|
||||
}
|
||||
.body-button-active {
|
||||
user-select: none;
|
||||
border: solid 1px #333;
|
||||
border-radius: 2px;
|
||||
background: #ada;
|
||||
}
|
||||
.body-button-stanby {
|
||||
user-select: none;
|
||||
border: solid 1px #999;
|
||||
border-radius: 2px;
|
||||
background: #aba;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
border: solid 1px #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
.body-select {
|
||||
color: rgb(30, 30, 30);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.body-select-50 {
|
||||
color: rgb(30, 30, 30);
|
||||
max-width: 50%;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.body-image-container,
|
||||
.body-wav-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
.body-image-container-title,
|
||||
.body-wav-container-title {
|
||||
width: 20%;
|
||||
}
|
||||
.body-image-container-img,
|
||||
.body-wav-container-wav {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.donate-img {
|
||||
border-radius: 35px;
|
||||
height: 1.5rem;
|
||||
}
|
30
client/so-vits-svc_40v2/src/css/Error.css
Normal file
@ -0,0 +1,30 @@
|
||||
.error-container {
|
||||
margin: 2rem;
|
||||
.top-error-message {
|
||||
color: #44a;
|
||||
font-size: 2rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
.top-error-description {
|
||||
color: #444;
|
||||
font-size: 1rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
.error-detail {
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid;
|
||||
.error-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
.error-message {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.error-info-container {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
.error-info-line {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
client/so-vits-svc_40v2/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,
|
||||
};
|
||||
};
|
33
client/so-vits-svc_40v2/tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
"lib": ["dom"],
|
||||
|
||||
/* ファイル名の大文字小文字を区別 */
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
/* 型チェック関係のオプション */
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
|
||||
/* Module解決方法 */
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
/* 型チェックだけさせたいので出力なし */
|
||||
// "noEmit": true,
|
||||
/* For avoid WebGL2 error */
|
||||
/* https://stackoverflow.com/questions/52846622/error-ts2430-interface-webglrenderingcontext-incorrectly-extends-interface-w */
|
||||
"skipLibCheck": true
|
||||
},
|
||||
/* tscコマンドで読み込むファイルを指定 */
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
51
client/so-vits-svc_40v2/webpack.common.js
Normal file
@ -0,0 +1,51 @@
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
module.exports = {
|
||||
mode: "production",
|
||||
entry: "./src/000_index.tsx",
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: [/\.ts$/, /\.tsx$/],
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"],
|
||||
plugins: ["@babel/plugin-transform-runtime"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: "html-loader",
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", { loader: "css-loader", options: { importLoaders: 1 } }, "postcss-loader"],
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
output: {
|
||||
filename: "index.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, "public/index.html"),
|
||||
filename: "./index.html",
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: "public/assets", to: "assets" }],
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: "public/favicon.ico", to: "favicon.ico" }],
|
||||
}),
|
||||
]
|
||||
};
|
20
client/so-vits-svc_40v2/webpack.dev.js
Normal file
@ -0,0 +1,20 @@
|
||||
const path = require("path");
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js')
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'development',
|
||||
devServer: {
|
||||
static: {
|
||||
directory: path.join(__dirname, "public"),
|
||||
},
|
||||
client: {
|
||||
overlay: {
|
||||
errors: false,
|
||||
warnings: false,
|
||||
},
|
||||
},
|
||||
host: "0.0.0.0",
|
||||
https: true,
|
||||
},
|
||||
})
|
6
client/so-vits-svc_40v2/webpack.prod.js
Normal file
@ -0,0 +1,6 @@
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js')
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
})
|
@ -37,5 +37,5 @@ def getFrontendPath():
|
||||
elif modelType == "MMVCv13":
|
||||
frontend_path = os.path.join(sys._MEIPASS, "dist_v13") if hasattr(sys, "_MEIPASS") else "../client/demo_v13/dist"
|
||||
elif modelType == "so-vits-svc-40v2":
|
||||
frontend_path = os.path.join(sys._MEIPASS, "dist_v13") if hasattr(sys, "_MEIPASS") else "../client/demo_v13/dist"
|
||||
frontend_path = os.path.join(sys._MEIPASS, "so-vits-svc_40v2") if hasattr(sys, "_MEIPASS") else "../client/so-vits-svc_40v2/dist"
|
||||
return frontend_path
|
||||
|