TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
PSKEncoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4
5#include "AudioTools.h"
6#include "ModemBase.h"
7#include "PSK.h"
8
9namespace tinymodem {
10
27class PSKEncoder : public ModemEncoder {
28 public:
29 PSKEncoder() = default;
30 PSKEncoder(Print& out) { setOutput(out); }
31
34 PSKConfig result;
35 result.setMode("psk31");
36 return result;
37 }
38
39 void setOutput(Print& out) override {
40 samples.setOutput(out);
41 ModemEncoder::setOutput(out); // writeSilence(), writeTone()
42 }
43
45 void setAudioInfo(audio_tools::AudioInfo info) override {
46 cfg.sample_rate = info.sample_rate;
47 cfg.channels = info.channels;
48 cfg.bits_per_sample = info.bits_per_sample;
49 if (is_active) begin();
50 }
51
52 audio_tools::AudioInfo audioInfo() override { return cfg; }
53
54 using audio_tools::AudioEncoder::begin;
55
58 cfg = config;
59 return begin();
60 }
61
62 bool begin() override {
63 is_active = false;
64 if (!cfg.isValid()) return false;
65 if (cfg.bits_per_sample != 16) {
66 LOGE("PSKEncoder: bits_per_sample must be 16, not %d",
67 (int)cfg.bits_per_sample);
68 return false;
69 }
70 if (cfg.channels < 1) {
71 LOGE("PSKEncoder: invalid number of channels: %d", (int)cfg.channels);
72 return false;
73 }
74 samples.begin(cfg.channels, cfg.volume);
75 if (!tones.begin(cfg, cfg.volume)) return false;
76 samples_per_symbol = cfg.samplesPerSymbol();
77 double w = 2.0 * M_PI * cfg.carrier_freq / cfg.sample_rate;
78 step_re = cos(w);
79 step_im = sin(w);
80 code.reset();
81 transmitting = false;
82 is_active = true;
83 return true;
84 }
85
86 void end() override {
88 is_active = false;
89 }
90
92 size_t write(const uint8_t* data, size_t len) override {
93 if (!is_active) return 0;
94 for (size_t j = 0; j < len; j++) {
96 sendChar(data[j]);
97 }
98 samples.flush();
99 return len;
100 }
101
104 bool endTransmission(size_t silence_samples = 0) override {
105 if (!is_active || !transmitting) return false;
106 switch (cfg.modulation) {
108 // flushes the convolutional code
109 for (int j = 0; j < cfg.postamble_symbols; j++) sendBit(0);
110 break;
112 for (int j = 0; j < 3; j++) sendChar(0);
113 bit_count = 0;
114 for (int j = 0; j < cfg.postamble_symbols; j++) sendStep(STEADY);
115 break;
116 default:
117 for (int j = 0; j < cfg.postamble_symbols; j++) sendBit(1);
118 break;
119 }
121 for (size_t j = 0; j < silence_samples; j++) samples.write(0.0f);
122 samples.flush();
123 transmitting = false;
124 return true;
125 }
126
129 bool writeIdle(size_t nsamples) override {
130 if (!is_active) return false;
132 size_t n = nsamples / samples_per_symbol + 1;
133 if (cfg.modulation == PSKModulation::PSK8) {
134 // 11 bits per character
135 for (size_t j = 0; j < n * 3 / 11 + 1; j++) sendChar(0);
136 } else {
137 for (size_t j = 0; j < n; j++) sendBit(0);
138 }
139 samples.flush();
140 return true;
141 }
142
144 bool isTransmitting() override { return transmitting; }
145
147 float symbolRate() override { return cfg.symbol_rate; }
148
150 PSKConfig& config() { return cfg; }
151
152 operator bool() override { return is_active; }
153
154 protected:
157 static constexpr int REVERSAL = 0;
158 static constexpr int STEADY = 8;
162 bool is_active = false;
163 bool transmitting = false;
165 uint64_t symbol_index = 0;
166 // current constellation point
167 float point_re = 1.0f, point_im = 0.0f;
168 // 8PSK: collected bits
169 int bits = 0, bit_count = 0;
170 // carrier oscillator (rotating phasor)
171 float re = 1.0f, im = 0.0f;
172 float step_re = 1.0f, step_im = 0.0f;
173 uint32_t sample_count = 0;
174
176 transmitting = true;
177 symbol_index = 0;
178 point_re = 1.0f;
179 point_im = 0.0f;
180 bit_count = 0;
182 for (int j = 0; j < cfg.preamble_symbols; j++) sendStep(REVERSAL);
183 // 8PSK: the character 0 marks the start of the data
184 if (cfg.modulation == PSKModulation::PSK8) sendChar(0);
185 }
186
187 void sendChar(uint8_t ch) {
188 if (cfg.modulation == PSKModulation::PSK8) {
190 for (int b = c.length - 1; b >= 0; b--) sendBit((c.bits >> b) & 1);
191 return;
192 }
194 for (int b = c.length - 1; b >= 0; b--) sendBit((c.bits >> b) & 1);
195 sendBit(0);
196 sendBit(0);
197 }
198
199 void sendBit(int bit) {
200 switch (cfg.modulation) {
201 case PSKModulation::QPSK: {
202 int symbol = code.encode(bit);
203 sendStep(((4 - symbol) & 3) * 4);
204 break;
205 }
207 // the first bit is the least significant bit of the symbol
208 if (bit_count == 0) bits = 0;
209 bits |= bit << bit_count;
210 if (++bit_count == 3) {
211 sendStep(bits * 2);
212 bit_count = 0;
213 }
214 break;
215 default:
216 // 0: phase reversal, 1: same phase
217 sendStep(bit ? STEADY : REVERSAL);
218 break;
219 }
220 }
221
222 void sendStep(int step) { sendSymbol(step, Shape::TRANSITION); }
223
226 void sendSymbol(int step, Shape shape) {
227 float prev_re = point_re, prev_im = point_im;
228 if (shape == Shape::TRANSITION) {
229 float angle = (float)M_PI + step * (float)M_PI / 8;
230 float c = cosf(angle), s = sinf(angle);
231 // round to the exact values of the 8 main positions
232 if (fabsf(c) < 1e-6f) c = 0;
233 if (fabsf(s) < 1e-6f) s = 0;
234 point_re = prev_re * c - prev_im * s;
235 point_im = prev_re * s + prev_im * c;
236 }
237 size_t start = llround(symbol_index * samples_per_symbol);
238 size_t stop = llround((symbol_index + 1) * samples_per_symbol);
239 symbol_index++;
240 size_t n = stop - start;
241 for (size_t j = 0; j < n; j++) {
242 float t = (float)j / n; // 0 to 1 within the symbol
243 float i_value, q_value;
244 if (shape == Shape::TRANSITION) {
245 // cosine shaped transition from the previous point
246 float a = 0.5f + 0.5f * cosf((float)M_PI * t);
247 i_value = a * prev_re + (1.0f - a) * point_re;
248 q_value = a * prev_im + (1.0f - a) * point_im;
249 } else {
250 float a = shape == Shape::FADE_IN ? sinf((float)M_PI / 2 * t)
251 : cosf((float)M_PI / 2 * t);
252 i_value = a * point_re;
253 q_value = a * point_im;
254 }
255 // like fldigi: I * cos + Q * sin
256 samples.write(i_value * re + q_value * im);
257 nextCarrier();
258 }
259 }
260
261 void nextCarrier() {
262 float r = re * step_re - im * step_im;
263 im = re * step_im + im * step_re;
264 re = r;
265 if (++sample_count % 1024 == 0) {
266 float mag = sqrtf(re * re + im * im);
267 re /= mag;
268 im /= mag;
269 }
270 }
271};
272
273} // namespace tinymodem
static Code encode(uint8_t ch)
Code of the indicated byte.
Definition PSK.h:236
Common API of the modulators (FSKEncoder, MFSKEncoder): the written data is converted to 16 bit PCM a...
Definition ModemBase.h:286
void setOutput(Print &out) override
Definition ModemBase.h:288
ToneOutput tones
Definition ModemBase.h:334
bool is_active
Definition PSKEncoder.h:162
void sendStep(int step)
Definition PSKEncoder.h:222
float point_im
Definition PSKEncoder.h:167
Shape
Definition PSKEncoder.h:155
@ FADE_IN
Definition PSKEncoder.h:155
@ TRANSITION
Definition PSKEncoder.h:155
@ FADE_OUT
Definition PSKEncoder.h:155
QPSKCode code
Definition PSKEncoder.h:161
void nextCarrier()
Definition PSKEncoder.h:261
SampleOutput samples
Definition PSKEncoder.h:160
bool begin(PSKConfig config)
Starts the processing with the indicated configuration.
Definition PSKEncoder.h:57
static constexpr int STEADY
Definition PSKEncoder.h:158
void sendSymbol(int step, Shape shape)
Definition PSKEncoder.h:226
void startTransmission()
Definition PSKEncoder.h:175
bool begin() override
Definition PSKEncoder.h:62
bool transmitting
Definition PSKEncoder.h:163
PSKConfig cfg
Definition PSKEncoder.h:159
uint64_t symbol_index
Definition PSKEncoder.h:165
int bits
Definition PSKEncoder.h:169
void sendBit(int bit)
Definition PSKEncoder.h:199
double samples_per_symbol
Definition PSKEncoder.h:164
audio_tools::AudioInfo audioInfo() override
Definition PSKEncoder.h:52
static constexpr int REVERSAL
phase steps as index of fldigi's 16 PSK positions (180 + n * 22.5 deg)
Definition PSKEncoder.h:157
void end() override
Definition PSKEncoder.h:86
bool writeIdle(size_t nsamples) override
Definition PSKEncoder.h:129
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the sample rate and number of channels of the generated audio.
Definition PSKEncoder.h:45
void setOutput(Print &out) override
Definition PSKEncoder.h:39
float im
Definition PSKEncoder.h:171
float point_re
Definition PSKEncoder.h:167
bool endTransmission(size_t silence_samples=0) override
Definition PSKEncoder.h:104
void sendChar(uint8_t ch)
Definition PSKEncoder.h:187
int bit_count
Definition PSKEncoder.h:169
float re
Definition PSKEncoder.h:171
PSKConfig & config()
The configuration which was provided in begin().
Definition PSKEncoder.h:150
PSKConfig defaultConfig()
Provides a default configuration: psk31.
Definition PSKEncoder.h:33
PSKEncoder(Print &out)
Definition PSKEncoder.h:30
float symbolRate() override
Symbols per second.
Definition PSKEncoder.h:147
float step_re
Definition PSKEncoder.h:172
float step_im
Definition PSKEncoder.h:172
size_t write(const uint8_t *data, size_t len) override
Modulates the bytes.
Definition PSKEncoder.h:92
bool isTransmitting() override
true between the first byte and endTransmission()
Definition PSKEncoder.h:144
uint32_t sample_count
Definition PSKEncoder.h:173
Rate 1/2 convolutional code with K=5 (polynomials 0x17 and 0x19) of QPSK31 (fldigi) and a soft decisi...
Definition PSK.h:302
Writes float samples (-1.0 to 1.0) as 16 bit PCM with the requested number of channels and volume to ...
Definition ModemBase.h:118
static Code encode(uint8_t ch)
Code of the indicated byte.
Definition PSK.h:164
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
@ QPSK
Definition PSK.h:18
@ PSK8
3 bits per symbol without FEC, MFSK Varicode
Definition PSK.h:20
Configuration of the PSK modem (PSKEncoder, PSKDecoder): differential PSK with cosine shaped transiti...
Definition PSK.h:34
bool setMode(const char *mode)
Definition PSK.h:66
Varicode bits of a character.
Definition PSK.h:158
uint8_t length
Definition PSK.h:160
uint16_t bits
Definition PSK.h:159