Bfsk modem isolated

This commit is contained in:
2026-03-26 10:44:46 +01:00
parent 4d548a7973
commit cc3ae754ff
14 changed files with 623 additions and 92 deletions

View File

@ -6,17 +6,21 @@
ZeroIf_1 [label="{ {<i0> input}| ZeroIf |{<o0> output} }"];
NullSink_2 [label="{ {<i0> input}| NullSink }"];
Scan_3 [label="{ {<i0> input}| Scan |{<o0> output} }"];
Multiplier_4 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
FirFilter_4 [label="{ {<i0> input}| FirFilter |{<o0> output} }"];
Tee_5 [label="{ {<i0> input}| Tee |{<o0> output_a|<o1> output_b} }"];
Scan_6 [label="{ {<i0> input}| Scan |{<o0> output} }"];
Map_6 [label="{ {<i0> input}| Map |{<o0> output} }"];
Scan_7 [label="{ {<i0> input}| Scan |{<o0> output} }"];
Multiplier_8 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
IterSource_0:o0 -> ZeroIf_1:i0 [label="f32"];
ZeroIf_1:o0 -> Tee_5:i0 [label="num_complex::Complex<f32>"];
Scan_3:o0 -> Multiplier_4:i1 [label="num_complex::Complex<f32>"];
Multiplier_4:o0 -> Scan_6:i0 [label="num_complex::Complex<f32>"];
Tee_5:o0 -> Multiplier_4:i0 [label="num_complex::Complex<f32>"];
Tee_5:o1 -> Scan_3:i0 [label="num_complex::Complex<f32>"];
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<f32>"];
Tee_5:o1 -> Scan_7:i0 [label="num_complex::Complex<f32>"];
Map_6:o0 -> FirFilter_4:i0 [label="f32"];
Scan_7:o0 -> Multiplier_8:i1 [label="num_complex::Complex<f32>"];
Multiplier_8:o0 -> Map_6:i0 [label="num_complex::Complex<f32>"];
}

Binary file not shown.

View File

@ -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<Complex<f32>>) = 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::<f32>::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::<f32>::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::<f32>::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::<Vec<f32>>();
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<bool>) = RxSource::new(data_rx);
//let last_tag = tags.allocate_tag("finished");
let (phase_map, phase) = Scan::new(bits, Complex::<f32>::new(1., 0.), |state, bit| {
if bit
{
*state *= Complex::new(1., 0.);
}
else
{
*state *= Complex::new(-1., 0.)
*state *= Complex::<f32>::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::<f32>::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::<Complex<f32>>();
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::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 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::<f32>() * 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)
}