tried to do multithreading, but that didn't work, so just did an overhaul of the demod and made it a bit better instead

This commit is contained in:
Fluora Eigenwire 2020-03-14 23:24:32 -05:00
parent 806fb76829
commit 5694ce2f55
8 changed files with 626 additions and 466 deletions

72
Cargo.lock generated
View File

@ -1,16 +1,88 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "crossbeam-channel"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lapp"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "threnodyne"
version = "0.1.0"
dependencies = [
"crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lapp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
"checksum lapp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60bf485afeba9437a275ad29a9383b03f2978450e7feceffb55be8c0dbad9829"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"

View File

@ -8,3 +8,5 @@ edition = "2018"
[dependencies]
lapp = "0.4.0"
crossbeam-channel = "0.4.2"
num_cpus = "1.12.0"

View File

@ -63,3 +63,8 @@ channel with heavy interference and tight bandwidth limits. Data transfer rate w
you'll be lucky to get 300 bits/sec over the air, and luckier still to get 100 bits/sec over a
tight analog voice link - but this is still reasonable enough for plaintext messages.
### Origin of name
A threnody is a song of mourning, similar to a lament. In earlier versions of this project, we were
expecting the sound it produced to be more musical, perhaps with the warble characteristic of PSK,
but then we settled on chirp spread spectrum instead, and now it sounds like a cicada. It is what it
is, we guess. It's a pretty name and we don't feel like changing it now.

View File

@ -2,313 +2,525 @@
use crate::posi;
use crate::chirp;
use crate::gauss;
use crate::gaussian;
struct SignalInput
{
template_rise: Vec<i64>,
template_fall: Vec<i64>,
norm_rise: i64,
norm_fall: i64,
symbol_size: i64,
buffer: posi::Buffer<i64>,
sum: i64,
}
impl SignalInput
{
fn new
(
center_freq: f32,
deviation: f32,
symbol_length: f32,
) ->
SignalInput
{
let upper_freq = (center_freq + deviation).max(0.0).min(1.0);
let lower_freq = (center_freq - deviation).max(0.0).min(1.0);
// these have to be reversed, since the input buffer shifts in at index 0 and is thus a mirror image
let template_rise:Vec<i64> =
(
chirp::generate(upper_freq, lower_freq, symbol_length)
.into_iter()
.map(|x| x as i64)
.collect()
);
let template_fall:Vec<i64> =
(
chirp::generate(lower_freq, upper_freq, symbol_length)
.into_iter()
.map(|x| x as i64)
.collect()
);
let norm_rise =
(
template_rise
.iter()
.map(|&x| x.abs())
.sum()
);
let norm_fall =
(
template_rise
.iter()
.map(|&x| x.abs())
.sum()
);
SignalInput
{
template_rise: template_rise,
template_fall: template_fall,
norm_rise: norm_rise,
norm_fall: norm_fall,
symbol_size: symbol_length.round() as i64,
buffer: posi::Buffer::new(symbol_length.round() as usize),
sum: 0,
}
}
fn ingest
(
&mut self,
sample: i16,
) {
let new_value = sample as i64;
let old_value = self.buffer.last();
self.sum -= old_value.abs();
self.sum += new_value.abs();
self.buffer.shift_in(new_value);
}
fn process
(
&self,
) ->
(i64, i64)
{
let local_signal = self.buffer.to_vec();
let mut rise_value = 0;
let mut fall_value = 0;
if self.sum > 0
{
for (&a, &b) in
(
local_signal.iter()
).zip(
self.template_rise.iter()
) {
rise_value += a * b;
}
for (&a, &b) in
(
local_signal.iter()
).zip(
self.template_fall.iter()
) {
fall_value += a * b;
}
rise_value =
(
(32768 * rise_value * self.symbol_size)
/
(self.norm_rise * self.sum)
);
fall_value =
(
(32768 * fall_value * self.symbol_size)
/
(self.norm_fall * self.sum)
);
}
return (rise_value, fall_value);
}
}
struct CorrelFilter
{
kernel: Vec<i64>,
buffer_rise: posi::Buffer<i64>,
buffer_fall: posi::Buffer<i64>,
}
impl CorrelFilter
{
fn new
(
freq: f32,
) ->
CorrelFilter
{
let kernel = gaussian::generate(freq);
let size = kernel.len();
CorrelFilter
{
kernel: kernel,
buffer_rise: posi::Buffer::new(size),
buffer_fall: posi::Buffer::new(size),
}
}
fn ingest
(
&mut self,
rise: i64,
fall: i64,
) {
self.buffer_rise.shift_in(rise);
self.buffer_fall.shift_in(fall);
}
fn hi_process
(
&self,
) ->
(i64, i64)
{
let local_rise = self.buffer_rise.to_vec();
let local_fall = self.buffer_fall.to_vec();
let mut rise_result:i64 = 0;
let mut fall_result:i64 = 0;
for (&a, &b) in
(
local_rise.iter()
).zip(
self.kernel.iter()
) {
rise_result += a * b;
}
for (&a, &b) in
(
local_fall.iter()
).zip(
self.kernel.iter()
) {
fall_result += a * b;
}
rise_result /= 32768;
fall_result /= 32768;
return
(
self.buffer_rise.mid() - rise_result,
self.buffer_fall.mid() - fall_result,
);
}
fn lo_process
(
&self,
) ->
(i64, i64)
{
let local_rise = self.buffer_rise.to_vec();
let local_fall = self.buffer_fall.to_vec();
let mut rise_result:i64 = 0;
let mut fall_result:i64 = 0;
for (&a, &b) in
(
local_rise.iter()
).zip(
self.kernel.iter()
) {
rise_result += a * b;
}
for (&a, &b) in
(
local_fall.iter()
).zip(
self.kernel.iter()
) {
fall_result += a * b;
}
rise_result /= 32768;
fall_result /= 32768;
return
(
rise_result,
fall_result,
);
}
}
struct CorrelOutput
{
buffer_rise: posi::Buffer<i64>,
buffer_fall: posi::Buffer<i64>,
rise_maxima: Vec<i64>,
fall_maxima: Vec<i64>,
rolling_max_rise: i64,
rolling_max_fall: i64,
rolling_max_decay: i64,
squelch: i64,
}
impl CorrelOutput
{
fn new
(
size: usize,
) ->
CorrelOutput
{
CorrelOutput
{
buffer_rise: posi::Buffer::new(size),
buffer_fall: posi::Buffer::new(size),
rise_maxima: Vec::new(),
fall_maxima: Vec::new(),
rolling_max_rise: 0,
rolling_max_fall: 0,
rolling_max_decay: (size as i64) / 2,
squelch: 0,
}
}
fn ingest
(
&mut self,
new_rise: i64,
new_fall: i64,
) {
let old_rise = self.buffer_rise.last();
let old_fall = self.buffer_fall.last();
if let Ok(index) = self.rise_maxima.binary_search(&old_rise)
{
self.rise_maxima.remove(index);
}
if let Ok(index) = self.fall_maxima.binary_search(&old_fall)
{
self.fall_maxima.remove(index);
}
if (
self.rise_maxima.len() == 0
||
Some(&new_rise) >= self.rise_maxima.last()
) {
self.rise_maxima.push(new_rise);
}
if (
self.fall_maxima.len() == 0
||
Some(&new_fall) >= self.fall_maxima.last()
) {
self.fall_maxima.push(new_fall);
}
self.buffer_rise.shift_in(new_rise);
self.buffer_fall.shift_in(new_fall);
let current_rise = self.buffer_rise.mid();
let current_fall = self.buffer_fall.mid();
self.rolling_max_rise =
(
self.rolling_max_rise
*
(self.rolling_max_decay - 1)
/
self.rolling_max_decay
);
self.rolling_max_fall =
(
self.rolling_max_fall
*
(self.rolling_max_decay - 1)
/
self.rolling_max_decay
);
self.rolling_max_rise = self.rolling_max_rise.max(current_rise);
self.rolling_max_fall = self.rolling_max_fall.max(current_fall);
}
fn have_peaks
(
&self,
) ->
(bool, bool)
{
let current_rise = self.buffer_rise.mid();
let current_fall = self.buffer_fall.mid();
let rise_max = *self.rise_maxima.last().unwrap_or(&0);
let fall_max = *self.fall_maxima.last().unwrap_or(&0);
let mut have_rise:bool = false;
let mut have_fall:bool = false;
if (
current_rise >= rise_max
&&
current_rise >= self.rolling_max_rise * 15 / 16
&&
current_rise >= self.squelch
) {
have_rise = true;
}
if (
current_fall >= fall_max
&&
current_fall >= self.rolling_max_fall * 15 / 16
&&
current_fall >= self.squelch
) {
have_fall = true;
}
return (have_rise, have_fall);
}
fn have_bit
(
&self
) ->
Option<bool>
{
match self.have_peaks()
{
(true, false) => Some(true),
(false, true) => Some(false),
( _, _ ) => None,
}
}
}
struct ByteAccumulator
{
byte: u8,
bit: u8,
since_bit: usize,
abandon: usize,
}
impl ByteAccumulator
{
fn new
(
abandon: usize,
) ->
ByteAccumulator
{
ByteAccumulator
{
byte: 0x00,
bit: 0,
since_bit: 0,
abandon: abandon,
}
}
fn accumulate
(
&mut self,
possible_bit: Option<bool>,
) ->
Option<u8>
{
let mut output:Option<u8> = None;
if let Some(new_bit) = possible_bit
{
self.since_bit = 0;
self.byte |= (new_bit as u8).reverse_bits() >> self.bit;
self.bit += 1;
if self.bit >= 8
{
output = Some(self.byte);
self.byte = 0x00;
self.bit = 0;
}
}
else
{
self.since_bit += 1;
if self.since_bit > self.abandon
{
self.byte = 0x00;
self.bit = 0;
}
}
return output;
}
}
pub struct Demodulator
{
squelch: i16,
symbol_rise: Vec<i16>, // symbol templates; chirps from lower_freq to upper_freq and vice versa, encoding 1 and 0
symbol_fall: Vec<i16>,
symbol_rise_norm: i64, // the absolute sum values of the symbol templates, used for normalizing the correlations
symbol_fall_norm: i64,
signal_buffer: posi::Buffer<i16>, // shift buffer holding incoming samples to be correlated with the symbol templates
signal_sum: i64, // absolute sum of the signal buffer, used to determine average signal level
correl_rise_lopass: gauss::Filter, // low-pass filters for the two correlation outputs
correl_fall_lopass: gauss::Filter,
correl_rise_hipass: gauss::Filter, // high-pass filters for the two correlation outputs
correl_fall_hipass: gauss::Filter,
correl_rise_buffer: posi::Buffer<i64>, // buffers holding the filtered correlation outputs for analysis
correl_fall_buffer: posi::Buffer<i64>,
correl_rise_maxima: Vec<i64>, // lists used to keep track of the maximum value currently present in the correlation output buffers
correl_fall_maxima: Vec<i64>,
correl_peak_average: i64, // running average height of a peak in the correlation buffers, used to reject spurious small peaks
accumulator_byte: u8, // a byte that we are collecting from bits (symbols) before returning when it is complete
accumulator_bit: u8, // which bit we want next to assemble a byte (0=MSB)
samples_since_bit: usize, // how long it has been since a symbol was last detected
abandon_samples: usize, // how long to wait for the next bit before discarding accumulated bits and starting a new byte
signal_input: SignalInput,
correl_lopass: CorrelFilter,
correl_hipass: CorrelFilter,
correl_output: CorrelOutput,
accumulator: ByteAccumulator,
}
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 Nyquist limit. (note: actual bandwidth will be higher due to sidebands.)
symbol_len: f32, // length of a symbol (a chirp) in samples
) ->
center_freq: f32,
deviation: f32,
symbol_length: f32,
) ->
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);
// 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 correlations will be backwards and produce inverted data.
// 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 symbol_rise:Vec<i16> = chirp::generate(upper_freq, lower_freq, symbol_len);
let symbol_fall:Vec<i16> = chirp::generate(lower_freq, upper_freq, symbol_len);
let symbol_rise_norm:i64 =
(
symbol_rise
.iter()
.map(
|&x| (x as i64).abs()
)
.sum()
);
let symbol_fall_norm:i64 =
(
symbol_fall
.iter()
.map(
|&x| (x as i64).abs()
)
.sum()
);
let signal_bufsize = symbol_len.round() as usize;
let correl_bufsize = (symbol_len * 1.0).round() as usize; // longer than 1.0*symbol_len may or may not be a good idea?
Demodulator
{
squelch: 0,
symbol_rise: symbol_rise,
symbol_fall: symbol_fall,
symbol_rise_norm: symbol_rise_norm,
symbol_fall_norm: symbol_fall_norm,
signal_buffer: posi::Buffer::new(signal_bufsize),
signal_sum: 0,
correl_rise_lopass: gauss::Filter::new(deviation / 2.0),
correl_fall_lopass: gauss::Filter::new(deviation / 2.0),
correl_rise_hipass: gauss::Filter::new(deviation / 16.0),
correl_fall_hipass: gauss::Filter::new(deviation / 16.0),
correl_rise_buffer: posi::Buffer::new(correl_bufsize),
correl_fall_buffer: posi::Buffer::new(correl_bufsize),
correl_rise_maxima: Vec::new(),
correl_fall_maxima: Vec::new(),
correl_peak_average: 0,
accumulator_byte: 0x00,
accumulator_bit: 0,
samples_since_bit: 0,
abandon_samples: (symbol_len * 4.0 / 3.0).round() as usize,
signal_input: SignalInput::new(center_freq, deviation, symbol_length),
correl_lopass: CorrelFilter::new(deviation / 2.0),
correl_hipass: CorrelFilter::new(deviation / 8.0),
correl_output: CorrelOutput::new(symbol_length.round() as usize),
accumulator: ByteAccumulator::new((symbol_length * 4.0 / 3.0).round() as usize),
}
}
// set the minimum signal level (absolute average) for demodulation to proceed
pub fn set_squelch
(
&mut self,
new_value: u16,
) {
self.squelch = (new_value / 2) as i16;
}
// basically a wrapper around signal_buffer.shift_in() that also updates signal_sum
fn ingest_signal
pub fn process
(
&mut self,
new_sample: i16,
) {
let old_sample = self.signal_buffer.last();
self.signal_sum -= (old_sample as i64).abs();
self.signal_sum += (new_sample as i64).abs();
self.signal_buffer.shift_in(new_sample);
}
// does the correlation of the signal with the templates
// also passes the result through the filters and shifts it into the correlation output buffers
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()
) {
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
);
}
correl_rise *= 32767;
correl_rise /= self.symbol_rise_norm;
correl_fall *= 32767;
correl_fall /= self.symbol_fall_norm;
correl_rise = correl_rise.abs();
correl_fall = correl_fall.abs();
correl_rise = self.correl_rise_lopass.lo_process(correl_rise);
correl_fall = self.correl_fall_lopass.lo_process(correl_fall);
correl_rise = self.correl_rise_hipass.hi_process(correl_rise);
correl_fall = self.correl_fall_hipass.hi_process(correl_fall);
let old_rise = self.correl_rise_buffer.last();
let old_fall = self.correl_fall_buffer.last();
if let Ok(index) = self.correl_rise_maxima.binary_search(&old_rise)
{
self.correl_rise_maxima.remove(index);
}
if let Ok(index) = self.correl_fall_maxima.binary_search(&old_fall)
{
self.correl_fall_maxima.remove(index);
}
if (
self.correl_rise_maxima.len() == 0
||
Some(&correl_rise) >= self.correl_rise_maxima.last()
) {
self.correl_rise_maxima.push(correl_rise);
}
if (
self.correl_fall_maxima.len() == 0
||
Some(&correl_fall) >= self.correl_fall_maxima.last()
) {
self.correl_fall_maxima.push(correl_fall);
}
self.correl_rise_buffer.shift_in(correl_rise);
self.correl_fall_buffer.shift_in(correl_fall);
}
// for the two output buffers, determines if the current midpoint is both
// a) the maximum value in the buffer
// b) higher, or nearly higher, than the average peak height (e.g. not a spurious small peak)
fn detect_peaks
(
&mut self,
) ->
(Option<i64>, Option<i64>)
{
let rise_value = self.correl_rise_buffer.mid();
let fall_value = self.correl_fall_buffer.mid();
let mut have_rise:Option<i64> = None;
let mut have_fall:Option<i64> = None;
if (
rise_value > 0
&&
Some(&rise_value) == self.correl_rise_maxima.last()
) {
if self.correl_peak_average <= 0
{
self.correl_peak_average = rise_value;
}
else
{
self.correl_peak_average = (rise_value + self.correl_peak_average * 15) / 16;
}
if rise_value > self.correl_peak_average * 15 / 16
{
have_rise = Some(rise_value);
}
}
if (
fall_value > 0
&&
Some(&fall_value) == self.correl_fall_maxima.last()
) {
if self.correl_peak_average <= 0
{
self.correl_peak_average = fall_value;
}
else
{
self.correl_peak_average = (fall_value + self.correl_peak_average * 15) / 16;
}
if fall_value > self.correl_peak_average * 15 / 16
{
have_fall = Some(fall_value);
}
}
return (have_rise, have_fall);
}
// determines if we actually have a symbol right now, and if so, emits its value
fn determine
(
&mut self,
) ->
Option<bool>
Option<u8>
{
if (self.signal_sum / self.signal_buffer.len() as i64) < self.squelch as i64
{
// note that this means all the demodulation stuff (namely the correlation) still happens all the time,
// it's just that if the signal is below the squelch threshold, then symbol detections are suppressed.
return None;
}
self.signal_input.ingest(new_sample);
let (correl_rise, correl_fall) = self.signal_input.process();
let (have_rise, have_fall) = self.detect_peaks();
self.correl_lopass.ingest(correl_rise.abs(), correl_fall.abs());
let (rise_lo, fall_lo) = self.correl_lopass.lo_process();
if have_rise.is_some() && have_fall.is_none()
{
return Some(true);
}
if have_fall.is_some() && have_rise.is_none()
{
return Some(false);
}
self.correl_hipass.ingest(rise_lo, fall_lo);
let (rise_hi, fall_hi) = self.correl_hipass.hi_process();
return None;
self.correl_output.ingest(rise_hi, fall_hi);
let possible_bit = self.correl_output.have_bit();
let possible_byte = self.accumulator.accumulate(possible_bit);
return possible_byte;
}
pub fn set_squelch
(
&mut self,
value: u16,
) {
self.correl_output.squelch = (value / 2) as i64;
}
// debugging method that emits the current values of the correlation buffer midpoints
pub fn dump_correl
(
&self,
@ -316,51 +528,8 @@ impl Demodulator
(i64, i64)
{
(
self.correl_rise_buffer.mid(),
self.correl_fall_buffer.mid(),
self.correl_output.buffer_rise.mid(),
self.correl_output.buffer_fall.mid(),
)
}
// primary outer method, which takes in samples and emits bytes if there are any.
pub fn process
(
&mut self,
new_sample: i16,
) ->
Option<u8>
{
self.ingest_signal(new_sample);
self.correlate();
if let Some(bit) = self.determine()
{
self.accumulator_byte |= (bit as u8).reverse_bits() >> self.accumulator_bit;
self.accumulator_bit += 1;
self.samples_since_bit = 0;
}
else
{
self.samples_since_bit += 1;
if self.samples_since_bit > self.abandon_samples
{
self.accumulator_byte = 0x00;
self.accumulator_bit = 0;
}
}
let mut result:Option<u8> = None;
if self.accumulator_bit >= 8
{
result = Some(self.accumulator_byte);
self.accumulator_byte = 0x00;
self.accumulator_bit = 0;
}
return result;
}
}

