242 lines
6.4 KiB
Rust
242 lines
6.4 KiB
Rust
#![allow(unused_parens)]
|
|
|
|
use crate::posi;
|
|
use crate::chirp;
|
|
|
|
pub struct Demodulator
|
|
{
|
|
symbol_rise: Vec<i16>, // chirp from lower_freq to upper_freq, encoding a 1
|
|
symbol_fall: Vec<i16>, // chirp from upper_freq to lower_freq, encoding a 0
|
|
|
|
signal_bufsize: usize,
|
|
|
|
signal_buffer: posi::Buffer<i16>, // buffer holding the incoming signal to be convolved with the rising and falling chirps
|
|
signal_abs_sum: i64,
|
|
|
|
correl_bufsize: usize,
|
|
|
|
correl_rise_buffer: posi::Buffer<i64>, // buffer holding the values of the convolution with the rising chirp
|
|
correl_rise_abs_sum: i64,
|
|
correl_rise_abs_max: Vec<i64>, // structure for tracking the absolute maximum value currently contained in this buffer
|
|
|
|
correl_fall_buffer: posi::Buffer<i64>, // buffer holding the values of the convolution with the falling chirp
|
|
correl_fall_abs_sum: i64,
|
|
correl_fall_abs_max: Vec<i64>,
|
|
|
|
holdoff: usize,
|
|
}
|
|
|
|
impl Demodulator
|
|
{
|
|
pub fn new
|
|
(
|
|
center_freq: f32, // center frequency of the signal, referenced to the Nyquist limit
|
|
deviation: f32, // half-width of the signal chirps, referenced to the center frequency. (note: actual bandwidth will be higher due to sidebands.)
|
|
symbol_len: f32, // length of a symbol (a chirp) in samples
|
|
) ->
|
|
Demodulator
|
|
{
|
|
let lower_freq = (center_freq - deviation).max(0.0).min(1.0);
|
|
let upper_freq = (center_freq + deviation).max(0.0).min(1.0);
|
|
|
|
// the signals are, in fact, just mirror images of each other, but we're coding this
|
|
// as though they are not in case this changes later.
|
|
let mut symbol_rise:Vec<i16> = chirp::generate(lower_freq, upper_freq, symbol_len);
|
|
let mut symbol_fall:Vec<i16> = chirp::generate(upper_freq, lower_freq, symbol_len);
|
|
|
|
// important!!
|
|
// since the posi buffers shift from low to high index, their signal contents is reversed.
|
|
// we need to reverse our templates, too, or our convolutions will be backwards and produce inverted data.
|
|
symbol_rise.reverse();
|
|
symbol_fall.reverse();
|
|
|
|
let signal_bufsize = symbol_len.round() as usize;
|
|
let correl_bufsize = (symbol_len * 0.9).round() as usize;
|
|
|
|
Demodulator
|
|
{
|
|
symbol_rise: symbol_rise,
|
|
symbol_fall: symbol_fall,
|
|
|
|
signal_bufsize: signal_bufsize,
|
|
|
|
signal_buffer: posi::Buffer::new(signal_bufsize),
|
|
signal_abs_sum: 0,
|
|
|
|
correl_bufsize: correl_bufsize,
|
|
|
|
correl_rise_buffer: posi::Buffer::new(correl_bufsize),
|
|
correl_rise_abs_sum: 0,
|
|
correl_rise_abs_max: Vec::new(),
|
|
|
|
correl_fall_buffer: posi::Buffer::new(correl_bufsize),
|
|
correl_fall_abs_sum: 0,
|
|
correl_fall_abs_max: Vec::new(),
|
|
|
|
holdoff: correl_bufsize,
|
|
}
|
|
}
|
|
|
|
fn ingest
|
|
(
|
|
&mut self,
|
|
new_sample: i16,
|
|
) {
|
|
let old_sample = self.signal_buffer.last();
|
|
self.signal_abs_sum += new_sample.abs() as i64;
|
|
self.signal_abs_sum -= old_sample.abs() as i64;
|
|
self.signal_buffer.shift_in(new_sample);
|
|
|
|
if self.signal_abs_sum == 0
|
|
{
|
|
self.holdoff = self.signal_bufsize;
|
|
}
|
|
}
|
|
|
|
fn correlate
|
|
(
|
|
&mut self,
|
|
) {
|
|
let local_signal = self.signal_buffer.to_vec();
|
|
|
|
let mut correl_rise:i64 = 0;
|
|
let mut correl_fall:i64 = 0;
|
|
|
|
for (&signal_value, &rise_value) in
|
|
(
|
|
local_signal.iter()
|
|
).zip(
|
|
self.symbol_rise.iter()
|
|
) {
|
|
// this results in a value normalized to 32767^2 * length
|
|
correl_rise +=
|
|
(
|
|
signal_value as i64
|
|
*
|
|
rise_value as i64
|
|
);
|
|
}
|
|
|
|
for (&signal_value, &fall_value) in
|
|
(
|
|
local_signal.iter()
|
|
).zip(
|
|
self.symbol_fall.iter()
|
|
) {
|
|
correl_fall +=
|
|
(
|
|
signal_value as i64
|
|
*
|
|
fall_value as i64
|
|
);
|
|
}
|
|
|
|
let (new_value_rise, new_value_fall) = (correl_rise, correl_fall);
|
|
let (old_value_rise, old_value_fall) = (self.correl_rise_buffer.last(), self.correl_fall_buffer.last());
|
|
|
|
self.correl_rise_abs_sum += new_value_rise.abs();
|
|
self.correl_rise_abs_sum -= old_value_rise.abs();
|
|
self.correl_fall_abs_sum += new_value_fall.abs();
|
|
self.correl_fall_abs_sum -= old_value_fall.abs();
|
|
|
|
if let Some(index) = self.correl_rise_abs_max.iter().position(|x| x == &old_value_rise.abs())
|
|
{
|
|
self.correl_rise_abs_max.remove(index);
|
|
}
|
|
|
|
if let Some(index) = self.correl_fall_abs_max.iter().position(|x| x == &old_value_fall.abs())
|
|
{
|
|
self.correl_fall_abs_max.remove(index);
|
|
}
|
|
|
|
if (
|
|
new_value_rise >= *self.correl_rise_abs_max.last().unwrap_or(&0)
|
|
&&
|
|
new_value_rise != 0
|
|
) {
|
|
self.correl_rise_abs_max.push(new_value_rise);
|
|
}
|
|
|
|
if (
|
|
new_value_fall >= *self.correl_fall_abs_max.last().unwrap_or(&0)
|
|
&&
|
|
new_value_fall != 0
|
|
) {
|
|
self.correl_fall_abs_max.push(new_value_fall);
|
|
}
|
|
|
|
self.correl_rise_buffer.shift_in(new_value_rise);
|
|
self.correl_fall_buffer.shift_in(new_value_fall);
|
|
}
|
|
|
|
fn determine
|
|
(
|
|
&self,
|
|
) ->
|
|
Option<bool>
|
|
{
|
|
let rise_avg = (self.correl_rise_abs_sum / self.correl_bufsize as i64);
|
|
let fall_avg = (self.correl_fall_abs_sum / self.correl_bufsize as i64);
|
|
|
|
// these values will stay at zero if no interval-maximum is found for their corresponding symbol template.
|
|
// otherwise, they will be set to that symbol's convolution magnitude.
|
|
let mut rise_weight:i64 = 0;
|
|
let mut fall_weight:i64 = 0;
|
|
|
|
let current_rise = self.correl_rise_buffer.mid().abs();
|
|
if (
|
|
current_rise > rise_avg * 4
|
|
&&
|
|
self.correl_rise_abs_max.last() == Some(¤t_rise)
|
|
) {
|
|
rise_weight = current_rise;
|
|
}
|
|
|
|
let current_fall = self.correl_fall_buffer.mid().abs();
|
|
if (
|
|
current_fall > fall_avg * 4
|
|
&&
|
|
self.correl_fall_abs_max.last() == Some(¤t_fall)
|
|
) {
|
|
fall_weight = current_fall;
|
|
}
|
|
|
|
if rise_weight != 0 || fall_weight != 0
|
|
{
|
|
return Some(fall_weight < rise_weight);
|
|
} else {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
pub fn process
|
|
(
|
|
&mut self,
|
|
new_sample: i16,
|
|
) ->
|
|
Option<bool>
|
|
{
|
|
// general principle of operation:
|
|
// 1. convolve input signal with 1-symbol and 0-symbol template signals.
|
|
// 2. divide the convolution values by the total magnitude of the input signal over the convolution interval.
|
|
// 3. continuously check the normalized convolution curve for peaks which are, for an interval of one
|
|
// symbol-length centered on the peak in question, both:
|
|
// a. the tallest peak in the interval.
|
|
// b. at least three times the average value in the interval.
|
|
// 4. upon finding such a peak for either symbol template, emit a bit of demodulated data.
|
|
// if peaks coincide for both symbols, emit whichever bit has a higher convolution value.
|
|
|
|
self.ingest(new_sample);
|
|
|
|
self.correlate();
|
|
|
|
if self.holdoff > 0
|
|
{
|
|
self.holdoff -= 1;
|
|
return None;
|
|
} else {
|
|
return self.determine();
|
|
}
|
|
}
|
|
}
|