mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
fix(voice): stop forcing automatic gain control off on every capture profile (#1781)
This commit is contained in:
1 parent
5331c0216a
commit
c020eded31
6 files changed
+87
-22
No files matched your search
@@ -169,8 +169,8 @@ const resolvePushToTalkReleaseDelayInput = (
|
|||||||
).value;
|
).value;
|
||||||
};
|
};
|
||||||
const AUTO_GAIN_DESCRIPTION_DESCRIPTOR = msg({
|
const AUTO_GAIN_DESCRIPTION_DESCRIPTOR = msg({
|
||||||
message: 'Evens out your mic volume. Off when enhanced suppression is on.',
|
message: 'Evens out your mic volume so you are not too quiet.',
|
||||||
comment: 'Description for the automatic gain control toggle in the custom voice processing settings.',
|
comment: 'Description for the automatic gain control toggle in the voice processing settings.',
|
||||||
});
|
});
|
||||||
const INPUT_AND_OUTPUT_DESCRIPTOR = msg({
|
const INPUT_AND_OUTPUT_DESCRIPTOR = msg({
|
||||||
message: 'Input and output',
|
message: 'Input and output',
|
||||||
@@ -293,7 +293,6 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
const noiseSuppressionMethod = resolveNoiseSuppressionMethod(deepFilterNoiseSuppression, noiseSuppression);
|
const noiseSuppressionMethod = resolveNoiseSuppressionMethod(deepFilterNoiseSuppression, noiseSuppression);
|
||||||
const effectiveAutoGainControl = !deepFilterNoiseSuppression && autoGainControl;
|
|
||||||
const noiseSuppressionOptions: Array<ComboboxOption<NoiseSuppressionMethod>> = [
|
const noiseSuppressionOptions: Array<ComboboxOption<NoiseSuppressionMethod>> = [
|
||||||
{value: 'enhanced', label: i18n._(NOISE_SUPPRESSION_ENHANCED_DESCRIPTOR)},
|
{value: 'enhanced', label: i18n._(NOISE_SUPPRESSION_ENHANCED_DESCRIPTOR)},
|
||||||
{value: 'standard', label: i18n._(NOISE_SUPPRESSION_STANDARD_DESCRIPTOR)},
|
{value: 'standard', label: i18n._(NOISE_SUPPRESSION_STANDARD_DESCRIPTOR)},
|
||||||
@@ -449,6 +448,16 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
const renderAutoGainControlSwitch = () => (
|
||||||
|
<Switch
|
||||||
|
label={i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR)}
|
||||||
|
description={i18n._(AUTO_GAIN_DESCRIPTION_DESCRIPTOR)}
|
||||||
|
value={autoGainControl}
|
||||||
|
onChange={(value) => VoiceSettingsCommands.update({autoGainControl: value})}
|
||||||
|
ariaLabel={i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR)}
|
||||||
|
data-flx="user.voice-tab.render-auto-gain-control-switch.switch.update-auto-gain-control"
|
||||||
|
/>
|
||||||
|
);
|
||||||
const renderCustomProfile = () => (
|
const renderCustomProfile = () => (
|
||||||
<div className={styles.profileSubSection} data-flx="user.voice-tab.render-custom-profile.profile-sub-section">
|
<div className={styles.profileSubSection} data-flx="user.voice-tab.render-custom-profile.profile-sub-section">
|
||||||
{renderPttControls()}
|
{renderPttControls()}
|
||||||
@@ -507,15 +516,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
ariaLabel={i18n._(VOICE_ECHO_CANCELLATION_DESCRIPTOR)}
|
ariaLabel={i18n._(VOICE_ECHO_CANCELLATION_DESCRIPTOR)}
|
||||||
data-flx="user.voice-tab.render-custom-profile.switch.update--2"
|
data-flx="user.voice-tab.render-custom-profile.switch.update--2"
|
||||||
/>
|
/>
|
||||||
<Switch
|
{renderAutoGainControlSwitch()}
|
||||||
label={i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR)}
|
|
||||||
description={i18n._(AUTO_GAIN_DESCRIPTION_DESCRIPTOR)}
|
|
||||||
value={effectiveAutoGainControl}
|
|
||||||
disabled={deepFilterNoiseSuppression}
|
|
||||||
onChange={(value) => VoiceSettingsCommands.update({autoGainControl: value})}
|
|
||||||
ariaLabel={i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR)}
|
|
||||||
data-flx="user.voice-tab.render-custom-profile.switch.update-auto-gain-control"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
@@ -662,6 +663,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
{voiceProcessingMode === 'voice' && (
|
{voiceProcessingMode === 'voice' && (
|
||||||
<div className={styles.profileSubSection} data-flx="user.voice-tab.profile-sub-section">
|
<div className={styles.profileSubSection} data-flx="user.voice-tab.profile-sub-section">
|
||||||
{renderPttControls()}
|
{renderPttControls()}
|
||||||
|
{renderAutoGainControlSwitch()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{voiceProcessingMode === 'studio' && pttCombo?.key && isPushToTalk && (
|
{voiceProcessingMode === 'studio' && pttCombo?.key && isPushToTalk && (
|
||||||
@@ -682,7 +684,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
|
|||||||
outputVolume,
|
outputVolume,
|
||||||
echoCancellation,
|
echoCancellation,
|
||||||
noiseSuppression,
|
noiseSuppression,
|
||||||
autoGainControl: effectiveAutoGainControl,
|
autoGainControl,
|
||||||
deepFilterNoiseSuppression,
|
deepFilterNoiseSuppression,
|
||||||
deepFilterNoiseSuppressionLevel,
|
deepFilterNoiseSuppressionLevel,
|
||||||
voiceProcessingMode,
|
voiceProcessingMode,
|
||||||
|
|||||||
@@ -359,8 +359,7 @@ export const VoiceAudioSettingsMenu: React.FC<VoiceAudioSettingsMenuProps> = obs
|
|||||||
data-flx="voice.voice-settings-menus.voice-audio-settings-menu.icon--2"
|
data-flx="voice.voice-settings-menus.voice-audio-settings-menu.icon--2"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
checked={voiceSettings.autoGainControl && !deepFilterEnabled}
|
checked={voiceSettings.autoGainControl}
|
||||||
disabled={deepFilterEnabled}
|
|
||||||
onCheckedChange={(checked) => VoiceSettingsCommands.update({autoGainControl: checked})}
|
onCheckedChange={(checked) => VoiceSettingsCommands.update({autoGainControl: checked})}
|
||||||
data-flx="voice.voice-settings-menus.voice-audio-settings-menu.checkbox-item--2"
|
data-flx="voice.voice-settings-menus.voice-audio-settings-menu.checkbox-item--2"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -281,7 +281,6 @@ export const VoiceAudioSettingsBottomSheet: React.FC<VoiceAudioSettingsBottomShe
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
label: i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR),
|
label: i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR),
|
||||||
disabled: deepFilterEnabled,
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
VoiceSettingsCommands.update({autoGainControl: !voiceSettings.autoGainControl});
|
VoiceSettingsCommands.update({autoGainControl: !voiceSettings.autoGainControl});
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-3
@@ -70,8 +70,8 @@ const STOPS_YOUR_SPEAKERS_FROM_LOOPING_BACK_INTO_YOUR_DESCRIPTOR = msg({
|
|||||||
comment: 'Description for the echo cancellation toggle in the custom voice processing settings.',
|
comment: 'Description for the echo cancellation toggle in the custom voice processing settings.',
|
||||||
});
|
});
|
||||||
const AUTO_GAIN_DESCRIPTION_DESCRIPTOR = msg({
|
const AUTO_GAIN_DESCRIPTION_DESCRIPTOR = msg({
|
||||||
message: 'Evens out your mic volume. Off when enhanced suppression is on.',
|
message: 'Evens out your mic volume so you are not too quiet.',
|
||||||
comment: 'Description for the automatic gain control toggle in the custom voice processing settings.',
|
comment: 'Description for the automatic gain control toggle in the voice processing settings.',
|
||||||
});
|
});
|
||||||
const DEEP_FILTER_STRENGTH_DESCRIPTOR = msg({
|
const DEEP_FILTER_STRENGTH_DESCRIPTOR = msg({
|
||||||
message: 'Suppression strength',
|
message: 'Suppression strength',
|
||||||
@@ -183,7 +183,6 @@ export const AudioProcessingModal = observer(() => {
|
|||||||
label={i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR)}
|
label={i18n._(VOICE_AUTOMATIC_GAIN_CONTROL_DESCRIPTOR)}
|
||||||
description={i18n._(AUTO_GAIN_DESCRIPTION_DESCRIPTOR)}
|
description={i18n._(AUTO_GAIN_DESCRIPTION_DESCRIPTOR)}
|
||||||
value={VoiceSettings.autoGainControl}
|
value={VoiceSettings.autoGainControl}
|
||||||
disabled={deepFilterEnabled}
|
|
||||||
onChange={(checked) => VoiceSettingsCommands.update({autoGainControl: checked})}
|
onChange={(checked) => VoiceSettingsCommands.update({autoGainControl: checked})}
|
||||||
compact
|
compact
|
||||||
data-flx="voice.voice-connection-status.audio-processing-modal.switch.update--2"
|
data-flx="voice.voice-connection-status.audio-processing-modal.switch.update--2"
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import {
|
||||||
|
FOCUSED_VOICE_DEEP_FILTER_NOISE_REDUCTION_LEVEL,
|
||||||
|
resolveVoiceProcessing,
|
||||||
|
type VoiceProcessingSettingsLike,
|
||||||
|
} from '@app/features/voice/utils/VoiceProcessingProfile';
|
||||||
|
import {describe, expect, it, vi} from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('@app/features/voice/engine/VoiceDevicePermissionState', () => ({
|
||||||
|
default: {getState: () => ({inputDevices: []})},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@app/features/voice/utils/VoiceDeviceManager', () => ({
|
||||||
|
resolveEffectiveDeviceId: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
function createSettings(overrides: Partial<VoiceProcessingSettingsLike> = {}): VoiceProcessingSettingsLike {
|
||||||
|
return {
|
||||||
|
voiceProcessingMode: 'voice',
|
||||||
|
echoCancellation: true,
|
||||||
|
noiseSuppression: true,
|
||||||
|
autoGainControl: true,
|
||||||
|
deepFilterNoiseSuppression: true,
|
||||||
|
deepFilterNoiseSuppressionLevel: 80,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('resolveVoiceProcessing', () => {
|
||||||
|
it('keeps automatic gain control on for focused voice at factory defaults', () => {
|
||||||
|
const resolved = resolveVoiceProcessing(createSettings());
|
||||||
|
expect(resolved.mode).toBe('voice');
|
||||||
|
expect(resolved.autoGainControl).toBe(true);
|
||||||
|
expect(resolved.deepFilter).toBe(true);
|
||||||
|
expect(resolved.deepFilterNoiseReductionLevel).toBe(FOCUSED_VOICE_DEEP_FILTER_NOISE_REDUCTION_LEVEL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('honours an explicit opt out of automatic gain control in focused voice', () => {
|
||||||
|
const resolved = resolveVoiceProcessing(createSettings({autoGainControl: false}));
|
||||||
|
expect(resolved.autoGainControl).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps automatic gain control on in custom mode while deep filter is enabled', () => {
|
||||||
|
const resolved = resolveVoiceProcessing(
|
||||||
|
createSettings({voiceProcessingMode: 'custom', deepFilterNoiseSuppression: true}),
|
||||||
|
);
|
||||||
|
expect(resolved.autoGainControl).toBe(true);
|
||||||
|
expect(resolved.deepFilter).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still suppresses browser noise suppression when deep filter is enabled in custom mode', () => {
|
||||||
|
const resolved = resolveVoiceProcessing(
|
||||||
|
createSettings({voiceProcessingMode: 'custom', noiseSuppression: true, deepFilterNoiseSuppression: true}),
|
||||||
|
);
|
||||||
|
expect(resolved.browserNoiseSuppression).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves studio mode as the untouched bypass', () => {
|
||||||
|
const resolved = resolveVoiceProcessing(createSettings({voiceProcessingMode: 'studio'}));
|
||||||
|
expect(resolved.autoGainControl).toBe(false);
|
||||||
|
expect(resolved.echoCancellation).toBe(false);
|
||||||
|
expect(resolved.browserNoiseSuppression).toBe(false);
|
||||||
|
expect(resolved.deepFilter).toBe(false);
|
||||||
|
expect(resolved.contentHint).toBe('music');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -50,13 +50,12 @@ export function resolveVoiceProcessing(settings: VoiceProcessingSettingsLike): R
|
|||||||
contentHint: 'music',
|
contentHint: 'music',
|
||||||
};
|
};
|
||||||
case 'custom': {
|
case 'custom': {
|
||||||
const autoGain = settings.deepFilterNoiseSuppression ? false : settings.autoGainControl;
|
|
||||||
const browserNs = settings.noiseSuppression && !settings.deepFilterNoiseSuppression;
|
const browserNs = settings.noiseSuppression && !settings.deepFilterNoiseSuppression;
|
||||||
return {
|
return {
|
||||||
mode: 'custom',
|
mode: 'custom',
|
||||||
echoCancellation: settings.echoCancellation,
|
echoCancellation: settings.echoCancellation,
|
||||||
browserNoiseSuppression: browserNs,
|
browserNoiseSuppression: browserNs,
|
||||||
autoGainControl: autoGain,
|
autoGainControl: settings.autoGainControl,
|
||||||
deepFilter: settings.deepFilterNoiseSuppression,
|
deepFilter: settings.deepFilterNoiseSuppression,
|
||||||
deepFilterNoiseReductionLevel: settings.deepFilterNoiseSuppression
|
deepFilterNoiseReductionLevel: settings.deepFilterNoiseSuppression
|
||||||
? clampDeepFilterNoiseReductionLevel(settings.deepFilterNoiseSuppressionLevel)
|
? clampDeepFilterNoiseReductionLevel(settings.deepFilterNoiseSuppressionLevel)
|
||||||
@@ -69,7 +68,7 @@ export function resolveVoiceProcessing(settings: VoiceProcessingSettingsLike): R
|
|||||||
mode: 'voice',
|
mode: 'voice',
|
||||||
echoCancellation: true,
|
echoCancellation: true,
|
||||||
browserNoiseSuppression: false,
|
browserNoiseSuppression: false,
|
||||||
autoGainControl: false,
|
autoGainControl: settings.autoGainControl,
|
||||||
deepFilter: true,
|
deepFilter: true,
|
||||||
deepFilterNoiseReductionLevel: FOCUSED_VOICE_DEEP_FILTER_NOISE_REDUCTION_LEVEL,
|
deepFilterNoiseReductionLevel: FOCUSED_VOICE_DEEP_FILTER_NOISE_REDUCTION_LEVEL,
|
||||||
contentHint: 'speech',
|
contentHint: 'speech',
|
||||||
|
|||||||
Reference in new issue
Block a user