feat: add advanced Sonora voice controls
This commit is contained in:
@@ -74,6 +74,7 @@ struct App {
|
||||
vad: Arc<Mutex<audio::VoiceActivation>>,
|
||||
noise_reducer: Arc<Mutex<noise_cancel::NoiseReducer>>,
|
||||
nc_method: noise_cancel::NoiseCancelMethod,
|
||||
sonora_settings: noise_cancel::SonoraSettings,
|
||||
imported_identity: Option<Identity>,
|
||||
identity_string_input: String,
|
||||
identity_file_input: String,
|
||||
@@ -92,6 +93,7 @@ impl App {
|
||||
let appearance_mode = settings.appearance_mode;
|
||||
let talk_mode = settings.talk_mode;
|
||||
let nc_method = settings.noise_cancel_method;
|
||||
let sonora_settings = settings.sonora_settings;
|
||||
let selected_output_device = settings.selected_output_device;
|
||||
let selected_input_device = settings.selected_input_device;
|
||||
|
||||
@@ -130,8 +132,12 @@ impl App {
|
||||
continuous_active: false,
|
||||
talk_mode,
|
||||
vad: Arc::new(Mutex::new(audio::VoiceActivation::new(0.005))),
|
||||
noise_reducer: Arc::new(Mutex::new(noise_cancel::NoiseReducer::new(nc_method))),
|
||||
noise_reducer: Arc::new(Mutex::new(noise_cancel::NoiseReducer::new(
|
||||
nc_method,
|
||||
sonora_settings,
|
||||
))),
|
||||
nc_method,
|
||||
sonora_settings,
|
||||
imported_identity: None,
|
||||
identity_string_input: String::new(),
|
||||
identity_file_input: String::new(),
|
||||
@@ -160,6 +166,7 @@ impl App {
|
||||
selected_input_device: self.selected_input_device.clone(),
|
||||
talk_mode: self.talk_mode,
|
||||
noise_cancel_method: self.nc_method,
|
||||
sonora_settings: self.sonora_settings,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -594,6 +601,36 @@ impl App {
|
||||
self.save_settings();
|
||||
Task::none()
|
||||
}
|
||||
Message::SetSonoraNoiseSuppressionLevel(level) => {
|
||||
self.sonora_settings.noise_suppression_level = level;
|
||||
let settings = self.sonora_settings;
|
||||
let nc = self.noise_reducer.clone();
|
||||
tokio::spawn(async move {
|
||||
nc.lock().await.set_sonora_settings(settings);
|
||||
});
|
||||
self.save_settings();
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleSonoraAgc => {
|
||||
self.sonora_settings.agc_enabled = !self.sonora_settings.agc_enabled;
|
||||
let settings = self.sonora_settings;
|
||||
let nc = self.noise_reducer.clone();
|
||||
tokio::spawn(async move {
|
||||
nc.lock().await.set_sonora_settings(settings);
|
||||
});
|
||||
self.save_settings();
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleSonoraHighPass => {
|
||||
self.sonora_settings.high_pass_enabled = !self.sonora_settings.high_pass_enabled;
|
||||
let settings = self.sonora_settings;
|
||||
let nc = self.noise_reducer.clone();
|
||||
tokio::spawn(async move {
|
||||
nc.lock().await.set_sonora_settings(settings);
|
||||
});
|
||||
self.save_settings();
|
||||
Task::none()
|
||||
}
|
||||
Message::ToggleMicMute => {
|
||||
self.mic_muted = !self.mic_muted;
|
||||
let h = self.handle.clone();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sonora::config::{NoiseSuppression, NoiseSuppressionLevel};
|
||||
use sonora::config::{
|
||||
AdaptiveDigital, GainController2, HighPassFilter, NoiseSuppression,
|
||||
NoiseSuppressionLevel,
|
||||
};
|
||||
use sonora::{AudioProcessing, Config, StreamConfig};
|
||||
|
||||
const SONORA_FRAME_SIZE: usize = 480;
|
||||
@@ -25,8 +28,58 @@ impl NoiseCancelMethod {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SonoraSettings {
|
||||
pub noise_suppression_level: NoiseSuppressionLevelSetting,
|
||||
pub agc_enabled: bool,
|
||||
pub high_pass_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for SonoraSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
noise_suppression_level: NoiseSuppressionLevelSetting::Moderate,
|
||||
agc_enabled: true,
|
||||
high_pass_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum NoiseSuppressionLevelSetting {
|
||||
Low,
|
||||
Moderate,
|
||||
High,
|
||||
VeryHigh,
|
||||
}
|
||||
|
||||
impl NoiseSuppressionLevelSetting {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Low => "Low",
|
||||
Self::Moderate => "Moderate",
|
||||
Self::High => "High",
|
||||
Self::VeryHigh => "Very High",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> &'static [NoiseSuppressionLevelSetting] {
|
||||
&[Self::Low, Self::Moderate, Self::High, Self::VeryHigh]
|
||||
}
|
||||
|
||||
fn to_sonora(self) -> NoiseSuppressionLevel {
|
||||
match self {
|
||||
Self::Low => NoiseSuppressionLevel::Low,
|
||||
Self::Moderate => NoiseSuppressionLevel::Moderate,
|
||||
Self::High => NoiseSuppressionLevel::High,
|
||||
Self::VeryHigh => NoiseSuppressionLevel::VeryHigh,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoiseReducer {
|
||||
method: NoiseCancelMethod,
|
||||
sonora_settings: SonoraSettings,
|
||||
nnnoiseless: Option<Box<nnnoiseless::DenoiseState<'static>>>,
|
||||
sonora: Option<SonoraNoiseReducer>,
|
||||
residual_buf: Vec<f32>,
|
||||
@@ -39,13 +92,18 @@ struct SonoraNoiseReducer {
|
||||
}
|
||||
|
||||
impl SonoraNoiseReducer {
|
||||
fn new() -> Self {
|
||||
fn new(settings: SonoraSettings) -> Self {
|
||||
let stream = StreamConfig::new(48_000, 1);
|
||||
let config = Config {
|
||||
high_pass_filter: settings.high_pass_enabled.then(HighPassFilter::default),
|
||||
noise_suppression: Some(NoiseSuppression {
|
||||
level: NoiseSuppressionLevel::Moderate,
|
||||
level: settings.noise_suppression_level.to_sonora(),
|
||||
..NoiseSuppression::default()
|
||||
}),
|
||||
gain_controller2: settings.agc_enabled.then(|| GainController2 {
|
||||
adaptive_digital: Some(AdaptiveDigital::default()),
|
||||
..GainController2::default()
|
||||
}),
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
@@ -73,9 +131,10 @@ impl SonoraNoiseReducer {
|
||||
}
|
||||
|
||||
impl NoiseReducer {
|
||||
pub fn new(method: NoiseCancelMethod) -> Self {
|
||||
pub fn new(method: NoiseCancelMethod, sonora_settings: SonoraSettings) -> Self {
|
||||
let mut this = Self {
|
||||
method,
|
||||
sonora_settings,
|
||||
nnnoiseless: None,
|
||||
sonora: None,
|
||||
residual_buf: Vec::new(),
|
||||
@@ -90,7 +149,7 @@ impl NoiseReducer {
|
||||
_ => None,
|
||||
};
|
||||
self.sonora = match self.method {
|
||||
NoiseCancelMethod::Sonora => Some(SonoraNoiseReducer::new()),
|
||||
NoiseCancelMethod::Sonora => Some(SonoraNoiseReducer::new(self.sonora_settings)),
|
||||
_ => None,
|
||||
};
|
||||
self.residual_buf.clear();
|
||||
@@ -101,6 +160,13 @@ impl NoiseReducer {
|
||||
self.init_method();
|
||||
}
|
||||
|
||||
pub fn set_sonora_settings(&mut self, settings: SonoraSettings) {
|
||||
self.sonora_settings = settings;
|
||||
if self.method == NoiseCancelMethod::Sonora {
|
||||
self.init_method();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, _samples: &mut [f32]) {
|
||||
match self.method {
|
||||
NoiseCancelMethod::None => {}
|
||||
|
||||
@@ -41,6 +41,9 @@ pub(crate) enum Message {
|
||||
PttReleased,
|
||||
ToggleTalkMode,
|
||||
SetNoiseCancelMethod(noise_cancel::NoiseCancelMethod),
|
||||
SetSonoraNoiseSuppressionLevel(noise_cancel::NoiseSuppressionLevelSetting),
|
||||
ToggleSonoraAgc,
|
||||
ToggleSonoraHighPass,
|
||||
StartContinuous,
|
||||
StopContinuous,
|
||||
MicSamples(Vec<f32>),
|
||||
@@ -135,6 +138,7 @@ pub(crate) struct AppSettings {
|
||||
pub(crate) selected_input_device: String,
|
||||
pub(crate) talk_mode: audio::TalkMode,
|
||||
pub(crate) noise_cancel_method: noise_cancel::NoiseCancelMethod,
|
||||
pub(crate) sonora_settings: noise_cancel::SonoraSettings,
|
||||
}
|
||||
|
||||
impl Default for AppSettings {
|
||||
@@ -145,6 +149,7 @@ impl Default for AppSettings {
|
||||
selected_input_device: String::new(),
|
||||
talk_mode: audio::TalkMode::PushToTalk,
|
||||
noise_cancel_method: noise_cancel::NoiseCancelMethod::None,
|
||||
sonora_settings: noise_cancel::SonoraSettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +649,7 @@ impl App {
|
||||
};
|
||||
|
||||
let nc_label = self.nc_method.label();
|
||||
let sonora_noise_label = self.sonora_settings.noise_suppression_level.label();
|
||||
|
||||
let voice_col = column![
|
||||
text("Voice Activation Mode").size(13),
|
||||
@@ -687,6 +688,64 @@ impl App {
|
||||
},
|
||||
);
|
||||
|
||||
let voice_col = if self.nc_method == noise_cancel::NoiseCancelMethod::Sonora {
|
||||
let voice_col = voice_col
|
||||
.push(vertical_space().height(8))
|
||||
.push(text("Sonora Noise Suppression").size(11).style(text::secondary))
|
||||
.push(text(sonora_noise_label).size(12).style(text::secondary));
|
||||
|
||||
let voice_col = noise_cancel::NoiseSuppressionLevelSetting::all()
|
||||
.iter()
|
||||
.copied()
|
||||
.fold(voice_col, |column, level| {
|
||||
column.push(
|
||||
button(text(level.label()).size(11))
|
||||
.padding([4, 12])
|
||||
.style(if self.sonora_settings.noise_suppression_level == level {
|
||||
theme::primary_button
|
||||
} else {
|
||||
theme::secondary_button
|
||||
})
|
||||
.on_press(Message::SetSonoraNoiseSuppressionLevel(level)),
|
||||
)
|
||||
});
|
||||
|
||||
voice_col
|
||||
.push(vertical_space().height(8))
|
||||
.push(
|
||||
button(text(if self.sonora_settings.agc_enabled {
|
||||
"AGC2: On"
|
||||
} else {
|
||||
"AGC2: Off"
|
||||
})
|
||||
.size(11))
|
||||
.padding([4, 12])
|
||||
.style(if self.sonora_settings.agc_enabled {
|
||||
theme::primary_button
|
||||
} else {
|
||||
theme::secondary_button
|
||||
})
|
||||
.on_press(Message::ToggleSonoraAgc),
|
||||
)
|
||||
.push(
|
||||
button(text(if self.sonora_settings.high_pass_enabled {
|
||||
"High-Pass: On"
|
||||
} else {
|
||||
"High-Pass: Off"
|
||||
})
|
||||
.size(11))
|
||||
.padding([4, 12])
|
||||
.style(if self.sonora_settings.high_pass_enabled {
|
||||
theme::primary_button
|
||||
} else {
|
||||
theme::secondary_button
|
||||
})
|
||||
.on_press(Message::ToggleSonoraHighPass),
|
||||
)
|
||||
} else {
|
||||
voice_col
|
||||
};
|
||||
|
||||
settings_col = settings_col.push(
|
||||
container(voice_col)
|
||||
.style(theme::card_container)
|
||||
|
||||
Reference in New Issue
Block a user