ELG Gate kind of working
This commit is contained in:
351
src/main.rs
351
src/main.rs
@ -1,11 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
f32::consts::PI,
|
||||
fs::File,
|
||||
i16,
|
||||
io::{Read, Write},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
os::unix::thread,
|
||||
sync::{
|
||||
Arc,
|
||||
mpsc::{self, Receiver, Sender, TryRecvError, sync_channel},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod bfsk;
|
||||
@ -14,18 +21,24 @@ 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 cpal::{
|
||||
SampleRate,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use fft::DFTAlgorithm;
|
||||
use nco::Nco;
|
||||
|
||||
use eframe::egui::{self, Color32, Context, debug_text::print};
|
||||
use eframe::egui::{self, Color32, Context, Vec2b, debug_text::print};
|
||||
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints, VLine};
|
||||
use plotters::style::Color;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{
|
||||
bfsk::BFSKDem,
|
||||
@ -45,138 +58,184 @@ where
|
||||
{
|
||||
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
|
||||
}
|
||||
const BAUD_RATE: u32 = 1200;
|
||||
|
||||
fn main() {
|
||||
modulate();
|
||||
println!("Demodulating");
|
||||
demodulate();
|
||||
//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>(1024);
|
||||
|
||||
// Build input stream
|
||||
let stream = device
|
||||
.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[i16], _| {
|
||||
for x in data.iter() {
|
||||
let _ = tx.send(*x as f32 / i16::MAX as f32); // 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 * 1.;
|
||||
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();
|
||||
});
|
||||
*/
|
||||
|
||||
return;
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
let _ = eframe::run_native(
|
||||
"Egui",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
|
||||
Box::new(|cc| Ok(Box::new(EguiApp::new(cc, eye_rx, ctx_tx)))),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EguiApp {
|
||||
samples: Vec<f32>,
|
||||
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();
|
||||
|
||||
iq: Vec<Complex32>,
|
||||
dem: Vec<f32>,
|
||||
vlines: Vec<f32>,
|
||||
elg_sampling: Vec<f32>,
|
||||
eye_diagram: Vec<Vec<f32>>,
|
||||
}
|
||||
const BAUD_RATE: u32 = 3200;
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
|
||||
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<_>>();
|
||||
// Data parameters
|
||||
let sample_rate = 48000;
|
||||
let baud_rate = BAUD_RATE;
|
||||
let sample_per_symbol = sample_rate / baud_rate;
|
||||
|
||||
// Modulation parameters
|
||||
let frequency = 1700.;
|
||||
let deviation = 500.;
|
||||
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
|
||||
|
||||
// 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();
|
||||
// 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_pos = (0..sample_per_symbol)
|
||||
.map(|_| {
|
||||
nco_pos.step();
|
||||
nco_pos.cexp()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let corellator_neg = (0..sample_per_symbol)
|
||||
.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 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 elg_buffer = VecDeque::new();
|
||||
let mut sps = sample_per_symbol as f32;
|
||||
let delta = 0.3;
|
||||
let loop_p = 0.1;
|
||||
let loop_i = 0.3;
|
||||
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 40]);
|
||||
let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 70]);
|
||||
let mut current_position = 0.;
|
||||
let mut next_sample = (sps as f32) / 2.;
|
||||
|
||||
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 dem = vec![];
|
||||
// Timing recovery
|
||||
while let Ok(real_sample) = rx.recv() {
|
||||
let iq = iq_sampler.sample(real_sample);
|
||||
|
||||
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
|
||||
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
|
||||
// Perform corellation
|
||||
let neg_energy = matched_filter_neg.next(iq);
|
||||
let pos_energy = matched_filter_pos.next(iq);
|
||||
|
||||
let mut dem = vec![];
|
||||
let matched = matched_filter.next_real(pos_energy.mag() - neg_energy.mag());
|
||||
//dem.push(matched);
|
||||
elg_buffer.push_front(matched);
|
||||
current_position += 1.;
|
||||
if current_position >= next_sample + sps / 2. {
|
||||
// Compute current error
|
||||
let early_id = (((sps / 2.) + sps * delta) as usize).min(elg_buffer.len() - 1);
|
||||
let late_id = (((sps / 2.) - sps * delta) as usize).max(0);
|
||||
let error =
|
||||
elg_buffer[(sps / 2.) as usize] * (elg_buffer[early_id] - elg_buffer[late_id]);
|
||||
|
||||
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() {
|
||||
next_sample +=
|
||||
sps - loop_p * error - loop_i * loop_filter.next(Complex32::new(error, 0.)).re;
|
||||
while elg_buffer.len() > sps as usize {
|
||||
elg_buffer.pop_back();
|
||||
}
|
||||
let _ = eye_sender.send(Vec::from(elg_buffer.clone()));
|
||||
//elg_buffer.clear();
|
||||
ctx.request_repaint();
|
||||
/*
|
||||
if dem.len() > 10_000 {
|
||||
let _ = eye_sender.send(dem.clone());
|
||||
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();
|
||||
//#[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 {
|
||||
samples: input_test.clone(),
|
||||
iq,
|
||||
dem,
|
||||
vlines,
|
||||
elg_sampling,
|
||||
eye_diagram,
|
||||
eye_rx,
|
||||
eyes: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -184,65 +243,31 @@ impl EguiApp {
|
||||
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")
|
||||
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();
|
||||
}
|
||||
|
||||
Plot::new("Eye")
|
||||
.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),
|
||||
);
|
||||
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
|
||||
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);
|
||||
}
|
||||
|
||||
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.),
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user