leodos_protocols/physical/modulator/
qpsk.rs1use super::clamp_i16;
8
9pub fn modulate(
19 bits: &[u8],
20 n_bits: usize,
21 symbols_i: &mut [f32],
22 symbols_q: &mut [f32],
23) {
24 assert!(n_bits % 2 == 0);
25 let n_sym = n_bits / 2;
26 assert!(bits.len() * 8 >= n_bits);
27 assert!(symbols_i.len() >= n_sym);
28 assert!(symbols_q.len() >= n_sym);
29
30 let s = core::f32::consts::FRAC_1_SQRT_2;
31
32 for k in 0..n_sym {
33 let bit_i = 2 * k;
34 let bit_q = 2 * k + 1;
35 let b0 = ((bits[bit_i / 8] >> (7 - (bit_i % 8))) & 1) as f32;
36 let b1 = ((bits[bit_q / 8] >> (7 - (bit_q % 8))) & 1) as f32;
37 symbols_i[k] = s * (1.0 - 2.0 * b0);
38 symbols_q[k] = s * (1.0 - 2.0 * b1);
39 }
40}
41
42pub fn demodulate(
47 symbols_i: &[f32],
48 symbols_q: &[f32],
49 n_bits: usize,
50 noise_var: f32,
51 scale: f32,
52 llr: &mut [i16],
53) {
54 assert!(n_bits % 2 == 0);
55 let n_sym = n_bits / 2;
56 assert!(symbols_i.len() >= n_sym);
57 assert!(symbols_q.len() >= n_sym);
58 assert!(llr.len() >= n_bits);
59
60 let s = core::f32::consts::FRAC_1_SQRT_2;
61 let factor = scale * 2.0 / noise_var;
62
63 let f = factor * s;
67
68 for k in 0..n_sym {
69 llr[2 * k] = clamp_i16(symbols_i[k] * f);
71 llr[2 * k + 1] = clamp_i16(symbols_q[k] * f);
72 }
73}
74
75pub struct Qpsk {
77 noise_var: f32,
78 scale: f32,
79}
80
81impl Qpsk {
82 pub fn new(noise_var: f32, scale: f32) -> Self {
85 Self { noise_var, scale }
86 }
87}
88
89impl super::Modulator for Qpsk {
90 fn modulate(
91 &self,
92 bits: &[u8],
93 n_bits: usize,
94 symbols: &mut [f32],
95 ) -> usize {
96 let n_sym = n_bits / 2;
97 let (si, sq) = symbols.split_at_mut(n_sym);
98 modulate(bits, n_bits, si, sq);
99 n_sym * 2
100 }
101}
102
103impl super::Demodulator for Qpsk {
104 fn demodulate_soft(
105 &self,
106 symbols: &[f32],
107 n_bits: usize,
108 llr: &mut [i16],
109 ) {
110 let n_sym = n_bits / 2;
111 let (si, sq) = symbols.split_at(n_sym);
112 demodulate(si, sq, n_bits, self.noise_var, self.scale, llr);
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use super::super::noise_variance;
120
121 #[test]
122 fn modulate_unit_energy() {
123 let bits = [0xFF]; let mut si = [0f32; 4];
125 let mut sq = [0f32; 4];
126 modulate(&bits, 8, &mut si, &mut sq);
127
128 let s = -core::f32::consts::FRAC_1_SQRT_2;
130 for k in 0..4 {
131 assert!((si[k] - s).abs() < 1e-6);
132 assert!((sq[k] - s).abs() < 1e-6);
133 let energy = si[k] * si[k] + sq[k] * sq[k];
135 assert!((energy - 1.0).abs() < 1e-6);
136 }
137 }
138
139 #[test]
140 fn roundtrip_hard() {
141 let bits = [0xC3, 0x5A]; let mut si = [0f32; 8];
143 let mut sq = [0f32; 8];
144 modulate(&bits, 16, &mut si, &mut sq);
145
146 let mut llr = [0i16; 16];
147 demodulate(&si, &sq, 16, 0.5, 100.0, &mut llr);
148
149 let mut recovered = [0u8; 2];
150 for i in 0..16 {
151 if llr[i] < 0 {
152 recovered[i / 8] |= 1 << (7 - (i % 8));
153 }
154 }
155 assert_eq!(recovered, bits);
156 }
157
158 #[test]
159 fn noise_variance_known_values() {
160 let v = noise_variance(0.0, 0.5);
162 assert!((v - 1.0).abs() < 1e-6);
163
164 let v = noise_variance(10.0, 1.0);
166 assert!((v - 0.05).abs() < 1e-4);
167 }
168}