7 Commits

7 changed files with 194 additions and 556 deletions

32
Cargo.lock generated
View File

@ -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",

View File

@ -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"] }

View File

@ -1,3 +0,0 @@
#!/bin/sh
ffmpeg -re -i audio/modulated.wav -f s16le -acodec pcm_s16le udp://127.0.0.1:8080

View File

@ -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(&centered_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
}
}

View File

@ -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.
}
}

View File

@ -12,39 +12,48 @@ 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, Read, Sink, Write, stdout},
ops::DerefMut,
sync::{Arc, mpsc::RecvTimeoutError},
sync::{Arc, atomic::AtomicU64, mpsc::RecvTimeoutError},
time::Duration,
u64,
};
use tokio::{
io::{self, AsyncReadExt, AsyncWriteExt},
join,
net::{TcpSocket, TcpStream, UdpSocket},
select,
sync::mpsc::error::TryRecvError,
sync::mpsc::{UnboundedSender, error::TryRecvError, unbounded_channel},
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};
@ -55,388 +64,9 @@ const SAMPLE_RATE: u32 = 48000;
const CENTER_FREQ: f32 = 1700.;
const DEVIATION: f32 = 500.;
static mut INSTANCE_ID: u32 = 0;
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 FSKReceiver {
pos_correllator: FIRFilter,
neg_correllator: FIRFilter,
eye_sender: Sender<Vec<f32>>,
matched_lowpass: FIRFilter,
elg: ELGate,
dc_block: DCBlocker,
last_byte: u8,
frame_constructor: FrameConstructor,
bit_count: Option<u32>,
}
impl FSKReceiver {
fn new(eye_sender: Sender<Vec<f32>>) -> Self {
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));
Self {
//iq_sampler: IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)),
pos_correllator,
neg_correllator,
matched_lowpass,
dc_block,
elg,
last_byte: 0x00u8,
frame_constructor: FrameConstructor::new(),
bit_count: None,
eye_sender,
}
}
async fn receive(&mut self, iq: Complex32) -> Result<Option<Frame>, FrameConstructionError> {
// Frame reconstruction
let matched =
self.matched_lowpass.next_real(self.dc_block.next_real(
self.pos_correllator.next(iq).mag() - self.neg_correllator.next(iq).mag(),
));
if let Some((bit_sample, eye)) = self.elg.next_eye(matched) {
let _ = self.eye_sender.send(eye).await;
self.last_byte >>= 1;
self.last_byte |= ((bit_sample > 0.) as u8) << 7;
//last_byte <<= 1;
//last_byte |= ((bit_sample < 0.) as u8);
self.bit_count = self.bit_count.map(|x| x + 1);
if let None = self.bit_count
&& self.last_byte == 0xD8
{
// Potential frame starts
self.last_byte = 0;
self.frame_constructor = FrameConstructor::new();
self.bit_count = Some(0);
}
if let Some(8) = self.bit_count {
let frame_opt = self.frame_constructor.add_byte(self.last_byte);
self.bit_count = Some(0);
//print!("{}", last_byte as char);
print!(".{:x}.", self.last_byte);
let _ = std::io::stdout().flush();
return frame_opt;
}
}
return Ok(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, mut rx_stream_receiver) = channel::<Vec<u8>>(1024);
let (tx_stream_sender, mut tx_stream_receiver) = channel::<Vec<u8>>(1024);
let receiving = Arc::new(RwLock::new(false));
tokio::spawn(async move {
let mut squelch = Squelch::new(200, 0.1);
let mut iq_sampler =
IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
let mut current_message = None;
loop {
select! {
_ = async {
while squelch.next(iq_sampler.sample(sample_stream.recv().await.unwrap())).is_none() {}
}
=>
{
// Wait for end of tranmission
let mut recv = Some(FSKReceiver::new(eyes_tx.clone()));
while let Some(iq) = squelch.next(iq_sampler.sample(sample_stream.recv().await.unwrap()))
{
if recv.as_ref().is_some()
{
match recv.as_mut().unwrap().receive(iq).await
{
Ok(Some(Frame::Data(_))) => {println!("GOT DATA"); Self::transmit(Frame::Ack, &mut sample_sender).await; recv = None;},
Ok(Some(Frame::Ack)) => {current_message = None; recv = None;},
Err(()) => {recv = None;},
_ => {}
}
}
}
println!("EOT");
},
message = async
{
if current_message.is_none()
{
current_message = Some((tx_stream_receiver).recv().await.unwrap());
}
tokio::time::sleep(Duration::from_secs(rand::random_range(1..3))).await;
current_message.as_ref().unwrap()
} =>
{
println!("Sending message");
Self::transmit(Frame::Data(message.clone()), &mut sample_sender).await;
//current_message = None;
println!("Sent message");
}
};
}
});
Self {
eye_receiver: eyes_rx,
tx_stream: tx_stream_sender,
rx_stream: rx_stream_receiver,
}
}
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;
}
}
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() {
// Read instance
let id = std::env::args().collect::<Vec<_>>()[1]
.parse::<u32>()
.expect("NO INPUT ID");
assert!(id == 0 || id == 1);
unsafe {
INSTANCE_ID = id;
};
//Transceiver::transmit(Frame::Data("Skibditoilet".repeat(100).bytes().collect::<Vec<_>>()), &mut WavSampleSender{}).await;
//Transceiver::transmit(Frame::Ack, &mut WavSampleSender::default()).await;
//return;
let native_options = eframe::NativeOptions::default();
let _ = eframe::run_native(
@ -446,154 +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 {
transceiver: Transceiver,
eyes: VecDeque<Vec<f32>>,
data: Vec<Vec<f32>>,
}
impl EguiApp {
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
let (up_sender, mut up_receiver) = channel::<SampleSenderCommand>(1024);
let (down_sender, down_receiver) = channel::<f32>(1024);
let transceiver = Transceiver::start(down_receiver, up_sender);
let instance_id = unsafe { INSTANCE_ID };
tokio::task::spawn(async move {
println!("Waiting for connection ...");
// let socket = Arc::new(
// UdpSocket::bind(format!("0.0.0.0:{}", 9000 + instance_id))
// .await
// .unwrap(),
// );
// socket
// .connect(format!("127.0.0.1:{}", 9000 + (1 - instance_id)))
// .await
// .unwrap();
let mut stream;
if instance_id == 0 {
let socket = TcpSocket::new_v4().unwrap();
socket.bind("0.0.0.0:9000".parse().unwrap()).unwrap();
stream = socket.listen(1).unwrap().accept().await.unwrap().0;
} else {
stream = TcpStream::connect("127.0.0.1:9000").await.unwrap();
}
println!("Connected");
let (mut sock_recv, mut sock_send) = io::split(stream);
// Receiving end
tokio::spawn(async move {
let mut buf = [0u8; 65536];
while let Ok(size) = sock_recv.read(&mut buf).await {
assert!(buf.len() % 4 == 0);
for x in buf.chunks(4).take(size / 4) {
let _ = down_sender.try_send(f32::from_ne_bytes(x.try_into().unwrap()));
}
}
});
// Sending end
let mut sending = false;
let mut wait_countdown = 0;
loop {
// Up link
//let mut sample = 0.;
let noise = rand::random::<f32>() * 0.1;
if let Ok(x) = up_receiver.try_recv() {
match x {
SampleSenderCommand::Open => {
sending = true;
}
SampleSenderCommand::Close => {
sending = false;
}
SampleSenderCommand::Sample(x) => {
if sending {
let _ = sock_send.write(&(x + noise).to_ne_bytes()).await;
wait_countdown += 1;
}
}
}
}
if !sending {
let _ = sock_send.write(&noise.to_ne_bytes()).await;
wait_countdown += 1;
}
if wait_countdown >= 480 {
tokio::time::sleep(Duration::from_millis(10)).await;
wait_countdown = 0;
}
}
});
EguiApp {
transceiver,
eyes: 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.transceiver.try_recv_eye() {
self.eyes.push_back(eye);
}
while self.eyes.len() > max_eyes {
self.eyes.pop_front();
}
Plot::new("EyeA")
.legend(Legend::default())
.show(ui, |plot_ui| {
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
for eye in self.eyes.iter() {
let line = Line::new(
"EyeA",
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::LIGHT_GREEN);
plot_ui.line(line);
}
});
if ui.button("Start").clicked() {
let snd = self.transceiver.get_sender();
tokio::spawn(async move {
let _ = snd
.send("Skibditoilet".repeat(10).as_bytes().to_vec())
.await;
});
}
}); // Central panel
std::thread::sleep(Duration::from_millis(16));
ctx.request_repaint();
.color(Color32::GREEN),
)
}
});
});
}
}
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,

View File

@ -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()
}