- device output recorder button is showed in server device mode.
Feature:
 - server device monitor
Improve:
 - default uvicorn error log
This commit is contained in:
w-okada 2023-07-01 14:57:57 +09:00
parent b051d08ae0
commit 75668e1534
17 changed files with 2528 additions and 2022 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -26,14 +26,14 @@
"@babel/preset-env": "^7.22.5", "@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5", "@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5", "@babel/preset-typescript": "^7.22.5",
"@types/node": "^20.3.2", "@types/node": "^20.3.3",
"@types/react": "^18.2.14", "@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.6",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"eslint": "^8.43.0", "eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.32.2",
@ -54,7 +54,7 @@
"webpack-dev-server": "^4.15.1" "webpack-dev-server": "^4.15.1"
}, },
"dependencies": { "dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.155", "@dannadori/voice-changer-client-js": "^1.0.157",
"@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0",

View File

@ -1,35 +1,34 @@
import React, { useEffect, useMemo, useRef, useState } from "react" 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_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } 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, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo } = useGuiState() const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo } = 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 audioSrcNode = useRef<MediaElementAudioSourceNode>() const audioSrcNode = useRef<MediaElementAudioSourceNode>();
const { getItem, setItem } = useIndexedDB({ clientType: null }) const { getItem, setItem } = useIndexedDB({ clientType: null });
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false) const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false);
// (1) Audio Mode // (1) Audio Mode
const deviceModeRow = useMemo(() => { const deviceModeRow = useMemo(() => {
const enableServerAudio = serverSetting.serverSetting.enableServerAudio const enableServerAudio = serverSetting.serverSetting.enableServerAudio;
const clientChecked = enableServerAudio == 1 ? false : true const clientChecked = enableServerAudio == 1 ? false : true;
const serverChecked = enableServerAudio == 1 ? true : false const serverChecked = enableServerAudio == 1 ? true : false;
const onDeviceModeChanged = (val: number) => { const onDeviceModeChanged = (val: number) => {
if (isConverting) { if (isConverting) {
alert("cannot change mode when voice conversion is enabled") alert("cannot change mode when voice conversion is enabled");
return return;
}
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, enableServerAudio: val })
} }
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, enableServerAudio: val });
};
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
@ -37,131 +36,182 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<div className="config-sub-area-noise-container"> <div className="config-sub-area-noise-container">
<div className="config-sub-area-noise-checkbox-container"> <div className="config-sub-area-noise-checkbox-container">
<input type="radio" id="client-device" name="device-mode" checked={clientChecked} onChange={() => { onDeviceModeChanged(0) }} /> <label htmlFor="client-device">client</label> <input
type="radio"
id="client-device"
name="device-mode"
checked={clientChecked}
onChange={() => {
onDeviceModeChanged(0);
}}
/>{" "}
<label htmlFor="client-device">client</label>
</div> </div>
<div className="config-sub-area-noise-checkbox-container"> <div className="config-sub-area-noise-checkbox-container">
<input className="left-padding-1" type="radio" id="server-device" name="device-mode" checked={serverChecked} onChange={() => { onDeviceModeChanged(1) }} /> <input
className="left-padding-1"
type="radio"
id="server-device"
name="device-mode"
checked={serverChecked}
onChange={() => {
onDeviceModeChanged(1);
}}
/>
<label htmlFor="server-device">server</label> <label htmlFor="server-device">server</label>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) );
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, isConverting]) }, [serverSetting.serverSetting, serverSetting.updateServerSettings, isConverting]);
// (2) Audio Input // (2) Audio Input
// キャッシュの設定は反映(たぶん、設定操作の時も起動していしまう。が問題は起こらないはず) // キャッシュの設定は反映(たぶん、設定操作の時も起動していしまう。が問題は起こらないはず)
useEffect(() => { useEffect(() => {
if (typeof setting.voiceChangerClientSetting.audioInput == "string") { if (typeof setting.voiceChangerClientSetting.audioInput == "string") {
if (inputAudioDeviceInfo.find(x => { if (
inputAudioDeviceInfo.find((x) => {
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput) // console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
return x.deviceId == setting.voiceChangerClientSetting.audioInput return x.deviceId == setting.voiceChangerClientSetting.audioInput;
})) { })
setAudioInputForGUI(setting.voiceChangerClientSetting.audioInput) ) {
setAudioInputForGUI(setting.voiceChangerClientSetting.audioInput);
} }
} }
}, [inputAudioDeviceInfo, setting.voiceChangerClientSetting.audioInput]) }, [inputAudioDeviceInfo, setting.voiceChangerClientSetting.audioInput]);
// (2-1) クライアント // (2-1) クライアント
const clientAudioInputRow = useMemo(() => { const clientAudioInputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) { if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></> return <></>;
} }
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">input</div> <div className="config-sub-area-control-title left-padding-1">input</div>
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<select className="body-select" value={audioInputForGUI} onChange={async (e) => { <select
setAudioInputForGUI(e.target.value) className="body-select"
value={audioInputForGUI}
onChange={async (e) => {
setAudioInputForGUI(e.target.value);
if (e.target.value != "file") { if (e.target.value != "file") {
try { try {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: e.target.value }) await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: e.target.value });
} catch (e) { } catch (e) {
alert(e) alert(e);
console.error(e) console.error(e);
setAudioInputForGUI("none") setAudioInputForGUI("none");
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: null }) await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: null });
} }
} }
}}> }}
{ >
inputAudioDeviceInfo.map(x => { {inputAudioDeviceInfo.map((x) => {
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option> return (
}) <option key={x.deviceId} value={x.deviceId}>
} {x.label}
</option>
);
})}
</select> </select>
</div> </div>
</div> </div>
) );
}, [setVoiceChangerClientSetting, setting, inputAudioDeviceInfo, audioInputForGUI, serverSetting.serverSetting.enableServerAudio]) }, [setVoiceChangerClientSetting, setting, inputAudioDeviceInfo, audioInputForGUI, serverSetting.serverSetting.enableServerAudio]);
// (2-2) サーバ // (2-2) サーバ
const serverAudioInputRow = useMemo(() => { const serverAudioInputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) { if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></> return <></>;
} }
const devices = serverSetting.serverSetting.serverAudioInputDevices const devices = serverSetting.serverSetting.serverAudioInputDevices;
const hostAPIs = new Set(devices.map(x => { return x.hostAPI })) const hostAPIs = new Set(
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => { return <option value={x} key={index} >{x}</option> }) devices.map((x) => {
return x.hostAPI;
})
);
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => {
return (
<option value={x} key={index}>
{x}
</option>
);
});
const filteredDevice = devices.map((x, index) => { const filteredDevice = devices
.map((x, index) => {
if (inputHostApi != "ALL" && x.hostAPI != inputHostApi) { if (inputHostApi != "ALL" && x.hostAPI != inputHostApi) {
return null return null;
} }
return <option value={x.index} key={index}>[{x.hostAPI}]{x.name}</option> return (
}).filter(x => x != null) <option value={x.index} key={index}>
[{x.hostAPI}]{x.name}
</option>
);
})
.filter((x) => x != null);
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">input</div> <div className="config-sub-area-control-title left-padding-1">input</div>
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-auido-io"> <div className="config-sub-area-control-field-auido-io">
<select className="config-sub-area-control-field-auido-io-filter" name="kinds" id="kinds" value={inputHostApi} onChange={(e) => { setInputHostApi(e.target.value) }}> <select
<option value="ALL" key="ALL" >ALL</option> className="config-sub-area-control-field-auido-io-filter"
name="kinds"
id="kinds"
value={inputHostApi}
onChange={(e) => {
setInputHostApi(e.target.value);
}}
>
<option value="ALL" key="ALL">
ALL
</option>
{hostAPIOptions} {hostAPIOptions}
</select> </select>
<select className="config-sub-area-control-field-auido-io-select" value={serverSetting.serverSetting.serverInputDeviceId} onChange={(e) => { <select
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputDeviceId: Number(e.target.value) }) className="config-sub-area-control-field-auido-io-select"
value={serverSetting.serverSetting.serverInputDeviceId}
}}> onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputDeviceId: Number(e.target.value) });
}}
>
{filteredDevice} {filteredDevice}
</select> </select>
</div> </div>
</div> </div>
</div> </div>
) );
}, [inputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
}, [inputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio])
// (2-3) File // (2-3) File
useEffect(() => { useEffect(() => {
[AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => { [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) {
audio.volume = fileInputEchoback ? 1 : 0 audio.volume = fileInputEchoback ? 1 : 0;
} }
}) });
}, [fileInputEchoback]) }, [fileInputEchoback]);
const audioInputMediaRow = useMemo(() => { const audioInputMediaRow = useMemo(() => {
if (audioInputForGUI != "file" || serverSetting.serverSetting.enableServerAudio == 1) { if (audioInputForGUI != "file" || serverSetting.serverSetting.enableServerAudio == 1) {
return <></> return <></>;
} }
const onFileLoadClicked = async () => { const onFileLoadClicked = async () => {
const url = await fileSelectorAsDataURL("") const url = await fileSelectorAsDataURL("");
// input stream for client. // input stream for client.
const audio = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED) as HTMLAudioElement const audio = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED) as HTMLAudioElement;
audio.pause() audio.pause();
audio.srcObject = null audio.srcObject = null;
audio.src = url audio.src = url;
await audio.play() await audio.play();
if (!audioSrcNode.current) { if (!audioSrcNode.current) {
audioSrcNode.current = audioContext!.createMediaElementSource(audio); audioSrcNode.current = audioContext!.createMediaElementSource(audio);
} }
@ -169,27 +219,27 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
audioSrcNode.current = audioContext!.createMediaElementSource(audio); audioSrcNode.current = audioContext!.createMediaElementSource(audio);
} }
const dst = audioContext.createMediaStreamDestination() const dst = audioContext.createMediaStreamDestination();
audioSrcNode.current.connect(dst) audioSrcNode.current.connect(dst);
try { try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: dst.stream }) setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: dst.stream });
} catch (e) { } catch (e) {
console.error(e) console.error(e);
} }
const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement const audio_echo = document.getElementById(AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) as HTMLAudioElement;
audio_echo.srcObject = dst.stream audio_echo.srcObject = dst.stream;
audio_echo.play() audio_echo.play();
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";
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
@ -202,190 +252,332 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div> <div>
<img className="config-sub-area-control-field-wav-file-folder" src="./assets/icons/folder.svg" onClick={onFileLoadClicked} /> <img className="config-sub-area-control-field-wav-file-folder" src="./assets/icons/folder.svg" onClick={onFileLoadClicked} />
</div> </div>
<div className={echobackClass} onClick={() => { setFileInputEchoback(!fileInputEchoback) }}> <div
className={echobackClass}
onClick={() => {
setFileInputEchoback(!fileInputEchoback);
}}
>
echo{fileInputEchoback} echo{fileInputEchoback}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) );
}, [audioInputForGUI, fileInputEchoback, serverSetting.serverSetting]) }, [audioInputForGUI, fileInputEchoback, serverSetting.serverSetting]);
// (3) Audio Output // (3) Audio Output
useEffect(() => { useEffect(() => {
const loadCache = async () => { const loadCache = async () => {
const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT) const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT);
if (key) { if (key) {
setAudioOutputForGUI(key as string) setAudioOutputForGUI(key as string);
} }
} };
loadCache() loadCache();
}, []) }, []);
useEffect(() => { useEffect(() => {
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) => {
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) {
// Server Audio を使う場合はElementから音は出さない。 // Server Audio を使う場合はElementから音は出さない。
audio.volume = 0 audio.volume = 0;
} else if (audioOutputForGUI == "none") { } else if (audioOutputForGUI == "none") {
// @ts-ignore // @ts-ignore
audio.setSinkId("") audio.setSinkId("");
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) { if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
audio.volume = 0 audio.volume = 0;
} else { } else {
audio.volume = 0 audio.volume = 0;
} }
} else { } else {
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" }) const audioOutputs = mediaDeviceInfos.filter((x) => {
const found = audioOutputs.some(x => { return x.deviceId == audioOutputForGUI }) return x.kind == "audiooutput";
});
const found = audioOutputs.some((x) => {
return x.deviceId == audioOutputForGUI;
});
if (found) { if (found) {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。 // @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioOutputForGUI) audio.setSinkId(audioOutputForGUI);
} else { } else {
console.warn("No audio output device. use default") console.warn("No audio output device. use default");
} }
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) { if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
audio.volume = fileInputEchoback ? 1 : 0 audio.volume = fileInputEchoback ? 1 : 0;
} else { } else {
audio.volume = 1 audio.volume = 1;
} }
} }
} }
}) });
} };
setAudioOutput() setAudioOutput();
}, [audioOutputForGUI, fileInputEchoback, serverSetting.serverSetting.enableServerAudio]) }, [audioOutputForGUI, fileInputEchoback, serverSetting.serverSetting.enableServerAudio]);
// (3-1) クライアント // (3-1) クライアント
const clientAudioOutputRow = useMemo(() => { const clientAudioOutputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) { if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></> return <></>;
} }
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">output</div> <div className="config-sub-area-control-title left-padding-1">output</div>
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<select className="body-select" value={audioOutputForGUI} onChange={(e) => { <select
setAudioOutputForGUI(e.target.value) className="body-select"
setItem(INDEXEDDB_KEY_AUDIO_OUTPUT, e.target.value) value={audioOutputForGUI}
}}> onChange={(e) => {
{ setAudioOutputForGUI(e.target.value);
outputAudioDeviceInfo.map(x => { setItem(INDEXEDDB_KEY_AUDIO_OUTPUT, e.target.value);
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option> }}
}) >
} {outputAudioDeviceInfo.map((x) => {
return (
<option key={x.deviceId} value={x.deviceId}>
{x.label}
</option>
);
})}
</select> </select>
</div> </div>
</div> </div>
) );
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioOutputForGUI]) }, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioOutputForGUI]);
useEffect(() => { useEffect(() => {
console.log("initializedRef.current", initializedRef.current) console.log("initializedRef.current", initializedRef.current);
setAudioOutputElementId(AUDIO_ELEMENT_FOR_PLAY_RESULT) setAudioOutputElementId(AUDIO_ELEMENT_FOR_PLAY_RESULT);
}, [initializedRef.current]) }, [initializedRef.current]);
// (3-2) サーバ // (3-2) サーバ
const serverAudioOutputRow = useMemo(() => { const serverAudioOutputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) { if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></> return <></>;
} }
const devices = serverSetting.serverSetting.serverAudioOutputDevices const devices = serverSetting.serverSetting.serverAudioOutputDevices;
const hostAPIs = new Set(devices.map(x => { return x.hostAPI })) const hostAPIs = new Set(
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => { return <option value={x} key={index} >{x}</option> }) devices.map((x) => {
return x.hostAPI;
})
);
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => {
return (
<option value={x} key={index}>
{x}
</option>
);
});
const filteredDevice = devices.map((x, index) => { const filteredDevice = devices
.map((x, index) => {
if (outputHostApi != "ALL" && x.hostAPI != outputHostApi) { if (outputHostApi != "ALL" && x.hostAPI != outputHostApi) {
return null return null;
} }
return <option value={x.index} key={index}>[{x.hostAPI}]{x.name}</option> return (
}).filter(x => x != null) <option value={x.index} key={index}>
[{x.hostAPI}]{x.name}
</option>
);
})
.filter((x) => x != null);
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">output</div> <div className="config-sub-area-control-title left-padding-1">output</div>
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-auido-io"> <div className="config-sub-area-control-field-auido-io">
<select className="config-sub-area-control-field-auido-io-filter" name="kinds" id="kinds" value={outputHostApi} onChange={(e) => { setOutputHostApi(e.target.value) }}> <select
<option value="ALL" key="ALL" >ALL</option> className="config-sub-area-control-field-auido-io-filter"
name="kinds"
id="kinds"
value={outputHostApi}
onChange={(e) => {
setOutputHostApi(e.target.value);
}}
>
<option value="ALL" key="ALL">
ALL
</option>
{hostAPIOptions} {hostAPIOptions}
</select> </select>
<select className="config-sub-area-control-field-auido-io-select" value={serverSetting.serverSetting.serverOutputDeviceId} onChange={(e) => { <select
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputDeviceId: Number(e.target.value) }) className="config-sub-area-control-field-auido-io-select"
}}> value={serverSetting.serverSetting.serverOutputDeviceId}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputDeviceId: Number(e.target.value) });
}}
>
{filteredDevice} {filteredDevice}
</select> </select>
</div> </div>
</div> </div>
</div> </div>
) );
}, [outputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]) }, [outputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (4) レコーダー // (4) レコーダー
const outputRecorderRow = useMemo(() => { const outputRecorderRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) { if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></> return <></>;
} }
const onOutputRecordStartClicked = async () => { const onOutputRecordStartClicked = async () => {
setOutputRecordingStarted(true) setOutputRecordingStarted(true);
await startOutputRecording() await startOutputRecording();
} };
const onOutputRecordStopClicked = async () => { const onOutputRecordStopClicked = async () => {
setOutputRecordingStarted(false) setOutputRecordingStarted(false);
const record = await stopOutputRecording() const record = await stopOutputRecording();
downloadRecord(record) downloadRecord(record);
} };
const startClassName = outputRecordingStarted ? "config-sub-area-button-active" : "config-sub-area-button" const startClassName = outputRecordingStarted ? "config-sub-area-button-active" : "config-sub-area-button";
const stopClassName = outputRecordingStarted ? "config-sub-area-button" : "config-sub-area-button-active" const stopClassName = outputRecordingStarted ? "config-sub-area-button" : "config-sub-area-button-active";
return ( return (
<div className="config-sub-area-control"> <div className="config-sub-area-control">
<div className="config-sub-area-control-title">REC.</div> <div className="config-sub-area-control-title">REC.</div>
<div className="config-sub-area-control-field"> <div className="config-sub-area-control-field">
<div className="config-sub-area-buttons"> <div className="config-sub-area-buttons">
<div onClick={onOutputRecordStartClicked} className={startClassName}>start</div> <div onClick={onOutputRecordStartClicked} className={startClassName}>
<div onClick={onOutputRecordStopClicked} className={stopClassName}>stop</div> start
</div>
<div onClick={onOutputRecordStopClicked} className={stopClassName}>
stop
</div> </div>
</div> </div>
</div> </div>
) </div>
);
}, [outputRecordingStarted, startOutputRecording, stopOutputRecording, serverSetting.serverSetting.enableServerAudio]);
}, [outputRecordingStarted, startOutputRecording, stopOutputRecording]) // (5) サンプリングレート
const sampleRateRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>;
}
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">S.R.</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-auido-io">
<select
className="config-sub-area-control-field-sample-rate-select"
value={serverSetting.serverSetting.serverAudioSampleRate}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioSampleRate: Number(e.target.value) });
}}
>
{[16000, 32000, 44100, 48000, 96000, 192000].map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
})}
</select>
</div>
</div>
</div>
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (6) Monitor
const serverMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>;
}
const devices = serverSetting.serverSetting.serverAudioOutputDevices;
const hostAPIs = new Set(
devices.map((x) => {
return x.hostAPI;
})
);
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => {
return (
<option value={x} key={index}>
{x}
</option>
);
});
const filteredDevice = devices
.map((x, index) => {
if (outputHostApi != "ALL" && x.hostAPI != outputHostApi) {
return null;
}
return (
<option value={x.index} key={index}>
[{x.hostAPI}]{x.name}
</option>
);
})
.filter((x) => x != null);
filteredDevice.unshift(
<option value={-1} key={-1}>
None
</option>
);
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">
<div className="config-sub-area-control-field-auido-io">
<select
className="config-sub-area-control-field-auido-io-filter"
name="kinds"
id="kinds"
value={outputHostApi}
onChange={(e) => {
setOutputHostApi(e.target.value);
}}
>
<option value="ALL" key="ALL">
ALL
</option>
{hostAPIOptions}
</select>
<select
className="config-sub-area-control-field-auido-io-select"
value={serverSetting.serverSetting.serverMonitorDeviceId}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverMonitorDeviceId: Number(e.target.value) });
}}
>
{filteredDevice}
</select>
</div>
</div>
</div>
);
}, [outputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
return ( return (
<div className="config-sub-area"> <div className="config-sub-area">
{deviceModeRow} {deviceModeRow}
{sampleRateRow}
{clientAudioInputRow} {clientAudioInputRow}
{serverAudioInputRow} {serverAudioInputRow}
{audioInputMediaRow} {audioInputMediaRow}
{clientAudioOutputRow} {clientAudioOutputRow}
{serverAudioOutputRow} {serverAudioOutputRow}
{serverMonitorRow}
{outputRecorderRow} {outputRecorderRow}
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio> <audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
</div> </div>
) );
} };
const downloadRecord = (data: Float32Array) => { const downloadRecord = (data: Float32Array) => {
const writeString = (view: DataView, offset: number, string: string) => { const writeString = (view: DataView, offset: number, string: string) => {
for (var i = 0; i < string.length; i++) { for (var i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i)); view.setUint8(offset + i, string.charCodeAt(i));
@ -395,7 +587,7 @@ const downloadRecord = (data: Float32Array) => {
const floatTo16BitPCM = (output: DataView, offset: number, input: Float32Array) => { const floatTo16BitPCM = (output: DataView, offset: number, input: Float32Array) => {
for (var i = 0; i < input.length; i++, offset += 2) { for (var i = 0; i < input.length; i++, offset += 2) {
var s = Math.max(-1, Math.min(1, input[i])); var s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
} }
}; };
@ -403,10 +595,10 @@ const downloadRecord = (data: Float32Array) => {
const view = new DataView(buffer); const view = new DataView(buffer);
// https://www.youfit.co.jp/archives/1418 // https://www.youfit.co.jp/archives/1418
writeString(view, 0, 'RIFF'); // RIFFヘッダ writeString(view, 0, "RIFF"); // RIFFヘッダ
view.setUint32(4, 32 + data.length * 2, true); // これ以降のファイルサイズ view.setUint32(4, 32 + data.length * 2, true); // これ以降のファイルサイズ
writeString(view, 8, 'WAVE'); // WAVEヘッダ writeString(view, 8, "WAVE"); // WAVEヘッダ
writeString(view, 12, 'fmt '); // fmtチャンク writeString(view, 12, "fmt "); // fmtチャンク
view.setUint32(16, 16, true); // fmtチャンクのバイト数 view.setUint32(16, 16, true); // fmtチャンクのバイト数
view.setUint16(20, 1, true); // フォーマットID view.setUint16(20, 1, true); // フォーマットID
view.setUint16(22, 1, true); // チャンネル数 view.setUint16(22, 1, true); // チャンネル数
@ -414,10 +606,10 @@ const downloadRecord = (data: Float32Array) => {
view.setUint32(28, 48000 * 2, true); // データ速度 view.setUint32(28, 48000 * 2, true); // データ速度
view.setUint16(32, 2, true); // ブロックサイズ view.setUint16(32, 2, true); // ブロックサイズ
view.setUint16(34, 16, true); // サンプルあたりのビット数 view.setUint16(34, 16, true); // サンプルあたりのビット数
writeString(view, 36, 'data'); // dataチャンク writeString(view, 36, "data"); // dataチャンク
view.setUint32(40, data.length * 2, true); // 波形データのバイト数 view.setUint32(40, data.length * 2, true); // 波形データのバイト数
floatTo16BitPCM(view, 44, data); // 波形データ floatTo16BitPCM(view, 44, data); // 波形データ
const audioBlob = new Blob([view], { type: 'audio/wav' }); const audioBlob = new Blob([view], { type: "audio/wav" });
const url = URL.createObjectURL(audioBlob); const url = URL.createObjectURL(audioBlob);
const a = document.createElement("a"); const a = document.createElement("a");
@ -427,4 +619,4 @@ const downloadRecord = (data: Float32Array) => {
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} };

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@dannadori/voice-changer-client-js", "name": "@dannadori/voice-changer-client-js",
"version": "1.0.155", "version": "1.0.157",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"directories": { "directories": {
@ -27,10 +27,10 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/audioworklet": "^0.0.48", "@types/audioworklet": "^0.0.48",
"@types/node": "^20.3.2", "@types/node": "^20.3.3",
"@types/react": "18.2.14", "@types/react": "18.2.14",
"@types/react-dom": "18.2.6", "@types/react-dom": "18.2.6",
"eslint": "^8.43.0", "eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.32.2",
@ -47,7 +47,7 @@
}, },
"dependencies": { "dependencies": {
"@types/readable-stream": "^2.3.15", "@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.14.1", "amazon-chime-sdk-js": "^3.15.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -83,12 +83,15 @@ export const ServerSettingKey = {
"enableServerAudio": "enableServerAudio", "enableServerAudio": "enableServerAudio",
"serverAudioStated": "serverAudioStated", "serverAudioStated": "serverAudioStated",
"serverAudioSampleRate": "serverAudioSampleRate",
"serverInputAudioSampleRate": "serverInputAudioSampleRate", "serverInputAudioSampleRate": "serverInputAudioSampleRate",
"serverOutputAudioSampleRate": "serverOutputAudioSampleRate", "serverOutputAudioSampleRate": "serverOutputAudioSampleRate",
"serverMonitorAudioSampleRate": "serverMonitorAudioSampleRate",
"serverInputAudioBufferSize": "serverInputAudioBufferSize", "serverInputAudioBufferSize": "serverInputAudioBufferSize",
"serverOutputAudioBufferSize": "serverOutputAudioBufferSize", "serverOutputAudioBufferSize": "serverOutputAudioBufferSize",
"serverInputDeviceId": "serverInputDeviceId", "serverInputDeviceId": "serverInputDeviceId",
"serverOutputDeviceId": "serverOutputDeviceId", "serverOutputDeviceId": "serverOutputDeviceId",
"serverMonitorDeviceId": "serverMonitorDeviceId",
"serverReadChunkSize": "serverReadChunkSize", "serverReadChunkSize": "serverReadChunkSize",
"serverInputAudioGain": "serverInputAudioGain", "serverInputAudioGain": "serverInputAudioGain",
"serverOutputAudioGain": "serverOutputAudioGain", "serverOutputAudioGain": "serverOutputAudioGain",
@ -138,12 +141,15 @@ export type VoiceChangerServerSetting = {
enableServerAudio: number // 0:off, 1:on enableServerAudio: number // 0:off, 1:on
serverAudioStated: number // 0:off, 1:on serverAudioStated: number // 0:off, 1:on
serverAudioSampleRate: number
serverInputAudioSampleRate: number serverInputAudioSampleRate: number
serverOutputAudioSampleRate: number serverOutputAudioSampleRate: number
serverMonitorAudioSampleRate: number
serverInputAudioBufferSize: number serverInputAudioBufferSize: number
serverOutputAudioBufferSize: number serverOutputAudioBufferSize: number
serverInputDeviceId: number serverInputDeviceId: number
serverOutputDeviceId: number serverOutputDeviceId: number
serverMonitorDeviceId: number
serverReadChunkSize: number serverReadChunkSize: number
serverInputAudioGain: number serverInputAudioGain: number
serverOutputAudioGain: number serverOutputAudioGain: number
@ -306,12 +312,15 @@ export const DefaultServerSetting: ServerInfo = {
enableServerAudio: 0, enableServerAudio: 0,
serverAudioStated: 0, serverAudioStated: 0,
serverAudioSampleRate: 48000,
serverInputAudioSampleRate: 48000, serverInputAudioSampleRate: 48000,
serverOutputAudioSampleRate: 48000, serverOutputAudioSampleRate: 48000,
serverMonitorAudioSampleRate: 48000,
serverInputAudioBufferSize: 1024 * 24, serverInputAudioBufferSize: 1024 * 24,
serverOutputAudioBufferSize: 1024 * 24, serverOutputAudioBufferSize: 1024 * 24,
serverInputDeviceId: -1, serverInputDeviceId: -1,
serverOutputDeviceId: -1, serverOutputDeviceId: -1,
serverMonitorDeviceId: -1,
serverReadChunkSize: 256, serverReadChunkSize: 256,
serverInputAudioGain: 1.0, serverInputAudioGain: 1.0,
serverOutputAudioGain: 1.0, serverOutputAudioGain: 1.0,

View File

@ -9,11 +9,9 @@
"editor.formatOnSave": true // "editor.formatOnSave": true //
}, },
"python.formatting.blackArgs": ["--line-length", "550"], "python.formatting.blackArgs": ["--line-length", "550"],
"flake8.args": [
"--ignore=E501,E402,E722,E741,E203,W503"
// "--max-line-length=150",
// "--max-complexity=20"
],
"python.linting.flake8Enabled": true, "python.linting.flake8Enabled": true,
"python.linting.flake8Args": [
"--max-line-length=99999"
],
"python.linting.enabled": true "python.linting.enabled": true
} }

View File

@ -31,7 +31,7 @@ setup_loggers()
def setupArgParser(): def setupArgParser():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--logLevel", type=str, default="critical", help="Log level info|critical. (default: critical)") parser.add_argument("--logLevel", type=str, default="error", help="Log level info|critical|error. (default: error)")
parser.add_argument("-p", type=int, default=18888, help="port") parser.add_argument("-p", type=int, default=18888, help="port")
parser.add_argument("--https", type=strtobool, default=False, help="use https") parser.add_argument("--https", type=strtobool, default=False, help="use https")
parser.add_argument("--httpsKey", type=str, default="ssl.key", help="path for the key of https") parser.add_argument("--httpsKey", type=str, default="ssl.key", help="path for the key of https")
@ -94,6 +94,7 @@ PORT = args.p
def localServer(logLevel: str = "critical"): def localServer(logLevel: str = "critical"):
try:
uvicorn.run( uvicorn.run(
f"{os.path.basename(__file__)[:-3]}:app_socketio", f"{os.path.basename(__file__)[:-3]}:app_socketio",
host="0.0.0.0", host="0.0.0.0",
@ -101,6 +102,8 @@ def localServer(logLevel: str = "critical"):
reload=False if hasattr(sys, "_MEIPASS") else True, reload=False if hasattr(sys, "_MEIPASS") else True,
log_level=logLevel, log_level=logLevel,
) )
except Exception as e:
print("[Voice Changer] Web Server Launch Exception", e)
if __name__ == "MMVCServerSIO": if __name__ == "MMVCServerSIO":
@ -204,6 +207,7 @@ if __name__ == "__main__":
# サーバ起動 # サーバ起動
if args.https: if args.https:
# HTTPS サーバ起動 # HTTPS サーバ起動
try:
uvicorn.run( uvicorn.run(
f"{os.path.basename(__file__)[:-3]}:app_socketio", f"{os.path.basename(__file__)[:-3]}:app_socketio",
host="0.0.0.0", host="0.0.0.0",
@ -213,6 +217,9 @@ if __name__ == "__main__":
ssl_certfile=cert_path, ssl_certfile=cert_path,
log_level=args.logLevel, log_level=args.logLevel,
) )
except Exception as e:
print("[Voice Changer] Web Server Launch Exception", e)
else: else:
p = mp.Process(name="p", target=localServer, args=(args.logLevel,)) p = mp.Process(name="p", target=localServer, args=(args.logLevel,))
p.start() p.start()

View File

@ -23,6 +23,8 @@ ModelType: TypeAlias = Literal[
STORED_SETTING_FILE = "stored_setting.json" STORED_SETTING_FILE = "stored_setting.json"
SERVER_DEVICE_SAMPLE_RATES = [16000, 32000, 44100, 48000, 96000, 192000]
tmpdir = tempfile.TemporaryDirectory() tmpdir = tempfile.TemporaryDirectory()
SSL_KEY_DIR = os.path.join(tmpdir.name, "keys") if hasattr(sys, "_MEIPASS") else "keys" SSL_KEY_DIR = os.path.join(tmpdir.name, "keys") if hasattr(sys, "_MEIPASS") else "keys"
MODEL_DIR = os.path.join(tmpdir.name, "logs") if hasattr(sys, "_MEIPASS") else "logs" MODEL_DIR = os.path.join(tmpdir.name, "logs") if hasattr(sys, "_MEIPASS") else "logs"

View File

@ -76,7 +76,6 @@ class MMVC_Rest_Fileuploader:
except Exception as e: except Exception as e:
print("[Voice Changer] post_update_settings ex:", e) print("[Voice Changer] post_update_settings ex:", e)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
def post_load_model( def post_load_model(

View File

@ -3,19 +3,19 @@ import os
class IORecorder: class IORecorder:
def __init__(self, inputFilename: str, outputFilename: str, samplingRate: int): def __init__(self, inputFilename: str, outputFilename: str, inputSamplingRate: int, outputSamplingRate: int):
self._clearFile(inputFilename) self._clearFile(inputFilename)
self._clearFile(outputFilename) self._clearFile(outputFilename)
self.fi = wave.open(inputFilename, "wb") self.fi = wave.open(inputFilename, "wb")
self.fi.setnchannels(1) self.fi.setnchannels(1)
self.fi.setsampwidth(2) self.fi.setsampwidth(2)
self.fi.setframerate(samplingRate) self.fi.setframerate(inputSamplingRate)
self.fo = wave.open(outputFilename, "wb") self.fo = wave.open(outputFilename, "wb")
self.fo.setnchannels(1) self.fo.setnchannels(1)
self.fo.setsampwidth(2) self.fo.setsampwidth(2)
self.fo.setframerate(samplingRate) self.fo.setframerate(outputSamplingRate)
def _clearFile(self, filename: str): def _clearFile(self, filename: str):
if os.path.exists(filename): if os.path.exists(filename):

View File

@ -1,6 +1,9 @@
import sounddevice as sd import sounddevice as sd
from dataclasses import dataclass from dataclasses import dataclass, field
from const import ServerAudioDeviceTypes from const import ServerAudioDeviceTypes
import numpy as np
# from const import SERVER_DEVICE_SAMPLE_RATES
@dataclass @dataclass
@ -12,6 +15,40 @@ class ServerAudioDevice:
maxInputChannels: int = 0 maxInputChannels: int = 0
maxOutputChannels: int = 0 maxOutputChannels: int = 0
default_samplerate: int = 0 default_samplerate: int = 0
available_samplerates: list[int] = field(default_factory=lambda: [])
def dummy_callback(data: np.ndarray, frames, times, status):
pass
def checkSamplingRate(deviceId: int, desiredSamplingRate: int, type: ServerAudioDeviceTypes):
if type == "input":
try:
with sd.InputStream(
device=deviceId,
callback=dummy_callback,
dtype="float32",
samplerate=desiredSamplingRate
):
pass
return True
except Exception as e: # NOQA
# print("[checkSamplingRate]", e)
return False
else:
try:
with sd.OutputStream(
device=deviceId,
callback=dummy_callback,
dtype="float32",
samplerate=desiredSamplingRate
):
pass
return True
except Exception as e: # NOQA
# print("[checkSamplingRate]", e)
return False
def list_audio_device(): def list_audio_device():
@ -20,7 +57,7 @@ def list_audio_device():
except Exception as e: except Exception as e:
print("[Voice Changer] ex:query_devices") print("[Voice Changer] ex:query_devices")
print(e) print(e)
return [], [] raise e
inputAudioDeviceList = [d for d in audioDeviceList if d["max_input_channels"] > 0] inputAudioDeviceList = [d for d in audioDeviceList if d["max_input_channels"] > 0]
outputAudioDeviceList = [d for d in audioDeviceList if d["max_output_channels"] > 0] outputAudioDeviceList = [d for d in audioDeviceList if d["max_output_channels"] > 0]
@ -55,4 +92,20 @@ def list_audio_device():
) )
serverAudioOutputDevices.append(serverOutputAudioDevice) serverAudioOutputDevices.append(serverOutputAudioDevice)
# print("check sample rate1")
# for d in serverAudioInputDevices:
# print("check sample rate1-1")
# for sr in SERVER_DEVICE_SAMPLE_RATES:
# print("check sample rate1-2")
# if checkSamplingRate(d.index, sr, "input"):
# d.available_samplerates.append(sr)
# print("check sample rate2")
# for d in serverAudioOutputDevices:
# print("check sample rate2-1")
# for sr in SERVER_DEVICE_SAMPLE_RATES:
# print("check sample rate2-2")
# if checkSamplingRate(d.index, sr, "output"):
# d.available_samplerates.append(sr)
# print("check sample rate3")
return serverAudioInputDevices, serverAudioOutputDevices return serverAudioInputDevices, serverAudioOutputDevices

View File

@ -1,8 +1,9 @@
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
import numpy as np import numpy as np
from const import SERVER_DEVICE_SAMPLE_RATES
from voice_changer.Local.AudioDeviceList import list_audio_device from voice_changer.Local.AudioDeviceList import checkSamplingRate, list_audio_device
import time import time
import sounddevice as sd import sounddevice as sd
from voice_changer.utils.Timer import Timer from voice_changer.utils.Timer import Timer
@ -10,6 +11,8 @@ import librosa
from voice_changer.utils.VoiceChangerModel import AudioInOut from voice_changer.utils.VoiceChangerModel import AudioInOut
from typing import Protocol from typing import Protocol
from typing import Literal, TypeAlias
AudioDeviceKind: TypeAlias = Literal["input", "output"]
@dataclass @dataclass
@ -18,12 +21,21 @@ class ServerDeviceSettings:
serverAudioStated: int = 0 # 0:off, 1:on serverAudioStated: int = 0 # 0:off, 1:on
serverInputAudioSampleRate: int = 44100 serverInputAudioSampleRate: int = 44100
serverOutputAudioSampleRate: int = 44100 serverOutputAudioSampleRate: int = 44100
serverMonitorAudioSampleRate: int = 44100
serverAudioSampleRate: int = 44100
# serverAudioSampleRate: int = 16000
# serverAudioSampleRate: int = 48000
serverInputDeviceId: int = -1 serverInputDeviceId: int = -1
serverOutputDeviceId: int = -1 serverOutputDeviceId: int = -1
serverMonitorDeviceId: int = -1 # -1 でモニター無効
serverReadChunkSize: int = 256 serverReadChunkSize: int = 256
serverInputAudioGain: float = 1.0 serverInputAudioGain: float = 1.0
serverOutputAudioGain: float = 1.0 serverOutputAudioGain: float = 1.0
exclusiveMode: bool = False
EditableServerDeviceSettings = { EditableServerDeviceSettings = {
"intData": [ "intData": [
@ -31,14 +43,20 @@ EditableServerDeviceSettings = {
"serverAudioStated", "serverAudioStated",
"serverInputAudioSampleRate", "serverInputAudioSampleRate",
"serverOutputAudioSampleRate", "serverOutputAudioSampleRate",
"serverMonitorAudioSampleRate",
"serverAudioSampleRate",
"serverInputDeviceId", "serverInputDeviceId",
"serverOutputDeviceId", "serverOutputDeviceId",
"serverMonitorDeviceId",
"serverReadChunkSize", "serverReadChunkSize",
], ],
"floatData": [ "floatData": [
"serverInputAudioGain", "serverInputAudioGain",
"serverOutputAudioGain", "serverOutputAudioGain",
], ],
"boolData": [
"exclusiveMode"
]
} }
@ -52,7 +70,10 @@ class ServerDeviceCallbacks(Protocol):
def get_processing_sampling_rate(self): def get_processing_sampling_rate(self):
... ...
def setSamplingRate(self, sr: int): def setInputSamplingRate(self, sr: int):
...
def setOutputSamplingRate(self, sr: int):
... ...
@ -60,6 +81,10 @@ class ServerDevice:
def __init__(self, serverDeviceCallbacks: ServerDeviceCallbacks): def __init__(self, serverDeviceCallbacks: ServerDeviceCallbacks):
self.settings = ServerDeviceSettings() self.settings = ServerDeviceSettings()
self.serverDeviceCallbacks = serverDeviceCallbacks self.serverDeviceCallbacks = serverDeviceCallbacks
self.out_wav = None
self.mon_wav = None
self.serverAudioInputDevices = None
self.serverAudioOutputDevices = None
def getServerInputAudioDevice(self, index: int): def getServerInputAudioDevice(self, index: int):
audioinput, _audiooutput = list_audio_device() audioinput, _audiooutput = list_audio_device()
@ -94,7 +119,221 @@ class ServerDevice:
except Exception as e: except Exception as e:
print("[Voice Changer] ex:", e) print("[Voice Changer] ex:", e)
def audioInput_callback(self, indata: np.ndarray, frames, times, status):
try:
indata = indata * self.settings.serverInputAudioGain
with Timer("all_inference_time") as t:
unpackedData = librosa.to_mono(indata.T) * 32768.0
unpackedData = unpackedData.astype(np.int16)
out_wav, times = self.serverDeviceCallbacks.on_request(unpackedData)
self.out_wav = out_wav
self.mon_wav = out_wav
all_inference_time = t.secs
self.performance = [all_inference_time] + times
self.serverDeviceCallbacks.emitTo(self.performance)
self.performance = [round(x * 1000) for x in self.performance]
except Exception as e:
print("[Voice Changer][ServerDevice][audioInput_callback] ex:", e)
# import traceback
# traceback.print_exc()
def audioOutput_callback(self, outdata: np.ndarray, frames, times, status):
try:
if self.out_wav is None:
return
out_wav = self.out_wav
outputChannels = outdata.shape[1]
outdata[:] = np.repeat(out_wav, outputChannels).reshape(-1, outputChannels) / 32768.0
outdata[:] = outdata * self.settings.serverOutputAudioGain
except Exception as e:
print("[Voice Changer][ServerDevice][audioOutput_callback] ex:", e)
# import traceback
# traceback.print_exc()
def audioMonitor_callback(self, outdata: np.ndarray, frames, times, status):
try:
if self.mon_wav is None:
return
mon_wav = self.mon_wav
outputChannels = outdata.shape[1]
outdata[:] = np.repeat(mon_wav, outputChannels).reshape(-1, outputChannels) / 32768.0
outdata[:] = outdata * self.settings.serverOutputAudioGain # GainはOutputのものをを流用
# Monitorモードが有効の場合はサンプリングレートはmonitorデバイスが優先されているためリサンプリング不要
except Exception as e:
print("[Voice Changer][ServerDevice][audioMonitor_callback] ex:", e)
# import traceback
# traceback.print_exc()
def start(self): def start(self):
currentModelSamplingRate = -1
while True:
if self.settings.serverAudioStated == 0 or self.settings.serverInputDeviceId == -1:
time.sleep(2)
else:
sd._terminate()
sd._initialize()
# Curret Device ID
currentServerInputDeviceId = self.settings.serverInputDeviceId
currentServerOutputDeviceId = self.settings.serverOutputDeviceId
currentServerMonitorDeviceId = self.settings.serverMonitorDeviceId
# Device 特定
serverInputAudioDevice = self.getServerInputAudioDevice(self.settings.serverInputDeviceId)
serverOutputAudioDevice = self.getServerOutputAudioDevice(self.settings.serverOutputDeviceId)
serverMonitorAudioDevice = None
if self.settings.serverMonitorDeviceId != -1:
serverMonitorAudioDevice = self.getServerOutputAudioDevice(self.settings.serverMonitorDeviceId)
# Generate ExtraSetting
inputExtraSetting = None
outputExtraSetting = None
if self.settings.exclusiveMode:
if "WASAPI" in serverInputAudioDevice.hostAPI:
inputExtraSetting = sd.WasapiSettings(exclusive=True)
if "WASAPI" in serverOutputAudioDevice.hostAPI:
outputExtraSetting = sd.WasapiSettings(exclusive=True)
monitorExtraSetting = None
if self.settings.exclusiveMode and serverMonitorAudioDevice is not None:
if "WASAPI" in serverMonitorAudioDevice.hostAPI:
monitorExtraSetting = sd.WasapiSettings(exclusive=True)
print("Devices:")
print(" [Input]:", serverInputAudioDevice, inputExtraSetting)
print(" [Output]:", serverOutputAudioDevice, outputExtraSetting)
print(" [Monitor]:", serverMonitorAudioDevice, monitorExtraSetting)
# Deviceがなかったらいったんスリープ
if serverInputAudioDevice is None or serverOutputAudioDevice is None:
print("serverInputAudioDevice or serverOutputAudioDevice is None")
time.sleep(2)
continue
# サンプリングレート
# 同一サンプリングレートに統一(変換時にサンプルが不足する場合があるため。パディング方法が明らかになれば、それぞれ設定できるかも)
currentAudioSampleRate = self.settings.serverAudioSampleRate
try:
currentModelSamplingRate = self.serverDeviceCallbacks.get_processing_sampling_rate()
except Exception as e:
print("[Voice Changer] ex: get_processing_sampling_rate", e)
time.sleep(2)
continue
self.settings.serverInputAudioSampleRate = currentAudioSampleRate
self.settings.serverOutputAudioSampleRate = currentAudioSampleRate
self.settings.serverMonitorAudioSampleRate = currentAudioSampleRate
# Sample Rate Check
inputAudioSampleRateAvailable = checkSamplingRate(self.settings.serverInputDeviceId, self.settings.serverInputAudioSampleRate, "input")
outputAudioSampleRateAvailable = checkSamplingRate(self.settings.serverOutputDeviceId, self.settings.serverOutputAudioSampleRate, "output")
monitorAudioSampleRateAvailable = checkSamplingRate(self.settings.serverMonitorDeviceId, self.settings.serverMonitorAudioSampleRate, "output") if serverMonitorAudioDevice else True
print("Sample Rate:")
print(f" [Model]: {currentModelSamplingRate}")
print(f" [Input]: {self.settings.serverInputAudioSampleRate} -> {inputAudioSampleRateAvailable}")
print(f" [Output]: {self.settings.serverOutputAudioSampleRate} -> {outputAudioSampleRateAvailable}")
if serverMonitorAudioDevice is not None:
print(f" [Monitor]: {self.settings.serverMonitorAudioSampleRate} -> {monitorAudioSampleRateAvailable}")
if inputAudioSampleRateAvailable and outputAudioSampleRateAvailable and monitorAudioSampleRateAvailable:
pass
else:
print("Sample Rate is not supported by device:")
print("Checking Available Sample Rate:")
availableInputSampleRate = []
availableOutputSampleRate = []
availableMonitorSampleRate = []
for sr in SERVER_DEVICE_SAMPLE_RATES:
if checkSamplingRate(self.settings.serverInputDeviceId, sr, "input"):
availableInputSampleRate.append(sr)
if checkSamplingRate(self.settings.serverOutputDeviceId, sr, "output"):
availableOutputSampleRate.append(sr)
if serverMonitorAudioDevice is not None:
if checkSamplingRate(self.settings.serverMonitorDeviceId, sr, "output"):
availableMonitorSampleRate.append(sr)
print("Available Sample Rate:")
print(f" [Input]: {availableInputSampleRate}")
print(f" [Output]: {availableOutputSampleRate}")
if serverMonitorAudioDevice is not None:
print(f" [Monitor]: {availableMonitorSampleRate}")
print("continue... ")
time.sleep(2)
continue
self.serverDeviceCallbacks.setInputSamplingRate(self.settings.serverInputAudioSampleRate)
self.serverDeviceCallbacks.setOutputSamplingRate(self.settings.serverOutputAudioSampleRate)
# Blockサイズを計算
currentInputChunkNum = self.settings.serverReadChunkSize
block_frame = currentInputChunkNum * 128
sd.default.blocksize = block_frame
# main loop
try:
with sd.InputStream(
callback=self.audioInput_callback,
dtype="float32",
device=self.settings.serverInputDeviceId,
blocksize=block_frame,
samplerate=self.settings.serverInputAudioSampleRate,
channels=serverInputAudioDevice.maxInputChannels,
extra_settings=inputExtraSetting
):
with sd.OutputStream(
callback=self.audioOutput_callback,
dtype="float32",
device=self.settings.serverOutputDeviceId,
blocksize=block_frame,
samplerate=self.settings.serverOutputAudioSampleRate,
channels=serverOutputAudioDevice.maxOutputChannels,
extra_settings=outputExtraSetting
):
if self.settings.serverMonitorDeviceId != -1:
with sd.OutputStream(
callback=self.audioMonitor_callback,
dtype="float32",
device=self.settings.serverMonitorDeviceId,
blocksize=block_frame,
samplerate=self.settings.serverMonitorAudioSampleRate,
channels=serverMonitorAudioDevice.maxOutputChannels,
extra_settings=monitorExtraSetting
):
while (
self.settings.serverAudioStated == 1 and
currentServerInputDeviceId == self.settings.serverInputDeviceId and
currentServerOutputDeviceId == self.settings.serverOutputDeviceId and
currentServerMonitorDeviceId == self.settings.serverMonitorDeviceId and
currentModelSamplingRate == self.serverDeviceCallbacks.get_processing_sampling_rate() and
currentInputChunkNum == self.settings.serverReadChunkSize and
currentAudioSampleRate == self.settings.serverAudioSampleRate
):
time.sleep(2)
print(f"[Voice Changer] server audio performance {self.performance}")
print(f" status: started:{self.settings.serverAudioStated}, model_sr:{currentModelSamplingRate}, chunk:{currentInputChunkNum}")
print(f" input : id:{self.settings.serverInputDeviceId}, sr:{self.settings.serverInputAudioSampleRate}, ch:{serverInputAudioDevice.maxInputChannels}")
print(f" output : id:{self.settings.serverOutputDeviceId}, sr:{self.settings.serverOutputAudioSampleRate}, ch:{serverOutputAudioDevice.maxOutputChannels}")
print(f" monitor: id:{self.settings.serverMonitorDeviceId}, sr:{self.settings.serverMonitorAudioSampleRate}, ch:{serverMonitorAudioDevice.maxOutputChannels}")
else:
while (
self.settings.serverAudioStated == 1 and
currentServerInputDeviceId == self.settings.serverInputDeviceId and
currentServerOutputDeviceId == self.settings.serverOutputDeviceId and
currentServerMonitorDeviceId == self.settings.serverMonitorDeviceId and
currentModelSamplingRate == self.serverDeviceCallbacks.get_processing_sampling_rate() and
currentInputChunkNum == self.settings.serverReadChunkSize and
currentAudioSampleRate == self.settings.serverAudioSampleRate
):
time.sleep(2)
print(f"[Voice Changer] server audio performance {self.performance}")
print(f" status: started:{self.settings.serverAudioStated}, model_sr:{currentModelSamplingRate}, chunk:{currentInputChunkNum}]")
print(f" input : id:{self.settings.serverInputDeviceId}, sr:{self.settings.serverInputAudioSampleRate}, ch:{serverInputAudioDevice.maxInputChannels}")
print(f" output : id:{self.settings.serverOutputDeviceId}, sr:{self.settings.serverOutputAudioSampleRate}, ch:{serverOutputAudioDevice.maxOutputChannels}")
except Exception as e:
print("[Voice Changer] processing, ex:", e)
time.sleep(2)
def start2(self):
# currentInputDeviceId = -1 # currentInputDeviceId = -1
# currentOutputDeviceId = -1 # currentOutputDeviceId = -1
# currentInputChunkNum = -1 # currentInputChunkNum = -1
@ -140,13 +379,15 @@ class ServerDevice:
): ):
pass pass
self.settings.serverInputAudioSampleRate = currentModelSamplingRate self.settings.serverInputAudioSampleRate = currentModelSamplingRate
self.serverDeviceCallbacks.setSamplingRate(currentModelSamplingRate) self.serverDeviceCallbacks.setInputSamplingRate(currentModelSamplingRate)
self.serverDeviceCallbacks.setOutputSamplingRate(currentModelSamplingRate)
print(f"[Voice Changer] sample rate {self.settings.serverInputAudioSampleRate}") print(f"[Voice Changer] sample rate {self.settings.serverInputAudioSampleRate}")
except Exception as e: except Exception as e:
print("[Voice Changer] ex: fallback to device default samplerate", e) print("[Voice Changer] ex: fallback to device default samplerate", e)
print("[Voice Changer] device default samplerate", serverInputAudioDevice.default_samplerate) print("[Voice Changer] device default samplerate", serverInputAudioDevice.default_samplerate)
self.settings.serverInputAudioSampleRate = round(serverInputAudioDevice.default_samplerate) self.settings.serverInputAudioSampleRate = round(serverInputAudioDevice.default_samplerate)
self.serverDeviceCallbacks.setSamplingRate(round(serverInputAudioDevice.default_samplerate)) self.serverDeviceCallbacks.setInputSamplingRate(round(serverInputAudioDevice.default_samplerate))
self.serverDeviceCallbacks.setOutputSamplingRate(round(serverInputAudioDevice.default_samplerate))
sd.default.samplerate = self.settings.serverInputAudioSampleRate sd.default.samplerate = self.settings.serverInputAudioSampleRate
sd.default.blocksize = block_frame sd.default.blocksize = block_frame
@ -171,10 +412,15 @@ class ServerDevice:
def get_info(self): def get_info(self):
data = asdict(self.settings) data = asdict(self.settings)
try:
audioinput, audiooutput = list_audio_device() audioinput, audiooutput = list_audio_device()
data["serverAudioInputDevices"] = audioinput self.serverAudioInputDevices = audioinput
data["serverAudioOutputDevices"] = audiooutput self.serverAudioOutputDevices = audiooutput
except Exception as e:
print(e)
data["serverAudioInputDevices"] = self.serverAudioInputDevices
data["serverAudioOutputDevices"] = self.serverAudioOutputDevices
return data return data
def update_settings(self, key: str, val: str | int | float): def update_settings(self, key: str, val: str | int | float):

View File

@ -77,7 +77,7 @@ class DeviceManager(object):
def getDeviceMemory(self, id: int): def getDeviceMemory(self, id: int):
try: try:
return torch.cuda.get_device_properties(id).total_memory return torch.cuda.get_device_properties(id).total_memory
# except Exception as e: except Exception as e:
except: # except:
# print(e) print(e)
return 0 return 0

View File

@ -13,7 +13,7 @@ from voice_changer.IORecorder import IORecorder
from voice_changer.utils.LoadModelParams import LoadModelParams from voice_changer.utils.LoadModelParams import LoadModelParams
from voice_changer.utils.Timer import Timer from voice_changer.utils.Timer import Timer
from voice_changer.utils.VoiceChangerModel import AudioInOut from voice_changer.utils.VoiceChangerModel import AudioInOut, VoiceChangerModel
from Exceptions import ( from Exceptions import (
DeviceCannotSupportHalfPrecisionException, DeviceCannotSupportHalfPrecisionException,
DeviceChangingException, DeviceChangingException,
@ -32,6 +32,7 @@ STREAM_OUTPUT_FILE = os.path.join(TMP_DIR, "out.wav")
@dataclass @dataclass
class VoiceChangerSettings: class VoiceChangerSettings:
inputSampleRate: int = 48000 # 48000 or 24000 inputSampleRate: int = 48000 # 48000 or 24000
outputSampleRate: int = 48000 # 48000 or 24000
crossFadeOffsetRate: float = 0.1 crossFadeOffsetRate: float = 0.1
crossFadeEndRate: float = 0.9 crossFadeEndRate: float = 0.9
@ -71,7 +72,7 @@ class VoiceChanger:
self.currentCrossFadeOverlapSize = 0 # setting self.currentCrossFadeOverlapSize = 0 # setting
self.crossfadeSize = 0 # calculated self.crossfadeSize = 0 # calculated
self.voiceChanger = None self.voiceChanger: VoiceChangerModel | None = None
self.modelType: ModelType | None = None self.modelType: ModelType | None = None
self.params = params self.params = params
self.gpu_num = torch.cuda.device_count() self.gpu_num = torch.cuda.device_count()
@ -115,6 +116,7 @@ class VoiceChanger:
if key == "serverAudioStated" and val == 0: if key == "serverAudioStated" and val == 0:
self.settings.inputSampleRate = 48000 self.settings.inputSampleRate = 48000
self.settings.outputSampleRate = 48000
if key in self.settings.intData: if key in self.settings.intData:
setattr(self.settings, key, int(val)) setattr(self.settings, key, int(val))
@ -123,7 +125,7 @@ class VoiceChanger:
if key == "recordIO" and val == 1: if key == "recordIO" and val == 1:
if hasattr(self, "ioRecorder"): if hasattr(self, "ioRecorder"):
self.ioRecorder.close() self.ioRecorder.close()
self.ioRecorder = IORecorder(STREAM_INPUT_FILE, STREAM_OUTPUT_FILE, self.settings.inputSampleRate) self.ioRecorder = IORecorder(STREAM_INPUT_FILE, STREAM_OUTPUT_FILE, self.settings.inputSampleRate, self.settings.outputSampleRate)
if key == "recordIO" and val == 0: if key == "recordIO" and val == 0:
if hasattr(self, "ioRecorder"): if hasattr(self, "ioRecorder"):
self.ioRecorder.close() self.ioRecorder.close()
@ -268,10 +270,10 @@ class VoiceChanger:
# 後処理 # 後処理
with Timer("post-process") as t: with Timer("post-process") as t:
result = result.astype(np.int16) result = result.astype(np.int16)
if self.settings.inputSampleRate != processing_sampling_rate: if self.settings.outputSampleRate != processing_sampling_rate:
# print( # print(
# "samplingrate", # "output samplingrate",
# self.settings.inputSampleRate, # self.settings.outputSampleRate,
# processing_sampling_rate, # processing_sampling_rate,
# ) # )
outputData = cast( outputData = cast(
@ -279,13 +281,13 @@ class VoiceChanger:
resampy.resample( resampy.resample(
result, result,
processing_sampling_rate, processing_sampling_rate,
self.settings.inputSampleRate, self.settings.outputSampleRate,
).astype(np.int16), ).astype(np.int16),
) )
else: else:
outputData = result outputData = result
print_convert_processing(f" Output data size of {result.shape[0]}/{processing_sampling_rate}hz {outputData.shape[0]}/{self.settings.inputSampleRate}hz") print_convert_processing(f" Output data size of {result.shape[0]}/{processing_sampling_rate}hz {outputData.shape[0]}/{self.settings.outputSampleRate}hz")
if receivedData.shape[0] != outputData.shape[0]: if receivedData.shape[0] != outputData.shape[0]:
# print( # print(

View File

@ -50,9 +50,12 @@ class VoiceChangerManager(ServerDeviceCallbacks):
def get_processing_sampling_rate(self): def get_processing_sampling_rate(self):
return self.voiceChanger.get_processing_sampling_rate() return self.voiceChanger.get_processing_sampling_rate()
def setSamplingRate(self, sr: int): def setInputSamplingRate(self, sr: int):
self.voiceChanger.settings.inputSampleRate = sr self.voiceChanger.settings.inputSampleRate = sr
def setOutputSamplingRate(self, sr: int):
self.voiceChanger.settings.outputSampleRate = sr
############################ ############################
# VoiceChangerManager # VoiceChangerManager
############################ ############################
@ -78,7 +81,7 @@ class VoiceChangerManager(ServerDeviceCallbacks):
self.update_settings(key, val) self.update_settings(key, val)
def store_setting(self, key: str, val: str | int | float): def store_setting(self, key: str, val: str | int | float):
saveItemForServerDevice = ["enableServerAudio", "serverInputDeviceId", "serverOutputDeviceId", "serverReadChunkSize", "serverInputAudioGain", "serverOutputAudioGain"] saveItemForServerDevice = ["enableServerAudio", "serverAudioSampleRate", "serverInputDeviceId", "serverOutputDeviceId", "serverMonitorDeviceId", "serverReadChunkSize", "serverInputAudioGain", "serverOutputAudioGain"]
saveItemForVoiceChanger = ["crossFadeOffsetRate", "crossFadeEndRate", "crossFadeOverlapSize"] saveItemForVoiceChanger = ["crossFadeOffsetRate", "crossFadeEndRate", "crossFadeOverlapSize"]
saveItemForVoiceChangerManager = ["modelSlotIndex"] saveItemForVoiceChangerManager = ["modelSlotIndex"]
saveItemForRVC = ["extraConvertSize", "gpu", "silentThreshold"] saveItemForRVC = ["extraConvertSize", "gpu", "silentThreshold"]