feat: RNNoise WASM AudioWorklet for ML-based noise suppression (Phase 3)
Inject @sapphi-red/web-noise-suppressor into AudioManager's input pipeline as a toggleable AudioWorkletNode. The worklet is loaded lazily on first enable, then kept alive — toggling bypasses by rewiring the graph without destroying the WASM instance. Browser NS is forced off when RNNoise is active to avoid double-processing. InputGain forced to stereo up-mix to prevent mono-left-only output from the worklet. Also wires Phase 2 output device routing (setSinkId) into AppLayout/ChannelSidebar.
This commit is contained in:
@@ -119,10 +119,7 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
AudioManager.getInstance().setInputVolume(volume);
|
||||
},
|
||||
setOutputVolume: (volume) => set({ outputVolume: volume }),
|
||||
setInputDevice: async (deviceId) => {
|
||||
set({ inputDeviceId: deviceId });
|
||||
await AudioManager.getInstance().setInputDevice(deviceId);
|
||||
},
|
||||
setInputDevice: (deviceId) => set({ inputDeviceId: deviceId }),
|
||||
setOutputDevice: (deviceId) => set({ outputDeviceId: deviceId }),
|
||||
toggleMic: () => set((state) => ({ isMuted: !state.isMuted })),
|
||||
toggleDeafen: () => set((state) => ({ isDeafened: !state.isDeafened })),
|
||||
@@ -133,9 +130,11 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
autoGainControl: false,
|
||||
rnnoiseEnabled: false,
|
||||
toggleNoiseSuppression: () => set((state) => ({ noiseSuppression: !state.noiseSuppression })),
|
||||
setEchoCancellation: (enabled) => set({ echoCancellation: enabled }),
|
||||
setAutoGainControl: (enabled) => set({ autoGainControl: enabled }),
|
||||
toggleRnnoise: () => set((state) => ({ rnnoiseEnabled: !state.rnnoiseEnabled })),
|
||||
deafenedUserIds: new Set(),
|
||||
setUserDeafened: (userId, deafened) => {
|
||||
set((state) => {
|
||||
@@ -207,7 +206,7 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
}),
|
||||
}), {
|
||||
name: 'opencord-voice-settings',
|
||||
version: 2,
|
||||
version: 3,
|
||||
migrate: (persistedState, version) => {
|
||||
if (version === 0) {
|
||||
persistedState.streamAttenuationEnabled = false;
|
||||
@@ -216,6 +215,9 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
persistedState.echoCancellation = true;
|
||||
persistedState.autoGainControl = false;
|
||||
}
|
||||
if (version < 3) {
|
||||
persistedState.rnnoiseEnabled = false;
|
||||
}
|
||||
return persistedState;
|
||||
},
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
@@ -232,6 +234,7 @@ export const useVoiceStore = create()(persist((set, get) => ({
|
||||
noiseSuppression: state.noiseSuppression,
|
||||
echoCancellation: state.echoCancellation,
|
||||
autoGainControl: state.autoGainControl,
|
||||
rnnoiseEnabled: state.rnnoiseEnabled,
|
||||
streamAttenuationEnabled: state.streamAttenuationEnabled,
|
||||
streamAttenuationStrength: state.streamAttenuationStrength,
|
||||
}),
|
||||
|
||||
@@ -53,7 +53,7 @@ interface VoiceState {
|
||||
setIsLiveKitConnected: (connected: boolean) => void;
|
||||
setInputVolume: (volume: number) => void;
|
||||
setOutputVolume: (volume: number) => void;
|
||||
setInputDevice: (deviceId: string) => Promise<void>;
|
||||
setInputDevice: (deviceId: string) => void;
|
||||
setOutputDevice: (deviceId: string) => void;
|
||||
toggleMic: () => void;
|
||||
toggleCamera: () => void;
|
||||
@@ -64,9 +64,11 @@ interface VoiceState {
|
||||
noiseSuppression: boolean;
|
||||
echoCancellation: boolean;
|
||||
autoGainControl: boolean;
|
||||
rnnoiseEnabled: boolean;
|
||||
toggleNoiseSuppression: () => void;
|
||||
setEchoCancellation: (enabled: boolean) => void;
|
||||
setAutoGainControl: (enabled: boolean) => void;
|
||||
toggleRnnoise: () => void;
|
||||
deafenedUserIds: Set<string>;
|
||||
setUserDeafened: (userId: string, deafened: boolean) => void;
|
||||
// WebSocket-based voice user status (visible without joining LiveKit)
|
||||
@@ -210,10 +212,7 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
},
|
||||
setOutputVolume: (volume) => set({ outputVolume: volume }),
|
||||
|
||||
setInputDevice: async (deviceId) => {
|
||||
set({ inputDeviceId: deviceId });
|
||||
await AudioManager.getInstance().setInputDevice(deviceId);
|
||||
},
|
||||
setInputDevice: (deviceId) => set({ inputDeviceId: deviceId }),
|
||||
|
||||
setOutputDevice: (deviceId) => set({ outputDeviceId: deviceId }),
|
||||
|
||||
@@ -227,9 +226,11 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
autoGainControl: false,
|
||||
rnnoiseEnabled: false,
|
||||
toggleNoiseSuppression: () => set((state) => ({ noiseSuppression: !state.noiseSuppression })),
|
||||
setEchoCancellation: (enabled) => set({ echoCancellation: enabled }),
|
||||
setAutoGainControl: (enabled) => set({ autoGainControl: enabled }),
|
||||
toggleRnnoise: () => set((state) => ({ rnnoiseEnabled: !state.rnnoiseEnabled })),
|
||||
deafenedUserIds: new Set(),
|
||||
setUserDeafened: (userId, deafened) => {
|
||||
set((state) => {
|
||||
@@ -304,7 +305,7 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
}),
|
||||
{
|
||||
name: 'opencord-voice-settings',
|
||||
version: 2,
|
||||
version: 3,
|
||||
migrate: (persistedState: any, version: number) => {
|
||||
if (version === 0) {
|
||||
persistedState.streamAttenuationEnabled = false;
|
||||
@@ -313,6 +314,9 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
persistedState.echoCancellation = true;
|
||||
persistedState.autoGainControl = false;
|
||||
}
|
||||
if (version < 3) {
|
||||
persistedState.rnnoiseEnabled = false;
|
||||
}
|
||||
return persistedState;
|
||||
},
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
@@ -329,6 +333,7 @@ export const useVoiceStore = create<VoiceState>()(
|
||||
noiseSuppression: state.noiseSuppression,
|
||||
echoCancellation: state.echoCancellation,
|
||||
autoGainControl: state.autoGainControl,
|
||||
rnnoiseEnabled: state.rnnoiseEnabled,
|
||||
streamAttenuationEnabled: state.streamAttenuationEnabled,
|
||||
streamAttenuationStrength: state.streamAttenuationStrength,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user