mirror of
https://github.com/w-okada/voice-changer.git
synced 2025-02-09 03:37:51 +03:00
add gain control
This commit is contained in:
parent
59a56d5507
commit
b87f7983b3
11
client/demo/dist/index.html
vendored
11
client/demo/dist/index.html
vendored
@ -1 +1,10 @@
|
|||||||
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>
|
<!DOCTYPE html>
|
||||||
|
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Voice Changer Client Demo</title>
|
||||||
|
<script defer src="index.js"></script></head>
|
||||||
|
<body style="width: 100%; height: 100%; margin: 0px">
|
||||||
|
<div id="app" style="width: 100%; height: 100%"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
597
client/demo/dist/index.js
vendored
597
client/demo/dist/index.js
vendored
File diff suppressed because one or more lines are too long
31
client/demo/dist/index.js.LICENSE.txt
vendored
31
client/demo/dist/index.js.LICENSE.txt
vendored
@ -1,31 +0,0 @@
|
|||||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-dom.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* scheduler.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
@ -151,7 +151,7 @@ export const useSpeakerSetting = (props: UseSpeakerSettingProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{srcIdRow}
|
{srcIdRow}
|
||||||
{dstIdRow}
|
{dstIdRow}
|
||||||
{editSpeakerIdMappingRow}
|
{/* {editSpeakerIdMappingRow} */}
|
||||||
{f0FactorRow}
|
{f0FactorRow}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -23,18 +23,38 @@ export const useServerControl = (props: UseServerControlProps) => {
|
|||||||
const stopClassName = isStarted ? "body-button-stanby" : "body-button-active"
|
const stopClassName = isStarted ? "body-button-stanby" : "body-button-active"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="body-row split-3-3-4 left-padding-1 guided">
|
<div className="body-row split-3-2-2-3 left-padding-1 guided">
|
||||||
<div className="body-item-title left-padding-1">Start</div>
|
<div className="body-item-title left-padding-1">Start</div>
|
||||||
<div className="body-button-container">
|
<div className="body-button-container">
|
||||||
<div onClick={onStartClicked} className={startClassName}>start</div>
|
<div onClick={onStartClicked} className={startClassName}>start</div>
|
||||||
<div onClick={onStopClicked} className={stopClassName}>stop</div>
|
<div onClick={onStopClicked} className={stopClassName}>stop</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="body-input-container split-4-4-2">
|
||||||
|
<div>gain(in)</div>
|
||||||
|
<input type="range" className="body-item-input" min="0.0" max="1.0" step="0.1" value={props.clientState.clientSetting.setting.inputGain} onChange={(e) => {
|
||||||
|
props.clientState.clientSetting.setInputGain(Number(e.target.value))
|
||||||
|
}}></input>
|
||||||
|
<div>{props.clientState.clientSetting.setting.inputGain}</div>
|
||||||
|
</div>
|
||||||
|
<div className="body-input-container split-4-4-2">
|
||||||
|
<div>gain(out)</div>
|
||||||
|
<input type="range" className="body-item-input" min="0.0" max="1.0" step="0.1" value={props.clientState.clientSetting.setting.outputGain} onChange={(e) => {
|
||||||
|
props.clientState.clientSetting.setOutputGain(Number(e.target.value))
|
||||||
|
}}></input>
|
||||||
|
<div>{props.clientState.clientSetting.setting.outputGain}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="body-input-container">
|
<div className="body-input-container">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}, [isStarted, props.clientState.clientSetting.start, props.clientState.clientSetting.stop])
|
}, [isStarted, props.clientState.clientSetting.start, props.clientState.clientSetting.stop,
|
||||||
|
props.clientState.clientSetting.setInputGain, props.clientState.clientSetting.setting.inputGain,
|
||||||
|
props.clientState.clientSetting.setOutputGain, props.clientState.clientSetting.setting.outputGain
|
||||||
|
])
|
||||||
|
|
||||||
const performanceRow = useMemo(() => {
|
const performanceRow = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
@ -125,6 +125,87 @@ body {
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.split-2-5-3 {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1px 0px 1px 0px;
|
||||||
|
& > div:nth-child(1) {
|
||||||
|
left: 0px;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(2) {
|
||||||
|
left: 20%;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(3) {
|
||||||
|
left: 70%;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.split-4-4-2 {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1px 0px 1px 0px;
|
||||||
|
& > div:nth-child(1) {
|
||||||
|
left: 0px;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(2) {
|
||||||
|
left: 40%;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(3) {
|
||||||
|
left: 80%;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.split-3-2-2-3 {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1px 0px 1px 0px;
|
||||||
|
& > div:nth-child(1) {
|
||||||
|
left: 0px;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(2) {
|
||||||
|
left: 30%;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(3) {
|
||||||
|
left: 50%;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(4) {
|
||||||
|
left: 70%;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.split-3-2-3-2 {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1px 0px 1px 0px;
|
||||||
|
& > div:nth-child(1) {
|
||||||
|
left: 0px;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(2) {
|
||||||
|
left: 30%;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(3) {
|
||||||
|
left: 50%;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
& > div:nth-child(4) {
|
||||||
|
left: 80%;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
.split-3-1-2-4 {
|
.split-3-1-2-4 {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -303,6 +384,9 @@ body {
|
|||||||
color: rgb(30, 30, 30);
|
color: rgb(30, 30, 30);
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
.body-input-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.body-item-input {
|
.body-item-input {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,15 @@ export class VoiceChangerClient {
|
|||||||
private currentMediaStream: MediaStream | null = null
|
private currentMediaStream: MediaStream | null = null
|
||||||
private currentMediaStreamAudioSourceNode: MediaStreamAudioSourceNode | null = null
|
private currentMediaStreamAudioSourceNode: MediaStreamAudioSourceNode | null = null
|
||||||
private outputNodeFromVF: MediaStreamAudioDestinationNode | null = null
|
private outputNodeFromVF: MediaStreamAudioDestinationNode | null = null
|
||||||
|
private inputGainNode: GainNode | null = null
|
||||||
|
private outputGainNode: GainNode | null = null
|
||||||
private micStream: MicrophoneStream | null = null
|
private micStream: MicrophoneStream | null = null
|
||||||
private audioStreamer!: AudioStreamer
|
private audioStreamer!: AudioStreamer
|
||||||
private vcNode!: VoiceChangerWorkletNode
|
private vcNode!: VoiceChangerWorkletNode
|
||||||
private currentMediaStreamAudioDestinationNode!: MediaStreamAudioDestinationNode
|
private currentMediaStreamAudioDestinationNode!: MediaStreamAudioDestinationNode
|
||||||
|
|
||||||
|
private inputGain = 1.0
|
||||||
|
|
||||||
private promiseForInitialize: Promise<void>
|
private promiseForInitialize: Promise<void>
|
||||||
private _isVoiceChanging = false
|
private _isVoiceChanging = false
|
||||||
|
|
||||||
@ -76,7 +80,9 @@ export class VoiceChangerClient {
|
|||||||
|
|
||||||
this.vcNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
|
this.vcNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
|
||||||
this.currentMediaStreamAudioDestinationNode = this.ctx.createMediaStreamDestination() // output node
|
this.currentMediaStreamAudioDestinationNode = this.ctx.createMediaStreamDestination() // output node
|
||||||
this.vcNode.connect(this.currentMediaStreamAudioDestinationNode) // vc node -> output node
|
this.outputGainNode = this.ctx.createGain()
|
||||||
|
this.vcNode.connect(this.outputGainNode) // vc node -> output node
|
||||||
|
this.outputGainNode.connect(this.currentMediaStreamAudioDestinationNode)
|
||||||
// (vc nodeにはaudio streamerのcallbackでデータが投げ込まれる)
|
// (vc nodeにはaudio streamerのcallbackでデータが投げ込まれる)
|
||||||
this.audioStreamer = new AudioStreamer(this.callbacks, audioStreamerListeners, { objectMode: true, })
|
this.audioStreamer = new AudioStreamer(this.callbacks, audioStreamerListeners, { objectMode: true, })
|
||||||
this.audioStreamer.setInputChunkNum(DefaultVoiceChangerClientSetting.inputChunkNum)
|
this.audioStreamer.setInputChunkNum(DefaultVoiceChangerClientSetting.inputChunkNum)
|
||||||
@ -149,14 +155,21 @@ export class VoiceChangerClient {
|
|||||||
// connect nodes.
|
// connect nodes.
|
||||||
if (this.currentDevice && forceVfDisable == false) {
|
if (this.currentDevice && forceVfDisable == false) {
|
||||||
this.currentMediaStreamAudioSourceNode = this.ctx.createMediaStreamSource(this.currentMediaStream) // input node
|
this.currentMediaStreamAudioSourceNode = this.ctx.createMediaStreamSource(this.currentMediaStream) // input node
|
||||||
|
this.inputGainNode = this.ctx.createGain()
|
||||||
|
this.inputGainNode.gain.value = this.inputGain
|
||||||
|
this.currentMediaStreamAudioSourceNode.connect(this.inputGainNode)
|
||||||
|
|
||||||
this.currentDevice.chooseNewInnerDevice(this.currentMediaStream)
|
this.currentDevice.chooseNewInnerDevice(this.currentMediaStream)
|
||||||
const voiceFocusNode = await this.currentDevice.createAudioNode(this.ctx); // vf node
|
const voiceFocusNode = await this.currentDevice.createAudioNode(this.ctx); // vf node
|
||||||
this.currentMediaStreamAudioSourceNode.connect(voiceFocusNode.start) // input node -> vf node
|
this.inputGainNode.connect(voiceFocusNode.start) // input node -> vf node
|
||||||
voiceFocusNode.end.connect(this.outputNodeFromVF!)
|
voiceFocusNode.end.connect(this.outputNodeFromVF!)
|
||||||
this.micStream.setStream(this.outputNodeFromVF!.stream) // vf node -> mic stream
|
this.micStream.setStream(this.outputNodeFromVF!.stream) // vf node -> mic stream
|
||||||
} else {
|
} else {
|
||||||
console.log("VF disabled")
|
console.log("VF disabled")
|
||||||
this.micStream.setStream(this.currentMediaStream) // input device -> mic stream
|
this.inputGainNode = this.ctx.createGain()
|
||||||
|
this.inputGainNode.gain.value = this.inputGain
|
||||||
|
const inputDestinationNodeForMicStream = this.ctx.createMediaStreamDestination()
|
||||||
|
this.micStream.setStream(inputDestinationNodeForMicStream.stream) // input device -> mic stream
|
||||||
}
|
}
|
||||||
this.micStream.pipe(this.audioStreamer) // mic stream -> audio streamer
|
this.micStream.pipe(this.audioStreamer) // mic stream -> audio streamer
|
||||||
if (!this._isVoiceChanging) {
|
if (!this._isVoiceChanging) {
|
||||||
@ -252,6 +265,22 @@ export class VoiceChangerClient {
|
|||||||
return this.configurator.updateSettings(key, val)
|
return this.configurator.updateSettings(key, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInputGain = (val: number) => {
|
||||||
|
this.inputGain = val
|
||||||
|
if (!this.inputGainNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.inputGainNode.gain.value = val
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutputGain = (val: number) => {
|
||||||
|
if (!this.outputGainNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.outputGainNode.gain.value = val
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Information
|
// Information
|
||||||
getClientSettings = () => {
|
getClientSettings = () => {
|
||||||
return this.audioStreamer.getSettings()
|
return this.audioStreamer.getSettings()
|
||||||
|
@ -35,6 +35,9 @@ export type VoiceChangerClientSetting = {
|
|||||||
correspondences: Correspondence[],
|
correspondences: Correspondence[],
|
||||||
forceVfDisable: boolean,
|
forceVfDisable: boolean,
|
||||||
voiceChangerMode: VoiceChangerMode,
|
voiceChangerMode: VoiceChangerMode,
|
||||||
|
|
||||||
|
inputGain: number
|
||||||
|
outputGain: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkletSetting = {
|
export type WorkletSetting = {
|
||||||
@ -183,6 +186,8 @@ export const DefaultVoiceChangerClientSetting: VoiceChangerClientSetting = {
|
|||||||
correspondences: [],
|
correspondences: [],
|
||||||
forceVfDisable: false,
|
forceVfDisable: false,
|
||||||
voiceChangerMode: "realtime",
|
voiceChangerMode: "realtime",
|
||||||
|
inputGain: 1.0,
|
||||||
|
outputGain: 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultWorkletSetting: WorkletSetting = {
|
export const DefaultWorkletSetting: WorkletSetting = {
|
||||||
|
@ -22,7 +22,8 @@ export type ClientSettingState = {
|
|||||||
setSampleRate: (num: SampleRate) => void
|
setSampleRate: (num: SampleRate) => void
|
||||||
setSpeakers: (speakers: Speaker[]) => void
|
setSpeakers: (speakers: Speaker[]) => void
|
||||||
setCorrespondences: (file: File | null) => Promise<void>
|
setCorrespondences: (file: File | null) => Promise<void>
|
||||||
|
setInputGain: (val: number) => void
|
||||||
|
setOutputGain: (val: number) => void
|
||||||
start: () => Promise<void>
|
start: () => Promise<void>
|
||||||
stop: () => Promise<void>
|
stop: () => Promise<void>
|
||||||
reloadClientSetting: () => Promise<void>
|
reloadClientSetting: () => Promise<void>
|
||||||
@ -64,6 +65,7 @@ export const useClientSetting = (props: UseClientSettingProps): ClientSettingSta
|
|||||||
props.voiceChangerClient.setInputChunkNum(settingRef.current.inputChunkNum)
|
props.voiceChangerClient.setInputChunkNum(settingRef.current.inputChunkNum)
|
||||||
props.voiceChangerClient.setProtocol(settingRef.current.protocol)
|
props.voiceChangerClient.setProtocol(settingRef.current.protocol)
|
||||||
props.voiceChangerClient.setVoiceChangerMode(settingRef.current.voiceChangerMode)
|
props.voiceChangerClient.setVoiceChangerMode(settingRef.current.voiceChangerMode)
|
||||||
|
props.voiceChangerClient.setInputGain(settingRef.current.inputGain)
|
||||||
|
|
||||||
// Input, bufferSize, VoiceFocus Disableは_setInputで設定
|
// Input, bufferSize, VoiceFocus Disableは_setInputで設定
|
||||||
_setInput()
|
_setInput()
|
||||||
@ -207,6 +209,23 @@ export const useClientSetting = (props: UseClientSettingProps): ClientSettingSta
|
|||||||
}
|
}
|
||||||
}, [props.voiceChangerClient])
|
}, [props.voiceChangerClient])
|
||||||
|
|
||||||
|
const setInputGain = useMemo(() => {
|
||||||
|
return (val: number) => {
|
||||||
|
if (!props.voiceChangerClient) return
|
||||||
|
props.voiceChangerClient.setInputGain(val)
|
||||||
|
settingRef.current.inputGain = val
|
||||||
|
setSetting({ ...settingRef.current })
|
||||||
|
}
|
||||||
|
}, [props.voiceChangerClient])
|
||||||
|
const setOutputGain = useMemo(() => {
|
||||||
|
return (val: number) => {
|
||||||
|
if (!props.voiceChangerClient) return
|
||||||
|
props.voiceChangerClient.setOutputGain(val)
|
||||||
|
settingRef.current.outputGain = val
|
||||||
|
setSetting({ ...settingRef.current })
|
||||||
|
}
|
||||||
|
}, [props.voiceChangerClient])
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
// 操作
|
// 操作
|
||||||
/////////////
|
/////////////
|
||||||
@ -245,6 +264,8 @@ export const useClientSetting = (props: UseClientSettingProps): ClientSettingSta
|
|||||||
setSampleRate,
|
setSampleRate,
|
||||||
setSpeakers,
|
setSpeakers,
|
||||||
setCorrespondences,
|
setCorrespondences,
|
||||||
|
setInputGain,
|
||||||
|
setOutputGain,
|
||||||
|
|
||||||
start,
|
start,
|
||||||
stop,
|
stop,
|
||||||
|
Loading…
Reference in New Issue
Block a user