Skip to main content

orion_sdr/modulate/
bpsk.rs

1// Copyright (c) 2026 G & R Associates LLC
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// src/modulate/bpsk.rs
5use crate::core::{Block, WorkReport};
6use crate::dsp::Rotator;
7use num_complex::Complex32 as C32;
8
9/// BPSK constellation mapper: u8 bit (LSB) → C32 symbol.
10/// bit 0 → (+1, 0),  bit 1 → (−1, 0)
11#[derive(Debug, Clone, Copy, Default)]
12pub struct BpskMapper;
13
14impl BpskMapper {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Block for BpskMapper {
21    type In = u8;
22    type Out = C32;
23
24    #[inline(always)]
25    fn process(&mut self, input: &[u8], output: &mut [C32]) -> WorkReport {
26        let n = input.len().min(output.len());
27        let mut i = 0;
28        let nn = n & !3;
29        while i < nn {
30            output[i] = C32::new(if (input[i] & 1) == 0 { 1.0 } else { -1.0 }, 0.0);
31            output[i + 1] = C32::new(if (input[i + 1] & 1) == 0 { 1.0 } else { -1.0 }, 0.0);
32            output[i + 2] = C32::new(if (input[i + 2] & 1) == 0 { 1.0 } else { -1.0 }, 0.0);
33            output[i + 3] = C32::new(if (input[i + 3] & 1) == 0 { 1.0 } else { -1.0 }, 0.0);
34            i += 4;
35        }
36        while i < n {
37            output[i] = C32::new(if (input[i] & 1) == 0 { 1.0 } else { -1.0 }, 0.0);
38            i += 1;
39        }
40        WorkReport {
41            in_read: n,
42            out_written: n,
43        }
44    }
45}
46
47/// BPSK waveform stage: C32 symbols → C32 IQ.
48/// rf_hz = 0.0 → baseband passthrough with gain.
49/// rf_hz != 0.0 → rotate symbols onto carrier via Rotator.
50#[derive(Debug, Clone)]
51pub struct BpskMod {
52    gain: f32,
53    rot: Rotator,
54}
55
56impl BpskMod {
57    pub fn new(fs: f32, rf_hz: f32, gain: f32) -> Self {
58        Self {
59            gain,
60            rot: Rotator::new(rf_hz, fs),
61        }
62    }
63    pub fn set_gain(&mut self, g: f32) {
64        self.gain = g;
65    }
66}
67
68impl Block for BpskMod {
69    type In = C32;
70    type Out = C32;
71
72    #[inline(always)]
73    fn process(&mut self, input: &[C32], output: &mut [C32]) -> WorkReport {
74        let n = input.len().min(output.len());
75        let g = self.gain;
76        let mut i = 0;
77        let nn = n & !3;
78        while i < nn {
79            let s0 = input[i];
80            let r0 = self.rot.next();
81            let s1 = input[i + 1];
82            let r1 = self.rot.next();
83            let s2 = input[i + 2];
84            let r2 = self.rot.next();
85            let s3 = input[i + 3];
86            let r3 = self.rot.next();
87            output[i] = C32::new(
88                g * s0.re.mul_add(r0.re, -s0.im * r0.im),
89                g * s0.im.mul_add(r0.re, s0.re * r0.im),
90            );
91            output[i + 1] = C32::new(
92                g * s1.re.mul_add(r1.re, -s1.im * r1.im),
93                g * s1.im.mul_add(r1.re, s1.re * r1.im),
94            );
95            output[i + 2] = C32::new(
96                g * s2.re.mul_add(r2.re, -s2.im * r2.im),
97                g * s2.im.mul_add(r2.re, s2.re * r2.im),
98            );
99            output[i + 3] = C32::new(
100                g * s3.re.mul_add(r3.re, -s3.im * r3.im),
101                g * s3.im.mul_add(r3.re, s3.re * r3.im),
102            );
103            i += 4;
104        }
105        while i < n {
106            let s = input[i];
107            let r = self.rot.next();
108            output[i] = C32::new(
109                g * s.re.mul_add(r.re, -s.im * r.im),
110                g * s.im.mul_add(r.re, s.re * r.im),
111            );
112            i += 1;
113        }
114        WorkReport {
115            in_read: n,
116            out_written: n,
117        }
118    }
119}