weaklink-modem 0.7.2__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/PKG-INFO +17 -47
  2. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/pyproject.toml +2 -2
  3. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/readme-pypi.md +15 -45
  4. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/__init__.py +1 -1
  5. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/benchmark.py +1 -1
  6. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/cli.py +2 -2
  7. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/codec.py +3 -3
  8. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/constants.py +1 -1
  9. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/exceptions.py +1 -1
  10. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/streaming.py +1 -1
  11. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/waveform.py +7 -7
  12. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/LICENSE +0 -0
  13. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/api.py +0 -0
  14. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/audio.py +0 -0
  15. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/fec.py +0 -0
  16. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/interleaver.py +0 -0
  17. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/ptt.py +0 -0
  18. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/py.typed +0 -0
  19. {weaklink_modem-0.7.2 → weaklink_modem-0.7.4}/src/weaklink/modem/rs.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: weaklink-modem
3
- Version: 0.7.2
4
- Summary: N-FSK streaming modem for HF SSB and wider channels: Reed-Solomon framing, soft Viterbi, round-robin block repetition, byte-pipe semantics.
3
+ Version: 0.7.4
4
+ Summary: MFSK streaming modem for HF SSB and wider channels: Reed-Solomon framing, soft Viterbi, round-robin block repetition, byte-pipe semantics.
5
5
  License: MIT
6
6
  Author: ivica3730k
7
7
  Requires-Python: >=3.10,<4.0
@@ -27,7 +27,7 @@ Project-URL: Homepage, https://github.com/ivica3730k/weaklink-9a3ice
27
27
  Project-URL: Repository, https://github.com/ivica3730k/weaklink-9a3ice
28
28
  Description-Content-Type: text/markdown
29
29
 
30
- <!-- generated from README.md at tag v0.7.2 by scripts/build_pypi_readme.py; do not hand-edit -->
30
+ <!-- generated from README.md at tag v0.7.4 by scripts/build_pypi_readme.py; do not hand-edit -->
31
31
 
32
32
  # weaklink
33
33
 
@@ -37,7 +37,7 @@ with `tail -f`; no memory buffering, no wait-for-EOF. RS + convolutional
37
37
  soft-LLR combining. Modes: **OOK (1-tone)** through **16-FSK**, at
38
38
  **45 / 300 / 1200 baud**.
39
39
 