View File

@ -42,7 +42,14 @@ impl Enmodulator
) ->
Vec<i16>
{
let mut new_signal:Vec<i16> = Vec::with_capacity(8*self.symbol_len + 2*self.guard_pad);
let mut new_signal:Vec<i16> =
(
Vec::with_capacity(
(8 * self.symbol_len)
+
(2 * self.guard_pad)
)
);
new_signal.append(&mut vec![0; self.guard_pad]);

View File

@ -1,137 +0,0 @@
use std::f32::consts::PI;
use crate::posi;
fn gaussian
(
width: f32,
) ->
Vec<i64>
{
let radius = (width * 9.4).round().max(1.0) as isize;
let mut output:Vec<i64> = Vec::with_capacity(radius as usize * 2 + 1);
for t in
(
(
-radius
..=
radius
).map(
|x| x as f32
)
) {
output.push(
(
32768.0 *
(
-0.5 * (t / width).powi(2)
)
.exp()
/
(
width * (2.0 * PI).sqrt()
)
)
.round()
as i64
);
}
return output;
}
pub struct Filter
{
kernel: Vec<i64>,
buffer: posi::Buffer<i64>,
}
impl Filter
{
pub fn new
(
frequency: f32,
) ->
Filter
{
let width:f32 = 1.0 / (PI * frequency);
let kernel = gaussian(width);
let buffer = posi::Buffer::new(kernel.len());
Filter
{
kernel: kernel,
buffer: buffer,
}
}
pub fn hi_process
(
&mut self,
new_value: i64,
) ->
i64
{
self.buffer.shift_in(new_value);
let mut output_value:i64 = 0;
for (&buffer_value, &kernel_value) in
(
self.buffer.to_vec().iter()
).zip(
self.kernel.iter()
) {
output_value +=
(
buffer_value
*
kernel_value
);
}
output_value /= 32768;
output_value =
(
self.buffer.mid() as i64
-
output_value
);
return output_value;
}
pub fn lo_process
(
&mut self,
new_value: i64,
) ->
i64
{
self.buffer.shift_in(new_value);
let mut output_value:i64 = 0;
for (&buffer_value, &kernel_value) in
(
self.buffer.to_vec().iter()
).zip(
self.kernel.iter()
) {
output_value +=
(
buffer_value
*
kernel_value
);
}
output_value /= 32768;
return output_value;
}
}

42
src/gaussian.rs Normal file
View File

@ -0,0 +1,42 @@
use std::f32::consts::PI;
pub fn generate
(
freq: f32,
) ->
Vec<i64>
{
let width = 1.0 / (PI * freq);
let radius = (width * 9.4).round().max(1.0) as isize;
let mut output:Vec<i64> = Vec::with_capacity(radius as usize * 2 + 1);
for t in
(
(
-radius
..=
radius
).map(
|x| x as f32
)
) {
output.push(
(
32768.0 *
(
-0.5 * (t / width).powi(2)
)
.exp()
/
(
width * (2.0 * PI).sqrt()
)
)
.round()
as i64
);
}
return output;
}

View File

@ -11,7 +11,7 @@ use std::process::exit;
extern crate lapp;
mod chirp;
mod gauss;
mod gaussian;
mod posi;
mod enmodulation;
mod demodulation;
@ -39,7 +39,7 @@ fn main
-b, --bit-rate (default 50.0) - Data rate, in chirps (bits) per second. Be aware that lower values will increase system load.
-f, --center-frequency (default 4000.0) - Center frequency of the signal in Hertz.
-d, --deviation (default 2000.0) - Maximum range traversed above and below the center frequency by the signal.
-t, --squelch (default 20) - Minimum signal amplitude for demodulation, from 0 (accept all) to 65535 (ignore all).
-t, --squelch (default 4000) - Minimum signal match degree for demodulation, from 0 (accept all) to 65535 (ignore all).
-a, --ascii-only - When demodulating, suppress all output that is not an ASCII character. Possibly useful if searching for text on a noisy channel.
-g, --debug (default 0) - If specified when demodulating, emits every nth correlation value in f32 form to threnodyne_rise.dat and threnodyne_fall.dat.