feat: Add Mic Sensitivity Noise Gate slider and core audio gating

This commit is contained in:
2026-05-27 16:11:37 -04:00
parent dac53fc2ad
commit 529367ee0c
6 changed files with 187 additions and 1 deletions
+12
View File
@@ -39,6 +39,7 @@ pub enum AppMessage {
PeerVolumeChanged(EndpointId, f32),
InputDeviceSelected(AudioDevice),
OutputDeviceSelected(AudioDevice),
NoiseGateChanged(f32),
EventOccurred(Event),
NavigateToSettings,
NavigateBack,
@@ -89,6 +90,7 @@ impl Default for AppState {
let _ = UI_RX.set(Mutex::new(Some(ui_rx)));
let config = AppConfig::load();
let _ = controller.send(CoreCommand::SetNoiseGateThreshold(config.noise_gate_threshold));
let all_devices = enumerate_audio_devices();
let input_devices: Vec<_> = all_devices.iter().filter(|d| d.is_input).cloned().collect();
let output_devices: Vec<_> = all_devices.iter().filter(|d| !d.is_input).cloned().collect();
@@ -251,6 +253,11 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
state.config.save();
state.selected_output = Some(dev);
}
AppMessage::NoiseGateChanged(val) => {
state.config.noise_gate_threshold = val;
state.config.save();
let _ = state.controller.send(CoreCommand::SetNoiseGateThreshold(val));
}
AppMessage::EventOccurred(Event::Keyboard(keyboard::Event::KeyPressed { key, .. })) => {
if state.is_setting_hotkey {
state.ptt_hotkey = key.clone();
@@ -384,6 +391,11 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
).width(iced::Length::Fixed(160.0))
].spacing(4),
].spacing(10),
vertical_space(20.0),
column![
text(format!("Mic Sensitivity (Noise Gate): {:.1}%", state.config.noise_gate_threshold * 100.0)).size(14).color(color_subtext),
slider(0.0..=0.1, state.config.noise_gate_threshold, AppMessage::NoiseGateChanged)
].spacing(10).width(iced::Length::Fixed(320.0)),
vertical_space(30.0),
button(
text("Back")
+12 -1
View File
@@ -2,10 +2,21 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub input_device: String,
pub output_device: String,
pub noise_gate_threshold: f32,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
input_device: "".to_string(),
output_device: "".to_string(),
noise_gate_threshold: 0.01,
}
}
}
impl AppConfig {
+1
View File
@@ -10,6 +10,7 @@ pub enum CoreCommand {
SetPttMode(bool),
SetPttActive(bool),
SetPeerVolume(EndpointId, f32),
SetNoiseGateThreshold(f32),
}
#[derive(Debug, Clone)]
+21
View File
@@ -95,6 +95,7 @@ async fn run_core_loop(
let is_deafened = Arc::new(AtomicBool::new(false));
let ptt_mode = Arc::new(AtomicBool::new(false));
let ptt_active = Arc::new(AtomicBool::new(false));
let noise_gate_threshold = Arc::new(std::sync::atomic::AtomicU32::new(0.01f32.to_bits()));
let peer_volumes = Arc::new(Mutex::new(HashMap::<EndpointId, f32>::new()));
let mut current_name = "Anonymous".to_string();
@@ -194,6 +195,7 @@ async fn run_core_loop(
let is_muted_clone = is_muted.clone();
let ptt_mode_clone = ptt_mode.clone();
let ptt_active_clone = ptt_active.clone();
let noise_gate_threshold_clone = noise_gate_threshold.clone();
let transport_clone = transport.clone();
let room_state_clone = room_state.clone();
let tokio_handle = tokio::runtime::Handle::current();
@@ -215,6 +217,21 @@ async fn run_core_loop(
if ptt_mode_clone.load(Ordering::Relaxed) && !ptt_active_clone.load(Ordering::Relaxed) {
continue;
}
let ng_bits = noise_gate_threshold_clone.load(Ordering::Relaxed);
let ng_thresh = f32::from_bits(ng_bits);
if ng_thresh > 0.0001 {
let mut sum_sq = 0.0f32;
for &sample in &pcm {
let normalized = (sample as f32) / 32768.0;
sum_sq += normalized * normalized;
}
let rms = (sum_sq / pcm.len() as f32).sqrt();
if rms < ng_thresh {
continue;
}
}
if let Ok(encoded) = encoder.encode(&pcm) {
let bytes = bytes::Bytes::from(encoded);
let active = room_state_clone.active_peers();
@@ -428,6 +445,10 @@ async fn run_core_loop(
let mut guard = peer_volumes.lock().await;
guard.insert(peer_id, vol);
}
CoreCommand::SetNoiseGateThreshold(threshold) => {
noise_gate_threshold.store(threshold.to_bits(), Ordering::Relaxed);
}
}
}