Compare commits
7 Commits
f3f99a412e
...
b947256af6
| Author | SHA1 | Date | |
|---|---|---|---|
| b947256af6 | |||
| 3ca4998ae9 | |||
| 819171720c | |||
| f402885ab8 | |||
| e44db49bb4 | |||
| 9f431783fb | |||
| bfea615351 |
83
src/agc.rs
Normal file
83
src/agc.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use num_complex::Complex;
|
||||||
|
|
||||||
|
use crate::iq_reader::IqChunk;
|
||||||
|
|
||||||
|
// Automatic Gain Control
|
||||||
|
pub struct Agc {
|
||||||
|
// Previous power estimate
|
||||||
|
pub power_estimate: f32,
|
||||||
|
// Previous gain
|
||||||
|
pub current_gain: f32,
|
||||||
|
pub target_power: f32,
|
||||||
|
pub alpha_attack: f32,
|
||||||
|
pub alpha_release: f32,
|
||||||
|
pub beta: f32,
|
||||||
|
pub min_gain: f32,
|
||||||
|
pub max_gain: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agc {
|
||||||
|
pub fn new(sample_rate: f32, target_power: f32, min_gain: f32, max_gain: f32) -> Self {
|
||||||
|
// Target attack time 5 ms
|
||||||
|
let tau_attack = 0.005;
|
||||||
|
|
||||||
|
// Target release time 50 ms
|
||||||
|
let tau_release = 0.05;
|
||||||
|
|
||||||
|
let alpha_attack = 1.0 - f32::exp(-1.0 / (sample_rate * tau_attack));
|
||||||
|
let alpha_release = 1.0 - f32::exp(-1.0 / (sample_rate * tau_release));
|
||||||
|
|
||||||
|
let beta = 0.999;
|
||||||
|
|
||||||
|
let power_estimate = 0.0;
|
||||||
|
let current_gain = 1.0;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
power_estimate,
|
||||||
|
current_gain,
|
||||||
|
target_power,
|
||||||
|
alpha_attack,
|
||||||
|
alpha_release,
|
||||||
|
beta,
|
||||||
|
min_gain,
|
||||||
|
max_gain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_chunk(&mut self, chunk: &mut IqChunk) {
|
||||||
|
for z in chunk.samples.iter_mut() {
|
||||||
|
let i = z.re;
|
||||||
|
let q = z.im;
|
||||||
|
|
||||||
|
// Instant Power
|
||||||
|
let inst_power = i * i + q * q;
|
||||||
|
|
||||||
|
let alpha = if inst_power > self.power_estimate {
|
||||||
|
self.alpha_attack
|
||||||
|
} else {
|
||||||
|
self.alpha_release
|
||||||
|
};
|
||||||
|
|
||||||
|
// IIR filter
|
||||||
|
let power_estimate = alpha * inst_power + (1.0 - alpha) * self.power_estimate;
|
||||||
|
|
||||||
|
// Update Power
|
||||||
|
self.power_estimate = power_estimate.max(1e-10);
|
||||||
|
|
||||||
|
let raw_gain = (self.target_power / self.power_estimate).sqrt();
|
||||||
|
|
||||||
|
// Gain in [min_gain ; max_gain]
|
||||||
|
let raw_gain = match raw_gain {
|
||||||
|
g if g < self.min_gain => self.min_gain,
|
||||||
|
g if g > self.max_gain => self.max_gain,
|
||||||
|
_ => raw_gain,
|
||||||
|
};
|
||||||
|
|
||||||
|
let final_gain = self.beta * self.current_gain + (1.0 - self.beta) * raw_gain;
|
||||||
|
|
||||||
|
self.current_gain = final_gain;
|
||||||
|
|
||||||
|
*z = Complex::new(i * final_gain, q * final_gain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/fir.rs
Normal file
27
src/fir.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Finite Impulse response + Decimation
|
||||||
|
use crate::utils::ring_buffer::RingBuffer;
|
||||||
|
use num_complex::Complex32;
|
||||||
|
|
||||||
|
pub struct Fir<const N: usize> {
|
||||||
|
// Filter coefs
|
||||||
|
pub taps: [f32; N],
|
||||||
|
|
||||||
|
// Ring Buffer of samples
|
||||||
|
pub history: RingBuffer<Complex32>,
|
||||||
|
|
||||||
|
decimation_factor: usize,
|
||||||
|
|
||||||
|
// When to keep a sample
|
||||||
|
decimator_counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Fir<N> {
|
||||||
|
fn new(taps: [f32; N], decimation_factor: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
taps,
|
||||||
|
history: RingBuffer::new(N),
|
||||||
|
decimation_factor,
|
||||||
|
decimator_counter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,12 @@
|
|||||||
use num_complex::Complex;
|
use num_complex::Complex;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader, Read};
|
use std::io::{BufReader, ErrorKind, Read};
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
|
|
||||||
pub type IqSample = Complex<f32>;
|
pub type IqSample = Complex<f32>;
|
||||||
|
|
||||||
// Data chunk
|
// Data chunk
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct IqChunk {
|
pub struct IqChunk {
|
||||||
pub samples: Vec<IqSample>,
|
pub samples: Vec<IqSample>,
|
||||||
}
|
}
|
||||||
@ -21,8 +21,8 @@ pub struct FileSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FileSource {
|
impl FileSource {
|
||||||
pub fn new(file_path: &str, chunk_samples_size: usize) -> Self {
|
pub fn new(file_path: &str, chunk_samples_size: usize) -> Result<Self, Box<dyn Error>> {
|
||||||
let file = File::open(file_path).unwrap();
|
let file = File::open(file_path)?;
|
||||||
|
|
||||||
// Init buffer with size 16 Mo
|
// Init buffer with size 16 Mo
|
||||||
let reader = BufReader::with_capacity(16 * 1024 * 1024, file);
|
let reader = BufReader::with_capacity(16 * 1024 * 1024, file);
|
||||||
@ -30,27 +30,44 @@ impl FileSource {
|
|||||||
// 1 sample = 2 bytes (1 byte : I, 1 byte : Q)
|
// 1 sample = 2 bytes (1 byte : I, 1 byte : Q)
|
||||||
let raw_reader = vec![0; chunk_samples_size * 2];
|
let raw_reader = vec![0; chunk_samples_size * 2];
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
reader,
|
reader,
|
||||||
raw_buffer: raw_reader,
|
raw_buffer: raw_reader,
|
||||||
chunk_samples_size,
|
chunk_samples_size,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_chunk(&mut self) -> Option<IqChunk> {
|
impl Iterator for FileSource {
|
||||||
// TODO : match for EOF
|
type Item = Result<IqChunk, Box<dyn Error>>;
|
||||||
self.reader.read_exact(&mut self.raw_buffer);
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// Buffer read
|
||||||
|
match self.reader.read_exact(&mut self.raw_buffer) {
|
||||||
|
Ok(_) => {}
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
return Some(Err(e.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Output samples
|
// Output samples
|
||||||
let mut samples = Vec::with_capacity(self.chunk_samples_size);
|
let mut samples = Vec::with_capacity(self.chunk_samples_size);
|
||||||
|
|
||||||
// Buffer read
|
// Buffer read
|
||||||
for iq in self.raw_buffer.chunks(2) {
|
for iq in self.raw_buffer.chunks(2) {
|
||||||
let i = (iq[0] as f32) / 128.0;
|
let i = iq[0] as i8;
|
||||||
let q = (iq[1] as f32) / 128.0;
|
let q = iq[1] as i8;
|
||||||
samples.push(Complex::new(i, q));
|
let i_f32 = (i as f32) / 128.0;
|
||||||
|
let q_f32 = (q as f32) / 128.0;
|
||||||
|
samples.push(Complex::new(i_f32, q_f32));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(IqChunk { samples })
|
Some(Ok(IqChunk { samples }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/main.rs
28
src/main.rs
@ -1,5 +1,27 @@
|
|||||||
mod iq_reader;
|
use crate::agc::Agc;
|
||||||
|
use crate::iq_reader::FileSource;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
fn main() {
|
mod agc;
|
||||||
println!("Hello, world!");
|
mod fir;
|
||||||
|
mod iq_reader;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let source = FileSource::new("test.iq", 32769)?;
|
||||||
|
|
||||||
|
// for chunk in source {
|
||||||
|
// println!("{chunk :?}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 20 MSps
|
||||||
|
let mut agc = Agc::new(20_000_000.0, 0.1, 0.001, 100.0);
|
||||||
|
|
||||||
|
// Apply Auto Gain Control
|
||||||
|
for chunk_r in source {
|
||||||
|
let mut chunk = chunk_r?;
|
||||||
|
agc.process_chunk(&mut chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/utils/mod.rs
Normal file
1
src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod ring_buffer;
|
||||||
78
src/utils/ring_buffer.rs
Normal file
78
src/utils/ring_buffer.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
pub struct RingBuffer<T: Copy + Default> {
|
||||||
|
pub data: Vec<T>,
|
||||||
|
|
||||||
|
// Index to next writing element
|
||||||
|
pub head: usize,
|
||||||
|
|
||||||
|
// Actual size
|
||||||
|
pub size: usize,
|
||||||
|
|
||||||
|
pub capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy + Default> RingBuffer<T> {
|
||||||
|
pub fn new(capacity: usize) -> Self {
|
||||||
|
assert!(capacity != 0);
|
||||||
|
Self {
|
||||||
|
data: vec![T::default(); capacity],
|
||||||
|
head: 0,
|
||||||
|
size: 0,
|
||||||
|
capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, value: T) {
|
||||||
|
self.data[self.head] = value;
|
||||||
|
self.head = (self.head + 1) % self.capacity;
|
||||||
|
self.size = (self.size + 1).min(self.capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> Option<T> {
|
||||||
|
if self.size == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let tail = (self.head + self.capacity - self.size) % self.capacity;
|
||||||
|
self.size -= 1;
|
||||||
|
Some(self.data[tail])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&self) -> Option<&T> {
|
||||||
|
if self.size == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let last = (self.head + self.capacity - 1) % self.capacity;
|
||||||
|
Some(&self.data[last])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read N samples before
|
||||||
|
pub fn read_at(&self, delay: usize) -> Option<&T> {
|
||||||
|
if delay >= self.size {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let index = (self.head + self.capacity - 1 - delay) % self.capacity;
|
||||||
|
Some(&self.data[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_read(&mut self, value: T, delay: usize) -> Option<T> {
|
||||||
|
let delayed = self.read_at(delay).copied();
|
||||||
|
self.write(value);
|
||||||
|
delayed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.size == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_full(&self) -> bool {
|
||||||
|
self.size == self.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.head = 0;
|
||||||
|
self.size = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user