Working fsk transmitter
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
pub mod filtering;
|
||||
pub mod iq;
|
||||
pub mod math;
|
||||
pub mod synthesis;
|
||||
pub mod ted;
|
||||
|
||||
1
oxydsp-dsp/src/blocks/iq.rs
Normal file
1
oxydsp-dsp/src/blocks/iq.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod zero_if;
|
||||
65
oxydsp-dsp/src/blocks/iq/zero_if.rs
Normal file
65
oxydsp-dsp/src/blocks/iq/zero_if.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use num::{Complex, Float};
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::SyncBlock,
|
||||
io::{In, Out},
|
||||
sync_block,
|
||||
};
|
||||
use rustfft::FftNum;
|
||||
|
||||
use crate::{
|
||||
filtering::fir::{Fir, FirFilter},
|
||||
synthesis::oscillator::Nco,
|
||||
};
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block]
|
||||
pub struct ZeroIf<T: std::clone::Clone + num::Num + Float + From<f32> + 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<Complex<T>>,
|
||||
|
||||
local_oscillator: Nco<T>,
|
||||
filter: FirFilter<Complex<T>, Complex<T>, Complex<T>>,
|
||||
}
|
||||
|
||||
impl<T> ZeroIf<T>
|
||||
where
|
||||
T: std::clone::Clone + num::Num + FftNum + From<f32> + 'static + num::Float,
|
||||
{
|
||||
pub fn new(input: In<T>, lo: Nco<T>) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (output, port) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
local_oscillator: lo,
|
||||
filter: FirFilter::new(Fir::lowpass(lo.frequency(), 100)),
|
||||
},
|
||||
port,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_fir(&mut self, fir: Fir<Complex<T>>)
|
||||
{
|
||||
self.filter = FirFilter::new(fir);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'view, T> SyncBlock<'view> for ZeroIf<T>
|
||||
where
|
||||
T: std::clone::Clone + num::Num + Float + From<f32> + 'static + num::Float,
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
// Mix
|
||||
let lo_sample = state.local_oscillator.next().unwrap();
|
||||
let iq = Complex::new(input * lo_sample.re, input * lo_sample.im);
|
||||
|
||||
Some(state.filter.next(iq))
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ use std::collections::VecDeque;
|
||||
use std::iter::Sum;
|
||||
|
||||
use num::Float;
|
||||
use num::NumCast;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::SyncBlock;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
@ -14,7 +15,7 @@ use crate::filtering::fir::FirFilter;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block(tagged)]
|
||||
pub struct EarlyLateGate<T: Float + Sum + Clone + 'static>
|
||||
pub struct EarlyLateGate<T: Float + Send + Sync + Sum + Clone + NumCast + 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
@ -34,13 +35,13 @@ pub struct EarlyLateGate<T: Float + Sum + Clone + 'static>
|
||||
|
||||
// The next window location, in relation to the last sample such that the window is centered on
|
||||
// a symbol center (hopefully)
|
||||
next_sample: usize,
|
||||
next_sample: f32,
|
||||
loop_filter: FirFilter<T, T, T>,
|
||||
}
|
||||
|
||||
impl<T> EarlyLateGate<T>
|
||||
where
|
||||
T: Float + Sum + Clone + 'static,
|
||||
T: Float + Sum + Clone + 'static + Send + Sync + NumCast,
|
||||
{
|
||||
pub fn new(input: In<T>, loop_filter: Fir<T>, symbol_length: usize) -> (Self, In<T>)
|
||||
{
|
||||
@ -53,7 +54,7 @@ where
|
||||
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
|
||||
next_sample: symbol_length as f32, // We assume that the first symbol is 1.5 windows into
|
||||
// the stream
|
||||
loop_filter: FirFilter::new(loop_filter),
|
||||
},
|
||||
@ -64,13 +65,14 @@ where
|
||||
|
||||
impl<'view, T> SyncBlock<'view> for EarlyLateGate<T>
|
||||
where
|
||||
T: Float + Sum + Clone + 'static,
|
||||
T: Float + Sum + Clone + 'static + Send + Sync + NumCast,
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
if state.window.len() < *state.symbol_length
|
||||
{
|
||||
state.window.push_back(input.0);
|
||||
*state.window_location += 1;
|
||||
return Some(input.0.into());
|
||||
}
|
||||
|
||||
@ -81,12 +83,8 @@ where
|
||||
|
||||
let sample = state.window[*state.window_center];
|
||||
let mut tag = None;
|
||||
if *state.window_location >= *state.next_sample
|
||||
if *state.window_location >= *state.next_sample as usize
|
||||
{
|
||||
let new_tag = Tag::default();
|
||||
new_tag.tag("elg_symbol", ());
|
||||
tag = Some(new_tag);
|
||||
|
||||
let early_index = *state.window_center - (0.25 * *state.symbol_length as f32) as usize;
|
||||
let late_index = *state.window_center + (0.25 * *state.symbol_length as f32) as usize;
|
||||
|
||||
@ -97,13 +95,16 @@ where
|
||||
let correction = state.loop_filter.next(error);
|
||||
|
||||
// Figure out next sample location
|
||||
*state.next_sample += (*state.symbol_length as isize
|
||||
+ correction.floor().to_isize().unwrap_or(0))
|
||||
.max(0) as usize;
|
||||
*state.next_sample +=
|
||||
(*state.symbol_length as f32 + correction.to_f32().unwrap()).max(0.);
|
||||
|
||||
// Turn everything back relative to current sample
|
||||
*state.next_sample -= *state.window_location;
|
||||
*state.next_sample -= *state.window_location as f32;
|
||||
*state.window_location = 0;
|
||||
|
||||
let new_tag = Tag::default();
|
||||
new_tag.tag("elg_symbol", error);
|
||||
tag = Some(new_tag);
|
||||
}
|
||||
|
||||
Some((sample, tag).into())
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod adapters;
|
||||
pub mod channels;
|
||||
pub mod iter;
|
||||
pub mod squelch;
|
||||
|
||||
@ -204,6 +204,55 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block(tagged)]
|
||||
pub struct ScanTagged<I: 'static, O: 'static, S, F>
|
||||
where
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O>,
|
||||
{
|
||||
#[input]
|
||||
input: In<I>,
|
||||
|
||||
#[output]
|
||||
output: Out<O>,
|
||||
|
||||
state: S,
|
||||
|
||||
map: F,
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, S, F> ScanTagged<I, O, S, F>
|
||||
where
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O>,
|
||||
{
|
||||
pub fn new(input: In<I>, initial_state: S, map: F) -> (Self, In<O>)
|
||||
{
|
||||
let (output, mapped) = stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
state: initial_state,
|
||||
map,
|
||||
},
|
||||
mapped,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'view, I, O, S, F> SyncBlock<'view> for ScanTagged<I, O, S, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
S: 'view,
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O> + 'view,
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
Some((*state.map)(state.state, input))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Repeat<T: 'static>
|
||||
{
|
||||
@ -248,22 +297,24 @@ impl<T: Clone + 'static> Block for Repeat<T>
|
||||
{
|
||||
if self.remaining == 0 || self.current.is_none()
|
||||
{
|
||||
self.current = Some(reader.pop().unwrap().into());
|
||||
self.remaining = self.repetitions;
|
||||
if let Some(x) = reader.pop()
|
||||
{
|
||||
self.current = Some(x.into());
|
||||
self.remaining = self.repetitions;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlockResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
writer
|
||||
.push(self.current.clone().unwrap().into())
|
||||
.unwrap_or_else(|_| panic!());
|
||||
|
||||
match &mut self.current
|
||||
if let Some((_, tag)) = &mut self.current
|
||||
{
|
||||
Some((_, tag)) =>
|
||||
{
|
||||
*tag = None;
|
||||
}
|
||||
None =>
|
||||
{}
|
||||
*tag = None;
|
||||
}
|
||||
|
||||
self.remaining -= 1;
|
||||
|
||||
@ -51,7 +51,7 @@ impl<I: 'static> Block for RxSource<Receiver<I>, I>
|
||||
{
|
||||
if self
|
||||
.output
|
||||
.push_iter(self.input.iter().map(|x| (x, None).into()))
|
||||
.push_iter(self.input.try_iter().map(|x| (x, None).into()))
|
||||
{
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
79
oxydsp-dsp/src/blocks/utilities/squelch.rs
Normal file
79
oxydsp-dsp/src/blocks/utilities/squelch.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use std::{collections::VecDeque, iter::Sum};
|
||||
|
||||
use num::{Float, FromPrimitive, One, Zero, complex::ComplexFloat};
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::{Block, BlockResult},
|
||||
io::{In, Out, PopIterable},
|
||||
};
|
||||
|
||||
use crate::filtering::fir::{Fir, FirFilter};
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Squelch<T>
|
||||
where
|
||||
T: ComplexFloat + 'static,
|
||||
T::Real: Float + One + Zero + FromPrimitive + Sum + Clone,
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
trigger_level: T::Real,
|
||||
|
||||
history: VecDeque<T::Real>,
|
||||
sum: T::Real,
|
||||
divider: T::Real,
|
||||
}
|
||||
|
||||
impl<T> Squelch<T>
|
||||
where
|
||||
T: ComplexFloat + 'static,
|
||||
T::Real: Float + Sum + Clone + FromPrimitive,
|
||||
{
|
||||
pub fn new(input: In<T>, trigger_level: T::Real, mean_length: usize) -> (Self, In<T>)
|
||||
{
|
||||
let (output, port) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
trigger_level,
|
||||
history: VecDeque::from(vec![T::Real::zero(); mean_length]),
|
||||
sum: T::Real::zero(),
|
||||
divider: T::Real::from_usize(mean_length).unwrap(),
|
||||
},
|
||||
port,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Block for Squelch<T>
|
||||
where
|
||||
T: ComplexFloat + 'static,
|
||||
T::Real: Float + Sum + Clone + FromPrimitive,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let writer = self.output.write();
|
||||
for x in self.input.pop_iter().take(writer.len())
|
||||
{
|
||||
let (element, tag) = x.into();
|
||||
|
||||
let oldest = self.history.pop_front().unwrap();
|
||||
let newest = element.abs();
|
||||
self.history.push_back(newest);
|
||||
|
||||
self.sum = self.sum - oldest;
|
||||
self.sum = self.sum + newest;
|
||||
|
||||
if (self.sum / self.divider) > self.trigger_level
|
||||
{
|
||||
let _ = writer.push((element, tag).into());
|
||||
}
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,11 @@ impl<T> Nco<T>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frequency(&self) -> DigitalFrequency
|
||||
{
|
||||
DigitalFrequency(self.d_phase)
|
||||
}
|
||||
|
||||
pub fn with_phase(frequency: DigitalFrequency, phase: Phase) -> Self
|
||||
{
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user