diff --git a/src/filtering/impulse_response.rs b/src/filtering/impulse_response.rs index 2f1204a..8274c1d 100644 --- a/src/filtering/impulse_response.rs +++ b/src/filtering/impulse_response.rs @@ -1,43 +1,44 @@ // Utilities for impulse response design -pub mod design -{ +pub mod design { + use crate::complex::Complex32; use crate::fft::FFT; use crate::windows::{self, Window}; - use crate::complex::Complex32; + + // Completely stolen from sdrpp dsp code + pub fn estimate_fir_length(transition_width: f32, sample_rate: f32) -> f32 { + 3.8 * sample_rate / transition_width + } ///Designs a impulse response from a desired transfer function using windowing technique - pub fn ir_from_transfer_function(transfer_function: &[Complex32], ir_length: usize, window: Window) -> Vec - { + pub fn ir_from_transfer_function( + transfer_function: &[Complex32], + window: Window, + ) -> Vec { let tf_len = transfer_function.len(); let mut ifft = FFT::new_inv(tf_len); - + // Compute ideal convolution kernel/impulse response ifft.execute(transfer_function); // Shorten and window let mut ir = vec![]; - for i in 0..ir_length - { + for i in 0..tf_len { // Get value within ifft result (centering/trimming) - let k = (tf_len - (ir_length / 2) + i) % tf_len; + let k = (tf_len - (tf_len / 2) + i) % tf_len; // Windowing - ir.push( - ifft.get_output()[k] * window(i as f32 / ir_length as f32) / tf_len as f32 - ); + ir.push(ifft.get_output()[k] * window(i as f32 / tf_len as f32) / tf_len as f32); } ir } - pub fn frequency_response(impulse_response: &[Complex32]) -> Vec - { + pub fn frequency_response(impulse_response: &[Complex32]) -> Vec { let len = impulse_response.len(); let mut fft = FFT::new(len, windows::rectangular); // Recenter impulse response - let mut centered_ir = vec![]; - for i in 0..len - { + let mut centered_ir = vec![]; + for i in 0..len { let k = (len / 2) + i; centered_ir.push(impulse_response[k % len]); } diff --git a/src/iq.rs b/src/iq.rs index 5da81ae..5e38d23 100644 --- a/src/iq.rs +++ b/src/iq.rs @@ -1,15 +1,13 @@ -use std::f32::consts::PI; use crate::{complex::Complex32, filtering::fir::FIRFilter, math::map, nco::Nco, windows}; +use std::f32::consts::PI; -pub struct IQSampler -{ +pub struct IQSampler { local_oscillator: Nco, low_pass_i: FIRFilter, low_pass_q: FIRFilter, } -impl IQSampler -{ +impl IQSampler { pub fn new(center_freq: f32) -> Self { // Design a lowpass filter that cuts off at the center freq // Estimate FIR length : @@ -17,14 +15,17 @@ impl IQSampler // Ideal transfer function : let mut transfer_function = vec![Complex32::zero(); fir_length]; - let bin_id = map(center_freq, 0., PI, 0., transfer_function.len() as f32 / 2.).floor() as usize; - for i in 0..bin_id - { + let bin_id = + map(center_freq, 0., PI, 0., transfer_function.len() as f32 / 2.).floor() as usize; + for i in 0..bin_id { transfer_function[i] = Complex32::new(1., 0.); transfer_function[fir_length - 1 - i] = Complex32::new(1., 0.); } - let ir = crate::filtering::impulse_response::design::ir_from_transfer_function(&transfer_function, fir_length, windows::blackmann); + let ir = crate::filtering::impulse_response::design::ir_from_transfer_function( + &transfer_function, + windows::blackmann, + ); let mut low_pass_i = FIRFilter::new(&ir); let mut low_pass_q = FIRFilter::new(&ir); low_pass_i.normalize_dc(); @@ -33,12 +34,11 @@ impl IQSampler IQSampler { local_oscillator: Nco::new(center_freq), low_pass_i, - low_pass_q + low_pass_q, } } - pub fn sample(&mut self, input_sample: f32) -> Complex32 - { + pub fn sample(&mut self, input_sample: f32) -> Complex32 { let i_mixed = self.local_oscillator.cexp().re * input_sample; let q_mixed = self.local_oscillator.cexp().im * input_sample; self.local_oscillator.step(); @@ -46,8 +46,7 @@ impl IQSampler // TODO: Could use one filter for both I and Q Complex32::new( self.low_pass_i.next(Complex32::new(i_mixed, 0.)).re, - self.low_pass_q.next(Complex32::new(q_mixed, 0.)).re + self.low_pass_q.next(Complex32::new(q_mixed, 0.)).re, ) * 2. - } } diff --git a/src/main.rs b/src/main.rs index de77b6c..dd9cf6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use std::{ cell::{Cell, RefCell}, collections::VecDeque, env::{self, args}, + f32::consts::PI, fmt::Display, fs::File, io::{BufWriter, Read, Sink, Write, stdout}, @@ -41,8 +42,16 @@ use tun_tap::Iface; use crate::{ bfsk::BFSKMod, complex::Complex32, - filtering::{dc_block::DCBlocker, fir::FIRFilter}, + filtering::{ + dc_block::DCBlocker, + fir::FIRFilter, + impulse_response::{ + self, + design::{estimate_fir_length, frequency_response}, + }, + }, iq::IQSampler, + math::map, nco::Nco, squelch::Squelch, ted::elg::ELGate, @@ -111,6 +120,7 @@ impl SampleSender for WavSampleSender { struct FSKReceiver { eye_sender: Sender>, phase_lowpass: FIRFilter, + baseband_filter: FIRFilter, elg: ELGate, last_byte: u8, frame_constructor: FrameConstructor, @@ -130,12 +140,35 @@ impl FSKReceiver { let loop_i = 0.03; let loop_p = 0.1; - let mut loop_ir = vec![Complex32::new(loop_i, 0.); samples_per_symbol as usize / 2]; + let mut loop_ir = vec![Complex32::new(loop_i, 0.); samples_per_symbol as usize / 4]; loop_ir.push(Complex32::new(loop_p, 0.)); let elg = ELGate::new(samples_per_symbol, FIRFilter::new(&loop_ir)); + + // Baseband filter + let bbf_length = estimate_fir_length(500., SAMPLE_RATE as f32).floor() as usize; + let mut frequency_response = vec![Complex32::zero(); bbf_length].into_boxed_slice(); + let cutoff_bin = map( + hz_to_rad_per_sample(DEVIATION + 100., SAMPLE_RATE as f32), + 0., + 2. * PI, + 0., + bbf_length as f32, + ) + .floor() as usize; + + // Design transfer function + for i in 0..cutoff_bin { + frequency_response[i] = Complex32::new(1., 0.); + frequency_response[bbf_length - 1 - i] = Complex32::new(1., 0.); + } + Self { //iq_sampler: IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)), phase_lowpass, + baseband_filter: FIRFilter::new(&impulse_response::design::ir_from_transfer_function( + &frequency_response, + windows::blackmann, + )), elg, last_byte: 0x00u8, frame_constructor: FrameConstructor::new(), @@ -147,10 +180,11 @@ impl FSKReceiver { async fn receive(&mut self, iq: Complex32) -> Result, FrameConstructionError> { // Frame reconstruction + let filtered_bb = self.baseband_filter.next(iq); let dphi = self .phase_lowpass - .next_real((self.last_sample * iq.conj()).arg()); - self.last_sample = iq; + .next_real((self.last_sample * filtered_bb.conj()).arg()); + self.last_sample = filtered_bb; if let Some((bit_sample, eye)) = self.elg.next_eye(dphi) { let _ = self.eye_sender.send(eye).await; self.last_byte >>= 1; @@ -253,7 +287,13 @@ impl Transceiver { { match recv.as_mut().unwrap().receive(iq).await { - Ok(Some(Frame::Data(dat))) => {rx_stream_sender.try_send(dat); println!("GOT DATA"); send_ack = false; recv = None; state_tx.try_send(TransceiverState::EOT);}, + Ok(Some(Frame::Data(dat))) => { + println!("GOT DATA"); + let _ = rx_stream_sender.try_send(dat); + send_ack = false; + recv = None; + state_tx.try_send(TransceiverState::EOT); + }, //Ok(Some(Frame::Ack)) => {current_message = None; recv = None; state_tx.try_send(TransceiverState::EOT);}, Err(()) => {recv = None;}, _ => {} @@ -460,6 +500,10 @@ impl Frame { #[tokio::main] async fn main() { // Read instance + println!( + "fir length: {}", + impulse_response::design::estimate_fir_length(1000., 48000.) + ); let id = std::env::args().collect::>()[1] .parse::() .expect("NO INPUT ID"); @@ -579,7 +623,8 @@ impl EguiApp { *d = stream[progression .fetch_add(1, std::sync::atomic::Ordering::Relaxed) - as usize]; + as usize] + * 0.; } }, move |err| { @@ -611,6 +656,7 @@ impl eframe::App for EguiApp { // INTERFACE let mut frame = [0u8; 2000]; while let Ok(length) = self.iface.recv(&mut frame) { + break; let _ = self .transceiver .get_sender()