Bunch'o things
This commit is contained in:
4534
Cargo.lock
generated
4534
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,3 +8,12 @@ ntw_flowgraph = {path = "ntw_flowgraph"}
|
||||
ntw_flowgraph_macros = {path = "ntw_flowgraph_macros"}
|
||||
ntw_dsp = {path = "ntw_dsp"}
|
||||
ringbuf = "0.4.8"
|
||||
num = "0.4.3"
|
||||
hound = "3.5.1"
|
||||
egui = "0.33.3"
|
||||
eframe = "0.33.3"
|
||||
egui_plot = "0.34.1"
|
||||
cpal = {version = "0.17.3"}
|
||||
rustfft = "6.4.1"
|
||||
rng = "0.1.0"
|
||||
rand = "0.10.0"
|
||||
|
||||
21
graph.dot
Normal file
21
graph.dot
Normal file
@ -0,0 +1,21 @@
|
||||
digraph {
|
||||
0 [ label = "\"FiniteIterSource\"" ]
|
||||
1 [ label = "\"Map\"" ]
|
||||
2 [ label = "\"ChannelSink\"" ]
|
||||
3 [ label = "\"ComplexNco\"" ]
|
||||
4 [ label = "\"ComplexOscillator\"" ]
|
||||
5 [ label = "\"Repeat\"" ]
|
||||
6 [ label = "\"Multiplier\"" ]
|
||||
7 [ label = "\"Tee\"" ]
|
||||
8 [ label = "\"Fft\"" ]
|
||||
9 [ label = "\"ChannelSink\"" ]
|
||||
0 -> 1 [ label = "()" ]
|
||||
1 -> 5 [ label = "()" ]
|
||||
3 -> 6 [ label = "()" ]
|
||||
4 -> 6 [ label = "()" ]
|
||||
5 -> 3 [ label = "()" ]
|
||||
6 -> 7 [ label = "()" ]
|
||||
7 -> 2 [ label = "()" ]
|
||||
7 -> 8 [ label = "()" ]
|
||||
8 -> 9 [ label = "()" ]
|
||||
}
|
||||
41
ntw_dsp/Cargo.lock
generated
41
ntw_dsp/Cargo.lock
generated
@ -64,6 +64,8 @@ dependencies = [
|
||||
"ntw_flowgraph",
|
||||
"ntw_flowgraph_macros",
|
||||
"num",
|
||||
"ringbuf",
|
||||
"rustfft",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -184,6 +186,15 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primal-check"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
@ -213,6 +224,20 @@ dependencies = [
|
||||
"portable-atomic-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustfft"
|
||||
version = "6.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89"
|
||||
dependencies = [
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"primal-check",
|
||||
"strength_reduce",
|
||||
"transpose",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@ -242,6 +267,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strength_reduce"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@ -253,6 +284,16 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transpose"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"strength_reduce",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
||||
@ -7,3 +7,5 @@ edition = "2024"
|
||||
ntw_flowgraph = {path = "../ntw_flowgraph/"}
|
||||
ntw_flowgraph_macros = {path = "../ntw_flowgraph_macros/"}
|
||||
num = "0.4.3"
|
||||
ringbuf = "0.4.8"
|
||||
rustfft = "6.4.1"
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
pub mod complex;
|
||||
pub mod early_late_gate;
|
||||
pub mod fft;
|
||||
pub mod fir;
|
||||
pub mod iir;
|
||||
pub mod iq;
|
||||
pub mod iter;
|
||||
pub mod map;
|
||||
pub mod math;
|
||||
pub mod nco;
|
||||
pub mod oscillator;
|
||||
pub mod tee;
|
||||
pub mod utilities;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use ntw_flowgraph::Block;
|
||||
use ntw_flowgraph::inout::{In, Out};
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
|
||||
|
||||
211
ntw_dsp/src/blocks/early_late_gate.rs
Normal file
211
ntw_dsp/src/blocks/early_late_gate.rs
Normal file
@ -0,0 +1,211 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Float;
|
||||
|
||||
use crate::filtering::fir::Fir;
|
||||
use crate::filtering::impulse_response::ImpulseResponse;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EarlyLateGateTag<T>
|
||||
{
|
||||
Symbol(T),
|
||||
InterSymbol(T),
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct EarlyLateGate<T: Float>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<EarlyLateGateTag<T>>,
|
||||
|
||||
symbol_length: usize,
|
||||
|
||||
// Window looking at symbol_length samples at a time
|
||||
window: VecDeque<T>,
|
||||
|
||||
// The current location of the window, in relation to the last sample
|
||||
window_location: usize,
|
||||
|
||||
window_center: usize,
|
||||
|
||||
// The next window location, in relation to the last sample such that the window is centered on
|
||||
// a symbol center (hopefully)
|
||||
next_sample: usize,
|
||||
loop_filter: Fir<T, T, T>,
|
||||
}
|
||||
|
||||
impl<T: Float> EarlyLateGate<T>
|
||||
{
|
||||
pub fn new(
|
||||
input: In<T>,
|
||||
loop_filter: ImpulseResponse<T>,
|
||||
symbol_length: usize,
|
||||
) -> (Self, In<EarlyLateGateTag<T>>)
|
||||
{
|
||||
let (output, samples) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
window: VecDeque::with_capacity(symbol_length),
|
||||
symbol_length,
|
||||
window_location: 0,
|
||||
window_center: symbol_length / 2,
|
||||
next_sample: symbol_length, // We assume that the first symbol is 1.5 windows into
|
||||
// the stream
|
||||
loop_filter: Fir::new(loop_filter),
|
||||
},
|
||||
samples,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> BlockWork for EarlyLateGate<T>
|
||||
{
|
||||
fn work(&mut self) -> ntw_flowgraph::BlockResult
|
||||
{
|
||||
// Handle begining, window is empty
|
||||
if self.window.len() < self.symbol_length
|
||||
{
|
||||
self.input
|
||||
.pop_iter()
|
||||
.take(self.symbol_length - self.window.len())
|
||||
.for_each(|x| self.window.push_back(x));
|
||||
|
||||
if self.input.available_len() == 0
|
||||
{
|
||||
return BlockResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
let max = self.input.available_len().min(self.output.vacant_len());
|
||||
for _ in 0..max
|
||||
{
|
||||
// Bring new sample in
|
||||
self.window.pop_front();
|
||||
self.window.push_back(self.input.try_pop().unwrap());
|
||||
self.window_location += 1;
|
||||
|
||||
let sample = self.window[self.window_center];
|
||||
if self.window_location >= self.next_sample
|
||||
{
|
||||
// Window is centered on a symbol
|
||||
let _ = self.output.try_push(EarlyLateGateTag::Symbol(sample));
|
||||
|
||||
// Sample early and late samples
|
||||
let early_index = self.window_center - (0.25 * self.symbol_length as f32) as usize;
|
||||
let late_index = self.window_center + (0.25 * self.symbol_length as f32) as usize;
|
||||
|
||||
let early_sample = self.window[early_index];
|
||||
let late_sample = self.window[late_index];
|
||||
|
||||
let error = (late_sample - early_sample) * sample;
|
||||
let correction = self.loop_filter.next(error);
|
||||
|
||||
// Figure out next sample location
|
||||
self.next_sample += (self.symbol_length as isize
|
||||
+ correction.floor().to_isize().unwrap_or(0))
|
||||
.max(0) as usize;
|
||||
|
||||
// Turn everything back relative to current sample
|
||||
self.next_sample -= self.window_location;
|
||||
self.window_location = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Window is not centered on a symbol
|
||||
let _ = self.output.try_push(EarlyLateGateTag::InterSymbol(sample));
|
||||
}
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct EyeExtractor<T>
|
||||
{
|
||||
#[input]
|
||||
input: In<EarlyLateGateTag<T>>,
|
||||
|
||||
output: Sender<Vec<T>>,
|
||||
|
||||
window: VecDeque<EarlyLateGateTag<T>>,
|
||||
symbol_length: usize,
|
||||
}
|
||||
|
||||
impl<T> EyeExtractor<T>
|
||||
{
|
||||
pub fn new(input: In<EarlyLateGateTag<T>>, output: Sender<Vec<T>>, symbol_length: usize)
|
||||
-> Self
|
||||
{
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
window: VecDeque::with_capacity(symbol_length),
|
||||
symbol_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> BlockWork for EyeExtractor<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
// Handle begining, window is empty
|
||||
if self.window.len() < self.symbol_length * 2
|
||||
{
|
||||
self.input
|
||||
.pop_iter()
|
||||
.take(2 * self.symbol_length - self.window.len())
|
||||
.for_each(|x| self.window.push_back(x));
|
||||
|
||||
if self.input.available_len() == 0
|
||||
{
|
||||
return BlockResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..self.input.available_len()
|
||||
{
|
||||
let x = self.input.try_pop().unwrap();
|
||||
self.window.pop_front();
|
||||
self.window.push_back(x);
|
||||
if let EarlyLateGateTag::Symbol(_) = self.window[self.symbol_length]
|
||||
{
|
||||
let vec = self
|
||||
.window
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|x| match x
|
||||
{
|
||||
EarlyLateGateTag::Symbol(x) | EarlyLateGateTag::InterSymbol(x) => x,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let _ = self.output.send(vec);
|
||||
}
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0
|
||||
}
|
||||
}
|
||||
75
ntw_dsp/src/blocks/fft.rs
Normal file
75
ntw_dsp/src/blocks/fft.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
use rustfft::FftDirection;
|
||||
use rustfft::FftNum;
|
||||
use rustfft::FftPlanner;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Fft<T>
|
||||
where
|
||||
T: FftNum,
|
||||
{
|
||||
#[input]
|
||||
input: In<Complex<T>>,
|
||||
|
||||
#[output]
|
||||
output: Out<Vec<Complex<T>>>,
|
||||
|
||||
length: usize,
|
||||
buffer: Vec<Complex<T>>,
|
||||
fft: Arc<dyn rustfft::Fft<T> + 'static>,
|
||||
}
|
||||
|
||||
impl<T: FftNum> Fft<T>
|
||||
{
|
||||
pub fn new(
|
||||
input: In<Complex<T>>,
|
||||
length: usize,
|
||||
direction: FftDirection,
|
||||
) -> (Self, In<Vec<Complex<T>>>)
|
||||
{
|
||||
let (output, ffts) = Stream::make(128);
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft = planner.plan_fft(length, direction);
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
length,
|
||||
buffer: Vec::with_capacity(length),
|
||||
fft,
|
||||
},
|
||||
ffts,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FftNum> BlockWork for Fft<T>
|
||||
{
|
||||
fn work(&mut self) -> ntw_flowgraph::BlockResult
|
||||
{
|
||||
if self.buffer.len() >= self.length
|
||||
{
|
||||
// Compute fft
|
||||
self.fft.process(self.buffer.as_mut_slice());
|
||||
let _ = self.output.try_push(self.buffer.clone());
|
||||
self.buffer.clear();
|
||||
}
|
||||
|
||||
self.buffer
|
||||
.extend(self.input.pop_iter().take(self.length - self.buffer.len()));
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
184
ntw_dsp/src/blocks/fir.rs
Normal file
184
ntw_dsp/src/blocks/fir.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use std::ops::Add;
|
||||
use std::ops::Mul;
|
||||
|
||||
use crate::filtering::impulse_response;
|
||||
use crate::filtering::impulse_response::ImpulseResponse;
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
use rustfft::FftDirection;
|
||||
use rustfft::FftNum;
|
||||
use rustfft::FftPlanner;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Fir<C, V, U>
|
||||
where
|
||||
C: Clone,
|
||||
V: Clone,
|
||||
V: Mul<C, Output = U>,
|
||||
U: Add<U, Output = U>,
|
||||
{
|
||||
filter: crate::filtering::fir::Fir<C, V, U>,
|
||||
|
||||
#[input]
|
||||
input: In<V>,
|
||||
|
||||
#[output]
|
||||
output: Out<U>,
|
||||
}
|
||||
|
||||
impl<C, V, U> Fir<C, V, U>
|
||||
where
|
||||
C: Clone,
|
||||
V: Clone,
|
||||
V: Mul<C, Output = U>,
|
||||
U: Add<U, Output = U>,
|
||||
{
|
||||
pub fn new(input: In<V>, impulse_response: ImpulseResponse<C>) -> (Fir<C, V, U>, In<U>)
|
||||
{
|
||||
let fir = crate::filtering::fir::Fir::new(impulse_response);
|
||||
let (output, stream) = Stream::make(1024);
|
||||
|
||||
(
|
||||
Self {
|
||||
filter: fir,
|
||||
input,
|
||||
output,
|
||||
},
|
||||
stream,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, V, U> BlockWork for Fir<C, V, U>
|
||||
where
|
||||
C: Clone,
|
||||
V: Clone,
|
||||
V: Mul<C, Output = U>,
|
||||
U: Add<U, Output = U>,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output
|
||||
.push_iter(self.input.pop_iter().map(|sample| self.filter.next(sample)));
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct FirFft<T>
|
||||
where
|
||||
T: FftNum,
|
||||
{
|
||||
#[input]
|
||||
input: In<Complex<T>>,
|
||||
|
||||
#[output]
|
||||
output: Out<Complex<T>>,
|
||||
|
||||
length: usize,
|
||||
buffer: Vec<Complex<T>>,
|
||||
transformed_ir: Vec<Complex<T>>,
|
||||
fft: Arc<dyn rustfft::Fft<T> + 'static>,
|
||||
ifft: Arc<dyn rustfft::Fft<T> + 'static>,
|
||||
accumulating: bool,
|
||||
}
|
||||
|
||||
impl<T: FftNum> FirFft<T>
|
||||
{
|
||||
pub fn new(
|
||||
input: In<Complex<T>>,
|
||||
impulse_response: ImpulseResponse<Complex<T>>,
|
||||
) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (output, filtered) = Stream::make(1024);
|
||||
let length = impulse_response.len();
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft = planner.plan_fft(impulse_response.len(), FftDirection::Forward);
|
||||
let ifft = planner.plan_fft(impulse_response.len(), FftDirection::Inverse);
|
||||
|
||||
let mut transformed_ir = impulse_response.0;
|
||||
fft.process(&mut transformed_ir);
|
||||
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
length,
|
||||
buffer: Vec::with_capacity(length),
|
||||
transformed_ir,
|
||||
fft,
|
||||
ifft,
|
||||
accumulating: true,
|
||||
},
|
||||
filtered,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FftNum> BlockWork for FirFft<T>
|
||||
{
|
||||
fn work(&mut self) -> ntw_flowgraph::BlockResult
|
||||
{
|
||||
if self.accumulating
|
||||
{
|
||||
if self.buffer.len() >= self.length && self.accumulating
|
||||
{
|
||||
// Compute fft
|
||||
self.fft.process(self.buffer.as_mut_slice());
|
||||
|
||||
// Convolve
|
||||
self.buffer
|
||||
.iter_mut()
|
||||
.zip(self.transformed_ir.iter())
|
||||
.for_each(|(x, y)| *x = *x * *y);
|
||||
|
||||
self.ifft.process(self.buffer.as_mut_slice());
|
||||
self.accumulating = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.buffer
|
||||
.extend(self.input.pop_iter().take(self.length - self.buffer.len()));
|
||||
}
|
||||
}
|
||||
|
||||
if !self.accumulating
|
||||
{
|
||||
let len = self.output.vacant_len().min(self.buffer.len());
|
||||
for _ in 0..len
|
||||
{
|
||||
let _ = self.output.try_push(self.buffer.pop().unwrap());
|
||||
}
|
||||
|
||||
if self.buffer.is_empty()
|
||||
{
|
||||
self.accumulating = true;
|
||||
}
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
if self.accumulating
|
||||
{
|
||||
self.input.available_len() > 0
|
||||
}
|
||||
else
|
||||
{
|
||||
self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
58
ntw_dsp/src/blocks/iir.rs
Normal file
58
ntw_dsp/src/blocks/iir.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::complex::ComplexFloat;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct SimpleDcBlock<T>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
prev_input: T,
|
||||
prev_output: T,
|
||||
r: T,
|
||||
}
|
||||
|
||||
impl<T: ComplexFloat> SimpleDcBlock<T>
|
||||
{
|
||||
pub fn new(input: In<T>, r: T) -> (Self, In<T>)
|
||||
{
|
||||
let (output, dc_removed) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
prev_input: T::zero(),
|
||||
prev_output: T::zero(),
|
||||
r,
|
||||
},
|
||||
dc_removed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ComplexFloat + Clone> BlockWork for SimpleDcBlock<T>
|
||||
{
|
||||
fn work(&mut self) -> ntw_flowgraph::BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.pop_iter().map(|x| {
|
||||
let out = x - self.prev_input + self.r * self.prev_output;
|
||||
self.prev_input = x;
|
||||
self.prev_output = out;
|
||||
out
|
||||
}));
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
98
ntw_dsp/src/blocks/iq.rs
Normal file
98
ntw_dsp/src/blocks/iq.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
use num::Float;
|
||||
use num::One;
|
||||
use num::Zero;
|
||||
use rustfft::FftNum;
|
||||
|
||||
use crate::Frequency;
|
||||
use crate::filtering::fir::Fir;
|
||||
use crate::filtering::impulse_response::ImpulseResponse;
|
||||
use crate::generation::Nco;
|
||||
use crate::generation::NcoType;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct IqSampler<T: Float>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<Complex<T>>,
|
||||
|
||||
bandpass_i: Fir<T, T, T>,
|
||||
bandpass_q: Fir<T, T, T>,
|
||||
local_oscillator: Nco<T>,
|
||||
}
|
||||
|
||||
impl<T: Float + FftNum> IqSampler<T>
|
||||
{
|
||||
pub fn new(input: In<T>, local_oscillator_frequency: Frequency) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (output, iq) = Stream::make(1024);
|
||||
let fir_len = 64;
|
||||
let mut transfer_function = vec![Complex::<T>::zero(); fir_len];
|
||||
let lobes_length = ((local_oscillator_frequency.as_rad() / PI)
|
||||
* (transfer_function.len() as f64 / 2.))
|
||||
.floor() as usize;
|
||||
|
||||
for i in 0..lobes_length
|
||||
{
|
||||
transfer_function[i] = Complex::<T>::one();
|
||||
transfer_function[fir_len - i - 1] = Complex::<T>::one();
|
||||
}
|
||||
let ir = ImpulseResponse(
|
||||
ImpulseResponse::from_transfer_function(&transfer_function)
|
||||
.0
|
||||
.iter()
|
||||
.map(|x| x.re)
|
||||
.collect(),
|
||||
)
|
||||
.normalized();
|
||||
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
bandpass_i: Fir::new(ir.clone()),
|
||||
bandpass_q: Fir::new(ir),
|
||||
local_oscillator: Nco::new(local_oscillator_frequency),
|
||||
},
|
||||
iq,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float + FftNum + NcoType> BlockWork for IqSampler<T>
|
||||
{
|
||||
fn work(&mut self) -> ntw_flowgraph::BlockResult
|
||||
{
|
||||
self.output.push_iter(
|
||||
&mut self
|
||||
.input
|
||||
.pop_iter()
|
||||
.zip(&mut self.local_oscillator)
|
||||
.map(|(x, lo)| {
|
||||
Complex::new(
|
||||
self.bandpass_i.next(x * lo.re),
|
||||
self.bandpass_q.next(x * lo.im),
|
||||
)
|
||||
//Complex::new(x * lo.re, x * lo.im)
|
||||
}),
|
||||
);
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
use std::iter::Peekable;
|
||||
|
||||
use ntw_flowgraph::{
|
||||
BlockWork,
|
||||
inout::{In, Out, Stream},
|
||||
};
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
|
||||
#[derive(Block)]
|
||||
@ -38,9 +39,10 @@ impl<T, I> BlockWork for IterSource<T, I>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
{
|
||||
fn work(&mut self)
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(&mut self.iter);
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
@ -48,3 +50,64 @@ where
|
||||
self.iter.peek().is_some() && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct FiniteIterSource<T, I>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
{
|
||||
iter: Peekable<I>,
|
||||
remaining: usize,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
}
|
||||
|
||||
impl<T, I> FiniteIterSource<T, I>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
{
|
||||
pub fn new(iter: I, len: usize) -> (Self, In<T>)
|
||||
{
|
||||
let (output, input) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
iter: iter.peekable(),
|
||||
remaining: len,
|
||||
output,
|
||||
},
|
||||
input,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T, I> FiniteIterSource<T, I>
|
||||
where
|
||||
I: Iterator<Item = T> + ExactSizeIterator,
|
||||
{
|
||||
pub fn from_len_iter(iter: I) -> (Self, In<T>)
|
||||
{
|
||||
let len = iter.len();
|
||||
Self::new(iter, len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I> BlockWork for FiniteIterSource<T, I>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.remaining -= self.output.push_iter((&mut self.iter).take(self.remaining));
|
||||
|
||||
match self.remaining
|
||||
{
|
||||
0 => BlockResult::Finished,
|
||||
_ => BlockResult::Ok,
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.remaining > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use ntw_flowgraph::{
|
||||
Block, BlockWork,
|
||||
inout::{In, Out, Stream},
|
||||
};
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
|
||||
#[derive(Block)]
|
||||
@ -33,12 +34,13 @@ impl<I, O, F> BlockWork for Map<I, O, F>
|
||||
where
|
||||
F: Fn(I) -> O,
|
||||
{
|
||||
fn work(&mut self)
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.pop_iter().map(&self.map));
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
|
||||
60
ntw_dsp/src/blocks/math.rs
Normal file
60
ntw_dsp/src/blocks/math.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use std::ops::Mul;
|
||||
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Multiplier<A, B, C>
|
||||
{
|
||||
#[input]
|
||||
input_a: In<A>,
|
||||
|
||||
#[input]
|
||||
input_b: In<B>,
|
||||
|
||||
#[output]
|
||||
product: Out<C>,
|
||||
}
|
||||
|
||||
impl<A, B, C> Multiplier<A, B, C>
|
||||
{
|
||||
pub fn new(input_a: In<A>, input_b: In<B>) -> (Self, In<C>)
|
||||
{
|
||||
let (product, stream) = Stream::make(1024);
|
||||
(
|
||||
Multiplier {
|
||||
input_a,
|
||||
input_b,
|
||||
product,
|
||||
},
|
||||
stream,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, C> BlockWork for Multiplier<A, B, C>
|
||||
where
|
||||
A: Mul<B, Output = C>,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.product.push_iter(
|
||||
self.input_a
|
||||
.pop_iter()
|
||||
.zip(self.input_b.pop_iter())
|
||||
.map(|(a, b)| a * b),
|
||||
);
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input_a.available_len() > 0
|
||||
&& self.input_b.available_len() > 0
|
||||
&& self.product.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
use ntw_flowgraph::Block;
|
||||
use ntw_flowgraph::inout::{In, Stream};
|
||||
use ntw_flowgraph::{BlockWork, inout::Out};
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
|
||||
@ -12,41 +14,65 @@ pub struct ComplexNco<T>
|
||||
{
|
||||
inner: crate::generation::Nco<T>,
|
||||
|
||||
#[input]
|
||||
freq_in: In<Frequency>,
|
||||
|
||||
#[output]
|
||||
out: Out<Complex<T>>,
|
||||
}
|
||||
|
||||
impl BlockWork for ComplexNco<f32>
|
||||
{
|
||||
fn work(&mut self)
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.out.push_iter(&mut self.inner);
|
||||
let len = self.freq_in.available_len().min(self.out.vacant_len());
|
||||
for _ in 0..len
|
||||
{
|
||||
self.inner.set_frequency(self.freq_in.try_pop().unwrap());
|
||||
self.out.try_push(self.inner.sample()).unwrap();
|
||||
self.inner.next();
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.out.vacant_len() > 0
|
||||
self.freq_in.available_len() > 0 && self.out.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockWork for ComplexNco<f64>
|
||||
{
|
||||
fn work(&mut self)
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.out.push_iter(&mut self.inner);
|
||||
let len = self.freq_in.available_len().min(self.out.vacant_len());
|
||||
for _ in 0..len
|
||||
{
|
||||
self.inner.set_frequency(self.freq_in.try_pop().unwrap());
|
||||
self.out.try_push(self.inner.sample()).unwrap();
|
||||
self.inner.next();
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.out.vacant_len() > 0
|
||||
self.freq_in.available_len() > 0 && self.out.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ComplexNco<T>
|
||||
{
|
||||
pub fn new(nco: Nco<T>) -> (Self, In<Complex<T>>)
|
||||
pub fn new(input_freq: In<Frequency>) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (a, b) = Stream::make(1024);
|
||||
(Self { inner: nco, out: a }, b)
|
||||
(
|
||||
Self {
|
||||
inner: Nco::new(Frequency::from_rad(0.)),
|
||||
freq_in: input_freq,
|
||||
out: a,
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
55
ntw_dsp/src/blocks/oscillator.rs
Normal file
55
ntw_dsp/src/blocks/oscillator.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
|
||||
use crate::generation::Nco;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct ComplexOscillator<T>
|
||||
{
|
||||
inner: crate::generation::Nco<T>,
|
||||
|
||||
#[output]
|
||||
out: Out<Complex<T>>,
|
||||
}
|
||||
|
||||
impl BlockWork for ComplexOscillator<f32>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.out.push_iter(&mut self.inner);
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.out.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockWork for ComplexOscillator<f64>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.out.push_iter(&mut self.inner);
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.out.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ComplexOscillator<T>
|
||||
{
|
||||
pub fn new(nco: Nco<T>) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (a, b) = Stream::make(1024);
|
||||
(Self { inner: nco, out: a }, b)
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,21 @@
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use ntw_flowgraph::{Block, BlockWork, inout::{In, Out, Stream}};
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Tee<T>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output_1: Out<T>
|
||||
output_1: Out<T>,
|
||||
|
||||
#[output]
|
||||
output_2: Out<T>
|
||||
output_2: Out<T>,
|
||||
}
|
||||
|
||||
impl<T> Tee<T>
|
||||
@ -21,37 +25,41 @@ impl<T> Tee<T>
|
||||
let (output_1, in_1) = Stream::make(1024);
|
||||
let (output_2, in_2) = Stream::make(1024);
|
||||
(
|
||||
Tee
|
||||
{
|
||||
Tee {
|
||||
input,
|
||||
output_1,
|
||||
output_2,
|
||||
},
|
||||
in_1,
|
||||
in_2
|
||||
in_2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> BlockWork for Tee<T>
|
||||
{
|
||||
fn work(&mut self)
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let len = self.output_1.vacant_len().min(self.output_2.vacant_len())
|
||||
let len = self
|
||||
.output_1
|
||||
.vacant_len()
|
||||
.min(self.output_2.vacant_len())
|
||||
.min(self.input.available_len());
|
||||
|
||||
|
||||
for _ in 0..len
|
||||
{
|
||||
let elem = self.input.try_pop().unwrap(); // Should be available because of len check
|
||||
|
||||
self.output_1.try_push(elem.clone());
|
||||
self.output_2.try_push(elem);
|
||||
|
||||
let _ = self.output_1.try_push(elem.clone());
|
||||
let _ = self.output_2.try_push(elem);
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool {
|
||||
self.input.available_len() > 0 &&
|
||||
self.output_1.vacant_len() > 0 &&
|
||||
self.output_2.vacant_len() > 0
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0
|
||||
&& self.output_1.vacant_len() > 0
|
||||
&& self.output_2.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
441
ntw_dsp/src/blocks/utilities.rs
Normal file
441
ntw_dsp/src/blocks/utilities.rs
Normal file
@ -0,0 +1,441 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Div;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::SendError;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
use num::Float;
|
||||
use num::One;
|
||||
use num::complex::ComplexFloat;
|
||||
|
||||
use crate::filtering::fir::Fir;
|
||||
use crate::filtering::impulse_response::ImpulseResponse;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct NullSink<T>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
}
|
||||
|
||||
impl<T> NullSink<T>
|
||||
{
|
||||
pub fn new(input: In<T>) -> Self
|
||||
{
|
||||
Self { input }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BlockWork for NullSink<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.input.pop_iter().for_each(|_| {});
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Repeat<T>
|
||||
{
|
||||
repetition: usize,
|
||||
current: usize,
|
||||
holding: Option<T>,
|
||||
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Repeat<T>
|
||||
{
|
||||
pub fn new(input: In<T>, repetition: usize) -> (Self, In<T>)
|
||||
{
|
||||
let (output, stream) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
repetition,
|
||||
current: 0,
|
||||
holding: None,
|
||||
input,
|
||||
output,
|
||||
},
|
||||
stream,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> BlockWork for Repeat<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
if self.current == 0
|
||||
{
|
||||
self.holding = Some(self.input.try_pop().unwrap());
|
||||
self.current = self.repetition;
|
||||
}
|
||||
|
||||
let pushed = self.output.push_iter(std::iter::repeat_n(
|
||||
self.holding.clone().unwrap(),
|
||||
self.current,
|
||||
));
|
||||
|
||||
self.current -= pushed;
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.output.vacant_len() > 0 && (self.current != 0 || self.input.available_len() > 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct ChannelSink<T>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
output: Sender<T>,
|
||||
}
|
||||
|
||||
impl<T> ChannelSink<T>
|
||||
{
|
||||
pub fn new(input: In<T>) -> (Self, Receiver<T>)
|
||||
{
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
(Self { input, output: tx }, rx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BlockWork for ChannelSink<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
if self
|
||||
.input
|
||||
.pop_iter()
|
||||
.map(|x| match self.output.send(x)
|
||||
{
|
||||
Ok(_) => BlockResult::Ok,
|
||||
Err(SendError(_)) => BlockResult::Finished,
|
||||
})
|
||||
.any(|r| r == BlockResult::Finished)
|
||||
{
|
||||
BlockResult::Finished
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct ChannelSource<T>
|
||||
{
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
input: Option<Receiver<T>>,
|
||||
}
|
||||
|
||||
impl<T> ChannelSource<T>
|
||||
{
|
||||
pub fn new(input: Receiver<T>) -> (Self, In<T>)
|
||||
{
|
||||
let (tx, rx) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
input: Some(input),
|
||||
output: tx,
|
||||
},
|
||||
rx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BlockWork for ChannelSource<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
if let Some(input) = &self.input
|
||||
{
|
||||
let len = self.output.vacant_len();
|
||||
for _ in 0..len
|
||||
{
|
||||
match input.try_recv()
|
||||
{
|
||||
Ok(x) =>
|
||||
{
|
||||
let _ = self.output.try_push(x);
|
||||
}
|
||||
Err(TryRecvError::Empty) => return BlockResult::Ok,
|
||||
Err(TryRecvError::Disconnected) =>
|
||||
{
|
||||
println!("FINISHED");
|
||||
self.input = None;
|
||||
return BlockResult::Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockResult::Finished
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.is_some() && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Chunks<T, const N: usize>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<[T; N]>,
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Chunks<T, N>
|
||||
{
|
||||
pub fn new(input: In<T>) -> (Self, In<[T; N]>)
|
||||
{
|
||||
let (output, chunks) = Stream::make(1024);
|
||||
(Self { input, output }, chunks)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, const N: usize> BlockWork for Chunks<T, N>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let len = (self.input.available_len() / N).min(self.output.vacant_len());
|
||||
|
||||
let mut acc = vec![];
|
||||
for _ in 0..len
|
||||
{
|
||||
for _ in 0..N
|
||||
{
|
||||
acc.push(self.input.try_pop().unwrap());
|
||||
}
|
||||
let _ = self.output.try_push(*acc.as_array().unwrap());
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > N && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Windows<T, const N: usize>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<[T; N]>,
|
||||
|
||||
buffer: VecDeque<T>,
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Windows<T, N>
|
||||
{
|
||||
pub fn new(input: In<T>) -> (Self, In<[T; N]>)
|
||||
{
|
||||
let (output, chunks) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
buffer: VecDeque::with_capacity(N),
|
||||
},
|
||||
chunks,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, const N: usize> BlockWork for Windows<T, N>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let len = (self.input.available_len()).min(self.output.vacant_len());
|
||||
|
||||
for _ in 0..len
|
||||
{
|
||||
let x = self.input.try_pop().unwrap();
|
||||
if self.buffer.len() == N
|
||||
{
|
||||
let (a, b) = self.buffer.as_slices();
|
||||
let _ = self.output.try_push(
|
||||
*a.iter()
|
||||
.chain(b.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<T>>()
|
||||
.as_array()
|
||||
.unwrap(),
|
||||
);
|
||||
self.buffer.pop_front();
|
||||
}
|
||||
self.buffer.push_back(x);
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct SimpleAgc<T: ComplexFloat>
|
||||
where
|
||||
T::Real: Float,
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
level_fir: Fir<T::Real, T::Real, T::Real>,
|
||||
}
|
||||
|
||||
impl<T: ComplexFloat> SimpleAgc<T>
|
||||
where
|
||||
T::Real: Float,
|
||||
{
|
||||
pub fn new(input: In<T>, filter_length: usize) -> (Self, In<T>)
|
||||
{
|
||||
let (output, agc) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
level_fir: Fir::new(
|
||||
ImpulseResponse(vec![T::Real::one(); filter_length]).normalized(),
|
||||
),
|
||||
},
|
||||
agc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ComplexFloat> BlockWork for SimpleAgc<T>
|
||||
where
|
||||
T::Real: Float,
|
||||
T: Div<T::Real, Output = T>,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.pop_iter().map(|x| {
|
||||
let norm = num::traits::real::Real::sqrt(x.re() * x.re() + x.im() * x.im());
|
||||
let level = self.level_fir.next(norm);
|
||||
x / level
|
||||
}));
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && self.output.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct SimpleSquelch<T: Float>
|
||||
{
|
||||
#[input]
|
||||
input: In<Complex<T>>,
|
||||
|
||||
#[output]
|
||||
output: Out<Complex<T>>,
|
||||
|
||||
level_fir: Fir<T, T, T>,
|
||||
minimum_level: T,
|
||||
open: bool,
|
||||
}
|
||||
|
||||
impl<T: Float> SimpleSquelch<T>
|
||||
{
|
||||
pub fn new(
|
||||
input: In<Complex<T>>,
|
||||
filter_length: usize,
|
||||
minimum_level: T,
|
||||
) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (output, agc) = Stream::make(1024);
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
minimum_level,
|
||||
level_fir: Fir::new(ImpulseResponse(vec![T::one(); filter_length]).normalized()),
|
||||
open: false,
|
||||
},
|
||||
agc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> BlockWork for SimpleSquelch<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
for _ in 0..(self.input.available_len())
|
||||
{
|
||||
let x = self.input.try_pop().unwrap();
|
||||
let level = self.level_fir.next(x.norm());
|
||||
self.open = level >= self.minimum_level;
|
||||
|
||||
if self.open && self.output.vacant_len() == 0
|
||||
{
|
||||
return BlockResult::Ok;
|
||||
}
|
||||
else if self.open
|
||||
{
|
||||
let _ = self.output.try_push(x);
|
||||
}
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.input.available_len() > 0 && (!self.open || self.output.vacant_len() > 0)
|
||||
}
|
||||
}
|
||||
2
ntw_dsp/src/filtering.rs
Normal file
2
ntw_dsp/src/filtering.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod fir;
|
||||
pub mod impulse_response;
|
||||
67
ntw_dsp/src/filtering/fir.rs
Normal file
67
ntw_dsp/src/filtering/fir.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Add;
|
||||
use std::ops::Mul;
|
||||
|
||||
use ringbuf::traits::Consumer;
|
||||
|
||||
use crate::filtering::impulse_response::ImpulseResponse;
|
||||
|
||||
pub struct Fir<C, V, U>
|
||||
where
|
||||
V: Mul<C, Output = U>,
|
||||
U: Add<U, Output = U>,
|
||||
{
|
||||
// Elements at index 0 represent the earliest elements
|
||||
taps: ImpulseResponse<C>,
|
||||
queue: VecDeque<V>,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl<C, V, U> Fir<C, V, U>
|
||||
where
|
||||
C: Clone,
|
||||
V: Clone,
|
||||
V: Mul<C, Output = U>,
|
||||
U: Add<U, Output = U>,
|
||||
{
|
||||
pub fn new(taps: ImpulseResponse<C>) -> Self
|
||||
{
|
||||
let len = taps.len();
|
||||
assert!(len > 0);
|
||||
Self {
|
||||
taps,
|
||||
queue: VecDeque::with_capacity(len),
|
||||
length: len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self, x: V) -> U
|
||||
{
|
||||
if self.queue.len() >= self.length
|
||||
{
|
||||
let _ = self.queue.pop_front();
|
||||
}
|
||||
|
||||
self.queue.push_back(x);
|
||||
|
||||
// Convolve
|
||||
self.taps
|
||||
.0
|
||||
.iter()
|
||||
.zip(self.queue.iter())
|
||||
.map(|(tap, sample)| sample.clone() * tap.clone())
|
||||
.reduce(Add::add)
|
||||
.unwrap() // Will not panic, and queue are not empty
|
||||
}
|
||||
|
||||
pub fn clear(&mut self)
|
||||
{
|
||||
self.queue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Completely stolen from sdrpp dsp code
|
||||
pub fn estimate_fir_length(transition_width: f32, sample_rate: f32) -> f32
|
||||
{
|
||||
3.8 * sample_rate / transition_width
|
||||
}
|
||||
154
ntw_dsp/src/filtering/impulse_response.rs
Normal file
154
ntw_dsp/src/filtering/impulse_response.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use std::ops::Add;
|
||||
use std::ops::Div;
|
||||
use std::ops::Mul;
|
||||
|
||||
use num::Complex;
|
||||
use rustfft::FftNum;
|
||||
use rustfft::FftPlanner;
|
||||
|
||||
use crate::filtering::impulse_response::window::Window;
|
||||
use crate::generation::Nco;
|
||||
use crate::generation::NcoType;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImpulseResponse<T>(pub Vec<T>);
|
||||
|
||||
impl<T> ImpulseResponse<T>
|
||||
{
|
||||
pub fn len(&self) -> usize
|
||||
{
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool
|
||||
{
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ImpulseResponse<Complex<T>>
|
||||
where
|
||||
T: NcoType,
|
||||
{
|
||||
pub fn from_nco(nco: &mut Nco<T>, length: usize) -> Self
|
||||
{
|
||||
ImpulseResponse(nco.take(length).collect::<Vec<Complex<T>>>())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ImpulseResponse<Complex<T>>
|
||||
where
|
||||
T: FftNum,
|
||||
{
|
||||
pub fn from_transfer_function(tranfer_function: &[Complex<T>]) -> Self
|
||||
{
|
||||
let mut planner = FftPlanner::new();
|
||||
let ifft = planner.plan_fft_inverse(tranfer_function.len());
|
||||
let mut tf = tranfer_function.to_vec();
|
||||
ifft.process(tf.as_mut_slice());
|
||||
|
||||
let mut ir = vec![];
|
||||
for i in 0..tranfer_function.len()
|
||||
{
|
||||
let k = (tranfer_function.len() - (tranfer_function.len() / 2) + i)
|
||||
% tranfer_function.len();
|
||||
ir.push(tf[k]);
|
||||
}
|
||||
|
||||
Self(ir)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ImpulseResponse<T>
|
||||
where
|
||||
T: Add<T, Output = T> + Div<T, Output = T> + Clone,
|
||||
{
|
||||
pub fn normalize(&mut self)
|
||||
{
|
||||
let sum = self
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.reduce(|a, b| T::add(a.clone(), b.clone()));
|
||||
if sum.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let sum = sum.unwrap().clone();
|
||||
self.0.iter_mut().for_each(|x| *x = x.clone() / sum.clone());
|
||||
}
|
||||
|
||||
pub fn normalized(&self) -> ImpulseResponse<T>
|
||||
{
|
||||
let mut new = self.clone();
|
||||
new.normalize();
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> From<U> for ImpulseResponse<T>
|
||||
where
|
||||
U: AsRef<[T]>,
|
||||
T: Clone,
|
||||
{
|
||||
fn from(value: U) -> Self
|
||||
{
|
||||
ImpulseResponse(Vec::from(value.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ImpulseResponse<T>
|
||||
where
|
||||
T: Mul<f32, Output = T> + Clone,
|
||||
{
|
||||
pub fn windowed(&self, w: Window) -> Self
|
||||
{
|
||||
let mut new = self.clone();
|
||||
let len = new.len();
|
||||
new.0
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, x)| *x = x.clone() * w(i as f32 / len as f32));
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
pub mod window
|
||||
{
|
||||
use std::f32::consts::PI;
|
||||
|
||||
pub type Window = fn(f32) -> f32;
|
||||
|
||||
pub fn rectangular(_: f32) -> f32
|
||||
{
|
||||
1.
|
||||
}
|
||||
|
||||
pub fn bartlett(t: f32) -> f32
|
||||
{
|
||||
if t < 0.5 { 2. * t } else { 2. - 2. * t }
|
||||
}
|
||||
|
||||
pub fn hann(t: f32) -> f32
|
||||
{
|
||||
0.5 - 0.5 * (2. * PI * t).cos()
|
||||
}
|
||||
|
||||
pub fn hamming(t: f32) -> f32
|
||||
{
|
||||
0.54 - 0.46 * (2. * PI * t).cos()
|
||||
}
|
||||
|
||||
pub fn blackmann(t: f32) -> f32
|
||||
{
|
||||
let x = 2. * PI * t;
|
||||
0.45 - 0.5 * x.cos() + 0.08 * (2. * x).cos()
|
||||
}
|
||||
|
||||
pub fn gaussian(sigma: f32, t: f32) -> f32
|
||||
{
|
||||
let sq = (t - 0.5) / sigma;
|
||||
(-sq * sq).exp()
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,32 @@
|
||||
use std::{f64::consts::PI, marker::PhantomData};
|
||||
use std::f64::consts::PI;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use num::Complex;
|
||||
|
||||
use crate::Frequency;
|
||||
|
||||
pub trait NcoType: Sized
|
||||
{
|
||||
fn angle(t: f64) -> Complex<Self>;
|
||||
}
|
||||
|
||||
impl NcoType for f32
|
||||
{
|
||||
fn angle(t: f64) -> Complex<Self>
|
||||
{
|
||||
Complex::new(t.cos() as f32, t.sin() as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl NcoType for f64
|
||||
{
|
||||
fn angle(t: f64) -> Complex<Self>
|
||||
{
|
||||
Complex::new(t.cos(), t.sin())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Nco<T>
|
||||
{
|
||||
// Phase offset : 0 = 0, usize::MAX = 2*pi
|
||||
@ -44,63 +67,34 @@ impl<T> Nco<T>
|
||||
}
|
||||
}
|
||||
|
||||
impl Nco<f32>
|
||||
impl<T> Nco<T>
|
||||
where
|
||||
T: NcoType,
|
||||
{
|
||||
pub fn sample_sin(&self) -> f32
|
||||
pub fn sample_sin(&self) -> T
|
||||
{
|
||||
let t = (self.phase as f32 / usize::MAX as f32) * 2. * std::f32::consts::PI;
|
||||
t.sin()
|
||||
let t = (self.phase as f64 / usize::MAX as f64) * 2. * std::f64::consts::PI;
|
||||
T::angle(t).im
|
||||
}
|
||||
|
||||
pub fn sample_cos(&self) -> f32
|
||||
pub fn sample_cos(&self) -> T
|
||||
{
|
||||
let t = (self.phase as f32 / usize::MAX as f32) * 2. * std::f32::consts::PI;
|
||||
t.cos()
|
||||
let t = (self.phase as f64 / usize::MAX as f64) * 2. * std::f64::consts::PI;
|
||||
T::angle(t).re
|
||||
}
|
||||
|
||||
pub fn sample(&self) -> Complex<f32>
|
||||
pub fn sample(&self) -> Complex<T>
|
||||
{
|
||||
let t = (self.phase as f32 / usize::MAX as f32) * 2. * std::f32::consts::PI;
|
||||
Complex::new(t.cos(), t.sin())
|
||||
let t = (self.phase as f64 / usize::MAX as f64) * 2. * std::f64::consts::PI;
|
||||
T::angle(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl Nco<f64>
|
||||
impl<T> Iterator for Nco<T>
|
||||
where
|
||||
T: NcoType,
|
||||
{
|
||||
pub fn sample_sin(&self) -> f64
|
||||
{
|
||||
let t = (self.phase as f64 / usize::MAX as f64) * 2. * std::f64::consts::PI;
|
||||
t.sin()
|
||||
}
|
||||
|
||||
pub fn sample_cos(&self) -> f64
|
||||
{
|
||||
let t = (self.phase as f64 / usize::MAX as f64) * 2. * std::f64::consts::PI;
|
||||
t.cos()
|
||||
}
|
||||
|
||||
pub fn sample(&self) -> Complex<f64>
|
||||
{
|
||||
let t = (self.phase as f64 / usize::MAX as f64) * 2. * std::f64::consts::PI;
|
||||
Complex::new(t.cos(), t.sin())
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Nco<f32>
|
||||
{
|
||||
type Item = Complex<f32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
let v = self.sample();
|
||||
self.step();
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Nco<f64>
|
||||
{
|
||||
type Item = Complex<f64>;
|
||||
type Item = Complex<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use std::f64::consts::PI;
|
||||
use std::ops::Neg;
|
||||
|
||||
pub mod blocks;
|
||||
pub mod filtering;
|
||||
pub mod generation;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
@ -35,3 +37,13 @@ fn euclid_mod(a: f64, m: f64) -> f64
|
||||
let r = a % m;
|
||||
if r < 0.0 { r + m } else { r }
|
||||
}
|
||||
|
||||
impl Neg for Frequency
|
||||
{
|
||||
type Output = Frequency;
|
||||
|
||||
fn neg(self) -> Self::Output
|
||||
{
|
||||
Frequency(usize::MAX - self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,25 @@
|
||||
use crate::{Block, BlockWork, RunnableBlock};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use crate::Block;
|
||||
use crate::BlockWork;
|
||||
use crate::RunnableBlock;
|
||||
use petgraph::dot::Dot;
|
||||
use petgraph::graph::DiGraph;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! graph {
|
||||
($( $b:ident ),* $(,)?) => {{
|
||||
let mut g = Graph::new();
|
||||
$(
|
||||
g.add_block($b);
|
||||
)*
|
||||
g
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct Graph
|
||||
{
|
||||
blocks: Vec<Box<dyn RunnableBlock>>,
|
||||
blocks: Vec<Box<dyn RunnableBlock + Send>>,
|
||||
}
|
||||
|
||||
impl Graph
|
||||
@ -13,13 +29,13 @@ impl Graph
|
||||
Graph { blocks: vec![] }
|
||||
}
|
||||
|
||||
pub fn add_block(&mut self, block: impl Block + BlockWork + 'static)
|
||||
pub fn add_block(&mut self, block: impl Block + BlockWork + Send + 'static)
|
||||
{
|
||||
block.set_block_index(self.blocks.len());
|
||||
self.blocks.push(Box::new(block));
|
||||
}
|
||||
|
||||
pub fn run(&mut self)
|
||||
pub fn run(mut self) -> JoinHandle<()>
|
||||
{
|
||||
// Compute the topo_order
|
||||
let mut digraph = DiGraph::<(), (), usize>::with_capacity(self.blocks.len(), 1);
|
||||
@ -43,24 +59,26 @@ impl Graph
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Dumbass round robin
|
||||
loop
|
||||
{
|
||||
let mut one_ready = false;
|
||||
for x in topo_order.iter()
|
||||
std::thread::spawn(move || {
|
||||
loop
|
||||
{
|
||||
let block = &mut self.blocks[*x];
|
||||
if block.ready()
|
||||
let mut one_ready = false;
|
||||
for x in topo_order.iter()
|
||||
{
|
||||
block.work();
|
||||
one_ready = true;
|
||||
let block = &mut self.blocks[*x];
|
||||
if block.ready()
|
||||
{
|
||||
block.work();
|
||||
one_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !one_ready
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !one_ready
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn print_deps(&self)
|
||||
@ -75,6 +93,25 @@ impl Graph
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dot(&self) -> String
|
||||
{
|
||||
let mut digraph = DiGraph::<String, (), usize>::with_capacity(self.blocks.len(), 1);
|
||||
let len = self.blocks.len();
|
||||
(0..len).for_each(|i| {
|
||||
digraph.add_node(self.blocks[i].get_block_name().to_owned());
|
||||
});
|
||||
|
||||
for (i, block) in self.blocks.iter().enumerate()
|
||||
{
|
||||
for next in block.get_successors()
|
||||
{
|
||||
digraph.add_edge(i.into(), next.into(), ());
|
||||
}
|
||||
}
|
||||
|
||||
format!("{:?}", Dot::with_config(&digraph, &[]))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Graph
|
||||
|
||||
@ -7,21 +7,20 @@ use ringbuf::traits::Observer;
|
||||
use ringbuf::traits::Producer;
|
||||
use ringbuf::traits::Split;
|
||||
use ringbuf::wrap::caching::Caching;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
// Represent a block input, of which data is popped
|
||||
pub struct In<T>
|
||||
{
|
||||
block: Rc<Cell<Option<usize>>>,
|
||||
block: Arc<Mutex<Option<usize>>>,
|
||||
pub rb: Caching<Arc<SharedRb<Heap<T>>>, false, true>,
|
||||
}
|
||||
|
||||
// Represent a block output, in which data is pushed
|
||||
pub struct Out<T>
|
||||
{
|
||||
to: Rc<Cell<Option<usize>>>,
|
||||
to: Arc<Mutex<Option<usize>>>,
|
||||
pub rb: Caching<Arc<SharedRb<Heap<T>>>, true, false>,
|
||||
}
|
||||
|
||||
@ -32,7 +31,7 @@ impl Stream
|
||||
pub fn make<T>(length: usize) -> (Out<T>, In<T>)
|
||||
{
|
||||
let (prod, cons) = HeapRb::<T>::new(length).split();
|
||||
let to = Rc::new(Cell::new(None));
|
||||
let to = Arc::new(Mutex::new(None));
|
||||
(
|
||||
Out {
|
||||
to: to.clone(),
|
||||
@ -50,7 +49,8 @@ impl<T> In<T>
|
||||
{
|
||||
pub fn set_index(&self, index: usize)
|
||||
{
|
||||
self.block.set(Some(index))
|
||||
let mut guard = self.block.lock().unwrap();
|
||||
*guard = Some(index);
|
||||
}
|
||||
|
||||
pub fn try_pop(&mut self) -> Option<T>
|
||||
@ -73,7 +73,7 @@ impl<T> Out<T>
|
||||
{
|
||||
pub fn get_successor(&self) -> Option<usize>
|
||||
{
|
||||
self.to.get()
|
||||
*self.to.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn try_push(&mut self, data: T) -> Result<(), T>
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
pub mod graph;
|
||||
pub mod inout;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum BlockResult
|
||||
{
|
||||
Ok,
|
||||
Finished,
|
||||
}
|
||||
|
||||
pub trait BlockWork
|
||||
{
|
||||
fn work(&mut self);
|
||||
fn work(&mut self) -> BlockResult;
|
||||
fn ready(&mut self) -> bool;
|
||||
}
|
||||
|
||||
@ -11,6 +18,9 @@ pub trait Block
|
||||
{
|
||||
fn set_block_index(&self, index: usize);
|
||||
fn get_successors(&self) -> Vec<usize>;
|
||||
|
||||
// Meta functions
|
||||
fn get_block_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
pub trait RunnableBlock: Block + BlockWork {}
|
||||
|
||||
@ -6,10 +6,22 @@ use syn::parse_macro_input;
|
||||
|
||||
struct BlockDerive
|
||||
{
|
||||
block_name: String,
|
||||
input_fields: Vec<Ident>,
|
||||
output_fields: Vec<Ident>,
|
||||
}
|
||||
|
||||
fn get_block_name_func(ctx: &BlockDerive) -> proc_macro2::TokenStream
|
||||
{
|
||||
let block_name = ctx.block_name.as_str();
|
||||
quote! {
|
||||
fn get_block_name(&self) -> &'static str
|
||||
{
|
||||
#block_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_block_index_func(ctx: &BlockDerive) -> proc_macro2::TokenStream
|
||||
{
|
||||
let inputs = ctx.input_fields.clone();
|
||||
@ -106,23 +118,27 @@ pub fn block_derive(item: TokenStream) -> TokenStream
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let derive = BlockDerive {
|
||||
block_name: input.ident.to_string(),
|
||||
input_fields,
|
||||
output_fields,
|
||||
};
|
||||
|
||||
let set_index_func = set_block_index_func(&derive);
|
||||
let get_successors_func = get_successors_func(&derive);
|
||||
let get_block_name_func = get_block_name_func(&derive);
|
||||
let struct_path = input.ident;
|
||||
let struct_where = input.generics.where_clause.clone();
|
||||
let struct_generics = input.generics;
|
||||
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
//item
|
||||
quote! {
|
||||
impl #struct_generics ntw_flowgraph::Block for #struct_path #struct_generics
|
||||
#struct_where
|
||||
impl #impl_generics ntw_flowgraph::Block for #struct_path #type_generics
|
||||
#where_clause
|
||||
{
|
||||
#set_index_func
|
||||
#get_successors_func
|
||||
|
||||
// Meta information
|
||||
#get_block_name_func
|
||||
}
|
||||
}
|
||||
.into()
|
||||
|
||||
388
src/main.rs
388
src/main.rs
@ -1,102 +1,59 @@
|
||||
use std::any;
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::btree_map::RangeMut;
|
||||
use std::f64::consts::PI;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::default_host;
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui::IntoAtoms;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::Plot;
|
||||
use egui_plot::PlotPoint;
|
||||
use egui_plot::PlotPoints;
|
||||
use ntw_dsp::Frequency;
|
||||
use ntw_dsp::blocks::early_late_gate::EarlyLateGate;
|
||||
use ntw_dsp::blocks::early_late_gate::EyeExtractor;
|
||||
use ntw_dsp::blocks::fft::Fft;
|
||||
use ntw_dsp::blocks::fir::Fir;
|
||||
use ntw_dsp::blocks::fir::FirFft;
|
||||
use ntw_dsp::blocks::iir::SimpleDcBlock;
|
||||
use ntw_dsp::blocks::iq::IqSampler;
|
||||
use ntw_dsp::blocks::iter::FiniteIterSource;
|
||||
use ntw_dsp::blocks::map::Map;
|
||||
use ntw_dsp::blocks::math::Multiplier;
|
||||
use ntw_dsp::blocks::nco::ComplexNco;
|
||||
use ntw_dsp::blocks::oscillator::ComplexOscillator;
|
||||
use ntw_dsp::blocks::tee::Tee;
|
||||
use ntw_dsp::blocks::utilities::ChannelSink;
|
||||
use ntw_dsp::blocks::utilities::ChannelSource;
|
||||
use ntw_dsp::blocks::utilities::Repeat;
|
||||
use ntw_dsp::blocks::utilities::SimpleAgc;
|
||||
use ntw_dsp::blocks::utilities::SimpleSquelch;
|
||||
use ntw_dsp::blocks::utilities::Windows;
|
||||
use ntw_dsp::filtering::fir::estimate_fir_length;
|
||||
use ntw_dsp::filtering::impulse_response::ImpulseResponse;
|
||||
use ntw_dsp::filtering::impulse_response::window;
|
||||
use ntw_dsp::generation::Nco;
|
||||
use ntw_flowgraph::Block;
|
||||
use ntw_flowgraph::BlockResult;
|
||||
use ntw_flowgraph::BlockWork;
|
||||
use ntw_flowgraph::graph;
|
||||
use ntw_flowgraph::graph::Graph;
|
||||
use ntw_flowgraph::inout::In;
|
||||
use ntw_flowgraph::inout::Out;
|
||||
use ntw_flowgraph::inout::Stream;
|
||||
use ntw_flowgraph_macros::Block;
|
||||
use num::Complex;
|
||||
use num::One;
|
||||
use num::Zero;
|
||||
use num::complex::Complex32;
|
||||
use rand::random;
|
||||
use ringbuf::traits::Consumer;
|
||||
use ringbuf::traits::Observer;
|
||||
use ringbuf::traits::Producer;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct VecSource
|
||||
{
|
||||
vector: Vec<u32>,
|
||||
|
||||
#[output]
|
||||
out: Out<u32>,
|
||||
}
|
||||
|
||||
impl BlockWork for VecSource
|
||||
{
|
||||
fn work(&mut self)
|
||||
{
|
||||
while let Some(element) = self.vector.pop()
|
||||
{
|
||||
match self.out.rb.try_push(element)
|
||||
{
|
||||
Ok(()) =>
|
||||
{}
|
||||
Err(x) =>
|
||||
{
|
||||
self.vector.push(x);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool
|
||||
{
|
||||
!self.vector.is_empty() && self.out.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl VecSource
|
||||
{
|
||||
pub fn new(vector: Vec<u32>) -> (VecSource, In<u32>)
|
||||
{
|
||||
let (out, stream) = Stream::make(16);
|
||||
(VecSource { vector, out }, stream)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct Adder
|
||||
{
|
||||
#[input]
|
||||
in_a: In<u32>,
|
||||
|
||||
#[input]
|
||||
in_b: In<u32>,
|
||||
|
||||
#[output]
|
||||
out: Out<u32>,
|
||||
}
|
||||
|
||||
impl BlockWork for Adder
|
||||
{
|
||||
fn work(&mut self)
|
||||
{
|
||||
while let Some(a) = self.in_a.rb.try_pop()
|
||||
&& let Some(b) = self.in_b.rb.try_pop()
|
||||
&& self.out.rb.vacant_len() > 0
|
||||
{
|
||||
let _ = self.out.try_push(a + b);
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool
|
||||
{
|
||||
self.in_a.available_len() > 0 && self.in_b.available_len() > 0 && self.out.vacant_len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Adder
|
||||
{
|
||||
pub fn new(in_a: In<u32>, in_b: In<u32>) -> (Adder, In<u32>)
|
||||
{
|
||||
let (out, stream) = Stream::make(16);
|
||||
(Adder { in_a, in_b, out }, stream)
|
||||
}
|
||||
}
|
||||
use rustfft::FftDirection;
|
||||
|
||||
#[derive(Block)]
|
||||
pub struct PrintSink<T>
|
||||
@ -107,15 +64,16 @@ pub struct PrintSink<T>
|
||||
|
||||
impl<T: Display> BlockWork for PrintSink<T>
|
||||
{
|
||||
fn work(&mut self)
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
if let Some(x) = self.stream.rb.try_pop()
|
||||
{
|
||||
println!("{x}");
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
fn ready(&self) -> bool
|
||||
fn ready(&mut self) -> bool
|
||||
{
|
||||
self.stream.available_len() > 0
|
||||
}
|
||||
@ -129,17 +87,245 @@ impl<T> PrintSink<T>
|
||||
}
|
||||
}
|
||||
|
||||
const SAMPLE_RATE: usize = 48_000;
|
||||
const FREQUENCY_OFFSET: usize = 1000;
|
||||
const CARRIER_FREQUENCY: usize = 1700;
|
||||
const SAMPLES_PER_SYMBOL: usize = 40;
|
||||
|
||||
fn main()
|
||||
{
|
||||
let (nco, out) = ComplexNco::<f32>::new(Nco::new(Frequency::from_rad(0.001)));
|
||||
let (map, im) = Map::new(out, |x| x.im);
|
||||
let printer = PrintSink::new(im);
|
||||
|
||||
let mut graph = Graph::new();
|
||||
|
||||
graph.add_block(printer);
|
||||
graph.add_block(map);
|
||||
graph.add_block(nco);
|
||||
|
||||
graph.run();
|
||||
modulate();
|
||||
demodulate();
|
||||
}
|
||||
|
||||
fn demodulate()
|
||||
{
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.expect("no output device available");
|
||||
println!("input device: {}", device.description().unwrap());
|
||||
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);
|
||||
|
||||
// Synthetise baseband filter
|
||||
let baseband_fir_len = estimate_fir_length(500., SAMPLE_RATE as f32) as usize;
|
||||
let cutoff_bin =
|
||||
((Frequency::from_frequency(FREQUENCY_OFFSET as f64 + 100., SAMPLE_RATE as f64).as_rad()
|
||||
/ (2. * PI))
|
||||
* baseband_fir_len as f64) as usize;
|
||||
let mut transfer_function = vec![Complex32::zero(); baseband_fir_len];
|
||||
for i in 0..cutoff_bin
|
||||
{
|
||||
transfer_function[i] = Complex32::one();
|
||||
transfer_function[baseband_fir_len - 1 - i] = Complex32::one();
|
||||
}
|
||||
let baseband_fir = ImpulseResponse::from_transfer_function(transfer_function.as_slice())
|
||||
.normalized()
|
||||
.windowed(window::blackmann);
|
||||
|
||||
let mut reader = hound::WavReader::open("mod.wav").unwrap();
|
||||
let (audio_tx, audio_rx) = std::sync::mpsc::sync_channel(100_000);
|
||||
let (eye_tx, eye_rx) = std::sync::mpsc::channel();
|
||||
|
||||
let (input_block, input) = ChannelSource::new(audio_rx);
|
||||
let (iq_sampler, iq) = IqSampler::<f32>::new(
|
||||
input,
|
||||
Frequency::from_frequency(CARRIER_FREQUENCY as f64, SAMPLE_RATE as f64),
|
||||
);
|
||||
let (fir_block, iq) = Fir::new(iq, baseband_fir);
|
||||
let (sq_block, iq) = SimpleSquelch::new(iq, 300, 0.005);
|
||||
//let (agc_block, iq) = SimpleAgc::new(iq, 200);
|
||||
let (window_block, windows) = Windows::<_, 2>::new(iq);
|
||||
let (diff_block, angle) = Map::new(windows, |[a, b]| (a / b).arg() * 15.);
|
||||
//let (dc_block, angle) = SimpleDcBlock::new(angle, 0.99.into());
|
||||
let (low_pass_block, angle) = Fir::new(
|
||||
angle,
|
||||
ImpulseResponse(vec![1.; SAMPLES_PER_SYMBOL / 2]).normalized(),
|
||||
);
|
||||
|
||||
let mut pi = vec![1. / 30.; 50];
|
||||
*pi.last_mut().unwrap() = 1.;
|
||||
let (elg_block, samples) = EarlyLateGate::new(angle, ImpulseResponse(pi), SAMPLES_PER_SYMBOL);
|
||||
let eye_block = EyeExtractor::new(samples, eye_tx, SAMPLES_PER_SYMBOL);
|
||||
|
||||
let graph = graph![
|
||||
input_block,
|
||||
iq_sampler,
|
||||
window_block,
|
||||
diff_block,
|
||||
elg_block,
|
||||
low_pass_block,
|
||||
eye_block,
|
||||
fir_block,
|
||||
sq_block,
|
||||
//dc_block //fir_block
|
||||
];
|
||||
graph.run();
|
||||
|
||||
// let samples = reader.samples().map(|x| x.unwrap()).collect::<Vec<i16>>();
|
||||
// std::thread::spawn(move || {
|
||||
// loop
|
||||
// {
|
||||
// samples
|
||||
// .iter()
|
||||
// //.skip(rand::random_range(0..100))
|
||||
// .map(|x| *x as f32 / i16::MAX as f32)
|
||||
// .for_each(|x| audio_tx.send(x).unwrap());
|
||||
// }
|
||||
// });
|
||||
|
||||
let stream = device.build_input_stream(
|
||||
&supported_config.into(),
|
||||
move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
||||
data.iter().for_each(|x| {
|
||||
let _ = audio_tx.send(*x / 2.);
|
||||
});
|
||||
},
|
||||
move |err| {
|
||||
// react to errors here.
|
||||
},
|
||||
None, // None=blocking, Some(Duration)=timeout
|
||||
);
|
||||
|
||||
let mut eyes = VecDeque::new();
|
||||
println!("Running ui");
|
||||
eframe::run_simple_native("Demod", NativeOptions::default(), move |ctx, _frame| {
|
||||
while let Ok(x) = eye_rx.try_recv()
|
||||
{
|
||||
eyes.push_back(x);
|
||||
}
|
||||
|
||||
while eyes.len() > 300
|
||||
{
|
||||
eyes.pop_front();
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("Demod").show(ui, |plot_ui| {
|
||||
for eye in eyes.iter()
|
||||
{
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"Angle",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::GREEN),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
ctx.request_repaint();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn modulate()
|
||||
{
|
||||
// Simple FSK modulation
|
||||
let data = (0u8..=255u8)
|
||||
.map(|_| rand::random::<u8>())
|
||||
.flat_map(to_bits); // Data to modulate
|
||||
let frequency_offset = Frequency::from_frequency(FREQUENCY_OFFSET as f64, SAMPLE_RATE as f64); // Bandwidth
|
||||
|
||||
let (bit_stream, bits) = FiniteIterSource::new(data, 255 * 8); // Iterator on data to modulate
|
||||
let (map, freq) = Map::new(bits, move |bit| [1., -1.][bit as usize]); // Computes frequency offset based on bit
|
||||
let (hold, freq) = Repeat::new(freq, SAMPLES_PER_SYMBOL); // Holds that frequency for 100 samples/symbol
|
||||
let (freq_fir, freq) = Fir::new(
|
||||
freq,
|
||||
ImpulseResponse(
|
||||
(0..SAMPLES_PER_SYMBOL)
|
||||
.map(|t| gaussian(0.3, t as f32 / SAMPLES_PER_SYMBOL as f32))
|
||||
.collect(),
|
||||
)
|
||||
.normalized(),
|
||||
);
|
||||
let (bit_to_freq, freq) = Map::new(freq, |x| {
|
||||
Frequency::from_frequency(FREQUENCY_OFFSET as f64 * x as f64, SAMPLE_RATE as f64)
|
||||
});
|
||||
let (baseband_nco, baseband) = ComplexNco::<f32>::new(freq); // Baseband oscillatore
|
||||
let (local_o, lo) = ComplexOscillator::<f32>::new(Nco::new(Frequency::from_frequency(
|
||||
CARRIER_FREQUENCY as f64,
|
||||
SAMPLE_RATE as f64,
|
||||
))); // Upconverter
|
||||
|
||||
let (prod_block, passband) = Multiplier::<_, _, Complex<f32>>::new(baseband, lo); // Multiply
|
||||
let (output, rx) = ChannelSink::new(passband); // Output in channel
|
||||
let graph = graph![
|
||||
bit_stream,
|
||||
map,
|
||||
output,
|
||||
baseband_nco,
|
||||
local_o,
|
||||
hold,
|
||||
prod_block,
|
||||
freq_fir,
|
||||
bit_to_freq
|
||||
];
|
||||
graph.run();
|
||||
|
||||
let mut output = vec![];
|
||||
while let Ok(x) = rx.recv()
|
||||
{
|
||||
output.push(x);
|
||||
}
|
||||
|
||||
println!("length: {}", output.len());
|
||||
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: 48000,
|
||||
bits_per_sample: 16,
|
||||
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
let mut writer = hound::WavWriter::create("mod.wav", spec).unwrap();
|
||||
for t in output
|
||||
{
|
||||
let amplitude = i16::MAX as f32;
|
||||
writer
|
||||
.write_sample(((t.re + rand::random::<f32>() * 0.0) * amplitude) as i16)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user