Finalizes fft, working-ish bfsk

This commit is contained in:
2025-09-26 17:12:48 +02:00
parent 00b4756138
commit 25a2dd47c3
9 changed files with 169 additions and 4941 deletions

4800
out.csv

File diff suppressed because it is too large Load Diff

BIN
out.txt

Binary file not shown.

BIN
sine.wav

Binary file not shown.

View File

@ -3,7 +3,7 @@
use std::f32::consts::PI;
use crate::complex::{Complex, Complex32};
use crate::fft::{self, windows, FFTDirection, FFT};
use crate::fft::{self, FFT, FFTDirection, windows};
use crate::map;
use crate::nco::Nco;
@ -36,11 +36,7 @@ where
self.sample_index = 0;
let bit = self.bit_stream.next()?;
let frequency = if bit {
self.deviation
} else {
-self.deviation
};
let frequency = if bit { self.deviation } else { -self.deviation };
self.oscillator.set_frequency(frequency);
}
@ -65,15 +61,15 @@ pub struct BFSKDem {
impl BFSKDem {
pub fn new(samples_per_bit: u32, deviation: f32) -> Self {
// Calculate bin locations :
let bin_index = map(deviation, 0., 2. * PI, 0., samples_per_bit as f32).floor() as u32;
let bin_index = map(deviation, 0., 2. * PI, 0., samples_per_bit as f32).round() as u32;
println!("bin_index: {bin_index}");
BFSKDem {
samples_per_bit,
deviation,
sample_index: 0,
fft: FFT::new(samples_per_bit as usize, windows::rectangular),
fft: FFT::new(samples_per_bit as usize, windows::bartlett),
bin_pos: bin_index as usize,
bin_neg: (samples_per_bit - bin_index - 1) as usize, // -deviation = negative frequency = upper half
}
@ -86,6 +82,6 @@ impl BFSKDem {
let positive_energy = self.fft.get_output()[self.bin_pos];
let negative_energy = self.fft.get_output()[self.bin_neg];
positive_energy.mag() < negative_energy.mag()
positive_energy.mag() > negative_energy.mag()
}
}

View File

@ -5,11 +5,12 @@ pub mod rader2;
pub mod radix2;
pub mod windows;
use std::{iter::Map, process::Output};
use crate::{
complex::Complex32,
fft::{dft::NaiveDFT, mixed_radix::MixedRadixFFT, rader::RaderFFT, radix2::Radix2FFT},
fft::{
dft::NaiveDFT, mixed_radix::MixedRadixFFT, rader::RaderFFT, rader2::Rader2FFT,
radix2::Radix2FFT,
},
};
pub type FFTWindow = fn(f32) -> f32;
@ -41,39 +42,35 @@ pub trait DFTAlgorithm {
pub fn create_fft(size: usize, direction: FFTDirection) -> Box<dyn DFTAlgorithm> {
if size <= 16 {
println!("Naive {size}");
//println!("Naive {size}");
return Box::new(NaiveDFT::create(size, direction));
}
if size.count_ones() == 1 {
println!("Radix 2 {size}");
//println!("Radix 2 {size}");
return Box::new(Radix2FFT::create(size, direction));
}
if is_prime(size) {
println!("Prime rader {size}");
return Box::new(RaderFFT::create(size, direction));
//println!("Prime rader {size}");
return Box::new(Rader2FFT::create(size, direction));
//return Box::new(NaiveDFT::create(size, direction));
}
println!("Mixed radix {size}");
//println!("Mixed radix {size}");
Box::new(MixedRadixFFT::create(size, direction))
//Box::new(NaiveDFT::create(size, direction))
}
pub struct FFT
{
pub struct FFT {
fft: Box<dyn DFTAlgorithm>,
size: usize,
window: FFTWindow,
input_buffer: Box<[Complex32]>
input_buffer: Box<[Complex32]>,
}
impl FFT
{
pub fn new(size: usize, window: FFTWindow) -> FFT
{
FFT
{
impl FFT {
pub fn new(size: usize, window: FFTWindow) -> FFT {
FFT {
fft: create_fft(size, FFTDirection::Forward),
window,
size,
@ -81,10 +78,8 @@ impl FFT
}
}
pub fn new_inv(size: usize) -> FFT
{
FFT
{
pub fn new_inv(size: usize) -> FFT {
FFT {
fft: create_fft(size, FFTDirection::Inverse),
window: windows::rectangular,
size,
@ -92,20 +87,17 @@ impl FFT
}
}
pub fn execute(&mut self, input: &[Complex32])
{
self.input_buffer.iter_mut().zip(input.iter())
pub fn execute(&mut self, input: &[Complex32]) {
self.input_buffer
.iter_mut()
.zip(input.iter())
.enumerate()
.for_each(|(i, (x, y))|
{
*x = *y * (self.window)(i as f32 / self.size as f32)
});
.for_each(|(i, (x, y))| *x = *y * (self.window)(i as f32 / self.size as f32));
self.fft.execute(&self.input_buffer);
}
pub fn get_output(&self) -> &[Complex32]
{
pub fn get_output(&self) -> &[Complex32] {
self.fft.get_output()
}
}

View File

@ -9,7 +9,6 @@ use crate::{
pub struct MixedRadixFFT {
//size: usize, size is implicitely stored in p and q
p: usize,
q: usize,
twiddle_factors: Box<[Complex32]>,
@ -19,7 +18,8 @@ pub struct MixedRadixFFT {
staging_buffer: Box<[Complex32]>,
pfft_input: Box<[Complex32]>,
output: Box<[Complex32]>
qfft_input: Box<[Complex32]>,
output: Box<[Complex32]>,
}
impl DFTAlgorithm for MixedRadixFFT {
@ -39,6 +39,7 @@ impl DFTAlgorithm for MixedRadixFFT {
staging_buffer: vec![Complex32::zero(); size].into_boxed_slice(),
pfft_input: vec![Complex32::zero(); p].into_boxed_slice(),
qfft_input: vec![Complex32::zero(); q].into_boxed_slice(),
output: vec![Complex32::zero(); size].into_boxed_slice(),
p,
q,
@ -52,10 +53,10 @@ impl DFTAlgorithm for MixedRadixFFT {
for k1 in 0..self.q {
let k = k1 * self.p + k0;
// Use output as staging buffer
self.output[k1] = input[k];
self.qfft_input[k1] = input[k];
}
self.qfft.execute(&self.output);
self.qfft.execute(&self.qfft_input);
for j0 in 0..self.q {
// "Store j0'th of k0'th fft into staging buffer"

View File

@ -4,7 +4,9 @@ use std::f32::consts::PI;
use crate::{
complex::Complex32,
fft::{create_fft, dft::NaiveDFT, is_prime, DFTAlgorithm, FFTDirection},
fft::{
DFTAlgorithm, FFTDirection, create_fft, dft::NaiveDFT, is_prime, mixed_radix::MixedRadixFFT,
},
};
pub struct RaderFFT {
@ -31,21 +33,23 @@ impl DFTAlgorithm for RaderFFT {
let permutations: Box<[usize]> = (0..(size - 1)).map(|i| exp_mod(g, i + 1, size)).collect();
// Compute fourrier transform of twiddle factors
//let mut convolution_fft = create_fft(size - 1, FFTDirection::Forward);
let mut convolution_fft = Box::new(NaiveDFT::create(size - 1, FFTDirection::Forward));
let mut convolution_operand = (0..(size - 1))
.map(|i| {Complex32::cexp(-2. * direction.sign() * PI * (permutations[i] as f32) / (size as f32))})
let twiddle_factors = (0..(size - 1))
.map(|i| {
Complex32::cexp(
-2. * PI * direction.sign() * (permutations[i] as f32) / (size as f32),
)
})
.collect::<Vec<Complex32>>();
convolution_fft.execute(&convolution_operand);
convolution_operand = Vec::from(convolution_fft.get_output());
let mut convolution_fft = create_fft(size - 1, FFTDirection::Forward);
convolution_fft.execute(&twiddle_factors);
RaderFFT {
permutations,
convolution_operand: convolution_operand.into(),
convolution_operand: convolution_fft.get_output().iter().copied().collect(),
//convolution_ifft: create_fft(size - 1, FFTDirection::Inverse),
convolution_ifft: Box::new(NaiveDFT::create(size - 1, FFTDirection::Inverse)),
//convolution_fft,
convolution_fft,
convolution_ifft: create_fft(size - 1, FFTDirection::Inverse),
output: vec![Complex32::zero(); size].into(),
size,
@ -70,13 +74,16 @@ impl DFTAlgorithm for RaderFFT {
self.convolution_ifft.execute(&self.output);
self.output[0] = input[0];
self.output[0] = Complex32::zero();
for x in input {
self.output[0] = self.output[0] + *x;
}
for i in 0..(self.size - 1) {
// Actually compute the output
let k = self.permutations[i];
self.output[k] = (self.convolution_ifft.get_output()[i] / (self.size - 1) as f32) + input[0];
self.output[0] = self.output[0] + input[i + 1];
self.output[k] =
(self.convolution_ifft.get_output()[i] / (self.size - 1) as f32) + input[0];
}
}

View File

@ -1,26 +1,29 @@
// Implementation of raders's fft for prime sized ffts
/*
use std::f32::consts::PI;
use crate::{
complex::Complex32,
fft::{create_fft, dft::NaiveDFT, is_prime, DFTAlgorithm, FFTDirection},
fft::{
DFTAlgorithm, FFTDirection, create_fft, dft::NaiveDFT, is_prime, mixed_radix::MixedRadixFFT,
},
};
pub struct RaderFFT {
pub struct Rader2FFT {
permutations: Box<[usize]>,
convolution_operand: Box<[Complex32]>,
convolution_fft_input: Box<[Complex32]>,
convolution_ifft: Box<dyn DFTAlgorithm>,
convolution_fft: Box<dyn DFTAlgorithm>,
output: Box<[Complex32]>,
sub_size: usize,
size: usize,
}
impl DFTAlgorithm for RaderFFT {
impl DFTAlgorithm for Rader2FFT {
fn create(size: usize, direction: FFTDirection) -> Self
where
Self: Sized,
@ -30,54 +33,69 @@ impl DFTAlgorithm for RaderFFT {
// Primitive root and its powers
let g = compute_prime_primitive_root(size);
let permutations: Box<[usize]> = (0..(size - 1)).map(|i| exp_mod(g, i + 1, size)).collect();
let sub_size = next_pow2(2 * size - 3);
println!("{}", sub_size);
// Compute fourrier transform of twiddle factors
let mut convolution_fft = create_fft(size - 1, FFTDirection::Forward);
//let mut convolution_fft = Box::new(NaiveDFT::create(size - 1, FFTDirection::Forward));
let mut convolution_operand = (0..(size - 1))
.map(|i| {Complex32::cexp(-2. * direction.sign() * PI * (permutations[i] as f32) / (size as f32))})
let twiddle_factors = (0..sub_size)
.map(|i| {
Complex32::cexp(
-2. * PI * direction.sign() * (permutations[i % (size - 1)] as f32)
/ (size as f32),
)
})
.collect::<Vec<Complex32>>();
convolution_fft.execute(&convolution_operand);
convolution_operand = Vec::from(convolution_fft.get_output());
RaderFFT {
let mut convolution_fft = create_fft(sub_size, FFTDirection::Forward);
convolution_fft.execute(&twiddle_factors);
Rader2FFT {
permutations,
convolution_operand: convolution_operand.into(),
convolution_operand: convolution_fft.get_output().iter().copied().collect(),
convolution_ifft: create_fft(size - 1, FFTDirection::Inverse),
//convolution_ifft: Box::new(NaiveDFT::create(size - 1, FFTDirection::Inverse)),
convolution_fft,
convolution_ifft: create_fft(sub_size, FFTDirection::Inverse),
convolution_fft_input: vec![Complex32::zero(); sub_size].into(),
output: vec![Complex32::zero(); size].into(),
size,
sub_size,
}
}
fn execute(&mut self, input: &[Complex32]) {
// Compute fft of input signal
for i in 0..(self.size - 1) {
self.convolution_fft_input[0] = input[self.permutations[self.size - 2]];
for i in 0..(self.sub_size - self.size + 1) {
self.convolution_fft_input[i + 1] = Complex32::zero();
}
for i in 1..(self.size - 1) {
// reverse sequence
let k = self.permutations[self.size - 1 - i - 1];
// Using output as staging buffer
self.output[i] = input[k];
self.convolution_fft_input[i + self.sub_size - self.size + 1] = input[k];
}
self.convolution_fft.execute(&self.output);
self.convolution_fft.execute(&self.convolution_fft_input);
// Compute convolution by multiplying in freq domain
for i in 0..(self.size - 1) {
for i in 0..self.sub_size {
// Using output as staging buffer
self.output[i] = self.convolution_fft.get_output()[i] * self.convolution_operand[i];
self.convolution_fft_input[i] =
self.convolution_fft.get_output()[i] * self.convolution_operand[i];
}
self.convolution_ifft.execute(&self.output);
self.convolution_ifft.execute(&self.convolution_fft_input);
self.output[0] = input[0];
self.output[0] = Complex32::zero();
for x in input {
self.output[0] = self.output[0] + *x;
}
for i in 0..(self.size - 1) {
// Actually compute the output
let k = self.permutations[i];
self.output[k] = (self.convolution_ifft.get_output()[i] / (self.size - 1) as f32) + input[0];
self.output[0] = self.output[0] + input[i + 1];
self.output[k] =
(self.convolution_ifft.get_output()[i] / (self.sub_size) as f32) + input[0];
}
}
@ -131,4 +149,15 @@ pub fn exp_mod(mut n: usize, mut exp: usize, m: usize) -> usize {
r
}
*/
pub fn next_pow2(mut n: usize) -> usize {
if n.count_ones() == 1 {
n
} else {
let mut p = 0;
while n > 0 {
n >>= 1;
p += 1;
}
1 << p
}
}

View File

@ -15,11 +15,34 @@ mod nco;
use bfsk::BFSKMod;
use complex::Complex;
use complex::Complex32;
use fft::DFTAlgorithm;
use nco::Nco;
use plotters::prelude::*;
use fft::DFTAlgorithm;
use crate::{bfsk::BFSKDem, fft::{create_fft, dft::NaiveDFT, mixed_radix::MixedRadixFFT, rader::RaderFFT, radix2::Radix2FFT, windows, FFTDirection, FFT}};
use crate::{
bfsk::BFSKDem,
fft::{
FFT, FFTDirection, create_fft,
dft::NaiveDFT,
mixed_radix::MixedRadixFFT,
prime_factors,
rader::{RaderFFT, compute_prime_primitive_root, exp_mod},
rader2::{Rader2FFT, next_pow2},
radix2::Radix2FFT,
windows,
},
};
struct QuickLCG(i32);
impl QuickLCG {
pub fn seed(val: i32) -> QuickLCG {
QuickLCG(val % 10)
}
pub fn next(&mut self) -> i32 {
self.0 = self.0.overflowing_mul(9321).0.overflowing_add(5672).0 % 10;
self.0
}
}
// Utilities
fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
@ -30,44 +53,14 @@ where
}
fn main() {
//modulate();
test();
}
fn test()
{
let mut o1 = Nco::new(PI / 2.0);
let mut o2 = Nco::new(PI / 4.0);
let sample_count = 4800;
//let mut fft = FFT::new(sample_count, windows::rectangular);
let mut dft = NaiveDFT::create(sample_count, FFTDirection::Forward);
let mut fft = FFT::new(sample_count, windows::rectangular);
//let mut fft = RaderFFT::create(sample_count, FFTDirection::Forward);
let mut fft_input = vec![Complex32::zero(); sample_count];
for x in fft_input.iter_mut()
{
*x = o1.cexp() + o2.cexp();
o1.step();
o2.step();
}
fft.execute(&fft_input);
dft.execute(&fft_input);
let mut out_file = File::create("out.csv").unwrap();
for (x, y) in fft.get_output().iter().zip(dft.get_output())
{
out_file.write_all(
format!("{},{},\n", x.mag(), y.mag()).as_bytes()
).unwrap();
}
modulate();
//œtest();
}
fn modulate() {
let sample_rate = 44100;
let frequency = 2000.0; //HZ
let bandwidth = 1000.0; //HZ
let bandwidth = 500.0; //HZ
println!("deviation: {}", PI * (bandwidth / sample_rate as f32));
let path = "s.txt";
@ -92,7 +85,7 @@ fn modulate() {
println!("{} samples/bit", sample_rate / baud_rate);
let mut bfsk = BFSKMod::new(
sample_rate / baud_rate,
PI * 0.05, //PI * (bandwidth / sample_rate as f32),
PI * (bandwidth / sample_rate as f32),
&mut bit_stream,
);
@ -111,35 +104,33 @@ fn modulate() {
let mut output_samples = vec![];
while let Some(sample) = bfsk.step_modulate() {
let amplitude = i16::MAX as f32;
let c_sample = lo.cexp() * sample;
let c_sample = sample * lo.cexp();
//let c_sample = sample;
let filtered = prev + (c_sample - prev) * alpha;
output_samples.push(filtered);
//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.finalize().unwrap();
let mut tfft = FFT::new(44100, windows::rectangular);
tfft.execute(&output_samples);
// Write csv
let mut out_csv = File::create("out.csv").unwrap();
for x in output_samples.iter().take(4400)
{
out_csv.write_all(
format!("{},\n", x.mag()).as_bytes()
).unwrap();
let mut of = File::create("out.jpg").unwrap();
let mut fft = FFT::new(110, windows::bartlett);
fft.execute(&output_samples[220..]);
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 of = File::create("out.txt").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 * 0.05, //PI * (bandwidth / sample_rate as f32),
PI * (bandwidth / sample_rate as f32),
);
for chunk in output_samples.chunks((sample_rate / baud_rate) as usize) {
let base_chunk: Vec<Complex32> = chunk
@ -155,14 +146,26 @@ fn modulate() {
}
for b in bits.chunks(8) {
of.write_all(&[(b[0] as u8)
| ((b[0] as u8) << 1)
| ((b[0] as u8) << 2)
| ((b[0] as u8) << 3)
| ((b[0] as u8) << 4)
| ((b[0] as u8) << 5)
| ((b[0] as u8) << 6)
/*
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();
}
}