leodos_protocols/physical/modulator/
oqpsk.rs1use super::clamp_i16;
14
15pub fn modulate_oqpsk(
24 bits: &[u8],
25 n_bits: usize,
26 symbols_i: &mut [f32],
27 symbols_q: &mut [f32],
28) {
29 assert!(n_bits % 2 == 0);
30 let n_sym = n_bits / 2;
31 assert!(bits.len() * 8 >= n_bits);
32 assert!(symbols_i.len() >= n_bits);
33 assert!(symbols_q.len() >= n_bits);
34
35 let s = core::f32::consts::FRAC_1_SQRT_2;
36
37 fn get_bit(bits: &[u8], idx: usize) -> f32 {
38 ((bits[idx / 8] >> (7 - (idx % 8))) & 1) as f32
39 }
40
41 for k in 0..n_sym {
42 let i_val = s * (1.0 - 2.0 * get_bit(bits, 2 * k));
43 let q_val = s * (1.0 - 2.0 * get_bit(bits, 2 * k + 1));
44
45 symbols_i[2 * k] = i_val;
47 symbols_i[2 * k + 1] = i_val;
48
49 if k > 0 {
51 } else {
54 symbols_q[0] = 0.0;
55 }
56 symbols_q[2 * k + 1] = q_val;
57 if 2 * k + 2 < n_bits {
58 symbols_q[2 * k + 2] = q_val;
59 }
60 }
61}
62
63pub fn demodulate_oqpsk(
70 symbols_i: &[f32],
71 symbols_q: &[f32],
72 n_bits: usize,
73 noise_var: f32,
74 scale: f32,
75 llr: &mut [i16],
76) {
77 assert!(n_bits % 2 == 0);
78 let n_sym = n_bits / 2;
79 assert!(symbols_i.len() >= n_bits);
80 assert!(symbols_q.len() >= n_bits);
81 assert!(llr.len() >= n_bits);
82
83 let s = core::f32::consts::FRAC_1_SQRT_2;
84 let factor = scale * 2.0 / noise_var * s;
85
86 for k in 0..n_sym {
87 llr[2 * k] = clamp_i16(symbols_i[2 * k] * factor);
89 llr[2 * k + 1] = clamp_i16(symbols_q[2 * k + 1] * factor);
91 }
92}
93
94pub struct Oqpsk {
96 noise_var: f32,
97 scale: f32,
98}
99
100impl Oqpsk {
101 pub fn new(noise_var: f32, scale: f32) -> Self {
104 Self { noise_var, scale }
105 }
106}
107
108impl super::Modulator for Oqpsk {
109 fn modulate(
110 &self,
111 bits: &[u8],
112 n_bits: usize,
113 symbols: &mut [f32],
114 ) -> usize {
115 let (si, sq) = symbols.split_at_mut(n_bits);
116 modulate_oqpsk(bits, n_bits, si, sq);
117 n_bits * 2
118 }
119}
120
121impl super::Demodulator for Oqpsk {
122 fn demodulate_soft(
123 &self,
124 symbols: &[f32],
125 n_bits: usize,
126 llr: &mut [i16],
127 ) {
128 let (si, sq) = symbols.split_at(n_bits);
129 demodulate_oqpsk(
130 si, sq, n_bits, self.noise_var, self.scale, llr,
131 );
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn oqpsk_modulate_all_zeros() {
141 let bits = [0x00u8];
142 let mut si = [0f32; 8];
143 let mut sq = [0f32; 8];
144 modulate_oqpsk(&bits, 8, &mut si, &mut sq);
145
146 let s = core::f32::consts::FRAC_1_SQRT_2;
147 for k in 0..4 {
149 assert!((si[2 * k] - s).abs() < 1e-6);
150 assert!((si[2 * k + 1] - s).abs() < 1e-6);
151 }
152 }
153
154 #[test]
155 fn oqpsk_i_q_never_simultaneous() {
156 let bits = [0xFFu8];
157 let mut si = [0f32; 8];
158 let mut sq = [0f32; 8];
159 modulate_oqpsk(&bits, 8, &mut si, &mut sq);
160
161 for n in 1..8 {
163 let i_changed = (si[n] - si[n - 1]).abs() > 1e-6;
164 let q_changed = (sq[n] - sq[n - 1]).abs() > 1e-6;
165 assert!(
166 !(i_changed && q_changed),
167 "I and Q both changed at sample {n}"
168 );
169 }
170 }
171
172 #[test]
173 fn oqpsk_roundtrip() {
174 let bits = [0xC3, 0x5A];
175 let mut si = [0f32; 16];
176 let mut sq = [0f32; 16];
177 modulate_oqpsk(&bits, 16, &mut si, &mut sq);
178
179 let mut llr = [0i16; 16];
180 demodulate_oqpsk(&si, &sq, 16, 0.5, 100.0, &mut llr);
181
182 let mut recovered = [0u8; 2];
183 for i in 0..16 {
184 if llr[i] < 0 {
185 recovered[i / 8] |= 1 << (7 - (i % 8));
186 }
187 }
188 assert_eq!(recovered, bits);
189 }
190
191 #[test]
192 fn oqpsk_unit_energy() {
193 let bits = [0xA5u8];
194 let mut si = [0f32; 8];
195 let mut sq = [0f32; 8];
196 modulate_oqpsk(&bits, 8, &mut si, &mut sq);
197
198 for k in 0..4 {
201 let n = 2 * k + 1;
202 let energy = si[n] * si[n] + sq[n] * sq[n];
203 assert!(
204 (energy - 1.0).abs() < 1e-5,
205 "energy at sample {n} = {energy}"
206 );
207 }
208 }
209}