Stream rework, qpsk example, splitter/merger
This commit is contained in:
@ -8,7 +8,6 @@ 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;
|
||||
@ -17,20 +16,17 @@ 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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1,38 +1,37 @@
|
||||
use std::cell::RefCell;
|
||||
use std::os::unix::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use cpal::traits::StreamTrait;
|
||||
use egui::Color32;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::PlotPoints;
|
||||
use egui_plot::Points;
|
||||
use num::Complex;
|
||||
use num::complex::ComplexFloat;
|
||||
use num::traits::sign;
|
||||
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
|
||||
use oxydsp_dsp::blocks::filtering::pulse_shaping::PulseShaper;
|
||||
use oxydsp_dsp::blocks::iq::zero_if::ZeroIf;
|
||||
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::Map;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Merger;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::NullSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::ScanTagged;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Splitter;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::graph_control::GraphKiller;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::filtering::history_buf::HistoryBuf;
|
||||
use oxydsp_dsp::map;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::stream;
|
||||
use oxydsp_flowgraph::tag::Tagged;
|
||||
use oxydsp_flowgraph::tag::Tags;
|
||||
use rand::random;
|
||||
|
||||
@ -128,9 +127,14 @@ pub fn modulator()
|
||||
[false, false] => Complex::new(-1., -1.),
|
||||
});
|
||||
|
||||
// let (pulse_shaper, iq) = PulseShaper::new(
|
||||
// iq,
|
||||
// Fir::root_raised_cosine(4 * SAMPLE_PER_SYMBOL, 1., SAMPLE_PER_SYMBOL),
|
||||
// SAMPLE_PER_SYMBOL,
|
||||
// );
|
||||
let (pulse_shaper, iq) = PulseShaper::new(
|
||||
iq,
|
||||
Fir::root_raised_cosine(4 * SAMPLE_PER_SYMBOL, 0.5, SAMPLE_PER_SYMBOL),
|
||||
Fir::gaussian(SAMPLE_PER_SYMBOL, 3.),
|
||||
SAMPLE_PER_SYMBOL,
|
||||
);
|
||||
let (carrier_oscillator, carrier) = OscillatorSource::new(
|
||||
@ -176,51 +180,82 @@ pub fn demodulator()
|
||||
let (downconverter, iq) = CostasLoop::new(
|
||||
signal,
|
||||
DigitalFrequency::from_time_frequency(CARRRIER_FREQ, SAMPLE_RATE as f64),
|
||||
Fir::proportional_integral(100, 0.000, 0.0000),
|
||||
Fir::proportional_integral(100, 0.01, 0.00005),
|
||||
);
|
||||
|
||||
//let agc_filter = Fir::proportional_integral(100, 0.1, 0.001);
|
||||
// let (agc, iq) = Scan::new(iq, 1., |gain, iq|
|
||||
// {
|
||||
// let mu = 0.1;
|
||||
// let mag = iq.abs();
|
||||
// *gain += mu * (1. - mag * *gain);
|
||||
//
|
||||
// iq * *gain
|
||||
// });
|
||||
//let mut agc_filter = oxydsp_dsp::filtering::fir::FirFilter::new(Fir::proportional_integral(100, 0.1, 0.001));
|
||||
let (agc_error_tx, agc_error_rx) = std::sync::mpsc::channel();
|
||||
let (agc, iq) = Scan::new(iq, (1., 0.), move |(gain, error_low), iq| {
|
||||
let out = *gain * iq;
|
||||
|
||||
let (matched_filter, iq) = FirFilter::new(
|
||||
let error = 1. - out.abs();
|
||||
let alpha = 0.01;
|
||||
*error_low = (1. - alpha) * *error_low + alpha * error;
|
||||
let _ = agc_error_tx.send(*error_low);
|
||||
*gain += 0.01 * error; // Feedback
|
||||
|
||||
out
|
||||
});
|
||||
|
||||
const DECIMATION: usize = 1;
|
||||
// let (matched_filter, iq) = FirFilter::<_, _, _, DECIMATION>::new_decimating(
|
||||
// iq,
|
||||
// Fir::<Complex<f32>>::lowpass(DigitalFrequency::from_time_frequency(2000., SAMPLE_RATE as f64), 100)
|
||||
// .normalized_len().convoluted_with(&Fir::<Complex<f32>>::root_raised_cosine(4 * SAMPLE_PER_SYMBOL, 1., SAMPLE_PER_SYMBOL)
|
||||
// .normalized_sqr())
|
||||
// );
|
||||
// let (matched_filter, iq) = FirFilter::<_, _, _, DECIMATION>::new_decimating(
|
||||
// iq,
|
||||
// Fir::<Complex<f32>>::lowpass(DigitalFrequency::from_time_frequency(2000., SAMPLE_RATE as f64), 100)
|
||||
// .normalized_len().convoluted_with(&Fir::<Complex<f32>>::gaussian(SAMPLE_PER_SYMBOL, 3.).normalized_sqr())
|
||||
// );
|
||||
let (matched_filter, iq) = FirFilter::<_, _, _, DECIMATION>::new_decimating(
|
||||
iq,
|
||||
Fir::<f32>::root_raised_cosine(4 * SAMPLE_PER_SYMBOL, 0.5, SAMPLE_PER_SYMBOL)
|
||||
.normalized_sqr(),
|
||||
Fir::<Complex<f32>>::gaussian(SAMPLE_PER_SYMBOL, 3.).normalized_sqr()
|
||||
);
|
||||
|
||||
let (splitter, [iq_i, iq_q]) = Splitter::new(iq);
|
||||
let (proj_i, i) = Map::new(iq_i, |x| x.re);
|
||||
let (proj_q, q) = Map::new(iq_q, |x| x.im);
|
||||
|
||||
let mut tags = Tags::default();
|
||||
|
||||
let elg_filter = Fir::proportional_integral(100, 0.2, 0.002);
|
||||
let i_key = tags.allocate_tag("i tag");
|
||||
let q_key = tags.allocate_tag("q tag");
|
||||
let (elg_i, i) = EarlyLateGate::new(i, elg_filter.clone(), SAMPLE_PER_SYMBOL / DECIMATION, i_key.clone());
|
||||
let (elg_q, q) = EarlyLateGate::new(q, elg_filter.clone(), SAMPLE_PER_SYMBOL / DECIMATION, q_key.clone());
|
||||
|
||||
let (eye_i_tx, eye_i_rx) = std::sync::mpsc::channel::<Vec<f32>>();
|
||||
let (eye_q_tx, eye_q_rx) = std::sync::mpsc::channel::<Vec<f32>>();
|
||||
|
||||
let (merger, iq) = Merger::new([i, q]);
|
||||
|
||||
let (constellation_tx, constellation_rx) = std::sync::mpsc::channel();
|
||||
|
||||
// let (debug, iq) = Scan::new(
|
||||
// iq,
|
||||
// (
|
||||
// HistoryBuf::new(0., SAMPLE_PER_SYMBOL * 2),
|
||||
// HistoryBuf::new(0., SAMPLE_PER_SYMBOL * 2),
|
||||
// 0usize,
|
||||
// ),
|
||||
// move |(buf_i, buf_q, counter), x| {
|
||||
// buf_i.push(x.re);
|
||||
// buf_q.push(x.im);
|
||||
// let _ = constellation_tx.send(x);
|
||||
// *counter += 1;
|
||||
// if *counter >= SAMPLE_PER_SYMBOL * 2
|
||||
// {
|
||||
// let _ = eye_i_tx.send(buf_i.as_slice().iter().copied().collect::<Vec<_>>());
|
||||
// let _ = eye_q_tx.send(buf_q.as_slice().iter().copied().collect::<Vec<_>>());
|
||||
// *counter = 0;
|
||||
// }
|
||||
// x
|
||||
// },
|
||||
// );
|
||||
let tx_sink = TxSink::new(iq, constellation_tx);
|
||||
let (debug, iq) = ScanTagged::new(
|
||||
iq,
|
||||
(
|
||||
HistoryBuf::new(0., (SAMPLE_PER_SYMBOL * 2) / DECIMATION),
|
||||
HistoryBuf::new(0., (SAMPLE_PER_SYMBOL * 2) / DECIMATION),
|
||||
),
|
||||
move |(buf_i, buf_q), input| {
|
||||
let ([re, im], tag) = input.into();
|
||||
buf_i.push(re);
|
||||
buf_q.push(im);
|
||||
|
||||
if tag.is_some_and(|t| t.retrieve(&i_key).is_some())
|
||||
{
|
||||
let _ = constellation_tx.send(Complex::new(re, im));
|
||||
let _ = eye_i_tx.send(buf_i.as_slice().iter().copied().collect::<Vec<_>>());
|
||||
let _ = eye_q_tx.send(buf_q.as_slice().iter().copied().collect::<Vec<_>>());
|
||||
}
|
||||
//(Complex::new(re, im), None).into()
|
||||
let k: Tagged<()> = ((), None).into();
|
||||
k
|
||||
},
|
||||
);
|
||||
let tx_sink = NullSink::new(iq);
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
@ -239,9 +274,7 @@ pub fn demodulator()
|
||||
move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
||||
for x in data
|
||||
{
|
||||
//let _ = audio_tx.send(*x * 10.);
|
||||
let _ = audio_tx.send(random::<f32>());
|
||||
//let _ = audio_tx.send(Complex::new(random::<f32>(), random::<f32>()));
|
||||
let _ = audio_tx.send(*x * 100.);
|
||||
}
|
||||
},
|
||||
move |_err| {},
|
||||
@ -252,17 +285,23 @@ pub fn demodulator()
|
||||
let graph = flowgraph![
|
||||
rx_source,
|
||||
downconverter,
|
||||
// agc,
|
||||
agc,
|
||||
matched_filter,
|
||||
//debug,
|
||||
//null_sink
|
||||
splitter,
|
||||
proj_i,
|
||||
proj_q,
|
||||
elg_i,
|
||||
elg_q,
|
||||
merger,
|
||||
debug,
|
||||
tx_sink
|
||||
];
|
||||
graph.run(6);
|
||||
|
||||
let mut eye_i_history = HistoryBuf::new(vec![], 200);
|
||||
let mut eye_q_history = HistoryBuf::new(vec![], 200);
|
||||
let mut constellation = HistoryBuf::new(Complex::new(0., 0.), 5000);
|
||||
let mut constellation = HistoryBuf::new(Complex::new(0., 0.), 500);
|
||||
let mut agc_error = HistoryBuf::new(0., 50_000);
|
||||
eframe::run_simple_native("Window", Default::default(), move |ctx, _frame| {
|
||||
for eye in eye_i_rx.try_iter().take(200)
|
||||
{
|
||||
@ -272,10 +311,14 @@ pub fn demodulator()
|
||||
{
|
||||
eye_q_history.push(eye);
|
||||
}
|
||||
for point in constellation_rx.try_iter().take(5000)
|
||||
for point in constellation_rx.try_iter().take(500)
|
||||
{
|
||||
constellation.push(point);
|
||||
}
|
||||
for x in agc_error_rx.try_iter().take(5000)
|
||||
{
|
||||
agc_error.push(x);
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("plot")
|
||||
@ -293,6 +336,19 @@ pub fn demodulator()
|
||||
.color(Color32::YELLOW.gamma_multiply_u8(70)),
|
||||
);
|
||||
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"AGC Error",
|
||||
agc_error
|
||||
.as_slice()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, point)| [map(i as f64, 0., 50_000., -2., 2.), *point as f64 + 2.])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.color(Color32::YELLOW.gamma_multiply_u8(70)),
|
||||
);
|
||||
|
||||
for (eye_i, eye_q) in eye_i_history
|
||||
.as_slice()
|
||||
.iter()
|
||||
@ -305,7 +361,7 @@ pub fn demodulator()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
[i as f64 / (SAMPLE_PER_SYMBOL as f64 * 2.) + 1., *x as f64]
|
||||
[i as f64 / ((SAMPLE_PER_SYMBOL as f64 * 2.) / DECIMATION as f64) + 1., *x as f64]
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
@ -319,27 +375,27 @@ pub fn demodulator()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
[*x as f64, i as f64 / (SAMPLE_PER_SYMBOL as f64 * 2.) - 2.]
|
||||
[*x as f64, i as f64 / ((SAMPLE_PER_SYMBOL as f64 * 2.) / DECIMATION as f64) - 2.]
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::GREEN),
|
||||
);
|
||||
|
||||
plot_ui.points(
|
||||
Points::new(
|
||||
"Constellation",
|
||||
eye_i
|
||||
.iter()
|
||||
.zip(eye_q.iter())
|
||||
.skip(SAMPLE_PER_SYMBOL / 2)
|
||||
.step_by(SAMPLE_PER_SYMBOL)
|
||||
.map(|(i, q)| [*i as f64, *q as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.color(Color32::GREEN)
|
||||
.radius(1.5),
|
||||
);
|
||||
// plot_ui.points(
|
||||
// Points::new(
|
||||
// "Constellation",
|
||||
// eye_i
|
||||
// .iter()
|
||||
// .zip(eye_q.iter())
|
||||
// .skip(SAMPLE_PER_SYMBOL / DECIMATION)
|
||||
// .step_by(SAMPLE_PER_SYMBOL / DECIMATION)
|
||||
// .map(|(i, q)| [*i as f64, *q as f64])
|
||||
// .collect::<PlotPoints>(),
|
||||
// )
|
||||
// .color(Color32::GREEN)
|
||||
// .radius(1.5),
|
||||
// );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
8
examples/simple/Cargo.toml
Normal file
8
examples/simple/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "simple"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
31
examples/simple/src/main.rs
Normal file
31
examples/simple/src/main.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use oxydsp_dsp::blocks::utilities::adapters::NullSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
|
||||
fn main()
|
||||
{
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let (rx_source, numbers) = RxSource::new(rx);
|
||||
let (inspect, numbers) = Scan::new(numbers, 0, |state, x: usize| {
|
||||
if x.is_multiple_of(100)
|
||||
{
|
||||
println!("{}", x);
|
||||
}
|
||||
x
|
||||
});
|
||||
let null_sink = NullSink::new(numbers);
|
||||
let graph = flowgraph![rx_source, inspect, null_sink];
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut x = 0usize;
|
||||
loop
|
||||
{
|
||||
let _ = tx.send(x);
|
||||
x += 1;
|
||||
}
|
||||
});
|
||||
graph.run(1).join();
|
||||
}
|
||||
Reference in New Issue
Block a user