leodos_protocols/physical/modulator/
gmsk.rs1use super::clamp_i16;
24
25const MAX_SPS: usize = 32;
27
28const TRUNC: usize = 4;
30
31const MAX_PULSE: usize = TRUNC * MAX_SPS + 1;
33
34pub fn output_len(n_bits: usize, sps: usize) -> usize {
40 n_bits * sps + TRUNC / 2 * sps
41}
42
43fn gaussian_pulse(bt: f32, sps: usize) -> ([f32; MAX_PULSE], usize) {
49 let pulse_len = TRUNC * sps + 1;
50 assert!(pulse_len <= MAX_PULSE);
51
52 let center = (pulse_len / 2) as f32;
53 let c = core::f32::consts::PI * bt
54 / libm::sqrtf(2.0 * libm::logf(2.0));
55
56 let mut pulse = [0f32; MAX_PULSE];
57 let mut sum = 0f32;
58
59 for m in 0..pulse_len {
60 let t = (m as f32 - center) / sps as f32;
61 let a1 = c * (2.0 * t - 1.0);
62 let a2 = c * (2.0 * t + 1.0);
63 pulse[m] = 0.25 * (libm::erff(a2) - libm::erff(a1));
64 sum += pulse[m];
65 }
66
67 let norm = (sps as f32 * 0.5) / sum;
69 for m in 0..pulse_len {
70 pulse[m] *= norm;
71 }
72
73 (pulse, pulse_len)
74}
75
76pub fn modulate_gmsk(
83 bits: &[u8],
84 n_bits: usize,
85 bt: f32,
86 sps: usize,
87 out_i: &mut [f32],
88 out_q: &mut [f32],
89) {
90 assert!(sps >= 1 && sps <= MAX_SPS);
91 assert!(bits.len() * 8 >= n_bits || n_bits == 0);
92
93 let (pulse, pulse_len) = gaussian_pulse(bt, sps);
94 let n_out = output_len(n_bits, sps);
95 assert!(out_i.len() >= n_out);
96 assert!(out_q.len() >= n_out);
97
98 let sps_f = sps as f32;
99 let mut phase = 0f32;
100
101 let half = (pulse_len / 2) as isize;
102
103 for n in 0..n_out {
104 let mut freq = 0f32;
109
110 let first_k = if (n as isize) >= half {
112 ((n as isize - half) as usize) / sps
113 } else {
114 0
115 };
116 let last_k = (((n as isize + half) as usize) / sps + 1)
117 .min(n_bits);
118
119 for k in first_k..last_k {
120 let tap = n as isize - (k * sps) as isize + half;
121 if tap >= 0 && (tap as usize) < pulse_len {
122 let bit =
123 (bits[k / 8] >> (7 - (k % 8))) & 1;
124 let nrz = 1.0 - 2.0 * bit as f32;
125 freq += nrz * pulse[tap as usize];
126 }
127 }
128
129 phase += core::f32::consts::PI / sps_f * freq;
130 out_i[n] = libm::cosf(phase);
131 out_q[n] = libm::sinf(phase);
132 }
133}
134
135pub fn demodulate_gmsk(
144 in_i: &[f32],
145 in_q: &[f32],
146 n_bits: usize,
147 sps: usize,
148 noise_var: f32,
149 scale: f32,
150 llr: &mut [i16],
151) {
152 assert!(sps >= 1 && sps <= MAX_SPS);
153 assert!(llr.len() >= n_bits);
154
155 let n_out = output_len(n_bits, sps);
156 assert!(in_i.len() >= n_out);
157 assert!(in_q.len() >= n_out);
158
159 let factor = scale / noise_var;
160 let half_sps = sps / 2;
161
162 for k in 0..n_bits {
163 let n = k * sps + half_sps;
165
166 let (prev_i, prev_q) = if n >= sps {
168 (in_i[n - sps], in_q[n - sps])
169 } else {
170 (1.0, 0.0) };
172
173 let cross = prev_i * in_q[n] - prev_q * in_i[n];
175 llr[k] = clamp_i16(factor * cross);
176 }
177}
178
179pub struct Gmsk {
181 bt: f32,
182 sps: usize,
183 noise_var: f32,
184 scale: f32,
185}
186
187impl Gmsk {
188 pub fn new(bt: f32, sps: usize, noise_var: f32, scale: f32) -> Self {
195 Self { bt, sps, noise_var, scale }
196 }
197}
198
199impl super::Modulator for Gmsk {
200 fn modulate(
201 &self,
202 bits: &[u8],
203 n_bits: usize,
204 symbols: &mut [f32],
205 ) -> usize {
206 let n_out = output_len(n_bits, self.sps);
207 let (oi, oq) = symbols.split_at_mut(n_out);
208 modulate_gmsk(bits, n_bits, self.bt, self.sps, oi, oq);
209 n_out * 2
210 }
211}
212
213impl super::Demodulator for Gmsk {
214 fn demodulate_soft(
215 &self,
216 symbols: &[f32],
217 n_bits: usize,
218 llr: &mut [i16],
219 ) {
220 let n_out = output_len(n_bits, self.sps);
221 let (ii, iq) = symbols.split_at(n_out);
222 demodulate_gmsk(
223 ii, iq, n_bits, self.sps,
224 self.noise_var, self.scale, llr,
225 );
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn pulse_shape_normalized() {
235 for &bt in &[0.25f32, 0.3, 0.5] {
236 let sps = 8;
237 let (pulse, len) = gaussian_pulse(bt, sps);
238 let sum: f32 = pulse[..len].iter().sum();
239 let expected = sps as f32 * 0.5;
240 assert!(
241 (sum - expected).abs() < 0.01,
242 "bt={bt}: sum={sum}, expected={expected}"
243 );
244 }
245 }
246
247 #[test]
248 fn pulse_shape_symmetric() {
249 let (pulse, len) = gaussian_pulse(0.25, 8);
250 for i in 0..len / 2 {
251 let diff = (pulse[i] - pulse[len - 1 - i]).abs();
252 assert!(diff < 1e-6, "asymmetry at {i}: {diff}");
253 }
254 }
255
256 #[test]
257 fn constant_envelope() {
258 let bits = [0xA5u8, 0x5A];
259 let n_bits = 16;
260 let sps = 8;
261 let n_out = output_len(n_bits, sps);
262 let mut oi = [0f32; 256];
263 let mut oq = [0f32; 256];
264 modulate_gmsk(&bits, n_bits, 0.25, sps, &mut oi, &mut oq);
265
266 for n in 0..n_out {
267 let env = libm::sqrtf(oi[n] * oi[n] + oq[n] * oq[n]);
268 assert!(
269 (env - 1.0).abs() < 1e-5,
270 "envelope at {n} = {env}"
271 );
272 }
273 }
274
275 #[test]
276 fn roundtrip_no_noise() {
277 let bits = [0b10110100u8];
278 let n_bits = 8;
279 let sps = 8;
280 let mut oi = [0f32; 256];
281 let mut oq = [0f32; 256];
282 modulate_gmsk(&bits, n_bits, 0.5, sps, &mut oi, &mut oq);
283
284 let mut llr = [0i16; 8];
285 demodulate_gmsk(&oi, &oq, n_bits, sps, 0.01, 100.0, &mut llr);
286
287 let mut recovered = [0u8; 1];
288 for i in 0..8 {
289 if llr[i] < 0 {
290 recovered[0] |= 1 << (7 - i);
291 }
292 }
293 assert_eq!(recovered[0], bits[0]);
294 }
295
296 #[test]
297 fn roundtrip_multiple_bytes() {
298 let bits = [0xDE, 0xAD, 0xBE, 0xEF];
299 let n_bits = 32;
300 let sps = 8;
301 let mut oi = [0f32; 512];
302 let mut oq = [0f32; 512];
303 modulate_gmsk(&bits, n_bits, 0.3, sps, &mut oi, &mut oq);
304
305 let mut llr = [0i16; 32];
306 demodulate_gmsk(&oi, &oq, n_bits, sps, 0.01, 100.0, &mut llr);
307
308 let mut recovered = [0u8; 4];
309 for i in 0..32 {
310 if llr[i] < 0 {
311 recovered[i / 8] |= 1 << (7 - (i % 8));
312 }
313 }
314 assert_eq!(recovered, bits);
315 }
316
317 #[test]
318 fn all_zeros_positive_llr() {
319 let bits = [0x00u8];
320 let n_bits = 8;
321 let sps = 8;
322 let mut oi = [0f32; 256];
323 let mut oq = [0f32; 256];
324 modulate_gmsk(&bits, n_bits, 0.5, sps, &mut oi, &mut oq);
325
326 let mut llr = [0i16; 8];
327 demodulate_gmsk(&oi, &oq, n_bits, sps, 0.01, 100.0, &mut llr);
328
329 for (i, &l) in llr.iter().enumerate() {
331 assert!(l > 0, "llr[{i}] = {l}, expected positive");
332 }
333 }
334
335 #[test]
336 fn output_len_calculation() {
337 assert_eq!(output_len(10, 8), 10 * 8 + 2 * 8);
338 assert_eq!(output_len(0, 8), 16);
339 }
340
341 #[test]
342 fn different_bt_products() {
343 for &bt in &[0.25f32, 0.3, 0.5] {
345 let bits = [0xC3u8];
346 let n_bits = 8;
347 let sps = 8;
348 let mut oi = [0f32; 256];
349 let mut oq = [0f32; 256];
350 modulate_gmsk(&bits, n_bits, bt, sps, &mut oi, &mut oq);
351
352 let mut llr = [0i16; 8];
353 demodulate_gmsk(
354 &oi, &oq, n_bits, sps, 0.01, 100.0, &mut llr,
355 );
356
357 let mut recovered = [0u8; 1];
358 for i in 0..8 {
359 if llr[i] < 0 {
360 recovered[0] |= 1 << (7 - i);
361 }
362 }
363 assert_eq!(
364 recovered[0], bits[0],
365 "roundtrip failed for BT={bt}"
366 );
367 }
368 }
369}