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)
/******************************************************************************/
|