File: JS8Submode.cpp

package info (click to toggle)
js8call 2.5.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,720 kB
  • sloc: cpp: 562,655; sh: 898; python: 132; ansic: 102; makefile: 4
file content (277 lines) | stat: -rw-r--r-- 11,072 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#include "JS8Submode.h"
#include "JS8_Include/commons.h"
#include "JS8_Main/varicode.h"
#include <QLoggingCategory>
#include <concepts>

/******************************************************************************/
// Private Implementation
/******************************************************************************/

Q_DECLARE_LOGGING_CATEGORY(js8submode_js8)

namespace JS8::Submode {
namespace {
// std::floor doesn't become constexpr until C++23; until then, our own
// implementation. We only use this for computation of the number of
// frames needed for a submode, so it doesn't need to be complicated.

template <std::floating_point T> constexpr int floor(T const v) {
    auto const i = static_cast<int>(v);
    return v < i ? i - 1 : i;
}

// Ensure that our implementation works as expected.

static_assert(floor(0.0) == 0);
static_assert(floor(0.499999) == 0);
static_assert(floor(0.5) == 0);
static_assert(floor(0.999999) == 0);
static_assert(floor(1.0) == 1);
static_assert(floor(123.0) == 123);
static_assert(floor(123.4) == 123);
static_assert(floor(-0.499999) == -1);
static_assert(floor(-0.5) == -1);
static_assert(floor(-0.999999) == -1);
static_assert(floor(-1.0) == -1);
static_assert(floor(-123.0) == -123);
static_assert(floor(-123.4) == -124);

// Data that describes a JS8 submode. Anything here should be able to be
// completely determined at compile time, i.e., any instance of this is
// just constant data.

class Data {
  public:
    // Constructor; in addition to the basics provided by the constructor
    // parameters, we'll determine various convenience constants in order
    // to simplify calling code. These derived values depend only on the
    // JS8_NUM_SYMBOLS and RX_SAMPLE_RATE definitions, and therefore can
    // be entirely computed at compile time.

    constexpr Data(const char *const name,
                   unsigned int const samplesForOneSymbol,
                   unsigned int const startDelayMS, unsigned int const period,
                   Costas::Type const costas, int const rxSNRThreshold,
                   int const rxThreshold = 10)
        : m_name(name), m_samplesForOneSymbol(samplesForOneSymbol),
          m_startDelayMS(startDelayMS), m_period(period), m_costas(costas),
          m_rxSNRThreshold(rxSNRThreshold), m_rxThreshold(rxThreshold),
          m_samplesForSymbols(JS8_NUM_SYMBOLS * samplesForOneSymbol),
          m_bandwidth(8 * JS8_RX_SAMPLE_RATE / samplesForOneSymbol),
          m_samplesPerPeriod(JS8_RX_SAMPLE_RATE * period),
          m_toneSpacing(JS8_RX_SAMPLE_RATE / (double)samplesForOneSymbol),
          m_samplesNeeded(
              floor(m_samplesForSymbols +
                    (0.5 + startDelayMS / 1000.0) * JS8_RX_SAMPLE_RATE)),
          m_dataDuration(m_samplesForSymbols / (double)JS8_RX_SAMPLE_RATE),
          m_txDuration(m_dataDuration + startDelayMS / 1000.0) {}

    // Inline accessors

    constexpr const char *name() const { return m_name; }
    constexpr unsigned int samplesForOneSymbol() const {
        return m_samplesForOneSymbol;
    }
    constexpr unsigned int startDelayMS() const { return m_startDelayMS; }
    constexpr unsigned int period() const { return m_period; }
    constexpr Costas::Type costas() const { return m_costas; }
    constexpr int rxSNRThreshold() const { return m_rxSNRThreshold; }
    constexpr int rxThreshold() const { return m_rxThreshold; }
    constexpr int samplesForSymbols() const { return m_samplesForSymbols; }
    constexpr int bandwidth() const { return m_bandwidth; }
    constexpr int samplesPerPeriod() const { return m_samplesPerPeriod; }
    constexpr int samplesNeeded() const { return m_samplesNeeded; }
    constexpr double dataDuration() const { return m_dataDuration; }
    constexpr double toneSpacing() const { return m_toneSpacing; }
    constexpr double txDuration() const { return m_txDuration; }

  private:
    // Data members ** ORDER DEPENDENCY **

