From 347cdc9b0deefa2acb5854577003e14f340680ed Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Sun, 19 Oct 2025 22:21:28 +0200 Subject: [PATCH] GFSK experiment: Works pretty damn well --- Cargo.lock | 32 ++ Cargo.toml | 1 + send_udp.sh | 3 - src/filtering/impulse_response.rs | 35 +- src/iq.rs | 30 +- src/main.rs | 680 +++++------------------------- src/windows.rs | 14 +- 7 files changed, 189 insertions(+), 606 deletions(-) delete mode 100755 send_udp.sh diff --git a/Cargo.lock b/Cargo.lock index 2eee39c..176e11b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,6 +638,12 @@ dependencies = [ "error-code", ] +[[package]] +name = "clone_dyn_types" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6df2a33058f975d6138d1eec61d99fc005bac031e3b6ebb3c47b9b1f05dc55" + [[package]] name = "codespan-reporting" version = "0.12.0" @@ -1033,6 +1039,12 @@ dependencies = [ "emath", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "emath" version = "0.32.3" @@ -1752,6 +1764,25 @@ dependencies = [ "libc", ] +[[package]] +name = "iter_tools" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ea448170950f78f23b4aa55eb87482bd2273c842877810606af6a6542be64d" +dependencies = [ + "clone_dyn_types", + "itertools", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "jack" version = "0.13.3" @@ -2869,6 +2900,7 @@ dependencies = [ "eframe", "egui_plot", "hound", + "iter_tools", "plotters", "rand", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 2218f6c..17d8b35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ cpal = { version = "0.16.0", features = ["jack"] } eframe = "0.32.3" egui_plot = "0.33.0" hound = "3.5.1" +iter_tools = "0.39.0" plotters = "0.3.7" rand = "0.9.2" tokio = { version = "1.47.1", features = ["full", "macros", "net", "sync", "time"] } diff --git a/send_udp.sh b/send_udp.sh deleted file mode 100755 index 88acf6d..0000000 --- a/send_udp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -ffmpeg -re -i audio/modulated.wav -f s16le -acodec pcm_s16le udp://127.0.0.1:8080 diff --git a/src/filtering/impulse_response.rs b/src/filtering/impulse_response.rs index 2f1204a..d695108 100644 --- a/src/filtering/impulse_response.rs +++ b/src/filtering/impulse_response.rs @@ -1,43 +1,40 @@ // 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; ///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], + ir_length: usize, + 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..ir_length { // Get value within ifft result (centering/trimming) let k = (tf_len - (ir_length / 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 / ir_length 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]); } @@ -45,4 +42,10 @@ pub mod design fft.execute(¢ered_ir); Vec::from(fft.get_output()) } + + pub fn pi_loop(loop_p: f32, loop_i: f32, length: u32) -> Vec { + let mut v = vec![Complex32::new(loop_i, 0.); length as usize]; + v[(length - 1) as usize] = Complex32::new(loop_p * length as f32, 0.); + v + } } diff --git a/src/iq.rs b/src/iq.rs index 5da81ae..b172718 100644 --- a/src/iq.rs +++ b/src/iq.rs @@ -1,30 +1,32 @@ -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 : - let fir_length = 50; + let fir_length = 100; // 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, + fir_length, + 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 +35,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 +47,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 73be544..a1fd21e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,9 +13,11 @@ mod units; mod windows; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use egui_plot::{Legend, Line, Plot}; +use egui_plot::{Legend, Line, Plot, PlotUi}; use hound::WavWriter; -use rand::{Rng, rand_core::le, seq::index::sample}; +use iter_tools::Itertools; +use plotters::data; +use rand::{Rng, SeedableRng, rand_core::le, seq::index::sample}; use std::{ cell::{Cell, RefCell}, collections::VecDeque, @@ -40,14 +42,18 @@ use tokio::{ use crate::{ bfsk::BFSKMod, complex::Complex32, - filtering::{dc_block::DCBlocker, fir::FIRFilter}, + filtering::{dc_block::DCBlocker, fir::FIRFilter, impulse_response}, iq::IQSampler, nco::Nco, squelch::Squelch, ted::elg::ELGate, units::frequency::hz_to_rad_per_sample, + windows::gaussian, +}; +use eframe::{ + egui::{self, CentralPanel, Color32, RichText}, + glow::NUM_SHADER_BINARY_FORMATS, }; -use eframe::egui::{self, CentralPanel, Color32, RichText}; use tokio::sync::RwLock; use tokio::sync::mpsc::{Receiver, Sender, channel}; @@ -58,413 +64,9 @@ const SAMPLE_RATE: u32 = 48000; const CENTER_FREQ: f32 = 1700.; const DEVIATION: f32 = 500.; -static mut INSTANCE_ID: u32 = 0; - -pub enum SampleSenderCommand { - Open, - Close, - Sample(f32), -} - -pub trait SampleSender { - async fn open_link(&mut self); - async fn send_sample(&mut self, sample: f32); - async fn close_link(&mut self); -} - -struct WavSampleSender { - writer: Option>>, -} - -impl Default for WavSampleSender { - fn default() -> Self { - Self { writer: None } - } -} - -impl SampleSender for WavSampleSender { - async fn open_link(&mut self) { - let spec = hound::WavSpec { - channels: 1, - sample_rate: SAMPLE_RATE, - bits_per_sample: 16, - sample_format: hound::SampleFormat::Int, - }; - self.writer = Some(hound::WavWriter::create("audio/modulated.wav", spec).unwrap()); - } - - async fn send_sample(&mut self, sample: f32) { - let out_sample = (sample * i16::MAX as f32) as i16; - self.writer - .as_mut() - .unwrap() - .write_sample(out_sample) - .unwrap(); - } - - async fn close_link(&mut self) { - self.writer = None; - } -} - -struct FSKReceiver { - pos_correllator: FIRFilter, - neg_correllator: FIRFilter, - eye_sender: Sender>, - matched_lowpass: FIRFilter, - elg: ELGate, - dc_block: DCBlocker, - last_byte: u8, - frame_constructor: FrameConstructor, - bit_count: Option, -} - -impl FSKReceiver { - fn new(eye_sender: Sender>) -> Self { - let samples_per_symbol = (SAMPLE_RATE as f32) / (BAUD_RATE as f32); - - let correllator_length = samples_per_symbol as usize; - let mut pos_nco = Nco::new(hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32)); - let mut neg_nco = Nco::new(hz_to_rad_per_sample(-DEVIATION, SAMPLE_RATE as f32)); - let pos_ir = (0..correllator_length).map(|i| { - pos_nco.step(); - pos_nco.cexp() * windows::blackmann(i as f32 / correllator_length as f32) - }); - let neg_ir = (0..correllator_length).map(|i| { - neg_nco.step(); - neg_nco.cexp() * windows::blackmann(i as f32 / correllator_length as f32) - }); - let mut pos_correllator = FIRFilter::new(&pos_ir.collect::>()); - let mut neg_correllator = FIRFilter::new(&neg_ir.collect::>()); - pos_correllator.normalize_freq(hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32)); - neg_correllator.normalize_freq(hz_to_rad_per_sample(-DEVIATION, SAMPLE_RATE as f32)); - - let mut matched_lowpass = FIRFilter::new(&vec![ - Complex32::new(1., 0.); - samples_per_symbol as usize / 2 - ]); - matched_lowpass.normalize_freq(hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32)); - //let mut dc_block = DCBlocker::new(0.999); - let mut dc_block = DCBlocker::new(1.); - - 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]; - loop_ir.push(Complex32::new(loop_p, 0.)); - let mut elg = ELGate::new(samples_per_symbol, FIRFilter::new(&loop_ir)); - Self { - //iq_sampler: IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)), - pos_correllator, - neg_correllator, - matched_lowpass, - dc_block, - elg, - last_byte: 0x00u8, - frame_constructor: FrameConstructor::new(), - bit_count: None, - eye_sender, - } - } - - async fn receive(&mut self, iq: Complex32) -> Result, FrameConstructionError> { - // Frame reconstruction - let matched = - self.matched_lowpass.next_real(self.dc_block.next_real( - self.pos_correllator.next(iq).mag() - self.neg_correllator.next(iq).mag(), - )); - if let Some((bit_sample, eye)) = self.elg.next_eye(matched) { - let _ = self.eye_sender.send(eye).await; - self.last_byte >>= 1; - self.last_byte |= ((bit_sample > 0.) as u8) << 7; - //last_byte <<= 1; - //last_byte |= ((bit_sample < 0.) as u8); - self.bit_count = self.bit_count.map(|x| x + 1); - - if let None = self.bit_count - && self.last_byte == 0xD8 - { - // Potential frame starts - self.last_byte = 0; - self.frame_constructor = FrameConstructor::new(); - self.bit_count = Some(0); - } - - if let Some(8) = self.bit_count { - let frame_opt = self.frame_constructor.add_byte(self.last_byte); - self.bit_count = Some(0); - //print!("{}", last_byte as char); - print!(".{:x}.", self.last_byte); - let _ = std::io::stdout().flush(); - return frame_opt; - } - } - return Ok(None); - } -} - -#[derive(Debug)] -enum TransceiverState { - Waiting, - Receiving, - EOT, - SendingAck, - Sending, - Listening, -} - -struct Transceiver { - tx_stream: Sender>, - rx_stream: Receiver>, - - eye_receiver: Receiver>, - state_receiver: Receiver, -} - -impl Transceiver { - pub async fn send(&self, data: Vec) { - self.tx_stream.send(data).await; - } - - pub fn get_sender(&self) -> Sender> { - self.tx_stream.clone() - } - - pub fn try_recv(&mut self) -> Result, TryRecvError> { - self.rx_stream.try_recv() - } - - pub fn try_recv_eye(&mut self) -> Result, TryRecvError> { - self.eye_receiver.try_recv() - } - - pub fn try_recv_state(&mut self) -> Result { - self.state_receiver.try_recv() - } - - pub fn start(mut sample_stream: Receiver, mut sample_sender: Sender>) -> Self { - let mut resend: Option> = None; - let (mut eyes_tx, eyes_rx) = channel::>(1024); - let (mut state_tx, state_rx) = channel::(1024); - state_tx.try_send(TransceiverState::Waiting); - - let (rx_stream_sender, mut rx_stream_receiver) = channel::>(1024); - let (tx_stream_sender, mut tx_stream_receiver) = channel::>(1024); - - let receiving = Arc::new(RwLock::new(false)); - tokio::spawn(async move { - let mut squelch = Squelch::new(200, 0.1); - let mut iq_sampler = - IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)); - - let mut current_message = None; - loop { - select! { - _ = async { - while squelch.next(iq_sampler.sample(sample_stream.recv().await.unwrap())).is_none() {} - } - => - { - state_tx.try_send(TransceiverState::Receiving); - // Wait for end of tranmission - let mut recv = Some(FSKReceiver::new(eyes_tx.clone())); - let mut send_ack = false; - while let Some(iq) = squelch.next(iq_sampler.sample(sample_stream.recv().await.unwrap())) - { - if recv.as_ref().is_some() - { - match recv.as_mut().unwrap().receive(iq).await - { - Ok(Some(Frame::Data(_))) => {println!("GOT DATA"); send_ack = true; 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;}, - _ => {} - } - } - } - if send_ack - { - state_tx.try_send(TransceiverState::SendingAck); - Self::transmit(Frame::Ack, &mut sample_sender).await; - } - state_tx.try_send(TransceiverState::Waiting); - }, - message = async - { - if current_message.is_none() - { - current_message = Some((tx_stream_receiver).recv().await.unwrap()); - } - state_tx.try_send(TransceiverState::Listening); - tokio::time::sleep(Duration::from_millis(500 * rand::random_range(1..6))).await; - current_message.as_ref().unwrap() - } => - { - state_tx.try_send(TransceiverState::Sending); - println!("Sending message"); - Self::transmit(Frame::Data(message.clone()), &mut sample_sender).await; - //current_message = None; - println!("Sent message"); - state_tx.try_send(TransceiverState::Waiting); - } - }; - } - }); - - Self { - eye_receiver: eyes_rx, - state_receiver: state_rx, - - tx_stream: tx_stream_sender, - rx_stream: rx_stream_receiver, - } - } - - pub async fn transmit(frame: Frame, samples_sender: &mut Sender>) { - let bytes = frame.bytes(); - let mut bit_stream = bytes.iter().flat_map(|x| byte_to_bits(*x)); - let modulator = BFSKMod::new( - (SAMPLE_RATE as f32 / BAUD_RATE as f32).round() as u32, - hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32), - &mut bit_stream, - ); - - let up_lo = Nco::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)); - let mut samples = vec![]; - for (m, up) in modulator.zip(up_lo) { - let sample = m * up; - samples.push(sample.re); - } - let len = samples.len(); - samples_sender.send(samples).await.unwrap(); - tokio::time::sleep(Duration::from_secs_f32(len as f32 / SAMPLE_RATE as f32)).await; - } -} - -enum Frame { - Data(Vec), - Ack, -} - -type FrameConstructionError = (); -pub struct FrameConstructor { - frame: Vec, - frame_countdown: Option, - checksum: u8, - started: bool, -} - -impl FrameConstructor { - pub fn new() -> Self { - Self { - frame: Vec::new(), - frame_countdown: None, - checksum: 0u8, - started: false, - } - } - - pub fn add_byte(&mut self, byte: u8) -> Result, FrameConstructionError> { - if self.frame.is_empty() && byte != 0xC4 && byte != 0x4C && !self.started { - println!("Wrong type {:x}", byte); - self.started = true; - return Err(()); - } - - if self.frame.is_empty() && byte == 0xC4 && !self.started { - self.started = true; - return Ok(Some(Frame::Ack)); - } - - if self.frame.is_empty() && byte == 0x4C && !self.started { - self.started = true; - return Ok(None); - } - - self.frame.push(byte); - - // Retrieve length - if self.frame.len() == 1 { - self.frame_countdown = Some(self.frame[0] as u16); - return Ok(None); - } - if self.frame.len() == 2 { - *self.frame_countdown.as_mut().unwrap() |= (self.frame[1] as u16) << 8; - return Ok(None); - } - - if self.frame_countdown.unwrap() == 0 { - // All data has been received - if self.checksum == byte { - return Ok(Some(Frame::Data( - self.frame.iter().skip(2).copied().collect(), - ))); - } - - println!("Checksum failed"); - return Err(()); - } - - //self.frame.push(byte); - self.checksum ^= byte; - *self.frame_countdown.as_mut().unwrap() -= 1; - - Ok(None) - } -} - -impl Frame { - pub fn bytes(&self) -> Vec { - let mut output_bytes = vec![]; - - // Initial training sequence - output_bytes.append(&mut vec![0b01010101; 32]); - - // Preamble byte - output_bytes.push(0xD8); - - // Command - match self { - Frame::Data(x) => { - assert!(x.len() < 65536, "Data size over MTU"); - - output_bytes.push(0x4C); // DATA FRAME - - let len_u16 = x.len() as u16; - output_bytes.push((len_u16 & 0xFF).try_into().unwrap()); - output_bytes.push(((len_u16 >> 8) & 0xFF).try_into().unwrap()); - - let mut checksum = 0u8; - x.iter().for_each(|x| checksum ^= x); - output_bytes.extend(x.iter()); - - output_bytes.push(checksum); - } - Frame::Ack => { - output_bytes.push(0xC4); // ACK FRAME - } - } - - // SEND EOT - output_bytes.extend(std::iter::repeat_n(4, 16)); - output_bytes - } -} - #[tokio::main] async fn main() { // Read instance - let id = std::env::args().collect::>()[1] - .parse::() - .expect("NO INPUT ID"); - assert!(id == 0 || id == 1); - - unsafe { - INSTANCE_ID = id; - }; - - //Transceiver::transmit(Frame::Data("Skibditoilet".repeat(100).bytes().collect::>()), &mut WavSampleSender{}).await; - //Transceiver::transmit(Frame::Ack, &mut WavSampleSender::default()).await; - //return; let native_options = eframe::NativeOptions::default(); let _ = eframe::run_native( @@ -474,181 +76,127 @@ async fn main() { ); } -//#[derive(Default)] - -struct DummySampleSender(); - -impl SampleSender for DummySampleSender { - async fn open_link(&mut self) {} - async fn send_sample(&mut self, _sample: f32) {} - async fn close_link(&mut self) {} -} - struct EguiApp { - transceiver: Transceiver, - - eyes: VecDeque>, - current_state: TransceiverState, + data: Vec>, } impl EguiApp { fn new(_cc: &eframe::CreationContext<'_>) -> Self { - let (up_sender, mut up_receiver) = channel::>(16); - let (down_sender, down_receiver) = channel::(1024); - - let transceiver = Transceiver::start(down_receiver, up_sender); - - let instance_id = unsafe { INSTANCE_ID }; - tokio::task::spawn(async move { - println!("Waiting for connection ..."); - - // let socket = Arc::new( - // UdpSocket::bind(format!("0.0.0.0:{}", 9000 + instance_id)) - // .await - // .unwrap(), - // ); - // socket - // .connect(format!("127.0.0.1:{}", 9000 + (1 - instance_id))) - // .await - // .unwrap(); - - // Receiving end - let host = cpal::default_host(); - - let device = host.default_input_device().expect("No input device"); - let mut config = device - .supported_input_configs() - .unwrap() - .next() - .unwrap() - .with_sample_rate(cpal::SampleRate(48000)); - - let stream = device - .build_input_stream( - &config.into(), - move |data: &[f32], _| { - for x in data.iter() { - let _ = down_sender.blocking_send(*x * 30.); // non-blocking send - } - }, - move |err| eprintln!("Stream error: {}", err), - None, - ) - .unwrap(); - stream.play().unwrap(); - - let device = host.default_output_device().unwrap(); - let mut supported_configs_range = device.supported_output_configs().unwrap(); - let supported_config = supported_configs_range - .find(|config| { - config.sample_format() == cpal::SampleFormat::F32 - && config.min_sample_rate().0 <= 48000 - && config.max_sample_rate().0 >= 48000 - }) - .expect("Device does not support 48kHz f32 output"); - let config = supported_config - .with_sample_rate(cpal::SampleRate(48_000)) - .config(); - - while let Some(stream) = up_receiver.recv().await { - let stream_len = stream.len(); - let progression = Arc::new(AtomicU64::new(0)); - let (finished_tx, mut finished_rx) = channel::<()>(16); - - let send_stream = device - .build_output_stream( - &config, - move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { - for d in data.iter_mut() { - if progression.load(std::sync::atomic::Ordering::Relaxed) as usize - == stream.len() - { - let _ = finished_tx.blocking_send(()); - break; - } - - *d = stream[progression - .fetch_add(1, std::sync::atomic::Ordering::Relaxed) - as usize] - * 0.1; // TODO - } - }, - move |err| { - eprintln!("Stream error: {}", err); - }, - None, - ) - .unwrap(); - send_stream.play().unwrap(); - let _ = finished_rx.recv().await; - } - }); - - EguiApp { - transceiver, - - eyes: VecDeque::new(), - current_state: TransceiverState::Waiting, - } + //let modulated = modulate(); + //println!("{}", modulated.len()); + EguiApp { data: demodulate() } } } impl eframe::App for EguiApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { - let max_eyes = 100; - - while let Ok(eye) = self.transceiver.try_recv_eye() { - self.eyes.push_back(eye); - } - while self.eyes.len() > max_eyes { - self.eyes.pop_front(); - } - if let Ok(new_state) = self.transceiver.try_recv_state() { - self.current_state = new_state; - } - - ui.horizontal(|ui| { - if ui.button("Start").clicked() { - let snd = self.transceiver.get_sender(); - let data = (0..rand::random_range(50..250)) - .map(|_| rand::random::() as u8) - .collect::>(); - - tokio::spawn(async move { - let _ = snd.send(data).await; - }); - } - - ui.label( - RichText::new(format!("{:?}", self.current_state)) - .size(35.) - .color(Color32::LIGHT_GREEN), - ); - }); - - Plot::new("EyeA") - .legend(Legend::default()) - .show(ui, |plot_ui| { - //plot_ui.set_auto_bounds(Vec2b { x: false, y: false }); - for eye in self.eyes.iter() { - let line = Line::new( - "EyeA", + CentralPanel::default().show(ctx, |ui| { + Plot::new("Plot").show(ui, |plot_ui| { + for eye in self.data.iter().skip(BAUD_RATE as usize) { + plot_ui.line( + Line::new( + "Bitstream", eye.iter() .enumerate() .map(|(i, x)| [i as f64, *x as f64]) .collect::>(), ) - .color(Color32::LIGHT_GREEN); - plot_ui.line(line); - } - }); - }); // Central panel - - std::thread::sleep(Duration::from_millis(16)); - ctx.request_repaint(); + .color(Color32::GREEN), + ) + } + }); + }); } } +fn modulate() -> Vec { + let sample_per_symbols = SAMPLE_RATE / BAUD_RATE; + let stream_len = (4 * SAMPLE_RATE) / sample_per_symbols; + let mut rng = rand::rngs::SmallRng::from_seed([0; 32]); + let data_stream = (0..stream_len) + .map(|_| rng.random::()) + .collect::>(); + + let bitstream = (0..(stream_len * sample_per_symbols)).map(|i| { + if data_stream[(i / sample_per_symbols) as usize] { + 1. + } else { + -1. + } + }); + + // Synthesise impulse response + let mut impulse_response = + vec![Complex32::zero(); sample_per_symbols as usize].into_boxed_slice(); + for (i, x) in impulse_response.iter_mut().enumerate() { + *x = Complex32::new(gaussian(0.3, i as f32 / sample_per_symbols as f32), 0.); + } + + let mut gaussian_filter = FIRFilter::new(&impulse_response); + gaussian_filter.normalize_dc(); + let filtered_bitstream = bitstream.map(|x| gaussian_filter.next_real(x)); + //let filtered_bitstream = bitstream; + + let mut nco = Nco::new(0.); + let mut lo = Nco::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)); + + // Save to wav + let spec = hound::WavSpec { + channels: 1, + sample_rate: SAMPLE_RATE, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + + let mut writer = hound::WavWriter::create("audio/gfsk.wav", spec).unwrap(); + + let res = filtered_bitstream + .map(|f| { + nco.set_frequency(hz_to_rad_per_sample(f * DEVIATION, SAMPLE_RATE as f32)); + nco.step_n(1); + lo.step_n(1); + let val = (nco.cexp() * lo.cexp()).re; + writer.write_sample((val * i16::MAX as f32) as i16).unwrap(); + val + }) + .collect::>(); + writer.finalize().unwrap(); + res +} + +fn demodulate() -> Vec> { + let samples_per_symbol = SAMPLE_RATE / BAUD_RATE; + let mut reader = hound::WavReader::open("audio/gfsk_rec.wav").unwrap(); + let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)); + let mut phase_lowpass = FIRFilter::new(&vec![ + Complex32::new(1.0, 0.); + (samples_per_symbol) as usize + ]); + phase_lowpass.normalize_dc(); + let mut elgate_filter = FIRFilter::new(&impulse_response::design::pi_loop( + 0.001, + 0., + samples_per_symbol, + )); + elgate_filter.normalize_dc(); + let mut elgate = ELGate::new(samples_per_symbol as f32, elgate_filter); + let samples = reader + .samples::() + .map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32)) + .tuple_windows() + .map(|(x, y)| (x * y.conj()).arg()) + .map(|x| phase_lowpass.next_real(x)) + .collect::>(); + + let mut eyes = vec![]; + for x in samples { + if let Some((_, eye)) = elgate.next_eye(x) { + eyes.push(eye); + } + } + println!("{}", eyes.len()); + eyes +} + fn byte_to_bits(byte: u8) -> Vec { vec![ byte & 1 == 1, diff --git a/src/windows.rs b/src/windows.rs index f0309e9..0d18945 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -10,18 +10,20 @@ pub fn bartlett(t: f32) -> f32 { if t < 0.5 { 2. * t } else { 2. - 2. * t } } -pub fn hann(t: f32) -> f32 -{ +pub fn hann(t: f32) -> f32 { 0.5 - 0.5 * (2. * PI * t).cos() } -pub fn hamming(t: f32) -> f32 -{ +pub fn hamming(t: f32) -> f32 { 0.54 - 0.46 * (2. * PI * t).cos() } -pub fn blackmann(t: f32) -> f32 -{ +pub fn blackmann(t: f32) -> f32 { let x = 2. * PI * t; 0.45 - 0.5 * x.cos() + 0.08 * (2. * x).cos() } + +pub fn gaussian(sigma: f32, t: f32) -> f32 { + let sq = (t - 0.5) / sigma; + (-sq * sq).exp() +}