- 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-react": "^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-dom": "^18.2.6",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"eslint": "^8.43.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
@ -54,7 +54,7 @@
"webpack-dev-server": "^4.15.1"
},
"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/free-brands-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 { useAppState } from "../../../001_provider/001_AppStateProvider"
import { fileSelectorAsDataURL, useIndexedDB, } from "@dannadori/voice-changer-client-js"
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 React, { useEffect, useMemo, useRef, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js";
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";
export type DeviceAreaProps = {
}
export type DeviceAreaProps = {};
export const DeviceArea = (_props: DeviceAreaProps) => {
const { setting, serverSetting, audioContext, setAudioOutputElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState()
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo } = useGuiState()
const [inputHostApi, setInputHostApi] = useState<string>("ALL")
const [outputHostApi, setOutputHostApi] = useState<string>("ALL")
const audioSrcNode = useRef<MediaElementAudioSourceNode>()
const { setting, serverSetting, audioContext, setAudioOutputElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo } = useGuiState();
const [inputHostApi, setInputHostApi] = useState<string>("ALL");
const [outputHostApi, setOutputHostApi] = useState<string>("ALL");
const audioSrcNode = useRef<MediaElementAudioSourceNode>();
const { getItem, setItem } = useIndexedDB({ clientType: null })
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false)
const { getItem, setItem } = useIndexedDB({ clientType: null });
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false);
// (1) Audio Mode
const deviceModeRow = useMemo(() => {
const enableServerAudio = serverSetting.serverSetting.enableServerAudio
const clientChecked = enableServerAudio == 1 ? false : true
const serverChecked = enableServerAudio == 1 ? true : false
const enableServerAudio = serverSetting.serverSetting.enableServerAudio;
const clientChecked = enableServerAudio == 1 ? false : true;
const serverChecked = enableServerAudio == 1 ? true : false;
const onDeviceModeChanged = (val: number) => {
if (isConverting) {
alert("cannot change mode when voice conversion is enabled")
return
}
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, enableServerAudio: val })
alert("cannot change mode when voice conversion is enabled");
return;
}
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, enableServerAudio: val });
};
return (
<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-noise-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 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>
</div>
</div>
</div>
</div>
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, isConverting])
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, isConverting]);
// (2) Audio Input
// キャッシュの設定は反映(たぶん、設定操作の時も起動していしまう。が問題は起こらないはず)
useEffect(() => {
if (typeof setting.voiceChangerClientSetting.audioInput == "string") {
if (inputAudioDeviceInfo.find(x => {
if (
inputAudioDeviceInfo.find((x) => {
// console.log("COMPARE:", x.deviceId, appState.clientSetting.setting.audioInput)
return x.deviceId == setting.voiceChangerClientSetting.audioInput
})) {
setAudioInputForGUI(setting.voiceChangerClientSetting.audioInput)
return x.deviceId == setting.voiceChangerClientSetting.audioInput;
})
) {
setAudioInputForGUI(setting.voiceChangerClientSetting.audioInput);
}
}
}, [inputAudioDeviceInfo, setting.voiceChangerClientSetting.audioInput])
}, [inputAudioDeviceInfo, setting.voiceChangerClientSetting.audioInput]);
// (2-1) クライアント
const clientAudioInputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>
return <></>;
}
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">input</div>
<div className="config-sub-area-control-field">
<select className="body-select" value={audioInputForGUI} onChange={async (e) => {
setAudioInputForGUI(e.target.value)
<select
className="body-select"
value={audioInputForGUI}
onChange={async (e) => {
setAudioInputForGUI(e.target.value);
if (e.target.value != "file") {
try {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: e.target.value })
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: e.target.value });
} catch (e) {
alert(e)
console.error(e)
setAudioInputForGUI("none")
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: null })
alert(e);
console.error(e);
setAudioInputForGUI("none");
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: null });
}
}
}}>
{
inputAudioDeviceInfo.map(x => {
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
})
}
}}
>
{inputAudioDeviceInfo.map((x) => {
return (
<option key={x.deviceId} value={x.deviceId}>
{x.label}
</option>
);
})}
</select>
</div>
</div>
)
}, [setVoiceChangerClientSetting, setting, inputAudioDeviceInfo, audioInputForGUI, serverSetting.serverSetting.enableServerAudio])
);
}, [setVoiceChangerClientSetting, setting, inputAudioDeviceInfo, audioInputForGUI, serverSetting.serverSetting.enableServerAudio]);
// (2-2) サーバ
const serverAudioInputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>
return <></>;
}
const devices = serverSetting.serverSetting.serverAudioInputDevices
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 devices = serverSetting.serverSetting.serverAudioInputDevices;
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) => {
const filteredDevice = devices
.map((x, index) => {
if (inputHostApi != "ALL" && x.hostAPI != inputHostApi) {
return null
return null;
}
return <option value={x.index} key={index}>[{x.hostAPI}]{x.name}</option>
}).filter(x => x != null)
return (
<option value={x.index} key={index}>
[{x.hostAPI}]{x.name}
</option>
);
})
.filter((x) => x != null);
return (
<div className="config-sub-area-control">
<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-auido-io">
<select 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>
<select
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}
</select>
<select className="config-sub-area-control-field-auido-io-select" value={serverSetting.serverSetting.serverInputDeviceId} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputDeviceId: Number(e.target.value) })
}}>
<select
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}
</select>
</div>
</div>
</div>
)
}, [inputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio])
);
}, [inputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (2-3) File
useEffect(() => {
[AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
const audio = document.getElementById(x) as HTMLAudioElement
[AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) {
audio.volume = fileInputEchoback ? 1 : 0
audio.volume = fileInputEchoback ? 1 : 0;
}
})
}, [fileInputEchoback])
});
}, [fileInputEchoback]);
const audioInputMediaRow = useMemo(() => {
if (audioInputForGUI != "file" || serverSetting.serverSetting.enableServerAudio == 1) {
return <></>
return <></>;
}
const onFileLoadClicked = async () => {
const url = await fileSelectorAsDataURL("")
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()
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 = audioContext!.createMediaElementSource(audio);
}
@ -169,27 +219,27 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
audioSrcNode.current = audioContext!.createMediaElementSource(audio);
}
const dst = audioContext.createMediaStreamDestination()
audioSrcNode.current.connect(dst)
const dst = audioContext.createMediaStreamDestination();
audioSrcNode.current.connect(dst);
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: dst.stream })
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: dst.stream });
} catch (e) {
console.error(e)
console.error(e);
}
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)
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()
}
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement;
audio_org.src = url;
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 (
<div className="config-sub-area-control">
<div className="config-sub-area-control-field">
@ -202,190 +252,332 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div>
<img className="config-sub-area-control-field-wav-file-folder" src="./assets/icons/folder.svg" onClick={onFileLoadClicked} />
</div>
<div className={echobackClass} onClick={() => { setFileInputEchoback(!fileInputEchoback) }}>
<div
className={echobackClass}
onClick={() => {
setFileInputEchoback(!fileInputEchoback);
}}
>
echo{fileInputEchoback}
</div>
</div>
</div>
</div>
)
}, [audioInputForGUI, fileInputEchoback, serverSetting.serverSetting])
);
}, [audioInputForGUI, fileInputEchoback, serverSetting.serverSetting]);
// (3) Audio Output
useEffect(() => {
const loadCache = async () => {
const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
const key = await getItem(INDEXEDDB_KEY_AUDIO_OUTPUT);
if (key) {
setAudioOutputForGUI(key as string)
setAudioOutputForGUI(key as string);
}
}
loadCache()
}, [])
};
loadCache();
}, []);
useEffect(() => {
const setAudioOutput = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach(x => {
const audio = document.getElementById(x) as HTMLAudioElement
[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 (serverSetting.serverSetting.enableServerAudio == 1) {
// Server Audio を使う場合はElementから音は出さない。
audio.volume = 0
audio.volume = 0;
} else if (audioOutputForGUI == "none") {
// @ts-ignore
audio.setSinkId("")
audio.setSinkId("");
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
audio.volume = 0
audio.volume = 0;
} else {
audio.volume = 0
audio.volume = 0;
}
} else {
const audioOutputs = mediaDeviceInfos.filter(x => { return x.kind == "audiooutput" })
const found = audioOutputs.some(x => { return x.deviceId == audioOutputForGUI })
const audioOutputs = mediaDeviceInfos.filter((x) => {
return x.kind == "audiooutput";
});
const found = audioOutputs.some((x) => {
return x.deviceId == audioOutputForGUI;
});
if (found) {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioOutputForGUI)
audio.setSinkId(audioOutputForGUI);
} 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) {
audio.volume = fileInputEchoback ? 1 : 0
audio.volume = fileInputEchoback ? 1 : 0;
} else {
audio.volume = 1
audio.volume = 1;
}
}
}
})
}
setAudioOutput()
}, [audioOutputForGUI, fileInputEchoback, serverSetting.serverSetting.enableServerAudio])
});
};
setAudioOutput();
}, [audioOutputForGUI, fileInputEchoback, serverSetting.serverSetting.enableServerAudio]);
// (3-1) クライアント
const clientAudioOutputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>
return <></>;
}
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">output</div>
<div className="config-sub-area-control-field">
<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
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>
</div>
</div>
)
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioOutputForGUI])
);
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioOutputForGUI]);
useEffect(() => {
console.log("initializedRef.current", initializedRef.current)
setAudioOutputElementId(AUDIO_ELEMENT_FOR_PLAY_RESULT)
}, [initializedRef.current])
console.log("initializedRef.current", initializedRef.current);
setAudioOutputElementId(AUDIO_ELEMENT_FOR_PLAY_RESULT);
}, [initializedRef.current]);
// (3-2) サーバ
const serverAudioOutputRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>
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 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) => {
const filteredDevice = devices
.map((x, index) => {
if (outputHostApi != "ALL" && x.hostAPI != outputHostApi) {
return null
return null;
}
return <option value={x.index} key={index}>[{x.hostAPI}]{x.name}</option>
}).filter(x => x != null)
return (
<option value={x.index} key={index}>
[{x.hostAPI}]{x.name}
</option>
);
})
.filter((x) => x != null);
return (
<div className="config-sub-area-control">
<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-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>
<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.serverOutputDeviceId} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputDeviceId: Number(e.target.value) })
}}>
<select
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}
</select>
</div>
</div>
</div>
)
}, [outputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio])
);
}, [outputHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (4) レコーダー
const outputRecorderRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>
return <></>;
}
const onOutputRecordStartClicked = async () => {
setOutputRecordingStarted(true)
await startOutputRecording()
}
setOutputRecordingStarted(true);
await startOutputRecording();
};
const onOutputRecordStopClicked = async () => {
setOutputRecordingStarted(false)
const record = await stopOutputRecording()
downloadRecord(record)
}
setOutputRecordingStarted(false);
const record = await stopOutputRecording();
downloadRecord(record);
};
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 startClassName = outputRecordingStarted ? "config-sub-area-button-active" : "config-sub-area-button";
const stopClassName = outputRecordingStarted ? "config-sub-area-button" : "config-sub-area-button-active";
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">REC.</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buttons">
<div onClick={onOutputRecordStartClicked} className={startClassName}>start</div>
<div onClick={onOutputRecordStopClicked} className={stopClassName}>stop</div>
<div onClick={onOutputRecordStartClicked} className={startClassName}>
start
</div>
<div onClick={onOutputRecordStopClicked} className={stopClassName}>
stop
</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 (
<div className="config-sub-area">
{deviceModeRow}
{sampleRateRow}
{clientAudioInputRow}
{serverAudioInputRow}
{audioInputMediaRow}
{clientAudioOutputRow}
{serverAudioOutputRow}
{serverMonitorRow}
{outputRecorderRow}
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
</div>
)
}
);
};
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));
@ -395,7 +587,7 @@ const downloadRecord = (data: Float32Array) => {
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);
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
}
};
@ -403,10 +595,10 @@ const downloadRecord = (data: Float32Array) => {
const view = new DataView(buffer);
// 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); // これ以降のファイルサイズ
writeString(view, 8, 'WAVE'); // WAVEヘッダ
writeString(view, 12, 'fmt '); // fmtチャンク
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); // チャンネル数
@ -414,10 +606,10 @@ const downloadRecord = (data: Float32Array) => {
view.setUint32(28, 48000 * 2, true); // データ速度
view.setUint16(32, 2, true); // ブロックサイズ
view.setUint16(34, 16, true); // サンプルあたりのビット数
writeString(view, 36, 'data'); // dataチャンク
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 audioBlob = new Blob([view], { type: "audio/wav" });
const url = URL.createObjectURL(audioBlob);
const a = document.createElement("a");
@ -427,4 +619,4 @@ const downloadRecord = (data: Float32Array) => {
a.click();
document.body.removeChild(a);
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",
"version": "1.0.155",
"version": "1.0.157",
"description": "",
"main": "dist/index.js",
"directories": {
@ -27,10 +27,10 @@
"license": "ISC",
"devDependencies": {
"@types/audioworklet": "^0.0.48",
"@types/node": "^20.3.2",
"@types/node": "^20.3.3",
"@types/react": "18.2.14",
"@types/react-dom": "18.2.6",
"eslint": "^8.43.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
@ -47,7 +47,7 @@
},
"dependencies": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.14.1",
"amazon-chime-sdk-js": "^3.15.0",
"buffer": "^6.0.3",
"localforage": "^1.10.0",
"react": "^18.2.0",

View File

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

View File

@ -9,11 +9,9 @@
"editor.formatOnSave": true //
},
"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.flake8Args": [
"--max-line-length=99999"
],
"python.linting.enabled": true
}

View File

@ -31,7 +31,7 @@ setup_loggers()
def setupArgParser():
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("--https", type=strtobool, default=False, help="use 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"):
try:
uvicorn.run(
f"{os.path.basename(__file__)[:-3]}:app_socketio",
host="0.0.0.0",
@ -101,6 +102,8 @@ def localServer(logLevel: str = "critical"):
reload=False if hasattr(sys, "_MEIPASS") else True,
log_level=logLevel,
)
except Exception as e:
print("[Voice Changer] Web Server Launch Exception", e)
if __name__ == "MMVCServerSIO":
@ -204,6 +207,7 @@ if __name__ == "__main__":
# サーバ起動
if args.https:
# HTTPS サーバ起動
try:
uvicorn.run(
f"{os.path.basename(__file__)[:-3]}:app_socketio",
host="0.0.0.0",
@ -213,6 +217,9 @@ if __name__ == "__main__":
ssl_certfile=cert_path,
log_level=args.logLevel,
)
except Exception as e:
print("[Voice Changer] Web Server Launch Exception", e)
else:
p = mp.Process(name="p", target=localServer, args=(args.logLevel,))
p.start()

View File

@ -23,6 +23,8 @@ ModelType: TypeAlias = Literal[
STORED_SETTING_FILE = "stored_setting.json"
SERVER_DEVICE_SAMPLE_RATES = [16000, 32000, 44100, 48000, 96000, 192000]
tmpdir = tempfile.TemporaryDirectory()
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"

View File

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

View File

@ -3,19 +3,19 @@ import os
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(outputFilename)
self.fi = wave.open(inputFilename, "wb")
self.fi.setnchannels(1)
self.fi.setsampwidth(2)
self.fi.setframerate(samplingRate)
self.fi.setframerate(inputSamplingRate)
self.fo = wave.open(outputFilename, "wb")
self.fo.setnchannels(1)
self.fo.setsampwidth(2)
self.fo.setframerate(samplingRate)
self.fo.setframerate(outputSamplingRate)
def _clearFile(self, filename: str):
if os.path.exists(filename):

View File

@ -1,6 +1,9 @@
import sounddevice as sd
from dataclasses import dataclass
from dataclasses import dataclass, field
from const import ServerAudioDeviceTypes
import numpy as np
# from const import SERVER_DEVICE_SAMPLE_RATES
@dataclass
@ -12,6 +15,40 @@ class ServerAudioDevice:
maxInputChannels: int = 0
maxOutputChannels: 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():
@ -20,7 +57,7 @@ def list_audio_device():
except Exception as e:
print("[Voice Changer] ex:query_devices")
print(e)
return [], []
raise e
inputAudioDeviceList = [d for d in audioDeviceList if d["max_input_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)
# 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

View File

@ -1,8 +1,9 @@
from dataclasses import dataclass, asdict
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 sounddevice as sd
from voice_changer.utils.Timer import Timer
@ -10,6 +11,8 @@ import librosa
from voice_changer.utils.VoiceChangerModel import AudioInOut
from typing import Protocol
from typing import Literal, TypeAlias
AudioDeviceKind: TypeAlias = Literal["input", "output"]
@dataclass
@ -18,12 +21,21 @@ class ServerDeviceSettings:
serverAudioStated: int = 0 # 0:off, 1:on
serverInputAudioSampleRate: int = 44100
serverOutputAudioSampleRate: int = 44100
serverMonitorAudioSampleRate: int = 44100
serverAudioSampleRate: int = 44100
# serverAudioSampleRate: int = 16000
# serverAudioSampleRate: int = 48000
serverInputDeviceId: int = -1
serverOutputDeviceId: int = -1
serverMonitorDeviceId: int = -1 # -1 でモニター無効
serverReadChunkSize: int = 256
serverInputAudioGain: float = 1.0
serverOutputAudioGain: float = 1.0
exclusiveMode: bool = False
EditableServerDeviceSettings = {
"intData": [
@ -31,14 +43,20 @@ EditableServerDeviceSettings = {
"serverAudioStated",
"serverInputAudioSampleRate",
"serverOutputAudioSampleRate",
"serverMonitorAudioSampleRate",
"serverAudioSampleRate",
"serverInputDeviceId",
"serverOutputDeviceId",
"serverMonitorDeviceId",
"serverReadChunkSize",
],
"floatData": [
"serverInputAudioGain",
"serverOutputAudioGain",
],
"boolData": [
"exclusiveMode"
]
}
@ -52,7 +70,10 @@ class ServerDeviceCallbacks(Protocol):
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):
self.settings = ServerDeviceSettings()
self.serverDeviceCallbacks = serverDeviceCallbacks
self.out_wav = None
self.mon_wav = None
self.serverAudioInputDevices = None
self.serverAudioOutputDevices = None
def getServerInputAudioDevice(self, index: int):
audioinput, _audiooutput = list_audio_device()
@ -94,7 +119,221 @@ class ServerDevice:
except Exception as 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):
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
# currentOutputDeviceId = -1
# currentInputChunkNum = -1
@ -140,13 +379,15 @@ class ServerDevice:
):
pass
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}")
except Exception as e:
print("[Voice Changer] ex: fallback to device default samplerate", e)
print("[Voice Changer] device default samplerate", 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.blocksize = block_frame
@ -171,10 +412,15 @@ class ServerDevice:
def get_info(self):
data = asdict(self.settings)
try:
audioinput, audiooutput = list_audio_device()
data["serverAudioInputDevices"] = audioinput
data["serverAudioOutputDevices"] = audiooutput
self.serverAudioInputDevices = audioinput
self.serverAudioOutputDevices = audiooutput
except Exception as e:
print(e)
data["serverAudioInputDevices"] = self.serverAudioInputDevices
data["serverAudioOutputDevices"] = self.serverAudioOutputDevices
return data
def update_settings(self, key: str, val: str | int | float):

View File

@ -77,7 +77,7 @@ class DeviceManager(object):
def getDeviceMemory(self, id: int):
try:
return torch.cuda.get_device_properties(id).total_memory
# except Exception as e:
except:
# print(e)
except Exception as e:
# except:
print(e)
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.Timer import Timer
from voice_changer.utils.VoiceChangerModel import AudioInOut
from voice_changer.utils.VoiceChangerModel import AudioInOut, VoiceChangerModel
from Exceptions import (
DeviceCannotSupportHalfPrecisionException,
DeviceChangingException,
@ -32,6 +32,7 @@ STREAM_OUTPUT_FILE = os.path.join(TMP_DIR, "out.wav")
@dataclass
class VoiceChangerSettings:
inputSampleRate: int = 48000 # 48000 or 24000
outputSampleRate: int = 48000 # 48000 or 24000
crossFadeOffsetRate: float = 0.1
crossFadeEndRate: float = 0.9
@ -71,7 +72,7 @@ class VoiceChanger:
self.currentCrossFadeOverlapSize = 0 # setting
self.crossfadeSize = 0 # calculated
self.voiceChanger = None
self.voiceChanger: VoiceChangerModel | None = None
self.modelType: ModelType | None = None
self.params = params
self.gpu_num = torch.cuda.device_count()
@ -115,6 +116,7 @@ class VoiceChanger:
if key == "serverAudioStated" and val == 0:
self.settings.inputSampleRate = 48000
self.settings.outputSampleRate = 48000
if key in self.settings.intData:
setattr(self.settings, key, int(val))
@ -123,7 +125,7 @@ class VoiceChanger:
if key == "recordIO" and val == 1:
if hasattr(self, "ioRecorder"):
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 hasattr(self, "ioRecorder"):
self.ioRecorder.close()
@ -268,10 +270,10 @@ class VoiceChanger:
# 後処理
with Timer("post-process") as t:
result = result.astype(np.int16)
if self.settings.inputSampleRate != processing_sampling_rate:
if self.settings.outputSampleRate != processing_sampling_rate:
# print(
# "samplingrate",
# self.settings.inputSampleRate,
# "output samplingrate",
# self.settings.outputSampleRate,
# processing_sampling_rate,
# )
outputData = cast(
@ -279,13 +281,13 @@ class VoiceChanger:
resampy.resample(
result,
processing_sampling_rate,
self.settings.inputSampleRate,
self.settings.outputSampleRate,
).astype(np.int16),
)
else:
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]:
# print(

View File

@ -50,9 +50,12 @@ class VoiceChangerManager(ServerDeviceCallbacks):
def get_processing_sampling_rate(self):
return self.voiceChanger.get_processing_sampling_rate()
def setSamplingRate(self, sr: int):
def setInputSamplingRate(self, sr: int):
self.voiceChanger.settings.inputSampleRate = sr
def setOutputSamplingRate(self, sr: int):
self.voiceChanger.settings.outputSampleRate = sr
############################
# VoiceChangerManager
############################
@ -78,7 +81,7 @@ class VoiceChangerManager(ServerDeviceCallbacks):
self.update_settings(key, val)
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"]
saveItemForVoiceChangerManager = ["modelSlotIndex"]
saveItemForRVC = ["extraConvertSize", "gpu", "silentThreshold"]