diff --git a/Cargo.lock b/Cargo.lock index 5b7ccd5..dc14f3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3330,6 +3330,18 @@ dependencies = [ "mio", "pin-project-lite", "slab", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7a14e2d..e6cb8ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,4 @@ egui_plot = "0.33.0" hound = "3.5.1" plotters = "0.3.7" rand = "0.9.2" -tokio = "1.47.1" +tokio = { version = "1.47.1", features = ["macros", "sync", "time"] } diff --git a/a.jpg b/a.jpg deleted file mode 100644 index edb51b9..0000000 Binary files a/a.jpg and /dev/null differ diff --git a/main.rs b/main.rs deleted file mode 100644 index f42dad3..0000000 --- a/main.rs +++ /dev/null @@ -1,525 +0,0 @@ -#![allow(dead_code)] - -use std::{ - collections::VecDeque, - f32::consts::PI, - fs::File, - i16, - io::{self, Read, Write, stdout}, - ops::{Add, Div, Mul, Sub}, - os::unix::thread, - sync::{ - Arc, - atomic::{AtomicU32, Ordering}, - mpsc::{self, Receiver, Sender, TryRecvError, channel, sync_channel}, - }, - time::Duration, -}; - -mod bfsk; -mod complex; -pub mod fft; -mod filtering; -mod iq; -mod nco; -mod units; -mod windows; -mod ted; - -use bfsk::BFSKMod; -use complex::Complex; -use complex::Complex32; -use cpal::{ - SampleRate, - traits::{DeviceTrait, HostTrait, StreamTrait}, -}; -use fft::DFTAlgorithm; -use nco::Nco; - -use eframe::{ - egui::{self, Color32, Context, Vec2b, debug_text::print, decode_animated_image_uri}, - glow::TOP_LEVEL_ARRAY_STRIDE, -}; -use egui_plot::{ - self, AxisHints, Bar, BarChart, HLine, Legend, Line, LineStyle, Plot, PlotPoints, VLine, -}; -use plotters::style::Color; -use rand::{Rng, seq::index::sample}; - -use crate::{ - bfsk::BFSKDem, fft::{dft::NaiveDFT, FFT}, filtering::{ - dc_block::DCBlocker, - fir::FIRFilter, - impulse_response::design::{self, frequency_response, ir_from_transfer_function}, - }, iq::IQSampler, ted::elg::ELGate, units::frequency::{self, hz_to_rad_per_sample} -}; - -// Utilities -fn map(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T -where - T: Clone + Add + Mul + Sub + Div, -{ - ((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min -} -const BAUD_RATE: u32 = 1000; - -fn main() { - //modulate(); - //demodulate(); - //return; - // Set up CPAL - let host = cpal::default_host(); - - let device = host - .default_input_device() - .expect("No input device available"); - let mut config = device - .supported_input_configs() - .unwrap() - .next() - .unwrap() - .with_sample_rate(SampleRate(48000)); - - // Channel to move samples from callback to main thread - let (tx, rx) = sync_channel::(4096); - - // Build input stream - /* - let stream = device - .build_input_stream( - &config.into(), - move |data: &[f32], _| { - for x in data.iter() { - let _ = tx.send(*x * 30.); // non-blocking send - } - }, - move |err| eprintln!("Stream error: {}", err), - None, - ) - .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, - sample_rate: 48000, - bits_per_sample: 16, - sample_format: hound::SampleFormat::Int, - }; - - let mut writer = hound::WavWriter::create("audio/noised.wav", spec).unwrap(); - - let mut reader = hound::WavReader::open("audio/rec.wav").unwrap(); - let mut rand = rand::rng(); - let samples = reader.samples::(); - for x in samples { - let noise = rand.random::() * 2. - 1.; - let sample = x.unwrap() as f32 / i16::MAX as f32 + noise * 0.9; - let _ = tx.send(sample); - writer - .write_sample((sample * i16::MAX as f32) as i16) - .unwrap(); - std::thread::sleep(Duration::from_micros(21)); - } - writer.finalize().unwrap(); - }); - - let native_options = eframe::NativeOptions::default(); - let _ = eframe::run_native( - "Egui", - native_options, - Box::new(|cc| Ok(Box::new(EguiApp::new(cc, eye_rx, ctx_tx)))), - ); -} - - -fn demodulator( - rx: Receiver, - eye_sender: Sender>, - ctx_rx: Receiver>, -) { - // Wait for egui context to request redraw - let ctx = ctx_rx.recv().unwrap(); - - // Modulation parameters - let frequency = 1700.; - let deviation = 500.; - - // Data parameters - let sample_rate = 48000; - let baud_rate = BAUD_RATE; - let sample_per_symbol = sample_rate / baud_rate; - - let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32)); - - // Corellators - let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32)); - let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32)); - let corellator_length = (sample_per_symbol as f32 * 1.5) as usize; - let corellator_pos = (0..corellator_length) - .map(|i| { - nco_pos.step(); - nco_pos.cexp() * windows::blackmann(i as f32 / (corellator_length as f32)) - //nco_pos.cexp() - }) - .collect::>(); - let corellator_neg = (0..corellator_length) - .map(|i| { - nco_neg.step(); - nco_neg.cexp() * windows::blackmann(i as f32 / (corellator_length as f32)) - //nco_neg.cexp() - }) - .collect::>(); - let mut matched_filter_pos = FIRFilter::new(&corellator_pos); - let mut matched_filter_neg = FIRFilter::new(&corellator_neg); - matched_filter_pos.normalize_freq(hz_to_rad_per_sample(deviation,sample_rate as f32)); - matched_filter_neg.normalize_freq(hz_to_rad_per_sample(deviation,sample_rate as f32)); - - let loop_p = 0.003; - let loop_i = 0.001; - let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 20]); - matched_filter.normalize_dc(); - - 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_dc(); - //loop_filter.normalize_sum(); - let mut elg = ELGate::new(sample_per_symbol as f32, loop_filter); - - let mut dc_blocker = DCBlocker::new(0.999); - - // Timing recovery - let mut preamble_count = 0; - let mut bit_index = 0; - let mut last = 0u8; - while let Ok(real_sample) = rx.recv() { - let iq = iq_sampler.sample(real_sample); - - // Perform corellation - let neg_energy = matched_filter_neg.next(iq); - let pos_energy = matched_filter_pos.next(iq); - - let matched = - matched_filter.next_real(dc_blocker.next_real(pos_energy.mag() - neg_energy.mag())); - - if let Some((elg_sample, eye)) = elg.next_eye(matched) { - let _ = eye_sender.send(eye); - ctx.request_repaint(); - - //last >>= 1; - //last |= (!bit as u8) << 7; - let bit = elg_sample > 0.; - if elg_sample * elg_sample > 0.005 { - last >>= 1; - last |= (bit as u8) << 7; - //last <<= 1; - //last |= ((bit) as u8); - - if preamble_count >= 2 { - bit_index += 1; - if bit_index >= 8 { - if last == 4 { - print!(" -- EOT"); - println!(); - preamble_count = 0; - bit_index = 0; - last = 0; - } else { - print!("{}", last as char); - bit_index = 0; - let _ = stdout().flush(); - } - } - } else if last == 0xD8 { - preamble_count += 1; - if preamble_count == 2 { - println!("Incoming: "); - } - } - } - } - } -} - -//#[derive(Default)] -struct EguiApp { - eye_rx: Receiver>, - eyes: VecDeque>, -} - -impl EguiApp { - fn new( - cc: &eframe::CreationContext<'_>, - eye_rx: Receiver>, - ctx_tx: Sender>, - ) -> Self { - ctx_tx.send(Arc::new(cc.egui_ctx.clone())).unwrap(); - - Self { - eye_rx, - eyes: VecDeque::new(), - } - } -} - -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.eye_rx.try_recv() { - self.eyes.push_back(eye); - } - - while self.eyes.len() > max_eyes { - self.eyes.pop_front(); - } - - let axis_hints = AxisHints::new_x().min_thickness(2.); - - Plot::new("Eye") - .legend(Legend::default()) - .show_axes(Vec2b::TRUE) - .custom_x_axes(vec![axis_hints]) - .show(ui, |plot_ui| { - //plot_ui.set_auto_bounds(Vec2b { x: false, y: false }); - plot_ui.hline(HLine::new("", 0.).color(Color32::DARK_RED).width(2.)); - for eye in self.eyes.iter() { - let line = Line::new( - "Eye", - eye.iter() - .enumerate() - .map(|(i, x)| [i as f64, *x as f64]) - .collect::>(), - ) - .color(Color32::LIGHT_GREEN); - plot_ui.line(line); - } - }) - }); - } -} - -fn demodulate() { - let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap(); - let samples = reader.samples::(); - - // Modulation parameters - let frequency = 1700.; - let deviation = 500.; - - // Data parameters - let sample_rate = 48000; - let baud_rate = BAUD_RATE; - let sample_per_symbol = sample_rate / baud_rate; - - let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32)); - let iq = samples - .map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32)) - .collect::>(); - - let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32)); - let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32)); - let corellator_pos = (0..(sample_per_symbol * 1)) - .map(|_| { - nco_pos.step(); - nco_pos.cexp() - }) - .collect::>(); - let corellator_neg = (0..(sample_per_symbol * 1)) - .map(|_| { - nco_neg.step(); - nco_neg.cexp() - }) - .collect::>(); - - let mut matched_filter_pos = FIRFilter::new(&corellator_pos); - let mut matched_filter_neg = FIRFilter::new(&corellator_neg); - - let mut dem = vec![]; - - for x in &iq { - let pos = matched_filter_pos.next(x.clone()); - let neg = matched_filter_neg.next(*x); - dem.push(pos.mag() - neg.mag()); - } - - // Symbol recovery - - let mut bits = vec![]; - let delta = 0.5; - let alpha = 0.01; - let mut current_sps = sample_per_symbol as f32; - let mut current_position = current_sps / 2.; - - while current_position < dem.len() as f32 { - // Sample before after - let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32; - let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32; - if late_id as usize >= dem.len() { - break; - } - let early = dem[early_id as usize]; - let late = dem[late_id as usize]; - let error = early * early - late * late; - current_sps -= alpha * error; - - bits.push(dem[current_position.floor() as usize] > 0.); - - current_position += current_sps; - } - - //assert!(bits.len() % 8 == 0); - - let mut out_file = File::create("out.txt").unwrap(); - let mut strip = 0; - - let bit_slice = bits.as_slice(); - for i in 0..100 { - let byte = bits_to_byte(&bit_slice[(i as usize)..(i as usize + 8)]); - if byte == 0b01010111u8 { - strip = i + 8; - } - } - - for i in 0..strip { - bits.remove(i as usize); - } - for x in bits.chunks(8) { - if x.len() != 8 { - break; - } - out_file.write_all(&[bits_to_byte(x)]).unwrap(); - } -} - -fn modulate() { - // Modulation parameters - let frequency = 1700.; - let deviation = 500.; - - // Data parameter - let sample_rate = 48000; - let baud_rate = BAUD_RATE; - - let host = cpal::default_host(); - 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(); - - loop { - let mut buffer = String::new(); - let stdin = io::stdin(); // We get `Stdin` here. - stdin.read_line(&mut buffer).unwrap(); - - for c in buffer.bytes() { - print!("{}", c as char); - } - println!(); - - // Construct payload - let mut bitstream = std::iter::repeat_n(0b01010101u8, 64) - .chain(std::iter::repeat_n(0xD8, 2)) - .chain(buffer.bytes()) - .chain(std::iter::repeat_n(4u8, 32)) - .flat_map(byte_to_bits); - - let mut modulator = BFSKMod::new( - sample_rate / baud_rate, - units::frequency::hz_to_rad_per_sample(deviation, sample_rate as f32), - &mut bitstream, - ); - - let mut lo = Nco::new(units::frequency::hz_to_rad_per_sample( - frequency, - sample_rate as f32, - )); - - // To send - let stream = modulator - .zip(lo) - .map(|(s, up)| (s * up).re) - .collect::>(); - - let sample_clock = Arc::new(AtomicU32::new(0)); - let (tx, rx) = channel::<()>(); - - let stream = device - .build_output_stream( - &config, - move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { - for d in data.iter_mut() { - if sample_clock.load(Ordering::Relaxed) as usize == stream.len() { - tx.send(()).unwrap(); - break; - } - *d = stream[sample_clock.fetch_add(1, Ordering::Relaxed) as usize] - } - }, - move |err| { - eprintln!("Stream error: {}", err); - }, - None, - ) - .unwrap(); - - stream.play().unwrap(); - let _ = rx.recv(); - stream.pause().unwrap(); - } -} - -fn byte_to_bits(byte: u8) -> Vec { - vec![ - byte & 1 == 1, - (byte >> 1) & 1 == 1, - (byte >> 2) & 1 == 1, - (byte >> 3) & 1 == 1, - (byte >> 4) & 1 == 1, - (byte >> 5) & 1 == 1, - (byte >> 6) & 1 == 1, - (byte >> 7) & 1 == 1, - ] -} - -/* -fn bits_to_byte(bits: &[bool]) -> u8 { - bits[7] as u8 | - bits[6] as u8 >> 1 | - bits[5] as u8 >> 2 | - bits[4] as u8 >> 3 | - bits[3] as u8 >> 4 | - bits[2] as u8 >> 5 | - bits[1] as u8 >> 6 | - bits[0] as u8 >> 7 -} -*/ - -fn bits_to_byte(bits: &[bool]) -> u8 { - bits[0] as u8 - | (bits[1] as u8) << 1 - | (bits[2] as u8) << 2 - | (bits[3] as u8) << 3 - | (bits[4] as u8) << 4 - | (bits[5] as u8) << 5 - | (bits[6] as u8) << 6 - | (bits[7] as u8) << 7 -} diff --git a/out.csv b/out.csv deleted file mode 100644 index 50b89ed..0000000 --- a/out.csv +++ /dev/null @@ -1,110 +0,0 @@ -0.38777804, -0.5346923, -0.955388, -1.733541, -5.2373223, -45.05751, -44.103336, -4.6842055, -1.8379081, -0.87091005, -0.5719485, -0.35533598, -0.27624997, -0.19268566, -0.16302158, -0.121304035, -0.10800954, -0.08377376, -0.077206604, -0.061651066, -0.058252066, -0.047531676, -0.045770597, -0.037980493, -0.037127394, -0.031228758, -0.03090615, -0.026285518, -0.026280245, -0.022565575, -0.02275963, -0.019703189, -0.020024413, -0.01745844, -0.017861607, -0.015673414, -0.01613357, -0.014235352, -0.014733706, -0.013069027, -0.013593689, -0.01211653, -0.012661362, -0.011337315, -0.011897493, -0.010705299, -0.011277529, -0.010171418, -0.010757942, -0.00974708, -0.010348838, -0.009411645, -0.010026283, -0.009147926, -0.009779742, -0.008949807, -0.00960072, -0.008821197, -0.009495176, -0.008750506, -0.009448569, -0.008736015, -0.009464292, -0.008779442, -0.009542818, -0.008887626, -0.009689722, -0.009043873, -0.009894198, -0.00927059, -0.010178313, -0.009571863, -0.010547835, -0.009952907, -0.011006483, -0.010420783, -0.011571476, -0.0110045895, -0.012267068, -0.011714736, -0.013115023, -0.012580418, -0.014148514, -0.013637692, -0.015413456, -0.014933306, -0.01697256, -0.016537804, -0.018906314, -0.01854057, -0.021339945, -0.021077268, -0.024448153, -0.024345227, -0.02848497, -0.028632956, -0.033848535, -0.03441413, -0.04118385, -0.042437002, -0.05156152, -0.05403404, -0.066911034, -0.071660355, -0.09098071, -0.100297675, -0.13183416, -0.1515972, -0.20976184, -0.25736627, diff --git a/out.jpg b/out.jpg deleted file mode 100644 index 5518463..0000000 --- a/out.jpg +++ /dev/null @@ -1 +0,0 @@ -џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ \ No newline at end of file diff --git a/out.png b/out.png deleted file mode 100644 index 99de976..0000000 Binary files a/out.png and /dev/null differ diff --git a/out.txt b/out.txt deleted file mode 100644 index c7a2888..0000000 --- a/out.txt +++ /dev/null @@ -1,3 +0,0 @@ -џibidi Toilet[1],[2] est une web-sУЉrie[3] machinima, crУЉУЉe par Alexey Gerasimov et diffusУЉe sur sa chaУЎne YouTube DaFuq!?Boom! Produite У  l'aide de Source Filmmaker, la sУЉrie suit une guerre fictive entre des toilettes У  tУЊte humaine et des personnages humanoУЏdes dotУЉs d'appareils УЉlectroniques У  la place de la tУЊte. - - … 8Йaд9Ж0И:1ЖДБ0КД77В:8ЙВЖД29БЗ:9:ЖсT:ЙАГ227ГсT;ЙД29™ЉЕ4Б4В4ЊЗ4Ж2:В2ЛД27::7ЖaдЖ2$7К29З2:Л4Й06Й:9В4Л2Й9ЙсдЙВА:<ЙЗБДА:<27И09КДБ:ЖД29А:ЙВ47В2Ж0ГсTЗсTЙ0КД77 68Д0ІВ91Й4КДИКВ977:Л:В0З9Б2:К2ЙсTЙД2Ж08ЙВЖДaTЙ24ЗБ:ЙЙД77В2Ж0ГсTЗсTЙ0КД77 68Д0В0З9Ж0Б:6К:Й2$7К29З2:27Б7ЗБ:9Й2ЗБ20ЛВ1Ж0ГсTЗсTЙ0КД77-8ЖК9aбГсд2…Љ<З7ИЙД9…ЉЕ4Б4В4ЊЗ4Ж2:ЙАБ77К2ЖВ9сTЛсTЗВЖ27К9В“:З2ГК29Й2ГД1К4Л227:Й2ВВ9ДЗЖЖВ9aPКaUК2В2БАЖсTЙ0В2ДА:КИ09ЖВ:9З:В2КсTЖсTЛДЙД772:ЖВ9ЉЕ4Б4В4ЊЗ4Ж2К9:З2ЙАБ22<:Й0К29ЙВ9:Й28Й2З07:6qРЬ08И0Й2ЗБ2В2КЗ4Ж2:КВ90ЛВ1ВВ9КaUКВ9ВДЗЖЖВ9З:В2ГВЖЖВ92:З“АМ07:ИК2БЗЖЖ2Ж0ЗГАГ2Ж2сUЉЕ4Б4В4с]Ђ4ЙДГсд9И09Ж2ЃЊЗ4Ж2:И2ЙЙ77ЗАГ24З9И4ЙсTВ:Ѓ–І07В0З9Ж0ЙсTЙД2В2ЕВ:<Л4ВсдЗ9Є06ГІ4ГВ-šЎ4Ж977:БЗЖЖ2А6Б4КД77В2Б7ЗИКсTЙ49Ж0Њ29Й227сTЖДЖ4З07:КЗ:К278ИЗЙ4КД7З­š.–-›.ЃЊЗ4Ж2:ИК4В9:ЙЗК9ЖВ9792ЙВ9ВВ9 9:ЙЗЊЗ4Ж2К9Й0ЙАБ2В“7ЙДГ4З2ИК4Й2:ЙЗ:Л2В0З9Ж“В9ИАБ2ВЗ4:Б7ЗИКсTЙ49Ж0К29Й20ЛВ1Ж“0ЙЖсд2ИК“4601Йсдсд2Й:9Њ29Й2Г4З4Й0И09И292Й2Й0Б77ГД0ЗБ2А:< 9:ЙЗ9Вс]aPЙ0ВсTГА4К2aPЖ“сTИДЙ7В2š2:aPЙ0ВД9И:К2aPЖ“сTИДЙ7В20ЛВ1ЖВ9ВВ:<Й76В0К9В“сTЖ4К2ВВ9 9:ЙЗ9a@И09К49В2Ж“сTИДЙ7В2ЖВ9 9:ЙЗ977:БЗЖЖ2ЗБсTaP27Л0Д49Ж0Њ29Й2ІВ9ЉЕ4Б4В4ЊЗ4Ж2К92:Ж“06ЖД0ЗБ2З“77:В“А::Й21ДЗ4<ИК2В2Й“06ЖД29ИЗ:9ЛА4З1Й2ЖВ9 9:ЙЗ9 \ No newline at end of file diff --git a/sine.wav b/sine.wav deleted file mode 100644 index ab39f0a..0000000 Binary files a/sine.wav and /dev/null differ diff --git a/src/main.rs b/src/main.rs index d847efb..f396ed2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,16 +11,198 @@ mod windows; mod ted; mod math; -use cpal::Data; -use eframe::egui; + +use std::{collections::VecDeque, time::Duration}; +use tokio::{join, select, time::timeout}; + +use eframe::{egui, glow::SAMPLE_MASK_VALUE}; +use tokio::sync::mpsc::{Receiver, Sender, channel}; +use crate::{bfsk::BFSKMod, complex::Complex32, filtering::{dc_block::DCBlocker, fir::FIRFilter}, iq::IQSampler, nco::Nco, ted::elg::ELGate, units::frequency::hz_to_rad_per_sample}; const BAUD_RATE: u32 = 1000; +const SAMPLE_RATE: u32 = 48000; -enum TransceiverState +// Modulation parameters +const CENTER_FREQ: f32 = 1700.; +const DEVIATION: f32 = 500.; + +pub trait SampleSender { - Idle, - Receiving, - Transmitting, + fn open_link(&mut self); + fn send_samples(&mut self, samples: &[f32]); + fn close_link(&mut self); +} + +struct Transceiver +{ + data_receiver: Receiver>, + data_sender: Sender>, + samples_sender: Sender, +} + +impl Transceiver +{ + pub async fn start(sample_sender: T) -> Self + { + let (transmitter_tx, transmitter_rx) = channel::>(4096); + let (acknowledged_tx, acknowledged_rx) = channel::<()>(32); + let (ack_tx, ack_rx) = channel::<()>(32); + let (samples_tx, samples_rx) = channel::(4096); + + let (receiver_tx, receiver_rx) = channel::>(4096); + join!( + Self::transmitter(acknowledged_rx, transmitter_rx, ack_rx, sample_sender), + Self::receiver(acknowledged_tx, samples_rx, receiver_tx, ack_tx) + ); + + Self + { + data_receiver: receiver_rx, + data_sender: transmitter_tx, + samples_sender: samples_tx + } + } + + async fn transmitter(mut acknowledged: Receiver<()>, mut data_receiver: Receiver>, mut ack_receiver: Receiver<()>, mut samples_sender: T) + { + let mut send_queue: VecDeque = VecDeque::new(); + + loop + { + if !send_queue.is_empty() + { + let to_send = send_queue.pop_front().unwrap(); + + // Create modulation + let bytes = to_send.bytes(); + let mut bit_stream = bytes.iter().flat_map(|x| byte_to_bits(*x)); + let mut 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 mut up_lo = Nco::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)); + + let mut sample_buffer = vec![]; + for (m, up) in modulator.zip(up_lo) + { + let sample = m * up; + sample_buffer.push(sample.re); // Project IQ + } + + samples_sender.open_link(); + samples_sender.send_samples(&sample_buffer); + samples_sender.close_link(); + + if let Frame::Data(_) = to_send + { + // Wait for ack + while !acknowledged.is_empty() + { + let _ = acknowledged.blocking_recv(); + } + + let ack_timout = timeout(Duration::from_secs(2), acknowledged.recv()).await; + if let Ok(Some(())) = ack_timout + { + // ACK Received : Ok + } + else + { + // Try again + send_queue.push_front(to_send); + } + } + } + else + { + let new = select! + { + Some(x) = data_receiver.recv() => Frame::Data(x), + Some(()) = ack_receiver.recv() => Frame::Ack, + }; + + match new + { + Frame::Ack => send_queue.push_front(Frame::Ack), // Highest importance + Frame::Data(x) => send_queue.push_back(Frame::Data(x)) + } + } + } + } + + async fn receiver(acknowledged: Sender<()>, mut samples: Receiver, data_sender: Sender>, ack_sender: Sender<()>) + { + let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)); + + 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(|_| {pos_nco.step(); pos_nco.cexp()}); + let neg_ir = (0..correllator_length).map(|_| {neg_nco.step(); neg_nco.cexp()}); + let mut pos_correllator = FIRFilter::new(&pos_ir.collect::>()); + let mut neg_correllator = FIRFilter::new(&neg_ir.collect::>()); + + let mut matched_lowpass = FIRFilter::new(&vec![Complex32::new(1., 0.); samples_per_symbol as usize]); + let mut dc_block = DCBlocker::new(0.995); + + let loop_i = 0.1; + let loop_p = 0.1; + let mut loop_ir = vec![Complex32::new(loop_i, 0.); samples_per_symbol as usize]; + loop_ir.push(Complex32::new(loop_p, 0.)); + let mut elg = ELGate::new(samples_per_symbol, FIRFilter::new(&loop_ir)); + + // Frame reconstruction + let mut last_byte = 0x00u8; + let mut frame_constructor = FrameConstructor::new(); + let mut bit_count: Option = None; + while let Some(sample) = samples.recv().await + { + let iq = iq_sampler.sample(sample); + let matched = dc_block.next_real(matched_lowpass.next_real(pos_correllator.next(iq).mag() - neg_correllator.next(iq).mag())); + if let Some(bit_sample) = elg.next(matched) + { + last_byte <<= 1; + last_byte |= (bit_sample > 0.) as u8; + bit_count = bit_count.map(|x| x + 1); + + if last_byte == 0xD8 // Potential frame starts + { + last_byte = 0; + frame_constructor = FrameConstructor::new(); + bit_count = Some(0); + } + + if let Some(8) = bit_count + { + let frame_opt = frame_constructor.add_byte(last_byte); + if let Ok(None) = frame_opt + { + bit_count = Some(0); + } + + if let Ok(Some(Frame::Ack)) = frame_opt + { + bit_count = None; + acknowledged.send(()).await.unwrap(); // Send acknowledgement to transmitter + } + + if let Ok(Some(Frame::Data(ref frame_data))) = frame_opt + { + bit_count = None; + data_sender.send(frame_data.to_vec()).await.unwrap(); + ack_sender.send(()).await.unwrap(); + } + + if let Err(()) = frame_opt + { + bit_count = None; // Erroneous frame, ignore + } + } + } + } + } } enum Frame @@ -29,6 +211,76 @@ enum Frame Ack } +type FrameConstructionError = (); +pub struct FrameConstructor +{ + frame: Vec, + frame_countdown: Option, + checksum: u8, +} + +impl FrameConstructor +{ + pub fn new() -> Self + { + Self + { + frame: Vec::new(), + frame_countdown: None, + checksum: 0u8, + } + } + + pub fn add_byte(&mut self, byte: u8) -> Result, FrameConstructionError> + { + if self.frame.is_empty() && byte != 0xC4 && byte != 0x4C + { + return Err(()); + } + + if self.frame.is_empty() && byte == 0xC4 + { + return Ok(Some(Frame::Ack)); + } + + if self.frame.is_empty() && byte == 0x4C + { + 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()))); + } + + 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 @@ -51,6 +303,7 @@ impl Frame assert!(x.len() < 65536, "Data size over MTU"); let len_u16 = x.len() as u16; + output_bytes.push(0x4C); // DATA FRAME output_bytes.push((len_u16 & 0xFF).try_into().unwrap()); output_bytes.push(((len_u16 >> 8) & 0xFF).try_into().unwrap()); @@ -60,7 +313,7 @@ impl Frame } Frame::Ack => { - output_bytes.push(0xC4); + output_bytes.push(0xC4); // ACK FRAME } }