diff --git a/src/filtering/fir.rs b/src/filtering/fir.rs index e2ae6c9..fcc40fd 100644 --- a/src/filtering/fir.rs +++ b/src/filtering/fir.rs @@ -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::().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::().mag() + } + pub fn next(&mut self, next: Complex32) -> Complex32 { let _ = self.taps.pop_front(); diff --git a/src/iq.rs b/src/iq.rs index 7df612f..e9cbc75 100644 --- a/src/iq.rs +++ b/src/iq.rs @@ -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 } } diff --git a/src/main.rs b/src/main.rs index 32b681b..bd1fdff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::(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::>(); let (ctx_tx, ctx_rx) = mpsc::channel::>(); 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, // 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 + { + Some(self.next_eye(sample)?.0) // Ignore eye + } + + pub fn next_eye(&mut self, sample: f32) -> Option<(bool, Vec)> + { + 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, eye_sender: Sender>, @@ -173,18 +242,26 @@ fn demodulator( .collect::>(); 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; - } - */ } } }