yttria_modulation/
psk.rs

1use num::complex::Complex32;
2use num::Zero;
3use yttria_math::linspace;
4use yttria_math::prelude::*;
5use rayon::prelude::*;
6
7use crate::traits::*;
8
9pub struct PskModulation {
10    pub bits_per_symbol: usize,
11    pub sample_rate: f32,
12    pub bandwidth: f32,
13    pub symbol_map: Vec<Complex32>,
14    // spreading_code: Option<Vec<u8>>,
15}
16
17impl PskModulation {
18    pub fn new(bits_per_symbol: usize, sample_rate: f32, bandwidth: f32) -> Self {
19        let npoints = usize::pow(2, bits_per_symbol as u32);
20
21        let offset = if bits_per_symbol % 2 == 0 {
22            std::f32::consts::PI / npoints as f32
23        } else {
24            0.0
25        };
26
27        let symbol_angles = linspace(offset, 2.0 * std::f32::consts::PI + offset, npoints, false);
28
29        let symbol_map = symbol_angles
30            .iter()
31            .map(|x| Complex32::new(0.0, *x).exp())
32            .collect::<Vec<_>>();
33
34        Self {
35            bits_per_symbol,
36            sample_rate,
37            bandwidth,
38            symbol_map,
39        }
40    }
41}
42
43impl Modulation for PskModulation {
44    fn sample_rate(&self) -> f32 {
45        self.sample_rate
46    }
47
48    fn bits_per_symbol(&self) -> usize {
49        self.bits_per_symbol
50    }
51
52    fn symbol_period(&self) -> f32 {
53        2.0 / self.bandwidth
54    }
55
56    fn bitrate(&self) -> f32 {
57        self.bits_per_symbol() as f32 / self.symbol_period()
58    }
59
60    fn samples_per_symbol(&self) -> usize {
61        (self.symbol_period() * self.sample_rate) as usize
62    }
63
64    fn symbol_map(&self) -> &[Complex32] {
65        self.symbol_map.as_ref()
66    }
67}
68
69impl Modulator for PskModulation {
70    fn modulate_into(&self, data: &[u8], out: &mut [Complex32]) {
71        assert!(out.len() >= (data.len() * 8).div_ceil(self.bits_per_symbol()) * self.samples_per_symbol());
72
73        let bits = data.unpackbits();
74        println!("{} {} {}", data.len(), self.samples_per_symbol(), out.len());
75
76        out.par_chunks_exact_mut(self.samples_per_symbol())
77            .zip(bits.par_chunks(self.bits_per_symbol()))
78            .for_each(|(out, symbol)| {
79                let sym = if symbol.len() == self.bits_per_symbol() {
80                    &self.symbol_map[symbol.pack_into::<usize>()]
81                }
82                else {
83                    let sym_offset = self.bits_per_symbol() - symbol.len();
84                    &self.symbol_map[symbol.pack_into::<usize>() << sym_offset]
85                };
86
87                out.fill(*sym);
88            });
89    }
90
91    fn modulate(&self, data: &[u8]) -> Vec<Complex32> {
92        let len = (data.len() * 8).div_ceil(self.bits_per_symbol()) * self.samples_per_symbol();
93        let mut out = vec![Complex32::zero(); len];
94        self.modulate_into(data, out.as_mut_slice());
95        out
96    }
97}
98
99impl Demodulator for PskModulation {
100    fn demodulate_into(&self, _samples: &[Complex32], _out: &mut [u8]) {
101        todo!()
102    }
103
104    fn demodulate(&self, samples: &[Complex32]) -> Vec<u8> {
105        let len = (samples.len() * self.bits_per_symbol()).div_ceil(self.samples_per_symbol() * 8);
106        let mut out = vec![u8::zero(); len];
107        self.demodulate_into(samples, out.as_mut_slice());
108        out
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use plotly::{Plot, Scatter};
115
116    use super::*;
117    use yttria_math::{arange, firwin2};
118
119    #[test]
120    fn test_bpsk_modulate() {
121        let psk = PskModulation::new(3, 20e6, 5e6 / 3.0);
122
123        let filter = firwin2(
124            psk.samples_per_symbol(),
125            &[0.0, 0.5 / 3.0, 0.5 / 3.0, 1.0],
126            &[1.0, 1.0, 0.0, 0.0],
127            false,
128        ).as_type();
129
130        let data = b"hello, world!";
131
132        let iq = psk.modulate(data);
133        let iq = iq.convolve(filter.as_slice());
134
135        let mut plot = Plot::new();
136
137        let trace = Scatter::new(arange(0, iq.len(), 1), iq.real());
138        plot.add_trace(trace);
139        let trace = Scatter::new(arange(0, iq.len(), 1), iq.imag());
140        plot.add_trace(trace);
141
142        plot.show();
143    }
144}