Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 347cdc9b0d | |||
| f5ae204c98 | |||
| 8cab34faa0 | |||
| 46c276b5ca | |||
| ef05e2c89e | |||
| 334870e3d6 | |||
| cc434e6ceb | |||
| 01a3b2819e |
32
Cargo.lock
generated
32
Cargo.lock
generated
@ -638,6 +638,12 @@ dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clone_dyn_types"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6df2a33058f975d6138d1eec61d99fc005bac031e3b6ebb3c47b9b1f05dc55"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.12.0"
|
||||
@ -1033,6 +1039,12 @@ dependencies = [
|
||||
"emath",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.32.3"
|
||||
@ -1752,6 +1764,25 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iter_tools"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ea448170950f78f23b4aa55eb87482bd2273c842877810606af6a6542be64d"
|
||||
dependencies = [
|
||||
"clone_dyn_types",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jack"
|
||||
version = "0.13.3"
|
||||
@ -2869,6 +2900,7 @@ dependencies = [
|
||||
"eframe",
|
||||
"egui_plot",
|
||||
"hound",
|
||||
"iter_tools",
|
||||
"plotters",
|
||||
"rand",
|
||||
"tokio",
|
||||
|
||||
@ -8,6 +8,7 @@ cpal = { version = "0.16.0", features = ["jack"] }
|
||||
eframe = "0.32.3"
|
||||
egui_plot = "0.33.0"
|
||||
hound = "3.5.1"
|
||||
iter_tools = "0.39.0"
|
||||
plotters = "0.3.7"
|
||||
rand = "0.9.2"
|
||||
tokio = { version = "1.47.1", features = ["full", "macros", "net", "sync", "time"] }
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ffmpeg -re -i audio/modulated.wav -f s16le -acodec pcm_s16le udp://127.0.0.1:8080
|
||||
@ -1,43 +1,40 @@
|
||||
// Utilities for impulse response design
|
||||
pub mod design
|
||||
{
|
||||
pub mod design {
|
||||
use crate::complex::Complex32;
|
||||
use crate::fft::FFT;
|
||||
use crate::windows::{self, Window};
|
||||
use crate::complex::Complex32;
|
||||
|
||||
///Designs a impulse response from a desired transfer function using windowing technique
|
||||
pub fn ir_from_transfer_function(transfer_function: &[Complex32], ir_length: usize, window: Window) -> Vec<Complex32>
|
||||
{
|
||||
pub fn ir_from_transfer_function(
|
||||
transfer_function: &[Complex32],
|
||||
ir_length: usize,
|
||||
window: Window,
|
||||
) -> Vec<Complex32> {
|
||||
let tf_len = transfer_function.len();
|
||||
let mut ifft = FFT::new_inv(tf_len);
|
||||
|
||||
|
||||
// Compute ideal convolution kernel/impulse response
|
||||
ifft.execute(transfer_function);
|
||||
|
||||
// Shorten and window
|
||||
let mut ir = vec![];
|
||||
|
||||
for i in 0..ir_length
|
||||
{
|
||||
for i in 0..ir_length {
|
||||
// Get value within ifft result (centering/trimming)
|
||||
let k = (tf_len - (ir_length / 2) + i) % tf_len;
|
||||
// Windowing
|
||||
ir.push(
|
||||
ifft.get_output()[k] * window(i as f32 / ir_length as f32) / tf_len as f32
|
||||
);
|
||||
ir.push(ifft.get_output()[k] * window(i as f32 / ir_length as f32) / tf_len as f32);
|
||||
}
|
||||
|
||||
ir
|
||||
}
|
||||
|
||||
pub fn frequency_response(impulse_response: &[Complex32]) -> Vec<Complex32>
|
||||
{
|
||||
pub fn frequency_response(impulse_response: &[Complex32]) -> Vec<Complex32> {
|
||||
let len = impulse_response.len();
|
||||
let mut fft = FFT::new(len, windows::rectangular);
|
||||
// Recenter impulse response
|
||||
let mut centered_ir = vec![];
|
||||
for i in 0..len
|
||||
{
|
||||
let mut centered_ir = vec![];
|
||||
for i in 0..len {
|
||||
let k = (len / 2) + i;
|
||||
centered_ir.push(impulse_response[k % len]);
|
||||
}
|
||||
@ -45,4 +42,10 @@ pub mod design
|
||||
fft.execute(¢ered_ir);
|
||||
Vec::from(fft.get_output())
|
||||
}
|
||||
|
||||
pub fn pi_loop(loop_p: f32, loop_i: f32, length: u32) -> Vec<Complex32> {
|
||||
let mut v = vec![Complex32::new(loop_i, 0.); length as usize];
|
||||
v[(length - 1) as usize] = Complex32::new(loop_p * length as f32, 0.);
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
30
src/iq.rs
30
src/iq.rs
@ -1,30 +1,32 @@
|
||||
use std::f32::consts::PI;
|
||||
use crate::{complex::Complex32, filtering::fir::FIRFilter, math::map, nco::Nco, windows};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
pub struct IQSampler
|
||||
{
|
||||
pub struct IQSampler {
|
||||
local_oscillator: Nco,
|
||||
low_pass_i: FIRFilter,
|
||||
low_pass_q: FIRFilter,
|
||||
}
|
||||
|
||||
impl IQSampler
|
||||
{
|
||||
impl IQSampler {
|
||||
pub fn new(center_freq: f32) -> Self {
|
||||
// Design a lowpass filter that cuts off at the center freq
|
||||
// Estimate FIR length :
|
||||
let fir_length = 50;
|
||||
let fir_length = 100;
|
||||
|
||||
// Ideal transfer function :
|
||||
let mut transfer_function = vec![Complex32::zero(); fir_length];
|
||||
let bin_id = map(center_freq, 0., PI, 0., transfer_function.len() as f32 / 2.).floor() as usize;
|
||||
for i in 0..bin_id
|
||||
{
|
||||
let bin_id =
|
||||
map(center_freq, 0., PI, 0., transfer_function.len() as f32 / 2.).floor() as usize;
|
||||
for i in 0..bin_id {
|
||||
transfer_function[i] = Complex32::new(1., 0.);
|
||||
transfer_function[fir_length - 1 - i] = Complex32::new(1., 0.);
|
||||
}
|
||||
|
||||
let ir = crate::filtering::impulse_response::design::ir_from_transfer_function(&transfer_function, fir_length, windows::blackmann);
|
||||
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_dc();
|
||||
@ -33,12 +35,11 @@ impl IQSampler
|
||||
IQSampler {
|
||||
local_oscillator: Nco::new(center_freq),
|
||||
low_pass_i,
|
||||
low_pass_q
|
||||
low_pass_q,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample(&mut self, input_sample: f32) -> Complex32
|
||||
{
|
||||
pub fn sample(&mut self, input_sample: f32) -> Complex32 {
|
||||
let i_mixed = self.local_oscillator.cexp().re * input_sample;
|
||||
let q_mixed = self.local_oscillator.cexp().im * input_sample;
|
||||
self.local_oscillator.step();
|
||||
@ -46,8 +47,7 @@ impl IQSampler
|
||||
// TODO: Could use one filter for both I and Q
|
||||
Complex32::new(
|
||||
self.low_pass_i.next(Complex32::new(i_mixed, 0.)).re,
|
||||
self.low_pass_q.next(Complex32::new(q_mixed, 0.)).re
|
||||
self.low_pass_q.next(Complex32::new(q_mixed, 0.)).re,
|
||||
) * 2.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
697
src/main.rs
697
src/main.rs
@ -7,35 +7,53 @@ mod filtering;
|
||||
mod iq;
|
||||
mod math;
|
||||
mod nco;
|
||||
mod squelch;
|
||||
mod ted;
|
||||
mod units;
|
||||
mod windows;
|
||||
|
||||
use egui_plot::{Legend, Line, Plot};
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use egui_plot::{Legend, Line, Plot, PlotUi};
|
||||
use hound::WavWriter;
|
||||
use rand::{Rng, rand_core::le, seq::index::sample};
|
||||
use iter_tools::Itertools;
|
||||
use plotters::data;
|
||||
use rand::{Rng, SeedableRng, rand_core::le, seq::index::sample};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::VecDeque,
|
||||
env::{self, args},
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{BufWriter, Sink, Write, stdout},
|
||||
io::{BufWriter, Read, Sink, Write, stdout},
|
||||
ops::DerefMut,
|
||||
sync::Arc,
|
||||
sync::{Arc, atomic::AtomicU64, mpsc::RecvTimeoutError},
|
||||
time::Duration,
|
||||
u64,
|
||||
};
|
||||
use tokio::{
|
||||
io::{self, AsyncReadExt, AsyncWriteExt},
|
||||
join,
|
||||
net::{TcpSocket, TcpStream, UdpSocket},
|
||||
select,
|
||||
sync::mpsc::{UnboundedSender, error::TryRecvError, unbounded_channel},
|
||||
time::timeout,
|
||||
};
|
||||
use tokio::{join, net::UdpSocket, select, sync::mpsc::error::TryRecvError, time::timeout};
|
||||
|
||||
use crate::{
|
||||
bfsk::BFSKMod,
|
||||
complex::Complex32,
|
||||
filtering::{dc_block::DCBlocker, fir::FIRFilter},
|
||||
filtering::{dc_block::DCBlocker, fir::FIRFilter, impulse_response},
|
||||
iq::IQSampler,
|
||||
nco::Nco,
|
||||
squelch::Squelch,
|
||||
ted::elg::ELGate,
|
||||
units::frequency::hz_to_rad_per_sample,
|
||||
windows::gaussian,
|
||||
};
|
||||
use eframe::{
|
||||
egui::{self, CentralPanel, Color32, RichText},
|
||||
glow::NUM_SHADER_BINARY_FORMATS,
|
||||
};
|
||||
use eframe::egui::{self, CentralPanel, Color32};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc::{Receiver, Sender, channel};
|
||||
|
||||
@ -46,403 +64,9 @@ const SAMPLE_RATE: u32 = 48000;
|
||||
const CENTER_FREQ: f32 = 1700.;
|
||||
const DEVIATION: f32 = 500.;
|
||||
|
||||
pub enum SampleSenderCommand {
|
||||
Open,
|
||||
Close,
|
||||
Sample(f32),
|
||||
}
|
||||
|
||||
pub trait SampleSender {
|
||||
async fn open_link(&mut self);
|
||||
async fn send_sample(&mut self, sample: f32);
|
||||
async fn close_link(&mut self);
|
||||
}
|
||||
|
||||
struct WavSampleSender {
|
||||
writer: Option<WavWriter<BufWriter<File>>>,
|
||||
}
|
||||
|
||||
impl Default for WavSampleSender {
|
||||
fn default() -> Self {
|
||||
Self { writer: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl SampleSender for WavSampleSender {
|
||||
async fn open_link(&mut self) {
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: SAMPLE_RATE,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
self.writer = Some(hound::WavWriter::create("audio/modulated.wav", spec).unwrap());
|
||||
}
|
||||
|
||||
async fn send_sample(&mut self, sample: f32) {
|
||||
let out_sample = (sample * i16::MAX as f32) as i16;
|
||||
self.writer
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_sample(out_sample)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn close_link(&mut self) {
|
||||
self.writer = None;
|
||||
}
|
||||
}
|
||||
|
||||
struct Transceiver {
|
||||
tx_stream: Sender<Vec<u8>>,
|
||||
rx_stream: Receiver<Vec<u8>>,
|
||||
|
||||
eye_receiver: Receiver<Vec<f32>>,
|
||||
}
|
||||
|
||||
impl Transceiver {
|
||||
pub async fn send(&self, data: Vec<u8>) {
|
||||
self.tx_stream.send(data).await;
|
||||
}
|
||||
|
||||
pub fn get_sender(&self) -> Sender<Vec<u8>> {
|
||||
self.tx_stream.clone()
|
||||
}
|
||||
|
||||
pub fn try_recv(&mut self) -> Result<Vec<u8>, TryRecvError> {
|
||||
self.rx_stream.try_recv()
|
||||
}
|
||||
|
||||
pub fn try_recv_eye(&mut self) -> Result<Vec<f32>, TryRecvError> {
|
||||
self.eye_receiver.try_recv()
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
mut sample_stream: Receiver<f32>,
|
||||
mut sample_sender: Sender<SampleSenderCommand>,
|
||||
) -> Self {
|
||||
let mut resend: Option<Vec<u8>> = None;
|
||||
let (mut eyes_tx, eyes_rx) = channel::<Vec<f32>>(1024);
|
||||
|
||||
let (rx_stream_sender, rx_stream_receiver) = channel::<Vec<u8>>(1024);
|
||||
let (tx_stream_sender, mut tx_stream_receiver) = channel::<Vec<u8>>(1024);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
select! {
|
||||
_ = Self::squelch_detector(&mut sample_stream) =>
|
||||
{
|
||||
println!("Squelch up");
|
||||
select!
|
||||
{
|
||||
x = Self::receive(&mut sample_stream, &mut eyes_tx) =>
|
||||
{
|
||||
match x
|
||||
{
|
||||
Err(()) => {continue;},
|
||||
Ok(Frame::Ack) =>
|
||||
{
|
||||
resend = None;
|
||||
}
|
||||
Ok(Frame::Data(data)) =>
|
||||
{
|
||||
println!("Got data frame, send data");
|
||||
let _ = rx_stream_sender.send(data).await;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
println!("Got data frame, sending ack");
|
||||
Self::transmit(Frame::Ack, &mut sample_sender).await;
|
||||
println!("Sent ack");
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = tokio::time::sleep(Duration::from_secs(100)) => {continue;}, //TODO: 65
|
||||
//sec
|
||||
//timeout
|
||||
}
|
||||
}, // End squelch
|
||||
data_opt = async
|
||||
{
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
if let Some(resend_data) = resend.clone()
|
||||
{
|
||||
Some(resend_data)
|
||||
}
|
||||
else
|
||||
{
|
||||
tx_stream_receiver.recv().await
|
||||
}
|
||||
}
|
||||
=>
|
||||
{
|
||||
if let Some(data) = data_opt
|
||||
{
|
||||
Self::transmit(Frame::Data(data.clone()), &mut sample_sender).await;
|
||||
resend = Some(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
eye_receiver: eyes_rx,
|
||||
|
||||
tx_stream: tx_stream_sender,
|
||||
rx_stream: rx_stream_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
async fn squelch_detector(sample_stream: &mut Receiver<f32>) {
|
||||
let length = 200;
|
||||
let level = 0.4;
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
|
||||
let mut squelch_sum = 0.;
|
||||
let mut i = 0;
|
||||
while let Some(smpl) = sample_stream.recv().await {
|
||||
let iq = iq_sampler.sample(smpl);
|
||||
squelch_sum += iq.mag() / length as f32;
|
||||
i += 1;
|
||||
|
||||
if i >= length {
|
||||
if squelch_sum >= level {
|
||||
return;
|
||||
}
|
||||
i = 0;
|
||||
squelch_sum = 0.;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn transmit(frame: Frame, samples_sender: &mut Sender<SampleSenderCommand>) {
|
||||
let bytes = frame.bytes();
|
||||
let mut bit_stream = bytes.iter().flat_map(|x| byte_to_bits(*x));
|
||||
let modulator = BFSKMod::new(
|
||||
(SAMPLE_RATE as f32 / BAUD_RATE as f32).round() as u32,
|
||||
hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32),
|
||||
&mut bit_stream,
|
||||
);
|
||||
|
||||
let up_lo = Nco::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
|
||||
samples_sender.send(SampleSenderCommand::Open).await;
|
||||
for (m, up) in modulator.zip(up_lo) {
|
||||
let sample = m * up;
|
||||
samples_sender
|
||||
.send(SampleSenderCommand::Sample(sample.re))
|
||||
.await;
|
||||
}
|
||||
samples_sender.send(SampleSenderCommand::Close).await;
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
sample_stream: &mut Receiver<f32>,
|
||||
eye_sender: &mut Sender<Vec<f32>>,
|
||||
) -> Result<Frame, FrameConstructionError> {
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
|
||||
|
||||
let samples_per_symbol = (SAMPLE_RATE as f32) / (BAUD_RATE as f32);
|
||||
|
||||
let correllator_length = samples_per_symbol as usize;
|
||||
let mut pos_nco = Nco::new(hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32));
|
||||
let mut neg_nco = Nco::new(hz_to_rad_per_sample(-DEVIATION, SAMPLE_RATE as f32));
|
||||
let pos_ir = (0..correllator_length).map(|i| {
|
||||
pos_nco.step();
|
||||
pos_nco.cexp() * windows::blackmann(i as f32 / correllator_length as f32)
|
||||
});
|
||||
let neg_ir = (0..correllator_length).map(|i| {
|
||||
neg_nco.step();
|
||||
neg_nco.cexp() * windows::blackmann(i as f32 / correllator_length as f32)
|
||||
});
|
||||
let mut pos_correllator = FIRFilter::new(&pos_ir.collect::<Vec<_>>());
|
||||
let mut neg_correllator = FIRFilter::new(&neg_ir.collect::<Vec<_>>());
|
||||
pos_correllator.normalize_freq(hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32));
|
||||
neg_correllator.normalize_freq(hz_to_rad_per_sample(-DEVIATION, SAMPLE_RATE as f32));
|
||||
|
||||
let mut matched_lowpass = FIRFilter::new(&vec![
|
||||
Complex32::new(1., 0.);
|
||||
samples_per_symbol as usize / 2
|
||||
]);
|
||||
matched_lowpass.normalize_freq(hz_to_rad_per_sample(DEVIATION, SAMPLE_RATE as f32));
|
||||
//let mut dc_block = DCBlocker::new(0.999);
|
||||
let mut dc_block = DCBlocker::new(1.);
|
||||
|
||||
let loop_i = 0.0;
|
||||
let loop_p = 0.1;
|
||||
let mut loop_ir = vec![Complex32::new(loop_i, 0.); samples_per_symbol as usize];
|
||||
loop_ir.push(Complex32::new(loop_p, 0.));
|
||||
let mut elg = ELGate::new(samples_per_symbol, FIRFilter::new(&loop_ir));
|
||||
|
||||
// Frame reconstruction
|
||||
let mut last_byte = 0x00u8;
|
||||
let mut frame_constructor = FrameConstructor::new();
|
||||
let mut bit_count: Option<u32> = None;
|
||||
while let Some(sample) = sample_stream.recv().await {
|
||||
let iq = iq_sampler.sample(sample);
|
||||
let matched =
|
||||
matched_lowpass
|
||||
.next_real(dc_block.next_real(
|
||||
pos_correllator.next(iq).mag() - neg_correllator.next(iq).mag(),
|
||||
));
|
||||
if let Some((bit_sample, eye)) = elg.next_eye(matched) {
|
||||
let _ = eye_sender.send(eye).await;
|
||||
last_byte >>= 1;
|
||||
last_byte |= ((bit_sample > 0.) as u8) << 7;
|
||||
//last_byte <<= 1;
|
||||
//last_byte |= ((bit_sample < 0.) as u8);
|
||||
bit_count = bit_count.map(|x| x + 1);
|
||||
|
||||
if let None = bit_count
|
||||
&& last_byte == 0xD8
|
||||
{
|
||||
// Potential frame starts
|
||||
last_byte = 0;
|
||||
frame_constructor = FrameConstructor::new();
|
||||
bit_count = Some(0);
|
||||
}
|
||||
|
||||
if let Some(8) = bit_count {
|
||||
let frame_opt = frame_constructor.add_byte(last_byte);
|
||||
bit_count = Some(0);
|
||||
//print!("{}", last_byte as char);
|
||||
print!(".{:x}.", last_byte);
|
||||
let _ = std::io::stdout().flush();
|
||||
|
||||
if let Ok(Some(Frame::Ack)) = frame_opt {
|
||||
println!("Got ack");
|
||||
return Ok(Frame::Ack);
|
||||
}
|
||||
|
||||
if let Ok(Some(Frame::Data(ref frame_data))) = frame_opt {
|
||||
println!("Got data");
|
||||
return Ok(Frame::Data(frame_data.to_vec()));
|
||||
}
|
||||
|
||||
if let Err(()) = frame_opt {
|
||||
// Erroneous frame
|
||||
println!("Error");
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
enum Frame {
|
||||
Data(Vec<u8>),
|
||||
Ack,
|
||||
}
|
||||
|
||||
type FrameConstructionError = ();
|
||||
pub struct FrameConstructor {
|
||||
frame: Vec<u8>,
|
||||
frame_countdown: Option<u16>,
|
||||
checksum: u8,
|
||||
started: bool,
|
||||
}
|
||||
|
||||
impl FrameConstructor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
frame: Vec::new(),
|
||||
frame_countdown: None,
|
||||
checksum: 0u8,
|
||||
started: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_byte(&mut self, byte: u8) -> Result<Option<Frame>, FrameConstructionError> {
|
||||
if self.frame.is_empty() && byte != 0xC4 && byte != 0x4C && !self.started {
|
||||
println!("Wrong type {:x}", byte);
|
||||
self.started = true;
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if self.frame.is_empty() && byte == 0xC4 && !self.started {
|
||||
self.started = true;
|
||||
return Ok(Some(Frame::Ack));
|
||||
}
|
||||
|
||||
if self.frame.is_empty() && byte == 0x4C && !self.started {
|
||||
self.started = true;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.frame.push(byte);
|
||||
|
||||
// Retrieve length
|
||||
if self.frame.len() == 1 {
|
||||
self.frame_countdown = Some(self.frame[0] as u16);
|
||||
return Ok(None);
|
||||
}
|
||||
if self.frame.len() == 2 {
|
||||
*self.frame_countdown.as_mut().unwrap() |= (self.frame[1] as u16) << 8;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if self.frame_countdown.unwrap() == 0 {
|
||||
// All data has been received
|
||||
if self.checksum == byte {
|
||||
return Ok(Some(Frame::Data(
|
||||
self.frame.iter().skip(2).copied().collect(),
|
||||
)));
|
||||
}
|
||||
|
||||
println!("Checksum failed");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
//self.frame.push(byte);
|
||||
self.checksum ^= byte;
|
||||
*self.frame_countdown.as_mut().unwrap() -= 1;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
assert!(x.len() < 65536, "Data size over MTU");
|
||||
|
||||
output_bytes.push(0x4C); // DATA FRAME
|
||||
|
||||
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());
|
||||
|
||||
let mut checksum = 0u8;
|
||||
x.iter().for_each(|x| checksum ^= x);
|
||||
output_bytes.extend(x.iter());
|
||||
|
||||
output_bytes.push(checksum);
|
||||
}
|
||||
Frame::Ack => {
|
||||
output_bytes.push(0xC4); // ACK FRAME
|
||||
}
|
||||
}
|
||||
|
||||
// SEND EOT
|
||||
output_bytes.extend(std::iter::repeat_n(4, 32));
|
||||
output_bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
//Transceiver::transmit(Frame::Data("Skibditoilet".repeat(100).bytes().collect::<Vec<_>>()), &mut WavSampleSender{}).await;
|
||||
//Transceiver::transmit(Frame::Ack, &mut WavSampleSender::default()).await;
|
||||
//return;
|
||||
// Read instance
|
||||
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
let _ = eframe::run_native(
|
||||
@ -452,182 +76,127 @@ async fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
//#[derive(Default)]
|
||||
|
||||
struct DummySampleSender();
|
||||
|
||||
impl SampleSender for DummySampleSender {
|
||||
async fn open_link(&mut self) {}
|
||||
async fn send_sample(&mut self, _sample: f32) {}
|
||||
async fn close_link(&mut self) {}
|
||||
}
|
||||
|
||||
struct EguiApp {
|
||||
a_transceiver: Transceiver,
|
||||
b_transceiver: Transceiver,
|
||||
|
||||
eyes_a: VecDeque<Vec<f32>>,
|
||||
eyes_b: VecDeque<Vec<f32>>,
|
||||
data: Vec<Vec<f32>>,
|
||||
}
|
||||
impl EguiApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let (up_a_sender, mut up_a_receiver) = channel::<SampleSenderCommand>(1024);
|
||||
let (down_a_sender, down_a_receiver) = channel::<f32>(1024);
|
||||
|
||||
let (up_b_sender, mut up_b_receiver) = channel::<SampleSenderCommand>(1024);
|
||||
let (down_b_sender, down_b_receiver) = channel::<f32>(1024);
|
||||
|
||||
let (a2b_tx, mut a2b_rx) = channel::<f32>(1024);
|
||||
let (b2a_tx, mut b2a_rx) = channel::<f32>(1024);
|
||||
|
||||
let a_txrx = Transceiver::start(down_a_receiver, up_a_sender);
|
||||
let b_txrx = Transceiver::start(down_b_receiver, up_b_sender);
|
||||
|
||||
// A dummy channel
|
||||
tokio::spawn(async move {
|
||||
//let rng = rand::thread_rng();
|
||||
let mut sending = false;
|
||||
loop {
|
||||
let noise = rand::random::<f32>() * 0.1;
|
||||
let mut sample = 0.;
|
||||
|
||||
match up_a_receiver.try_recv() {
|
||||
Ok(SampleSenderCommand::Open) => {
|
||||
sending = true;
|
||||
}
|
||||
Ok(SampleSenderCommand::Close) => {
|
||||
sending = false;
|
||||
}
|
||||
Ok(SampleSenderCommand::Sample(x)) => {
|
||||
sample = x;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if sending {
|
||||
// Flush receiver buffer but ignore
|
||||
while let Ok(_) = b2a_rx.try_recv() {}
|
||||
|
||||
// Send to other
|
||||
a2b_tx.send(sample + noise).await.unwrap();
|
||||
} else if let Ok(down_sample) = b2a_rx.try_recv() {
|
||||
down_a_sender.send(down_sample).await.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// B dummy channel
|
||||
tokio::spawn(async move {
|
||||
let mut sending = false;
|
||||
loop {
|
||||
let noise = rand::random::<f32>() * 0.1;
|
||||
let mut sample = 0.;
|
||||
|
||||
match up_b_receiver.try_recv() {
|
||||
Ok(SampleSenderCommand::Open) => {
|
||||
sending = true;
|
||||
}
|
||||
Ok(SampleSenderCommand::Close) => {
|
||||
sending = false;
|
||||
}
|
||||
Ok(SampleSenderCommand::Sample(x)) => {
|
||||
sample = x;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if sending {
|
||||
// Flush receiver buffer but ignore
|
||||
while let Ok(_) = a2b_rx.try_recv() {}
|
||||
|
||||
// Send to other
|
||||
b2a_tx.send(sample + noise).await.unwrap();
|
||||
} else if let Ok(down_sample) = a2b_rx.try_recv() {
|
||||
down_b_sender.send(down_sample).await.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EguiApp {
|
||||
a_transceiver: a_txrx,
|
||||
b_transceiver: b_txrx,
|
||||
|
||||
eyes_a: VecDeque::new(),
|
||||
eyes_b: VecDeque::new(),
|
||||
}
|
||||
//let modulated = modulate();
|
||||
//println!("{}", modulated.len());
|
||||
EguiApp { data: demodulate() }
|
||||
}
|
||||
}
|
||||
|
||||
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.a_transceiver.try_recv_eye() {
|
||||
self.eyes_a.push_back(eye);
|
||||
}
|
||||
while self.eyes_a.len() > max_eyes {
|
||||
self.eyes_a.pop_front();
|
||||
}
|
||||
|
||||
while let Ok(eye) = self.b_transceiver.try_recv_eye() {
|
||||
self.eyes_b.push_back(eye);
|
||||
}
|
||||
while self.eyes_b.len() > max_eyes {
|
||||
self.eyes_b.pop_front();
|
||||
}
|
||||
|
||||
ui.columns(2, |uis| {
|
||||
Plot::new("EyeA")
|
||||
.legend(Legend::default())
|
||||
.show(&mut uis[0], |plot_ui| {
|
||||
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
|
||||
for eye in self.eyes_a.iter() {
|
||||
let line = Line::new(
|
||||
"EyeA",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::LIGHT_GREEN);
|
||||
plot_ui.line(line);
|
||||
}
|
||||
});
|
||||
|
||||
if uis[0].button("Start").clicked() {
|
||||
let snd = self.a_transceiver.get_sender();
|
||||
tokio::spawn(async move {
|
||||
let _ = snd
|
||||
.send("Skibditoilet".repeat(100).as_bytes().to_vec())
|
||||
.await;
|
||||
});
|
||||
CentralPanel::default().show(ctx, |ui| {
|
||||
Plot::new("Plot").show(ui, |plot_ui| {
|
||||
for eye in self.data.iter().skip(BAUD_RATE as usize) {
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"Bitstream",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::GREEN),
|
||||
)
|
||||
}
|
||||
|
||||
Plot::new("EyeB")
|
||||
.legend(Legend::default())
|
||||
.show(&mut uis[1], |plot_ui| {
|
||||
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
|
||||
for eye in self.eyes_b.iter() {
|
||||
let line = Line::new(
|
||||
"EyeB",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| [i as f64, *x as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::LIGHT_GREEN);
|
||||
plot_ui.line(line);
|
||||
}
|
||||
});
|
||||
});
|
||||
}); // Central panel
|
||||
|
||||
std::thread::sleep(Duration::from_millis(16));
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn modulate() -> Vec<f32> {
|
||||
let sample_per_symbols = SAMPLE_RATE / BAUD_RATE;
|
||||
let stream_len = (4 * SAMPLE_RATE) / sample_per_symbols;
|
||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||
let data_stream = (0..stream_len)
|
||||
.map(|_| rng.random::<bool>())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let bitstream = (0..(stream_len * sample_per_symbols)).map(|i| {
|
||||
if data_stream[(i / sample_per_symbols) as usize] {
|
||||
1.
|
||||
} else {
|
||||
-1.
|
||||
}
|
||||
});
|
||||
|
||||
// Synthesise impulse response
|
||||
let mut impulse_response =
|
||||
vec![Complex32::zero(); sample_per_symbols as usize].into_boxed_slice();
|
||||
for (i, x) in impulse_response.iter_mut().enumerate() {
|
||||
*x = Complex32::new(gaussian(0.3, i as f32 / sample_per_symbols as f32), 0.);
|
||||
}
|
||||
|
||||
let mut gaussian_filter = FIRFilter::new(&impulse_response);
|
||||
gaussian_filter.normalize_dc();
|
||||
let filtered_bitstream = bitstream.map(|x| gaussian_filter.next_real(x));
|
||||
//let filtered_bitstream = bitstream;
|
||||
|
||||
let mut nco = Nco::new(0.);
|
||||
let mut lo = Nco::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
|
||||
|
||||
// Save to wav
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: SAMPLE_RATE,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut writer = hound::WavWriter::create("audio/gfsk.wav", spec).unwrap();
|
||||
|
||||
let res = filtered_bitstream
|
||||
.map(|f| {
|
||||
nco.set_frequency(hz_to_rad_per_sample(f * DEVIATION, SAMPLE_RATE as f32));
|
||||
nco.step_n(1);
|
||||
lo.step_n(1);
|
||||
let val = (nco.cexp() * lo.cexp()).re;
|
||||
writer.write_sample((val * i16::MAX as f32) as i16).unwrap();
|
||||
val
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
writer.finalize().unwrap();
|
||||
res
|
||||
}
|
||||
|
||||
fn demodulate() -> Vec<Vec<f32>> {
|
||||
let samples_per_symbol = SAMPLE_RATE / BAUD_RATE;
|
||||
let mut reader = hound::WavReader::open("audio/gfsk_rec.wav").unwrap();
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
|
||||
let mut phase_lowpass = FIRFilter::new(&vec![
|
||||
Complex32::new(1.0, 0.);
|
||||
(samples_per_symbol) as usize
|
||||
]);
|
||||
phase_lowpass.normalize_dc();
|
||||
let mut elgate_filter = FIRFilter::new(&impulse_response::design::pi_loop(
|
||||
0.001,
|
||||
0.,
|
||||
samples_per_symbol,
|
||||
));
|
||||
elgate_filter.normalize_dc();
|
||||
let mut elgate = ELGate::new(samples_per_symbol as f32, elgate_filter);
|
||||
let samples = reader
|
||||
.samples::<i16>()
|
||||
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
|
||||
.tuple_windows()
|
||||
.map(|(x, y)| (x * y.conj()).arg())
|
||||
.map(|x| phase_lowpass.next_real(x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut eyes = vec![];
|
||||
for x in samples {
|
||||
if let Some((_, eye)) = elgate.next_eye(x) {
|
||||
eyes.push(eye);
|
||||
}
|
||||
}
|
||||
println!("{}", eyes.len());
|
||||
eyes
|
||||
}
|
||||
|
||||
fn byte_to_bits(byte: u8) -> Vec<bool> {
|
||||
vec![
|
||||
byte & 1 == 1,
|
||||
|
||||
34
src/squelch.rs
Normal file
34
src/squelch.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use rand::seq::index::sample;
|
||||
|
||||
use crate::complex::Complex32;
|
||||
|
||||
pub struct Squelch {
|
||||
window: VecDeque<f32>,
|
||||
sum: f32,
|
||||
level: f32,
|
||||
}
|
||||
|
||||
impl Squelch {
|
||||
pub fn new(length: usize, level: f32) -> Self {
|
||||
Squelch {
|
||||
window: VecDeque::from(vec![0.; length]),
|
||||
sum: 0.,
|
||||
level,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self, sample: Complex32) -> Option<Complex32> {
|
||||
let oldest = self.window.pop_back().unwrap();
|
||||
self.window.push_front(sample.mag());
|
||||
self.sum -= oldest;
|
||||
self.sum += sample.mag();
|
||||
|
||||
if self.sum / (self.window.len() as f32) > self.level {
|
||||
Some(sample)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,18 +10,20 @@ pub fn bartlett(t: f32) -> f32 {
|
||||
if t < 0.5 { 2. * t } else { 2. - 2. * t }
|
||||
}
|
||||
|
||||
pub fn hann(t: f32) -> f32
|
||||
{
|
||||
pub fn hann(t: f32) -> f32 {
|
||||
0.5 - 0.5 * (2. * PI * t).cos()
|
||||
}
|
||||
|
||||
pub fn hamming(t: f32) -> f32
|
||||
{
|
||||
pub fn hamming(t: f32) -> f32 {
|
||||
0.54 - 0.46 * (2. * PI * t).cos()
|
||||
}
|
||||
|
||||
pub fn blackmann(t: f32) -> f32
|
||||
{
|
||||
pub fn blackmann(t: f32) -> f32 {
|
||||
let x = 2. * PI * t;
|
||||
0.45 - 0.5 * x.cos() + 0.08 * (2. * x).cos()
|
||||
}
|
||||
|
||||
pub fn gaussian(sigma: f32, t: f32) -> f32 {
|
||||
let sq = (t - 0.5) / sigma;
|
||||
(-sq * sq).exp()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user