Better tag interface, starting bpsk
This commit is contained in:
15
examples/dpsk-modem/Cargo.toml
Normal file
15
examples/dpsk-modem/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "dpsk-modem"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
egui = "0.33.3"
|
||||
egui_plot = "0.34.1"
|
||||
eframe = { version = "0.33.3", features = ["default_fonts", "wayland"] }
|
||||
num = "0.4.3"
|
||||
hound = "3.5.1"
|
||||
rand = "0.10.0"
|
||||
cpal = "0.17.3"
|
||||
BIN
examples/dpsk-modem/mod.wav
Normal file
BIN
examples/dpsk-modem/mod.wav
Normal file
Binary file not shown.
153
examples/dpsk-modem/src/main.rs
Normal file
153
examples/dpsk-modem/src/main.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Duration;
|
||||
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui_plot::PlotPoints;
|
||||
use egui_plot::Points;
|
||||
use num::Complex;
|
||||
use num::Integer;
|
||||
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::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::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::tag::Tags;
|
||||
use rand::random;
|
||||
use rand::random_bool;
|
||||
|
||||
const SAMPLE_RATE: usize = 48_000;
|
||||
const CARRIER: f64 = 1000.;
|
||||
const SAMPLE_PER_SYMBOL: usize = 96;
|
||||
|
||||
fn main()
|
||||
{
|
||||
modulator();
|
||||
demodulator();
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
fn demodulator()
|
||||
{
|
||||
let mut reader = hound::WavReader::open("mod.wav").unwrap();
|
||||
let samples = reader
|
||||
.samples::<i16>()
|
||||
.map(|x| (x.unwrap() as f32) / (i16::MAX as f32))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (signal_source, signal) = IterSource::new(samples.into_iter());
|
||||
let (mut zero_if, baseband) = ZeroIf::new(
|
||||
signal,
|
||||
DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64).into(),
|
||||
);
|
||||
zero_if.set_fir(Fir::lowpass(
|
||||
DigitalFrequency::from_time_frequency(CARRIER + 100., SAMPLE_RATE as f64),
|
||||
100,
|
||||
));
|
||||
|
||||
let (constellation_tx, conbtellation_rx) = channel::<Complex<f32>>();
|
||||
let tx_sink = TxSink::new(baseband, constellation_tx);
|
||||
|
||||
let graph = flowgraph![signal_source, zero_if, tx_sink];
|
||||
graph.run();
|
||||
|
||||
let mut constellation = VecDeque::new();
|
||||
let mut n = 0;
|
||||
eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| {
|
||||
while let Ok(sample) = conbtellation_rx.try_recv()
|
||||
{
|
||||
if constellation.len() >= 100_000
|
||||
{
|
||||
let _ = constellation.pop_back();
|
||||
}
|
||||
|
||||
if n.is_multiple_of(&SAMPLE_PER_SYMBOL)
|
||||
{
|
||||
constellation.push_front(sample);
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("hello").show(ui, |plot_ui| {
|
||||
plot_ui.points(
|
||||
Points::new(
|
||||
"constellation",
|
||||
constellation
|
||||
.iter()
|
||||
.map(|s| [s.re as f64, s.im as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.id("constellation")
|
||||
.color(Color32::GREEN),
|
||||
);
|
||||
});
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn modulator()
|
||||
{
|
||||
let random_source = (0..1000).map(|_| random_bool(0.5));
|
||||
|
||||
let mut tags = Tags::new();
|
||||
let (mut bit_source, bits) = IterSource::new(random_source);
|
||||
let last_tag = tags.allocate_tag("finished");
|
||||
bit_source.tag_last_with(last_tag.clone());
|
||||
|
||||
let (phase_map, phase) = Scan::new(bits, 1., |state, bit| {
|
||||
if bit
|
||||
{
|
||||
*state *= -1.;
|
||||
}
|
||||
*state
|
||||
});
|
||||
let (repeater, phase) = Repeat::new(phase, SAMPLE_PER_SYMBOL);
|
||||
|
||||
let (oscillator, passband) = OscillatorSource::<f32>::new(
|
||||
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 null_sink = NullSink::new(passband);
|
||||
|
||||
let graph = flowgraph![
|
||||
bit_source, phase_map, repeater, oscillator, multiplier, tx_map, null_sink
|
||||
];
|
||||
graph.run();
|
||||
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: SAMPLE_RATE as u32,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
let mut writer = hound::WavWriter::create("mod.wav", spec).unwrap();
|
||||
for sample in output_rx.iter()
|
||||
{
|
||||
let amplitude = i16::MAX as f32;
|
||||
writer
|
||||
.write_sample(((sample.re + random::<f32>() * 0.2) * amplitude) as i16)
|
||||
.unwrap();
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user