Encapsulated early late gate

This commit is contained in:
2025-10-03 01:46:57 +02:00
parent cf527a13f2
commit 8a51c4489c
3 changed files with 106 additions and 41 deletions

View File

@ -2,7 +2,7 @@
use std::collections::VecDeque;
use crate::complex::Complex32;
use crate::{complex::Complex32, filtering::impulse_response};
pub struct FIRFilter {
size: usize,
@ -13,8 +13,6 @@ pub struct FIRFilter {
impl FIRFilter {
pub fn new(impulse_response: &[Complex32]) -> Self {
let normalization = impulse_response.iter().copied().sum::<Complex32>().mag(); // DC normalization
println!("normalization factor {}", normalization);
FIRFilter {
size: impulse_response.len(),
impulse_response: impulse_response.iter().copied().collect(),
@ -25,12 +23,17 @@ impl FIRFilter {
.reduce(|acc, e| acc.max(e))
.unwrap(),
*/
normalization: normalization.max(0.001), // DC normalization
normalization: 1., // DC normalization
// TODO: Maybe we'd want other types of normalization (per frequency)
taps: VecDeque::from(vec![Complex32::zero(); impulse_response.len()]),
}
}
pub fn normalize_sum(&mut self)
{
self.normalization = self.impulse_response.iter().copied().sum::<Complex32>().mag()
}
pub fn next(&mut self, next: Complex32) -> Complex32 {
let _ = self.taps.pop_front();

View File

@ -26,11 +26,15 @@ impl IQSampler
}
let ir = crate::filtering::impulse_response::design::ir_from_transfer_function(&transfer_function, fir_length, windows::blackmann);
let mut low_pass_i = FIRFilter::new(&ir);
let mut low_pass_q = FIRFilter::new(&ir);
low_pass_i.normalize_sum();
low_pass_q.normalize_sum();
IQSampler {
local_oscillator: Nco::new(center_freq),
low_pass_i: FIRFilter::new(&ir),
low_pass_q: FIRFilter::new(&ir)
low_pass_i,
low_pass_q
}
}

View File

@ -38,7 +38,7 @@ use nco::Nco;
use eframe::egui::{self, Color32, Context, Vec2b, debug_text::print};
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints, VLine};
use plotters::style::Color;
use rand::Rng;
use rand::{seq::index::sample, Rng};
use crate::{
bfsk::BFSKDem,
@ -81,6 +81,7 @@ fn main() {
let (tx, rx) = sync_channel::<f32>(4096);
// Build input stream
/*
let stream = device
.build_input_stream(
&config.into(),
@ -94,12 +95,12 @@ fn main() {
)
.unwrap();
stream.play().unwrap();
*/
let (eye_tx, eye_rx) = mpsc::channel::<Vec<f32>>();
let (ctx_tx, ctx_rx) = mpsc::channel::<Arc<egui::Context>>();
std::thread::spawn(move || demodulator(rx, eye_tx, ctx_rx));
/*
std::thread::spawn(move || {
let spec = hound::WavSpec {
channels: 1,
@ -124,7 +125,6 @@ fn main() {
}
writer.finalize().unwrap();
});
*/
let native_options = eframe::NativeOptions::default();
let _ = eframe::run_native(
@ -134,6 +134,75 @@ fn main() {
);
}
// Early late gate
struct ELGate
{
samples_per_symbol: f32,
buffer: VecDeque<f32>, // Store baseband, matched filtered samples,
loop_filter: FIRFilter,
delta: f32,
next_sample: f32,
current_position: f32,
}
impl ELGate
{
pub fn new(samples_per_symbol: f32, loop_filter: FIRFilter) -> Self
{
Self
{
samples_per_symbol,
loop_filter,
buffer: VecDeque::with_capacity(2 * samples_per_symbol.ceil() as usize),
delta: 0.5,
next_sample: samples_per_symbol,
current_position: 0.
}
}
pub fn next(&mut self, sample: f32) -> Option<bool>
{
Some(self.next_eye(sample)?.0) // Ignore eye
}
pub fn next_eye(&mut self, sample: f32) -> Option<(bool, Vec<f32>)>
{
self.buffer.push_front(sample);
self.current_position += 1.;
if self.current_position >= self.next_sample
{
// Sample center, early late
let early_id = (self.samples_per_symbol / 2. + self.samples_per_symbol * self.delta).floor().min(self.buffer.len() as f32 - 1.) as usize;
let late_id = (self.samples_per_symbol / 2. - self.samples_per_symbol * self.delta).floor().max(0.) as usize;
let sample_id = (self.samples_per_symbol / 2.) as usize;
let early_sample = self.buffer[early_id];
let late_sample = self.buffer[late_id];
let sample = self.buffer[sample_id];
let error = (early_sample - late_sample) * sample;
// Remove until only current sample is in buffer (the next sample might need data from
// current sample if error advances sample position)
while self.buffer.len() > self.samples_per_symbol as usize
{
let _ = self.buffer.pop_back();
}
// Predict next length based on error
self.current_position = self.next_sample - self.next_sample.floor();
self.next_sample = self.samples_per_symbol - self.loop_filter.next_real(error);
Some((sample > 0., Vec::from(self.buffer.clone())))
}
else
{
None
}
}
}
fn demodulator(
rx: Receiver<f32>,
eye_sender: Sender<Vec<f32>>,
@ -173,18 +242,26 @@ fn demodulator(
.collect::<Vec<_>>();
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
matched_filter_pos.normalize_sum();
matched_filter_neg.normalize_sum();
let mut elg_buffer = VecDeque::new();
let mut sps = sample_per_symbol as f32;
let delta = 0.5;
let loop_p = 0.8;
let loop_i = 0.2;
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 30]);
let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 20]);
let mut current_position = 0.;
let mut next_sample = (sps as f32) / 2.;
let loop_p = 0.3;
let loop_i = 0.1;
let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 40]);
matched_filter.normalize_sum();
//let mut dem = vec![];
let mut loop_filter_ir = vec![Complex32::new(loop_i, 0.); 30];
let len = loop_filter_ir.len();
//loop_filter_ir[0] = Complex32::new(loop_p + loop_i, 0.);
loop_filter_ir[len - 1] = Complex32::new(loop_p + loop_i, 0.);
let mut loop_filter = FIRFilter::new(&loop_filter_ir);
//loop_filter.normalize_sum();
let mut elg = ELGate::new(
sample_per_symbol as f32,
loop_filter
);
// Timing recovery
while let Ok(real_sample) = rx.recv() {
let iq = iq_sampler.sample(real_sample);
@ -194,30 +271,11 @@ fn demodulator(
let pos_energy = matched_filter_pos.next(iq);
let matched = matched_filter.next_real(pos_energy.mag() - neg_energy.mag());
//dem.push(matched);
elg_buffer.push_front(matched);
current_position += 1.;
if current_position >= next_sample + sps / 2. {
// Compute current error
let early_id = (((sps / 2.) + sps * delta) as usize).min(elg_buffer.len() - 1);
let late_id = (((sps / 2.) - sps * delta) as usize).max(0);
let error =
elg_buffer[(sps / 2.) as usize] * (elg_buffer[early_id] - elg_buffer[late_id]);
next_sample +=
sps - loop_p * error - loop_i * loop_filter.next(Complex32::new(error, 0.)).re;
while elg_buffer.len() > sps as usize {
elg_buffer.pop_back();
}
let _ = eye_sender.send(Vec::from(elg_buffer.clone()));
//elg_buffer.clear();
if let Some((_, eye)) = elg.next_eye(matched)
{
let _ = eye_sender.send(eye);
ctx.request_repaint();
/*
if dem.len() > 10_000 {
let _ = eye_sender.send(dem.clone());
break;
}
*/
}
}
}