#![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 }