Compare commits

...

3 Commits

Author SHA1 Message Date
e1c7ba7865 fm demod 2026-06-15 13:16:40 +02:00
2e76bae04f fm demod 2026-06-15 13:08:12 +02:00
d2bc12d5fd chunk to sample process 2026-06-15 12:50:30 +02:00
6 changed files with 107 additions and 106 deletions

View File

@ -1,4 +1,3 @@
use crate::iq_reader::IqChunk;
use crate::iq_reader::IqSample; use crate::iq_reader::IqSample;
// Automatic Gain Control // Automatic Gain Control
@ -51,56 +50,51 @@ impl<I> Agc<I> {
} }
} }
pub fn process_chunk(&mut self, chunk: &mut IqChunk) { pub fn process_sample(&mut self, sample: IqSample) -> IqSample {
for z in chunk.samples.iter_mut() { let i = sample.re;
let i = z.re; let q = sample.im;
let q = z.im;
// Instant Power // Instant Power
let inst_power = i * i + q * q; let inst_power = i * i + q * q;
let alpha = if inst_power > self.power_estimate { let alpha = if inst_power > self.power_estimate {
self.alpha_attack self.alpha_attack
} else { } else {
self.alpha_release self.alpha_release
}; };
// IIR filter // IIR filter
let power_estimate = alpha * inst_power + (1.0 - alpha) * self.power_estimate; let power_estimate = alpha * inst_power + (1.0 - alpha) * self.power_estimate;
// Update Power // Update Power
self.power_estimate = power_estimate.max(1e-10); self.power_estimate = power_estimate.max(1e-10);
let raw_gain = (self.target_power / self.power_estimate).sqrt(); let raw_gain = (self.target_power / self.power_estimate).sqrt();
// Gain in [min_gain ; max_gain] // Gain in [min_gain ; max_gain]
let raw_gain = match raw_gain { let raw_gain = match raw_gain {
g if g < self.min_gain => self.min_gain, g if g < self.min_gain => self.min_gain,
g if g > self.max_gain => self.max_gain, g if g > self.max_gain => self.max_gain,
_ => raw_gain, _ => raw_gain,
}; };
let final_gain = self.beta * self.current_gain + (1.0 - self.beta) * raw_gain; let final_gain = self.beta * self.current_gain + (1.0 - self.beta) * raw_gain;
self.current_gain = final_gain; self.current_gain = final_gain;
*z = IqSample::new(i * final_gain, q * final_gain); IqSample::new(i * final_gain, q * final_gain)
}
} }
} }
impl<I, E> Iterator for Agc<I> impl<I, E> Iterator for Agc<I>
where where
I: Iterator<Item = Result<IqChunk, E>>, I: Iterator<Item = Result<IqSample, E>>,
{ {
type Item = Result<IqChunk, E>; type Item = Result<IqSample, E>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match self.inner.next()? { match self.inner.next()? {
Ok(mut chunk) => { Ok(sample) => Some(Ok(self.process_sample(sample))),
self.process_chunk(&mut chunk);
Some(Ok(chunk))
}
Err(e) => Some(Err(e)), Err(e) => Some(Err(e)),
} }
} }

View File

