commit 20cac4cb6046c1b7f275b2bc619df4a1b075805e Author: Albin Chaboissier Date: Wed Sep 17 20:33:25 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..431b3bb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "rdsp" +version = "0.1.0" +dependencies = [ + "hound", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..85a24eb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rdsp" +version = "0.1.0" +edition = "2024" + +[dependencies] +hound = "3.5.1" diff --git a/a.jpg b/a.jpg new file mode 100644 index 0000000..edb51b9 Binary files /dev/null and b/a.jpg differ diff --git a/sine.wav b/sine.wav new file mode 100644 index 0000000..95fec33 Binary files /dev/null and b/sine.wav differ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e3886bf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,152 @@ +use std::{ + f32::consts::PI, + fs::File, + io::Read, + ops::{Add, Div, Mul, Sub}, +}; + +fn map(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T +where + T: Clone + Add + Mul + Sub + Div, +{ + ((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min +} + +struct Nco { + // Phase of NCO + theta: u32, // 0 <=> 0, 0xFFFFFFFF <=> 2pi + dtheta: u32, // Dtheta = freq : f = dtheta/dt +} + +impl Nco { + pub fn new(freq: f32) -> Self { + let mut nco = Nco { + theta: 0, + dtheta: 0, + }; + nco.set_frequency(freq); + nco + } + + // Sets freq, freq in radian per sample + pub fn set_frequency(&mut self, freq: f32) { + if freq < 0.0 { + self.dtheta = map(2. * PI - freq, 0., 2. * PI, 0., 0xFFFFFFFFu32 as f32).floor() as u32; + } else { + self.dtheta = map(freq, 0., 2. * PI, 0., 0xFFFFFFFFu32 as f32).floor() as u32; + } + } + + // Adjusts freq, freq in radian per sample + pub fn adjust_frequency(&mut self, freq_off: f32) { + self.set_frequency(self.get_frequency() + freq_off); + } + + pub fn get_frequency(&self) -> f32 { + map(self.dtheta as f32, 0., 0xFFFFFFFFu32 as f32, 0., 2. * PI) + } + + pub fn step(&mut self) { + let bef = self.theta; + self.theta = self.theta.overflowing_add(self.dtheta).0; + println!("{}", bef.overflowing_sub(self.theta).0); + } + + pub fn step_n(&mut self, n: u32) { + self.theta = self + .theta + .overflowing_add(self.dtheta.overflowing_mul(n).0) + .0; + } + + pub fn sin(&self) -> f32 { + map(self.theta as f32, 0., 0xFFFFFFFFu32 as f32, 0., 2. * PI).sin() + } + + pub fn cos(&self) -> f32 { + map(self.theta as f32, 0., 0xFFFFFFFFu32 as f32, 0., 2. * PI).cos() + } +} + +struct BFSKMod<'a, T: Iterator> { + samples_per_bit: u32, + bandwidth: f32, + bit_stream: &'a mut T, + + // State + oscillator: Nco, + sample_index: u32, +} + +impl<'a, T> BFSKMod<'a, T> +where + T: Iterator, +{ + pub fn new(samples_per_bit: u32, bandwidth: f32, bit_stream: &'a mut T) -> Self { + BFSKMod { + samples_per_bit, + bandwidth, + oscillator: Nco::new(0.0), + bit_stream, + sample_index: samples_per_bit, + } + } + + pub fn step_modulate(&mut self) -> Option { + if self.sample_index == self.samples_per_bit { + self.sample_index = 0; + let bit = self.bit_stream.next()?; + + let frequency = if bit { self.bandwidth } else { -self.bandwidth }; + self.oscillator.set_frequency(frequency); + } + + self.sample_index += 1; + self.oscillator.step(); + Some(self.oscillator.sin()) + } +} + +fn main() { + let sample_rate = 44100; + let mut frequency = 500.0; //HZ + + /* + let path = "a.jpg"; + 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(|_| false)); + + let mut bfsk = BFSKMod::new( + 20, + 2. * PI * (frequency / sample_rate as f32), + &mut bit_stream, + ); + + 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(); + + while let Some(sample) = bfsk.step_modulate() { + let amplitude = i16::MAX as f32; + writer.write_sample((amplitude * sample) as i16).unwrap(); + } +}