summaryrefslogtreecommitdiff
path: root/stacks/crdop/lib/afsk_demodulator.py
blob: 5ba3d8bdbb612f0643342241fd502c61a2214614 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
"""
AFSK demodulator — Bell 202 mark/space discrimination per symbol period.

Uses per-symbol Goertzel energy at 1200 Hz and 2200 Hz (Dire Wolf class approach).
"""
from __future__ import annotations

import math
import struct
from array import array

from bell202_line_code import TONE_MARK, TONE_SPACE, decode_tones_to_bits

_TWO_PI = 2.0 * math.pi


def _goertzel_power(samples: array, freq: float, sample_rate: int) -> float:
    n = len(samples)
    if n < 8:
        return 0.0
    k = int(0.5 + (n * freq) / sample_rate)
    w = _TWO_PI * k / n
    coeff = 2.0 * math.cos(w)
    s0 = s1 = s2 = 0.0
    for x in samples:
        s0 = x + coeff * s1 - s2
        s2 = s1
        s1 = s0
    return s1 * s1 + s2 * s2 - coeff * s1 * s2


class AfskDemodulator:
    def __init__(self, sample_rate: int = 48000, baud: int = 1200) -> None:
        self.sample_rate = sample_rate
        self.baud = baud
        self.samples_per_symbol = sample_rate // baud

    def demodulate_pcm(self, pcm: bytes) -> list[int]:
        """Return mark/space tone sequence from mono S16_LE PCM."""
        count = len(pcm) // 2
        if count < self.samples_per_symbol:
            return []
        samples = array("h")
        samples.frombytes(pcm[: count * 2])
        tones: list[int] = []
        pos = 0
        while pos + self.samples_per_symbol <= len(samples):
            window = samples[pos : pos + self.samples_per_symbol]
            pos += self.samples_per_symbol
            p_mark = _goertzel_power(window, 1200.0, self.sample_rate)
            p_space = _goertzel_power(window, 2200.0, self.sample_rate)
            tones.append(TONE_MARK if p_mark >= p_space else TONE_SPACE)
        return tones

    def demodulate_to_bits(self, pcm: bytes, start_tone: int = TONE_SPACE) -> bytes:
        return decode_tones_to_bits(self.demodulate_pcm(pcm), start_tone=start_tone)
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com