@ -1,5 +1,5 @@
// Finite Impulse response + Decimation // Finite Impulse response + Decimation
use crate::{iq_reader::IqChunk, iq_reader::IqSample, utils::ring_buffer::RingBuffer}; use crate::{iq_reader::IqSample, utils::ring_buffer::RingBuffer};
pub struct Fir<I, const N: usize> { pub struct Fir<I, const N: usize> {
inner: I, inner: I,
@ -27,40 +27,43 @@ impl<I, const N: usize> Fir<I, N> {
} }
} }
pub fn process_chunk(&mut self, chunk: &IqChunk) -> IqChunk { // None if not emited
let mut chunk_out = IqChunk::new(); pub fn process_sample(&mut self, sample: IqSample) -> Option<IqSample> {
self.history.push(sample);
for iq in chunk.samples.iter() { self.decimation_index += 1;
self.history.push(*iq);
// Decimation // Decimation
if self.decimation_index.is_multiple_of(self.decimation_factor) { if self.decimation_index.is_multiple_of(self.decimation_factor) {
let mut y_n = IqSample::default(); let mut y_n = IqSample::default();
for k in 0..N { for k in 0..N {
if let Some(sample) = self.history.read_at(k) { if let Some(s) = self.history.read_at(k) {
y_n += *sample * self.taps[k]; y_n += *s * self.taps[k];
}
} }
chunk_out.samples.push(y_n);
} }
Some(y_n)
self.decimation_index += 1; } else {
None
} }
chunk_out
} }
} }
impl<I, E, const N: usize> Iterator for Fir<I, N> impl<I, E, const N: usize> Iterator for Fir<I, N>
where where
I: Iterator<Item = Result<IqChunk, E>>, I: Iterator<Item = Result<IqSample, E>>,
{ {
type Item = Result<IqChunk, E>; type Item = Result<IqSample, E>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match self.inner.next()? { loop {
Ok(chunk) => Some(Ok(self.process_chunk(&chunk))), match self.inner.next()? {
Err(e) => Some(Err(e)), Ok(sample) => {
if let Some(y_n) = self.process_sample(sample) {
return Some(Ok(y_n));
}
}
Err(e) => return Some(Err(e)),
}
} }
} }
} }

View File

@ -1,6 +1,6 @@
use std::{os::unix::process, slice::Chunks}; use crate::iq_reader::IqSample;
use crate::iq_reader::{IqChunk, IqSample}; pub type Sample = f32;
pub struct FmDemod<I> { pub struct FmDemod<I> {
pub inner: I, pub inner: I,
@ -15,24 +15,22 @@ impl<I> FmDemod<I> {
} }
} }
pub fn process_chunk(&mut self, chunk: &mut IqChunk) { pub fn process_sample(&mut self, sample: IqSample) -> Sample {
// TODO: FM Demodulation let phase_diff = sample * self.prev_sample.conj();
todo!(); self.prev_sample = sample;
phase_diff.arg()
} }
} }
impl<I, E> Iterator for FmDemod<I> impl<I, E> Iterator for FmDemod<I>
where where
I: Iterator<Item = Result<IqChunk, E>>, I: Iterator<Item = Result<IqSample, E>>,
{ {
type Item = Result<IqChunk, E>; type Item = Result<Sample, E>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match self.inner.next()? { match self.inner.next()? {
Ok(mut chunk) => { Ok(sample) => Some(Ok(self.process_sample(sample))),
self.process_chunk(&mut chunk);
Some(Ok(chunk))
}
Err(e) => Some(Err(e)), Err(e) => Some(Err(e)),
} }
} }

View File

