feat: add advanced Sonora voice controls
This commit is contained in:
@@ -74,6 +74,7 @@ struct App {
|
|||||||
vad: Arc<Mutex<audio::VoiceActivation>>,
|
vad: Arc<Mutex<audio::VoiceActivation>>,
|
||||||
noise_reducer: Arc<Mutex<noise_cancel::NoiseReducer>>,
|
noise_reducer: Arc<Mutex<noise_cancel::NoiseReducer>>,
|
||||||
nc_method: noise_cancel::NoiseCancelMethod,
|
nc_method: noise_cancel::NoiseCancelMethod,
|
||||||
|
sonora_settings: noise_cancel::SonoraSettings,
|
||||||
imported_identity: Option<Identity>,
|
imported_identity: Option<Identity>,
|
||||||
identity_string_input: String,
|
identity_string_input: String,
|
||||||
identity_file_input: String,
|
identity_file_input: String,
|
||||||
@@ -92,6 +93,7 @@ impl App {
|
|||||||
let appearance_mode = settings.appearance_mode;
|
let appearance_mode = settings.appearance_mode;
|
||||||
let talk_mode = settings.talk_mode;
|
let talk_mode = settings.talk_mode;
|
||||||
let nc_method = settings.noise_cancel_method;
|
let nc_method = settings.noise_cancel_method;
|
||||||
|
let sonora_settings = settings.sonora_settings;
|
||||||
let selected_output_device = settings.selected_output_device;
|
let selected_output_device = settings.selected_output_device;
|
||||||
let selected_input_device = settings.selected_input_device;
|
let selected_input_device = settings.selected_input_device;
|
||||||
|
|
||||||
@@ -130,8 +132,12 @@ impl App {
|
|||||||
continuous_active: false,
|
continuous_active: false,
|
||||||
talk_mode,
|
talk_mode,
|
||||||
vad: Arc::new(Mutex::new(audio::VoiceActivation::new(0.005))),
|
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,
|
nc_method,
|
||||||
|
sonora_settings,
|
||||||
|
))),
|
||||||
|
nc_method,
|
||||||
|
sonora_settings,
|
||||||
imported_identity: None,
|
imported_identity: None,
|
||||||
identity_string_input: String::new(),
|
identity_string_input: String::new(),
|
||||||
identity_file_input: String::new(),
|
identity_file_input: String::new(),
|
||||||
@@ -160,6 +166,7 @@ impl App {
|
|||||||
selected_input_device: self.selected_input_device.clone(),
|
selected_input_device: self.selected_input_device.clone(),
|
||||||
talk_mode: self.talk_mode,
|
talk_mode: self.talk_mode,
|
||||||
noise_cancel_method: self.nc_method,
|
noise_cancel_method: self.nc_method,
|
||||||
|
sonora_settings: self.sonora_settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,6 +601,36 @@ impl App {
|
|||||||
self.save_settings();
|
self.save_settings();
|
||||||
Task::none()
|
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 => {
|
Message::ToggleMicMute => {
|
||||||
self.mic_muted = !self.mic_muted;
|
self.mic_muted = !self.mic_muted;
|
||||||
let h = self.handle.clone();
|
let h = self.handle.clone();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sonora::config::{NoiseSuppression, NoiseSuppressionLevel};
|
use sonora::config::{
|
||||||
|
AdaptiveDigital, GainController2, HighPassFilter, NoiseSuppression,
|
||||||
|
NoiseSuppressionLevel,
|
||||||
|
};
|
||||||
use sonora::{AudioProcessing, Config, StreamConfig};
|
use sonora::{AudioProcessing, Config, StreamConfig};
|
||||||
|
|
||||||
const SONORA_FRAME_SIZE: usize = 480;
|
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 {
|
pub struct NoiseReducer {
|
||||||
method: NoiseCancelMethod,
|
method: NoiseCancelMethod,
|
||||||
|
sonora_settings: SonoraSettings,
|
||||||
nnnoiseless: Option<Box<nnnoiseless::DenoiseState<'static>>>,
|
nnnoiseless: Option<Box<nnnoiseless::DenoiseState<'static>>>,
|
||||||
sonora: Option<SonoraNoiseReducer>,
|
sonora: Option<SonoraNoiseReducer>,
|
||||||
residual_buf: Vec<f32>,
|
residual_buf: Vec<f32>,
|
||||||
@@ -39,13 +92,18 @@ struct SonoraNoiseReducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SonoraNoiseReducer {
|
impl SonoraNoiseReducer {
|
||||||
fn new() -> Self {
|
fn new(settings: SonoraSettings) -> Self {
|
||||||
let stream = StreamConfig::new(48_000, 1);
|
let stream = StreamConfig::new(48_000, 1);
|
||||||
let config = Config {
|
let config = Config {
|
||||||
|
high_pass_filter: settings.high_pass_enabled.then(HighPassFilter::default),
|
||||||
noise_suppression: Some(NoiseSuppression {
|
noise_suppression: Some(NoiseSuppression {
|
||||||
level: NoiseSuppressionLevel::Moderate,
|
level: settings.noise_suppression_level.to_sonora(),
|
||||||
..NoiseSuppression::default()
|
..NoiseSuppression::default()
|
||||||
}),
|
}),
|
||||||
|
gain_controller2: settings.agc_enabled.then(|| GainController2 {
|
||||||
|
adaptive_digital: Some(AdaptiveDigital::default()),
|
||||||
|
..GainController2::default()
|
||||||
|
}),
|
||||||
..Config::default()
|
..Config::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,9 +131,10 @@ impl SonoraNoiseReducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NoiseReducer {
|
impl NoiseReducer {
|
||||||
pub fn new(method: NoiseCancelMethod) -> Self {
|
pub fn new(method: NoiseCancelMethod, sonora_settings: SonoraSettings) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
method,
|
method,
|
||||||
|
sonora_settings,
|
||||||
nnnoiseless: None,
|
nnnoiseless: None,
|
||||||
sonora: None,
|
sonora: None,
|
||||||
residual_buf: Vec::new(),
|
residual_buf: Vec::new(),
|
||||||
@@ -90,7 +149,7 @@ impl NoiseReducer {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
self.sonora = match self.method {
|
self.sonora = match self.method {
|
||||||
NoiseCancelMethod::Sonora => Some(SonoraNoiseReducer::new()),
|
NoiseCancelMethod::Sonora => Some(SonoraNoiseReducer::new(self.sonora_settings)),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
self.residual_buf.clear();
|
self.residual_buf.clear();
|
||||||
@@ -101,6 +160,13 @@ impl NoiseReducer {
|
|||||||
self.init_method();
|
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]) {
|
pub fn process(&mut self, _samples: &mut [f32]) {
|
||||||
match self.method {
|
match self.method {
|
||||||
NoiseCancelMethod::None => {}
|
NoiseCancelMethod::None => {}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub(crate) enum Message {
|
|||||||
PttReleased,
|
PttReleased,
|
||||||
ToggleTalkMode,
|
ToggleTalkMode,
|
||||||
SetNoiseCancelMethod(noise_cancel::NoiseCancelMethod),
|
SetNoiseCancelMethod(noise_cancel::NoiseCancelMethod),
|
||||||
|
SetSonoraNoiseSuppressionLevel(noise_cancel::NoiseSuppressionLevelSetting),
|
||||||
|
ToggleSonoraAgc,
|
||||||
|
ToggleSonoraHighPass,
|
||||||
StartContinuous,
|
StartContinuous,
|
||||||
StopContinuous,
|
StopContinuous,
|
||||||
MicSamples(Vec<f32>),
|
MicSamples(Vec<f32>),
|
||||||
@@ -135,6 +138,7 @@ pub(crate) struct AppSettings {
|
|||||||
pub(crate) selected_input_device: String,
|
pub(crate) selected_input_device: String,
|
||||||
pub(crate) talk_mode: audio::TalkMode,
|
pub(crate) talk_mode: audio::TalkMode,
|
||||||
pub(crate) noise_cancel_method: noise_cancel::NoiseCancelMethod,
|
pub(crate) noise_cancel_method: noise_cancel::NoiseCancelMethod,
|
||||||
|
pub(crate) sonora_settings: noise_cancel::SonoraSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppSettings {
|
impl Default for AppSettings {
|
||||||
@@ -145,6 +149,7 @@ impl Default for AppSettings {
|
|||||||
selected_input_device: String::new(),
|
selected_input_device: String::new(),
|
||||||
talk_mode: audio::TalkMode::PushToTalk,
|
talk_mode: audio::TalkMode::PushToTalk,
|
||||||
noise_cancel_method: noise_cancel::NoiseCancelMethod::None,
|
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 nc_label = self.nc_method.label();
|
||||||
|
let sonora_noise_label = self.sonora_settings.noise_suppression_level.label();
|
||||||
|
|
||||||
let voice_col = column![
|
let voice_col = column![
|
||||||
text("Voice Activation Mode").size(13),
|
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(
|
settings_col = settings_col.push(
|
||||||
container(voice_col)
|
container(voice_col)
|
||||||
.style(theme::card_container)
|
.style(theme::card_container)
|
||||||
|
|||||||
Reference in New Issue
Block a user