use log::{debug, trace, warn};
use crate::block::{Block, BlockRet};
use crate::stream::{NCReadStream, NCWriteStream, Tag, TagValue};
use crate::{Complex, Float, Result};
#[derive(rustradio_macros::Block)]
#[rustradio(crate, new)]
pub struct Midpointer {
#[rustradio(in)]
src: NCReadStream<Vec<Float>>,
#[rustradio(out)]
dst: NCWriteStream<Vec<Float>>,
}
impl Block for Midpointer {
fn work(&mut self) -> Result<BlockRet<'_>> {
if self.dst.remaining() == 0 {
return Ok(BlockRet::WaitForStream(&self.dst, 1));
}
let v = match self.src.pop() {
None => return Ok(BlockRet::WaitForStream(&self.src, 1)),
Some((x, _tags)) => x,
};
let mean: Float = v.iter().sum::<Float>() / v.len() as Float;
if mean.is_nan() {
warn!("Midpointer got NaN");
} else {
let (mut a, mut b): (Vec<Float>, Vec<Float>) = v.iter().partition(|&t| *t > mean);
if a.is_empty() || b.is_empty() {
warn!("Midpointer got a burst without samples on both sides of mean");
return Ok(BlockRet::Again);
}
a.sort_by(|a, b| a.total_cmp(b));
b.sort_by(|a, b| a.total_cmp(b));
let high = a[a.len() / 2];
let low = b[b.len() / 2];
let offset = low + (high - low) / 2.0;
self.dst
.push(v.iter().map(|t| t - offset).collect::<Vec<_>>(), &[]);
}
Ok(BlockRet::Again)
}
}
pub struct WpcrBuilder {
wpcr: Wpcr,
out: NCReadStream<Vec<Float>>,
}
impl WpcrBuilder {
#[must_use]
pub fn samp_rate(mut self, s: Float) -> WpcrBuilder {
self.wpcr.set_samp_rate(Some(s));
self
}
#[must_use]
pub fn build(self) -> (Wpcr, NCReadStream<Vec<Float>>) {
(self.wpcr, self.out)
}
}
#[derive(rustradio_macros::Block)]
#[rustradio(crate, new)]
pub struct Wpcr {
#[rustradio(in)]
src: NCReadStream<Vec<Float>>,
#[rustradio(out)]
dst: NCWriteStream<Vec<Float>>,
#[rustradio(default)]
samp_rate: Option<Float>,
}
impl Wpcr {
#[must_use]
pub fn builder(src: NCReadStream<Vec<Float>>) -> WpcrBuilder {
let (wpcr, out) = Wpcr::new(src);
WpcrBuilder { wpcr, out }
}
pub fn set_samp_rate(&mut self, s: Option<Float>) {
self.samp_rate = s;
}
fn process_one(&self, samples: &[Float]) -> Option<(Vec<Float>, Vec<Tag>)> {
if samples.len() < 4 {
return None;
}
let mid = 0.0;
let sliced = samples.iter().map(|v| if *v > mid { 1.0 } else { 0.0 });
let sliced_delayed = sliced.clone().skip(1);
let mut d = sliced
.zip(sliced_delayed)
.map(|(a, b)| {
let x = a - b;
Complex::new(x * x, 0.0)
})
.collect::<Vec<_>>();
let mut planner = rustfft::FftPlanner::new();
let fft = planner.plan_fft_forward(d.len());
fft.process(&mut d);
d.truncate(d.len() / 2);
let Some(bin) = find_best_bin(&d) else {
trace!("No best bin found, giving up on burst");
return None;
};
let samples_per_symbol = bin as Float / samples.len() as Float;
let mut clock_phase = {
let t = 0.5 + d[bin].arg() / (std::f64::consts::PI * 2.0) as Float;
if t > 0.5 { t } else { t + 1.0 }
};
debug!("WPCR: sps: {samples_per_symbol}");
if let Some(samp_rate) = self.samp_rate {
let frequency = samples_per_symbol * samp_rate;
debug!("WPCR: Frequency: {frequency} Hz");
}
debug!("WPCR: Phase: {} rad", d[bin].arg());
let mut syms =
Vec::with_capacity((samples.len() as Float / samples_per_symbol) as usize + 10);
for s in samples {
if clock_phase >= 1.0 {
clock_phase -= 1.0;
syms.push(*s);
}
clock_phase += samples_per_symbol;
}
let mut tags = vec![
Tag::new(0, "sps", TagValue::Float(samples_per_symbol)),
Tag::new(0, "phase", TagValue::Float(clock_phase)),
];
if let Some(samp_rate) = self.samp_rate {
let frequency = samples_per_symbol * samp_rate;
tags.push(Tag::new(0, "frequency", TagValue::Float(frequency)));
}
debug!("WPCR: Bits: {}", syms.len());
Some((syms, tags))
}
}
impl Block for Wpcr {
fn work(&mut self) -> Result<BlockRet<'_>> {
if self.dst.remaining() == 0 {
return Ok(BlockRet::WaitForStream(&self.dst, 1));
}
let x = match self.src.pop() {
None => return Ok(BlockRet::WaitForStream(&self.src, 1)),
Some((x, _tags)) => x,
};
if let Some((packet, tags)) = self.process_one(&x) {
self.dst.push(packet, tags);
}
Ok(BlockRet::Again)
}
}
fn find_best_bin(data: &[Complex]) -> Option<usize> {
let skip = 2;
let mag = data.iter().map(|x| x.norm_sqr().sqrt()).collect::<Vec<_>>();
let thresh = mag
.iter()
.take(data.len())
.skip(skip)
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))?
* 0.8;
for (n, (v, nxt)) in mag.iter().zip(mag.iter().skip(1)).enumerate().skip(skip) {
if *v > thresh && *v > *nxt {
return Some(n);
}
}
None
}