#[derive(Debug, Clone)]
pub struct Agc {
target: f32,
bandwidth: f32,
gain: f32,
envelope: f32,
gain_min: f32,
gain_max: f32,
is_locked: bool,
}
impl Agc {
pub fn new(bandwidth: f32) -> Self {
Self {
target: 1.0,
bandwidth: bandwidth.clamp(0.0, 1.0),
gain: 1.0,
envelope: 1.0,
gain_min: 1e-6,
gain_max: 1e6,
is_locked: false,
}
}
pub fn with_target(bandwidth: f32, target: f32) -> Self {
Self {
target,
bandwidth: bandwidth.clamp(0.0, 1.0),
gain: 1.0,
envelope: 1.0,
gain_min: 1e-6,
gain_max: 1e6,
is_locked: false,
}
}
pub fn set_bandwidth(&mut self, bandwidth: f32) {
self.bandwidth = bandwidth.clamp(0.0, 1.0);
}
pub fn get_bandwidth(&self) -> f32 {
self.bandwidth
}
pub fn set_target(&mut self, target: f32) {
self.target = target;
}
pub fn get_target(&self) -> f32 {
self.target
}
pub fn set_gain_limits(&mut self, min: f32, max: f32) {
self.gain_min = min.max(1e-10);
self.gain_max = max.min(1e10);
}
pub fn get_gain(&self) -> f32 {
self.gain
}
pub fn set_gain(&mut self, gain: f32) {
self.gain = gain.clamp(self.gain_min, self.gain_max);
}
pub fn get_envelope(&self) -> f32 {
self.envelope
}
pub fn lock(&mut self) {
self.is_locked = true;
}
pub fn unlock(&mut self) {
self.is_locked = false;
}
pub fn is_locked(&self) -> bool {
self.is_locked
}
pub fn reset(&mut self) {
self.gain = 1.0;
self.envelope = 1.0;
}
pub fn execute(&mut self, i: f32, q: f32) -> (f32, f32) {
let out_i = i * self.gain;
let out_q = q * self.gain;
if !self.is_locked {
let mag_sq = out_i * out_i + out_q * out_q;
let mag = mag_sq.sqrt();
self.envelope = (1.0 - self.bandwidth) * self.envelope + self.bandwidth * mag;
if self.envelope > 1e-10 {
let error = self.target / self.envelope;
self.gain *= 1.0 + self.bandwidth * (error - 1.0);
self.gain = self.gain.clamp(self.gain_min, self.gain_max);
}
}
(out_i, out_q)
}
pub fn execute_real(&mut self, x: f32) -> f32 {
let (out, _) = self.execute(x, 0.0);
out
}
pub fn execute_batch(&mut self, input: &[(f32, f32)]) -> Vec<(f32, f32)> {
input.iter().map(|&(i, q)| self.execute(i, q)).collect()
}
pub fn get_signal_level_db(&self) -> f32 {
20.0 * self.envelope.log10()
}
pub fn get_gain_db(&self) -> f32 {
20.0 * self.gain.log10()
}
}
impl Default for Agc {
fn default() -> Self {
Self::new(0.01)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agc_creation() {
let agc = Agc::new(0.01);
assert!((agc.get_bandwidth() - 0.01).abs() < 1e-6);
assert!((agc.get_target() - 1.0).abs() < 1e-6);
assert!((agc.get_gain() - 1.0).abs() < 1e-6);
}
#[test]
fn test_agc_unity_gain() {
let mut agc = Agc::new(0.01);
for _ in 0..1000 {
let (out_i, out_q) = agc.execute(1.0, 0.0);
let mag = (out_i * out_i + out_q * out_q).sqrt();
if agc.get_envelope() > 0.9 {
assert!(
(mag - 1.0).abs() < 0.2,
"Output magnitude {} too far from target",
mag
);
}
}
}
#[test]
fn test_agc_amplification() {
let mut agc = Agc::new(0.1);
for _ in 0..1000 {
agc.execute(0.1, 0.0);
}
assert!(
agc.get_gain() > 1.0,
"Gain {} should be > 1",
agc.get_gain()
);
}
#[test]
fn test_agc_attenuation() {
let mut agc = Agc::new(0.1);
for _ in 0..1000 {
agc.execute(10.0, 0.0);
}
assert!(
agc.get_gain() < 1.0,
"Gain {} should be < 1",
agc.get_gain()
);
}
#[test]
fn test_agc_lock() {
let mut agc = Agc::new(0.1);
for _ in 0..100 {
agc.execute(0.5, 0.0);
}
let gain_before = agc.get_gain();
agc.lock();
assert!(agc.is_locked());
for _ in 0..100 {
agc.execute(2.0, 0.0); }
assert!(
(agc.get_gain() - gain_before).abs() < 1e-6,
"Gain changed while locked"
);
agc.unlock();
for _ in 0..100 {
agc.execute(2.0, 0.0);
}
assert!(
(agc.get_gain() - gain_before).abs() > 0.01,
"Gain should change after unlock"
);
}
#[test]
fn test_agc_reset() {
let mut agc = Agc::new(0.1);
for _ in 0..100 {
agc.execute(0.1, 0.0);
}
assert!((agc.get_gain() - 1.0).abs() > 0.1);
agc.reset();
assert!((agc.get_gain() - 1.0).abs() < 1e-6);
}
#[test]
fn test_agc_bandwidth_clamping() {
let mut agc = Agc::new(0.5);
agc.set_bandwidth(2.0);
assert!((agc.get_bandwidth() - 1.0).abs() < 1e-6);
agc.set_bandwidth(-0.5);
assert!((agc.get_bandwidth() - 0.0).abs() < 1e-6);
}
#[test]
fn test_agc_complex_signal() {
let mut agc = Agc::new(0.1);
for i in 0..1000 {
let phase = (i as f32) * 0.1;
let amp = 0.5 + 0.3 * (i as f32 * 0.01).sin();
let (out_i, out_q) = agc.execute(amp * phase.cos(), amp * phase.sin());
if i > 500 {
let out_mag = (out_i * out_i + out_q * out_q).sqrt();
assert!(
out_mag < 2.0 && out_mag > 0.1,
"Output magnitude {} out of range",
out_mag
);
}
}
}
}