Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple AGC #213

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
469 changes: 468 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion ladspa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ crate-type = ["cdylib"]

[features]
dbus = ["dep:zbus", "dep:event-listener"]
agc = ["deep_filter/agc"]

[dependencies]
deep_filter = { path = "../libDF", features = ["tract", "use-jemalloc"] }
deep_filter = { path = "../libDF", features = ["tract", "use-jemalloc", "logging"] }
ladspa = "0.3.4"
ndarray = "^0.15"
env_logger = "0.10"
Expand Down
1 change: 1 addition & 0 deletions ladspa/filter-chain-configs/deepfilter-mono-source.conf
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ context.modules = [
label = deep_filter_mono
control = {
"Attenuation Limit (dB)" 100
"Automatic Gain Control target RMS" 0.001
}
}
]
Expand Down
1 change: 1 addition & 0 deletions ladspa/filter-chain-configs/deepfilter-stereo-sink.conf
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ context.modules = [
label = deep_filter_stereo
control = {
"Attenuation Limit (dB)" 100
"Automatic Gain Control target RMS" 0.001
}
}
]
Expand Down
58 changes: 57 additions & 1 deletion ladspa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::sync::{
use std::thread::{self, sleep, JoinHandle};
use std::time::{Duration, Instant};

#[cfg(feature = "agc")]
use df::agc::Agc;
use df::tract::*;
use ladspa::{DefaultValue, Plugin, PluginDescriptor, Port, PortConnection, PortDescriptor};
use ndarray::prelude::*;
Expand Down Expand Up @@ -91,17 +93,34 @@ fn get_worker_fn(
move || {
let mut inframe = Array2::zeros((df.ch, df.hop_size));
let mut outframe = Array2::zeros((df.ch, df.hop_size));
#[cfg(feature = "agc")]
let mut agc: Option<Agc> = None;
let t_audio_ms = df.hop_size as f32 / df.sr as f32 * 1000.;
loop {
if let Ok((c, v)) = controls.try_recv() {
log::info!("DF {} | Setting '{}' to {:.1}", id, c, v);
log::info!("DF {} | Setting '{}' to {}", id, c, v);
match c {
DfControl::AttenLim => {
df.set_atten_lim(v).expect("Failed to set attenuation limit.")
}
DfControl::MinThreshDb => df.min_db_thresh = v,
DfControl::MaxErbThreshDb => df.max_db_erb_thresh = v,
DfControl::MaxDfThreshDb => df.max_db_df_thresh = v,
#[cfg(feature = "agc")]
DfControl::AgcRms => {
if v == 0. {
log::info!("DF {} | Disabling AGC", id);
agc = None;
} else if let Some(agc) = agc.as_mut() {
agc.desired_output_rms = v
} else {
agc = Some(Agc::new(v, 0.00001, 0.));
}
}
#[cfg(not(feature = "agc"))]
DfControl::AgcRms => log::warn!(
"Compiled without AGC support. This option will not have any effect. To use it compile with `--feature=agc`."
),
}
}
let got_samples = {
Expand All @@ -125,6 +144,14 @@ fn get_worker_fn(
let lsnr = df
.process(inframe.view(), outframe.view_mut())
.expect("Error during df::process");
#[cfg(feature = "agc")]
if let Some(agc) = agc.as_mut() {
agc.process(outframe.view_mut(), Some(lsnr));
}
let max_a = df::find_max_abs(outframe.iter()).expect("NaN");
if max_a > 0.9999 {
log::warn!("Possible clipping detected ({:.3}).", max_a)
}
{
let mut o_q = outqueue.lock().unwrap();
for (o_ch, o_q_ch) in outframe.outer_iter().zip(o_q.iter_mut()) {
Expand Down Expand Up @@ -240,6 +267,7 @@ enum DfControl {
MinThreshDb,
MaxErbThreshDb,
MaxDfThreshDb,
AgcRms,
}
impl DfControl {
fn from_port_name(name: &str) -> Self {
Expand All @@ -248,6 +276,7 @@ impl DfControl {
"Min processing threshold (dB)" => Self::MinThreshDb,
"Max ERB processing threshold (dB)" => Self::MaxErbThreshDb,
"Max DF processing threshold (dB)" => Self::MaxDfThreshDb,
"Automatic Gain Control target RMS" => Self::AgcRms,
_ => panic!("name not found"),
}
}
Expand All @@ -259,6 +288,7 @@ impl fmt::Display for DfControl {
DfControl::MinThreshDb => write!(f, "Min processing threshold (dB)"),
DfControl::MaxErbThreshDb => write!(f, "Max ERB processing threshold (dB)"),
DfControl::MaxDfThreshDb => write!(f, "Max DF processing threshold (dB)"),
DfControl::AgcRms => write!(f, "Automatic Gain Control target RMS"),
}
}
}
Expand All @@ -268,6 +298,7 @@ struct DfControlHistory {
min_thresh_db: f32,
max_erb_thresh_db: f32,
max_df_thresh_db: f32,
agc_rms: Option<f32>,
}
impl Default for DfControlHistory {
fn default() -> Self {
Expand All @@ -276,6 +307,7 @@ impl Default for DfControlHistory {
min_thresh_db: -10.,
max_erb_thresh_db: 30.,
max_df_thresh_db: 20.,
agc_rms: None,
}
}
}
Expand All @@ -286,6 +318,7 @@ impl DfControlHistory {
DfControl::MinThreshDb => self.min_thresh_db,
DfControl::MaxErbThreshDb => self.max_erb_thresh_db,
DfControl::MaxDfThreshDb => self.max_df_thresh_db,
DfControl::AgcRms => self.agc_rms.unwrap_or_default(),
}
}
fn set(&mut self, c: &DfControl, v: f32) {
Expand All @@ -294,6 +327,7 @@ impl DfControlHistory {
DfControl::MinThreshDb => self.min_thresh_db = v,
DfControl::MaxErbThreshDb => self.max_erb_thresh_db = v,
DfControl::MaxDfThreshDb => self.max_df_thresh_db = v,
DfControl::AgcRms => self.agc_rms = Some(v),
}
}
}
Expand Down Expand Up @@ -359,6 +393,7 @@ impl Plugin for DfPlugin {
}
}
if v != self.control_hist.get(&c) {
log::info!("DF {} | Setting '{}' to {}", self.id, p.port.name, v);
self.control_hist.set(&c, v);
self.control_tx.send((c, v)).expect("Failed to send control parameter");
}
Expand Down Expand Up @@ -480,6 +515,11 @@ impl DfDbusControl {
.send((DfControl::AttenLim, lim as f32))
.expect("Failed to send DfControl");
}

#[cfg(feature = "agc")]
fn agc_rms(&self, rms: f64) {
self.tx.send((DfControl::AgcRms, rms as f32)).expect("Failed to send DfControl");
}
}

#[no_mangle]
Expand Down Expand Up @@ -535,6 +575,14 @@ pub fn get_ladspa_descriptor(index: u64) -> Option<PluginDescriptor> {
lower_bound: Some(-15.),
upper_bound: Some(35.),
},
Port {
name: "Automatic Gain Control target RMS",
desc: PortDescriptor::ControlInput,
hint: None,
default: Some(DefaultValue::Minimum),
lower_bound: Some(0.0),
upper_bound: Some(0.1),
},
],
new: |d, sr| Box::new(get_new_df(1)(d, sr)),
}),
Expand Down Expand Up @@ -598,6 +646,14 @@ pub fn get_ladspa_descriptor(index: u64) -> Option<PluginDescriptor> {
lower_bound: Some(-15.),
upper_bound: Some(35.),
},
Port {
name: "Automatic Gain Control target RMS",
desc: PortDescriptor::ControlInput,
hint: None,
default: Some(DefaultValue::Minimum),
lower_bound: Some(0.0),
upper_bound: Some(0.1),
},
],
new: |d, sr| Box::new(get_new_df(2)(d, sr)),
}),
Expand Down
1 change: 1 addition & 0 deletions libDF/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ tract = [
default_model = []
capi = ["tract", "default_model", "dep:ndarray"]
hdf5-static = ["hdf5?/static"]
agc = ["dep:ndarray"]

[dependencies]
rustfft = "^6.1.0"
Expand Down
42 changes: 42 additions & 0 deletions libDF/src/agc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#[cfg(feature = "logging")]
use log;
/// This module is based on sile/dagc but also supports a simple stereo version
use ndarray::{ArrayViewMut2, Axis};

#[derive(Debug)]
pub struct Agc {
pub desired_output_rms: f32,
pub distortion_factor: f32,
pub gain: f32,
pub snr_thresh: f32,
}

impl Agc {
pub fn new(desired_output_rms: f32, distortion_factor: f32, snr_thresh: f32) -> Self {
assert!(desired_output_rms > 0.);
assert!((0f32..1f32).contains(&distortion_factor));
Self {
desired_output_rms,
distortion_factor,
gain: 1.,
snr_thresh,
}
}

/// Process a chunk of samples
pub fn process(&mut self, mut samples: ArrayViewMut2<f32>, snr: Option<f32>) {
let frozen = snr.unwrap_or_default() < self.snr_thresh;
if frozen {
samples.map_inplace(|s| *s *= self.gain);
} else {
for mut s in samples.axis_iter_mut(Axis(1)) {
s.map_inplace(|s| *s *= self.gain);
let y = s.mean().unwrap().powi(2) / self.desired_output_rms;
let z = 1.0 + (self.distortion_factor * (1.0 - y));
self.gain *= z;
}
#[cfg(feature = "logging")]
log::trace!("AGC gain set to {:.2}", self.gain);
}
}
}
16 changes: 9 additions & 7 deletions libDF/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ mod reexport_dataset_modules {
}
#[cfg(feature = "dataset")]
pub use reexport_dataset_modules::*;
#[cfg(feature = "agc")]
pub mod agc;
#[cfg(any(cargo_c, feature = "capi"))]
mod capi;
#[cfg(feature = "tract")]
Expand Down Expand Up @@ -459,7 +461,7 @@ pub fn post_filter(gains: &mut [f32]) {
}
}

