diff --git a/sine.wav b/sine.wav index 2c038d5..ab39f0a 100644 Binary files a/sine.wav and b/sine.wav differ diff --git a/src/bfsk.rs b/src/bfsk.rs index f71b4b3..ebc4feb 100644 --- a/src/bfsk.rs +++ b/src/bfsk.rs @@ -46,6 +46,14 @@ where } } +impl<'a, T: Iterator> Iterator for BFSKMod<'a, T> { + type Item = Complex32; + + fn next(&mut self) -> Option { + self.step_modulate() + } +} + // FSK Demodulator (dumb non coherent + no symbol timing recovery) pub struct BFSKDem { samples_per_bit: u32, diff --git a/src/complex.rs b/src/complex.rs index 05b892c..622c354 100644 --- a/src/complex.rs +++ b/src/complex.rs @@ -2,7 +2,7 @@ use std::{ f32::consts::PI, fmt::Display, iter::Sum, - ops::{Add, Div, Mul, Neg, Sub}, + ops::{Add, Deref, Div, Mul, Neg, Sub}, }; #[derive(Copy, Clone, Debug)] @@ -130,3 +130,11 @@ impl Sum for Complex32 { iter.fold(Complex32::zero(), |acc, el| acc + el) } } + +impl Deref for Complex { + type Target = Complex; + + fn deref(&self) -> &Self::Target { + self + } +} diff --git a/src/fft.rs b/src/fft.rs index 91b068b..e89f185 100644 --- a/src/fft.rs +++ b/src/fft.rs @@ -3,7 +3,6 @@ pub mod mixed_radix; pub mod rader; pub mod rader2; pub mod radix2; -pub mod windows; use crate::{ complex::Complex32, diff --git a/src/filtering.rs b/src/filtering.rs new file mode 100644 index 0000000..8fce811 --- /dev/null +++ b/src/filtering.rs @@ -0,0 +1,2 @@ +mod fir; +mod impulse_response; diff --git a/src/filtering/fir.rs b/src/filtering/fir.rs new file mode 100644 index 0000000..ae083a9 --- /dev/null +++ b/src/filtering/fir.rs @@ -0,0 +1,55 @@ +// Finite impulse response filters + +use std::collections::VecDeque; + +use crate::complex::Complex32; + +pub struct FIRFilter +where + T: Iterator, +{ + size: usize, + impulse_response: Box<[Complex32]>, + taps: VecDeque, + input: T, +} + +impl FIRFilter +where + T: Iterator, +{ + pub fn new(impulse_response: &[Complex32], input: T) -> Self { + FIRFilter { + size: impulse_response.len(), + impulse_response: impulse_response.iter().copied().collect(), + taps: VecDeque::from(vec![Complex32::zero(); impulse_response.len()]), + input, + } + } +} + +impl Iterator for FIRFilter +where + T: Iterator, +{ + type Item = Complex32; + + fn next(&mut self) -> Option { + let _ = self.taps.pop_front(); + + let sample = self.input.next(); + if let None = sample { + self.taps.push_back(Complex32::zero()); + None + } else { + self.taps.push_back(sample.unwrap()); + Some( + self.taps + .iter() + .zip(self.impulse_response.iter()) + .map(|(tap, coef)| *tap * *coef) + .sum(), + ) + } + } +} diff --git a/src/filtering/impulse_response.rs b/src/filtering/impulse_response.rs new file mode 100644 index 0000000..8b49119 --- /dev/null +++ b/src/filtering/impulse_response.rs @@ -0,0 +1,8 @@ +// Utilities for impulse response design + +pub struct WindowIRDesigner { + window: fn(f32) -> f32, + + // Ideal requested transfer function + transfer_function: Box<[Complex32]>, +} diff --git a/src/iq.rs b/src/iq.rs new file mode 100644 index 0000000..eef7439 --- /dev/null +++ b/src/iq.rs @@ -0,0 +1,37 @@ +use crate::{complex::Complex32, nco::Nco}; + +pub struct IQSampler +where + T: Iterator, +{ + passband: T, + local_oscillator: Nco, +} + +impl IQSampler +where + T: Iterator, +{ + fn new(passband: T, center_freq: f32) -> Self { + IQSampler { + passband, + local_oscillator: Nco::new(center_freq), + } + } + + fn new_from_lo(passband: T, local_oscillator: Nco) -> Self { + IQSampler { + passband, + local_oscillator, + } + } +} + +impl Iterator for IQSampler +where + T: Iterator, +{ + type Item = Complex32; + + fn next(&mut self) -> Option {} +} diff --git a/src/main.rs b/src/main.rs index 1059dda..889f5cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,11 @@ use std::{ mod bfsk; mod complex; pub mod fft; +mod filtering; +mod iq; mod nco; mod units; +mod windows; use bfsk::BFSKMod; use complex::Complex; @@ -59,114 +62,54 @@ fn main() { } fn modulate() { + // Modulation parameters + let frequency = 2000.; + let deviation = 500.; + + // Data parameters let sample_rate = 44100; - let frequency = 2000.0; //HZ - let bandwidth = 400.0; //HZ - println!("deviation: {}", PI * (bandwidth / sample_rate as f32)); - - let path = "s.txt"; - let file = File::open(path).unwrap(); - let mut bit_stream = file.bytes().flat_map(|byte| { - let byte = byte.unwrap(); - [ - 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, - ] - }); - //let mut bit_stream = (0..22000).map(|_| false).chain((0..22000).map(|_| true)); - //let mut bit_stream = (0..22000).flat_map(|_| [true, false]); - let baud_rate = 400; - println!("{} samples/bit", sample_rate / baud_rate); - let mut bfsk = BFSKMod::new( + + // File to modulate + let f = File::open("s.txt").unwrap(); + let mut bitstream = f.bytes().flat_map(|b| byte_to_bits(b.unwrap())); + + let mut modulator = BFSKMod::new( sample_rate / baud_rate, - PI * (bandwidth / sample_rate as f32), - &mut bit_stream, + 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("sine.wav", spec).unwrap(); - - let mut lo = Nco::new(2. * PI * (frequency / sample_rate as f32)); - - let prev = Complex::new(0., 0.); - let alpha = 1.0 - (-2.0 * PI * ((1.5 * 0.5 * bandwidth) / sample_rate as f32)); - let mut output_samples = vec![]; - while let Some(sample) = bfsk.step_modulate() { + for (s, up) in modulator.zip(lo) { + let sample = (s * up).re; // Project to I coords let amplitude = i16::MAX as f32; - let c_sample = sample * lo.cexp(); - //let c_sample = sample; - - //let filtered = prev + (c_sample - prev) * alpha; - output_samples.push(c_sample); - writer - .write_sample((amplitude * c_sample.re) as i16) - .unwrap(); - lo.step(); + writer.write_sample((sample * amplitude) as i16).unwrap(); } writer.finalize().unwrap(); - - let mut of = File::create("out.txt").unwrap(); - - let mut fft = FFT::new(110, windows::bartlett); - fft.execute(&output_samples); - - let mut csv = File::create("out.csv").unwrap(); - for x in fft.get_output() { - csv.write_all(format!("{},\n", x.mag()).as_bytes()).unwrap(); - } - - let mut bits = vec![]; - let mut lodem = Nco::new(-2. * PI * (frequency / sample_rate as f32)); - let mut demod = BFSKDem::new( - sample_rate / baud_rate, - PI * (bandwidth / sample_rate as f32), - ); - for chunk in output_samples.chunks((sample_rate / baud_rate) as usize) { - let base_chunk: Vec = chunk - .iter() - .map(|x| { - lodem.step(); - *x * lodem.cexp() - }) - .collect(); - let bit = demod.demod(base_chunk.as_slice()); - bits.push(bit); - //println!("{:?}", bit) - } - - for b in bits.chunks(8) { - /* - of.write_all(&[(b[7] as u8) - | ((b[6] as u8) << 1) - | ((b[5] as u8) << 2) - | ((b[4] as u8) << 3) - | ((b[3] as u8) << 4) - | ((b[2] as u8) << 5) - | ((b[1] as u8) << 6) - | ((b[0] as u8) << 7)]) - .unwrap(); - */ - - of.write_all(&[(b[0] as u8) - | ((b[1] as u8) << 1) - | ((b[2] as u8) << 2) - | ((b[3] as u8) << 3) - | ((b[4] as u8) << 4) - | ((b[5] as u8) << 5) - | ((b[6] as u8) << 6) - | ((b[7] as u8) << 7)]) - .unwrap(); - } +} + +fn byte_to_bits(byte: u8) -> Vec { + 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, + ] } diff --git a/src/units.rs b/src/units.rs index 352978f..20dc64b 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,17 +1,13 @@ // Provides nice unit conversions - -mod frequency -{ +pub mod frequency { use std::f32::consts::PI; - pub fn hz_to_rad_per_sample(hz: f32, sample_rate: f32) -> f32 - { + pub fn hz_to_rad_per_sample(hz: f32, sample_rate: f32) -> f32 { 2. * PI * hz / sample_rate } - pub fn rad_per_sample_to_hz(rad: f32, sample_rate: f32) -> f32 - { + pub fn rad_per_sample_to_hz(rad: f32, sample_rate: f32) -> f32 { (rad * sample_rate) / (2. * PI) } } diff --git a/src/fft/windows.rs b/src/windows.rs similarity index 71% rename from src/fft/windows.rs rename to src/windows.rs index 9e7882b..c306a16 100644 --- a/src/fft/windows.rs +++ b/src/windows.rs @@ -1,4 +1,4 @@ -pub fn rectangular(t: f32) -> f32 { +pub fn rectangular(_: f32) -> f32 { 1. }