From 04d965d67a7264a1c7c211494aebda1953df7603 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Fri, 7 Aug 2026 18:25:13 +0000 Subject: Initial push --- stacks/crdop/lib/afsk_modulator.py | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 stacks/crdop/lib/afsk_modulator.py (limited to 'stacks/crdop/lib/afsk_modulator.py') diff --git a/stacks/crdop/lib/afsk_modulator.py b/stacks/crdop/lib/afsk_modulator.py new file mode 100644 index 0000000..1a6bf68 --- /dev/null +++ b/stacks/crdop/lib/afsk_modulator.py @@ -0,0 +1,51 @@ +""" +Continuous-phase AFSK modulator — Bell 202 mark 1200 Hz / space 2200 Hz. +""" +from __future__ import annotations + +import math +import struct +from array import array + +from bell202_line_code import MARK_HZ, SPACE_HZ, TONE_MARK, encode_bits_to_tones + +_TWO_PI = 2.0 * math.pi + + +class AfskModulator: + def __init__(self, sample_rate: int = 48000, baud: int = 1200) -> None: + self.sample_rate = sample_rate + self.baud = baud + self._phase = 0.0 + + def _tone_freq(self, tone: int) -> float: + return MARK_HZ if tone == TONE_MARK else SPACE_HZ + + def modulate_bits(self, bits: bytes, start_tone: int = 0) -> bytes: + """Return mono S16_LE PCM for the given bit stream.""" + tones = encode_bits_to_tones(bits, start_tone=start_tone) + samples_per_symbol = self.sample_rate // self.baud + out: array[int] = array("h") + for tone in tones: + freq = self._tone_freq(tone) + step = _TWO_PI * freq / self.sample_rate + for _ in range(samples_per_symbol): + sample = int(0.7 * 32767.0 * math.sin(self._phase)) + out.append(sample) + self._phase += step + if self._phase >= _TWO_PI: + self._phase -= _TWO_PI + return struct.pack(f"<{len(out)}h", *out) + + def steady_tone(self, tone: int, duration_s: float) -> bytes: + """Calibration tone (Dire Wolf -x style mark/space hold).""" + n = int(self.sample_rate * duration_s) + freq = self._tone_freq(tone) + step = _TWO_PI * freq / self.sample_rate + out: array[int] = array("h") + for _ in range(n): + out.append(int(0.7 * 32767.0 * math.sin(self._phase))) + self._phase += step + if self._phase >= _TWO_PI: + self._phase -= _TWO_PI + return struct.pack(f"<{len(out)}h", *out) -- cgit v1.3.1