TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
CSSEncoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4
5#include "AudioTools.h"
6#include "CSS.h"
7#include "ModemBase.h"
8
9namespace tinymodem {
10
20class CSSEncoder : public ModemEncoder {
21 public:
22 CSSEncoder() = default;
23 CSSEncoder(Print& out) { setOutput(out); }
24
27
28 void setOutput(Print& out) override {
29 samples.setOutput(out);
30 ModemEncoder::setOutput(out); // writeSilence(), writeTone()
31 }
32
34 void setAudioInfo(audio_tools::AudioInfo info) override {
35 cfg.sample_rate = info.sample_rate;
36 cfg.channels = info.channels;
37 cfg.bits_per_sample = info.bits_per_sample;
38 if (is_active) begin();
39 }
40
41 audio_tools::AudioInfo audioInfo() override { return cfg; }
42
43 using audio_tools::AudioEncoder::begin;
44
47 cfg = config;
48 return begin();
49 }
50
51 bool begin() override {
52 is_active = false;
53 if (!cfg.isValid()) return false;
54 if (cfg.bits_per_sample != 16) {
55 LOGE("CSSEncoder: bits_per_sample must be 16, not %d",
56 (int)cfg.bits_per_sample);
57 return false;
58 }
59 if (cfg.channels < 1) {
60 LOGE("CSSEncoder: invalid number of channels: %d", (int)cfg.channels);
61 return false;
62 }
63 samples.begin(cfg.channels, cfg.volume);
64 if (!tones.begin(cfg, cfg.volume)) return false;
65 frame_len = 0;
66 frames = 0;
67 is_active = true;
68 return true;
69 }
70
71 void end() override {
73 is_active = false;
74 }
75
77 size_t write(const uint8_t* data, size_t len) override {
78 if (!is_active) return 0;
79 for (size_t j = 0; j < len; j++) {
80 frame[frame_len++] = data[j];
81 if (frame_len == cfg.max_frame) sendFrame();
82 }
83 return len;
84 }
85
88 bool endTransmission(size_t silence_samples = 0) override {
89 if (!is_active || frame_len == 0) return false;
90 sendFrame();
91 for (size_t j = 0; j < silence_samples; j++) samples.write(0.0f);
92 samples.flush();
93 return true;
94 }
95
97 bool isTransmitting() override { return frame_len > 0; }
98
100 float symbolRate() override { return cfg.symbolRate(); }
101
103 size_t frameCount() { return frames; }
104
106 CSSConfig& config() { return cfg; }
107
108 operator bool() override { return is_active; }
109
110 protected:
111 static constexpr float RAMP_SEC = 0.002f;
114 bool is_active = false;
116 int frame_len = 0;
117 size_t frames = 0;
118 // modulator
119 double phase = 0;
120 uint64_t symbol_index = 0;
121 size_t ramp = 0;
122 size_t total = 0, written = 0; // samples of the frame
123
124 void sendFrame() {
125 int sf = cfg.spreading_factor;
126 uint8_t header[CSSFrame::HEADER_BYTES];
128 int payload_len = CSSFrame::payloadBytes(frame_len, cfg.ecc_bytes);
129 uint8_t payload[CSSConfig::MAX_FRAME + 2 + RSCodec::MAX_ECC];
130 CSSFrame::payload(frame, frame_len, cfg.ecc_bytes, payload);
131 uint16_t header_symbols[CSSFrame::HEADER_BYTES * 8];
132 uint16_t payload_symbols[(CSSConfig::MAX_FRAME + 2 + RSCodec::MAX_ECC) * 8 / 5 + 1];
133 int nh = CSSFrame::toSymbols(header, sizeof(header), sf, header_symbols);
134 int np = CSSFrame::toSymbols(payload, payload_len, sf, payload_symbols);
135
136 symbol_index = 0;
137 phase = 0;
138 written = 0;
139 int nsymbols = cfg.preamble_symbols + 2 + nh + np;
140 total = llround(nsymbols * (double)cfg.symbolTime() * cfg.sample_rate);
141 ramp = RAMP_SEC * cfg.sample_rate;
142 for (int j = 0; j < cfg.preamble_symbols; j++) sendChirp(0, true);
143 for (int j = 0; j < 2; j++) sendChirp(0, false);
144 for (int j = 0; j < nh; j++) sendChirp(header_symbols[j], true);
145 for (int j = 0; j < np; j++) sendChirp(payload_symbols[j], true);
146 samples.flush();
147 frame_len = 0;
148 frames++;
149 }
150
152 static double sawIntegral(double x) {
153 double f = floor(x);
154 return f / 2 + (x - f) * (x - f) / 2;
155 }
156
160 void sendChirp(int value, bool up) {
161 double t_sym = cfg.symbolTime();
162 double t_start = symbol_index * t_sym;
163 uint64_t start = llround(t_start * cfg.sample_rate);
164 uint64_t stop = llround((symbol_index + 1) * t_sym * cfg.sample_rate);
165 symbol_index++;
166 double offset = (double)value / cfg.chips();
167 double cycles_per_unit = cfg.bandwidth * t_sym; // B * T
168 for (uint64_t j = start; j < stop; j++) {
169 double t = j / (double)cfg.sample_rate - t_start;
170 double u = t / t_sym; // 0 - 1 within the symbol
171 // phase = 2 pi * integral of (f0 + B * frac(...)) dt
172 double sweep = up ? sawIntegral(offset + u) - sawIntegral(offset)
173 : -sawIntegral(-u);
174 double ph = phase + 2.0 * M_PI * (cfg.lowFreq() * t + cycles_per_unit * sweep);
175 float gain = 1.0f;
176 if (written < ramp) gain = 0.5f - 0.5f * cosf((float)M_PI * written / ramp);
177 if (total - written <= ramp)
178 gain = 0.5f - 0.5f * cosf((float)M_PI * (total - written) / ramp);
179 samples.write(gain * (float)sin(ph));
180 written++;
181 }
182 // phase at the end of the symbol: the mean frequency is the center
183 phase = fmod(phase + 2.0 * M_PI * cfg.center_freq * t_sym, 2.0 * M_PI);
184 }
185};
186
187} // namespace tinymodem
size_t frames
Definition CSSEncoder.h:117
bool isTransmitting() override
true while data is waiting to be sent
Definition CSSEncoder.h:97
bool begin() override
Definition CSSEncoder.h:51
CSSConfig cfg
Definition CSSEncoder.h:112
uint8_t frame[CSSConfig::MAX_FRAME]
Definition CSSEncoder.h:115
size_t ramp
Definition CSSEncoder.h:121
int frame_len
Definition CSSEncoder.h:116
size_t write(const uint8_t *data, size_t len) override
Collects the data: a frame is sent when max_frame bytes are available.
Definition CSSEncoder.h:77
size_t written
Definition CSSEncoder.h:122
bool endTransmission(size_t silence_samples=0) override
Definition CSSEncoder.h:88
void sendChirp(int value, bool up)
Definition CSSEncoder.h:160
void sendFrame()
Definition CSSEncoder.h:124
double phase
Definition CSSEncoder.h:119
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the sample rate and number of channels of the generated audio.
Definition CSSEncoder.h:34
void end() override
Definition CSSEncoder.h:71
bool begin(CSSConfig config)
Starts the processing with the indicated configuration.
Definition CSSEncoder.h:46
static constexpr float RAMP_SEC
Definition CSSEncoder.h:111
CSSConfig & config()
The configuration which was provided in begin().
Definition CSSEncoder.h:106
float symbolRate() override
Symbols per second.
Definition CSSEncoder.h:100
uint64_t symbol_index
Definition CSSEncoder.h:120
void setOutput(Print &out) override
Definition CSSEncoder.h:28
bool is_active
Definition CSSEncoder.h:114
audio_tools::AudioInfo audioInfo() override
Definition CSSEncoder.h:41
SampleOutput samples
Definition CSSEncoder.h:113
size_t frameCount()
Number of sent frames.
Definition CSSEncoder.h:103
CSSConfig defaultConfig()
Provides a default configuration: css7 (SF 7, 1000 Hz).
Definition CSSEncoder.h:26
static double sawIntegral(double x)
Integral of frac(x): the normalized frequency of a chirp is a saw tooth.
Definition CSSEncoder.h:152
CSSEncoder(Print &out)
Definition CSSEncoder.h:23
size_t total
Definition CSSEncoder.h:122
static constexpr int HEADER_BYTES
Definition CSS.h:154
static int toSymbols(const uint8_t *data, int len, int sf, uint16_t *out)
Converts bytes into symbols.
Definition CSS.h:196
static void payload(const uint8_t *data, int len, int ecc, uint8_t *out)
Payload code word: data, CRC-16 and parity.
Definition CSS.h:176
static int payloadBytes(int len, int ecc)
bytes of the payload code word
Definition CSS.h:160
static void header(int len, uint8_t *out)
Header code word: length and parity.
Definition CSS.h:163
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
static constexpr int MAX_ECC
Definition ReedSolomon.h:24
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
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
Configuration of the chirp spread spectrum modem (CSSEncoder, CSSDecoder), which works like LoRa in t...
Definition CSS.h:31
static constexpr int MAX_FRAME
max payload of a frame
Definition CSS.h:118