feat(voice): ship noise suppression treatment to everyone (#3029)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-29 03:43:58 +02:00
1 parent 39c72f0fb0
commit c488906131
88 files changed
+2861 -5837

No files matched your search

@@ -33,7 +33,7 @@ import {useMediaPermission} from '@app/features/user/components/modals/tabs/hook
import styles from '@app/features/user/components/modals/tabs/UserVoiceTab.module.css';
import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands';
import MediaEngine from '@app/features/voice/engine/MediaEngineFacade';
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import type VoiceSettings from '@app/features/voice/state/VoiceSettings';
import {
type ExternalAudioProcessorMatch,
findExternalProcessorForDevice,
@@ -209,10 +209,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
inputVolume,
outputVolume,
echoCancellation,
noiseSuppression,
autoGainControl,
deepFilterNoiseSuppression,
deepFilterNoiseSuppressionLevel,
vadThreshold,
vadAutoSensitivity,
} = voiceSettings;
@@ -451,9 +448,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
label={i18n._(STEREO_MICROPHONE_DESCRIPTOR)}
description={i18n._(STEREO_MICROPHONE_DESCRIPTION_DESCRIPTOR)}
value={isStereoMicrophoneEnabled()}
onChange={(value) => {
VoiceSettings.stereoMicrophone = value;
}}
onChange={(value) => VoiceSettingsCommands.update({stereoMicrophone: value})}
ariaLabel={i18n._(STEREO_MICROPHONE_DESCRIPTOR)}
data-flx={dataFlx}
/>
@@ -693,10 +688,7 @@ export const VoiceTab: React.FC<VoiceTabProps> = observer(({voiceSettings, autoR
inputVolume,
outputVolume,
echoCancellation,
noiseSuppression,
autoGainControl,
deepFilterNoiseSuppression,
deepFilterNoiseSuppressionLevel,
voiceProcessingMode,
}}
data-flx="user.voice-tab.mic-test-section"
@@ -31,9 +31,7 @@ interface CreateMicTestAudioGraphOptions {
playbackTarget: AudioNode;
playbackDelaySeconds: number;
deepFilter: boolean;
deepFilterNoiseReductionLevel: number;
workletBackend: NoiseSuppressionWorkletBackend | null;
suppressionStrength: number;
}
export async function createMicTestAudioGraph({
@@ -44,9 +42,7 @@ export async function createMicTestAudioGraph({
playbackTarget,
playbackDelaySeconds,
deepFilter,
deepFilterNoiseReductionLevel,
workletBackend,
suppressionStrength,
}: CreateMicTestAudioGraphOptions): Promise<MicTestAudioGraph> {
const source = audioContext.createMediaStreamSource(new MediaStream([sourceTrack]));
const inputGainNode = audioContext.createGain();
@@ -59,10 +55,7 @@ export async function createMicTestAudioGraph({
let monitorHead: AudioNode = inputGainNode;
try {
if (deepFilter) {
deepFilterChain = await buildDeepFilterAudioChain({
audioContext,
noiseReductionLevel: deepFilterNoiseReductionLevel,
});
deepFilterChain = await buildDeepFilterAudioChain(audioContext);
inputGainNode.connect(deepFilterChain.inputDestination);
suppressedSource = audioContext.createMediaStreamSource(new MediaStream([deepFilterChain.processedTrack]));
monitorHead = suppressedSource;
@@ -70,7 +63,6 @@ export async function createMicTestAudioGraph({
workletChain = await buildNoiseSuppressionWorkletChain({
audioContext,
backend: workletBackend,
suppressionStrength,
});
inputGainNode.connect(workletChain.inputDestination);
suppressedSource = audioContext.createMediaStreamSource(new MediaStream([workletChain.processedTrack]));
@@ -12,8 +12,7 @@ import {
getNoiseSuppressionBackendDescriptor,
type VoiceNoiseSuppressionBackend,
} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionBackends';
import {readEffectiveNoiseSuppression} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionRuntime';
import {applyNoiseSuppressionOverride} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionSelection';
import {readNoiseSuppressionBackend} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionRuntime';
import type {NoiseSuppressionWorkletBackend} from '@app/features/voice/utils/noise_suppression/NoiseSuppressionWorkletTypes';
import {
applyContentHintToTrack,
@@ -41,15 +40,10 @@ export interface MicTestSettings {
inputVolume: number;
outputVolume: number;
echoCancellation: boolean;
noiseSuppression: boolean;
autoGainControl: boolean;
deepFilterNoiseSuppression: boolean;
deepFilterNoiseSuppressionLevel: number;
voiceProcessingMode: VoiceProcessingMode;
}
const MIC_TEST_PROBE_SAMPLE_RATE = 48000;
function resolveMicTestWorkletBackend(backend: VoiceNoiseSuppressionBackend): NoiseSuppressionWorkletBackend | null {
return getNoiseSuppressionBackendDescriptor(backend).engine === 'worklet'
? (backend as NoiseSuppressionWorkletBackend)
@@ -77,32 +71,24 @@ export const useMicTest = (settings: MicTestSettings) => {
const restartPendingRef = useRef(false);
const activeCaptureSignatureRef = useRef<string | null>(null);
const micExplicitlyDenied = MediaPermission.microphoneExplicitlyDenied;
const activeNoiseSuppression = readEffectiveNoiseSuppression(MIC_TEST_PROBE_SAMPLE_RATE);
const noiseSuppressionBackend = readNoiseSuppressionBackend();
const captureSignature = useMemo(
() =>
JSON.stringify({
inputDeviceId: settings.inputDeviceId,
outputDeviceId: settings.outputDeviceId,
echoCancellation: settings.echoCancellation,
noiseSuppression: settings.noiseSuppression,
autoGainControl: settings.autoGainControl,
deepFilterNoiseSuppression: settings.deepFilterNoiseSuppression,
deepFilterNoiseSuppressionLevel: settings.deepFilterNoiseSuppressionLevel,
voiceProcessingMode: settings.voiceProcessingMode,
noiseSuppressionBackend: activeNoiseSuppression.backend,
noiseSuppressionStrength: activeNoiseSuppression.suppressionStrength,
noiseSuppressionBackend,
}),
[
settings.autoGainControl,
settings.deepFilterNoiseSuppression,
settings.deepFilterNoiseSuppressionLevel,
settings.echoCancellation,
settings.inputDeviceId,
settings.noiseSuppression,
settings.outputDeviceId,
settings.voiceProcessingMode,
activeNoiseSuppression.backend,
activeNoiseSuppression.suppressionStrength,
noiseSuppressionBackend,
],
);
const updateLevel = useCallback(() => {
@@ -191,8 +177,7 @@ export const useMicTest = (settings: MicTestSettings) => {
return exhaustive;
}
}
const effectiveNoiseSuppression = readEffectiveNoiseSuppression(MIC_TEST_PROBE_SAMPLE_RATE);
const profile = applyNoiseSuppressionOverride(resolveVoiceProcessing(settings), effectiveNoiseSuppression);
const profile = resolveVoiceProcessing(settings, readNoiseSuppressionBackend(), false);
const workletBackend = resolveMicTestWorkletBackend(profile.noiseSuppressionBackend);
const baseAudioConstraints: MediaTrackConstraints & {voiceIsolation?: boolean} = {
echoCancellation: profile.echoCancellation,
@@ -240,9 +225,7 @@ export const useMicTest = (settings: MicTestSettings) => {
playbackTarget,
playbackDelaySeconds: MIC_TEST_MONITOR_DELAY_SECONDS,
deepFilter: profile.deepFilter,
deepFilterNoiseReductionLevel: profile.deepFilterNoiseReductionLevel,
workletBackend,
suppressionStrength: effectiveNoiseSuppression.suppressionStrength,
});
timeDomainDataRef.current = new Float32Array(graphRef.current.analyser.fftSize);
const audioElement = new Audio();