    const char *m_name;
    unsigned int m_samplesForOneSymbol;
    unsigned int m_startDelayMS;
    unsigned int m_period;
    Costas::Type m_costas;
    int m_rxSNRThreshold;
    int m_rxThreshold;
    int m_samplesForSymbols;
    int m_bandwidth;
    int m_samplesPerPeriod;
    double m_toneSpacing;
    int m_samplesNeeded;
    double m_dataDuration;
    double m_txDuration;
};

// Data for known submodes. Normal mode uses the old Costas Array
// definition; all other modes use the new one. Note that as of this
// writing, Ultra is a known, but unused, submode; we handle it here
// nevertheless, but it's in general disabled in the calling code.

constexpr Data Normal = {
    "NORMAL",        JS8A_SYMBOL_SAMPLES,    JS8A_START_DELAY_MS,
    JS8A_TX_SECONDS, Costas::Type::ORIGINAL, -24};
constexpr Data Fast = {"FAST",
                       JS8B_SYMBOL_SAMPLES,
                       JS8B_START_DELAY_MS,
                       JS8B_TX_SECONDS,
                       Costas::Type::MODIFIED,
                       -22,
                       16};
constexpr Data Turbo = {"TURBO",
                        JS8C_SYMBOL_SAMPLES,
                        JS8C_START_DELAY_MS,
                        JS8C_TX_SECONDS,
                        Costas::Type::MODIFIED,
                        -20,
                        32};
constexpr Data Slow = {
    "SLOW",          JS8E_SYMBOL_SAMPLES,    JS8E_START_DELAY_MS,
    JS8E_TX_SECONDS, Costas::Type::MODIFIED, -28};
constexpr Data Ultra = {"ULTRA",
                        JS8I_SYMBOL_SAMPLES,
                        JS8I_START_DELAY_MS,
                        JS8I_TX_SECONDS,
                        Costas::Type::MODIFIED,
                        -18,
                        50};

// Given a submode, return data for it, or, if we don't have any idea
// what the caller is talking about, throw.
//
// Note that the original code in all cases did its best to just carry
// on in the event of an invalid submode, e.g., by returning 0, etc.,
// but that approach will in general lead to things like division by
// zero in computeCycleForDecode(), below, so either way we're going
// to end up with a runtime error, and it seems preferable that it's
// an informative one.
//
// Note that the Varicode::SubModeType enum is not dense, so we can't
// just do direct indexed access here.

constexpr Data const &data(int const submode) {
    switch (submode) {
    case Varicode::JS8CallNormal:
        return Normal;
    case Varicode::JS8CallFast:
        return Fast;
    case Varicode::JS8CallTurbo:
        return Turbo;
    case Varicode::JS8CallSlow:
        return Slow;
    case Varicode::JS8CallUltra:
        return Ultra;
    default: {
        throw error{QObject::tr("Invalid JS8 submode %1").arg(submode)};
    }
    }
};

class ListDataAsDebugOutput {
  private:
    inline void list_one_mode(Data data) {
        qCDebug(js8submode_js8)
            << "\nname               " << data.name() << "\nsamplesForOneSymbol"
            << data.samplesForOneSymbol() << "\nstartDelayMS       "
            << data.startDelayMS() << "\nperiod             " << data.period()
            << "\ncostas             "
            << (data.costas() == Costas::Type::MODIFIED
                    ? "MODIFIED"
                    : (data.costas() == Costas::Type::ORIGINAL ? "ORIGINAL"
                                                               : "????"))
            << "\nrxSNRThreshold     " << data.rxSNRThreshold()
            << "\nrxThreshold        " << data.rxThreshold()
            << "\nsamplesForSymbols  " << data.samplesForSymbols()
            << "\nbandwidth          " << data.bandwidth()
            << "\nsamplesPerPeriod   " << data.samplesPerPeriod()
            << "\nsamplesNeeded      " << data.samplesNeeded()
            << "\ndataDuration       " << data.dataDuration()
            << "\ntoneSpacing        " << data.toneSpacing()
            << "\ntxDuration         " << data.txDuration() << "\n";
    }

  public:
    inline ListDataAsDebugOutput() {
        list_one_mode(Slow);
        list_one_mode(Normal);
        list_one_mode(Fast);
        list_one_mode(Turbo);
        // list_one_mode(Ultra);
    }
};

static ListDataAsDebugOutput list_data_as_debug_output =
    ListDataAsDebugOutput{};
} // namespace
} // namespace JS8::Submode

/******************************************************************************/
// Public Implementation
/******************************************************************************/

namespace JS8::Submode {
// Submode name inquiry function; return a translated value, if there is
// a translated value, otherwise, the untranslated mode name.

QString name(int const submode) { return QObject::tr(data(submode).name()); }

// Basic submode numeric inquiry functions, i.e., parameterized only by
// the submode, returning constant data.

unsigned int bandwidth(int const submode) { return data(submode).bandwidth(); }
Costas::Type costas(int const submode) { return data(submode).costas(); }
unsigned int samplesPerPeriod(int const submode) {
    return data(submode).samplesPerPeriod();
}
unsigned int samplesForSymbols(int const submode) {
    return data(submode).samplesForSymbols();
}
unsigned int samplesNeeded(int const submode) {
    return data(submode).samplesNeeded();
}
unsigned int period(int const submode) { return data(submode).period(); }
int rxSNRThreshold(int const submode) { return data(submode).rxSNRThreshold(); }
int rxThreshold(int const submode) { return data(submode).rxThreshold(); }
unsigned int startDelayMS(int const submode) {
    return data(submode).startDelayMS();
}
unsigned int samplesForOneSymbol(int const submode) {
    return data(submode).samplesForOneSymbol();
}
double toneSpacing(int const submode) { return data(submode).toneSpacing(); }
double dataDuration(int const submode) { return data(submode).dataDuration(); }
double txDuration(int const submode) { return data(submode).txDuration(); }

// Compute which cycle we are currently in based on submode frames per cycle
// and current k position.

int computeCycleForDecode(int const submode, int const k) {
    int const maxFrames = JS8_RX_SAMPLE_SIZE;
    int const cycleFrames = samplesPerPeriod(submode);

    return (k / cycleFrames) %        // we mod here so we loop
           (maxFrames / cycleFrames); // back to zero correctly
}

// Compute an alternate cycle offset by a specific number of frames e.g.,
// if we want the 0 cycle to start at second 5, we'd provide an offset of
// (5 * RX_SAMPLE_RATE).

int computeAltCycleForDecode(int const submode, int const k,
                             int const offsetFrames) {
    int const altK = k - offsetFrames;

    return computeCycleForDecode(submode,
                                 altK < 0 ? altK + JS8_RX_SAMPLE_SIZE : altK);
}

double computeRatio(int const submode, double const period) {
    return (period - data(submode).dataDuration()) / period;
}
} // namespace JS8::Submode

Q_LOGGING_CATEGORY(js8submode_js8, "js8submode.js8", QtWarningMsg)

/******************************************************************************/