improve: add monitor to client mode

This commit is contained in:
w-okada 2023-08-03 05:15:05 +09:00
parent 2b08e47377
commit 0e878edc1e
9 changed files with 1279 additions and 50 deletions

View File

@ -1 +1,10 @@
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html> <!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8" />
<title>Voice Changer Client Demo</title>
<script defer src="index.js"></script></head>
<body style="width: 100%; height: 100%; margin: 0px">
<div id="app" style="width: 100%; height: 100%"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,31 +0,0 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@ -11,8 +11,8 @@
"build:dev": "npm-run-all clean webpack:dev", "build:dev": "npm-run-all clean webpack:dev",
"start": "webpack-dev-server --config webpack.dev.js", "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/", "build:mod": "cd ../lib && npm run build:dev && cd - && cp -r ../lib/dist/* node_modules/@dannadori/voice-changer-client-js/dist/",
"build:mod_dos": "cd ../lib && npm run build:dev && cd ../demo && copy ../lib/dist/index.js node_modules/@dannadori/voice-changer-client-js/dist/", "build:mod_dos": "cd ../lib && npm run build:dev && cd ../demo && npm-run-all build:mod_copy",
"build:mod_dos2": "copy ../lib/dist/index.js node_modules/@dannadori/voice-changer-client-js/dist/", "build:mod_copy": "XCOPY ..\\lib\\dist\\* .\\node_modules\\@dannadori\\voice-changer-client-js\\dist\\* /s /e /h /y",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"keywords": [ "keywords": [

View File

@ -63,6 +63,7 @@ type GuiStateAndMethod = {
outputAudioDeviceInfo: MediaDeviceInfo[]; outputAudioDeviceInfo: MediaDeviceInfo[];
audioInputForGUI: string; audioInputForGUI: string;
audioOutputForGUI: string; audioOutputForGUI: string;
audioMonitorForGUI: string;
fileInputEchoback: boolean | undefined; fileInputEchoback: boolean | undefined;
shareScreenEnabled: boolean; shareScreenEnabled: boolean;
audioOutputForAnalyzer: string; audioOutputForAnalyzer: string;
@ -70,6 +71,7 @@ type GuiStateAndMethod = {
setOutputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void; setOutputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void;
setAudioInputForGUI: (val: string) => void; setAudioInputForGUI: (val: string) => void;
setAudioOutputForGUI: (val: string) => void; setAudioOutputForGUI: (val: string) => void;
setAudioMonitorForGUI: (val: string) => void;
setFileInputEchoback: (val: boolean) => void; setFileInputEchoback: (val: boolean) => void;
setShareScreenEnabled: (val: boolean) => void; setShareScreenEnabled: (val: boolean) => void;
setAudioOutputForAnalyzer: (val: string) => void; setAudioOutputForAnalyzer: (val: string) => void;
@ -106,6 +108,7 @@ export const GuiStateProvider = ({ children }: Props) => {
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([]); const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([]);
const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none"); const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none");
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none"); const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none");
const [audioMonitorForGUI, setAudioMonitorForGUI] = useState<string>("none");
const [fileInputEchoback, setFileInputEchoback] = useState<boolean>(false); //最初のmuteが有効になるように。undefined <-- ??? falseしておけばよさそう。undefinedだとwarningがでる。 const [fileInputEchoback, setFileInputEchoback] = useState<boolean>(false); //最初のmuteが有効になるように。undefined <-- ??? falseしておけばよさそう。undefinedだとwarningがでる。
const [shareScreenEnabled, setShareScreenEnabled] = useState<boolean>(false); const [shareScreenEnabled, setShareScreenEnabled] = useState<boolean>(false);
const [audioOutputForAnalyzer, setAudioOutputForAnalyzer] = useState<string>("default"); const [audioOutputForAnalyzer, setAudioOutputForAnalyzer] = useState<string>("default");
@ -270,6 +273,7 @@ export const GuiStateProvider = ({ children }: Props) => {
outputAudioDeviceInfo, outputAudioDeviceInfo,
audioInputForGUI, audioInputForGUI,
audioOutputForGUI, audioOutputForGUI,
audioMonitorForGUI,
fileInputEchoback, fileInputEchoback,
shareScreenEnabled, shareScreenEnabled,
audioOutputForAnalyzer, audioOutputForAnalyzer,
@ -277,6 +281,7 @@ export const GuiStateProvider = ({ children }: Props) => {
setOutputAudioDeviceInfo, setOutputAudioDeviceInfo,
setAudioInputForGUI, setAudioInputForGUI,
setAudioOutputForGUI, setAudioOutputForGUI,
setAudioMonitorForGUI,
setFileInputEchoback, setFileInputEchoback,
setShareScreenEnabled, setShareScreenEnabled,
setAudioOutputForAnalyzer, setAudioOutputForAnalyzer,

View File

@ -2,14 +2,14 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider"; import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js"; import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { useGuiState } from "../001_GuiStateProvider"; import { useGuiState } from "../001_GuiStateProvider";
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const"; import { AUDIO_ELEMENT_FOR_PLAY_MONITOR, AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_MONITR, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const";
import { isDesktopApp } from "../../../const"; import { isDesktopApp } from "../../../const";
export type DeviceAreaProps = {}; export type DeviceAreaProps = {};
export const DeviceArea = (_props: DeviceAreaProps) => { export const DeviceArea = (_props: DeviceAreaProps) => {
const { setting, serverSetting, audioContext, setAudioOutputElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState(); const { setting, serverSetting, audioContext, setAudioOutputElementId, setAudioMonitorElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo, shareScreenEnabled, setShareScreenEnabled } = useGuiState(); const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, setAudioMonitorForGUI, audioOutputForGUI, audioMonitorForGUI, outputAudioDeviceInfo, shareScreenEnabled, setShareScreenEnabled } = useGuiState();
const [inputHostApi, setInputHostApi] = useState<string>("ALL"); const [inputHostApi, setInputHostApi] = useState<string>("ALL");
const [outputHostApi, setOutputHostApi] = useState<string>("ALL"); const [outputHostApi, setOutputHostApi] = useState<string>("ALL");
const [monitorHostApi, setMonitorHostApi] = useState<string>("ALL"); const [monitorHostApi, setMonitorHostApi] = useState<string>("ALL");
@ -244,10 +244,10 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
audio_echo.volume = 0; audio_echo.volume = 0;
setFileInputEchoback(false); setFileInputEchoback(false);
// original stream to play. // // original stream to play.
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement; // const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement;
audio_org.src = url; // audio_org.src = url;
audio_org.pause(); // audio_org.pause();
}; };
const echobackClass = fileInputEchoback ? "config-sub-area-control-field-wav-file-echoback-button-active" : "config-sub-area-control-field-wav-file-echoback-button"; const echobackClass = fileInputEchoback ? "config-sub-area-control-field-wav-file-echoback-button-active" : "config-sub-area-control-field-wav-file-echoback-button";
@ -256,7 +256,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-wav-file left-padding-1"> <div className="config-sub-area-control-field-wav-file left-padding-1">
<div className="config-sub-area-control-field-wav-file-audio-container"> <div className="config-sub-area-control-field-wav-file-audio-container">
<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio> {/* <audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio> */}
<audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls controlsList="nodownload noplaybackrate"></audio> <audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls controlsList="nodownload noplaybackrate"></audio>
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio> <audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
</div> </div>
@ -381,7 +381,8 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const setAudioOutput = async () => { const setAudioOutput = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices(); const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => { // [AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement; const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) { if (audio) {
if (serverSetting.serverSetting.enableServerAudio == 1) { if (serverSetting.serverSetting.enableServerAudio == 1) {
@ -598,7 +599,88 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
); );
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]); }, [serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (6) Monitor // (6) モニター
useEffect(() => {
const loadCache = async () => {
const key = await getItem(INDEXEDDB_KEY_AUDIO_MONITR);
if (key) {
setAudioMonitorForGUI(key as string);
}
};
loadCache();
}, []);
useEffect(() => {
const setAudioMonitor = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_MONITOR].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) {
if (serverSetting.serverSetting.enableServerAudio == 1) {
// Server Audio を使う場合はElementから音は出さない。
audio.volume = 0;
} else if (audioMonitorForGUI == "none") {
// @ts-ignore
audio.setSinkId("");
audio.volume = 0;
} else {
const audioOutputs = mediaDeviceInfos.filter((x) => {
return x.kind == "audiooutput";
});
const found = audioOutputs.some((x) => {
return x.deviceId == audioMonitorForGUI;
});
if (found) {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioMonitorForGUI);
audio.volume = 1;
} else {
console.warn("No audio output device. use default");
}
}
}
});
};
setAudioMonitor();
}, [audioMonitorForGUI, serverSetting.serverSetting.enableServerAudio]);
// (6-1) クライアント
const clientMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>;
}
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">monitor</div>
<div className="config-sub-area-control-field">
<select
className="body-select"
value={audioMonitorForGUI}
onChange={(e) => {
setAudioMonitorForGUI(e.target.value);
setItem(INDEXEDDB_KEY_AUDIO_MONITR, e.target.value);
}}
>
{outputAudioDeviceInfo.map((x) => {
return (
<option key={x.deviceId} value={x.deviceId}>
{x.label}
</option>
);
})}
</select>
</div>
</div>
);
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioMonitorForGUI]);
useEffect(() => {
console.log("initializedRef.current", initializedRef.current);
setAudioMonitorElementId(AUDIO_ELEMENT_FOR_PLAY_MONITOR);
}, [initializedRef.current]);
// (6-2) サーバ
const serverMonitorRow = useMemo(() => { const serverMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) { if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>; return <></>;
@ -685,10 +767,12 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
{audioInputScreenRow} {audioInputScreenRow}
{clientAudioOutputRow} {clientAudioOutputRow}
{serverAudioOutputRow} {serverAudioOutputRow}
{clientMonitorRow}
{serverMonitorRow} {serverMonitorRow}
{outputRecorderRow} {outputRecorderRow}
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio> <audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_MONITOR}></audio>
</div> </div>
); );
}; };

