#include "JS8Submode.h" #include "JS8_Include/commons.h" #include "JS8_Main/varicode.h" #include #include /******************************************************************************/ // 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 constexpr int floor(T const v) { auto const i = static_cast(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) /******************************************************************************/