refactor: remove browser NS toggle, default RNNoise on, rework voice settings
- RNNoise (AI Noise Suppression) now enabled by default for all users - Remove redundant browser Noise Suppression toggle from VoiceControls and UserSettings — AudioManager handles it automatically as fallback - Add AI Noise Suppression toggle to UserSettings panel - Rename toggleRnnoise → setRnnoiseEnabled for clearer API - Store migration v3→v4: enable RNNoise for existing users - Keep Echo Cancellation and Auto Gain Control (orthogonal features)
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -20,11 +20,11 @@ export function UserSettingsModal() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const echoCancellation = useVoiceStore((s) => s.echoCancellation);
|
||||
const noiseSuppression = useVoiceStore((s) => s.noiseSuppression);
|
||||
const autoGainControl = useVoiceStore((s) => s.autoGainControl);
|
||||
const rnnoiseEnabled = useVoiceStore((s) => s.rnnoiseEnabled);
|
||||
const setEchoCancellation = useVoiceStore((s) => s.setEchoCancellation);
|
||||
const toggleNoiseSuppression = useVoiceStore((s) => s.toggleNoiseSuppression);
|
||||
const setAutoGainControl = useVoiceStore((s) => s.setAutoGainControl);
|
||||
const setRnnoiseEnabled = useVoiceStore((s) => s.setRnnoiseEnabled);
|
||||
|
||||
const isOpen = activeModal === 'userSettings';
|
||||
|
||||
@@ -126,9 +126,19 @@ export function UserSettingsModal() {
|
||||
<h3 className="text-xs font-bold text-discord-text-secondary uppercase mb-3">
|
||||
Voice Processing
|
||||
</h3>
|
||||
<p className="text-xs text-discord-text-muted mb-3">
|
||||
Echo Cancellation is automatically disabled while you screen share to prevent Chrome from ducking your mic volume.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
<div className="text-sm text-discord-text-primary">AI Noise Suppression</div>
|
||||
<div className="text-xs text-discord-text-muted">ML-based noise removal (RNNoise) — filters keyboard, fans, and background noise</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
|
||||
className={`relative w-10 h-5 rounded-full transition-colors ${rnnoiseEnabled ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
|
||||
>
|
||||
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${rnnoiseEnabled ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
@@ -143,19 +153,6 @@ export function UserSettingsModal() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
<div className="text-sm text-discord-text-primary">Noise Suppression</div>
|
||||
<div className="text-xs text-discord-text-muted">Filters background noise from your mic</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={toggleNoiseSuppression}
|
||||
className={`relative w-10 h-5 rounded-full transition-colors ${noiseSuppression ? 'bg-discord-green' : 'bg-discord-bg-tertiary'}`}
|
||||
>
|
||||
<div className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${noiseSuppression ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
<div className="text-sm text-discord-text-primary">Auto Gain Control</div>
|
||||
|
||||
@@ -15,10 +15,8 @@ export function VoiceControls() {
|
||||
const isScreenSharing = useVoiceStore((s) => s.isScreenSharing);
|
||||
const toggleCamera = useVoiceStore((s) => s.toggleCamera);
|
||||
const toggleScreenShare = useVoiceStore((s) => s.toggleScreenShare);
|
||||
const noiseSuppression = useVoiceStore((s) => s.noiseSuppression);
|
||||
const toggleNoiseSuppression = useVoiceStore((s) => s.toggleNoiseSuppression);
|
||||
const rnnoiseEnabled = useVoiceStore((s) => s.rnnoiseEnabled);
|
||||
const toggleRnnoise = useVoiceStore((s) => s.toggleRnnoise);
|
||||
const setRnnoiseEnabled = useVoiceStore((s) => s.setRnnoiseEnabled);
|
||||
const connectionError = useVoiceStore((s) => s.connectionError);
|
||||
const isLiveKitConnected = useVoiceStore((s) => s.isLiveKitConnected);
|
||||
const channels = useServerStore((s) => s.channels);
|
||||
@@ -56,9 +54,6 @@ export function VoiceControls() {
|
||||
console.error('[VoiceControls] Failed to toggle screen share:', err);
|
||||
}
|
||||
};
|
||||
const handleNoiseSuppression = () => {
|
||||
toggleNoiseSuppression();
|
||||
};
|
||||
const handleDisconnect = () => {
|
||||
wsSend({ type: 'voice_leave' });
|
||||
useVoiceStore.getState().leaveVoice();
|
||||
@@ -81,9 +76,7 @@ export function VoiceControls() {
|
||||
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
|
||||
: btnDefaultStyle}`, title: isScreenSharing ? 'Stop Sharing' : 'Share Screen', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M20 18C21.1 18 22 17.1 22 16V6C22 4.9 21.1 4 20 4H4C2.9 4 2 4.9 2 6V16C2 17.1 2.9 18 4 18H0V20H24V18H20ZM4 6H20V16H4V6Z" }), _jsx("path", { d: "M15 11L11 14V12H9V10H11V8L15 11Z" })] }) }), _jsx("button", { onClick: () => setShowVideoQuality(!showVideoQuality), className: `${btnBase} ${showVideoQuality
|
||||
? 'bg-[#111214] text-discord-blurple hover:bg-[#1a1b1e]'
|
||||
: btnDefaultStyle}`, title: "Video Quality", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M3 5v14h18V5H3zm16 12H5V7h14v10z" }), _jsx("path", { d: "M8 15l2.5-3.21L13 15l2-2.5L18 17H6z" })] }) }), _jsx("button", { onClick: handleNoiseSuppression, className: `${btnBase} ${noiseSuppression
|
||||
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
|
||||
: btnDefaultStyle}`, title: noiseSuppression ? 'Disable Browser Noise Suppression' : 'Enable Browser Noise Suppression', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M7 9v6h4l5 5V4l-5 5H7z" }), noiseSuppression ? (_jsxs(_Fragment, { children: [_jsx("path", { d: "M19 12c0-1.66-.68-3.16-1.76-4.24l-1.42 1.42C16.55 9.9 17 10.9 17 12c0 1.1-.45 2.1-1.18 2.82l1.42 1.42C18.32 15.16 19 13.66 19 12z" }), _jsx("path", { d: "M21 12c0-2.76-1.12-5.26-2.93-7.07l-1.42 1.42C18.2 7.9 19 9.85 19 12c0 2.15-.8 4.1-2.35 5.65l1.42 1.42C19.88 17.26 21 14.76 21 12z", opacity: "0.6" })] })) : (_jsx("line", { x1: "19", y1: "5", x2: "19", y2: "19", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", opacity: "0.4" }))] }) }), _jsx("button", { onClick: toggleRnnoise, className: `${btnBase} ${rnnoiseEnabled
|
||||
: btnDefaultStyle}`, title: "Video Quality", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M3 5v14h18V5H3zm16 12H5V7h14v10z" }), _jsx("path", { d: "M8 15l2.5-3.21L13 15l2-2.5L18 17H6z" })] }) }), _jsx("button", { onClick: () => setRnnoiseEnabled(!rnnoiseEnabled), className: `${btnBase} ${rnnoiseEnabled
|
||||
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
|
||||
: btnDefaultStyle}`, title: rnnoiseEnabled ? 'Disable AI Noise Suppression' : 'Enable AI Noise Suppression', children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z", opacity: rnnoiseEnabled ? 0.15 : 0.08 }), _jsx("path", { d: "M12 1a2 2 0 012 2v1a2 2 0 01-4 0V3a2 2 0 012-2z" }), _jsx("path", { d: "M12 7c-1.66 0-3 1.34-3 3v2c0 1.66 1.34 3 3 3s3-1.34 3-3v-2c0-1.66-1.34-3-3-3z" }), _jsx("path", { d: "M17 11v1c0 2.76-2.24 5-5 5s-5-2.24-5-5v-1H5v1c0 3.53 2.61 6.43 6 6.92V21h2v-2.08c3.39-.49 6-3.39 6-6.92v-1h-2z" }), rnnoiseEnabled ? (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "18", cy: "5", r: "1.2", fill: "currentColor" }), _jsx("circle", { cx: "20", cy: "8", r: "0.9", fill: "currentColor", opacity: "0.7" }), _jsx("circle", { cx: "6", cy: "5", r: "1.2", fill: "currentColor" }), _jsx("circle", { cx: "4", cy: "8", r: "0.9", fill: "currentColor", opacity: "0.7" })] })) : (_jsx("line", { x1: "4", y1: "4", x2: "20", y2: "20", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", opacity: "0.4" }))] }) }), _jsx(VideoQualityPopover, { open: showVideoQuality, onClose: () => setShowVideoQuality(false) })] })] }));
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@ export function VoiceControls() {
|
||||
const isScreenSharing = useVoiceStore((s) => s.isScreenSharing);
|
||||
const toggleCamera = useVoiceStore((s) => s.toggleCamera);
|
||||
const toggleScreenShare = useVoiceStore((s) => s.toggleScreenShare);
|
||||
const noiseSuppression = useVoiceStore((s) => s.noiseSuppression);
|
||||
const toggleNoiseSuppression = useVoiceStore((s) => s.toggleNoiseSuppression);
|
||||
const rnnoiseEnabled = useVoiceStore((s) => s.rnnoiseEnabled);
|
||||
const toggleRnnoise = useVoiceStore((s) => s.toggleRnnoise);
|
||||
const setRnnoiseEnabled = useVoiceStore((s) => s.setRnnoiseEnabled);
|
||||
const connectionError = useVoiceStore((s) => s.connectionError);
|
||||
const isLiveKitConnected = useVoiceStore((s) => s.isLiveKitConnected);
|
||||
const channels = useServerStore((s) => s.channels);
|
||||
@@ -58,11 +56,6 @@ export function VoiceControls() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleNoiseSuppression = () => {
|
||||
// Store toggle triggers syncMic → AudioManager re-acquires stream with correct constraints
|
||||
toggleNoiseSuppression();
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
wsSend({ type: 'voice_leave' });
|
||||
useVoiceStore.getState().leaveVoice();
|
||||
@@ -174,32 +167,9 @@ export function VoiceControls() {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Browser Noise Suppression */}
|
||||
<button
|
||||
onClick={handleNoiseSuppression}
|
||||
className={`${btnBase} ${
|
||||
noiseSuppression
|
||||
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
|
||||
: btnDefaultStyle
|
||||
}`}
|
||||
title={noiseSuppression ? 'Disable Browser Noise Suppression' : 'Enable Browser Noise Suppression'}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7 9v6h4l5 5V4l-5 5H7z" />
|
||||
{noiseSuppression ? (
|
||||
<>
|
||||
<path d="M19 12c0-1.66-.68-3.16-1.76-4.24l-1.42 1.42C16.55 9.9 17 10.9 17 12c0 1.1-.45 2.1-1.18 2.82l1.42 1.42C18.32 15.16 19 13.66 19 12z" />
|
||||
<path d="M21 12c0-2.76-1.12-5.26-2.93-7.07l-1.42 1.42C18.2 7.9 19 9.85 19 12c0 2.15-.8 4.1-2.35 5.65l1.42 1.42C19.88 17.26 21 14.76 21 12z" opacity="0.6" />
|
||||
</>
|
||||
) : (
|
||||
<line x1="19" y1="5" x2="19" y2="19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" opacity="0.4" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* AI Noise Suppression (RNNoise) */}
|
||||
<button
|
||||
onClick={toggleRnnoise}
|
||||
onClick={() => setRnnoiseEnabled(!rnnoiseEnabled)}
|
||||
className={`${btnBase} ${
|
||||
rnnoiseEnabled
|
||||
? 'bg-[#111214] text-discord-green hover:bg-[#1a1b1e]'
|
||||
|
||||
@@ -130,11 +130,10 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
autoGainControl: false,
|
||||
rnnoiseEnabled: false,
|
||||
toggleNoiseSuppression: () => set((state) => ({ noiseSuppression: !state.noiseSuppression })),
|
||||
rnnoiseEnabled: true,
|
||||
setEchoCancellation: (enabled) => set({ echoCancellation: enabled }),
|
||||
setAutoGainControl: (enabled) => set({ autoGainControl: enabled }),
|
||||
toggleRnnoise: () => set((state) => ({ rnnoiseEnabled: !state.rnnoiseEnabled })),
|
||||
setRnnoiseEnabled: (enabled) => set({ rnnoiseEnabled: enabled }),
|
||||
deafenedUserIds: new Set(),
|
||||
setUserDeafened: (userId, deafened) => {
|
||||
set((state) => {
|
||||
@@ -206,7 +205,7 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
}),
|
||||
}), {
|
||||
name: 'opencord-voice-settings',
|
||||
version: 3,
|
||||
version: 4,
|
||||
migrate: (persistedState, version) => {
|
||||
if (version === 0) {
|
||||
persistedState.streamAttenuationEnabled = false;
|
||||
@@ -215,13 +214,16 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
persistedState.echoCancellation = true;
|
||||
persistedState.autoGainControl = false;
|
||||
}
|
||||
if (version < 3) {
|
||||
persistedState.rnnoiseEnabled = false;
|
||||
if (version < 4) {
|
||||
// v4: RNNoise on by default, browser NS is no longer user-configurable
|
||||
persistedState.rnnoiseEnabled = true;
|
||||
persistedState.noiseSuppression = true;
|
||||
}
|
||||
return persistedState;
|
||||
},
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
// Only persist these keys. Maps and Sets are complex to serialize.
|
||||
// noiseSuppression intentionally excluded — always true, managed by AudioManager.
|
||||
partialize: (state) => ({
|
||||
currentVoiceChannelId: state.currentVoiceChannelId,
|
||||
isMuted: state.isMuted,
|
||||
@@ -231,7 +233,6 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
inputDeviceId: state.inputDeviceId,
|
||||
outputDeviceId: state.outputDeviceId,
|
||||
videoQuality: state.videoQuality,
|
||||
noiseSuppression: state.noiseSuppression,
|
||||
echoCancellation: state.echoCancellation,
|
||||
autoGainControl: state.autoGainControl,
|
||||
rnnoiseEnabled: state.rnnoiseEnabled,
|
||||
|
||||
@@ -65,10 +65,9 @@ interface VoiceState {
|
||||
echoCancellation: boolean;
|
||||
autoGainControl: boolean;
|
||||
rnnoiseEnabled: boolean;
|
||||
toggleNoiseSuppression: () => void;
|
||||
setEchoCancellation: (enabled: boolean) => void;
|
||||
setAutoGainControl: (enabled: boolean) => void;
|
||||
toggleRnnoise: () => void;
|
||||
setRnnoiseEnabled: (enabled: boolean) => void;
|
||||
deafenedUserIds: Set<string>;
|
||||
setUserDeafened: (userId: string, deafened: boolean) => void;
|
||||
// WebSocket-based voice user status (visible without joining LiveKit)
|
||||
@@ -226,11 +225,10 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
autoGainControl: false,
|
||||
rnnoiseEnabled: false,
|
||||
toggleNoiseSuppression: () => set((state) => ({ noiseSuppression: !state.noiseSuppression })),
|
||||
rnnoiseEnabled: true,
|
||||
setEchoCancellation: (enabled) => set({ echoCancellation: enabled }),
|
||||
setAutoGainControl: (enabled) => set({ autoGainControl: enabled }),
|
||||
toggleRnnoise: () => set((state) => ({ rnnoiseEnabled: !state.rnnoiseEnabled })),
|
||||
setRnnoiseEnabled: (enabled) => set({ rnnoiseEnabled: enabled }),
|
||||
deafenedUserIds: new Set(),
|
||||
setUserDeafened: (userId, deafened) => {
|
||||
set((state) => {
|
||||
@@ -305,7 +303,7 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
}),
|
||||
{
|
||||
name: 'opencord-voice-settings',
|
||||
version: 3,
|
||||
version: 4,
|
||||
migrate: (persistedState: any, version: number) => {
|
||||
if (version === 0) {
|
||||
persistedState.streamAttenuationEnabled = false;
|
||||
@@ -314,13 +312,18 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
persistedState.echoCancellation = true;
|
||||
persistedState.autoGainControl = false;
|
||||
}
|
||||
if (version < 3) {
|
||||
persistedState.rnnoiseEnabled = false;
|
||||
if (version < 4) {
|
||||
// v4: RNNoise on by default, browser NS is no longer user-configurable
|
||||
// (AudioManager uses it as automatic fallback when RNNoise is off)
|
||||
persistedState.rnnoiseEnabled = true;
|
||||
persistedState.noiseSuppression = true;
|
||||
}
|
||||
return persistedState;
|
||||
},
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
// Only persist these keys. Maps and Sets are complex to serialize.
|
||||
// noiseSuppression is intentionally excluded — always true internally,
|
||||
// managed automatically by AudioManager based on RNNoise state.
|
||||
partialize: (state) => ({
|
||||
currentVoiceChannelId: state.currentVoiceChannelId,
|
||||
isMuted: state.isMuted,
|
||||
@@ -330,7 +333,6 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
inputDeviceId: state.inputDeviceId,
|
||||
outputDeviceId: state.outputDeviceId,
|
||||
videoQuality: state.videoQuality,
|
||||
noiseSuppression: state.noiseSuppression,
|
||||
echoCancellation: state.echoCancellation,
|
||||
autoGainControl: state.autoGainControl,
|
||||
rnnoiseEnabled: state.rnnoiseEnabled,
|
||||
|
||||
Reference in New Issue
Block a user