40
- ![alt text](https://github.com/ivica3730k/weaklink-9a3ice/raw/v0.7.2/image.png)
40
+ ![alt text](https://github.com/ivica3730k/weaklink-9a3ice/raw/v0.7.4/image.png)
41
41
 
42
42
  Distribution: `weaklink-modem` (PyPI + release binaries).
43
43
 
@@ -121,7 +121,7 @@ cross-baud comparison convention, not a physical channel filter.
121
121
 
122
122
  Doubling `--modem-block-repeats` buys ~2–3 dB via soft-LLR combining
123
123
  at proportional air time. Full sweep of every combo we test is in
124
- [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.2/results.md).
124
+ [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.4/results.md).
125
125
 
126
126
  ### OOK / 1-tone mode
127
127
 
@@ -131,52 +131,21 @@ bandwidth of any mode (no tone stack — just the carrier and its
131
131
  modulation sidelobes). Same 1 bit/symbol as 2-FSK but a few dB worse
132
132
  in AWGN — the trade you make for the narrow spectrum. Cliff at
133
133
  45 baud with `block_repeats=4`: ≈ −14 dB, matching 4-FSK at the same
134
- settings. See [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.2/results.md) for the per-baud numbers.
134
+ settings. See [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.4/results.md) for the per-baud numbers.
135
135
 
136
- ### One tone at a time (constant envelope, any N)
136
+ ### One tone at a time (constant envelope, any M)
137
137
 
138
138
  Every mode in this modem is **single-tone-at-a-time CPFSK**.
139
139
  `--modem-num-tones 16` means "16 possible frequencies to pick from
140
140
  per symbol", **not** "16 frequencies playing simultaneously". The
141
141
  transmitter emits exactly one sinusoid at any instant, hopping between
142
142
  frequencies at the symbol clock. Constant envelope (PAPR = 3 dB, the
143
- peak-to-RMS of a pure sine) regardless of N.
144
-
145
- Time-frequency view of a 4-FSK burst — each column is one symbol,
146
- each row is one of the 4 available frequencies:
147
-
148
- ```
149
- freq
150
-
151
- F₃ │ . ▓ . . . ▓ . .
152
- F₂ │ . . . ▓ . . . .
153
- F₁ │ ▓ . . . . . ▓ .
154
- F₀ │ . . ▓ . ▓ . . ▓
155
- └────────────────────────→ time
156
- s₀ s₁ s₂ s₃ s₄ s₅ s₆ s₇
157
-
158
- Exactly one ▓ per column: at any instant, one tone at full amplitude.
159
- Over 8 symbols the modem has visited all 4 slots (that's what you see
160
- on an SDR waterfall over time), but never more than one at once.
161
- ```
162
-
163
- vs. what parallel multi-tone (OFDM-style; **not** this modem) would do:
164
-
165
- ```
166
- freq
167
-
168
- F₃ │ ▓ . ▓ ▓ . ▓ . ▓
169
- F₂ │ . ▓ ▓ . ▓ . ▓ ▓ Multiple ▓ per column: N tones
170
- F₁ │ ▓ ▓ . ▓ . ▓ ▓ . summed at each instant. PAPR grows
171
- F₀ │ ▓ . ▓ . ▓ ▓ . ▓ with N (~10·log₁₀(N)).
172
- └────────────────────────→ time
173
- s₀ s₁ s₂ s₃ s₄ s₅ s₆ s₇
174
- ```
143
+ peak-to-RMS of a pure sine) regardless of M.
175
144
 
176
145
  Why single-tone:
177
146
 
178
147
  - **All transmit power in one tone at a time** — maximum per-symbol SNR, no `1/N` power split across a stack.
179
- - **Higher N buys log₂(N) bits/symbol** (more throughput) without any PAPR cost. 16-FSK carries 4× the bits of 2-FSK at the same baud, same peak power.
148
+ - **Higher M buys log₂(M) bits/symbol** (more throughput) without any PAPR cost. 16-FSK carries 4× the bits of 2-FSK at the same baud, same peak power.
180
149
 
181
150
  On an SDR waterfall you'll see all N frequency slots "lit up" during a long transmission — that's the display integrating over time, showing every slot the modem visited. Zoom the FFT window below one symbol duration (~3.3 ms at 300 baud) and you'll see the transmitter chasing one tone across the slots instead.
182
151
 
@@ -197,7 +166,7 @@ On an SDR waterfall you'll see all N frequency slots "lit up" during a long tran
197
166
  ## Full SNR sweep
198
167
 
199
168
  Every baud × num_tones × RS × repeats combo is measured in
200
- [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.2/results.md). Re-run `poetry run weaklink-modem-benchmark` to
169
+ [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.4/results.md). Re-run `poetry run weaklink-modem-benchmark` to
201
170
  refresh.
202
171
 
203
172
  ---
@@ -219,7 +188,7 @@ refresh.
219
188
  | Flag | Default | Description |
220
189
  |------|---------|-------------|
221
190
  | `--modem-baud N` | `300` | Symbol rate. Only `45`, `300`, `1200` supported. |
222
- | `--modem-num-tones N` | `4` | N-FSK order: 2 / 4 / 8 / 16. Higher packs more bits per symbol at wider bandwidth and worse cliff. 2 halves throughput but fits narrow audio paths (e.g. FM voice via SignaLink). TX and RX must match. |
191
+ | `--modem-num-tones N` | `4` | MFSK order: 2 / 4 / 8 / 16. Higher packs more bits per symbol at wider bandwidth and worse cliff. 2 halves throughput but fits narrow audio paths (e.g. FM voice via SignaLink). TX and RX must match. |
223
192
  | `--modem-rs-data-bytes N` | preset | Reed-Solomon data bytes per block. |
224
193
  | `--modem-rs-parity-bytes N` | preset | RS parity bytes. Corrects up to N/2 byte errors per block. |
225
194
  | `--modem-no-rs-crc` | CRC on | Skip the CRC-32 inside each RS block. |
@@ -283,7 +252,7 @@ One tx session (live audio):
283
252
  │ pilot │ pre │ slot 0 │ pre │ slot 1 │ pre │ ... │slot N-1│ pre │ pilot │
284
253
  └────────┴─────┴────────┴─────┴────────┴─────┴─────┴────────┴─────┴────────┘
285
254
 
286
- One RS block, data area (before conv + interleave + FSK):
255
+ One RS block, data area (before conv + interleave + MFSK):
287
256
 
288
257
  ┌── 1B ──┬── 2B ────┬──── rs_data − 3 B ────┬── 4B CRC ──┬── rs_parity B ──┐
289
258
  │ length │block_idx │ payload (zero-padded) │ CRC-32 │ RS parity │
@@ -307,10 +276,11 @@ through the same `_StreamingRxPump` the CLI uses.
307
276
 
308
277
  ## Glossary
309
278
 
310
- - **N-FSK / CPFSK** — Continuous-phase FSK with an **N-value alphabet**: N possible tone frequencies, exactly one on the air at any instant (never a stack). Each symbol picks one → log₂(N) bits/symbol. Default N=4.
279
+ - **MFSK** — M-ary Frequency Shift Keying. M possible tone frequencies; each symbol picks one and emits it → log₂(M) bits/symbol. Exactly one tone on the air at any instant (never a stack). Default M=4.
280
+ - **CPFSK** — Continuous-Phase FSK. Frequency changes between symbols with no phase discontinuity, so the envelope stays clean at symbol boundaries. This modem's MFSK is CPFSK.
311
281
  - **Single-tone-at-a-time** — The whole family from OOK through 16-FSK emits at most one sinusoid at any instant. Envelope stays constant (PAPR = 3 dB, same as a pure sine) regardless of `--modem-num-tones`.
312
282
  - **OOK** — On-off keying. `num_tones=1` mode: single carrier, symbol 0 = silence, symbol 1 = tone. 1 bit/symbol like 2-FSK but at the narrowest possible bandwidth (only the carrier + modulation sidelobes). Pays a few dB vs 2-FSK in AWGN.
313
- - **PAPR** — Peak-to-average power ratio. 0 dB = pure DC; 3 dB = pure sine; ~10·log₁₀(N) dB = N tones summed with random phases. This modem stays at 3 dB.
283
+ - **PAPR** — Peak-to-average power ratio. 0 dB = pure DC; 3 dB = pure sine; ~10·log₁₀(M) dB = M tones summed with random phases. This modem stays at 3 dB.
314
284
  - **Preamble** — Fixed 32-symbol PN sequence bracketing every slot; RX locks timing / frequency / amplitude from it.
315
285
  - **Slot** — Preamble + one RS-encoded block.
316
286
  - **Block** — RS-encoded chunk carrying header + payload.
@@ -321,12 +291,12 @@ through the same `_StreamingRxPump` the CLI uses.
321
291
  - **Interleaver** — Bit shuffle so bursts become isolated errors. Ours changes every block (32-permutation cycle).
322
292
  - **Non-coherent demod** — Tone detection by energy; ~3 dB behind coherent.
323
293
  - **LO offset** — Radio frequency error; we correct up to ±500 Hz.
324
- - **Pilot** — Short random N-FSK burst before / after every live TX.
294
+ - **Pilot** — Short random MFSK burst before / after every live TX.
325
295
  - **SNR (dB)** — Signal-to-noise ratio. Negative = noise louder than signal.
326
296
  - **AWGN** — Additive white Gaussian noise; the standard clean-channel noise model used by the benchmark.
327
297
  - **Shannon limit** — Theoretical lowest SNR at which a given data rate can be decoded error-free. Every FEC decoder sits some dB above it — that gap is what "Gap" columns report.
328
298
  - **Best SNR / cliff** — Lowest SNR at which decode still works for a given config. Below it, everything breaks.
329
- - **Nyquist theorem** — A signal is only recoverable if sampled above twice its highest frequency. In practice: tones must sit below sample_rate/2, and N-FSK tone spacing must be at least `1/T_symbol` for non-coherent orthogonality.
299
+ - **Nyquist theorem** — A signal is only recoverable if sampled above twice its highest frequency. In practice: tones must sit below sample_rate/2, and MFSK tone spacing must be at least `1/T_symbol` for non-coherent orthogonality.
330
300
 
331
301
  ---
332
302
 
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "weaklink-modem"
3
- version = "0.7.2"
4
- description = "N-FSK streaming modem for HF SSB and wider channels: Reed-Solomon framing, soft Viterbi, round-robin block repetition, byte-pipe semantics."
3
+ version = "0.7.4"
4
+ description = "MFSK streaming modem for HF SSB and wider channels: Reed-Solomon framing, soft Viterbi, round-robin block repetition, byte-pipe semantics."
5
5
  authors = ["ivica3730k"]
6
6
  license = "MIT"
7
7
  readme = "readme-pypi.md"
@@ -1,4 +1,4 @@
1
- <!-- generated from README.md at tag v0.7.2 by scripts/build_pypi_readme.py; do not hand-edit -->
1
+ <!-- generated from README.md at tag v0.7.4 by scripts/build_pypi_readme.py; do not hand-edit -->
2
2
 
3
3
  # weaklink
4
4
 
@@ -8,7 +8,7 @@ with `tail -f`; no memory buffering, no wait-for-EOF. RS + convolutional
8
8
  soft-LLR combining. Modes: **OOK (1-tone)** through **16-FSK**, at
9
9
  **45 / 300 / 1200 baud**.
10
10
 
11
- ![alt text](https://github.com/ivica3730k/weaklink-9a3ice/raw/v0.7.2/image.png)
11
+ ![alt text](https://github.com/ivica3730k/weaklink-9a3ice/raw/v0.7.4/image.png)
12
12
 
13
13
  Distribution: `weaklink-modem` (PyPI + release binaries).
14
14
 
@@ -92,7 +92,7 @@ cross-baud comparison convention, not a physical channel filter.
92
92
 
93
93
  Doubling `--modem-block-repeats` buys ~2–3 dB via soft-LLR combining
94
94
  at proportional air time. Full sweep of every combo we test is in
95
- [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.2/results.md).
95
+ [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.4/results.md).
96
96
 
97
97
  ### OOK / 1-tone mode
98
98
 
@@ -102,52 +102,21 @@ bandwidth of any mode (no tone stack — just the carrier and its
102
102
  modulation sidelobes). Same 1 bit/symbol as 2-FSK but a few dB worse
103
103
  in AWGN — the trade you make for the narrow spectrum. Cliff at
104
104
  45 baud with `block_repeats=4`: ≈ −14 dB, matching 4-FSK at the same
105
- settings. See [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.2/results.md) for the per-baud numbers.
105
+ settings. See [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.4/results.md) for the per-baud numbers.
106
106
 
107
- ### One tone at a time (constant envelope, any N)
107
+ ### One tone at a time (constant envelope, any M)
108
108
 
109
109
  Every mode in this modem is **single-tone-at-a-time CPFSK**.
110
110
  `--modem-num-tones 16` means "16 possible frequencies to pick from
111
111
  per symbol", **not** "16 frequencies playing simultaneously". The
112
112
  transmitter emits exactly one sinusoid at any instant, hopping between
113
113
  frequencies at the symbol clock. Constant envelope (PAPR = 3 dB, the
114
- peak-to-RMS of a pure sine) regardless of N.
115
-
116
- Time-frequency view of a 4-FSK burst — each column is one symbol,
117
- each row is one of the 4 available frequencies:
118
-
119
- ```
120
- freq
121
-
122
- F₃ │ . ▓ . . . ▓ . .
123
- F₂ │ . . . ▓ . . . .
124
- F₁ │ ▓ . . . . . ▓ .
125
- F₀ │ . . ▓ . ▓ . . ▓
126
- └────────────────────────→ time
127
- s₀ s₁ s₂ s₃ s₄ s₅ s₆ s₇
128
-
129
- Exactly one ▓ per column: at any instant, one tone at full amplitude.
130
- Over 8 symbols the modem has visited all 4 slots (that's what you see
131
- on an SDR waterfall over time), but never more than one at once.
132
- ```
133
-
134
- vs. what parallel multi-tone (OFDM-style; **not** this modem) would do:
135
-
136
- ```
137
- freq
138
-
139
- F₃ │ ▓ . ▓ ▓ . ▓ . ▓
140
- F₂ │ . ▓ ▓ . ▓ . ▓ ▓ Multiple ▓ per column: N tones
141
- F₁ │ ▓ ▓ . ▓ . ▓ ▓ . summed at each instant. PAPR grows
142
- F₀ │ ▓ . ▓ . ▓ ▓ . ▓ with N (~10·log₁₀(N)).
143
- └────────────────────────→ time
144
- s₀ s₁ s₂ s₃ s₄ s₅ s₆ s₇
145
- ```
114
+ peak-to-RMS of a pure sine) regardless of M.
146
115
 
147
116
  Why single-tone:
148
117
 
149
118
  - **All transmit power in one tone at a time** — maximum per-symbol SNR, no `1/N` power split across a stack.
150
- - **Higher N buys log₂(N) bits/symbol** (more throughput) without any PAPR cost. 16-FSK carries 4× the bits of 2-FSK at the same baud, same peak power.
119
+ - **Higher M buys log₂(M) bits/symbol** (more throughput) without any PAPR cost. 16-FSK carries 4× the bits of 2-FSK at the same baud, same peak power.
151
120
 
152
121
  On an SDR waterfall you'll see all N frequency slots "lit up" during a long transmission — that's the display integrating over time, showing every slot the modem visited. Zoom the FFT window below one symbol duration (~3.3 ms at 300 baud) and you'll see the transmitter chasing one tone across the slots instead.
153
122
 
@@ -168,7 +137,7 @@ On an SDR waterfall you'll see all N frequency slots "lit up" during a long tran
168
137
  ## Full SNR sweep
169
138
 
170
139
  Every baud × num_tones × RS × repeats combo is measured in
171
- [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.2/results.md). Re-run `poetry run weaklink-modem-benchmark` to
140
+ [`results.md`](https://github.com/ivica3730k/weaklink-9a3ice/blob/v0.7.4/results.md). Re-run `poetry run weaklink-modem-benchmark` to
172
141
  refresh.
173
142
 
174
143
  ---
@@ -190,7 +159,7 @@ refresh.
190
159
  | Flag | Default | Description |
191
160
  |------|---------|-------------|
192
161
  | `--modem-baud N` | `300` | Symbol rate. Only `45`, `300`, `1200` supported. |
193
- | `--modem-num-tones N` | `4` | N-FSK order: 2 / 4 / 8 / 16. Higher packs more bits per symbol at wider bandwidth and worse cliff. 2 halves throughput but fits narrow audio paths (e.g. FM voice via SignaLink). TX and RX must match. |
162
+ | `--modem-num-tones N` | `4` | MFSK order: 2 / 4 / 8 / 16. Higher packs more bits per symbol at wider bandwidth and worse cliff. 2 halves throughput but fits narrow audio paths (e.g. FM voice via SignaLink). TX and RX must match. |
194
163
  | `--modem-rs-data-bytes N` | preset | Reed-Solomon data bytes per block. |
195
164
  | `--modem-rs-parity-bytes N` | preset | RS parity bytes. Corrects up to N/2 byte errors per block. |
196
165
  | `--modem-no-rs-crc` | CRC on | Skip the CRC-32 inside each RS block. |
@@ -254,7 +223,7 @@ One tx session (live audio):
254
223
  │ pilot │ pre │ slot 0 │ pre │ slot 1 │ pre │ ... │slot N-1│ pre │ pilot │
255
224
  └────────┴─────┴────────┴─────┴────────┴─────┴─────┴────────┴─────┴────────┘
256
225
 
257
- One RS block, data area (before conv + interleave + FSK):
226
+ One RS block, data area (before conv + interleave + MFSK):
258
227
 
259
228
  ┌── 1B ──┬── 2B ────┬──── rs_data − 3 B ────┬── 4B CRC ──┬── rs_parity B ──┐
260
229
  │ length │block_idx │ payload (zero-padded) │ CRC-32 │ RS parity │
@@ -278,10 +247,11 @@ through the same `_StreamingRxPump` the CLI uses.
278
247
 
279
248
  ## Glossary
280
249
 
281
- - **N-FSK / CPFSK** — Continuous-phase FSK with an **N-value alphabet**: N possible tone frequencies, exactly one on the air at any instant (never a stack). Each symbol picks one → log₂(N) bits/symbol. Default N=4.
250
+ - **MFSK** — M-ary Frequency Shift Keying. M possible tone frequencies; each symbol picks one and emits it → log₂(M) bits/symbol. Exactly one tone on the air at any instant (never a stack). Default M=4.
251
+ - **CPFSK** — Continuous-Phase FSK. Frequency changes between symbols with no phase discontinuity, so the envelope stays clean at symbol boundaries. This modem's MFSK is CPFSK.
282
252
  - **Single-tone-at-a-time** — The whole family from OOK through 16-FSK emits at most one sinusoid at any instant. Envelope stays constant (PAPR = 3 dB, same as a pure sine) regardless of `--modem-num-tones`.
283
253
  - **OOK** — On-off keying. `num_tones=1` mode: single carrier, symbol 0 = silence, symbol 1 = tone. 1 bit/symbol like 2-FSK but at the narrowest possible bandwidth (only the carrier + modulation sidelobes). Pays a few dB vs 2-FSK in AWGN.
284
- - **PAPR** — Peak-to-average power ratio. 0 dB = pure DC; 3 dB = pure sine; ~10·log₁₀(N) dB = N tones summed with random phases. This modem stays at 3 dB.
254
+ - **PAPR** — Peak-to-average power ratio. 0 dB = pure DC; 3 dB = pure sine; ~10·log₁₀(M) dB = M tones summed with random phases. This modem stays at 3 dB.
285
255
  - **Preamble** — Fixed 32-symbol PN sequence bracketing every slot; RX locks timing / frequency / amplitude from it.
286
256
  - **Slot** — Preamble + one RS-encoded block.
287
257
  - **Block** — RS-encoded chunk carrying header + payload.
@@ -292,12 +262,12 @@ through the same `_StreamingRxPump` the CLI uses.
292
262
  - **Interleaver** — Bit shuffle so bursts become isolated errors. Ours changes every block (32-permutation cycle).
293
263
  - **Non-coherent demod** — Tone detection by energy; ~3 dB behind coherent.
294
264
  - **LO offset** — Radio frequency error; we correct up to ±500 Hz.
295
- - **Pilot** — Short random N-FSK burst before / after every live TX.
265
+ - **Pilot** — Short random MFSK burst before / after every live TX.
296
266
  - **SNR (dB)** — Signal-to-noise ratio. Negative = noise louder than signal.
297
267
  - **AWGN** — Additive white Gaussian noise; the standard clean-channel noise model used by the benchmark.
298
268
  - **Shannon limit** — Theoretical lowest SNR at which a given data rate can be decoded error-free. Every FEC decoder sits some dB above it — that gap is what "Gap" columns report.
299
269
  - **Best SNR / cliff** — Lowest SNR at which decode still works for a given config. Below it, everything breaks.
300
- - **Nyquist theorem** — A signal is only recoverable if sampled above twice its highest frequency. In practice: tones must sit below sample_rate/2, and N-FSK tone spacing must be at least `1/T_symbol` for non-coherent orthogonality.
270
+ - **Nyquist theorem** — A signal is only recoverable if sampled above twice its highest frequency. In practice: tones must sit below sample_rate/2, and MFSK tone spacing must be at least `1/T_symbol` for non-coherent orthogonality.
301
271
 
302
272
  ---
303
273
 
@@ -1,4 +1,4 @@
1
- """Weaklink modem: N-FSK + convolutional FEC + soft Viterbi, audio I/O."""
1
+ """Weaklink modem: MFSK + convolutional FEC + soft Viterbi, audio I/O."""
2
2
 
3
3
  from weaklink.modem.api import ModemOptions, build_config, rx, tx
4
4
  from weaklink.modem.codec import ModemConfig
@@ -119,7 +119,7 @@ def _find_cliff(config: Config, *, trials: int, payload: bytes) -> Result:
119
119
  shannon = shannon_snr_db(info_rate)
120
120
 
121
121
  cliff: float | None = None
122
- # OOK's cliff sits ~10 dB higher than N-FSK, so the sweep needs a
122
+ # OOK's cliff sits ~10 dB higher than MFSK, so the sweep needs a
123
123
  # higher starting SNR to see it. Everything else still starts at
124
124
  # +10 dB, matching the previous behaviour.
125
125
  snr_db = 25.0 if config.num_tones == 1 else 10.0
@@ -64,7 +64,7 @@ def _add_modem_args(sub: argparse.ArgumentParser) -> None:
64
64
  default=None,
65
65
  dest="modem_num_tones",
66
66
  choices=(1, 2, 4, 8, 16),
67
- help="Number of FSK tones. 4 (default) is standard; 8/16 pack "
67
+ help="Number of MFSK tones. 4 (default) is standard; 8/16 pack "
68
68
  "more bits per symbol at wider bandwidth and worse cliff. 2 "
69
69
  "halves throughput but fits narrow audio paths. 1 selects OOK "
70
70
  "(single carrier, on/off keying) -- narrowest bandwidth of any "
@@ -128,7 +128,7 @@ def _build_parser() -> argparse.ArgumentParser:
128
128
  except PackageNotFoundError:
129
129
  version = "unknown"
130
130
 
131
- parser = argparse.ArgumentParser(prog="weaklink-modem", description="Streaming N-FSK modem.")
131
+ parser = argparse.ArgumentParser(prog="weaklink-modem", description="Streaming MFSK modem.")
132
132
  parser.add_argument("--version", action="version", version=f"weaklink-modem {version}")
133
133
  subparsers = parser.add_subparsers(dest="direction", required=True)
134
134
  tx_parser = subparsers.add_parser("tx", help="Encode stdin bytes and transmit (or write to WAV).")
@@ -111,7 +111,7 @@ def _preamble_deterministic_sidelobe(num_tones: int) -> float:
111
111
  max_score = max(max_score, abs(numerator / denominator))
112
112
  return max_score
113
113
 
114
- # N-FSK correlator: score = (M * wanted - total) / ((M-1) * total).
114
+ # MFSK correlator: score = (M * wanted - total) / ((M-1) * total).
115
115
  for shift in range(1, length):
116
116
  magnitudes = np.zeros((length, num_tones), dtype=np.float64)
117
117
  # Positions inside the overlap carry the shifted preamble's tone.
@@ -820,7 +820,7 @@ def _find_preamble_peaks(
820
820
  mag_col = magnitudes[:, 0]
821
821
  raw = np.correlate(mag_col, tone_mask, mode="valid") - np.correlate(mag_col, silence_mask, mode="valid")
822
822
  else:
823
- # N-FSK: for each tone k, mask marks where the preamble expects
823
+ # MFSK: for each tone k, mask marks where the preamble expects
824
824
  # that tone; sum the wanted energy across all N tones.
825
825
  wanted = np.zeros(max_offset + 1, dtype=np.float64)
826
826
  for tone in range(num_tones):
@@ -846,7 +846,7 @@ def _find_preamble_peaks(
846
846
  cutoff = float(np.quantile(abs_scores, 0.75))
847
847
  noise_pool = scores[abs_scores <= cutoff]
848
848
  else:
849
- # N-FSK noise floor: bottom (2/M) of scores (adaptive quantile).
849
+ # MFSK noise floor: bottom (2/M) of scores (adaptive quantile).
850
850
  # At M=2 sidelobes fill the lower half, so we go lower (Q0.25).
851
851
  # At higher M sidelobes are tighter and the lower half is fine.
852
852
  q = max(0.25, 1.0 - 2.0 / num_tones)
@@ -24,7 +24,7 @@ BAUD_PRESETS: dict[float, dict[str, float]] = {
24
24
 
25
25
  #: Pilot padding on each side of live-tx: brings the audio sink out
26
26
  #: of its IDLE state (~50 ms of wake-up latency) and gives the
27
- #: coarse-offset FFT real N-FSK tone energy to lock onto.
27
+ #: coarse-offset FFT real MFSK tone energy to lock onto.
28
28
  LIVE_TX_PILOT_MIN_SECONDS: float = 0.2
29
29
 
30
30
  #: Pilot must also exceed the preamble in symbol space so back-to-back
@@ -18,7 +18,7 @@ class ConfigError(WeaklinkError):
18
18
 
19
19
 
20
20
  class NyquistError(ConfigError):
21
- """N-FSK tone stack won't fit under sample_rate/2, or spacing is
21
+ """MFSK tone stack won't fit under sample_rate/2, or spacing is
22
22
  below the non-coherent orthogonality floor."""
23
23
 
24
24
 
@@ -29,7 +29,7 @@ _log = logging.getLogger("weaklink.streaming")
29
29
 
30
30
 
31
31
  def pilot_signal(config: ModemConfig, duration_seconds: float) -> np.ndarray:
32
- """Random N-FSK symbols for ``duration_seconds``. All tones exercised
32
+ """Random MFSK symbols for ``duration_seconds``. All tones exercised
33
33
  uniformly so the coarse-offset FFT locks cleanly."""
34
34
  symbols_needed = max(1, int(round(duration_seconds * config.waveform.baud)))
35
35
  rng = np.random.default_rng(0xC0DE)
@@ -1,4 +1,4 @@
1
- """N-FSK CPFSK modulator + non-coherent soft demodulator.
1
+ """MFSK CPFSK modulator + non-coherent soft demodulator.
2
2
 
3
3
  Continuous-phase for narrow spectrum, non-coherent I/Q magnitudes (~3 dB
4
4
  worse than coherent but no carrier recovery). Gray-coded symbols so
@@ -25,7 +25,7 @@ NUM_TONES = 4
25
25
  def _num_symbols(num_tones: int) -> int:
26
26
  """Size of the symbol alphabet. For ``num_tones=1`` we run OOK
27
27
  (1 frequency, 2 symbols: silence + tone); otherwise symbols and
28
- tones are 1:1 as in standard N-FSK."""
28
+ tones are 1:1 as in standard MFSK."""
29
29
  return 2 if num_tones == 1 else num_tones
30
30
 
31
31
 
@@ -70,7 +70,7 @@ class WaveformConfig:
70
70
  amplitude: float = 0.25
71
71
  """Peak amplitude, well under 1.0 to leave headroom in WAV / audio devices."""
72
72
  num_tones: int = 4
73
- """Number of tone frequencies. Standard N-FSK for values in
73
+ """Number of tone frequencies. Standard MFSK for values in
74
74
  {2, 4, 8, 16}: each tone is one symbol. ``num_tones=1`` selects
75
75
  OOK -- one carrier, symbol 0 = silence, symbol 1 = tone --
76
76
  which trades throughput for the narrowest bandwidth possible."""
@@ -83,7 +83,7 @@ class WaveformConfig:
83
83
  def __post_init__(self) -> None:
84
84
  if self.num_tones != 1 and (self.num_tones < 2 or (self.num_tones & (self.num_tones - 1)) != 0):
85
85
  raise ConfigError(f"num_tones must be 1 or a power of 2 >= 2, got {self.num_tones}")
86
- # OOK sits on a single carrier at ``center_hz``; N-FSK spreads
86
+ # OOK sits on a single carrier at ``center_hz``; MFSK spreads
87
87
  # symmetrically around ``center_hz`` at ``tone_spacing_hz``.
88
88
  if self.num_tones == 1:
89
89
  relative_offsets: tuple[float, ...] = (0.0,)
@@ -114,7 +114,7 @@ class WaveformConfig:
114
114
 
115
115
  @property
116
116
  def num_symbols(self) -> int:
117
- """Symbol-alphabet size. Equals ``num_tones`` for standard N-FSK;
117
+ """Symbol-alphabet size. Equals ``num_tones`` for standard MFSK;
118
118
  equals 2 for OOK (``num_tones=1``)."""
119
119
  return _num_symbols(self.num_tones)
120
120
 
@@ -142,7 +142,7 @@ def symbols_to_bits(symbols: np.ndarray, num_tones: int = NUM_TONES) -> bytes:
142
142
 
143
143
 
144
144
  def modulate(symbols: np.ndarray, config: WaveformConfig) -> np.ndarray:
145
- """Vectorised modulator: continuous-phase N-FSK for ``num_tones >= 2``;
145
+ """Vectorised modulator: continuous-phase MFSK for ``num_tones >= 2``;
146
146
  on-off keying for ``num_tones == 1`` (symbol 0 = silence, symbol 1 =
147
147
  continuous-phase sine at the carrier)."""
148
148
  samples_per_symbol = config.samples_per_symbol
@@ -164,7 +164,7 @@ def modulate(symbols: np.ndarray, config: WaveformConfig) -> np.ndarray:
164
164
  gate = symbols_int.astype(np.float64)[:, None]
165
165
  return (config.amplitude * gate * carrier).ravel().astype(np.float32)
166
166
 
167
- # Standard N-FSK: per-symbol frequency + continuous phase.
167
+ # Standard MFSK: per-symbol frequency + continuous phase.
168
168
  omega_per_symbol = 2.0 * np.pi * np.asarray(config.tones_hz, dtype=np.float64)[symbols_int] * dt
169
169
  # Phase at the START of each symbol = 0 for i=0, cumsum(omega*sps) shifted for i>=1.
170
170
  end_phases = np.cumsum(omega_per_symbol * samples_per_symbol)
File without changes