Skip to main content

orion_sdr/codec/
varicode.rs

1// Copyright (c) 2026 G & R Associates LLC
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// src/codec/varicode.rs
5//
6// IZ8BLY / G3PLX Varicode encoding / decoding for PSK31.
7//
8// Each ASCII character (0–127) maps to a codeword of 1–11 bits (MSB-first).
9// Characters are separated by two 0-bits ("00") when transmitted.
10// This matches the convention used by fldigi and the original PSK31 spec.
11//
12// Table source: Peter Martinez G3PLX, "PSK31: A New Radio-Teletype Mode" (1998).
13// The table is also given verbatim in the fldigi source (varicode.cxx).
14
15use std::collections::VecDeque;
16
17/// Maximum codeword length in the table (10 bits).  The decoder's shift
18/// register must accommodate one extra bit for the leading zero of the "00"
19/// separator, so the effective capacity is VARICODE_MAX_BITS + 1 = 11.
20pub const VARICODE_MAX_BITS: usize = 10;
21
22/// Varicode table: index = ASCII value (0–127).
23/// Each entry is `(codeword: u16, len: u8)` where the codeword is stored
24/// MSB-first so bit `(len-1)` is the first bit transmitted.
25///
26/// Canonical IZ8BLY / fldigi table (pskvaricode.cxx).
27const VARICODE: [(u16, u8); 128] = [
28    (0b1010101011, 10), //   0  NUL
29    (0b1011011011, 10), //   1  SOH
30    (0b1011101101, 10), //   2  STX
31    (0b1101110111, 10), //   3  ETX
32    (0b1011101011, 10), //   4  EOT
33    (0b1101011111, 10), //   5  ENQ
34    (0b1011101111, 10), //   6  ACK
35    (0b1011111101, 10), //   7  BEL
36    (0b1011111111, 10), //   8  BS
37    (0b11101111, 8),    //   9  HT
38    (0b11101, 5),       //  10  LF
39    (0b1101101111, 10), //  11  VT
40    (0b1011011101, 10), //  12  FF
41    (0b11111, 5),       //  13  CR
42    (0b1101110101, 10), //  14  SO
43    (0b1110101011, 10), //  15  SI
44    (0b1011110111, 10), //  16  DLE
45    (0b1011110101, 10), //  17  DC1
46    (0b1110101101, 10), //  18  DC2
47    (0b1110101111, 10), //  19  DC3
48    (0b1101011011, 10), //  20  DC4
49    (0b1101101011, 10), //  21  NAK
50    (0b1101101101, 10), //  22  SYN
51    (0b1101010111, 10), //  23  ETB
52    (0b1101111011, 10), //  24  CAN
53    (0b1101111101, 10), //  25  EM
54    (0b1110110111, 10), //  26  SUB
55    (0b1101010101, 10), //  27  ESC
56    (0b1101011101, 10), //  28  FS
57    (0b1110111011, 10), //  29  GS
58    (0b1011111011, 10), //  30  RS
59    (0b1101111111, 10), //  31  US
60    (0b1, 1),           //  32  SP
61    (0b111111111, 9),   //  33  !
62    (0b101011111, 9),   //  34  "
63    (0b111110101, 9),   //  35  #
64    (0b111011011, 9),   //  36  $
65    (0b1011010101, 10), //  37  %
66    (0b1010111011, 10), //  38  &
67    (0b101111111, 9),   //  39  '
68    (0b11111011, 8),    //  40  (
69    (0b11110111, 8),    //  41  )
70    (0b101101111, 9),   //  42  *
71    (0b111011111, 9),   //  43  +
72    (0b1110101, 7),     //  44  ,
73    (0b110101, 6),      //  45  -
74    (0b1010111, 7),     //  46  .
75    (0b110101111, 9),   //  47  /
76    (0b10110111, 8),    //  48  0
77    (0b10111101, 8),    //  49  1
78    (0b11101101, 8),    //  50  2
79    (0b11111111, 8),    //  51  3
80    (0b101110111, 9),   //  52  4
81    (0b101011011, 9),   //  53  5
82    (0b101101011, 9),   //  54  6
83    (0b110101101, 9),   //  55  7
84    (0b110101011, 9),   //  56  8
85    (0b110110111, 9),   //  57  9
86    (0b11110101, 8),    //  58  :
87    (0b110111101, 9),   //  59  ;
88    (0b111101101, 9),   //  60  <
89    (0b1010101, 7),     //  61  =
90    (0b111010111, 9),   //  62  >
91    (0b1010101111, 10), //  63  ?
92    (0b1010111101, 10), //  64  @
93    (0b1111101, 7),     //  65  A
94    (0b11101011, 8),    //  66  B
95    (0b10101101, 8),    //  67  C
96    (0b10110101, 8),    //  68  D
97    (0b1110111, 7),     //  69  E
98    (0b11011011, 8),    //  70  F
99    (0b11111101, 8),    //  71  G
100    (0b101010101, 9),   //  72  H
101    (0b1111111, 7),     //  73  I
102    (0b111111101, 9),   //  74  J
103    (0b101111101, 9),   //  75  K
104    (0b11010111, 8),    //  76  L
105    (0b10111011, 8),    //  77  M
106    (0b11011101, 8),    //  78  N
107    (0b10101011, 8),    //  79  O
108    (0b11010101, 8),    //  80  P
109    (0b111011101, 9),   //  81  Q
110    (0b10101111, 8),    //  82  R
111    (0b1101111, 7),     //  83  S
112    (0b1101101, 7),     //  84  T
113    (0b101010111, 9),   //  85  U
114    (0b110110101, 9),   //  86  V
115    (0b101011101, 9),   //  87  W
116    (0b101110101, 9),   //  88  X
117    (0b101111011, 9),   //  89  Y
118    (0b1010101101, 10), //  90  Z
119    (0b111110111, 9),   //  91  [
120    (0b111101111, 9),   //  92  backslash
121    (0b111111011, 9),   //  93  ]
122    (0b1010111111, 10), //  94  ^
123    (0b101101101, 9),   //  95  _
124    (0b1011011111, 10), //  96  `
125    (0b1011, 4),        //  97  a
126    (0b1011111, 7),     //  98  b
127    (0b101111, 6),      //  99  c
128    (0b101101, 6),      // 100  d
129    (0b11, 2),          // 101  e
130    (0b111101, 6),      // 102  f
131    (0b1011011, 7),     // 103  g
132    (0b101011, 6),      // 104  h
133    (0b1101, 4),        // 105  i
134    (0b111101011, 9),   // 106  j
135    (0b10111111, 8),    // 107  k
136    (0b11011, 5),       // 108  l
137    (0b111011, 6),      // 109  m
138    (0b1111, 4),        // 110  n
139    (0b111, 3),         // 111  o
140    (0b111111, 6),      // 112  p
141    (0b110111111, 9),   // 113  q
142    (0b10101, 5),       // 114  r
143    (0b10111, 5),       // 115  s
144    (0b101, 3),         // 116  t
145    (0b110111, 6),      // 117  u
146    (0b1111011, 7),     // 118  v
147    (0b1101011, 7),     // 119  w
148    (0b11011111, 8),    // 120  x
149    (0b1011101, 7),     // 121  y
150    (0b111010101, 9),   // 122  z
151    (0b1010110111, 10), // 123  {
152    (0b110111011, 9),   // 124  |
153    (0b1010110101, 10), // 125  }
154    (0b1011010111, 10), // 126  ~
155    (0b1110110101, 10), // 127  DEL
156];
157
158/// Encode one ASCII byte → `(codeword: u16, len: u8)`.
159///
160/// `codeword` is MSB-first: bit `len-1` is the first bit transmitted.
161/// Values ≥ 128 are mapped to the NUL entry (index 0).
162#[inline]
163pub fn varicode_encode(byte: u8) -> (u16, u8) {
164    if byte >= 128 {
165        VARICODE[0]
166    } else {
167        VARICODE[byte as usize]
168    }
169}
170
171/// Decode a Varicode codeword back to an ASCII byte.
172///
173/// Returns `None` if the codeword is not in the table.
174/// Linear scan over 128 entries (fast enough for 31.25 baud).
175#[inline]
176pub fn varicode_decode(bits: u16, len: u8) -> Option<u8> {
177    for (i, &(cw, cw_len)) in VARICODE.iter().enumerate() {
178        if cw_len == len && cw == bits {
179            return Some(i as u8);
180        }
181    }
182    None
183}
184
185// ── VaricodeEncoder ───────────────────────────────────────────────────────────
186
187/// Stateful Varicode bit-stream encoder.
188///
189/// Encodes ASCII bytes to a bit stream, inserting a 2-bit "00" gap between
190/// characters.  The leading gap before the first character is suppressed.
191#[derive(Debug, Clone)]
192pub struct VaricodeEncoder {
193    pending: VecDeque<u8>,
194    first: bool,
195}
196
197impl Default for VaricodeEncoder {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203impl VaricodeEncoder {
204    pub fn new() -> Self {
205        Self {
206            pending: VecDeque::new(),
207            first: true,
208        }
209    }
210
211    /// Push `n` 0-bits (preamble — continuous phase reversals for AFC lock).
212    ///
213    /// The preamble ends with zeros so the first character's leading "00" gap is
214    /// naturally provided.  We leave `first = true` so that `push_byte` after a
215    /// preamble does NOT insert an additional "00" before the codeword.
216    pub fn push_preamble(&mut self, n_bits: usize) {
217        for _ in 0..n_bits {
218            self.pending.push_back(0);
219        }
220        // Do NOT change self.first — the next push_byte will skip the "00" prefix,
221        // which is the right behaviour since the preamble zeros serve as the gap.
222        self.first = true;
223    }
224
225    /// Push one ASCII byte.
226    /// Inserts a "00" inter-character gap before the codeword (except the first).
227    pub fn push_byte(&mut self, b: u8) {
228        if !self.first {
229            self.pending.push_back(0);
230            self.pending.push_back(0);
231        }
232        self.first = false;
233        let (cw, len) = varicode_encode(b);
234        for i in (0..len).rev() {
235            self.pending.push_back(((cw >> i) & 1) as u8);
236        }
237    }
238
239    /// Push `n` 1-bits (postamble — carrier hold / idle).
240    ///
241    /// First inserts a "00" inter-character gap so the Varicode decoder can
242    /// flush the last encoded character before the 1-bit idle sequence begins.
243    pub fn push_postamble(&mut self, n_bits: usize) {
244        // "00" gap to flush the last character through the decoder.
245        if !self.first {
246            self.pending.push_back(0);
247            self.pending.push_back(0);
248        }
249        for _ in 0..n_bits {
250            self.pending.push_back(1);
251        }
252    }
253
254    /// Pop the next bit.  Returns `None` when the queue is empty.
255    pub fn next_bit(&mut self) -> Option<u8> {
256        self.pending.pop_front()
257    }
258
259    pub fn is_empty(&self) -> bool {
260        self.pending.is_empty()
261    }
262
263    /// Drain all pending bits into a `Vec<u8>`.
264    pub fn drain_bits(&mut self) -> Vec<u8> {
265        self.pending.drain(..).collect()
266    }
267}
268
269// ── VaricodeDecoder ───────────────────────────────────────────────────────────
270
271/// Stateful Varicode bit-stream decoder.
272///
273/// Feed bits one at a time with `push_bit`.  Characters are emitted whenever
274/// two consecutive 0-bits are detected (the inter-character separator).
275/// Retrieve decoded characters with `pop_char`.
276#[derive(Debug, Clone, Default)]
277pub struct VaricodeDecoder {
278    shift: u16,
279    len: u8,
280    prev_zero: bool,
281    chars: VecDeque<u8>,
282}
283
284impl VaricodeDecoder {
285    pub fn new() -> Self {
286        Self::default()
287    }
288
289    /// Push one received bit (0 or 1).
290    pub fn push_bit(&mut self, bit: u8) {
291        let is_zero = bit == 0;
292
293        if is_zero && self.prev_zero {
294            // "00" boundary detected.
295            // The previous zero was already shifted into `self.shift` in the last call.
296            // Remove it before decoding: the codeword is `shift >> 1` with `len - 1`.
297            let cw = if self.len > 0 { self.shift >> 1 } else { 0 };
298            let cw_len = self.len.saturating_sub(1);
299            if cw_len > 0
300                && let Some(ch) = varicode_decode(cw, cw_len)
301            {
302                self.chars.push_back(ch);
303            }
304            self.shift = 0;
305            self.len = 0;
306            self.prev_zero = false;
307        } else {
308            self.shift = (self.shift << 1) | (bit & 1) as u16;
309            if self.len < (VARICODE_MAX_BITS as u8 + 1) {
310                self.len += 1;
311            }
312            self.prev_zero = is_zero;
313        }
314    }
315
316    /// Pop the next decoded character.  Returns `None` when the queue is empty.
317    pub fn pop_char(&mut self) -> Option<u8> {
318        self.chars.pop_front()
319    }
320}