use super::quant::{optimum_step_sigma, Quantiser};
use super::{norm_sqr, Cf64, Stage};
const EULER_GAMMA: f64 = 0.577_215_664_901_532_9;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Agc {
log_gain: f64,
mu: f64,
ln_target: f64,
}
impl Agc {
pub fn new(fs_hz: f64, tau_s: f64, target_power: f64, initial_gain: f64) -> Self {
assert!(fs_hz > 0.0 && tau_s > 0.0 && target_power > 0.0 && initial_gain > 0.0);
let mu = 1.0 / (2.0 * tau_s * fs_hz);
assert!(mu < 0.5, "time constant must be longer than one sample");
Self {
log_gain: initial_gain.ln(),
mu,
ln_target: target_power.ln(),
}
}
pub fn for_quantiser(q: &Quantiser, fs_hz: f64, tau_s: f64) -> Self {
let sigma = q.step / optimum_step_sigma(q.bits()).0;
Self::new(fs_hz, tau_s, 2.0 * sigma * sigma, 1.0)
}
pub fn gain(&self) -> f64 {
self.log_gain.exp()
}
pub fn gain_db(&self) -> f64 {
self.log_gain * 20.0 / std::f64::consts::LN_10
}
pub fn target_power(&self) -> f64 {
self.ln_target.exp()
}
}
impl Stage for Agc {
fn process(&mut self, block: &mut [Cf64]) {
for x in block.iter_mut() {
let y = *x * self.log_gain.exp();
let p = norm_sqr(y);
if p > 0.0 && p.is_finite() {
self.log_gain -= self.mu * (p.ln() + EULER_GAMMA - self.ln_target);
}
*x = y;
}
}
}