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 }
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}