pub(crate) struct NonNan(f32);
pub struct NonNan(f32);

impl NonNan {
fn new(val: f32) -> Option<NonNan> {
Expand All @@ -474,7 +476,7 @@ impl NonNan {
}
}

pub(crate) fn find_max<'a, I>(vals: I) -> Option<f32>
pub fn find_max<'a, I>(vals: I) -> Option<f32>
where
I: IntoIterator<Item = &'a f32>,
{
Expand All @@ -487,7 +489,7 @@ where
})
}

pub(crate) fn find_max_abs<'a, I>(vals: I) -> Option<f32>
pub fn find_max_abs<'a, I>(vals: I) -> Option<f32>
where
I: IntoIterator<Item = &'a f32>,
{
Expand All @@ -500,7 +502,7 @@ where
})
}

pub(crate) fn find_min<'a, I>(vals: I) -> Option<f32>
pub fn find_min<'a, I>(vals: I) -> Option<f32>
where
I: IntoIterator<Item = &'a f32>,
{
Expand All @@ -513,7 +515,7 @@ where
})
}

pub(crate) fn find_min_abs<'a, I>(vals: I) -> Option<f32>
pub fn find_min_abs<'a, I>(vals: I) -> Option<f32>
where
I: IntoIterator<Item = &'a f32>,
{
Expand All @@ -526,7 +528,7 @@ where
})
}

