diff --git a/Cargo.lock b/Cargo.lock index 7fae3d810..b81242d21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,100 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "async-broadcast" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "windows-sys", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -213,6 +307,15 @@ dependencies = [ "cc", ] +[[package]] +name = "concurrent-queue" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "cpufeatures" version = "0.2.5" @@ -296,7 +399,7 @@ version = "3.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ - "nix", + "nix 0.26.1", "windows-sys", ] @@ -306,10 +409,12 @@ version = "0.4.1-pre" dependencies = [ "deep_filter", "env_logger", + "event-listener", "ladspa", "log", "ndarray", "uuid", + "zbus", ] [[package]] @@ -352,6 +457,17 @@ dependencies = [ "tract-pulse", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive-new" version = "0.5.9" @@ -373,6 +489,26 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dlv-list" version = "0.3.0" @@ -429,6 +565,27 @@ dependencies = [ "syn", ] +[[package]] +name = "enumflags2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -463,6 +620,21 @@ dependencies = [ "libc", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "filetime" version = "0.2.19" @@ -539,6 +711,21 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.25" @@ -718,6 +905,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hound" version = "3.5.0" @@ -736,6 +929,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -1033,6 +1235,20 @@ dependencies = [ "rand_distr", ] +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", + "pin-utils", +] + [[package]] name = "nix" version = "0.26.1" @@ -1155,12 +1371,28 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ordered-stream" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ca8c99d73c6e92ac1358f9f692c22c0bfd9c4701fa086f5d365c0d4ea818ea" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_str_bytes" version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1258,6 +1490,20 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "windows-sys", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1273,6 +1519,17 @@ dependencies = [ "num-integer", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1523,6 +1780,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.7.0" @@ -1540,6 +1808,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "roots" version = "0.0.7" @@ -1705,6 +1982,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.5" @@ -1731,6 +2019,16 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1777,6 +2075,20 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1848,6 +2160,47 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "tract-core" version = "0.18.5" @@ -2018,6 +2371,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -2067,6 +2430,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -2084,6 +2453,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2190,3 +2568,92 @@ checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] + +[[package]] +name = "zbus" +version = "3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938ea6da98c75c2c37a86007bd17fd8e208cbec24e086108c87ece98e9edec0d" +dependencies = [ + "async-broadcast", + "async-channel", + "async-executor", + "async-io", + "async-lock", + "async-recursion", + "async-task", + "async-trait", + "byteorder", + "derivative", + "dirs", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.25.1", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45066039ebf3330820e495e854f8b312abb68f0a39e97972d092bd72e8bb3e8e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c737644108627748a660d038974160e0cbb62605536091bdfa28fd7f64d43c8" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zvariant" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8c89c183461e11867ded456db252eae90874bc6769b7adbea464caa777e51" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155247a5d1ab55e335421c104ccd95d64f17cebbd02f50cdbc1c33385f9c4d81" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] diff --git a/ladspa/Cargo.toml b/ladspa/Cargo.toml index 61fcc19ed..1a85058b0 100644 --- a/ladspa/Cargo.toml +++ b/ladspa/Cargo.toml @@ -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" diff --git a/ladspa/filter-chain-configs/deepfilter-mono-source.conf b/ladspa/filter-chain-configs/deepfilter-mono-source.conf index e0879b14e..2e8f594ba 100644 --- a/ladspa/filter-chain-configs/deepfilter-mono-source.conf +++ b/ladspa/filter-chain-configs/deepfilter-mono-source.conf @@ -36,6 +36,7 @@ context.modules = [ label = deep_filter_mono control = { "Attenuation Limit (dB)" 100 + "Automatic Gain Control target RMS" 0.001 } } ] diff --git a/ladspa/filter-chain-configs/deepfilter-stereo-sink.conf b/ladspa/filter-chain-configs/deepfilter-stereo-sink.conf index 5576561e8..cb0e48f2b 100644 --- a/ladspa/filter-chain-configs/deepfilter-stereo-sink.conf +++ b/ladspa/filter-chain-configs/deepfilter-stereo-sink.conf @@ -39,6 +39,7 @@ context.modules = [ label = deep_filter_stereo control = { "Attenuation Limit (dB)" 100 + "Automatic Gain Control target RMS" 0.001 } } ] diff --git a/ladspa/src/lib.rs b/ladspa/src/lib.rs index d0cc75141..5e9702cf4 100644 --- a/ladspa/src/lib.rs +++ b/ladspa/src/lib.rs @@ -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::*; @@ -91,10 +93,12 @@ 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 = 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.") @@ -102,6 +106,21 @@ fn get_worker_fn( 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 = { @@ -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()) { @@ -240,6 +267,7 @@ enum DfControl { MinThreshDb, MaxErbThreshDb, MaxDfThreshDb, + AgcRms, } impl DfControl { fn from_port_name(name: &str) -> Self { @@ -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"), } } @@ -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"), } } } @@ -268,6 +298,7 @@ struct DfControlHistory { min_thresh_db: f32, max_erb_thresh_db: f32, max_df_thresh_db: f32, + agc_rms: Option, } impl Default for DfControlHistory { fn default() -> Self { @@ -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, } } } @@ -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) { @@ -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), } } } @@ -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"); } @@ -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] @@ -535,6 +575,14 @@ pub fn get_ladspa_descriptor(index: u64) -> Option { 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)), }), @@ -598,6 +646,14 @@ pub fn get_ladspa_descriptor(index: u64) -> Option { 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)), }), diff --git a/libDF/Cargo.toml b/libDF/Cargo.toml index c7a76abe0..81fedc2a3 100644 --- a/libDF/Cargo.toml +++ b/libDF/Cargo.toml @@ -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" diff --git a/libDF/src/agc.rs b/libDF/src/agc.rs new file mode 100644 index 000000000..42ac31680 --- /dev/null +++ b/libDF/src/agc.rs @@ -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, snr: Option) { + 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); + } + } +} diff --git a/libDF/src/lib.rs b/libDF/src/lib.rs index dfcd625a7..84832bba3 100644 --- a/libDF/src/lib.rs +++ b/libDF/src/lib.rs @@ -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")] @@ -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 { @@ -474,7 +476,7 @@ impl NonNan { } } -pub(crate) fn find_max<'a, I>(vals: I) -> Option +pub fn find_max<'a, I>(vals: I) -> Option where I: IntoIterator, { @@ -487,7 +489,7 @@ where }) } -pub(crate) fn find_max_abs<'a, I>(vals: I) -> Option +pub fn find_max_abs<'a, I>(vals: I) -> Option where I: IntoIterator, { @@ -500,7 +502,7 @@ where }) } -pub(crate) fn find_min<'a, I>(vals: I) -> Option +pub fn find_min<'a, I>(vals: I) -> Option where I: IntoIterator, { @@ -513,7 +515,7 @@ where }) } -pub(crate) fn find_min_abs<'a, I>(vals: I) -> Option +pub fn find_min_abs<'a, I>(vals: I) -> Option where I: IntoIterator, { @@ -526,7 +528,7 @@ where }) } -pub(crate) fn argmax<'a, I>(vals: I) -> Option +pub fn argmax<'a, I>(vals: I) -> Option where I: IntoIterator, { @@ -541,7 +543,7 @@ where Some(index) } -pub(crate) fn argmax_abs<'a, I>(vals: I) -> Option +pub fn argmax_abs<'a, I>(vals: I) -> Option where I: IntoIterator, { diff --git a/libDF/src/tract.rs b/libDF/src/tract.rs index 1b9627b1e..a8f961c21 100644 --- a/libDF/src/tract.rs +++ b/libDF/src/tract.rs @@ -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(); @@ -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 diff --git a/scripts/demo.py b/scripts/demo.py index bd94793bb..0527620aa 100644 --- a/scripts/demo.py +++ b/scripts/demo.py @@ -4,6 +4,7 @@ from math import ceil from multiprocessing import Process, Queue from tkinter import ttk +from xml.dom import minidom import numpy as np import pyaudio as pa @@ -167,50 +168,120 @@ def init_df_pa_stream(input=None): drop_df = tk.OptionMenu(opt_frame, tk_df, *choices_df, command=init_df_pa_stream) drop_df.grid(column=1, row=1, sticky=tk.W, padx=5, pady=5) -df_attenlim_value = tk.DoubleVar() -df_attenlim_value.set(100) -df_attenlim_label = ttk.Label(opt_frame, text="DeepFilter attenuation limit:") -df_attenlim_label.grid(column=0, row=2, sticky=tk.W, padx=5, pady=5) +init_non_df_pa_stream() +init_df_pa_stream() -def attenlim_callback(input=None): - lim = get_attenlim(input).rstrip(" [dB]") - df_cur_attenlim_label.configure(text=lim) + +def get_df_dbus_methods(): args = [ "busctl", "--user", - "call", + "introspect", + "--xml-interface", "org.deepfilter.DeepFilterLadspa", "/org/deepfilter/DeepFilterLadspa", "org.deepfilter.DeepFilterLadspa", - "AttenLim", - "u", - lim, ] try: - subprocess.run(args, timeout=0.01) - except subprocess.TimeoutExpired: - print("DBUS timeout") - - -def get_attenlim(input=None): - return str(int(float(input or df_attenlim_value.get()))) + " [dB]" - - -df_cur_attenlim_label = ttk.Label(opt_frame, text=get_attenlim()) -df_cur_attenlim_label.grid(column=1, row=2, sticky=tk.E, padx=5, pady=5) -df_attenlim_slider = ttk.Scale( - opt_frame, - from_=0, - to=100, - orient="horizontal", - variable=df_attenlim_value, - command=attenlim_callback, -) -df_attenlim_slider.grid(column=1, row=2, sticky=tk.W, padx=5, pady=5) + xml = subprocess.check_output(args).strip().decode() + except subprocess.CalledProcessError: + return [] + dom = minidom.parseString(xml) + methods = [ + i.getElementsByTagName("method") + for i in dom.getElementsByTagName("interface") + if "deepfilter" in i.attributes["name"].value + ] + methods = [item for sublist in methods for item in sublist] + methods = [m.attributes["name"].value for m in methods] + return methods + + +df_dbus_methods = get_df_dbus_methods() + +next_row = 2 + +if "AttenLim" in df_dbus_methods: + df_attenlim_value = tk.DoubleVar() + df_attenlim_value.set(100) + df_attenlim_label = ttk.Label(opt_frame, text="DeepFilter attenuation limit:") + df_attenlim_label.grid(column=0, row=next_row, sticky=tk.W, padx=5, pady=5) + + def attenlim_callback(input=None): + lim = get_attenlim(input).rstrip(" [dB]") + df_cur_attenlim_label.configure(text=lim) + args = [ + "busctl", + "--user", + "call", + "org.deepfilter.DeepFilterLadspa", + "/org/deepfilter/DeepFilterLadspa", + "org.deepfilter.DeepFilterLadspa", + "AttenLim", + "u", + lim, + ] + try: + subprocess.run(args, timeout=0.01) + except subprocess.TimeoutExpired: + print("DBUS timeout") + + def get_attenlim(input=None): + return str(int(float(input or df_attenlim_value.get()))) + " [dB]" + + df_cur_attenlim_label = ttk.Label(opt_frame, text=get_attenlim()) + df_cur_attenlim_label.grid(column=1, row=next_row, sticky=tk.E, padx=5, pady=5) + df_attenlim_slider = ttk.Scale( + opt_frame, + from_=0, + to=100, + orient="horizontal", + variable=df_attenlim_value, + command=attenlim_callback, + ) + df_attenlim_slider.grid(column=1, row=next_row, sticky=tk.W, padx=5, pady=5) + next_row += 1 + +if "AgcRms" in df_dbus_methods: + df_agcrms_value = tk.StringVar() + df_agcrms_value.set("0") + df_agcrms_label = ttk.Label(opt_frame, text="DeepFilter AGC rms:") + df_agcrms_label.grid(column=0, row=next_row, sticky=tk.W, padx=5, pady=5) + + def agcrms_callback(input=None): + rms = get_agcrms(input) + df_cur_agcrms_label.configure(text=rms) + args = [ + "busctl", + "--user", + "call", + "org.deepfilter.DeepFilterLadspa", + "/org/deepfilter/DeepFilterLadspa", + "org.deepfilter.DeepFilterLadspa", + "AgcRms", + "d", + rms, + ] + try: + subprocess.run(args, timeout=0.01) + except subprocess.TimeoutExpired: + print("DBUS timeout") + + def get_agcrms(input=None): + return input or df_agcrms_value.get() + + df_cur_agcrms_label = ttk.Label(opt_frame, text=get_agcrms()) + df_cur_agcrms_label.grid(column=1, row=next_row, sticky=tk.E, padx=5, pady=5) + options = ["0", "0.00001", "0.0001", "0.001", "0.01", "0.1"] + df_agcrms_drop = tk.OptionMenu( + opt_frame, df_agcrms_value, *options, command=agcrms_callback + ) + df_agcrms_drop.grid(column=1, row=next_row, sticky=tk.W, padx=5, pady=5) + next_row += 1 button = tk.Button(master=opt_frame, text="Quit", command=root.quit) -button.grid(column=0, row=3, sticky=tk.SW, padx=5, pady=5) +button.grid(column=0, row=next_row, sticky=tk.SW, padx=5, pady=5) ### Animation GUI ### fig, (ax_non_df, ax_df) = plt.subplots(2)