View File

@ -1,13 +1,15 @@
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result" export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result" // 変換後の出力用プレイヤー
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original" export const AUDIO_ELEMENT_FOR_PLAY_MONITOR = "audio-monitor" // 変換後のモニター用プレイヤー
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted" export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original" // ??? 使ってないかも。
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback" export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted" // ファイルインプットのコントロール
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback" // ファイルインプットのエコーバック
export const AUDIO_ELEMENT_FOR_SAMPLING_INPUT = "body-wav-container-wav-input" export const AUDIO_ELEMENT_FOR_SAMPLING_INPUT = "body-wav-container-wav-input"
export const AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT = "body-wav-container-wav-output" export const AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT = "body-wav-container-wav-output"
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT" export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT"
export const INDEXEDDB_KEY_AUDIO_MONITR = "INDEXEDDB_KEY_AUDIO_MONITOR"
export const INDEXEDDB_KEY_DEFAULT_MODEL_TYPE = "INDEXEDDB_KEY_DEFALT_MODEL_TYPE" export const INDEXEDDB_KEY_DEFAULT_MODEL_TYPE = "INDEXEDDB_KEY_DEFALT_MODEL_TYPE"

View File

@ -97,6 +97,7 @@ export const ServerSettingKey = {
"serverReadChunkSize": "serverReadChunkSize", "serverReadChunkSize": "serverReadChunkSize",
"serverInputAudioGain": "serverInputAudioGain", "serverInputAudioGain": "serverInputAudioGain",
"serverOutputAudioGain": "serverOutputAudioGain", "serverOutputAudioGain": "serverOutputAudioGain",
"serverMonitorAudioGain": "serverMonitorAudioGain",
"tran": "tran", "tran": "tran",
"noiseScale": "noiseScale", "noiseScale": "noiseScale",
@ -157,6 +158,7 @@ export type VoiceChangerServerSetting = {
serverReadChunkSize: number serverReadChunkSize: number
serverInputAudioGain: number serverInputAudioGain: number
serverOutputAudioGain: number serverOutputAudioGain: number
serverMonitorAudioGain: number
tran: number // so-vits-svc tran: number // so-vits-svc
@ -361,6 +363,7 @@ export const DefaultServerSetting: ServerInfo = {
serverReadChunkSize: 256, serverReadChunkSize: 256,
serverInputAudioGain: 1.0, serverInputAudioGain: 1.0,
serverOutputAudioGain: 1.0, serverOutputAudioGain: 1.0,
serverMonitorAudioGain: 1.0,
// VC Specific // VC Specific
srcId: 0, srcId: 0,

View File

@ -47,6 +47,7 @@ export type ClientState = {
clearSetting: () => Promise<void> clearSetting: () => Promise<void>
// AudioOutputElement 設定 // AudioOutputElement 設定
setAudioOutputElementId: (elemId: string) => void setAudioOutputElementId: (elemId: string) => void
setAudioMonitorElementId: (elemId: string) => void
ioErrorCount: number ioErrorCount: number
resetIoErrorCount: () => void resetIoErrorCount: () => void
@ -215,6 +216,18 @@ export const useClient = (props: UseClientProps): ClientState => {
} }
} }
const setAudioMonitorElementId = (elemId: string) => {
if (!voiceChangerClientRef.current) {
console.warn("[voiceChangerClient] is not ready for set audio output.")
return
}
const audio = document.getElementById(elemId) as HTMLAudioElement
if (audio.paused) {
audio.srcObject = voiceChangerClientRef.current.stream
audio.play()
}
}
// (2-2) 情報リロード // (2-2) 情報リロード
const getInfo = useMemo(() => { const getInfo = useMemo(() => {
return async () => { return async () => {
@ -286,6 +299,7 @@ export const useClient = (props: UseClientProps): ClientState => {
// AudioOutputElement 設定 // AudioOutputElement 設定
setAudioOutputElementId, setAudioOutputElementId,
setAudioMonitorElementId,
ioErrorCount, ioErrorCount,
resetIoErrorCount resetIoErrorCount