From 02145b6ef0e7d15d1fc6b396386224916fad73ac Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Mon, 23 Mar 2026 20:29:53 +0100 Subject: [PATCH] Working tx/rx --- example/output.wav | Bin 0 -> 786432 bytes example/src/main.rs | 26 ++- example/src/receiver.rs | 406 +++++++++++++++++++++++----------- example/src/transmitter.rs | 39 +++- oxydsp-flowgraph/src/graph.rs | 1 + 5 files changed, 335 insertions(+), 137 deletions(-) create mode 100644 example/output.wav diff --git a/example/output.wav b/example/output.wav new file mode 100644 index 0000000000000000000000000000000000000000..cd189b09db583ad62c269515a34cfaf24f83f077 GIT binary patch literal 786432 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 N^}pgj|8M^De*n1755@oh literal 0 HcmV?d00001 diff --git a/example/src/main.rs b/example/src/main.rs index f85953d..954db26 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -35,6 +35,7 @@ use oxydsp_flowgraph::flowgraph; use oxydsp_flowgraph::graph::FlowGraph; use rand::random; +use crate::receiver::RadioReceiver; use crate::transmitter::Transmitter; pub mod receiver; @@ -42,19 +43,28 @@ pub mod transmitter; fn main() { - let tx = Transmitter::start_new(); - - loop + if std::env::args().len() == 2 { - let mut user_input = String::new(); - io::stdin().read_line(&mut user_input).unwrap(); - println!("Transmitting ..."); - tx.transmit(user_input.as_bytes().to_vec()); + println!("Transmitter"); + let tx = Transmitter::start_new(); + + loop + { + let mut user_input = String::new(); + io::stdin().read_line(&mut user_input).unwrap(); + println!("Transmitting ..."); + tx.transmit(user_input.as_bytes().to_vec()); + } + } + else + { + println!("Receiver"); + let _tx = RadioReceiver::start_new(); } } pub const SAMPLE_RATE: usize = 48_000; -pub const SAMPLE_PER_SYMBOL: usize = 96; +pub const SAMPLE_PER_SYMBOL: usize = 48; pub const DEVIATION: f64 = 500.; pub const CARRIER: f64 = 1700.; diff --git a/example/src/receiver.rs b/example/src/receiver.rs index a50ea4e..830102d 100644 --- a/example/src/receiver.rs +++ b/example/src/receiver.rs @@ -1,6 +1,10 @@ use std::collections::VecDeque; +use std::net::UdpSocket; use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::thread::JoinHandle; +use cpal::Stream; use cpal::traits::DeviceTrait; use cpal::traits::HostTrait; use eframe::NativeOptions; @@ -12,6 +16,7 @@ use num::Zero; use oxydsp_dsp::blocks::filtering::fir::FirFilter; use oxydsp_dsp::blocks::iq::zero_if::ZeroIf; use oxydsp_dsp::blocks::ted::early_late::EarlyLateGate; +use oxydsp_dsp::blocks::utilities::adapters::Map; use oxydsp_dsp::blocks::utilities::adapters::NullSink; use oxydsp_dsp::blocks::utilities::adapters::Scan; use oxydsp_dsp::blocks::utilities::adapters::ScanTagged; @@ -21,153 +26,306 @@ use oxydsp_dsp::filtering::fir::Fir; use oxydsp_dsp::units::DigitalFrequency; use oxydsp_flowgraph::flowgraph; use oxydsp_flowgraph::graph::FlowGraph; +use oxydsp_flowgraph::tag::Tag; +use oxydsp_flowgraph::tag::Tagged; use crate::CARRIER; use crate::DEVIATION; use crate::SAMPLE_PER_SYMBOL; use crate::SAMPLE_RATE; -fn main_demod() +pub enum PacketBuilderBitState { - let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64); + WaitingForPreamble, + InPacket, +} - let mut reader = hound::WavReader::open("mod.wav").unwrap(); - let sqr_sum = reader - .samples::() - .map(|sample| (sample.unwrap() as f32) / (i16::MAX as f32)) - .collect::>(); - let (audio_tx, audio_rx) = mpsc::channel(); +pub enum PacketBuilderByteState +{ + Length1, + Length2, + Data, +} - //let (source, signal) = IterSource::new(sqr_sum.into_iter().cycle()); - let (source, signal) = RxSource::new(audio_rx); +pub struct PacketBuilder +{ + current_byte: u8, + bit_index: u8, - let (mut zero_if, iq) = ZeroIf::new(signal, carrier.into()); + bit_state: PacketBuilderBitState, + packet_state: PacketBuilderByteState, - zero_if.set_fir(Fir::lowpass( - DigitalFrequency::from_time_frequency(DEVIATION * 2. + 100., SAMPLE_RATE as f64), - 200, - )); + // Packet building + length: u16, + data: Vec, +} - let (squelch, iq) = Squelch::new(iq, 5., 100); - - let (arg_extract, arg) = Scan::new(iq, Complex::zero(), |state, sample| { - let angle: Complex = sample / *state; - *state = sample; - angle.arg() * 14. - }); - let (sig_lowpass, arg) = - FirFilter::::new(arg, Fir(vec![1.; SAMPLE_PER_SYMBOL]).normalized()); - - let mut elg_loop = Fir(vec![1. / 30.; 30]); - //let mut elg_loop = elg_loop.normalized(); - *elg_loop.0.last_mut().unwrap() = 0.4; - //*elg_loop.0.first_mut().unwrap() = 0.001; - let (elg, arg) = EarlyLateGate::new(arg, elg_loop, SAMPLE_PER_SYMBOL); - - // Eye diagram - let (tx, rx) = mpsc::channel(); - let (eye_sender, arg) = ScanTagged::new(arg, VecDeque::new(), move |history, x| { - if history.len() == SAMPLE_PER_SYMBOL - { - history.pop_back(); +impl PacketBuilder +{ + pub fn new() -> Self + { + Self { + current_byte: 0, + bit_index: 0, + bit_state: PacketBuilderBitState::WaitingForPreamble, + packet_state: PacketBuilderByteState::Length1, + length: 0, + data: vec![], } + } - let mut error: f32 = 0.; - let is_symbol_center = x.1.as_ref().is_some_and(|t| { - if let Some(err) = t.retrieve("elg_symbol") - { - error = *err.downcast().unwrap(); - true - } - else - { - false - } - }); - history.push_front(((is_symbol_center, error), x.0)); - - if history.len() > SAMPLE_PER_SYMBOL / 2 && history[SAMPLE_PER_SYMBOL / 2].0.0 + fn next_byte(&mut self) -> Option> + { + match self.packet_state { - let _ = tx.send(( - history.iter().map(|(_, x)| *x).collect::>(), - history[SAMPLE_PER_SYMBOL / 2].0.1, - )); - } - - x.0.into() - }); - let null_sink = NullSink::new(arg); - - let graph = flowgraph![ - source, - squelch, - zero_if, - arg_extract, - sig_lowpass, - elg, - eye_sender, - null_sink - ]; - - // Setup input - let host = cpal::default_host(); - let device = host.default_input_device().expect("No input device"); - let mut supported_configs_range = device - .supported_input_configs() - .expect("error while querying configs"); - let supported_config = supported_configs_range - .next() - .expect("no supported config?!") - .with_sample_rate(SAMPLE_RATE as u32); - let stream = device.build_input_stream( - &supported_config.into(), - move |data: &[f32], _: &cpal::InputCallbackInfo| { - for x in data.iter() + PacketBuilderByteState::Length1 => { - let _ = audio_tx.send(*x); + self.length = 0; + self.length |= self.current_byte as u16; + println!("starting packet, length 1 {}", self.current_byte); + self.packet_state = PacketBuilderByteState::Length2; } - }, - move |err| { - panic!() // react to errors here. - }, - None, // None=blocking, Some(Duration)=timeout - ); - - let j = graph.run(); - - let mut eyes = VecDeque::new(); - eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| { - while let Ok(eye) = rx.try_recv() - { - if eyes.len() >= 100 + PacketBuilderByteState::Length2 => { - let _ = eyes.pop_back(); + println!("starting packet, length 2 {}", self.current_byte); + self.length |= (self.current_byte as u16) << 8; + self.data = vec![]; + self.packet_state = PacketBuilderByteState::Data; + println!("length : {}", self.length); } - eyes.push_front(eye); - } - - egui::CentralPanel::default().show(ctx, |ui| { - egui_plot::Plot::new("hello").show(ui, |plot_ui| { - for eye in eyes.iter() + PacketBuilderByteState::Data => + { + self.data.push(self.current_byte); + self.length -= 1; + if self.length == 0 { - plot_ui.line( - Line::new( - "eyes", - eye.0 - .iter() - .enumerate() - .map(|(i, s)| [i as f64, *s as f64]) - .collect::(), - ) - .id("eyes") - .color(color_from_err(eye.1, 0.3)), - ); + println!("finished"); + let current = std::mem::replace(self, Self::new()); + return Some(current.data); + } + } + } + None + } + + pub fn next_bit(&mut self, bit: bool) -> Option> + { + self.current_byte >>= 1; + self.current_byte |= (bit as u8) << 7; + match self.bit_state + { + PacketBuilderBitState::WaitingForPreamble => + { + if self.current_byte == 0b01100111 + { + println!("preamble heard !"); + self.bit_state = PacketBuilderBitState::InPacket; + self.bit_index = 0; + } + return None; + } + PacketBuilderBitState::InPacket => + { + self.bit_index += 1; + if self.bit_index == 8 + { + let out = self.next_byte(); + self.bit_index = 0; + return out; + } + None + } + } + } +} + +pub struct RadioReceiver { + //stream: Stream, + //pub packet_receiver: Receiver>, +} + +impl RadioReceiver +{ + pub fn start_new() -> Self + { + let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64); + + let (audio_tx, audio_rx) = mpsc::channel(); + let (packet_tx, packet_rx) = mpsc::channel::>(); + let (source, signal) = RxSource::new(audio_rx); + let (inspect, signal) = Map::new(signal, |x| { + //println!("{x}"); + x + }); + let (mut zero_if, iq) = ZeroIf::new(signal, carrier.into()); + + zero_if.set_fir(Fir::lowpass( + DigitalFrequency::from_time_frequency(2. * DEVIATION + 100., SAMPLE_RATE as f64), + SAMPLE_PER_SYMBOL * 4, + )); + + let (squelch, iq) = Squelch::new(iq, 5., 100); + + let (arg_extract, arg) = Scan::new(iq, Complex::zero(), |state, sample| { + let angle: Complex = sample / *state; + *state = sample; + angle.arg() * 14. + }); + + let mut elg_loop = Fir(vec![1. / 20.; 30]); + *elg_loop.0.last_mut().unwrap() = 1.; + let (elg, arg) = EarlyLateGate::new(arg, elg_loop, SAMPLE_PER_SYMBOL); + + // // Eye diagram + let (tx, rx) = mpsc::channel::<(Vec, f32)>(); + //let (eye_sender, arg) = ScanTagged::new(arg, VecDeque::<()>::new(), move |history, x| { + let (eye_sender, arg) = ScanTagged::new(arg, VecDeque::new(), move |history, x| { + let cloned_tag = x.1.clone(); + if history.len() == 2 * SAMPLE_PER_SYMBOL + { + history.pop_back(); + } + + let mut error: f32 = 0.; + let is_symbol_center = x.1.as_ref().is_some_and(|t| { + if let Some(err) = t.retrieve("elg_symbol") + { + error = *err.downcast().unwrap(); + true + } + else + { + false } }); - ctx.request_repaint(); + history.push_front(((is_symbol_center, error), x.0)); + + if history.len() > SAMPLE_PER_SYMBOL && history[SAMPLE_PER_SYMBOL].0.0 + { + let _ = tx.send(( + history.iter().map(|(_, x)| *x).collect::>(), + history[SAMPLE_PER_SYMBOL].0.1, + )); + } + + Tagged::new(x.0, None) }); - }) - .unwrap(); + + let (packet_map, arg) = + ScanTagged::new(arg, PacketBuilder::new(), move |builder, sample| { + if sample + .1 + .as_ref() + .is_some_and(|t| t.retrieve("elg_symbol").is_some()) + && let Some(packet) = builder.next_bit(sample.0 < 0.) + { + let _ = packet_tx.send(packet); + } + + Tagged::new(sample.0, None) + }); + let null_sink = NullSink::new(arg); + + let graph = flowgraph![ + source, + inspect, + squelch, + zero_if, + packet_map, + arg_extract, + //sig_lowpass, + elg, + eye_sender, + null_sink + ]; + let t = graph.run(); + + // Setup input + // let host = cpal::default_host(); + // let device = host.default_input_device().expect("No input device"); + // let mut supported_configs_range = device + // .supported_input_configs() + // .expect("error while querying configs"); + // let supported_config = supported_configs_range + // .next() + // .expect("no supported config?!") + // .with_sample_rate(SAMPLE_RATE as u32); + // let stream = device + // .build_input_stream( + // &supported_config.into(), + // move |data: &[f32], _: &cpal::InputCallbackInfo| { + // for x in data.iter() + // { + // let _ = audio_tx.send(*x); + // } + // }, + // move |err| { + // panic!() // react to errors here. + // }, + // None, // None=blocking, Some(Duration)=timeout + // ) + // .unwrap(); + std::thread::spawn(move || { + let socket = UdpSocket::bind("0.0.0.0:25565").unwrap(); + + let mut buffer = [0u8; 4096]; + while let Ok(read) = socket.recv(&mut buffer) + { + let read_buffer = &mut buffer[0..read]; + for x in read_buffer.chunks(4) + { + let val = f32::from_le_bytes([x[0], x[1], x[2], x[3]]); + let _ = audio_tx.send(val); + } + } + }); + + let mut eyes = VecDeque::new(); + eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| { + while let Ok(x) = packet_rx.try_recv() + { + println!("Got data: {} bytes.", x.len()); + let str: String = x.iter().map(|x| *x as char).collect(); + println!("-----\n\n{}\n\n-----", str); + } + + while let Ok(eye) = rx.try_recv() + { + if eyes.len() >= 100 + { + let _ = eyes.pop_back(); + } + eyes.push_front(eye); + } + + egui::CentralPanel::default().show(ctx, |ui| { + egui_plot::Plot::new("hello").show(ui, |plot_ui| { + for eye in eyes.iter() + { + plot_ui.line( + Line::new( + "eyes", + eye.0 + .iter() + .enumerate() + .map(|(i, s)| [i as f64 / 2., *s as f64]) + .collect::(), + ) + .id("eyes") + .color(Color32::GREEN), + ); + } + }); + ctx.request_repaint(); + }); + }) + .unwrap(); + + Self { + //stream, + //packet_receiver: packet_rx, + } + } } pub fn color_from_err(error: f32, max: f32) -> Color32 diff --git a/example/src/transmitter.rs b/example/src/transmitter.rs index 60d6542..ecd831e 100644 --- a/example/src/transmitter.rs +++ b/example/src/transmitter.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::fs::File; use std::io::Write; use std::iter::FusedIterator; +use std::net::UdpSocket; use std::ops::BitXor; use std::sync::mpsc; use std::sync::mpsc::Receiver; @@ -9,6 +10,7 @@ use std::sync::mpsc::Sender; use std::sync::mpsc::SyncSender; use std::sync::mpsc::sync_channel; use std::thread::JoinHandle; +use std::time::Duration; use cpal::Stream; use cpal::traits::DeviceTrait; @@ -176,30 +178,33 @@ impl Transmitter let (packet_rec, packets): (_, In>) = RxSource::new(packet_rx); let (linearizer, bits) = FlatMap::new(packets, |packet| { // +1 for chksum - let packet_length = (packet.len() + 1) as u16; + let packet_length = packet.len() as u16; let checksum = packet.iter().copied().reduce(BitXor::bitxor).unwrap(); // Learning sequence let mut frame = vec![0b10101010; 8]; + // Preamble + frame.push(0b01100111); frame.push(packet_length.to_le_bytes()[0]); frame.push(packet_length.to_le_bytes()[1]); frame.extend(packet.iter()); frame.push(checksum); + frame.extend((0..16).map(|_| 0)); frame .into_iter() .flat_map(to_bits) - .map(|x| if x { 1. } else { -1.0f32 }) + .map(|x| if x { 1. } else { -1. }) }); let (repeat, bits) = Repeat::new(bits, SAMPLE_PER_SYMBOL); // gaussian fir let fir = Fir((0..SAMPLE_PER_SYMBOL) - .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) + .map(|x| gaussian(0.1, x as f32 / SAMPLE_PER_SYMBOL as f32)) .collect()) .normalized(); - let (bit_filter, bits) = FirFilter::new(bits, fir); + //let (bit_filter, bits) = FirFilter::new(bits, fir); let (to_freq, freq) = Map::new(bits, move |x| { DigitalFrequency::from_time_frequency(DEVIATION * x as f64, SAMPLE_RATE as f64) }); @@ -207,12 +212,36 @@ impl Transmitter let (local_oscillator, lo) = OscillatorSource::::new(carrier.into()); let (frontend, passband) = Multiplier::new(baseband, lo); let (audio_tx, audio_rx) = mpsc::channel::>(); + + let reverb_length = 20; + // let (reverb, passband) = FirFilter::new( + // passband, + // Fir((0..reverb_length) + // .map(|x| (-15. * (x as f32) / (reverb_length as f32)).exp()) + // .collect()) + // .normalized(), + // ); + + let (udp_map, passband) = Scan::new( + passband, + UdpSocket::bind("127.0.0.1:25566").unwrap(), + |sckt, sample| { + sckt.send_to( + &(sample.re + ((random::() * 2.) - 1.) * 0.0).to_le_bytes(), + "127.0.0.1:25565", + ) + .unwrap(); + sample + }, + ); let tx_sink = TxSink::new(passband, audio_tx); let graph = flowgraph![ packet_rec, linearizer, - bit_filter, + //reverb, + //bit_filter, + udp_map, to_freq, repeat, base_oscillator, diff --git a/oxydsp-flowgraph/src/graph.rs b/oxydsp-flowgraph/src/graph.rs index f40429f..79726e3 100644 --- a/oxydsp-flowgraph/src/graph.rs +++ b/oxydsp-flowgraph/src/graph.rs @@ -53,6 +53,7 @@ impl FlowGraph } crate::block::BlockResult::Exit => { + println!("KILLING GRAPH"); break 'outer; } }