""" Apollo BPSK Subcarrier Modulator -- NRZ data onto 1.024 MHz subcarrier. The transmit-side counterpart to bpsk_subcarrier_demod. Takes an NRZ baseband waveform (+1/-1) and modulates it onto a 1.024 MHz cosine subcarrier via simple multiplication: output(t) = nrz(t) * cos(2*pi*f_sc*t). This is Bi-Phase Shift Keying (BPSK): the cosine phase flips 180 degrees when the NRZ data changes sign. On the real spacecraft, a 33522B AWG (or equivalent) generates this BPSK-modulated subcarrier before summing with the voice subcarrier. Reference: IMPLEMENTATION_SPEC.md section 4.2 """ from gnuradio import analog, blocks, gr from apollo.constants import PCM_SUBCARRIER_HZ, SAMPLE_RATE_BASEBAND class bpsk_subcarrier_mod(gr.hier_block2): """BPSK modulator: NRZ float input -> BPSK subcarrier float output.""" def __init__( self, subcarrier_freq: float = PCM_SUBCARRIER_HZ, sample_rate: float = SAMPLE_RATE_BASEBAND, ): gr.hier_block2.__init__( self, "apollo_bpsk_subcarrier_mod", gr.io_signature(1, 1, gr.sizeof_float), gr.io_signature(1, 1, gr.sizeof_float), ) self._subcarrier_freq = subcarrier_freq self._sample_rate = sample_rate # 1.024 MHz cosine subcarrier (continuous phase, maintained by sig_source) self.carrier = analog.sig_source_f( sample_rate, analog.GR_COS_WAVE, subcarrier_freq, 1.0, 0, ) # Multiply NRZ data by subcarrier self.mixer = blocks.multiply_ff(1) # Connect: input (NRZ) -> mixer port 0, carrier -> mixer port 1 -> output self.connect(self, (self.mixer, 0)) self.connect(self.carrier, (self.mixer, 1)) self.connect(self.mixer, self) @property def subcarrier_freq(self) -> float: return self._subcarrier_freq @property def sample_rate(self) -> float: return self._sample_rate