diff --git a/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx b/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx index 8c3db784b..27c737e18 100644 --- a/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx +++ b/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx @@ -445,6 +445,19 @@ export const VoiceTab: React.FC = observer(({voiceSettings, autoR data-flx="user.voice-tab.render-auto-gain-control-switch.switch.update-auto-gain-control" /> ); + const renderStereoMicrophoneSwitch = (dataFlx: string) => + stereoMicrophoneAvailable && ( + { + VoiceSettings.stereoMicrophone = value; + }} + ariaLabel={i18n._(STEREO_MICROPHONE_DESCRIPTOR)} + data-flx={dataFlx} + /> + ); const renderCustomProfile = () => (
{renderPttControls()} @@ -496,18 +509,7 @@ export const VoiceTab: React.FC = observer(({voiceSettings, autoR dataFlx="user.voice-tab.render-custom-profile.select.set-noise-suppression-method" data-flx="user.user-voice-tab.render-custom-profile.compact-combobox-row.set-noise-suppression-method" /> - {stereoMicrophoneAvailable && ( - { - VoiceSettings.stereoMicrophone = value; - }} - ariaLabel={i18n._(STEREO_MICROPHONE_DESCRIPTOR)} - data-flx="user.voice-tab.render-custom-profile.switch.set-stereo-microphone" - /> - )} + {renderStereoMicrophoneSwitch('user.voice-tab.render-custom-profile.switch.set-stereo-microphone')} = observer(({voiceSettings, autoR {renderAutoGainControlSwitch()}
)} + {voiceProcessingMode === 'studio' && stereoMicrophoneAvailable && ( +
+ {renderStereoMicrophoneSwitch('user.voice-tab.studio-profile.switch.set-stereo-microphone')} +
+ )} {voiceProcessingMode === 'studio' && pttCombo?.key && isPushToTalk && ( Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it. diff --git a/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx b/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx index 3edd76d77..24aed9041 100644 --- a/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx +++ b/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx @@ -98,7 +98,7 @@ import EntranceSoundLibrary from '@app/features/voice/state/EntranceSoundLibrary import LocalVoiceState from '@app/features/voice/state/LocalVoiceState'; import ParticipantVolume from '@app/features/voice/state/ParticipantVolume'; import VoiceSettings from '@app/features/voice/state/VoiceSettings'; -import {buildMicrophonePublishOptions} from '@app/features/voice/utils/AudioPublishOptions'; +import {buildMicrophonePublishOptions, sendsStereoMicrophone} from '@app/features/voice/utils/AudioPublishOptions'; import { buildCameraPublishOptions, findVideoPublishCodecPolicyViolation, @@ -605,24 +605,31 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store { return applyNoiseSuppressionOverride(profile, readEffectiveNoiseSuppression(MICROPHONE_CAPTURE_SAMPLE_RATE)); } - private getMicrophoneCaptureOptions(options: VoiceEngineV2MicrophoneOptions = {}): AudioCaptureOptions { + private resolveMicrophoneChannelBitrate(channelId: string | null): number { + const channel = channelId ? Channels.getChannel(channelId) : null; + const guild = channel?.guildId ? Guilds.getGuild(channel.guildId) : null; + return resolveVoiceChannelBitrate(channel?.bitrate, guild?.features); + } + + private getMicrophoneCaptureOptions( + options: VoiceEngineV2MicrophoneOptions = {}, + channelId: string | null = this.getActiveChannelId(), + ): AudioCaptureOptions { const profile = this.resolveActiveMicrophoneProfile(); + const stereo = sendsStereoMicrophone(this.resolveMicrophoneChannelBitrate(channelId), profile.stereoCapture); return { deviceId: options.deviceId ?? this.resolveInputDeviceId(), echoCancellation: options.echoCancellation ?? profile.echoCancellation, noiseSuppression: options.noiseSuppression ?? profile.browserNoiseSuppression, autoGainControl: options.autoGainControl ?? profile.autoGainControl, voiceIsolation: false, - ...(profile.stereoCapture ? {channelCount: {ideal: 2}} : {}), + ...(stereo ? {channelCount: {ideal: 2}} : {}), }; } private getMicrophonePublishOptions(channelId: string | null): TrackPublishOptions | undefined { - const channel = channelId ? Channels.getChannel(channelId) : null; - const guild = channel?.guildId ? Guilds.getGuild(channel.guildId) : null; - const channelBitrate = resolveVoiceChannelBitrate(channel?.bitrate, guild?.features); const profile = this.resolveActiveMicrophoneProfile(); - return buildMicrophonePublishOptions(channelBitrate, profile.stereoCapture); + return buildMicrophonePublishOptions(this.resolveMicrophoneChannelBitrate(channelId), profile.stereoCapture); } async refreshMicrophonePublishSettings(room: Room | null, channelId: string | null): Promise { @@ -902,7 +909,7 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store { assertObjectLike(state, 'publishMicrophoneAudioTrack.state'); await ctx.room.localParticipant.setMicrophoneEnabled( true, - this.getMicrophoneCaptureOptions(ctx.options), + this.getMicrophoneCaptureOptions(ctx.options, ctx.channelId), this.getMicrophonePublishOptions(ctx.channelId), ); state.microphoneWasPublished = true; diff --git a/fluxer_app/src/features/voice/utils/AudioPublishOptions.ts b/fluxer_app/src/features/voice/utils/AudioPublishOptions.ts index 9fca376b1..f467855d7 100644 --- a/fluxer_app/src/features/voice/utils/AudioPublishOptions.ts +++ b/fluxer_app/src/features/voice/utils/AudioPublishOptions.ts @@ -16,6 +16,11 @@ export function normaliseAudioBitrateBps(value: number | null | undefined): numb return Math.min(Math.max(bitsPerSecond, VOICE_CHANNEL_MIN_AUDIO_BITRATE_BPS), OPUS_MAX_AUDIO_BITRATE_BPS); } +export function sendsStereoMicrophone(channelBitrate: number | null | undefined, stereoCapture: boolean): boolean { + const maxBitrate = normaliseAudioBitrateBps(channelBitrate); + return stereoCapture && maxBitrate !== undefined && maxBitrate >= STEREO_VOICE_MIN_AUDIO_BITRATE_BPS; +} + export function buildMicrophonePublishOptions( channelBitrate: number | null | undefined, stereoCapture = false, @@ -29,7 +34,7 @@ export function buildMicrophonePublishOptions( }, dtx: false, red: true, - forceStereo: stereoCapture && maxBitrate >= STEREO_VOICE_MIN_AUDIO_BITRATE_BPS ? undefined : false, + forceStereo: sendsStereoMicrophone(maxBitrate, stereoCapture) ? undefined : false, }; } diff --git a/fluxer_app/src/features/voice/utils/noise_suppression/NoiseSuppressionSelection.ts b/fluxer_app/src/features/voice/utils/noise_suppression/NoiseSuppressionSelection.ts index 5a0d58a12..a9e612e6f 100644 --- a/fluxer_app/src/features/voice/utils/noise_suppression/NoiseSuppressionSelection.ts +++ b/fluxer_app/src/features/voice/utils/noise_suppression/NoiseSuppressionSelection.ts @@ -84,9 +84,8 @@ export function supportsStereoCapture(profile: ResolvedVoiceProcessing): boolean export function resolveStereoCapture(effective: EffectiveNoiseSuppression, profile: ResolvedVoiceProcessing): boolean { if (!supportsStereoCapture(profile)) return false; - if (profile.mode === 'studio') return true; - if (profile.mode === 'custom') return effective.stereoPreferred; - return false; + if (profile.mode === 'voice') return false; + return effective.stereoPreferred; } export function applyNoiseSuppressionOverride(