Bfsk modem isolated
This commit is contained in:
1
examples/bfsk-modem-tx/.gitignore
vendored
Normal file
1
examples/bfsk-modem-tx/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
||||
12
examples/bfsk-modem-tx/Cargo.toml
Normal file
12
examples/bfsk-modem-tx/Cargo.toml
Normal file
@ -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"
|
||||
BIN
examples/bfsk-modem-tx/mod.wav
Normal file
BIN
examples/bfsk-modem-tx/mod.wav
Normal file
Binary file not shown.
25
examples/bfsk-modem-tx/out.dot
Normal file
25
examples/bfsk-modem-tx/out.dot
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
digraph G {
|
||||
node [shape=record];
|
||||
rankdir=TB;
|
||||
IterSource_0 [label="{ IterSource |{<o0> output} }"];
|
||||
FirFilter_1 [label="{ {<i0> input}| FirFilter |{<o0> output} }"];
|
||||
Map_2 [label="{ {<i0> input}| Map |{<o0> output} }"];
|
||||
Repeat_3 [label="{ {<i0> input}| Repeat |{<o0> output} }"];
|
||||
Nco_4 [label="{ {<i0> frequency}| Nco |{<o0> output} }"];
|
||||
OscillatorSource_5 [label="{ OscillatorSource |{<o0> output} }"];
|
||||
Multiplier_6 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
|
||||
MapResultTagged_7 [label="{ {<i0> input}| MapResultTagged |{<o0> output} }"];
|
||||
NullSink_8 [label="{ {<i0> 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<f32>"];
|
||||
OscillatorSource_5:o0 -> Multiplier_6:i1 [label="num_complex::Complex<f32>"];
|
||||
Multiplier_6:o0 -> MapResultTagged_7:i0 [label="num_complex::Complex<f32>"];
|
||||
MapResultTagged_7:o0 -> NullSink_8:i0 [label="num_complex::Complex<f32>"];
|
||||
|
||||
}
|
||||
|
||||
BIN
examples/bfsk-modem-tx/output.wav
Normal file
BIN
examples/bfsk-modem-tx/output.wav
Normal file
Binary file not shown.
54
examples/bfsk-modem-tx/src/main.rs
Normal file
54
examples/bfsk-modem-tx/src/main.rs
Normal file
@ -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()
|
||||
}
|
||||
211
examples/bfsk-modem-tx/src/transmitter.rs
Normal file
211
examples/bfsk-modem-tx/src/transmitter.rs
Normal file
@ -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<Vec<u8>>,
|
||||
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<Vec<u8>>) = sync_channel(128);
|
||||
let (packet_rec, packets): (_, In<Vec<u8>>) = 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::<f32>::new(freq);
|
||||
let (local_oscillator, lo) = OscillatorSource::<f32>::new(carrier.into());
|
||||
let (frontend, passband) = Multiplier::new(baseband, lo);
|
||||
let (audio_tx, audio_rx) = mpsc::channel::<Complex<f32>>();
|
||||
|
||||
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::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 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<u8>)
|
||||
{
|
||||
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)
|
||||
}
|
||||
@ -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<f32> = 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
|
||||
|
||||
@ -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::<Complex<f32>>();
|
||||
|
||||
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::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 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::<f32>() * 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)
|
||||
}
|
||||
|
||||
@ -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>"];
|
||||
|
||||
}
|
||||
|
||||
BIN
examples/dpsk-modem/output.wav
Normal file
BIN
examples/dpsk-modem/output.wav
Normal file
Binary file not shown.
@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user