cicadenade/src/demodulation.rs

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(&current_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(&current_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();
}
}
}