@ -6,18 +6,18 @@ use std::io::{BufReader, ErrorKind, Read};
pub type IqSample = Complex<f32>; pub type IqSample = Complex<f32>;
// Data chunk // Data chunk
#[derive(Debug)] // #[derive(Debug)]
pub struct IqChunk { // pub struct IqChunk {
pub samples: Vec<IqSample>, // pub samples: Vec<IqSample>,
} // }
//
impl IqChunk { // impl IqChunk {
pub fn new() -> Self { // pub fn new() -> Self {
IqChunk { // IqChunk {
samples: Vec::<IqSample>::new(), // samples: Vec::<IqSample>::new(),
} // }
} // }
} // }
pub struct FileSource { pub struct FileSource {
// Buffer // Buffer
@ -26,6 +26,8 @@ pub struct FileSource {
pub raw_buffer: Vec<u8>, pub raw_buffer: Vec<u8>,
// Size of a sample chunk // Size of a sample chunk
pub chunk_samples_size: usize, pub chunk_samples_size: usize,
// raw_buffer position (in samples)
pub cursor: usize,
} }
impl FileSource { impl FileSource {
@ -42,40 +44,38 @@ impl FileSource {
reader, reader,
raw_buffer: raw_reader, raw_buffer: raw_reader,
chunk_samples_size, chunk_samples_size,
cursor: chunk_samples_size,
}) })
} }
} }
impl Iterator for FileSource { impl Iterator for FileSource {
type Item = Result<IqChunk, Box<dyn Error>>; type Item = Result<IqSample, Box<dyn Error>>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
// Buffer read if self.cursor >= self.chunk_samples_size {
match self.reader.read_exact(&mut self.raw_buffer) { // Buffer read chunk
Ok(_) => {} match self.reader.read_exact(&mut self.raw_buffer) {
Ok(_) => {}
// EOF // EOF
Err(e) if e.kind() == ErrorKind::UnexpectedEof => { Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
return None; return None;
} }
Err(e) => { Err(e) => {
return Some(Err(e.into())); return Some(Err(e.into()));
}
} }
self.cursor = 0;
} }
// Output samples // Buffer read sample
let mut samples = Vec::with_capacity(self.chunk_samples_size); let i = (self.raw_buffer[self.cursor * 2] as i8) as f32 / 128.0;
let q = (self.raw_buffer[self.cursor * 2 + 1] as i8) as f32 / 128.0;
// Buffer read self.cursor += 1;
for iq in self.raw_buffer.chunks(2) {
let i = iq[0] as i8;
let q = iq[1] as i8;
let i_f32 = (i as f32) / 128.0;
let q_f32 = (q as f32) / 128.0;
samples.push(Complex::new(i_f32, q_f32));
}
Some(Ok(IqChunk { samples })) Some(Ok(IqSample::new(i, q)))
} }
} }

View File

@ -17,11 +17,12 @@ fn main() -> Result<(), Box<dyn Error>> {
let pipeline = source let pipeline = source
.agc(20_000_000.0, 1.0, 0.001, 100.0) .agc(20_000_000.0, 1.0, 0.001, 100.0)
.fir::<64>(taps, 4); .fir::<64>(taps, 4)
.demodulate();
for chunk_r in pipeline { for phase_r in pipeline {
let chunk = chunk_r?; let phase = phase_r?;
println!("size : {}", chunk.samples.len()); // println!("phase : {}", phase);
} }
Ok(()) Ok(())

View File

@ -1,8 +1,9 @@
use crate::agc::Agc; use crate::agc::Agc;
use crate::fir::Fir; use crate::fir::Fir;
use crate::iq_reader::IqChunk; use crate::fm_demod::FmDemod;
use crate::iq_reader::IqSample;
pub trait DspPipelineExt<E>: Iterator<Item = Result<IqChunk, E>> + Sized { pub trait DspPipelineExt<E>: Iterator<Item = Result<IqSample, E>> + Sized {
fn agc(self, sample_rate: f32, target_power: f32, min_gain: f32, max_gain: f32) -> Agc<Self> { fn agc(self, sample_rate: f32, target_power: f32, min_gain: f32, max_gain: f32) -> Agc<Self> {
Agc::new(self, sample_rate, target_power, min_gain, max_gain) Agc::new(self, sample_rate, target_power, min_gain, max_gain)
} }
@ -10,6 +11,10 @@ pub trait DspPipelineExt<E>: Iterator<Item = Result<IqChunk, E>> + Sized {
fn fir<const N: usize>(self, taps: [f32; N], decimation_factor: usize) -> Fir<Self, N> { fn fir<const N: usize>(self, taps: [f32; N], decimation_factor: usize) -> Fir<Self, N> {
Fir::new(self, taps, decimation_factor) Fir::new(self, taps, decimation_factor)
} }
fn demodulate(self) -> FmDemod<Self> {
FmDemod::new(self)
}
} }
impl<I, E> DspPipelineExt<E> for I where I: Iterator<Item = Result<IqChunk, E>> {} impl<I, E> DspPipelineExt<E> for I where I: Iterator<Item = Result<IqSample, E>> {}