ELG Gate kind of working

This commit is contained in:
2025-10-02 15:01:04 +02:00
parent 6f01bbdc5d
commit 942cc1fdf0
9 changed files with 1342 additions and 177 deletions

View File

@ -42,4 +42,8 @@ impl FIRFilter {
.sum::<Complex32>()
/ self.normalization
}
pub fn next_real(&mut self, next: f32) -> f32 {
self.next(Complex32::new(next, 0.)).re
}
}

View File

@ -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.),
);
})
});
}

484
src/main.rs.old Normal file
View File

@ -0,0 +1,484 @@
#![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, Vec2, Vec2b, 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>,
elg_late: Vec<f32>,
elg_eary: Vec<f32>,
elg_error: Vec<f32>,
eye_diagram: Vec<Vec<f32>>,
}
const BAUD_RATE: u32 = 1200;
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 mut early_ids = vec![];
let mut late_ids = vec![];
let mut error_vals = vec![];
let delta = 0.4;
let alpha = 0.1;
let mut filter = FIRFilter::new(&vec![Complex32::new(1., 0.); 10]);
let mut current_sps = sample_per_symbol as f32;
let mut current_position = current_sps / 2. + 23.;
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 - late;
let filtered_error = filter.next(Complex32::new(error, 0.0)).re;
//current_sps -= alpha * error;
//error_vals.push(filtered_error);
error_vals.push(error);
sample_ids.push(current_position);
early_ids.push(current_position - delta * current_sps);
late_ids.push(current_position + delta * current_sps);
/*
*/
let eye = ((current_position - current_sps / 2.).max(0.) as usize
..(current_position + current_sps / 2.).min(sample_count as f32) as usize)
.map(|i| dem[i])
.collect();
eye_diagram.push(eye);
//current_position += current_sps;
current_position +=
current_sps - current_sps * alpha * error - current_sps * 0.05 * filtered_error;
}
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,
elg_error: error_vals,
elg_late: late_ids.iter().map(|x| *x / sample_count as f32).collect(),
elg_eary: early_ids.iter().map(|x| *x / sample_count as f32).collect(),
}
}
}
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")
.show_grid(Vec2b::FALSE)
.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::WHITE)
.width(0.5)
.style(LineStyle::dotted_dense()),
);
});
/*
self.elg_late.iter().for_each(|l| {
plot_ui.vline(
VLine::new("LATE", *l as f64)
.color(Color32::BLUE)
.width(1.5)
.style(LineStyle::dotted_dense()),
);
});
self.elg_eary.iter().for_each(|l| {
plot_ui.vline(
VLine::new("EARLY", *l as f64)
.color(Color32::RED)
.width(1.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.),
);
plot_ui.line(
Line::new(
"Error",
self.elg_error
.iter()
.enumerate()
.map(|(i, x)| {
[i as f64 / self.elg_error.len() as f64, 100. * *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
}

0
src/signal.rs Normal file
View File