initial commit; preliminary working state
This commit is contained in:
commit
5fffcbce43
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
**/*.rs.bk
|
|
@ -0,0 +1,16 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "lapp"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "threnodyne"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lapp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum lapp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60bf485afeba9437a275ad29a9383b03f2978450e7feceffb55be8c0dbad9829"
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "threnodyne"
|
||||
version = "0.1.0"
|
||||
authors = ["Fluora Eigendiode <ellie.martin.eberhardt@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lapp = "0.4.0"
|
|
@ -0,0 +1,33 @@
|
|||
# Threnodyne
|
||||
#### More audio-based data transmission! This time it's using chirp spread spectrum
|
||||
|
||||
### What this is
|
||||
Threnodyne is another thing for making a sound card work as a silly budget SDR, in the same vein as
|
||||
[transgride](https://tildegit.org/fluora/transgride). To work on radio waves, it probably needs some
|
||||
kind of upconverter, unless you're into VLF.
|
||||
|
||||
The use case we have in mind is just plugging it into a radio set that is designed to handle audio.
|
||||
We are also hoping it will work for other audio data encoding purposes, like storing data on
|
||||
cassette tapes, or even transmitting data between computers using sound.
|
||||
|
||||
### How it works
|
||||
Modulation is simple: you simply select an audio frequency center and deviation, and the program
|
||||
generates a series of chirps - tones with linearly-changing frequency - centered on the specified
|
||||
frequency and traversing the specified deviation. A rising chirp encodes a 1 bit, and a falling
|
||||
chirp encodes a 0 bit.
|
||||
|
||||
Demodulation is accomplished by taking the cross-correlation of the template chirp signals with the
|
||||
incoming received signal, then searching for the sharp spikes in either of the results which
|
||||
correspond to the two possible symbols.
|
||||
|
||||
### How to use it
|
||||
As usual for Rust projects, you can build it (after installing the toolchain) with
|
||||
`cargo build --release`, which will emit an executable into `target/release`. Run that executable
|
||||
with `--help` for detailed usage information.
|
||||
|
||||
### Is it useful?
|
||||
Maybe? The use case we have in mind is relatively low-rate (a handful of bytes per second) digital
|
||||
radio communication, mainly for using voice-oriented radios as text-mode transceivers. This will not
|
||||
be especially useful for *fast* data transfer, but may help when trying to cram data through a janky
|
||||
channel with heavy interference and tight bandwidth limits.
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
use std::f32::consts::PI;
|
||||
|
||||
// this function is used by both the modulator and demodulator to generate chirps, so it's split off here
|
||||
// to ensure that it is always exactly the same for both modules.
|
||||
|
||||
pub fn generate
|
||||
(
|
||||
freq_lo: f32, // 1.0 = Nyquist limit
|
||||
freq_hi: f32,
|
||||
length: f32, // samples
|
||||
) ->
|
||||
Vec<i16>
|
||||
{
|
||||
let freq_delta = (freq_hi - freq_lo) / (length * 2.0);
|
||||
|
||||
let env_center = length / 2.0;
|
||||
let env_falloff = env_center / 4.8;
|
||||
|
||||
let mut new_chirp:Vec<i16> = Vec::with_capacity(length.round() as usize);
|
||||
for i in 0..(length.round() as usize)
|
||||
{
|
||||
let t = i as f32;
|
||||
|
||||
let env:f32 =
|
||||
(
|
||||
(
|
||||
-0.5 *
|
||||
(
|
||||
(t - env_center) / env_falloff
|
||||
)
|
||||
.powi(2)
|
||||
)
|
||||
.exp()
|
||||
);
|
||||
|
||||
new_chirp.push(
|
||||
(
|
||||
32000.0 *
|
||||
env *
|
||||
(
|
||||
PI * t *
|
||||
(
|
||||
freq_lo
|
||||
+
|
||||
(t * freq_delta)
|
||||
)
|
||||
)
|
||||
.sin()
|
||||
)
|
||||
.round()
|
||||
as i16
|
||||
);
|
||||
}
|
||||
|
||||
return new_chirp;
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
#![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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
#![allow(unused_parens)]
|
||||
use std::io::prelude::*;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::BufReader;
|
||||
use std::process::exit;
|
||||
|
||||
extern crate lapp;
|
||||
|
||||
mod chirp;
|
||||
mod posi;
|
||||
mod modulation;
|
||||
mod demodulation;
|
||||
|
||||
fn main
|
||||
() {
|
||||
|
||||
let args =
|
||||
(
|
||||
lapp::parse_args(
|
||||
"
|
||||
Threnodyne is program for transmitting and receiving data in chirp-sequence spread-spectrum form
|
||||
using your computer's audio card.
|
||||
|
||||
Chirps can provide substantial resistance to unfavorable channel conditions, which makes this program
|
||||
potentially suitable for transmitting data over channels intended for narrowband voice communication.
|
||||
|
||||
Audio input/output is in raw 16-bit little-endian PCM format.
|
||||
Data input/output is arbitrary binary data, which can be text, file contents, or whatever else.
|
||||
|
||||
Usage:
|
||||
<mode> (string) - Either 'e' to encode/modulate, or 'd' to decode/demodulate.
|
||||
-i, --input (default stdin) - Filelike object from which to read input.
|
||||
-o, --output (default stdout) - Filelike object into which to write output.
|
||||
-s, --sample-rate (default 48000.0) - Sample rate in Hertz, used for calculating modulation frequencies
|
||||
-b, --bit-rate (default 480.0) - Data rate, in chirps (bits) per second. Be aware that lower values will increase system load.
|
||||
-f, --center-frequency (default 12000.0) - Center frequency of the signal in Hertz.
|
||||
-d, --deviation (default 1000.0) - Maximum distance traversed above and below the center frequency by the signal.
|
||||
"
|
||||
)
|
||||
);
|
||||
|
||||
let mode = args.get_string("mode");
|
||||
|
||||
let mut input_reader = BufReader::new(args.get_infile("input"));
|
||||
let mut output_file = args.get_outfile("output");
|
||||
|
||||
let arg_samplerate = args.get_float("sample-rate");
|
||||
let arg_bitrate= args.get_float("bit-rate");
|
||||
let arg_centerfreq = args.get_float("center-frequency");
|
||||
let arg_deviation = args.get_float("deviation");
|
||||
|
||||
if !(
|
||||
&["e", "d"].contains(&mode.as_str())
|
||||
) {
|
||||
eprintln!("threnodyne: please specify either 'e' to encode/modulate or 'd' to decode/demodulate.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if arg_samplerate <= 0.0
|
||||
{
|
||||
eprintln!("threnodyne: the sample rate must be a positive, nonzero number ({} won't do)", arg_samplerate);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if arg_bitrate < 0.0
|
||||
{
|
||||
eprintln!("threnodyne: the bitrate must be a positive number ({} won't do)", arg_bitrate);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if arg_bitrate > arg_samplerate
|
||||
{
|
||||
eprintln!("threnodyne: the bitrate cannot be larger than the sample rate (a bitrate of {} won't do when the sample rate is {})", arg_bitrate, arg_samplerate);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if arg_centerfreq < 0.0
|
||||
{
|
||||
eprintln!("threnodyne: the center frequency must be a positive number ({} won't do)", arg_centerfreq);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if arg_deviation < 0.0
|
||||
{
|
||||
eprintln!("threnodyne: the frequency deviation must be a positive number ({} won't do)", arg_deviation);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if arg_deviation > arg_centerfreq
|
||||
{
|
||||
eprintln!("threnodyne: the frequency deviation cannot be larger than the center frequency (a deviation of {} won't do when the center frequency is {})", arg_deviation, arg_centerfreq);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let symbol_len = arg_samplerate / arg_bitrate;
|
||||
let symbol_size = symbol_len.round() as usize;
|
||||
let center_freq = 2.0 * arg_centerfreq / arg_samplerate;
|
||||
let deviation = 2.0 * arg_deviation / arg_samplerate;
|
||||
|
||||
if mode.as_str() == "e"
|
||||
{
|
||||
let modulator = modulation::Modulator::new(center_freq, deviation, symbol_len);
|
||||
|
||||
let mut output_buffer:Vec<u8> = Vec::with_capacity(symbol_size);
|
||||
|
||||
loop
|
||||
{
|
||||
let mut input_buffer:[u8;1] = [0x00];
|
||||
let byte = match input_reader.read_exact(&mut input_buffer)
|
||||
{
|
||||
Err(why) if why.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(why) =>
|
||||
{
|
||||
eprintln!("threnodyne: input read error: {}", why);
|
||||
exit(1);
|
||||
},
|
||||
Ok(_) => input_buffer[0],
|
||||
};
|
||||
|
||||
let signal = modulator.process(byte);
|
||||
|
||||
output_buffer.clear();
|
||||
|
||||
for &sample in signal.iter()
|
||||
{
|
||||
output_buffer.extend_from_slice(&sample.to_le_bytes());
|
||||
}
|
||||
|
||||
match output_file.write_all(&output_buffer)
|
||||
{
|
||||
Err(why) =>
|
||||
{
|
||||
eprintln!("threnodyne: output write error: {}", why);
|
||||
exit(1);
|
||||
},
|
||||
Ok(_) => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if mode.as_str() == "d"
|
||||
{
|
||||
let mut demodulator = demodulation::Demodulator::new(center_freq, deviation, symbol_len);
|
||||
|
||||
let mut accumulator_byte:u8 = 0x00;
|
||||
let mut accumulator_bit:u8 = 0;
|
||||
let mut samples_since_bit:usize = 0;
|
||||
|
||||
let guard_pad = symbol_size / 16;
|
||||
|
||||
loop
|
||||
{
|
||||
let mut sample_bytes:[u8;2] = [0x00, 0x00];
|
||||
match input_reader.read_exact(&mut sample_bytes)
|
||||
{
|
||||
Err(why) if why.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(why) =>
|
||||
{
|
||||
eprintln!("threnodyne: input read error: {}", why);
|
||||
exit(1);
|
||||
},
|
||||
Ok(_) => (),
|
||||
}
|
||||
|
||||
let sample = i16::from_le_bytes(sample_bytes);
|
||||
if let Some(bit) = demodulator.process(sample)
|
||||
{
|
||||
accumulator_byte |= [0b0000_0000, 0b1000_0000] [bit as usize] >> accumulator_bit;
|
||||
accumulator_bit += 1;
|
||||
samples_since_bit = 0;
|
||||
} else {
|
||||
samples_since_bit += 1;
|
||||
if samples_since_bit >= (symbol_size + guard_pad)
|
||||
{
|
||||
accumulator_bit = 0;
|
||||
accumulator_byte = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
if accumulator_bit >= 8
|
||||
{
|
||||
match output_file.write_all(&[accumulator_byte])
|
||||
{
|
||||
Err(why) =>
|
||||
{
|
||||
eprintln!("threnodyne: output write error: {}", why);
|
||||
exit(1);
|
||||
},
|
||||
Ok(_) => (),
|
||||
};
|
||||
accumulator_bit = 0;
|
||||
accumulator_byte = 0x00;
|
||||
|
||||
let _ = output_file.flush();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#![allow(unused_parens)]
|
||||
|
||||
use crate::chirp;
|
||||
|
||||
pub struct Modulator
|
||||
{
|
||||
symbol_len: usize,
|
||||
guard_pad: usize,
|
||||
symbol_rise: Vec<i16>,
|
||||
symbol_fall: Vec<i16>,
|
||||
}
|
||||
|
||||
impl Modulator
|
||||
{
|
||||
pub fn new
|
||||
(
|
||||
center_freq: f32,
|
||||
deviation: f32,
|
||||
symbol_len: f32,
|
||||
) ->
|
||||
Modulator
|
||||
{
|
||||
let lower_freq = (center_freq - deviation).max(0.0).min(1.0);
|
||||
let upper_freq = (center_freq + deviation).max(0.0).min(1.0);
|
||||
|
||||
let symbol_rise:Vec<i16> = chirp::generate(lower_freq, upper_freq, symbol_len);
|
||||
let symbol_fall:Vec<i16> = chirp::generate(upper_freq, lower_freq, symbol_len);
|
||||
|
||||
Modulator
|
||||
{
|
||||
symbol_len: symbol_len.round() as usize,
|
||||
guard_pad: (symbol_len / 16.0).round() as usize,
|
||||
symbol_rise: symbol_rise,
|
||||
symbol_fall: symbol_fall,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process
|
||||
(
|
||||
&self,
|
||||
byte: u8,
|
||||
) ->
|
||||
Vec<i16>
|
||||
{
|
||||
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]);
|
||||
|
||||
for i in 0..8
|
||||
{
|
||||
new_signal.extend_from_slice(
|
||||
[
|
||||
&self.symbol_fall,
|
||||
&self.symbol_rise,
|
||||
] [
|
||||
(
|
||||
(
|
||||
byte
|
||||
&
|
||||
(0b1000_0000 >> i)
|
||||
)
|
||||
!= 0
|
||||
)
|
||||
as usize
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
new_signal.append(&mut vec![0; self.guard_pad]);
|
||||
|
||||
return new_signal;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Buffer<T>
|
||||
where T: Copy + Clone + Default
|
||||
{
|
||||
data: Vec<T>, // the actual data in the buffer; length never changes after construction
|
||||
front: usize, // where in the data vec is the first element?
|
||||
bound: usize, // len-1; modulo-length can be done very fast by just bitwise-anding with this, since the length is 2^n
|
||||
size: usize, // len; separate from bound just to make the code look nicer
|
||||
}
|
||||
|
||||
impl<T> Buffer<T>
|
||||
where T: Copy + Clone + Default
|
||||
{
|
||||
|
||||
pub fn new
|
||||
(
|
||||
size: usize,
|
||||
) ->
|
||||
Buffer<T>
|
||||
{
|
||||
if size == 0
|
||||
{
|
||||
panic!("attempt to create a posi::Buffer with zero size");
|
||||
}
|
||||
let true_size = size.next_power_of_two();
|
||||
Buffer {
|
||||
data: vec![Default::default(); true_size],
|
||||
front: 0,
|
||||
bound: true_size - 1,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shift_in
|
||||
(
|
||||
&mut self,
|
||||
input: T,
|
||||
) {
|
||||
self.front = self.front.wrapping_sub(1);
|
||||
self.front &= self.bound;
|
||||
unsafe
|
||||
{
|
||||
*self.data.get_unchecked_mut(self.front) = input;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get
|
||||
(
|
||||
&self,
|
||||
index: usize,
|
||||
) ->
|
||||
T
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*self.data.get_unchecked((self.front + index) & self.bound)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mid
|
||||
(
|
||||
&self,
|
||||
) ->
|
||||
T
|
||||
{
|
||||
self.get(self.size / 2)
|
||||
}
|
||||
|
||||
pub fn last
|
||||
(
|
||||
&self,
|
||||
) ->
|
||||
T
|
||||
{
|
||||
self.get(self.size - 1)
|
||||
}
|
||||
|
||||
pub fn len
|
||||
(
|
||||
&self,
|
||||
) ->
|
||||
usize
|
||||
{
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn to_vec
|
||||
(
|
||||
&self
|
||||
) ->
|
||||
Vec<T>
|
||||
{
|
||||
let mut output:Vec<T> = Vec::with_capacity(self.data.len());
|
||||
let (upper_slice, lower_slice) = self.data.split_at(self.front);
|
||||
output.extend_from_slice(lower_slice);
|
||||
output.extend_from_slice(upper_slice);
|
||||
output.truncate(self.size);
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
THIS IS NOT A PLACE OF HONOR
|
||||
NO HIGHLY ESTEEMED DEED IS COMMEMORATED HERE
|
||||
NOTHING VALUED IS HERE
|
||||
THIS PLACE IS A MESSAGE AND PART OF A SYSTEM OF MESSAGES
|
||||
PAY ATTENTION TO IT
|
||||
SENDING THIS MESSAGE WAS IMPORTANT TO US
|
||||
WE CONSIDERED OURSELVES TO BE A POWERFUL CULTURE
|
||||
|
Loading…
Reference in New Issue