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}