diff --git a/Cargo.lock b/Cargo.lock index 8504b79..c7d018d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,6 +446,18 @@ dependencies = [ "rand", ] +[[package]] +name = "bfsk-modem-tx" +version = "0.1.0" +dependencies = [ + "cpal", + "hound", + "num", + "oxydsp-dsp", + "oxydsp-flowgraph", + "rand", +] + [[package]] name = "bit-set" version = "0.8.0" diff --git a/examples/bfsk-modem-tx/.gitignore b/examples/bfsk-modem-tx/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/examples/bfsk-modem-tx/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/bfsk-modem-tx/Cargo.toml b/examples/bfsk-modem-tx/Cargo.toml new file mode 100644 index 0000000..f879ec9 --- /dev/null +++ b/examples/bfsk-modem-tx/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bfsk-modem-tx" +version = "0.1.0" +edition = "2024" + +[dependencies] +oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"} +oxydsp-dsp = {path = "../../oxydsp-dsp/"} +num = "0.4.3" +hound = "3.5.1" +rand = "0.10.0" +cpal = "0.17.3" diff --git a/examples/bfsk-modem-tx/mod.wav b/examples/bfsk-modem-tx/mod.wav new file mode 100644 index 0000000..662e18f Binary files /dev/null and b/examples/bfsk-modem-tx/mod.wav differ diff --git a/examples/bfsk-modem-tx/out.dot b/examples/bfsk-modem-tx/out.dot new file mode 100644 index 0000000..15d7de4 --- /dev/null +++ b/examples/bfsk-modem-tx/out.dot @@ -0,0 +1,25 @@ + + digraph G { + node [shape=record]; + rankdir=TB; + IterSource_0 [label="{ IterSource |{ output} }"]; +FirFilter_1 [label="{ { input}| FirFilter |{ output} }"]; +Map_2 [label="{ { input}| Map |{ output} }"]; +Repeat_3 [label="{ { input}| Repeat |{ output} }"]; +Nco_4 [label="{ { frequency}| Nco |{ output} }"]; +OscillatorSource_5 [label="{ OscillatorSource |{ output} }"]; +Multiplier_6 [label="{ { input_a| input_b}| Multiplier |{ output} }"]; +MapResultTagged_7 [label="{ { input}| MapResultTagged |{ output} }"]; +NullSink_8 [label="{ { input}| NullSink }"]; + + IterSource_0:o0 -> Repeat_3:i0 [label="f32"]; +FirFilter_1:o0 -> Map_2:i0 [label="f32"]; +Map_2:o0 -> Nco_4:i0 [label="oxydsp_dsp::units::DigitalFrequency"]; +Repeat_3:o0 -> FirFilter_1:i0 [label="f32"]; +Nco_4:o0 -> Multiplier_6:i0 [label="num_complex::Complex"]; +OscillatorSource_5:o0 -> Multiplier_6:i1 [label="num_complex::Complex"]; +Multiplier_6:o0 -> MapResultTagged_7:i0 [label="num_complex::Complex"]; +MapResultTagged_7:o0 -> NullSink_8:i0 [label="num_complex::Complex"]; + + } + \ No newline at end of file diff --git a/examples/bfsk-modem-tx/output.wav b/examples/bfsk-modem-tx/output.wav new file mode 100644 index 0000000..cd189b0 Binary files /dev/null and b/examples/bfsk-modem-tx/output.wav differ diff --git a/examples/bfsk-modem-tx/src/main.rs b/examples/bfsk-modem-tx/src/main.rs new file mode 100644 index 0000000..caed68b --- /dev/null +++ b/examples/bfsk-modem-tx/src/main.rs @@ -0,0 +1,54 @@ +use crate::transmitter::Transmitter; + +pub mod transmitter; + +fn main() +{ + println!("Transmitter"); + let tx = Transmitter::start_new(); + + loop + { + let mut user_input = String::new(); + std::io::stdin().read_line(&mut user_input).unwrap(); + println!("Transmitting ..."); + tx.transmit(user_input.as_bytes().to_vec()); + } +} + +pub const SAMPLE_RATE: usize = 48_000; +pub const SAMPLE_PER_SYMBOL: usize = 48; +pub const DEVIATION: f64 = 500.; +pub const CARRIER: f64 = 1700.; + +pub fn to_bits(n: u8) -> [bool; 8] +{ + [ + (n & 1) == 1, + (n >> 1) & 1 == 1, + (n >> 2) & 1 == 1, + (n >> 3) & 1 == 1, + (n >> 4) & 1 == 1, + (n >> 5) & 1 == 1, + (n >> 6) & 1 == 1, + (n >> 7) & 1 == 1, + ] +} + +pub fn from_bits(n: [bool; 8]) -> u8 +{ + (n[0] as u8) + | ((n[1] as u8) << 1) + | ((n[2] as u8) << 2) + | ((n[3] as u8) << 3) + | ((n[4] as u8) << 4) + | ((n[5] as u8) << 5) + | ((n[6] as u8) << 6) + | ((n[7] as u8) << 7) +} + +pub fn gaussian(sigma: f32, t: f32) -> f32 +{ + let sq = (t - 0.5) / sigma; + (-sq * sq).exp() +} diff --git a/examples/bfsk-modem-tx/src/transmitter.rs b/examples/bfsk-modem-tx/src/transmitter.rs new file mode 100644 index 0000000..666ff8e --- /dev/null +++ b/examples/bfsk-modem-tx/src/transmitter.rs @@ -0,0 +1,211 @@ +use cpal::Stream; +use cpal::traits::DeviceTrait; +use cpal::traits::HostTrait; +use num::Complex; +use oxydsp_dsp::blocks::filtering::fir::FirFilter; +use oxydsp_dsp::blocks::math::basic::Multiplier; +use oxydsp_dsp::blocks::synthesis::Nco; +use oxydsp_dsp::blocks::synthesis::OscillatorSource; +use oxydsp_dsp::blocks::utilities::adapters::FlatMap; +use oxydsp_dsp::blocks::utilities::adapters::Map; +use oxydsp_dsp::blocks::utilities::adapters::Scan; +use oxydsp_dsp::blocks::utilities::channels::RxSource; +use oxydsp_dsp::blocks::utilities::channels::TxSink; +use oxydsp_dsp::filtering::fir::Fir; +use oxydsp_dsp::units::DigitalFrequency; +use oxydsp_flowgraph::flowgraph; +use oxydsp_flowgraph::io::In; +use rand::random; +use std::f32::consts::PI; +use std::net::UdpSocket; +use std::ops::BitXor; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::sync::mpsc::SyncSender; +use std::sync::mpsc::sync_channel; +use std::thread::JoinHandle; +use std::time::Duration; + +use crate::CARRIER; +use crate::DEVIATION; +use crate::SAMPLE_PER_SYMBOL; +use crate::SAMPLE_RATE; +use crate::gaussian; +use crate::to_bits; + +pub struct Transmitter +{ + flowgraph_handle: JoinHandle<()>, + packet_sender: SyncSender>, + stream: Stream, +} + +impl Transmitter +{ + pub fn start_new() -> Self + { + let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64); + let deviation = DigitalFrequency::from_time_frequency(DEVIATION, SAMPLE_RATE as f64); + + let (packet_tx, packet_rx): (_, Receiver>) = sync_channel(128); + let (packet_rec, packets): (_, In>) = RxSource::new(packet_rx); + let (linearizer, bits) = FlatMap::new(packets, |packet| { + // +1 for chksum + 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. }) + }); + + let (repeat, bits) = FlatMap::new(bits, |symbol| { + let mut v = vec![0.; SAMPLE_PER_SYMBOL - 1]; + v.push(symbol); + v + }); + + // gaussian fir + // let fir = Fir((0..SAMPLE_PER_SYMBOL) + // .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) + // .collect()); + //.normalized(); + + // RRC fir + let rrc_symbol_count = 4; + let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count; + let fir = Fir((0..rrc_fir_length) + .map(|x| { + let centered = oxydsp_dsp::map( + x as f32, + 0., + rrc_fir_length as f32, + -(rrc_symbol_count as f32) * 0.5, + rrc_symbol_count as f32 * 0.5, + ); + root_raised_cosine(centered, 1., 1.) + }) + .collect()); + + 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) + }); + let (base_oscillator, baseband) = Nco::::new(freq); + 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 = 200; + // let (reverb, passband) = FirFilter::new( + // passband, + // Fir((0..reverb_length) + // .map(|x| (-5. * (x as f32) / (reverb_length as f32)).exp()) + // .collect()) + // .normalized(), + // ); + // + let (awgn, passband) = Map::new(passband, |x| { + x + Complex::::new(2. * (random::() - 0.5), 2. * (random::() - 0.5)) + * 0.0 + }); + + let tx_sink = TxSink::new(passband, audio_tx); + + let graph = flowgraph![ + packet_rec, + linearizer, + //reverb, + bit_filter, + //udp_map, + to_freq, + repeat, + base_oscillator, + local_oscillator, + frontend, + awgn, + tx_sink, + ]; + + // Open output device + let host = cpal::default_host(); + let device = host + .default_output_device() + .expect("no output device available"); + let mut supported_configs_range = device + .supported_output_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_output_stream( + &supported_config.into(), + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { + for x in data.iter_mut() + { + if let Ok(y) = audio_rx.try_recv() + { + *x = y.re * 0.01; + } + else + { + *x = 0.; + } + } + }, + move |err| panic!(), + None, // None=blocking, Some(Duration)=timeout + ) + .unwrap(); + + Self { + flowgraph_handle: graph.run(), + packet_sender: packet_tx, + stream, + } + } + + pub fn transmit(&self, data: Vec) + { + let _ = self.packet_sender.send(data); + } +} + +pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32 +{ + let eps = 1e-8; + + if t.abs() < eps + { + // t = 0 special case + return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0)); + } + + if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps + { + // t = ±T / (4β) special case + let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin(); + let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos(); + return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2); + } + + // General case + let numerator = (PI * t * (1.0 - beta) / ts).sin() + + 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos(); + + let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts; + + (1.0 / ts.sqrt()) * (numerator / denominator) +} diff --git a/examples/bfsk-modem/src/receiver.rs b/examples/bfsk-modem/src/receiver.rs index fdd08a3..688387b 100644 --- a/examples/bfsk-modem/src/receiver.rs +++ b/examples/bfsk-modem/src/receiver.rs @@ -34,6 +34,8 @@ use crate::CARRIER; use crate::DEVIATION; use crate::SAMPLE_PER_SYMBOL; use crate::SAMPLE_RATE; +use crate::gaussian; +use crate::transmitter::root_raised_cosine; pub enum PacketBuilderBitState { @@ -171,11 +173,37 @@ impl RadioReceiver let (arg_extract, arg) = Scan::new(iq, Complex::zero(), |state, sample| { let angle: Complex = sample / *state; *state = sample; - angle.arg() * 14. + angle.arg() + / DigitalFrequency::from_time_frequency(DEVIATION, SAMPLE_RATE as f64).as_rad() + as f32 }); - let mut elg_loop = Fir(vec![1. / 20.; 30]); - *elg_loop.0.last_mut().unwrap() = 1.; + // gaussian fir + let fir = Fir((0..SAMPLE_PER_SYMBOL) + .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) + .collect()) + .normalized(); + + // RRC fir + let rrc_symbol_count = 4; + let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count; + let fir = Fir((0..rrc_fir_length) + .map(|x| { + let centered = oxydsp_dsp::map( + x as f32, + 0., + rrc_fir_length as f32, + -(rrc_symbol_count as f32) * 0.5, + rrc_symbol_count as f32 * 0.5, + ); + root_raised_cosine(centered, 1., 1.) + }) + .collect()) + .normalized(); + + let (matched_filter, arg) = FirFilter::new(arg, fir); + let mut elg_loop = Fir(vec![1. / 30.; 30]); + *elg_loop.0.last_mut().unwrap() = 0.3; let symbol_tag = tags.allocate_tag("early late gate symbol"); let (elg, arg) = EarlyLateGate::new(arg, elg_loop, SAMPLE_PER_SYMBOL, symbol_tag.clone()); @@ -238,6 +266,7 @@ impl RadioReceiver packet_map, arg_extract, //sig_lowpass, + matched_filter, elg, eye_sender, null_sink diff --git a/examples/bfsk-modem/src/transmitter.rs b/examples/bfsk-modem/src/transmitter.rs index 3afaf5d..b6cf4f5 100644 --- a/examples/bfsk-modem/src/transmitter.rs +++ b/examples/bfsk-modem/src/transmitter.rs @@ -6,6 +6,7 @@ use oxydsp_dsp::blocks::filtering::fir::FirFilter; use oxydsp_dsp::blocks::math::basic::Multiplier; use oxydsp_dsp::blocks::synthesis::Nco; use oxydsp_dsp::blocks::synthesis::OscillatorSource; +use oxydsp_dsp::blocks::utilities::adapters::FlatMap; use oxydsp_dsp::blocks::utilities::adapters::Map; use oxydsp_dsp::blocks::utilities::adapters::Repeat; use oxydsp_dsp::blocks::utilities::adapters::Scan; @@ -21,6 +22,7 @@ use oxydsp_flowgraph::graph::FlowGraph; use oxydsp_flowgraph::io::In; use oxydsp_flowgraph::io::Out; use rand::random; +use std::f32::consts::PI; use std::iter::FusedIterator; use std::net::UdpSocket; use std::ops::BitXor; @@ -81,11 +83,27 @@ impl Transmitter }); // gaussian fir - let fir = Fir((0..SAMPLE_PER_SYMBOL) - .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) - .collect()); + // let fir = Fir((0..SAMPLE_PER_SYMBOL) + // .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) + // .collect()); //.normalized(); + // RRC fir + let rrc_symbol_count = 4; + let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count; + let fir = Fir((0..rrc_fir_length) + .map(|x| { + let centered = oxydsp_dsp::map( + x as f32, + 0., + rrc_fir_length as f32, + -(rrc_symbol_count as f32) * 0.5, + rrc_symbol_count as f32 * 0.5, + ); + root_raised_cosine(centered, 1., 1.) + }) + .collect()); + 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) @@ -95,25 +113,27 @@ impl Transmitter let (frontend, passband) = Multiplier::new(baseband, lo); let (audio_tx, audio_rx) = mpsc::channel::>(); - let reverb_length = 20; + let reverb_length = 200; let (reverb, passband) = FirFilter::new( passband, Fir((0..reverb_length) - .map(|x| (-15. * (x as f32) / (reverb_length as f32)).exp()) + .map(|x| (-5. * (x as f32) / (reverb_length as f32)).exp()) .collect()) .normalized(), ); + let (awgn, passband) = Map::new(passband, |x| { + x + Complex::::new(2. * (random::() - 0.5), 2. * (random::() - 0.5)) + * 0.3 + }); + let (udp_map, passband) = Scan::new( passband, - UdpSocket::bind("127.0.0.1:25566").unwrap(), + UdpSocket::bind("0.0.0.0:0").unwrap(), |sckt, sample| { - std::thread::sleep(Duration::from_micros(20)); - sckt.send_to( - &(sample.re + ((random::() * 2.) - 1.) * 0.0).to_le_bytes(), - "127.0.0.1:25565", - ) - .unwrap(); + std::thread::sleep(Duration::from_micros(12)); + sckt.send_to(&(sample.re).to_le_bytes(), "127.0.0.1:25565") + .unwrap(); sample }, ); @@ -130,6 +150,7 @@ impl Transmitter base_oscillator, local_oscillator, frontend, + awgn, tx_sink, ]; @@ -178,3 +199,30 @@ impl Transmitter let _ = self.packet_sender.send(data); } } + +pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32 +{ + let eps = 1e-8; + + if t.abs() < eps + { + // t = 0 special case + return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0)); + } + + if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps + { + // t = ±T / (4β) special case + let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin(); + let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos(); + return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2); + } + + // General case + let numerator = (PI * t * (1.0 - beta) / ts).sin() + + 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos(); + + let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts; + + (1.0 / ts.sqrt()) * (numerator / denominator) +} diff --git a/examples/dpsk-modem/demodulator.dot b/examples/dpsk-modem/demodulator.dot index ca7f5a3..30cff18 100644 --- a/examples/dpsk-modem/demodulator.dot +++ b/examples/dpsk-modem/demodulator.dot @@ -6,17 +6,21 @@ ZeroIf_1 [label="{ { input}| ZeroIf |{ output} }"]; NullSink_2 [label="{ { input}| NullSink }"]; Scan_3 [label="{ { input}| Scan |{ output} }"]; -Multiplier_4 [label="{ { input_a| input_b}| Multiplier |{ output} }"]; +FirFilter_4 [label="{ { input}| FirFilter |{ output} }"]; Tee_5 [label="{ { input}| Tee |{ output_a| output_b} }"]; -Scan_6 [label="{ { input}| Scan |{ output} }"]; +Map_6 [label="{ { input}| Map |{ output} }"]; +Scan_7 [label="{ { input}| Scan |{ output} }"]; +Multiplier_8 [label="{ { input_a| input_b}| Multiplier |{ output} }"]; IterSource_0:o0 -> ZeroIf_1:i0 [label="f32"]; ZeroIf_1:o0 -> Tee_5:i0 [label="num_complex::Complex"]; -Scan_3:o0 -> Multiplier_4:i1 [label="num_complex::Complex"]; -Multiplier_4:o0 -> Scan_6:i0 [label="num_complex::Complex"]; -Tee_5:o0 -> Multiplier_4:i0 [label="num_complex::Complex"]; -Tee_5:o1 -> Scan_3:i0 [label="num_complex::Complex"]; -Scan_6:o0 -> NullSink_2:i0 [label="()"]; +Scan_3:o0 -> NullSink_2:i0 [label="()"]; +FirFilter_4:o0 -> Scan_3:i0 [label="f32"]; +Tee_5:o0 -> Multiplier_8:i0 [label="num_complex::Complex"]; +Tee_5:o1 -> Scan_7:i0 [label="num_complex::Complex"]; +Map_6:o0 -> FirFilter_4:i0 [label="f32"]; +Scan_7:o0 -> Multiplier_8:i1 [label="num_complex::Complex"]; +Multiplier_8:o0 -> Map_6:i0 [label="num_complex::Complex"]; } \ No newline at end of file diff --git a/examples/dpsk-modem/output.wav b/examples/dpsk-modem/output.wav new file mode 100644 index 0000000..67e1d38 Binary files /dev/null and b/examples/dpsk-modem/output.wav differ diff --git a/examples/dpsk-modem/src/main.rs b/examples/dpsk-modem/src/main.rs index 2d13d2b..a03da6c 100644 --- a/examples/dpsk-modem/src/main.rs +++ b/examples/dpsk-modem/src/main.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; use std::env::args; +use std::f32::consts::PI; use std::fs::File; use std::io::Write; use std::net::Ipv4Addr; @@ -9,6 +10,8 @@ use std::sync::mpsc::channel; use std::sync::mpsc::sync_channel; use std::time::Duration; +use cpal::traits::DeviceTrait; +use cpal::traits::HostTrait; use eframe::NativeOptions; use egui::Color32; use egui_plot::Line; @@ -18,13 +21,17 @@ use num::Complex; use num::Integer; use oxydsp_dsp::blocks::filtering::fir::FirFilter; use oxydsp_dsp::blocks::iq::zero_if::ZeroIf; +use oxydsp_dsp::blocks::math::basic; use oxydsp_dsp::blocks::math::basic::Multiplier; use oxydsp_dsp::blocks::synthesis::OscillatorSource; +use oxydsp_dsp::blocks::ted::early_late::EarlyLateGate; use oxydsp_dsp::blocks::utilities::adapters::FlatMap; +use oxydsp_dsp::blocks::utilities::adapters::Map; use oxydsp_dsp::blocks::utilities::adapters::MapResultTagged; use oxydsp_dsp::blocks::utilities::adapters::NullSink; use oxydsp_dsp::blocks::utilities::adapters::Repeat; use oxydsp_dsp::blocks::utilities::adapters::Scan; +use oxydsp_dsp::blocks::utilities::adapters::ScanTagged; use oxydsp_dsp::blocks::utilities::adapters::Tee; use oxydsp_dsp::blocks::utilities::channels::RxSource; use oxydsp_dsp::blocks::utilities::channels::TxSink; @@ -33,12 +40,13 @@ use oxydsp_dsp::filtering::fir::Fir; use oxydsp_dsp::units::DigitalFrequency; use oxydsp_flowgraph::block::BlockResult; use oxydsp_flowgraph::flowgraph; +use oxydsp_flowgraph::io::In; use oxydsp_flowgraph::tag::Tags; use rand::random; const SAMPLE_RATE: usize = 48_000; const CARRIER: f64 = 1000.; -const SAMPLE_PER_SYMBOL: usize = 96; +const SAMPLE_PER_SYMBOL: usize = 48; fn main() { @@ -57,25 +65,51 @@ fn main() fn demodulator() { let (signal_tx, signal_rx) = channel(); - std::thread::spawn(move || { - let udp_socket = UdpSocket::bind("0.0.0.0:25565").unwrap(); - let mut buffer = [0u8; 4096]; - while let Ok(size) = udp_socket.recv(&mut buffer) - { - let read = &mut buffer[..size]; - for bytes in read.chunks(4) - { - if bytes.len() == 4 + // std::thread::spawn(move || { + // let udp_socket = UdpSocket::bind("0.0.0.0:25565").unwrap(); + // let mut buffer = [0u8; 4096]; + // while let Ok(size) = udp_socket.recv(&mut buffer) + // { + // let read = &mut buffer[..size]; + // for bytes in read.chunks(4) + // { + // if bytes.len() == 4 + // { + // let _ = signal_tx + // .send(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])); + // } + // } + // } + // }); + + // Open output device + let host = cpal::default_host(); + let device = host + .default_input_device() + .expect("no output device available"); + 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 _ = signal_tx - .send(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])); + let _ = signal_tx.send(*x); } - } - } - }); + }, + move |err| panic!(), + None, // None=blocking, Some(Duration)=timeout + ) + .unwrap(); let (signal_source, signal) = IterSource::new(signal_rx.into_iter()); - let (mut zero_if, baseband) = ZeroIf::new( + let (mut zero_if, baseband): (_, In>) = ZeroIf::new( signal, DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64).into(), ); @@ -84,40 +118,67 @@ fn demodulator() 100, )); - // Matched corellator - let (tee, baseband, baseband_late) = Tee::new(baseband); + // Pulse shaping + // gaussian fir + let fir = Fir((0..SAMPLE_PER_SYMBOL) + .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) + .collect()) + .normalized(); - // Delay - let (delay, baseband_late) = Scan::new( - baseband_late, - VecDeque::from([Complex::::ZERO; SAMPLE_PER_SYMBOL]), + // RRC fir + let rrc_symbol_count = 4; + let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count; + let fir = Fir((0..rrc_fir_length) + .map(|x| { + let centered = oxydsp_dsp::map( + x as f32, + 0., + rrc_fir_length as f32, + -(rrc_symbol_count as f32) * 0.5, + rrc_symbol_count as f32 * 0.5, + ); + root_raised_cosine(centered, 1., 1.) + }) + .collect()) + .normalized(); + + let (tee, b_a, b_b) = Tee::new(baseband); + + let (delay, b_b) = Scan::new( + b_b, + VecDeque::from(vec![Complex::::new(0., 0.); SAMPLE_PER_SYMBOL]), |history, x| { - history.push_front(x); - history.pop_back().unwrap().conj() + history.push_back(x); + history.pop_front().unwrap().conj() }, ); - let (multiplier, matched) = Multiplier::new(baseband, baseband_late); + let (mult, baseband) = Multiplier::new(b_a, b_b); + let (to_arg, baseband) = Map::new(baseband, |x| (x * Complex::::new(0., -1.)).arg()); + let (phase_filter, baseband) = FirFilter::new(baseband, fir); - let (constellation_tx, conbtellation_rx) = channel(); - let (sender_scan, matched) = Scan::new(matched, VecDeque::new(), move |history, x| { - history.push_back(x.re); - if history.len() >= SAMPLE_PER_SYMBOL + let (eye_tx, eye_rx) = channel::>(); + let (eye, baseband) = Scan::new(baseband, VecDeque::new(), move |state, x| { + if state.len() >= SAMPLE_PER_SYMBOL * 2 { - let _ = constellation_tx.send(Vec::from(history.clone())); - history.clear(); + //state.pop_front(); + eye_tx.send(Vec::from(state.clone())); + state.clear(); } + state.push_back(x); }); - let tx_sink = NullSink::new(matched); + let null_sink = NullSink::new(baseband); let graph = flowgraph![ signal_source, zero_if, - tx_sink, - delay, - multiplier, + null_sink, + eye, + phase_filter, tee, - sender_scan + to_arg, + delay, + mult ]; File::create("demodulator.dot") .unwrap() @@ -127,9 +188,9 @@ fn demodulator() let mut constellation = VecDeque::new(); eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| { - while let Ok(sample) = conbtellation_rx.try_recv() + while let Ok(sample) = eye_rx.try_recv() { - if constellation.len() >= 100 + if constellation.len() >= 300 { let _ = constellation.pop_back(); } @@ -188,17 +249,13 @@ fn modulator() }); let mut tags = Tags::new(); - let (bit_source, bits) = RxSource::new(data_rx); + let (bit_source, bits): (_, In) = RxSource::new(data_rx); //let last_tag = tags.allocate_tag("finished"); let (phase_map, phase) = Scan::new(bits, Complex::::new(1., 0.), |state, bit| { if bit { - *state *= Complex::new(1., 0.); - } - else - { - *state *= Complex::new(-1., 0.) + *state *= Complex::::new(-1., 0.); } *state }); @@ -206,16 +263,26 @@ fn modulator() // Convert to pulse train let (repeater, phase) = FlatMap::new(phase, |x| { let mut v = vec![Complex::::new(0., 0.); SAMPLE_PER_SYMBOL - 1]; + //let mut v = vec![x; SAMPLE_PER_SYMBOL - 1]; v.push(x); v }); - // Pulse shaping - // gaussian fir - let fir = Fir((0..SAMPLE_PER_SYMBOL) - .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32)) + // RRC fir + let rrc_symbol_count = 4; + let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count; + let fir = Fir((0..rrc_fir_length) + .map(|x| { + let centered = oxydsp_dsp::map( + x as f32, + 0., + rrc_fir_length as f32, + -(rrc_symbol_count as f32) * 0.5, + rrc_symbol_count as f32 * 0.5, + ); + root_raised_cosine(centered, 1., 1.) + }) .collect()); - //.normalized(); let (phase_filter, phase) = FirFilter::new(phase, fir); @@ -223,16 +290,32 @@ fn modulator() DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64).into(), ); let (multiplier, passband) = Multiplier::new(passband, phase); - let (output_tx, output_rx) = channel::>(); - let (tx_map, passband) = MapResultTagged::new(passband, move |s| { - let _ = output_tx.send(s.0); - // if s.retrieve(&last_tag).is_some() - // { - // return (s, BlockResult::Exit); - // } - (s, BlockResult::Ok) + + let reverb_length = 200; + // let (reverb, passband) = FirFilter::new( + // passband, + // Fir((0..reverb_length) + // .map(|x| (-20. * (x as f32) / (reverb_length as f32)).exp()) + // .collect()) + // .normalized(), + // ); + + let (awgn, passband) = Map::new(passband, |x| { + x + Complex::::new(2. * (random::() - 0.5), 2. * (random::() - 0.5)) * 0.0 }); - let null_sink = NullSink::new(passband); + + let (udp_map, passband) = Scan::new( + passband, + UdpSocket::bind("0.0.0.0:0").unwrap(), + |sckt, sample| { + std::thread::sleep(Duration::from_micros(15)); + sckt.send_to(&(sample.re).to_le_bytes(), "127.0.0.1:25565") + .unwrap(); + sample + }, + ); + let (audio_tx, audio_rx) = channel(); + let tx_sink = TxSink::new(passband, audio_tx); let graph = flowgraph![ bit_source, @@ -240,20 +323,45 @@ fn modulator() repeater, oscillator, multiplier, - tx_map, - null_sink, + tx_sink, //reverb, + awgn, + udp_map, phase_filter ]; - graph.run(); - let udp_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - while let Ok(sample) = output_rx.recv() - { - std::thread::sleep(Duration::from_micros(15)); - let val = sample.re + random::() * 0.5; - let _ = udp_socket.send_to(&val.to_le_bytes(), "127.0.0.1:25565"); - let _ = udp_socket.send_to(&val.to_le_bytes(), "127.0.0.1:25566"); - } + // Open output device + let host = cpal::default_host(); + let device = host + .default_output_device() + .expect("no output device available"); + let mut supported_configs_range = device + .supported_output_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_output_stream( + &supported_config.into(), + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { + for x in data.iter_mut() + { + if let Ok(y) = audio_rx.try_recv() + { + *x = y.re * 0.01; + } + else + { + *x = 0.; + } + } + }, + move |err| panic!(), + None, // None=blocking, Some(Duration)=timeout + ) + .unwrap(); + let _ = graph.run().join(); } pub fn to_bits(n: u8) -> [bool; 8] @@ -287,3 +395,30 @@ pub fn gaussian(sigma: f32, t: f32) -> f32 let sq = (t - 0.5) / sigma; (-sq * sq).exp() } + +pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32 +{ + let eps = 1e-8; + + if t.abs() < eps + { + // t = 0 special case + return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0)); + } + + if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps + { + // t = ±T / (4β) special case + let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin(); + let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos(); + return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2); + } + + // General case + let numerator = (PI * t * (1.0 - beta) / ts).sin() + + 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos(); + + let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts; + + (1.0 / ts.sqrt()) * (numerator / denominator) +} diff --git a/oxydsp-dsp/src/lib.rs b/oxydsp-dsp/src/lib.rs index cc324e5..b0ef802 100644 --- a/oxydsp-dsp/src/lib.rs +++ b/oxydsp-dsp/src/lib.rs @@ -5,7 +5,7 @@ pub mod filtering; pub mod synthesis; pub mod units; -fn map(x: T, x_min: T, x_max: T, o_min: T, o_max: T) -> T +pub fn map(x: T, x_min: T, x_max: T, o_min: T, o_max: T) -> T { ((x - x_min) / (x_max - x_min)) * (o_max - o_min) + o_min }