pub(crate) fn argmax<'a, I>(vals: I) -> Option<usize>
pub fn argmax<'a, I>(vals: I) -> Option<usize>
where
I: IntoIterator<Item = &'a f32>,
{
Expand All @@ -541,7 +543,7 @@ where
Some(index)
}

pub(crate) fn argmax_abs<'a, I>(vals: I) -> Option<usize>
pub fn argmax_abs<'a, I>(vals: I) -> Option<usize>
where
I: IntoIterator<Item = &'a f32>,
{
Expand Down
14 changes: 0 additions & 14 deletions libDF/src/tract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,6 @@ impl DfTract {
debug_assert_eq!(noisy.len_of(Axis(0)), enh.len_of(Axis(0)));
debug_assert_eq!(noisy.len_of(Axis(1)), enh.len_of(Axis(1)));
debug_assert_eq!(noisy.len_of(Axis(1)), self.hop_size);
let max_a = find_max_abs(noisy.iter()).expect("NaN");
if max_a > 0.9999 {
log::warn!("Possible clipping detected ({:.3}).", max_a)
}
if self.atten_lim.unwrap_or_default() == 1. {
enh.assign(&noisy);
return Ok(35.);
}

// Signal model: y = f(s + n) = f(x)
self.rolling_spec_buf_y.pop_front();
Expand Down Expand Up @@ -406,12 +398,6 @@ impl DfTract {
// Regular noisy signal detected, apply 1st and 2nd stage
(true, false, true)
};
log::trace!(
"Enhancing frame with lsnr {:.1}. Applying stage 1: {} and stage 2: {}.",
lsnr,
apply_erb,
apply_df
);

let mut spec = self
.rolling_spec_buf_y
Expand Down
Loading