No warnings ! Starts csma-like behaviour
This commit is contained in:
82
Cargo.lock
generated
82
Cargo.lock
generated
@ -108,6 +108,15 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@ -425,6 +434,21 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@ -1346,6 +1370,12 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.14.0"
|
||||
@ -1711,6 +1741,17 @@ dependencies = [
|
||||
"hashbrown 0.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jack"
|
||||
version = "0.13.3"
|
||||
@ -1959,6 +2000,17 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.5"
|
||||
@ -2412,6 +2464,15 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@ -2810,6 +2871,7 @@ dependencies = [
|
||||
"hound",
|
||||
"plotters",
|
||||
"rand",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2847,6 +2909,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
@ -3250,6 +3318,20 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.2"
|
||||
|
||||
@ -10,3 +10,4 @@ egui_plot = "0.33.0"
|
||||
hound = "3.5.1"
|
||||
plotters = "0.3.7"
|
||||
rand = "0.9.2"
|
||||
tokio = "1.47.1"
|
||||
|
||||
525
main.rs
Normal file
525
main.rs
Normal file
@ -0,0 +1,525 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
f32::consts::PI,
|
||||
fs::File,
|
||||
i16,
|
||||
io::{self, Read, Write, stdout},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
os::unix::thread,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
mpsc::{self, Receiver, Sender, TryRecvError, channel, sync_channel},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod bfsk;
|
||||
mod complex;
|
||||
pub mod fft;
|
||||
mod filtering;
|
||||
mod iq;
|
||||
mod nco;
|
||||
mod units;
|
||||
mod windows;
|
||||
mod ted;
|
||||
|
||||
use bfsk::BFSKMod;
|
||||
use complex::Complex;
|
||||
use complex::Complex32;
|
||||
use cpal::{
|
||||
SampleRate,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use fft::DFTAlgorithm;
|
||||
use nco::Nco;
|
||||
|
||||
use eframe::{
|
||||
egui::{self, Color32, Context, Vec2b, debug_text::print, decode_animated_image_uri},
|
||||
glow::TOP_LEVEL_ARRAY_STRIDE,
|
||||
};
|
||||
use egui_plot::{
|
||||
self, AxisHints, Bar, BarChart, HLine, Legend, Line, LineStyle, Plot, PlotPoints, VLine,
|
||||
};
|
||||
use plotters::style::Color;
|
||||
use rand::{Rng, seq::index::sample};
|
||||
|
||||
use crate::{
|
||||
bfsk::BFSKDem, fft::{dft::NaiveDFT, FFT}, filtering::{
|
||||
dc_block::DCBlocker,
|
||||
fir::FIRFilter,
|
||||
impulse_response::design::{self, frequency_response, ir_from_transfer_function},
|
||||
}, iq::IQSampler, ted::elg::ELGate, units::frequency::{self, hz_to_rad_per_sample}
|
||||
};
|
||||
|
||||
// Utilities
|
||||
fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
|
||||
where
|
||||
T: Clone + Add<Output = T> + Mul<Output = T> + Sub<Output = T> + Div<Output = T>,
|
||||
{
|
||||
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
|
||||
}
|
||||
const BAUD_RATE: u32 = 1000;
|
||||
|
||||
fn main() {
|
||||
//modulate();
|
||||
//demodulate();
|
||||
//return;
|
||||
// Set up CPAL
|
||||
let host = cpal::default_host();
|
||||
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.expect("No input device available");
|
||||
let mut config = device
|
||||
.supported_input_configs()
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_sample_rate(SampleRate(48000));
|
||||
|
||||
// Channel to move samples from callback to main thread
|
||||
let (tx, rx) = sync_channel::<f32>(4096);
|
||||
|
||||
// Build input stream
|
||||
/*
|
||||
let stream = device
|
||||
.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[f32], _| {
|
||||
for x in data.iter() {
|
||||
let _ = tx.send(*x * 30.); // non-blocking send
|
||||
}
|
||||
},
|
||||
move |err| eprintln!("Stream error: {}", err),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
stream.play().unwrap();
|
||||
*/
|
||||
|
||||
let (eye_tx, eye_rx) = mpsc::channel::<Vec<f32>>();
|
||||
let (ctx_tx, ctx_rx) = mpsc::channel::<Arc<egui::Context>>();
|
||||
std::thread::spawn(move || demodulator(rx, eye_tx, ctx_rx));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: 48000,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut writer = hound::WavWriter::create("audio/noised.wav", spec).unwrap();
|
||||
|
||||
let mut reader = hound::WavReader::open("audio/rec.wav").unwrap();
|
||||
let mut rand = rand::rng();
|
||||
let samples = reader.samples::<i16>();
|
||||
for x in samples {
|
||||
let noise = rand.random::<f32>() * 2. - 1.;
|
||||
let sample = x.unwrap() as f32 / i16::MAX as f32 + noise * 0.9;
|
||||
let _ = tx.send(sample);
|
||||
writer
|
||||
.write_sample((sample * i16::MAX as f32) as i16)
|
||||
.unwrap();
|
||||
std::thread::sleep(Duration::from_micros(21));
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
});
|
||||
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
let _ = eframe::run_native(
|
||||
"Egui",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(EguiApp::new(cc, eye_rx, ctx_tx)))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
fn demodulator(
|
||||
rx: Receiver<f32>,
|
||||
eye_sender: Sender<Vec<f32>>,
|
||||
ctx_rx: Receiver<Arc<egui::Context>>,
|
||||
) {
|
||||
// Wait for egui context to request redraw
|
||||
let ctx = ctx_rx.recv().unwrap();
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
|
||||
// Corellators
|
||||
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
|
||||
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
|
||||
let corellator_length = (sample_per_symbol as f32 * 1.5) as usize;
|
||||
let corellator_pos = (0..corellator_length)
|
||||
.map(|i| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp() * windows::blackmann(i as f32 / (corellator_length as f32))
|
||||
//nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..corellator_length)
|
||||
.map(|i| {
|
||||
nco_neg.step();
|
||||
nco_neg.cexp() * windows::blackmann(i as f32 / (corellator_length as f32))
|
||||
//nco_neg.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
matched_filter_pos.normalize_freq(hz_to_rad_per_sample(deviation,sample_rate as f32));
|
||||
matched_filter_neg.normalize_freq(hz_to_rad_per_sample(deviation,sample_rate as f32));
|
||||
|
||||
let loop_p = 0.003;
|
||||
let loop_i = 0.001;
|
||||
let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 20]);
|
||||
matched_filter.normalize_dc();
|
||||
|
||||
let mut loop_filter_ir = vec![Complex32::new(loop_i, 0.); 30];
|
||||
let len = loop_filter_ir.len();
|
||||
//loop_filter_ir[0] = Complex32::new(loop_p + loop_i, 0.);
|
||||
loop_filter_ir[len - 1] = Complex32::new(loop_p + loop_i, 0.);
|
||||
|
||||
let mut loop_filter = FIRFilter::new(&loop_filter_ir);
|
||||
//loop_filter.normalize_dc();
|
||||
//loop_filter.normalize_sum();
|
||||
let mut elg = ELGate::new(sample_per_symbol as f32, loop_filter);
|
||||
|
||||
let mut dc_blocker = DCBlocker::new(0.999);
|
||||
|
||||
// Timing recovery
|
||||
let mut preamble_count = 0;
|
||||
let mut bit_index = 0;
|
||||
let mut last = 0u8;
|
||||
while let Ok(real_sample) = rx.recv() {
|
||||
let iq = iq_sampler.sample(real_sample);
|
||||
|
||||
// Perform corellation
|
||||
let neg_energy = matched_filter_neg.next(iq);
|
||||
let pos_energy = matched_filter_pos.next(iq);
|
||||
|
||||
let matched =
|
||||
matched_filter.next_real(dc_blocker.next_real(pos_energy.mag() - neg_energy.mag()));
|
||||
|
||||
if let Some((elg_sample, eye)) = elg.next_eye(matched) {
|
||||
let _ = eye_sender.send(eye);
|
||||
ctx.request_repaint();
|
||||
|
||||
//last >>= 1;
|
||||
//last |= (!bit as u8) << 7;
|
||||
let bit = elg_sample > 0.;
|
||||
if elg_sample * elg_sample > 0.005 {
|
||||
last >>= 1;
|
||||
last |= (bit as u8) << 7;
|
||||
//last <<= 1;
|
||||
//last |= ((bit) as u8);
|
||||
|
||||
if preamble_count >= 2 {
|
||||
bit_index += 1;
|
||||
if bit_index >= 8 {
|
||||
if last == 4 {
|
||||
print!(" -- EOT");
|
||||
println!();
|
||||
preamble_count = 0;
|
||||
bit_index = 0;
|
||||
last = 0;
|
||||
} else {
|
||||
print!("{}", last as char);
|
||||
bit_index = 0;
|
||||
let _ = stdout().flush();
|
||||
}
|
||||
}
|
||||
} else if last == 0xD8 {
|
||||
preamble_count += 1;
|
||||
if preamble_count == 2 {
|
||||
println!("Incoming: ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#[derive(Default)]
|
||||
struct EguiApp {
|
||||
eye_rx: Receiver<Vec<f32>>,
|
||||
eyes: VecDeque<Vec<f32>>,
|
||||
}
|
||||
|
||||
impl EguiApp {
|
||||
fn new(
|
||||
cc: &eframe::CreationContext<'_>,
|
||||
eye_rx: Receiver<Vec<f32>>,
|
||||
ctx_tx: Sender<Arc<egui::Context>>,
|
||||
) -> Self {
|
||||
ctx_tx.send(Arc::new(cc.egui_ctx.clone())).unwrap();
|
||||
|
||||
Self {
|
||||
eye_rx,
|
||||
eyes: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for EguiApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
let max_eyes = 100;
|
||||
|
||||
while let Ok(eye) = self.eye_rx.try_recv() {
|
||||
self.eyes.push_back(eye);
|
||||
}
|
||||
|
||||
while self.eyes.len() > max_eyes {
|
||||
self.eyes.pop_front();
|
||||
}
|
||||
|
||||
let axis_hints = AxisHints::new_x().min_thickness(2.);
|
||||
|
||||
Plot::new("Eye")
|
||||
.legend(Legend::default())
|
||||
.show_axes(Vec2b::TRUE)
|
||||
.custom_x_axes(vec![axis_hints])
|
||||
.show(ui, |plot_ui| {
|
||||
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
|
||||
plot_ui.hline(HLine::new("", 0.).color(Color32::DARK_RED).width(2.));
|
||||
for eye in self.eyes.iter() {
|
||||
let line = Line::new(
|
||||
"Eye",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::LIGHT_GREEN);
|
||||
plot_ui.line(line);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn demodulate() {
|
||||
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
|
||||
let samples = reader.samples::<i16>();
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
let iq = samples
|
||||
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
|
||||
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
|
||||
let corellator_pos = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_neg.step();
|
||||
nco_neg.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
|
||||
let mut dem = vec![];
|
||||
|
||||
for x in &iq {
|
||||
let pos = matched_filter_pos.next(x.clone());
|
||||
let neg = matched_filter_neg.next(*x);
|
||||
dem.push(pos.mag() - neg.mag());
|
||||
}
|
||||
|
||||
// Symbol recovery
|
||||
|
||||
let mut bits = vec![];
|
||||
let delta = 0.5;
|
||||
let alpha = 0.01;
|
||||
let mut current_sps = sample_per_symbol as f32;
|
||||
let mut current_position = current_sps / 2.;
|
||||
|
||||
while current_position < dem.len() as f32 {
|
||||
// Sample before after
|
||||
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
|
||||
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
|
||||
if late_id as usize >= dem.len() {
|
||||
break;
|
||||
}
|
||||
let early = dem[early_id as usize];
|
||||
let late = dem[late_id as usize];
|
||||
let error = early * early - late * late;
|
||||
current_sps -= alpha * error;
|
||||
|
||||
bits.push(dem[current_position.floor() as usize] > 0.);
|
||||
|
||||
current_position += current_sps;
|
||||
}
|
||||
|
||||
//assert!(bits.len() % 8 == 0);
|
||||
|
||||
let mut out_file = File::create("out.txt").unwrap();
|
||||
let mut strip = 0;
|
||||
|
||||
let bit_slice = bits.as_slice();
|
||||
for i in 0..100 {
|
||||
let byte = bits_to_byte(&bit_slice[(i as usize)..(i as usize + 8)]);
|
||||
if byte == 0b01010111u8 {
|
||||
strip = i + 8;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..strip {
|
||||
bits.remove(i as usize);
|
||||
}
|
||||
for x in bits.chunks(8) {
|
||||
if x.len() != 8 {
|
||||
break;
|
||||
}
|
||||
out_file.write_all(&[bits_to_byte(x)]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn modulate() {
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameter
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_output_device().unwrap();
|
||||
let mut supported_configs_range = device.supported_output_configs().unwrap();
|
||||
let supported_config = supported_configs_range
|
||||
.find(|config| {
|
||||
config.sample_format() == cpal::SampleFormat::F32
|
||||
&& config.min_sample_rate().0 <= 48000
|
||||
&& config.max_sample_rate().0 >= 48000
|
||||
})
|
||||
.expect("Device does not support 48kHz f32 output");
|
||||
let config = supported_config
|
||||
.with_sample_rate(cpal::SampleRate(48_000))
|
||||
.config();
|
||||
|
||||
loop {
|
||||
let mut buffer = String::new();
|
||||
let stdin = io::stdin(); // We get `Stdin` here.
|
||||
stdin.read_line(&mut buffer).unwrap();
|
||||
|
||||
for c in buffer.bytes() {
|
||||
print!("{}", c as char);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Construct payload
|
||||
let mut bitstream = std::iter::repeat_n(0b01010101u8, 64)
|
||||
.chain(std::iter::repeat_n(0xD8, 2))
|
||||
.chain(buffer.bytes())
|
||||
.chain(std::iter::repeat_n(4u8, 32))
|
||||
.flat_map(byte_to_bits);
|
||||
|
||||
let mut modulator = BFSKMod::new(
|
||||
sample_rate / baud_rate,
|
||||
units::frequency::hz_to_rad_per_sample(deviation, sample_rate as f32),
|
||||
&mut bitstream,
|
||||
);
|
||||
|
||||
let mut lo = Nco::new(units::frequency::hz_to_rad_per_sample(
|
||||
frequency,
|
||||
sample_rate as f32,
|
||||
));
|
||||
|
||||
// To send
|
||||
let stream = modulator
|
||||
.zip(lo)
|
||||
.map(|(s, up)| (s * up).re)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let sample_clock = Arc::new(AtomicU32::new(0));
|
||||
let (tx, rx) = channel::<()>();
|
||||
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&config,
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
for d in data.iter_mut() {
|
||||
if sample_clock.load(Ordering::Relaxed) as usize == stream.len() {
|
||||
tx.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
*d = stream[sample_clock.fetch_add(1, Ordering::Relaxed) as usize]
|
||||
}
|
||||
},
|
||||
move |err| {
|
||||
eprintln!("Stream error: {}", err);
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
stream.play().unwrap();
|
||||
let _ = rx.recv();
|
||||
stream.pause().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_to_bits(byte: u8) -> Vec<bool> {
|
||||
vec![
|
||||
byte & 1 == 1,
|
||||
(byte >> 1) & 1 == 1,
|
||||
(byte >> 2) & 1 == 1,
|
||||
(byte >> 3) & 1 == 1,
|
||||
(byte >> 4) & 1 == 1,
|
||||
(byte >> 5) & 1 == 1,
|
||||
(byte >> 6) & 1 == 1,
|
||||
(byte >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
/*
|
||||
fn bits_to_byte(bits: &[bool]) -> u8 {
|
||||
bits[7] as u8 |
|
||||
bits[6] as u8 >> 1 |
|
||||
bits[5] as u8 >> 2 |
|
||||
bits[4] as u8 >> 3 |
|
||||
bits[3] as u8 >> 4 |
|
||||
bits[2] as u8 >> 5 |
|
||||
bits[1] as u8 >> 6 |
|
||||
bits[0] as u8 >> 7
|
||||
}
|
||||
*/
|
||||
|
||||
fn bits_to_byte(bits: &[bool]) -> u8 {
|
||||
bits[0] as u8
|
||||
| (bits[1] as u8) << 1
|
||||
| (bits[2] as u8) << 2
|
||||
| (bits[3] as u8) << 3
|
||||
| (bits[4] as u8) << 4
|
||||
| (bits[5] as u8) << 5
|
||||
| (bits[6] as u8) << 6
|
||||
| (bits[7] as u8) << 7
|
||||
}
|
||||
424
main.rs.mt
424
main.rs.mt
@ -1,424 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
fs::File,
|
||||
i16,
|
||||
io::{Read, Write},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
};
|
||||
|
||||
mod bfsk;
|
||||
mod complex;
|
||||
pub mod fft;
|
||||
mod filtering;
|
||||
mod iq;
|
||||
mod nco;
|
||||
mod signal;
|
||||
mod units;
|
||||
mod windows;
|
||||
|
||||
use bfsk::BFSKMod;
|
||||
use complex::Complex;
|
||||
use complex::Complex32;
|
||||
use fft::DFTAlgorithm;
|
||||
use nco::Nco;
|
||||
|
||||
use eframe::egui::{self, Color32, Context, debug_text::print};
|
||||
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints, VLine};
|
||||
use plotters::style::Color;
|
||||
|
||||
use crate::{
|
||||
bfsk::BFSKDem,
|
||||
fft::{FFT, dft::NaiveDFT},
|
||||
filtering::{
|
||||
fir::FIRFilter,
|
||||
impulse_response::design::{self, frequency_response, ir_from_transfer_function},
|
||||
},
|
||||
iq::IQSampler,
|
||||
units::frequency::{self, hz_to_rad_per_sample},
|
||||
};
|
||||
|
||||
// Utilities
|
||||
fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
|
||||
where
|
||||
T: Clone + Add<Output = T> + Mul<Output = T> + Sub<Output = T> + Div<Output = T>,
|
||||
{
|
||||
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
|
||||
}
|
||||
|
||||
fn main() {
|
||||
modulate();
|
||||
println!("Demodulating");
|
||||
demodulate();
|
||||
|
||||
return;
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
let _ = eframe::run_native(
|
||||
"Egui",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EguiApp {
|
||||
samples: Vec<f32>,
|
||||
|
||||
iq: Vec<Complex32>,
|
||||
dem: Vec<f32>,
|
||||
vlines: Vec<f32>,
|
||||
elg_sampling: Vec<f32>,
|
||||
eye_diagram: Vec<Vec<f32>>,
|
||||
}
|
||||
const BAUD_RATE: u32 = 3200;
|
||||
|
||||
impl EguiApp {
|
||||
fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
|
||||
let samples = reader.samples::<i16>();
|
||||
let sample_count = 10_000;
|
||||
let input_test = samples
|
||||
.take(sample_count)
|
||||
.map(|x| x.unwrap() as f32 / i16::MAX as f32)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
let vlines = (0..sample_count)
|
||||
.filter(|i| i % sample_per_symbol as usize == 0)
|
||||
.map(|x| x as f32 / sample_count as f32)
|
||||
.collect();
|
||||
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
let iq = input_test
|
||||
.iter()
|
||||
.map(|x| iq_sampler.sample(*x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
|
||||
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
|
||||
let corellator_pos = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_neg.step();
|
||||
nco_neg.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
|
||||
let mut dem = vec![];
|
||||
|
||||
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 1]);
|
||||
for x in &iq {
|
||||
let pos = matched_filter_pos.next(x.clone());
|
||||
let neg = matched_filter_neg.next(*x);
|
||||
dem.push(
|
||||
loop_filter
|
||||
.next(Complex32::new(pos.mag() - neg.mag(), 0.))
|
||||
.re,
|
||||
);
|
||||
}
|
||||
|
||||
// Symbol recovery
|
||||
|
||||
let mut sample_ids = vec![];
|
||||
let delta = 0.5;
|
||||
let alpha = 0.01;
|
||||
let mut current_sps = sample_per_symbol as f32;
|
||||
let mut current_position = current_sps / 2.;
|
||||
|
||||
let mut eye_diagram = vec![];
|
||||
while current_position < dem.len() as f32 {
|
||||
// Sample before after
|
||||
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
|
||||
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
|
||||
if late_id as usize >= dem.len() {
|
||||
break;
|
||||
}
|
||||
let early = dem[early_id as usize];
|
||||
let late = dem[late_id as usize];
|
||||
let error = early * early - late * late;
|
||||
current_sps -= alpha * error;
|
||||
|
||||
sample_ids.push(current_position);
|
||||
|
||||
let eye = ((current_position - current_sps).max(0.) as usize
|
||||
..(current_position + current_sps).min(sample_count as f32) as usize)
|
||||
.map(|i| dem[i])
|
||||
.collect();
|
||||
|
||||
eye_diagram.push(eye);
|
||||
|
||||
current_position += current_sps;
|
||||
}
|
||||
|
||||
let elg_sampling = sample_ids
|
||||
.iter()
|
||||
.map(|x| *x / sample_count as f32)
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
samples: input_test.clone(),
|
||||
iq,
|
||||
dem,
|
||||
vlines,
|
||||
elg_sampling,
|
||||
eye_diagram,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for EguiApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Hello World!");
|
||||
Plot::new("Fourrier transform")
|
||||
.legend(Legend::default())
|
||||
.show(ui, |plot_ui| {
|
||||
for (i, eye) in self.eye_diagram.iter().enumerate().skip(1) {
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"eye",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64 / eye.len() as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.width(1.)
|
||||
.color(Color32::RED),
|
||||
);
|
||||
}
|
||||
|
||||
self.vlines.iter().for_each(|l| {
|
||||
plot_ui.vline(
|
||||
VLine::new("Boundaries", *l as f64)
|
||||
.color(Color32::LIGHT_BLUE)
|
||||
.width(0.5)
|
||||
.style(LineStyle::dashed_dense()),
|
||||
);
|
||||
});
|
||||
|
||||
self.elg_sampling.iter().for_each(|l| {
|
||||
plot_ui.vline(
|
||||
VLine::new("ELG", *l as f64)
|
||||
.color(Color32::RED)
|
||||
.width(0.5)
|
||||
.style(LineStyle::dotted_dense()),
|
||||
);
|
||||
});
|
||||
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"Passband",
|
||||
self.samples
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64 / self.samples.len() as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.width(2.),
|
||||
);
|
||||
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"Demodulated",
|
||||
self.dem
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64 / self.iq.len() as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.width(2.),
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn demodulate() {
|
||||
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
|
||||
let samples = reader.samples::<i16>();
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
let iq = samples
|
||||
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
|
||||
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
|
||||
let corellator_pos = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_neg.step();
|
||||
nco_neg.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
|
||||
let mut dem = vec![];
|
||||
|
||||
for x in &iq {
|
||||
let pos = matched_filter_pos.next(x.clone());
|
||||
let neg = matched_filter_neg.next(*x);
|
||||
dem.push(pos.mag() - neg.mag());
|
||||
}
|
||||
|
||||
// Symbol recovery
|
||||
|
||||
let mut bits = vec![];
|
||||
let delta = 0.5;
|
||||
let alpha = 0.01;
|
||||
let mut current_sps = sample_per_symbol as f32;
|
||||
let mut current_position = current_sps / 2.;
|
||||
|
||||
while current_position < dem.len() as f32 {
|
||||
// Sample before after
|
||||
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
|
||||
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
|
||||
if late_id as usize >= dem.len() {
|
||||
break;
|
||||
}
|
||||
let early = dem[early_id as usize];
|
||||
let late = dem[late_id as usize];
|
||||
let error = early * early - late * late;
|
||||
current_sps -= alpha * error;
|
||||
|
||||
bits.push(dem[current_position.floor() as usize] > 0.);
|
||||
|
||||
current_position += current_sps;
|
||||
}
|
||||
|
||||
//assert!(bits.len() % 8 == 0);
|
||||
|
||||
let mut out_file = File::create("out.txt").unwrap();
|
||||
let mut strip = 0;
|
||||
|
||||
let bit_slice = bits.as_slice();
|
||||
for i in 0..100 {
|
||||
let byte = bits_to_byte(&bit_slice[(i as usize)..(i as usize + 8)]);
|
||||
if byte == 0b01010111u8 {
|
||||
strip = i + 8;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..strip {
|
||||
bits.remove(i as usize);
|
||||
}
|
||||
for x in bits.chunks(8) {
|
||||
if x.len() != 8 {
|
||||
break;
|
||||
}
|
||||
out_file.write_all(&[bits_to_byte(x)]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn modulate() {
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameter
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
|
||||
// File to modulate
|
||||
let f = File::open("s.txt").unwrap();
|
||||
let mut bitstream = std::iter::repeat_n(0b01010101u8, 1)
|
||||
.chain(std::iter::repeat_n(0b01010111u8, 1))
|
||||
.chain(f.bytes().map(|x| x.unwrap()))
|
||||
.chain(std::iter::repeat_n(0u8, 1))
|
||||
.flat_map(byte_to_bits);
|
||||
|
||||
let mut modulator = BFSKMod::new(
|
||||
sample_rate / baud_rate,
|
||||
units::frequency::hz_to_rad_per_sample(deviation, sample_rate as f32),
|
||||
&mut bitstream,
|
||||
);
|
||||
|
||||
let mut lo = Nco::new(units::frequency::hz_to_rad_per_sample(
|
||||
frequency,
|
||||
sample_rate as f32,
|
||||
));
|
||||
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut writer = hound::WavWriter::create("audio/modulated.wav", spec).unwrap();
|
||||
for (s, up) in modulator.zip(lo) {
|
||||
let sample = (s * up).re; // Project to I coords
|
||||
let amplitude = i16::MAX as f32;
|
||||
writer.write_sample((sample * amplitude) as i16).unwrap();
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
}
|
||||
|
||||
fn byte_to_bits(byte: u8) -> Vec<bool> {
|
||||
vec![
|
||||
byte & 1 == 1,
|
||||
(byte >> 1) & 1 == 1,
|
||||
(byte >> 2) & 1 == 1,
|
||||
(byte >> 3) & 1 == 1,
|
||||
(byte >> 4) & 1 == 1,
|
||||
(byte >> 5) & 1 == 1,
|
||||
(byte >> 6) & 1 == 1,
|
||||
(byte >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
/*
|
||||
fn bits_to_byte(bits: &[bool]) -> u8 {
|
||||
bits[7] as u8 |
|
||||
bits[6] as u8 >> 1 |
|
||||
bits[5] as u8 >> 2 |
|
||||
bits[4] as u8 >> 3 |
|
||||
bits[3] as u8 >> 4 |
|
||||
bits[2] as u8 >> 5 |
|
||||
bits[1] as u8 >> 6 |
|
||||
bits[0] as u8 >> 7
|
||||
}
|
||||
*/
|
||||
|
||||
fn bits_to_byte(bits: &[bool]) -> u8 {
|
||||
bits[0] as u8
|
||||
| (bits[1] as u8) << 1
|
||||
| (bits[2] as u8) << 2
|
||||
| (bits[3] as u8) << 3
|
||||
| (bits[4] as u8) << 4
|
||||
| (bits[5] as u8) << 5
|
||||
| (bits[6] as u8) << 6
|
||||
| (bits[7] as u8) << 7
|
||||
}
|
||||
@ -1,11 +1,8 @@
|
||||
// 2-FSK Modulator
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use crate::complex::{Complex, Complex32};
|
||||
use crate::fft::{self, FFT, FFTDirection};
|
||||
use crate::{units, windows};
|
||||
use crate::map;
|
||||
use crate::windows;
|
||||
use crate::nco::Nco;
|
||||
|
||||
pub struct BFSKMod<'a, T: Iterator<Item = bool>> {
|
||||
|
||||
@ -2,7 +2,7 @@ use std::{
|
||||
f32::consts::PI,
|
||||
fmt::Display,
|
||||
iter::Sum,
|
||||
ops::{Add, Deref, Div, Mul, Neg, Sub},
|
||||
ops::{Add, Div, Mul, Neg, Sub},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
||||
@ -4,7 +4,7 @@ use std::f32::consts::PI;
|
||||
|
||||
use crate::{
|
||||
complex::Complex32,
|
||||
fft::{DFTAlgorithm, FFTDirection, create_fft, dft::NaiveDFT, prime_factors, windows},
|
||||
fft::{DFTAlgorithm, FFTDirection, create_fft, prime_factors},
|
||||
};
|
||||
|
||||
pub struct MixedRadixFFT {
|
||||
|
||||
@ -5,7 +5,7 @@ use std::f32::consts::PI;
|
||||
use crate::{
|
||||
complex::Complex32,
|
||||
fft::{
|
||||
DFTAlgorithm, FFTDirection, create_fft, dft::NaiveDFT, is_prime, mixed_radix::MixedRadixFFT,
|
||||
create_fft, is_prime, DFTAlgorithm, FFTDirection
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ use std::f32::consts::PI;
|
||||
use crate::{
|
||||
complex::Complex32,
|
||||
fft::{
|
||||
DFTAlgorithm, FFTDirection, create_fft, dft::NaiveDFT, is_prime, mixed_radix::MixedRadixFFT,
|
||||
create_fft, is_prime, DFTAlgorithm, FFTDirection
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
// Finite impulse response filters
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{complex::Complex32, filtering::impulse_response};
|
||||
use std::{collections::VecDeque, f32::consts::PI};
|
||||
use crate::complex::Complex32;
|
||||
|
||||
pub struct FIRFilter {
|
||||
size: usize,
|
||||
@ -29,11 +28,24 @@ impl FIRFilter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_sum(&mut self)
|
||||
// Normalizes FIR Filter such that DC is at unity gain
|
||||
pub fn normalize_dc(&mut self)
|
||||
{
|
||||
self.normalization = self.impulse_response.iter().copied().sum::<Complex32>().mag()
|
||||
}
|
||||
|
||||
// Normalizes FIR Filter such that frequency is at unity gain
|
||||
pub fn normalize_freq(&mut self, frequency: f32)
|
||||
{
|
||||
// Perform DTFT of specified frequency
|
||||
let dft: Complex32 = self.impulse_response
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| *x * Complex32::cexp(- 2. * PI * i as f32 * frequency / self.impulse_response.len() as f32))
|
||||
.sum();
|
||||
self.normalization = dft.mag();
|
||||
}
|
||||
|
||||
pub fn next(&mut self, next: Complex32) -> Complex32 {
|
||||
let _ = self.taps.pop_front();
|
||||
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
// Utilities for impulse response design
|
||||
|
||||
use crate::complex::Complex32;
|
||||
|
||||
pub mod design
|
||||
{
|
||||
use crate::fft::FFT;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use crate::{complex::Complex32, filtering::fir::FIRFilter, map, nco::Nco, windows};
|
||||
use crate::{complex::Complex32, filtering::fir::FIRFilter, math::map, nco::Nco, windows};
|
||||
|
||||
pub struct IQSampler
|
||||
{
|
||||
@ -28,8 +27,8 @@ impl IQSampler
|
||||
let ir = crate::filtering::impulse_response::design::ir_from_transfer_function(&transfer_function, fir_length, windows::blackmann);
|
||||
let mut low_pass_i = FIRFilter::new(&ir);
|
||||
let mut low_pass_q = FIRFilter::new(&ir);
|
||||
low_pass_i.normalize_sum();
|
||||
low_pass_q.normalize_sum();
|
||||
low_pass_i.normalize_dc();
|
||||
low_pass_q.normalize_dc();
|
||||
|
||||
IQSampler {
|
||||
local_oscillator: Nco::new(center_freq),
|
||||
|
||||
585
src/main.rs
585
src/main.rs
@ -1,557 +1,107 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
f32::consts::PI,
|
||||
fs::File,
|
||||
i16,
|
||||
io::{self, Read, Write, stdout},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
os::unix::thread,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
mpsc::{self, Receiver, Sender, TryRecvError, channel, sync_channel},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod bfsk;
|
||||
mod complex;
|
||||
pub mod fft;
|
||||
mod filtering;
|
||||
mod iq;
|
||||
mod nco;
|
||||
mod signal;
|
||||
mod units;
|
||||
mod windows;
|
||||
mod ted;
|
||||
mod math;
|
||||
|
||||
use bfsk::BFSKMod;
|
||||
use complex::Complex;
|
||||
use complex::Complex32;
|
||||
use cpal::{
|
||||
SampleRate,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use fft::DFTAlgorithm;
|
||||
use nco::Nco;
|
||||
use cpal::Data;
|
||||
use eframe::egui;
|
||||
|
||||
use eframe::{
|
||||
egui::{self, Color32, Context, Vec2b, debug_text::print, decode_animated_image_uri},
|
||||
glow::TOP_LEVEL_ARRAY_STRIDE,
|
||||
};
|
||||
use egui_plot::{
|
||||
self, AxisHints, Bar, BarChart, HLine, Legend, Line, LineStyle, Plot, PlotPoints, VLine,
|
||||
};
|
||||
use plotters::style::Color;
|
||||
use rand::{Rng, seq::index::sample};
|
||||
const BAUD_RATE: u32 = 1000;
|
||||
|
||||
use crate::{
|
||||
bfsk::BFSKDem,
|
||||
fft::{FFT, dft::NaiveDFT},
|
||||
filtering::{
|
||||
dc_block::DCBlocker,
|
||||
fir::FIRFilter,
|
||||
impulse_response::design::{self, frequency_response, ir_from_transfer_function},
|
||||
},
|
||||
iq::IQSampler,
|
||||
units::frequency::{self, hz_to_rad_per_sample},
|
||||
};
|
||||
|
||||
// Utilities
|
||||
fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
|
||||
where
|
||||
T: Clone + Add<Output = T> + Mul<Output = T> + Sub<Output = T> + Div<Output = T>,
|
||||
enum TransceiverState
|
||||
{
|
||||
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
|
||||
Idle,
|
||||
Receiving,
|
||||
Transmitting,
|
||||
}
|
||||
|
||||
enum Frame
|
||||
{
|
||||
Data(Vec<u8>),
|
||||
Ack
|
||||
}
|
||||
|
||||
impl Frame
|
||||
{
|
||||
pub fn bytes(&self) -> Vec<u8>
|
||||
{
|
||||
let mut output_bytes = vec![];
|
||||
|
||||
// Initial training sequence
|
||||
output_bytes.append(&mut vec![0b01010101; 64]);
|
||||
|
||||
// Preamble byte
|
||||
output_bytes.push(0xD8);
|
||||
|
||||
// Command
|
||||
match self
|
||||
{
|
||||
Frame::Data(x) =>
|
||||
{
|
||||
let mut checksum = 0u8;
|
||||
x.iter().for_each(|x| checksum ^= x);
|
||||
|
||||
assert!(x.len() < 65536, "Data size over MTU");
|
||||
let len_u16 = x.len() as u16;
|
||||
output_bytes.push((len_u16 & 0xFF).try_into().unwrap());
|
||||
output_bytes.push(((len_u16 >> 8) & 0xFF).try_into().unwrap());
|
||||
|
||||
output_bytes.extend(x.iter());
|
||||
|
||||
output_bytes.push(checksum);
|
||||
}
|
||||
Frame::Ack =>
|
||||
{
|
||||
output_bytes.push(0xC4);
|
||||
}
|
||||
}
|
||||
|
||||
// SEND EOT
|
||||
output_bytes.extend(std::iter::repeat_n(4, 32));
|
||||
output_bytes
|
||||
}
|
||||
}
|
||||
const BAUD_RATE: u32 = 1200;
|
||||
|
||||
fn main() {
|
||||
//modulate();
|
||||
//demodulate();
|
||||
//return;
|
||||
// Set up CPAL
|
||||
let host = cpal::default_host();
|
||||
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.expect("No input device available");
|
||||
let mut config = device
|
||||
.supported_input_configs()
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_sample_rate(SampleRate(48000));
|
||||
|
||||
// Channel to move samples from callback to main thread
|
||||
let (tx, rx) = sync_channel::<f32>(4096);
|
||||
|
||||
// Build input stream
|
||||
let stream = device
|
||||
.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[f32], _| {
|
||||
for x in data.iter() {
|
||||
let _ = tx.send(*x * 30.); // non-blocking send
|
||||
}
|
||||
},
|
||||
move |err| eprintln!("Stream error: {}", err),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
stream.play().unwrap();
|
||||
|
||||
let (eye_tx, eye_rx) = mpsc::channel::<Vec<f32>>();
|
||||
let (ctx_tx, ctx_rx) = mpsc::channel::<Arc<egui::Context>>();
|
||||
std::thread::spawn(move || demodulator(rx, eye_tx, ctx_rx));
|
||||
|
||||
/*
|
||||
std::thread::spawn(move || {
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: 48000,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut writer = hound::WavWriter::create("audio/noised.wav", spec).unwrap();
|
||||
|
||||
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
|
||||
let mut rand = rand::rng();
|
||||
let samples = reader.samples::<i16>();
|
||||
for x in samples {
|
||||
let noise = rand.random::<f32>() * 2. - 1.;
|
||||
let sample = x.unwrap() as f32 / i16::MAX as f32 + noise * 0.9;
|
||||
let _ = tx.send(sample);
|
||||
writer
|
||||
.write_sample((sample * i16::MAX as f32) as i16)
|
||||
.unwrap();
|
||||
std::thread::sleep(Duration::from_micros(21));
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
});
|
||||
*/
|
||||
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
let _ = eframe::run_native(
|
||||
"Egui",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(EguiApp::new(cc, eye_rx, ctx_tx)))),
|
||||
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
|
||||
);
|
||||
}
|
||||
|
||||
// Early late gate
|
||||
struct ELGate {
|
||||
samples_per_symbol: f32,
|
||||
buffer: VecDeque<f32>, // Store baseband, matched filtered samples,
|
||||
|
||||
loop_filter: FIRFilter,
|
||||
delta: f32,
|
||||
next_sample: f32,
|
||||
current_position: f32,
|
||||
}
|
||||
|
||||
impl ELGate {
|
||||
pub fn new(samples_per_symbol: f32, loop_filter: FIRFilter) -> Self {
|
||||
Self {
|
||||
samples_per_symbol,
|
||||
loop_filter,
|
||||
buffer: VecDeque::with_capacity(2 * samples_per_symbol.ceil() as usize),
|
||||
delta: 0.5,
|
||||
next_sample: samples_per_symbol,
|
||||
current_position: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self, sample: f32) -> Option<f32> {
|
||||
Some(self.next_eye(sample)?.0) // Ignore eye
|
||||
}
|
||||
|
||||
pub fn next_eye(&mut self, sample: f32) -> Option<(f32, Vec<f32>)> {
|
||||
self.buffer.push_front(sample);
|
||||
self.current_position += 1.;
|
||||
if self.current_position >= self.next_sample {
|
||||
// Sample center, early late
|
||||
let early_id = (self.samples_per_symbol / 2. + self.samples_per_symbol * self.delta)
|
||||
.floor()
|
||||
.min(self.buffer.len() as f32 - 1.) as usize;
|
||||
let late_id = (self.samples_per_symbol / 2. - self.samples_per_symbol * self.delta)
|
||||
.floor()
|
||||
.max(0.) as usize;
|
||||
let sample_id = (self.samples_per_symbol / 2.) as usize;
|
||||
|
||||
let early_sample = self.buffer[early_id];
|
||||
let late_sample = self.buffer[late_id];
|
||||
let sample = self.buffer[sample_id];
|
||||
|
||||
let error = (early_sample - late_sample) * sample;
|
||||
|
||||
// Remove until only current sample is in buffer (the next sample might need data from
|
||||
// current sample if error advances sample position)
|
||||
while self.buffer.len() > self.samples_per_symbol as usize {
|
||||
let _ = self.buffer.pop_back();
|
||||
}
|
||||
|
||||
// Predict next length based on error
|
||||
self.current_position = self.next_sample - self.next_sample.floor();
|
||||
self.next_sample = self.samples_per_symbol - self.loop_filter.next_real(error);
|
||||
|
||||
Some((sample, Vec::from(self.buffer.clone())))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn demodulator(
|
||||
rx: Receiver<f32>,
|
||||
eye_sender: Sender<Vec<f32>>,
|
||||
ctx_rx: Receiver<Arc<egui::Context>>,
|
||||
) {
|
||||
// Wait for egui context to request redraw
|
||||
let ctx = ctx_rx.recv().unwrap();
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
|
||||
// Corellators
|
||||
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
|
||||
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
|
||||
let corellator_length = (sample_per_symbol as f32 * 1.5) as usize;
|
||||
let corellator_pos = (0..corellator_length)
|
||||
.map(|i| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp() * windows::blackmann(i as f32 / (corellator_length as f32))
|
||||
//nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..corellator_length)
|
||||
.map(|i| {
|
||||
nco_neg.step();
|
||||
nco_neg.cexp() * windows::blackmann(i as f32 / (corellator_length as f32))
|
||||
//nco_neg.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
matched_filter_pos.normalize_sum();
|
||||
matched_filter_neg.normalize_sum();
|
||||
|
||||
let loop_p = 0.003;
|
||||
let loop_i = 0.001;
|
||||
let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 20]);
|
||||
matched_filter.normalize_sum();
|
||||
|
||||
let mut loop_filter_ir = vec![Complex32::new(loop_i, 0.); 30];
|
||||
let len = loop_filter_ir.len();
|
||||
//loop_filter_ir[0] = Complex32::new(loop_p + loop_i, 0.);
|
||||
loop_filter_ir[len - 1] = Complex32::new(loop_p + loop_i, 0.);
|
||||
|
||||
let mut loop_filter = FIRFilter::new(&loop_filter_ir);
|
||||
//loop_filter.normalize_sum();
|
||||
let mut elg = ELGate::new(sample_per_symbol as f32, loop_filter);
|
||||
|
||||
let mut dc_blocker = DCBlocker::new(0.999);
|
||||
|
||||
// Timing recovery
|
||||
let mut preamble_count = 0;
|
||||
let mut bit_index = 0;
|
||||
let mut last = 0u8;
|
||||
while let Ok(real_sample) = rx.recv() {
|
||||
let iq = iq_sampler.sample(real_sample);
|
||||
|
||||
// Perform corellation
|
||||
let neg_energy = matched_filter_neg.next(iq);
|
||||
let pos_energy = matched_filter_pos.next(iq);
|
||||
|
||||
let matched =
|
||||
matched_filter.next_real(dc_blocker.next_real(pos_energy.mag() - neg_energy.mag()));
|
||||
|
||||
if let Some((elg_sample, eye)) = elg.next_eye(matched) {
|
||||
let _ = eye_sender.send(eye);
|
||||
ctx.request_repaint();
|
||||
|
||||
//last >>= 1;
|
||||
//last |= (!bit as u8) << 7;
|
||||
let bit = elg_sample > 0.;
|
||||
if elg_sample * elg_sample > 0.005 {
|
||||
last >>= 1;
|
||||
last |= (bit as u8) << 7;
|
||||
//last <<= 1;
|
||||
//last |= ((bit) as u8);
|
||||
|
||||
if preamble_count >= 2 {
|
||||
bit_index += 1;
|
||||
if bit_index >= 8 {
|
||||
if last == 4 {
|
||||
print!(" -- EOT");
|
||||
println!();
|
||||
preamble_count = 0;
|
||||
bit_index = 0;
|
||||
last = 0;
|
||||
} else {
|
||||
print!("{}", last as char);
|
||||
bit_index = 0;
|
||||
let _ = stdout().flush();
|
||||
}
|
||||
}
|
||||
} else if last == 0xD8 {
|
||||
preamble_count += 1;
|
||||
if preamble_count == 2 {
|
||||
println!("Incoming: ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#[derive(Default)]
|
||||
struct EguiApp {
|
||||
eye_rx: Receiver<Vec<f32>>,
|
||||
eyes: VecDeque<Vec<f32>>,
|
||||
}
|
||||
|
||||
impl EguiApp {
|
||||
fn new(
|
||||
cc: &eframe::CreationContext<'_>,
|
||||
eye_rx: Receiver<Vec<f32>>,
|
||||
ctx_tx: Sender<Arc<egui::Context>>,
|
||||
_cc: &eframe::CreationContext<'_>,
|
||||
) -> Self {
|
||||
ctx_tx.send(Arc::new(cc.egui_ctx.clone())).unwrap();
|
||||
|
||||
Self {
|
||||
eye_rx,
|
||||
eyes: VecDeque::new(),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for EguiApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
let max_eyes = 100;
|
||||
|
||||
while let Ok(eye) = self.eye_rx.try_recv() {
|
||||
self.eyes.push_back(eye);
|
||||
}
|
||||
|
||||
while self.eyes.len() > max_eyes {
|
||||
self.eyes.pop_front();
|
||||
}
|
||||
|
||||
let axis_hints = AxisHints::new_x().min_thickness(2.);
|
||||
|
||||
Plot::new("Eye")
|
||||
.legend(Legend::default())
|
||||
.show_axes(Vec2b::TRUE)
|
||||
.custom_x_axes(vec![axis_hints])
|
||||
.show(ui, |plot_ui| {
|
||||
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
|
||||
plot_ui.hline(HLine::new("", 0.).color(Color32::DARK_RED).width(2.));
|
||||
for eye in self.eyes.iter() {
|
||||
let line = Line::new(
|
||||
"Eye",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::LIGHT_GREEN);
|
||||
plot_ui.line(line);
|
||||
}
|
||||
})
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |_ui| {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn demodulate() {
|
||||
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
|
||||
let samples = reader.samples::<i16>();
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
let iq = samples
|
||||
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
|
||||
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
|
||||
let corellator_pos = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..(sample_per_symbol * 1))
|
||||
.map(|_| {
|
||||
nco_neg.step();
|
||||
nco_neg.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
|
||||
let mut dem = vec![];
|
||||
|
||||
for x in &iq {
|
||||
let pos = matched_filter_pos.next(x.clone());
|
||||
let neg = matched_filter_neg.next(*x);
|
||||
dem.push(pos.mag() - neg.mag());
|
||||
}
|
||||
|
||||
// Symbol recovery
|
||||
|
||||
let mut bits = vec![];
|
||||
let delta = 0.5;
|
||||
let alpha = 0.01;
|
||||
let mut current_sps = sample_per_symbol as f32;
|
||||
let mut current_position = current_sps / 2.;
|
||||
|
||||
while current_position < dem.len() as f32 {
|
||||
// Sample before after
|
||||
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
|
||||
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
|
||||
if late_id as usize >= dem.len() {
|
||||
break;
|
||||
}
|
||||
let early = dem[early_id as usize];
|
||||
let late = dem[late_id as usize];
|
||||
let error = early * early - late * late;
|
||||
current_sps -= alpha * error;
|
||||
|
||||
bits.push(dem[current_position.floor() as usize] > 0.);
|
||||
|
||||
current_position += current_sps;
|
||||
}
|
||||
|
||||
//assert!(bits.len() % 8 == 0);
|
||||
|
||||
let mut out_file = File::create("out.txt").unwrap();
|
||||
let mut strip = 0;
|
||||
|
||||
let bit_slice = bits.as_slice();
|
||||
for i in 0..100 {
|
||||
let byte = bits_to_byte(&bit_slice[(i as usize)..(i as usize + 8)]);
|
||||
if byte == 0b01010111u8 {
|
||||
strip = i + 8;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..strip {
|
||||
bits.remove(i as usize);
|
||||
}
|
||||
for x in bits.chunks(8) {
|
||||
if x.len() != 8 {
|
||||
break;
|
||||
}
|
||||
out_file.write_all(&[bits_to_byte(x)]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn modulate() {
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
// Data parameter
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_output_device().unwrap();
|
||||
let mut supported_configs_range = device.supported_output_configs().unwrap();
|
||||
let supported_config = supported_configs_range
|
||||
.find(|config| {
|
||||
config.sample_format() == cpal::SampleFormat::F32
|
||||
&& config.min_sample_rate().0 <= 48000
|
||||
&& config.max_sample_rate().0 >= 48000
|
||||
})
|
||||
.expect("Device does not support 48kHz f32 output");
|
||||
let config = supported_config
|
||||
.with_sample_rate(cpal::SampleRate(48_000))
|
||||
.config();
|
||||
|
||||
loop {
|
||||
let mut buffer = String::new();
|
||||
let stdin = io::stdin(); // We get `Stdin` here.
|
||||
stdin.read_line(&mut buffer).unwrap();
|
||||
|
||||
for c in buffer.bytes() {
|
||||
print!("{}", c as char);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Construct payload
|
||||
let mut bitstream = std::iter::repeat_n(0b01010101u8, 64)
|
||||
.chain(std::iter::repeat_n(0xD8, 2))
|
||||
.chain(buffer.bytes())
|
||||
.chain(std::iter::repeat_n(4u8, 32))
|
||||
.flat_map(byte_to_bits);
|
||||
|
||||
let mut modulator = BFSKMod::new(
|
||||
sample_rate / baud_rate,
|
||||
units::frequency::hz_to_rad_per_sample(deviation, sample_rate as f32),
|
||||
&mut bitstream,
|
||||
);
|
||||
|
||||
let mut lo = Nco::new(units::frequency::hz_to_rad_per_sample(
|
||||
frequency,
|
||||
sample_rate as f32,
|
||||
));
|
||||
|
||||
// To send
|
||||
let stream = modulator
|
||||
.zip(lo)
|
||||
.map(|(s, up)| (s * up).re)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let sample_clock = Arc::new(AtomicU32::new(0));
|
||||
let (tx, rx) = channel::<()>();
|
||||
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&config,
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
for d in data.iter_mut() {
|
||||
if sample_clock.load(Ordering::Relaxed) as usize == stream.len() {
|
||||
tx.send(()).unwrap();
|
||||
break;
|
||||
}
|
||||
*d = stream[sample_clock.fetch_add(1, Ordering::Relaxed) as usize]
|
||||
}
|
||||
},
|
||||
move |err| {
|
||||
eprintln!("Stream error: {}", err);
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
stream.play().unwrap();
|
||||
let _ = rx.recv();
|
||||
stream.pause().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_to_bits(byte: u8) -> Vec<bool> {
|
||||
vec![
|
||||
byte & 1 == 1,
|
||||
@ -565,19 +115,6 @@ fn byte_to_bits(byte: u8) -> Vec<bool> {
|
||||
]
|
||||
}
|
||||
|
||||
/*
|
||||
fn bits_to_byte(bits: &[bool]) -> u8 {
|
||||
bits[7] as u8 |
|
||||
bits[6] as u8 >> 1 |
|
||||
bits[5] as u8 >> 2 |
|
||||
bits[4] as u8 >> 3 |
|
||||
bits[3] as u8 >> 4 |
|
||||
bits[2] as u8 >> 5 |
|
||||
bits[1] as u8 >> 6 |
|
||||
bits[0] as u8 >> 7
|
||||
}
|
||||
*/
|
||||
|
||||
fn bits_to_byte(bits: &[bool]) -> u8 {
|
||||
bits[0] as u8
|
||||
| (bits[1] as u8) << 1
|
||||
|
||||
9
src/math.rs
Normal file
9
src/math.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use std::ops::{Add, Mul, Sub, Div};
|
||||
|
||||
// Useful math used throughout
|
||||
pub fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
|
||||
where
|
||||
T: Clone + Add<Output = T> + Mul<Output = T> + Sub<Output = T> + Div<Output = T>,
|
||||
{
|
||||
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
|
||||
}
|
||||
@ -67,7 +67,6 @@ impl Nco {
|
||||
}
|
||||
|
||||
pub fn step(&mut self) {
|
||||
let bef = self.theta as i64;
|
||||
self.theta = self.theta.overflowing_add(self.dtheta).0;
|
||||
}
|
||||
|
||||
|
||||
1
src/ted.rs
Normal file
1
src/ted.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod elg;
|
||||
66
src/ted/elg.rs
Normal file
66
src/ted/elg.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::collections::VecDeque;
|
||||
use crate::filtering::fir::FIRFilter;
|
||||
|
||||
// Crued Early late gate timing error detector
|
||||
pub struct ELGate {
|
||||
samples_per_symbol: f32,
|
||||
buffer: VecDeque<f32>, // Store baseband, matched filtered samples,
|
||||
|
||||
loop_filter: FIRFilter,
|
||||
delta: f32,
|
||||
next_sample: f32,
|
||||
current_position: f32,
|
||||
}
|
||||
|
||||
impl ELGate {
|
||||
pub fn new(samples_per_symbol: f32, loop_filter: FIRFilter) -> Self {
|
||||
Self {
|
||||
samples_per_symbol,
|
||||
loop_filter,
|
||||
buffer: VecDeque::with_capacity(2 * samples_per_symbol.ceil() as usize),
|
||||
delta: 0.5,
|
||||
next_sample: samples_per_symbol,
|
||||
current_position: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self, sample: f32) -> Option<f32> {
|
||||
Some(self.next_eye(sample)?.0) // Ignore eye
|
||||
}
|
||||
|
||||
pub fn next_eye(&mut self, sample: f32) -> Option<(f32, Vec<f32>)> {
|
||||
self.buffer.push_front(sample);
|
||||
self.current_position += 1.;
|
||||
if self.current_position >= self.next_sample {
|
||||
// Sample center, early late
|
||||
let early_id = (self.samples_per_symbol / 2. + self.samples_per_symbol * self.delta)
|
||||
.floor()
|
||||
.min(self.buffer.len() as f32 - 1.) as usize;
|
||||
let late_id = (self.samples_per_symbol / 2. - self.samples_per_symbol * self.delta)
|
||||
.floor()
|
||||
.max(0.) as usize;
|
||||
let sample_id = (self.samples_per_symbol / 2.) as usize;
|
||||
|
||||
let early_sample = self.buffer[early_id];
|
||||
let late_sample = self.buffer[late_id];
|
||||
let sample = self.buffer[sample_id];
|
||||
|
||||
let error = (early_sample - late_sample) * sample;
|
||||
|
||||
// Remove until only current sample is in buffer (the next sample might need data from
|
||||
// current sample if error advances sample position)
|
||||
while self.buffer.len() > self.samples_per_symbol as usize {
|
||||
let _ = self.buffer.pop_back();
|
||||
}
|
||||
|
||||
// Predict next length based on error
|
||||
self.current_position = self.next_sample - self.next_sample.floor();
|
||||
self.next_sample = self.samples_per_symbol - self.loop_filter.next_real(error);
|
||||
|
||||
Some((sample, Vec::from(self.buffer.clone())))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user